Lesson 5: Textures

View the full source.

In this lesson, we will learn how to use texture mapping in Lux. Texture mapping was originally a technique to cheaply add detail to curved surfaces. The way it works is that instead by deciding the color of each fragment (a fragment is a pixel-sized bit of a surface that end up on the screen) not by a simple combination of vertex colors, but selecting a certain portion of an image, or texture. This way, when modeling a complicated surface, instead of having to use thousands of triangles, we use instead the thousands of pixels of an image.

The main new idea behind texture mapping is that, together with positions, models must include additional per-vertex information. Specifically, models need to provide what is known as the texture coordinate of that vertex. Imagine that you were creating a globe, and wanted it to represent a map of the world. At some point, you will have to decide on a color of a fragment, and that will be a function of latitude and longitude. The latitude and longitude are coordinates that tell you where you are in the model. This is different from the position of the 3D model in the world; although the globe is clearly three-dimensional, latitude and longitude are only two dimensions, and are enough to completely determine which point in the globe one is talking about.

We start with the same basic setup as before, but, in this demo, we will use a predetermined model:

var ctx = Lux.init({
    clearColor: [0,0,0,0.2]
});

var camera = Shade.Camera.perspective({
    look_at: [Shade.vec(0, 0, 6), Shade.vec(0, 0, -1), Shade.vec(0, 1, 0)]
});
var angle = ctx.parameters.now.mul(50).radians();
var cube = Lux.Models.flat_cube();

cube has texture coordinates stored in cube.tex_coord. In order to use texture mapping, we will need to learn about two new calls: Lux.texture and Shade.texture2D.

Lux.texture

The Lux.texture call creates the texture object that can be used in texture mapping. It can load an image from a URL, from an img element in your DOM, or from a canvas. Here, we'll open an URL:

Lux.texture({ 
    src: "../../img/nehe.jpg",

The important thing to know when loading images from URLs is that this is an asynchronous process. The javascript interpreter does not wait for the image to finish loading before continuing the execution. Instead, you give Lux a function, and it to call that function when the image has finally loaded. For Lux.texture, you use the onload field:

    onload: function() {

Shade.texture2D

Now turn your attention to how the Lux actor is specified. Most of the code will be familiar to you, but the color field of appearance contains a reference to Shade.texture2D:

        Lux.Scene.add(Lux.actor({ 
            model: cube,
            appearance: {
                position: camera(Shade.rotation(angle, Shade.vec(1,1,1))(cube.vertex)),
                color: Shade.texture2D(this, cube.tex_coord)}}));

The call builds a Lux expression that represents a texture being sampled at a particular location. It takes two parameters: a texture object, and a texture coordinate. The texture object, in our case, is simply this; the result of Lux.texture is given as the this object for the onload handler. The texture coordinate, on the other hand, comes from cube, in the same way that vertex positions do. The texture coordinates for cube repeat themselves on each face; so you see a copy of the image on every face.

The rest of the code is uninteresting:

        Lux.Scene.animate();
    }
});

Back to the index.