Page 4: Textures in THREE

CS559 Spring 2023 Sample Solution

Using textures is notoriously tricky because there are a lot of steps involved. Fortunately, THREE makes it remarkably easy - it takes care of all of the little details, allowing you to focus on the big parts (setting coordinates, defining the texture image).

If you want a sense of how much had to happen to get a cube with texture on it, have a look at the 2015 example! And this doesn’t do any lighting!

Fortunately, THREE takes care of all of this for us. And it gives us access to lots of fancier kinds of texture things as well.

The Complete Example

We’re going to do this backwards. Here is a complete example program. It is the two triangles from Page  1  (Meshes: What you need to know), with an extra plane and a texture added. The plane is so you can see what the texture looks like when flat. There are a few changes - the triangles are white, not yellow.

08-04-01

We’ll explain how it works in the following, but you might want to have a look first (the program is 08-04-01.js).

On the left, we have the triangles without texture, for reference. In the center, we put the texture on the triangles. On the right, we again put the texture on, but adjust the texture coordinates - the left triangle gets its colors from the lower-left part of the texture map (as it appears on the square), and the right triangle gets its colors from the upper right corner.

Texture Coordinates in THREE

Most THREE primitives have reasonable texture coordinates defined. For example, the square (a THREE PlaneGeometry) in the demo 1 has its u,v going from 0 to 1 over the course of a plane.

For user-defined geometries (of class BufferGeometry), we have to provide texture coordinates ourselves. We need to split a vertex if it needs to have different coordinates for the different triangles it is part of (as we did with color and normals before).

Note: in previous versions of the class, we used the old style Geom - now we are only using BufferGeometry.

Setting up the texture coordinates for the BufferGeometry is simple: we make a new BufferAttribute that is the uvs. We give it an array of 2 * number of vertices (a uv pair for each vertex). Here’s the code from 08-04-01.js - it’s actually only 2 statements (since I put each vertex’s data on a separate line, it takes longer).

64
65
66
67
68
69
70
71
72
      const uvs = new Float32Array( [
        0,0,
        1,0,
        0,1,        
        1,1,
        0,1,
        1,0
      ]);
      geometry.setAttribute('uv',new T.BufferAttribute(uvs,2));

Notice that I need to make a typed array of the values (that’s what FloatArray32 does). It has 12 values (6 vertices, 2 values per vertex - u and v). I then use that array to make a BuffferAttribute that gets assigned to “uv”.

Using the Texture

Once we have UV values we simply need to make sure our material knows to use those UV values and color the triangles using the correct map. In 08-04-01.js it is done by:

76
77
78
79
80
81
      let tl = new T.TextureLoader().load("../textures/UV_Grid_Sm.jpg");
      let material = new T.MeshStandardMaterial({
        color: "white",
        roughness: 0.75,
        map: tl
      });

Here there are two steps: first, we load an image texture (more on that in a moment), and then we create the material. When we create the material, it can be as simple as setting the map attribute to be the texture we want to use. Technically, we are using the texture as a color map. Note that we make the material white: the color will be blended with the texture color, so if you want to see the actual colors in the texture, you need to have the base color be white.

We can specify other properties for the material. In the example, I specified roughness. You can even specify color, and this color will be combined with the colors from the texture. And of course, with a MeshStandardMaterial, the colors are affected by lighting as well. If you want to use textures but do not want the color affected by lighting, use a MeshBasicMaterial.

THREE allows us to control many properties of how textures are used. These will make more sense once we learn about how texturing works in class. For now, the default parameters will probably be fine.

Texture Image Loading in THREE

In order to have a texture image, we need to get the image from somewhere. Usually, this is from some image file.

Loading an image into a texture is tricky. We need to load the image. We need to wait until the image is loaded. We need to process the image and put it into a special format. We need to attach the image to its surface.

Fortunately, THREE takes care of all this for you with a texture loader. You say “load me a texture”, and it loads the texture in and sets things up once its loaded. A cool feature: before the texture is loaded, things still work (it just shows up as blank). Remember, this is JavaScript asynchronicity, so there really isn’t any multi-threading. You need to make sure you return back to the main event loop for loading to happen (remember how requestAnimationFrame works?).

The line of code is:

76
      let tl = new T.TextureLoader().load("../textures/UV_Grid_Sm.jpg");

Texture Re-Use

It’s so easy to load and use textures with THREE that you might forget how many you’ve loaded. In general, using lots of textures can be a problem: they take up a lot of memory.

For example, in the example code 08-04-01.js, the texture file /textures/UV_Grid_Sm.jpg is loaded three times. A separate copy will be loaded for each instance of TwoTrianglesTextured and TwoTrianglesTextured2 that we create. Right now, there is just one of each (so that’s two copies of the texture - a third is used for the plane). If we made a lot of these objects, the memory usage would add up fast.

Therefore, when we use textures, we need to be careful to re-use things that we’ve loaded already and not load multiple copies. In fact, we might want to try to re-use the same texture over and over to save memory. Think about this when you make your own objects for future projects.

The obvious way to fix this in 08-04-01.js would be to move the line that loads the texture to near the top of the file (after the THREE library is loaded), outside of any function. That way tl would be a “module” variable (effectively a global), rather than a local variable. The texture would be loaded once, when the module is first loaded.

Summary: Textures in THREE

That was a quick summary of how textures are applied in THREE. On Page  5  (Dice and Dominos (Exercise 2 and 3)), you can try doing it yourself!

There are no points associated with this page.