Texture Atlas Animation - WebGL Textures: Introduction to Mipmaps, Sub Images & Atlases (2015)

WebGL Textures: Introduction to Mipmaps, Sub Images & Atlases (2015)

Texture Atlas Animation

Animate Textures

The second texture atlas project demonstrates how to animate images on a texture. Tap the Play button to see the animation. A coffee pot explodes. The JavaScript file GLImageAnim.js implements animation with oneWebGLTexture. The following texture atlas provides all four frames for the exploding coffee pot animation.

Texture Atlas for Animation

Diagram 3: Texture Atlas Animation

This project demonstrates how to use multiple images from one WebGLTexture texture atlas, to provide the illusion of animation.

Optimization

This example includes a few optimization techniques. We upload one texture for multiple animation frames. We use one buffer for all vertices and texels. GPUs generally run faster with fewer buffers and more data per buffer. As usual for texture atlases it's faster to change buffer offsets, than to change textures.

Create an array of non-interleaved vertex and texel data. Prepare vertices with with no gaps between each vertex. Prepare texels with no gaps between each texel. With non-interleaved buffers, we can apply a stride of 0 when calling the WebGL API method vertexAttribPointer(). This example creates an array of offsets in advance. During animation increment an index into the array of offsets, to change the vertexAttribPointer() offset parameter.

Texture Animation: Texels

The background color for the animation's frames remain a constant blue color. The color between frame edges doesn't change. Therefore artifacts between the edges of frames shouldn't display. We don't need to offset the S texel coordinates by 1/100 to prevent blending along frame edges. Use texels to evenly divide the texture four times along the S axis. The following graphic demonstrates texels applied to the exploding coffee pot animation. S and T values appear between square brackets. Only the values for S change per frame. The set of S values equals {0.0,0.25,0.5,0.75,1.0}.

Texels for Animation

Diagram 4: Texture Atlas Animation: Texel Mapping

Class GLImageAnim declares an index element array identical to the index element array used with the previous project.

Class GLImageAnim declares an array of vertices and texels with data arranged in blocks. In other words vertices are grouped together at the beginning of the array. Texels are grouped together after the vertices. Vertices and texels aren't interleaved. The following listing includes four vertices at the top, and four sets of texels after the vertices. Comments explain which set of texels map to each frame. For example, the first set of texels map to frame 0 and the second set of texels map to frame 1.

var aVertices = new Float32Array(

[

// Vertices:

-1.0, 1.0, 0.0,

1.0, 1.0, 0.0,

1.0, -1.0, 0.0,

-1.0, -1.0, 0.0,

// Texels:

// Frame 0:

0.0, 1.0,

0.25, 1.0,

0.25, 0.0,

0.0, 0.0,

// Frame 1:

0.25, 1.0,

0.5, 1.0,

0.5, 0.0,

0.25, 0.0,

// Frame 2:

0.5, 1.0,

0.75, 1.0,

0.75, 0.0,

0.5, 0.0,

// Frame 3:

0.75, 1.0,

1.0, 1.0,

1.0, 0.0,

0.75, 0.0,

]

);

Listing 8: Texture Atlas Animation: Vertex Texel Array

Prepare Offsets

The animation example uses an array of prepared offsets to render frame by frame. The previous project demonstrated how to calculate offsets. This project presents a pattern useful for animation.

Frame Zero = 48

First calculate frame zero's offset. The array begins with 4 vertices of 3 coordinates per vertex. Each coordinate is a FLOAT. Floats require 4 Bytes each. The first frame's offset equals 48, because 4 * 3 * 4 = 48. Four Bytes times three coordinates time four vertices equals forty eight.

The offset for frame zero counts vertices with X, Y, and Z coordinates. Subsequent frames count texels only with S and T coordinates. No more vertices remain in the array. For each additional frame increment the offset by 32. Each frame includes 4 texels times 2 coordinates times 4 Bytes per coordinate. 4 * 2 * 4 = 32.

Frame One = 80

Add 32 to the offset from frame zero to find the offset for frame one. The offset for frame zero equals 48. The offset for frame one equals 80 because 48 + 32 = 80.

Frame Two = 112

Add 32 to the offset from frame one to find the offset for frame two. The offset for frame one equals 80. The offset for frame two equals 112 because 80 + 32 = 112.

Frame Three = 144

Add 32 to the offset from frame two to find the offset for frame three. The offset for frame two equals 112. The offset for frame three equals 144 because 112 + 32 = 144.

The following listing includes the array of offsets used to render the explosion animation.

this.aOffsets=[

48,

80,

112,

144

];

Listing 9: Texture Atlas Animation: Array of Offsets

Complete Initialization

To complete initialization of the GLImageAnim class, follow the same process used to initialize the previous project, named Texture Atlas: Switch Images. Create an array of GLEntity with one GLEntity reference. Instantiate the controller. However also prepare animation frame count variables, modify the default matrix, implement an init() method to assign non interleaved processing for vertices.

Set a maximum frame count, based on the length of the array of offsets. We have four offsets in the array. Assign 4 to the controller's instance variable named FRAME_MAX. The FRAME_MAX variable sets a limit on the number of frames available to animate. The following line assigns the maximum frame count.

controller.FRAME_MAX = Number(4);.

The animation sequence iterates over the array of offsets. Create an instance variable within the GLImageAnim class. The variable indexes into the array. The following line initializes nIdx to display the first animation frame.

this.nIdx = Number(0);.

Upload a Transformation Matrix

By default the controller rotates a mesh around the Y axis. For each frame the default rendering method modifies a matrix, then uploads the matrix to the vertex shader to display rotation. The default matrix places each mesh four units away from the view port, along the Z axis. The distance minimizes cropping along the boundaries of the canvas during rotation.

This project implements a unique render() method to animate the texture. With this project, the square mesh doesn't rotate. The mesh always stays parallel to the view port. Therefore we can move the mesh closer to the view port. Additionally the texture image animates, but the mesh doesn't move during animation. Therefore this project only needs to upload the transformation matrix once. We don't need to upload the transformation matrix for every frame of animation.

The GLImageAnim constructor calls setDefaultMatrix(). Method setDefaultMatrix() defined within GLImageAnim.js, modifies and uploads a matrix to the vertex shader. Method setDefaultMatrix() changes the value of the entry in the matrix responsible for Z translation.

Originally the GLImageAnim constructor created one GLEntity, assigned the entity to an array, and passed the array to the controller. The controller saves the array in the property named aEntities. Each GLEntity maintains a matrixproperty. Therefore method setDefaultMatrix() obtains a reference to the GLEntity matrix from the controller's list, with the following line.

var matrix = controller.aEntities[0].matrix;

Next setDefaultMatrix() modifies the matrix to place the mesh three units away from the view port. The fourteenth entry in a 4 x 4 matrix, provides translation along the Z axis. The following line demonstrates modifying the matrix to move the mesh three units from the view port.

matrix[14] = -3.0;

Last setDefaultMatrix() uploads the matrix once for all animation frames, with the WebGL API method uniformMatrix4fv().

WebGL API Method uniformMatrix4fv((WebGLUniformLocation, boolean, Float32Array)

The first parameter to uniformMatrix4fv() is the location of a uniform within the shader. The controller's instance variable named uMatrixTransform, is the location of the vertex shader's uniform named um4_matrix. The shader multiplies each vertex by um4_matrix to transform the mesh. The second parameter to uniformMatrix4f() must equal false. The last parameter casts our modified matrix to type Float32Array. The following listing demonstrates uploading the new matrix to the vertex shader. See GLImageAnim's setDefaultMatrix() method. After the texture initializes and before rendering the animation, the controller calls GLImageAnim's init() method.

gl.uniformMatrix4fv

(

controller.uMatrixTransform,

gl.FALSE,

new Float32Array(matrix)

);

Listing 10: Texture Atlas Animation: Upload new Matrix

Unique init() Method

The controller calls a project's init() method if one exists, just before rendering. This project's init() method calls vertexAttribPointer() to change the stride value for vertex processing. The following listing demonstrates changing the stride. The controller's instance variable named aPosition is the location of the vertex shader's attribute named a_position. The offset equals 0 because the buffer of vertices and texels begin with vertices. The stride equals 0 because vertices follow each other sequentially in the buffer. The first entries in the array are vertices and no gaps exist between vertices in the array.

init: function(controller){

// WebGLContext

var gl = controller.gl;

// Change data assigned

// to a_position.

gl.vertexAttribPointer

(

controller.aPosition,

3,

gl.FLOAT,

gl.FALSE,

0,

0

);

},

Listing 11: Texture Atlas Animation: Initialize Vertex Offsets

Render the Animation

Method render() defined within GLImageAnim.js displays the animation. The controller calls render() when the user taps the Play button or the canvas. If the user taps the canvas then the next frame renders. If the user taps the Play button then the four frame animation plays. This section covers the details of the rendering method.

The only parameter to render() is a reference to the controller. First copy properties from the controller to a few local variables needed for rendering. Save a reference to the WebGLContext with the following line.

var gl = controller.gl;.

Save a reference to this particular class. The controller saves a reference to the current project's class in the instance variable named glDemo. Save a GLImageAnim reference to the local variable imgAnim with the following line.

var imgAnim = controller.glDemo;

Obtain the location of the vertex shader's attribute named a_tex_coord0, from the first GLEntity in the list. Instances of GLEntity save a reference to a shader attribute used to process texels. The only GLEntity in the list has an idxvalue of 0. The GLEntity's property aTexCoord is the location of the vertex shader's attribute a_tex_coord0. We need to modify the texels which process through a_tex_coord0. The following line demonstrates copying the location of a_tex_coord0 to the local variable aTexCoord.

var aTexCoord = controller.aEntities[0].aTexCoord;

Draw the Scene

The render() method calls vertexAttribPointer() to change the set of texels which process through the vertex shader's attribute a_tex_coord0. Use the array of offsets we previously prepared. Remember the constructor initializes property nIdx with the number 0. Each frame uses imgAnim.nIdx as an index into the array of offsets. Then render() increments imgAnim.nIdx, for the next frame. The following listing demonstrates assigning a set of texels to process.

gl.vertexAttribPointer(

aTexCoord,

2,

gl.FLOAT,

false,

0,

imgAnim.aOffsets[imgAnim.nIdx]

);

Listing 12:Texture Atlas Animation: Modify Texel Offsets

Call the WebGL method drawElements() to process the six entries declared in the index element array. The controller's instance variable named nBufferLength equals 6 for this project, because the index array's length equals 6. Method drawElements() causes four of our buffer's vertices to process through the vertex shader, in the order specified by the index element array. Method drawElements() causes four of our buffer's texels to process through the vertex shader, in the order specified by the index element array. However the texels processed begin at the offset previously specified with vertexAttribPointer(). The following listing demonstrates calling drawElements() within GLImageAnim's render() method.

gl.drawElements

(

gl.TRIANGLES,

controller.nBufferLength,

gl.UNSIGNED_SHORT,

0

);

Listing 13: Texture Atlas Animation: WebGL API drawElements()

Last increment the index into our array of offsets. If the index passes the bounds of the array, then reset to zero. The following listing demonstrates index updates per frame. That completes the render() method for texture atlas animation. See the entire GLImageAnim render() method.

imgAnim.nIdx++;

if(imgAnim.nIdx >=

controller.FRAME_MAX)

{

imgAnim.nIdx = Number(0);

}

Listing 14: Texture Atlas Animation: Update the Index

Summary Texture Atlas Animation

The second texture atlas project demonstrates how to animate images with a texture. Tap the Play button to see the animation. A coffee pot explodes.

This example included a few optimization techniques. We demonstrated how to animate with multiple images from one WebGLTexture texture atlas. Texture atlas animation provides more efficient animation. It's better to upload one texture than to upload multiple textures. It's faster to change texel offsets than change textures. The Texture Atlas Animation project prepares one array of vertices and texels for multiple frames. GPUs optimize with fewer buffers and more data per buffer, as long as the buffer doesn't exceed storage size.

The Texture Atlas Animation project demonstrated how to upload vertices and texels in a non interleaved array. With non-interleaved buffers, apply a stride of 0 when calling the WebGL API method vertexAttribPointer(). The array uploads to a buffer with no gaps between vertices or texels.

The Texture Atlas Animation project included separate render() and init() methods. We discussed each method in detail. See the fully commented GLImageAnim.js JavaScript file.