Textures
========

The full source code for this part of the tutorial is available in the file
`root/tutorials/s3d/texture.bs`. You can run it using `storm -f tutorials.s3d.texture`. As in the
previous part, the code is fairly well-documented, so it is possible to read it and understand what
is happening. Note, however, that both the source code and this tutorial assumes that you have read
and understood the basics of rendering as outlined in the previous tutorial.

If you wish to write the code yourself, you can modify the code you wrote in the
[previous tutorial](md:Simple_Rendering) to add support for textures. As you will see, the majority of the
code is very similar. This part of the tutorial therefore only describes the differences from the
previous step. Note, however, that the code (`texture.bs`) uses different names for all types to not
clash with the previous sample. As such, anythin named `Simple...` is now renamed to `Tex...` to
differentiate the two. All other differences are described below.


Texture Coordinates
-------------------

The first step in rendering textures is to describe how textures should be mapped to the 3D
geometry. To do this, we need *texture coordinates*. In the vertex types used in the previous part
of the tutorial, there is nowhere to store texture coordinates, so the first thing we need to do is
to replace the `color` member in our vertex formats with *texture coordinates*. A texture coordinate
is, as the name implies, a 2D coordinate that corresponds to a point on the texture. By convention,
the texture coordinates range from 0 to 1 (inclusive), regardless of the size of the texture.
Coordinates outside of this range either clip to the edge of the texture, mirror it, or repeat it,
depending on the settings of the texture sampler.

As such, the first step is to update the vertex types as follows. Note that we do *not* need to
revise the `SimpleOutput` type. Instead we just use the `Output` vertex type from `common.bs`, which
is identical to `SimpleOutput`.

```bsfragment:SFile:use=ui.s3d
vertex TexVertex {
    // Position.
    Vector pos;

    // Texture coordinate (Point is a 2D coordinate).
    Point tex;
}

vertex TexIntermediate {
    // Screen position.
    Vector4 pos : position, perspective;

    // Texture coordinate.
    Point tex : perspective;
}
```

As we have now changed the vertex used to store the cube, we also need to update the code that
creates the cube. Luckily it is enough to update the code that creates the vertices. The indices can
stay the same.

```bs
class TexPainter extends SamplePainter3D {
    // ...

    init() {
        init {
            // ...
        }

        // Create a cube:
        vertices << TexVertex(Vector(-1, 1, 1), Point(1, 0));
        vertices << TexVertex(Vector(1, 1, 1), Point(0, 0));
        vertices << TexVertex(Vector(-1, -1, 1), Point(1, 1));
        vertices << TexVertex(Vector(1, -1, 1), Point(0, 1));
        vertices << TexVertex(Vector(-1, 1, -1), Point(0, 0));
        vertices << TexVertex(Vector(1, 1, -1), Point(1, 0));
        vertices << TexVertex(Vector(-1, -1, -1), Point(0, 1));
        vertices << TexVertex(Vector(1, -1, -1), Point(1, 1));

        // ...
    }

    // ...
}
```

Texture Objects
---------------

The rendering pipeline also needs to know *which* texture to use. To do this, we need to add a
[stormname:ui.Texture] member to the pipeline. Even though a texture is not a vertex type, the
pipeline knows how to handle textures properly. The texture object represents a 2D bitmap
(potentially with different mip-map levels) that is uploaded to the GPU as required.

As such, we can modify our pipeline as follows:

```bsfragment:SFile:use=ui.s3d
pipeline TexPipeline {
    data (TexVertex point) -> TexIntermediate -> Output;

    // Transform.
    Transform camera;

    // Texture.
    Texture texture;
}
```

We also need to supply an image for the texture. We can do that by adding the following code to the
`init` function of the painter class:

```bsfragment:SClassBody
init() {
    init {
        // ...
    }

    // Set the texture.
    pipeline.texture.image = loadImage(resUrl("flower-raw.ppm"));

    // ...
}
```

Note that the added line of code utilizes the fact that the pipeline will create an empty
[stormname:ui.Texture] object for us by default. As such, we can just set `image` to a
[stormname:graphics.Image] instance that represents the image. Here, we load an image from disk
using the [stormname:graphics.loadImage(core.io.Url)] function. The [stormname:core.io.Url] is
supplied by the `resUrl` function, which looks for the file in `root/tutorials/s3d/res` and
`root/res`.

This approach works well when we want to set the texture once and use the same texture all the time.
However, if we were to render different sets of geometry with different textures (e.g. cubes with
different textures), this would not be efficient. Since each [stormname:ui.Texture] instance
represents one texture uploaded to the GPU, constantly replacing the image in the texture would
cause the 3D library to upload the same images to the GPU repeatedly. In such cases, it is better to
create a set of [stormname:ui.Texture] instances up front, set their images, and then assign entire
[stormname:ui.Texture] objects to the pipeline. That way the 3D library does not need to re-upload
textures to the GPU all the time. For example:

```bs
class TexPainter extends SamplePainter3D {
    // Textures.
    Texture tex1;
    Texture tex2;

    // Create.
    init() {
        init {
            // ...
        }

        // Load textures.
        tex1.image = loadImage(resUrl("flower-raw.ppm"));
        tex2.image = loadImage(resUrl("tree-24.bmp"));

        // ...
    }

    // Render.
    Bool render(Size size, Graphics3D g) : override {
        // ...

        pipeline.texture = tex1;
        pipeline.render(g, indices, vertices);

        // ...

        pipeline.texture = tex2;
        pipeline.render(g, indices, vertices);

        // ...
    }
}
```

Using Textures in Shaders
-------------------------

Finally, we need to adjust our shaders. The vertex shader need to be updated to forward the texture
coordinates instead of the vertex color. This can be done by simply replacing `point.color` with
`point.tex` as below:

```bsfragment:SFile:use=ui.s3d
vertex shader for TexPipeline: TexIntermediate texVertexShader(TexVertex point) {
    Vector4 pos = Vector4(point.pos) * camera;
    return TexIntermediate(pos, point.tex);
}
```

The pixel shader requires a slightly larger modification. Instead of simply forwarding the color, we
need to sample the texture. We can do this by calling the
[stormname:ui.Texture.sample(core.geometry.Point)] member inside the shader to read the relevant
pixel (possibly with interpolation) from the texture. This looks as follows:

```bsfragment:SFile:use=ui.s3d
pixel shader for TexPipeline: Output texPixelShader(TexIntermediate point) {
    Color texColor = texture.sample(point.tex);
    return Output(texColor);
}
```

With those changes, we can now run the program to see a textured rotating cube!
