Simple Rendering
================

The full source code for this part of the tutorial is available in the file
`root/tutorials/s3d/simple.bs`. You can run it using `storm -f tutorials.s3d.simple`. The code
itself is fairly well-documented, so it is possible to read it and understand what is happening. The
rest of this page gives an overview of why all parts are needed, and the role they have in the
complete rendering pipeline.

If you want to follow along, you can create a `.bs` file in the current working directory and run it
on the command line (`storm <file>.bs`) as long as you use the name `main` for the function that is
named `simple` below. You also need to add `use tutorials:s3d` to the use statements at the start of
your file.


Geometry
--------

The goal of this example is to render a multi-colored cube on the screen. The first step in this
process is to define a vertex type to *store* the cube itself. To do this, we define a new vertex
type called `SimpleVertex`. For this example, we only need a position and a color for each vertex.

```bs
use ui;
use ui:s3d;
use core:geometry;
use graphics;
// Needed if you create your own file, for SamplePainter3D later on.
// use tutorials:s3d;

vertex SimpleVertex {
    Vector pos;
    Color color;
}
```

As can be seen above, the 3D library allows using normal types from the `core:geometry` and
`graphics` libraries to do this. As we will se later, we can even use these types in shaders.


Once we have a vertex type, we can define the geometry and store it inside a `VertexBuffer` and an
`IndexBuffer`. These will eventually end up in our `Painter3D` class, so we will create them there
right away.

```bsfragment:SFile:use=ui.s3d
class SimplePainter extends SimplePainter3D {
    // Vertex buffer, containing the vertices of the cube.
    VertexBuffer<SimpleVertex> vertices;

    // Index buffer. Lets us define faces without duplicating vertices.
    IndexBuffer indices;

    // The constructor.
    init() {
        init {}

        // Create a cube. First, create one vertex for each corner.
        vertices << SimpleVertex(Vector(-1, 1, 1), red);
        vertices << SimpleVertex(Vector(1, 1, 1), green);
        vertices << SimpleVertex(Vector(-1, -1, 1), blue);
        vertices << SimpleVertex(Vector(1, -1, 1), yellow);
        vertices << SimpleVertex(Vector(-1, 1, -1), red);
        vertices << SimpleVertex(Vector(1, 1, -1), green);
        vertices << SimpleVertex(Vector(-1, -1, -1), blue);
        vertices << SimpleVertex(Vector(1, -1, -1), yellow);

        // Then connect them using the index buffer.

        // Back face:
        indices << 0 << 2 << 1;
        indices << 1 << 2 << 3;

        // Top face:
        indices << 0 << 1 << 4;
        indices << 1 << 5 << 4;

        // Bottom face:
        indices << 2 << 6 << 3;
        indices << 3 << 6 << 7;

        // Left face:
        indices << 0 << 4 << 2;
        indices << 4 << 6 << 2;

        // Right face:
        indices << 1 << 3 << 5;
        indices << 5 << 3 << 7;

        // Front face:
        indices << 4 << 5 << 6;
        indices << 5 << 7 << 6;
    }
}
```

Above, we first add one vertex for each corner of the cube (using the automatically generated
constructor). We also give each corner an arbitrary color. After that we populate the index buffer
with indices into the vertex buffer. This way we can use the same vertex as a part of more than one
triangle, without duplicating the vertices.


Basic Rendering
---------------

To render things, we can overload the `render` function in our painter. Much like rendering 2D
graphics, the `render` function is called by the UI library to render new frames. The return value
from the `render` function determines whether the UI library will render frames continuously (e.g.
for an animation) or not. We can try this out by overriding the `render` function and create a
window that uses our painter using the `runSample` function in the `common.bs` file.

```bs
class SimplePainter extends SimplePainter3D {
    // ...

    // Render a frame.
    Bool render(Size size, Graphics3D g) : override {
        print("Render a frame!");
        false; // no need for more frames
    }
}

// Note: call this function 'main' if you are writing code in a separate file.
void simple() {
    runSample("Simple 3D", SimplePainter());
}
```

If we run the program at this point, we will se a blank window and a the text `Render a frame!`
printed to standard output whenever the window needs to be repainted.

As with 2D rendering, we need to use the [stormname:ui.Graphics3D] instance to actually render
things. The `Graphics3D` class has a few member functions that are useful to call at the start of
rendering, such as `clearDepth` to clear the depth buffer, and `fill` to fill the entire screen with
a background color. If we call them in our `render` function we can see control the background color
of the window.

```bsfragment:SClassBody
Bool render(Size size, Graphics3D g) : override {
    // Clear the depth buffer.
    g.clearDepth();

    // Set the background color to something interesting!
    g.fill(green);

    false; // No need for animations.
}
```

However, if you inspect the members of the [stormname:ui.Graphics3D] object (e.g.
[here](md:/Library_Reference/UI_Library/3D_Rendering)), you will see that it does not provide any
way to draw vertices. To achieve that, we need to define a pipeline, a few additional vertex types,
and two shaders.

Pipelines and Intermediate Vertex Types
---------------------------------------

We start with the pipeline. A pipeline describes how data flows through the rendering pipeline: what
data is unique for each vertex, what data is common between all vertices, how should this data be
interpolated across the rendered triangles, and so on.

In this case, we want to render a set of `SimpleVertex` vertices in a vertex buffer (along with an
index buffer, but that is not too important at this time). However, we also need to transform them
from model space into screen space. In essence, we want to apply a transform to them. As such, we
can define our pipeline like below:

```bsfragment:SFile:use=ui.s3d
pipeline SimplePipeline {
    // The flow of data through the pipeline:
    // (input parameters) -> vertex shader output -> pixel shader output
    data (SimpleVertex point) -> SimpleIntermediate -> SimpleOutput;

    // Additional members become uniforms automatically, but are set globally rather than passed to
    // the function.
    Transform camera;
}
```

The pipeline we defined is similar in structure to a class. Any variables (e.g. `camera`) will
become members of the class. What is special for a pipeline is that these will also become available
to shaders in the pipeline. They are often called *uniforms* since they have the same value for all
vertices.

Another difference is that a pipeline needs a `data` statement. This specifies how the pipeline
operates. One can think of a pipeline as a function that transforms data on the GPU. What makes it a
bit special is that it parallelizes its work on the GPU by operating on an array of its inputs in
parallel (this is one part of why GPUs are fast - the programming model makes it almost trivial to
parallellize the execution!). In this case, the pipeline accepts an array of `SimpleVertex` as an
input. The first stage is the vertex shader, and in this case the vertex shader will transform each
`SimpleVertex` into a `SimpleIntermediate` vertex. After that, the rendering hardware will
interpolate the `SimpleIntermediate` vertex type across all triangles, and execute the pixel shader
for each pixel. The pixel shader therefore accepts `SimpleIntermediate` as an input and outputs a
`SimpleOutput`, which the rendering hardware then uses to determine the final color of that pixel on
the screen.

The next step is therefore to define `SimpleIntermediate` and `SimpleOutput`. Since the rendering
hardware will use the information in `SimpleIntermediate` to construct and render a triangle on the
screen, it needs to contain at least a screen-space coordinate. Apart from that, we want it to
contain the color of the rendered triangle. As such, we can define it as follows:

```bsfragment:SFile:use=ui.s3d
vertex SimpleIntermediate {
    // Screen position.
    Vector4 pos : position, perspective;

    // Vertex color.
    Color color : perspective;
}
```

Note that we need to annotate at least some of the members of `SimpleIntermediate`. To let the
rendering hardware know which element is the screen-space position, we need to mark one member as
`position`. We may also specify how each vertex compontent should be interpolated across the 3D
geometry. By default, perspective correct interpolation is used (i.e. the `perspective` keyword). As
such, we don't need to add `perspective` on the members. We chose to do it for clarity here. Other
interpolation options are `linear` for a linear interpolation, and `none` for no interpolation (it
simply uses the value from the first vertex).

It is worth noting that the position needs to be of the type `Vector4`, since the fourth component
(`w`) is used to perform perspective correct interpolation, for example.


The situation is similar for the `SimpleOutput` vertex type. It needs to contain at least one
element marked `color` or `output` (they are synonyms) to let the rendering hardware know which
member should be used for the screen color. As such, we can define it as follows:


```bsfragment:SFile:use=ui.s3d
vertex SimpleOutput {
    // Output color.
    Color color : color;
}
```

Shaders
-------

Now that we have defined a pipeline, we need to describe how to transform data between the different
vertex types we just defined. We do this using *shaders*, which are essentially small programs that
execute (often in parallel) on the GPU.

We need two shaders: a vertex shader and a pixel shader. The vertex shader is executed once for each
vertex in the vertex buffer. As we have defined our pipeline, its job is to convert a `SimpleVertex`
into a `SimpleIntermediate` vertex. As a part of this transform, it should transform the position of
the vertex from model space to world space. The pixel shader then receives a `SimpleIntermediate`
and determines what color the corresponding pixel should have.

In the 3D library, we can implement the shaders in a stripped-down version of Basic Storm. That way
Storm is able to type-check the shader before it executes. It also cross-compiles the shader into a
suitable shader language based on your current rendering backend (e.g. GLSL or HLSL).

We start by implementing the vertex shader as follows:

```bsfragment:SFile:use=ui.s3d
vertex shader for SimplePipeline: SimpleIntermediate simpleVertexShader(SimpleVertex point) {
    // Note: we can use the 'camera' uniform here.
    Vector4 pos = Vector4(point.pos) * camera;

    // Note: 'return' is optional, just as in Basic Storm.
    return SimpleIntermediate(pos, point.color);
}
```

As can be seen above, pixel and vertex shaders are tied to a specific pipeline. This makes it
possible to use uniforms defined in the pipeline. In this case, the `camera` transform. The vertex
shader is quite simple in this case. The first line inside the shader casts the
[stormname:core.geometry.Vector] into a [stormname:core.geometry.Vector4] and applies the `camera`
transformation to it. The intent is that we set up the camera rotation to transform model space into
camera space (i.e. screen coordinates). The second line of code then uses the variable `pos` to
create a `SimpleIntermediate` instance with the position and the original vertex color. As in Basic
Storm, if `return` is omitted, the value of the last statement is returned automatically.

Since we do not do any lightning, the pixel shader can simply use the `color` value of the
`SimpleIntermediate` vertex it receives (remember: the rendering hardware interpolates it for us).
As such, we can implement the pixel shader as follows:

```bsfragment:SFile:use=ui.s3d
pixel shader for SimplePipeline: SimpleOutput simplePixelShader(SimpleIntermediate point) {
    SimpleOutput(point.color);
}
```

Note that we utilize the fact that `return` is optional in the implementation above.


Rendering Geometry
------------------

Now we have everything needed to render our cube, we just need to connect the pieces together.

The first thing we need to do is to create an instance of the pipeline. We add it as a member
variable to the `SimplePainter` class we created earlier. We also need to initialize it in the
`init` block of the class.

```bs
class SimplePainter extends SamplePainter3D {
    // The pipeline.
    SimplePipeline pipeline;

    // ...

    init() {
        init {
            pipeline = SimplePipeline:create(simpleVertexShader, simplePixelShader);
        }

        // ...
    }
}
```

As can be seen above, creating a pipeline requires us to provide a vertex shader and a pixel shader.
The type system will check that the shaders are actually associated with the pipeline we try to
create.

Now we can use the pipeline in the `render` function to render geometry to the screen. The
`pipeline` has a `render` function that accepts the inputs we specified previously. In this case, we
use the overload that accepts a `Graphics3D`, a `IndexBuffer`, and a `VertexBuffer<SimpleVertex>`.
Note that the last parameter corresponds to what we specified after `data` in the pipeline! As such,
we can modify the `render` function in our class as follows:

```bsfragment:SClassBody
Bool render(Size size, Graphics3D g) : override {
    // Clear the depth buffer and the background color.
    g.clearDepth();
    g.fill(black);

    // Render using the pipeline.
    pipeline.render(g, indices, vertices);

    false; // No need for animations.
}
```

If we run the program at this point, we will still not see anything useful. The reason for this is
that we have not yet set `camera` to a useful transform. Since the uniforms are initialized to 0 by
default, all vertices will simply be transformed to the coordinate (0, 0, 0), which is not very
interesting to look at.

Luckily, the fix is fairly easy. We just need to create a suitable transform and assign it to
`camera`. In this case, we can do the following:

```bsfragment:SClassBody
Bool render(Size size, Graphics3D g) : override {
    // Clear the depth buffer and the background color.
    g.clearDepth();
    g.fill(black);

    // Update the transform.
    Transform cameraPos = rotateX(-30 deg) * translate(Vector(0, 0, 3));
    pipeline.camera = cameraPos * perspectiveProject(100 deg, size.aspect, 0.1, 100);

    // Render using the pipeline.
    pipeline.render(g, indices, vertices);

    false; // No need for animations.
}
```

We first set `cameraPos` to a transform that rotates everything -30 degrees around the X axis, and
then translates everything 3 units away in the Z direction. This corresponds to a camera that looks
down on our cube (due to the rotation), and sits 3 units away in the Z direction. In the final code,
this part of the transform is stores as a member of the class so that we don't have to re-compute it
every frame.

The second line appends a perspective projection to everything. This is what causes the final result
to appear to be 3D. The parameters to `perspectiveProject` are: (1) the vertical FOV, (2) the aspect
ratio of the screen, (3) the near plane's Z coordinate, and (4) the far plane's Z coordinate.

This concatenated transform is then assigned to `camera` in the pipeline. This makes it so that the
data is uploaded to the GPU as appropriate. It is worth noting that it is *not* necessary to re-set
the transform every time `render` is called. Rather, the uniform retains its value across multiple
calls to `render`.

At this point, we can run the program and now we will see a stationary cube on the screen!


Simple Animations
-----------------

To make the end result a bit more interesting (and to make it easier to see the back of the cube),
we want to make the cube rotate. To do this, we use the `animationTime` in the `SamplePainter3D`
class. This function simply returns a `Float` value that goes from 0 to 1 in the specified interval.
We can observe its behavior by updating our `render` function as follows:

```bsfragment:SClassBody
Bool render(Size size, Graphics3D g) : override {
    // Get the animation time.
    Float time = animationTime(5 s);
    print("Time: ${time}");

    // ...

    true; // We wish to do animations now!
}
```

If you run the program now, you will see the output `Time: ...` and that the value increases from 0
to 1 every 5 seconds. Let's use this to make the cube move!

To make the cube rotate, we can simply add another transform before we apply the camera position and
the perspective projection like below:

```bsfragment:SClassBody
Bool render(Size size, Graphics3D g) : override {
    // Get the animation time.
    Float time = animationTime(5 s);
    print("Time: ${time}");

    // Clear the depth buffer and the background color.
    g.clearDepth();
    g.fill(black);

    // Update the transform.
    Transform cameraPos = rotateX(-30 deg) * translate(Vector(0, 0, 3));
    pipeline.camera = rotateY(360 deg * time) * cameraPos * perspectiveProject(100 deg, size.aspect, 0.1, 100);

    // Render using the pipeline.
    pipeline.render(g, indices, vertices);

    true; // We wish to do animations now!
}
```

Note that we use the expression `360 deg * time` to "convert" `time` from a number in the range 0 to
1 into an angle expressed in degrees. This is a useful way to convert between units, without needing
to use `pi`.

If we run the program now, we see a cube that makes one full rotation every 5 seconds.


We can also use the `time` variable in shaders. Before we do, we first need to pass it to the
shader. Of course, we could add it as a member of the pipeline, just as we did with the `camera`
transform. However, we will illustrate a different way to pass uniforms to shaders. This way is
useful for values that will vary from one call to `render` to another (`time` is perhaps not the
best use-case for this, as it is the same for every frame).

First, we add another parameter after `data` in the pipeline. Note that we mark it as `uniform`
since we want the shader to use the same value for all vertices.

```bsfragment:SFile:use=ui.s3d
pipeline SimplePipeline {
    // The flow of data through the pipeline:
    // (input parameters) -> vertex shader output -> pixel shader output
    data (SimpleVertex point, uniform Float time) -> SimpleIntermediate -> SimpleOutput;

    // Additional members become uniforms automatically, but are set globally rather than passed to
    // the function.
    Transform camera;
}
```

Much in the same way that `camera` is now available in our shaders, `time` is also available to our
shaders without any other modifications. For example, we can move the cube around in the vertex
shader, after applying the camera transform. We can do this by modifying the vertex shader as
follows:

```bsfragment:SFile:use=ui.s3d
vertex shader for SimplePipeline: SimpleIntermediate simpleVertexShader(SimpleVertex point) {
    // Note: we can use the 'camera' uniform here.
    Vector4 pos = Vector4(point.pos) * camera;

    // Modify the x coordinate a bit, for a wavy effect:
    pos.x += sin(360.0 deg * time);

    // Note: 'return' is optional, just as in Basic Storm.
    return SimpleIntermediate(pos, point.color);
}
```

Finally, we need to pass the time to the rendering pipeline. We do this by updating the call to
`pipeline.render` as follows:

```bsstmt
// Render using the pipeline.
pipeline.render(g, indices, vertices, time);
```

Note how the list of types after `data` in the pipeline correspond to the parameters passed to
`pipeline.render`. However, also note that uniform parameters do *not* appear as a formal parameter
to the vertex (or pixel) shader. This is to illustrate the fact that only the formal parameters of
the vertex and pixel shaders are unique for this invocation. Remaining parameters are constant.
