Using textures in WebGL

Introduced in Gecko 2

(Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1)

Now that our sample program has a rotating 3D cube, let's map a texture onto it instead of having its faces be solid colors.

Loading textures

The first thing to do is add code to load the textures. In our case, we'll be using a single texture, mapped onto all six sides of our rotating cube, but the same technique can be used for any number of textures.

Note: It's important to note that the loading of textures follows cross-domain rules; that is, you can only load textures from sites for which your content has CORS approval. See Cross-domain textures for details.

The code that loads the texture looks like this:

function initTextures() {
  cubeTexture = gl.createTexture();
  cubeImage = new Image();
  cubeImage.onload = function() { handleTextureLoaded(cubeImage, cubeTexture); }
  cubeImage.src = "cubetexture.png";

function handleTextureLoaded(image, texture) {
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.bindTexture(gl.TEXTURE_2D, null);

The initTextures() routine starts by creating the GL texture object cubeTexture by calling the GL createTexture() function. To load the texture from the image file, it then creates an Image object and loads into it the graphic file we wish to use as our texture. The handleTextureLoaded() callback routine is run when the image is done loading.

To actually create the texture, we specify that the new texture is the current texture on which we want to operate by binding it to gl.TEXTURE_2D. After that, the loaded image is passed into texImage2D() to write the image data into the texture.

Note: Textures' widths and heights must be a power of two number of pixels (that is, 1, 2, 4, 8, 16, etc).

The next two lines set up filtering for the texture; this controls how the image is filtered while scaling. In this case we're using linear filtering when scaling the image up, and a mipmap when scaling down. Then the mipmap is generated by calling generateMipMap(), and we finish up by telling WebGL we're done manipulating the texture by binding null to gl.TEXTURE_2D.

Mapping the texture onto the faces

At this point, the texture is loaded and ready to use. But before we can use it, we need to establish the mapping of the texture coordinates to the vertices of the faces of our cube. This replaces all the previously existing code for configuring colors for each of the cube's faces in initBuffers().

cubeVerticesTextureCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer);
  var textureCoordinates = [
    // Front
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0,
    // Back
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0,
    // Top
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0,
    // Bottom
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0,
    // Right
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0,
    // Left
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0

  gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(textureCoordinates),

First, this code creates a GL buffer into which we'll store the texture coordinates for each face, then we bind that buffer as the array we'll be writing into.

The textureCoordinates array defines the texture coordinates corresponding to each vertex of each face. Note that the texture coordinates range from 0.0 to 1.0; the dimensions of textures are normalized to a range of 0.0 to 1.0 regardless of their actual size, for the purpose of texture mapping.

Once we've set up the texture mapping array, we pass the array into the buffer, so that GL has that data ready for its use.

Note: You will have to use Float32Array in place of WebGLFloatArray in WebKit based browsers. 

Updating the shaders

The shader program -- and the code that initializes the shaders -- also needs to be updated to use the textures instead of solid colors.

First, let's take a look at the very simple change needed in initShaders():

textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");

This replaces the code that set up the vertex color attribute with one that contains the texture coordinate for each vertex.

The vertex shader

Next, we need to replace the vertex shader so that instead of fetching color data, it instead fetches the texture coordinate data.

<script id="shader-vs" type="x-shader/x-vertex">
      attribute vec3 aVertexPosition;
      attribute vec2 aTextureCoord;
      uniform mat4 uMVMatrix;
      uniform mat4 uPMatrix;
      varying highp vec2 vTextureCoord;
      void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        vTextureCoord = aTextureCoord;

The key change here is that instead of fetching the vertex color, we're setting the texture coordinates; this will indicate the location within the texture corresponding to the vertex.

The fragment shader

The fragment shader likewise needs to be updated:

<script id="shader-fs" type="x-shader/x-fragment">
      varying highp vec2 vTextureCoord;
      uniform sampler2D uSampler;
      void main(void) {
        gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));

Instead of assigning a color value to the fragment's color, the fragment's color is computed by fetching the texel (that is, the pixel within the texture) that the sampler says best maps to the fragment's position.

Drawing the textured cube

The change to the drawScene() function is simple (except that for the purpose of clarity, I've removed the code that causes the cube to translate through space while animating; instead it just rotates).

The code to map colors to the texture is gone, replaced with this:

  gl.bindTexture(gl.TEXTURE_2D, cubeTexture);
  gl.uniform1i(gl.getUniformLocation(shaderProgram, "uSampler"), 0);

GL provides 32 texture registers; the first of these is gl.TEXTURE0. We bind our previously-loaded texture to that register, then set the shader sampler uSampler (specified in the shader program) to use that texture.

At this point, the rotating cube should be good to go. If your browser supports WebGL, you can try the live demo.

Tags (2)

Edit tags

Attachments (0)


Attach file