🔰 Starter tutorial

Installing the viewer

The viewer is the most accessible tool for working with the framework. The simplest way to grab it is to download the last pre-build of it on the Releases page.

An alternative to this is to build the development environment without installing anything on the system, more specifically: the first method of the installation guide. In a nutshell: ./configure.py && make creates a complete Python-based working environment. Within that virtual environment, the ngl-viewer command is available.

Creating a simple scene in Python

Edit a new script such as mydemo.py and add the following code:

Hello World!
Code
Graph
import pynopegl as ngl


@ngl.scene()
def hello_world(cfg: ngl.SceneCfg):
    cfg.aspect_ratio = (1, 1)

    return ngl.Text("Hello World!")
graph

Loading that script in the viewer should provide the same rendering observed here.

In this first example, all we did was to declare a function that returns a single node (the Text node). The @ngl.scene() decorator is used to make it discoverable by the viewer. The function receives a configuration of type SceneCfg which gives us access to various metadata of the scene such as aspect_ratio, duration or framerate (for both reading and sometimes writing).

Try to modify the text in the script: the viewer should automatically update the preview.

Making a simple composition

To add a background to our scene, we need to create a composition where we first draw that background, then our text. For the background, we can pick one of the proposition from the renders howtos. For the composition itself, we can rely on the Group node to split the graph in two branches:

Simple background and text composition
Code
Graph
import pynopegl as ngl


@ngl.scene()
def bg_fg_composition(cfg: ngl.SceneCfg):
    cfg.aspect_ratio = (1, 1)

    bg = ngl.RenderGradient(color0=(0, 0.5, 0.5), color1=(1, 0.5, 0))
    fg = ngl.Text("Hello World!", bg_opacity=0)
    return ngl.Group(children=[bg, fg])
graph

Looking at the graph tab should clarify the structure constructed here.

You may have noticed that we did change the Text.bg_opacity parameter. Try to play with that value (somewhere between 0 and 1) to see how it impacts the scene.

Animating values

In many cases, node parameters that accept constant values also accept nodes. We can see that it’s the case with RenderGradient parameters (look for the node flag), and notably color1 and color2 we’re currently using in our scene. Accepting nodes means we could animate these parameters:

Animating the background colors
Code
Graph
import pynopegl as ngl


@ngl.scene()
def animated(cfg: ngl.SceneCfg):
    cfg.aspect_ratio = (1, 1)
    cfg.duration = 3

    d = cfg.duration

    color0_animkf = [
        ngl.AnimKeyFrameColor(time=0, color=(0.0, 0.5, 0.5)),
        ngl.AnimKeyFrameColor(time=d / 2, color=(1.0, 0.0, 1.0)),
        ngl.AnimKeyFrameColor(time=d, color=(0.0, 0.5, 0.5)),
    ]

    color1_animkf = [
        ngl.AnimKeyFrameColor(time=0, color=(1.0, 0.5, 0.5)),
        ngl.AnimKeyFrameColor(time=d / 2, color=(1.0, 1.0, 0.5)),
        ngl.AnimKeyFrameColor(time=d, color=(1.0, 0.5, 0.5)),
    ]

    color0 = ngl.AnimatedColor(keyframes=color0_animkf)
    color1 = ngl.AnimatedColor(keyframes=color1_animkf)

    bg = ngl.RenderGradient(color0=color0, color1=color1)
    fg = ngl.Text("Hello World!", bg_opacity=0)
    return ngl.Group(children=[bg, fg])
graph

color0_animkf and color1_animkf define time key frames. Each of these time key frames associate a time with a value, for which the engine interpolates the intermediate values for a given time. The interpolations are linear by default, but you can customize that. More specifically, try to change the easing parameter of the AnimkeyFrameColor nodes (but the first one) to adjust the rhythm of the scene.

You may also have noticed in the RenderGradient documentation that the positions of the gradients could be changed. You could try to do that with the help of an AnimatedVec2 and AnimKeyFrameVec2.

Transforming geometries

The object geometries can be modified using various transformation nodes. You can find a few examples in the transforms howtos. They can be nested just like functions, so if we want to scale down then rotate, we would express it with Rotate(Scale(target)). Let’s do that to our text node:

Apply 2 transforms to the text
Code
Graph
import pynopegl as ngl


@ngl.scene()
def transforms(cfg: ngl.SceneCfg):
    cfg.aspect_ratio = (1, 1)
    cfg.duration = 3

    d = cfg.duration

    color0_animkf = [
        ngl.AnimKeyFrameColor(time=0, color=(0.0, 0.5, 0.5)),
        ngl.AnimKeyFrameColor(time=d / 2, color=(1.0, 0.0, 1.0)),
        ngl.AnimKeyFrameColor(time=d, color=(0.0, 0.5, 0.5)),
    ]

    color1_animkf = [
        ngl.AnimKeyFrameColor(time=0, color=(1.0, 0.5, 0.5)),
        ngl.AnimKeyFrameColor(time=d / 2, color=(1.0, 1.0, 0.5)),
        ngl.AnimKeyFrameColor(time=d, color=(1.0, 0.5, 0.5)),
    ]

    color0 = ngl.AnimatedColor(keyframes=color0_animkf)
    color1 = ngl.AnimatedColor(keyframes=color1_animkf)

    bg = ngl.RenderGradient(color0=color0, color1=color1)
    fg = ngl.Text("Hello World!", bg_opacity=0)

    rotate_animkf = [
        ngl.AnimKeyFrameFloat(time=0, value=15),
        ngl.AnimKeyFrameFloat(time=d / 2, value=-15, easing="exp_in"),
        ngl.AnimKeyFrameFloat(time=d, value=15, easing="exp_out"),
    ]

    scaled_fg = ngl.Scale(fg, factors=(0.7, 0.7, 0.7))
    rotated_fg = ngl.Rotate(scaled_fg, angle=ngl.AnimatedFloat(rotate_animkf))

    return ngl.Group(children=[bg, rotated_fg])
graph

Again, looking at the graph tab may clarify the structure constructed here.

Exposing parameters

Some node parameters have the live flag. This means we can expose a live-control up to the GUI. Try for example to add live_id="my_text" to the Text node and see how it appears in the viewer.

Controlling the visibility of objects

For this last subsection, we are going to see how to control when things are visible or not. We will be using a TimeRangeFilter to make a shape shortly appears behind the text.

Time controlled shape
Code
Graph
import pynopegl as ngl


@ngl.scene()
def timeranges(cfg: ngl.SceneCfg):
    cfg.aspect_ratio = (1, 1)
    cfg.duration = 3

    d = cfg.duration

    color0_animkf = [
        ngl.AnimKeyFrameColor(time=0, color=(0.0, 0.5, 0.5)),
        ngl.AnimKeyFrameColor(time=d / 2, color=(1.0, 0.0, 1.0)),
        ngl.AnimKeyFrameColor(time=d, color=(0.0, 0.5, 0.5)),
    ]

    color1_animkf = [
        ngl.AnimKeyFrameColor(time=0, color=(1.0, 0.5, 0.5)),
        ngl.AnimKeyFrameColor(time=d / 2, color=(1.0, 1.0, 0.5)),
        ngl.AnimKeyFrameColor(time=d, color=(1.0, 0.5, 0.5)),
    ]

    color0 = ngl.AnimatedColor(keyframes=color0_animkf)
    color1 = ngl.AnimatedColor(keyframes=color1_animkf)

    bg = ngl.RenderGradient(color0=color0, color1=color1)
    fg = ngl.Text("Hello World!", bg_opacity=0)

    rotate_animkf = [
        ngl.AnimKeyFrameFloat(time=0, value=15),
        ngl.AnimKeyFrameFloat(time=d / 2, value=-15, easing="exp_in"),
        ngl.AnimKeyFrameFloat(time=d, value=15, easing="exp_out"),
    ]

    scaled_fg = ngl.Scale(fg, factors=(0.7, 0.7, 0.7))
    rotated_fg = ngl.Rotate(scaled_fg, angle=ngl.AnimatedFloat(rotate_animkf))

    shape = ngl.RenderColor(color=(0.3, 0.3, 0.3), geometry=ngl.Circle(npoints=5))
    timed_shape = ngl.TimeRangeFilter(shape, start=1, end=2)

    return ngl.Group(children=[bg, timed_shape, rotated_fg])
graph

Diving into the documentation

From here, if you you’re looking at a specific area, you may want to look at the how-to guides.

If you need to understand the why of certain design decisions or limitations, the discussions and explanations section will come to an help.

Finally, in every situation, you will feel the need to check out the reference documentation for austere but exhaustive information, and in particular, all the node definitions.