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

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

Texture Atlas: Two Meshes

Screen Shot of Two Meshes

The third texture atlas project demonstrates how to display multiple meshes mapped with different images. The images use one texture atlas. The meshes share one vertex buffer object. Efficiently display unique meshes and textures while sharing one buffer and texture. Interleave vertices and texels within one vertex buffer object. While rendering the scene, call the WebGL API method drawElements() for each mesh, with different parameters. This technique provides the appearance of multiple entities with different meshes and maps.

The JavaScript file GLTwoMesh.js defines the GLTwoMesh class with supporting methods and instance variables. This project displays a square and a triangle. The square requires four vertices with texels. The triangle requires only three vertices with texels. Tap the Play button to run the animation. The square rotates around the Y axis. The triangle rotates around the X axis. The render() method uploads a matrix with Y rotation values for the square, and a matrix with X rotation values for the triangle. The graphic for the texture atlas follows.

Texture Atlas for Two Meshes

Diagram 5: Texture Atlas for Two Meshes

Texels for Two Meshes

The following graphic demonstrates the texels prepared to display both meshes. S and T values for texels appear within square brackets. Arrows point to dots representing texel locations. The black outline demonstrates which portions of the atlas render to the display screen. We don't need to render the white area on both sides of the triangle. Neither do we need to draw more than three vertices for the triangle. We only need three texels and three vertices for the triangle mesh.

Texels for Two Meshes

Diagram 6: Texels for Two Meshes

Vertices for Two Meshes

The following graphic demonstrates preparing vertices to display both meshes. We manually scaled each mesh down by 50%. The X and Y coordinates for each mesh range between -0.5 and +0.5. The meshes would naturally overlap. However the constructor moves the matrix for the square mesh left and the triangle mesh right. X marks the center of each mesh at the origin (0,0,0). Rotation matrices simply rotate around the origin. The gray vertex coordinates define the triangle. The black vertex coordinates define the square.

Vertices for Two Meshes

Diagram 7: Vertices for Two Meshes

Vertex Texel Array for Two Meshes

Place values from the two previous graphics, in an array of alternating vertices and texels. The following JavaScript array includes four vertices and texels to display a square. After the square, three vertices and texels describe the shape of a triangle. The comments associate indices with each vertex texel set. We need seven indices for the element array.

var aVertices = new Float32Array(

[

//index 0

-0.5, 0.5, 0.0,

0.0, 1.0,

//index 1

0.5, 0.5, 0.0,

0.5, 1.0,

//index 2

0.5, -0.5, 0.0,

0.5, 0.0,

//index 3

-0.5, -0.5, 0.0,

0.0, 0.0,

// index 4

0.0, 0.5, 0.0,

0.75, 1.0,

//index 5

0.5, -0.5, 0.0,

1.0, 0.0,

//index 6

-0.5, -0.5, 0.0,

0.5, 0.0,

]

);

Listing 15: Array for Two Meshes

Element Array for Two Meshes

We'll need an index element array with enough entries for both meshes. The following listing demonstrates an array of indices for this project. Notice the first six entries in the element array point to the first four entries from the preceding vertex texel array. The last three entries in the element array point to the last three entries from the vertex texel array. When rendering we'll need to remember six elements provide the shape of the square, and three elements provide the shape of the triangle. Additionally we'll calculate the offset for drawing the triangle, based on the elements which draw the square. In other words, determine the distance from the start of the element array to the start of indices which form the triangle's mesh.

var aIndices = new Uint16Array([

// The square

// plane's

// vertices

// and textures.

3,2,0,

0,2,1,

// The triangle's

// vertices

// and textures.

6,5,4

]);

Listing 16: Texture Atlas: Two Meshes Index Element Array

Move Individual Meshes

The supporting file GLEntity.js declares functionality to display one entity. Some entities include separate textures. Each entity includes a 4 x 4 matrix representing transformation. Change values in the matrix to move or rotate a mesh. This section describes how to initialize each matrix in order to move the mesh, when rendering.

The twelfth, thirteenth, and fourteenth elements in a 4 x 4 matrix provide translation. Modify values at those index locations, to move a mesh with the matrix. Change the twelfth element to move vertices along the X axis, left or right. Change the thirteenth element to move vertices along the Y axis, up or down. Change the fourteenth element to move vertices along the Z axis, away from or toward the view port. The following diagram represents a 4 x 4 matrix. The entries marked Tx, Ty, and Tz indicate translation along the X, Y, or Z axis.

Matrix Translation

Diagram 8: Matrix Translation

The GLTwoMesh constructor instantiates two GLEntity, adds them to an array, then passes the array to the GLControl constructor.

The GLEntity constructor includes two parameters. The first parameter is a String representing the path to an image file for use as a texture. The String may point to an image file or equal null. The second parameter represents the texture unit associated with the entity. However if no image data is available, then the previous entity's texture remains the active texture unit. We only need one GLEntity with texture data, to render a mesh. We can use the matrixproperty from other GLEntity in the list.

In this case one GLEntity maintains an active texture and a matrix with rotation around the Y axis. A second GLEntity maintains a matrix with rotation around the X axis.

Use the matrix to move each mesh along both the X and Y axes, during rendering. The following listing creates a new GLEntity then adds the entity to an array. The parameter s represents a path to the image file provided by the GLTwoMesh constructor. The parameter 0 becomes the idx property of the first entity. The entity's matrix receives values to move a mesh left and up.

// Array to store

// GLEntity.

var aIm = new Array();

// New GLEntity

// 's' is an

// image file path.

// '0' is the

// texture unit

// for this entity.

var n = new GLEntity(s,0);

// Assign matrix values

// to move the entity

// to the left.

n.matrix[12] = -0.45;

// Assign matrix values

// to move the entity

// up one unit.

n.matrix[13] = 1;

// Add the entity

// to the array.

aIm.push(n);

Listing 17: First Entity of Two Meshes

The following listing creates a new GLEntity. The parameter null indicates this entity doesn't maintain a texture from an image file. Modify entries in the entity's matrix to move a mesh right and up.

// New GLEntity

// 'null' indicates

// this entity

// doesn't maintain

// a texture.

var n = new GLEntity(null,1);

// Assign matrix values

// to move the entity

// to the right.

n.matrix[12] = 0.6;

// Assign matrix values

// to move the entity

// up one unit.

n.matrix[13] = 1;

// Add the entity

// to the array.

aIm.push(n);

Listing 18: Second Entity of Two Meshes

The Texture Atlas: Two Meshes project has completed initialization. See the GLTwoMesh constructor. We can now render the meshes.

Draw Two Meshes

Class GLTwoMesh includes a render() method to display both meshes. The render() method follows the same sequence for each entity. First obtain a reference to the entity's matrix. Second provide rotation values for the matrix. Third upload the rotated matrix to the vertex shader with the WebGL method uniformMatrix4fv(). Fourth draw the mesh with the WebGL method drawElements().

Prepare the Square's Matrix

The following listing demonstrates providing rotation values for the square's matrix. The book WebGL Textures & Vertices: Beginner's Guide describes matrix rotation with the method matrixRotationY(). The controller's instance variable named nRad maintains rotation values in radians. The end of each animation frame increases the value for nRad.

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

matrix = controller.matrixRotationY

(

matrix,

controller.nRad

);

Listing 19: Rotate the Square's Matrix

Next upload the matrix to the vertex shader's uniform named um4_matrix. The controller's instance variable named uMatrixTransform contains the location of um4_matrix.

gl.uniformMatrix4fv

(

controller.uMatrixTransform,

false,

newFloat32Array(matrix)

);

Listing 20: Upload the Square's Matrix

Draw the Square

Draw the square with the WebGL method drawElements(). Method drawElements() signals WebGL to process attributes indirectly pointed to with the element array buffer. The first six entries in the index buffer for this project point to vertices and texels for the square. The controller uploads the index array as an element array buffer. Therefore draw just the first six entries assigned to the element array buffer. The second parameter to drawElements() tells WebGL how many entries from the element array buffer to draw. Pass 6 to the second parameter.

WebGL method drawElements() last parameter is the offset from the start of the array. In other words, define where in Bytes the indices for the square begin. The square begins at the first entry. Therefore pass 0 as the last parameter. The following listing draws the square to the canvas.

gl.drawElements

(

gl.TRIANGLES,

6,

gl.UNSIGNED_SHORT,

0

);

Listing 21: Draw the Square

Prepare the Triangle's Matrix

The render() method rotates the first entity around the Y axis and the second entity around the X axis. Separate rotations highlight the activity of each mesh as a unique entity. We haven't covered X axis rotation yet.

Rotate Around the X Axis

The GLControl class includes a method named matrixRotationX(), which modifies values in a matrix to provide mesh rotation around the X axis. The following graphic illustrates rotating a square plane around the X axis.

X Rotation

Diagram 9: Rotate Around the X Axis

The following matrix rotation method demonstrates modifications to a 4 x 4 matrix, which provide rotation around the X axis. The parameter m is a JavaScript array of 16 entries. The array represents a 4 x 4 matrix. The parameter xis a number representing the amount of rotation to apply, in radians. Assign the cosine of the angle to the fifth and tenth entries of the array. Assign the negative of the sine of the angle to the sixth entry, and the sine of the angle to the ninth entry. Then return a matrix with the new values. The following method was modified from a function licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the Apache 2.0 License.

matrixRotationX: function (m,x){

var c = Math.cos(x);

var s = Math.sin(x);

return [

m[0], m[1], m[2], m[3],

m[4], c, -s, m[7],

m[8], s, c, m[11],

m[12], m[13],m[14],m[15],

];

}

Listing 22: Method matrixRotationX()

The following listing demonstrates providing rotation values for the triangle's matrix. The controller's instance variable named nRad maintains rotation values in radians. At the end of each animation frame, method render()increases the value for nRad.

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

matrix = controller.matrixRotationX

(

matrix,

controller.nRad

);

Listing 23: Rotate the Triangle's Matrix

Next upload the matrix to the vertex shader's uniform named um4_matrix. The controller's instance variable named uMatrixTransform contains the location of um4_matrix.

gl.uniformMatrix4fv

(

controller.uMatrixTransform,

false,

new Float32Array(matrix)

);

Listing 24: Upload the Triangle's Matrix

Draw the Triangle

Draw the triangle with the WebGL method drawElements(). Method drawElements() signals WebGL to process attributes indirectly pointed to with the element array buffer. The last three entries in the index buffer for this project point to vertices and texels for the triangle. The controller uploads the index array as an element array buffer. Therefore draw just the last three entries assigned to the element array buffer. The second parameter to drawElements()tells WebGL how many entries from the element array buffer to draw. Pass 3 to the second parameter.

WebGL method drawElements() last parameter is the offset from the start of the array. In other words, define where in Bytes the indices for the triangle begin. The triangle begins at the seventh entry in the element array buffer. The element array buffer includes six entries before those which define the triangle. To find the offset location for the triangle indices, multiply the number of array entries preceding the triangle entries, times the size in Bytes for each entry. Each entry in the element array buffer is of type UNSIGNED_SHORT. The number of Bytes per UNSIGNED_SHORT equals 2. The last parameter equals 12 because 6 entries times 2 Bytes per entry, equals 12.

6 * 2 = 12

The following listing demonstrates calling drawElements() to display the triangle.

gl.drawElements

(

gl.TRIANGLES,

3,

gl.UNSIGNED_SHORT,

12

);

Listing 25: Draw the Triangle

Now method render() has drawn the square and triangle separately. For each frame increment nRad to animate rotation for the meshes. The controller's nRad property contains the current rotation in radians. The controller's N_RADproperty contains a number representing a constant amount of rotation per frame. Increment the controller's nRad property by N_RAD units. The following line increments rotation.

controller.nRad += controller.N_RAD;

See the render() method defined for the Texture Atlas: Two Meshes project.

Summary Texture Atlas: Two Meshes

The third texture atlas project demonstrated how to display multiple meshes mapped with different images. The images use one texture atlas. The meshes share one vertex buffer object. Efficiently display unique meshes and textures while sharing one buffer and texture. Interleave vertices and texels within one vertex buffer object. While rendering the scene, call the WebGL API method drawElements() for each mesh, with different parameters. This technique provides the appearance of multiple entities with different meshes and maps.

See the JavaScript file for the Texture Atlas: Two Meshes project.