5.6 - Example 2: One Color per Triangle

We would like to have more control over the rendering of our model, where each face could possibly have a different color. This could be done using the previous version of our shading program by calling gl.drawArrays() to draw each triangle separately. The code would look something like this:

// Draw each triangle separately
for (start = 0, color_index = 0; start < number_vertices; start += 3, color_index += 1) {
  // Set the color of the triangle
  gl.uniform4fv(u_Color_location, colors[color_index]);

  // Draw a single triangle
  gl.drawArrays(gl.LINE_LOOP, start, 3);
}

However, if a model is composed of 100’s, or perhaps 1000’s of triangles, we would have a major speed problem. Every call in your JavaScript program to a WebGL command is a huge time sink. If we want the graphics to be fast, we need to draw an entire model using a single call to gl.drawArrays.

So we need to change things. And when we say change things, we mean change just about everything!

The Model

The data structures for your model data will impact how you write your other code. There are many possibilities, but let’s just store a color value with each of our “triangle” objects. A new version of our 3D model is shown in the following example. Please note the following changes:

Lines Description
38-42 A Triangle2 object now stores a color.
72-75 Various color values are defined.
78-81 A different color is passed to the creation of each Triangle2 object.
Show: Code Canvas Run Info
 
1
/**
2
 * simple_model_02.js, By Wayne Brown, Spring 2016
3
 */
4
5
/**
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2015 C. Wayne Brown
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy
11
 * of this software and associated documentation files (the "Software"), to deal
12
 * in the Software without restriction, including without limitation the rights
13
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
 * copies of the Software, and to permit persons to whom the Software is
15
 * furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in all
18
 * copies or substantial portions of the Software.
19
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
 * SOFTWARE.
27
 */
28
29
"use strict";
30
31
//-------------------------------------------------------------------------
32
/**
33
 * A triangle composed of 3 vertices and a color.
34
 * @param vertices Array The triangle's vertices.
35
 * @param color Array The triangle's color.
36
 * @constructor
37
  */
38
window.Triangle2 = function (vertices, color) {
39
  var self = this;
40
  self.vertices = vertices;
41
  self.color = color;
42
}
43
44
//-------------------------------------------------------------------------
45
/**
46
 * A simple model composed of an array of triangles.
47
 * @param name String The name of the model.
48
 * @constructor
49
 */
50
window.SimpleModel2 = function (name) {
51
  var self = this;
52
  self.name = name;
53
  self.triangles = [];
54
}
55
56
//-------------------------------------------------------------------------
57
/**
58
 * Create a Simple_model of 4 triangles that forms a pyramid.
59
 * @return SimpleModel
60
 */
61
window.CreatePyramid2 = function () {
62
  var vertices, triangle1, triangle2, triangle3, triangle4;
63
  var red, green, blue, purple;
64
65
  // Vertex data
66
  vertices = [  [ 0.0, -0.25, -0.50],
67
                [ 0.0,  0.25,  0.00],
68
                [ 0.5, -0.25,  0.25],
69
                [-0.5, -0.25,  0.25] ];
70
71
  // Colors in RGB
72
  red    = [1.0, 0.0, 0.0];
73
  green  = [0.0, 1.0, 0.0];
74
  blue   = [0.0, 0.0, 1.0];
75
  purple = [1.0, 0.0, 1.0];
76
77
  // Create 4 triangles
78
  triangle1 = new Triangle2([vertices[2], vertices[1], vertices[3]], green);
79
  triangle2 = new Triangle2([vertices[3], vertices[1], vertices[0]], blue);
80
  triangle3 = new Triangle2([vertices[0], vertices[1], vertices[2]], red);
81
  triangle4 = new Triangle2([vertices[0], vertices[2], vertices[3]], purple);
82
83
  // Create a model that is composed of 4 triangles
84
  var model = new SimpleModel2("simple");
85
  model.triangles = [ triangle1, triangle2, triangle3, triangle4 ];
86
87
  return model;
88
}
89
90
./simple_pyramid_color_triangle/simple_pyramid_color_triangle.html

A simple, 3D model where each triangle has a different color.

Please use a browser that supports "canvas"
Animate
Open this webgl program in a new tab or window

The Shader Programs

Our shader programs must change because each vertex of our model now has two attributes: a location, (x,y,z), and a color (r,g,b). Therefore, our vertex shader program has two attribute variables: a_Vertex and a_Color. Examine the shader programs in the following demo and then study their descriptions below.

Show: Code Canvas Run Info
21
 
1
// Vertex Shader
2
// By: Dr. Wayne Brown, Spring 2016
3
4
precision mediump int;
5
precision mediump float;
6
7
uniform   mat4 u_Transform;
8
9
attribute vec3 a_Vertex;
10
attribute vec3 a_Color;
11
12
varying vec4 v_vertex_color;
13
14
void main() {
15
  // Transform the location of the vertex
16
  gl_Position = u_Transform * vec4(a_Vertex, 1.0);
17
18
  v_vertex_color = vec4(a_Color, 1.0);
19
}
20
21
13
 
1
// Fragment shader
2
// By: Dr. Wayne Brown, Spring 2016
3
4
precision mediump int;
5
precision mediump float;
6
7
varying vec4 v_vertex_color;
8
9
void main() {
10
  gl_FragColor = v_vertex_color;
11
}
12
13
./simple_pyramid_color_triangle/simple_pyramid_color_triangle2.html

A simple, 3D model where each triangle has a different color.

Please use a browser that supports "canvas"
Animate
Open this webgl program in a new tab or window

Vertex Shader

Lines Description
7 There is only one variable that is constant for an execution of gl.drawArrays() for this shader, the uniform model transformation matrix, u_Transform.
9-10 Each vertex has two attributes: a location and a color.
12 Values are passed from a vertex shader to a fragment shader using the varying storage quantifier. This will make more sense later. For now, we need a ‘varying’ variable to pass a vertex’s color to the fragment shader. (Note that the vertex’s location is being passed to the frament shader through the gl_Position variable.
18 Convert the RGB color value for this vertex into an RGBA color value and pass it on to the fragment shader.

Fragment Shader

Lines Description
7 Declare a varying variable using the same name as the vertex shader. When the shaders are compiled and linked, this variable will contain the value set in the vertex shader.
10 Use the color of the vertex to set the color of every pixel inside the triangle that is being rendered.

The Buffer Object(s)

Since we have two attributes for each vertex, a location and a color, we will create two buffer objects. As we just discussed, each vertex must be assigned a color, even though this requires the same color value to repeated three times. Study the code in the simple_model_render_02.js file in the following example. Make sure you find where the data for the two buffer objects are collected and then the separate buffer objects are created in the GPU.

Show: Code Canvas Run Info
189
 
1
/**
2
 * simple_model_render_02.js, By Wayne Brown, Spring 2016
3
 */
4
5
/**
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2015 C. Wayne Brown
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy
11
 * of this software and associated documentation files (the "Software"), to deal
12
 * in the Software without restriction, including without limitation the rights
13
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
 * copies of the Software, and to permit persons to whom the Software is
15
 * furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in all
18
 * copies or substantial portions of the Software.
19
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
 * SOFTWARE.
27
 */
28
29
"use strict";
30
31
//-------------------------------------------------------------------------
32
/**
33
 * Given a model description, create the buffer objects needed to render
34
 * the model. This is very closely tied to the shader implementations.
35
 * @param gl Object The WebGL state and API
36
 * @param program Object The shader program the will render the model.
37
 * @param model Simple_model The model data.
38
 * @param model_color The color of the model faces.
39
 * @param out Object Can display messages to the webpage.
40
 * @constructor
41
 */
42
window.SimpleModelRender_02 = function (gl, program, model, out) {
43
44
  var self = this;
45
46
  // Variables to remember so the model can be rendered.
47
  var number_triangles = 0;
48
  var triangles_vertex_buffer_id = null;
49
  var triangles_color_buffer_id = null;
50
51
  // Shader variable locations
52
  var a_Vertex_location = null;
53
  var a_Color_location = null;
54
  var u_Transform_location = null;
55
56
  //-----------------------------------------------------------------------
57
  /**
58
   * Create a Buffer Object in the GPU's memory and upload data into it.
59
   * @param gl Object The WebGL state and API
60
   * @param data TypeArray An array of data values.
61
   * @returns Number a unique ID for the Buffer Object
62
   * @private
63
   */
64
  function _createBufferObject(gl, data) {
65
    // Create a buffer object
66
    var buffer_id;
67
68
    buffer_id = gl.createBuffer();
69
    if (!buffer_id) {
70
      out.displayError('Failed to create the buffer object for ' + model.name);
71
      return null;
72
    }
73
74
    // Make the buffer object the active buffer.
75
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer_id);
76
77
    // Upload the data for this buffer object to the GPU.
78
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
79
80
    return buffer_id;
81
  }
82
83
  //-----------------------------------------------------------------------
84
  /**
85
   * Using the model data, build a 1D array for the Buffer Object
86
   * @private
87
   */
88
  function _buildBufferObjectData() {
89
    var j, k, m, nv, numberVertices, triangle, vertex, all_vertices;
90
    var nc, all_colors;
91
92
    // Create a 1D array that holds all of the  for the triangles
93
    if (model.triangles.length > 0) {
94
      number_triangles = model.triangles.length;
95
      numberVertices = number_triangles * 3;
96
      all_vertices = new Float32Array(numberVertices * 3);
97
      all_colors = new Float32Array(numberVertices * 3);
98
99
      nv = 0;
100
      nc = 0;
101
      for (j = 0; j < model.triangles.length; j += 1) {
102
        triangle = model.triangles[j];
103
104
        for (k = 0; k < 3; k += 1) {
105
          vertex = triangle.vertices[k];
106
107
          // Store the vertices.
108
          for (m = 0; m < 3; m += 1, nv += 1) {
109
            all_vertices[nv] = vertex[m];
110
          }
111
112
          // Store the colors.
113
          for (m = 0; m < 3; m += 1, nc += 1) {
114
            all_colors[nc] = triangle.color[m];
115
          }
116
        }
117
      }
118
119
      triangles_vertex_buffer_id = _createBufferObject(gl, all_vertices);
120
      triangles_color_buffer_id = _createBufferObject(gl, all_colors);
121
    }
122
123
    // Release the temporary vertex array so the memory can be reclaimed.
124
    all_vertices = null;
125
    all_colors = null;
126
  }
127
128
  //-----------------------------------------------------------------------
129
  /**
130
   * Get the location of the shader variables in the shader program.
131
   * @private
132
   */
133
  function _getLocationOfShaderVariables() {
134
    // Get the location of the shader variables
135
    u_Transform_location = gl.getUniformLocation(program, 'u_Transform');
136
137
    a_Vertex_location    = gl.getAttribLocation(program, 'a_Vertex');
138
    a_Color_location     = gl.getAttribLocation(program, 'a_Color');
139
  }
140
141
  //-----------------------------------------------------------------------
142
  // These one-time tasks set up the rendering of the models.
143
  _buildBufferObjectData();
144
  _getLocationOfShaderVariables();
145
146
  //-----------------------------------------------------------------------
147
  /**
148
   * Delete the Buffer Objects associated with this model.
149
   * @param gl Object The WebGL state and API.
150
   */
151
  self.delete = function (gl) {
152
    if (number_triangles > 0) {
153
      gl.deleteBuffer(triangles_vertex_buffer_id);
154
      gl.deleteBuffer(triangles_color_buffer_id);
155
    }
156
  };
157
158
  //-----------------------------------------------------------------------
159
  /**
160
   * Render the model.
161
   * @param gl Object The WebGL state and API.
162
   * @param transform 4x4Matrix The transformation to apply to the model vertices.
163
   */
164
  self.render = function (gl, transform) {
165
166
    // Set the transform for all the triangle vertices
167
    gl.uniformMatrix4fv(u_Transform_location, false, transform);
168
169
    // Activate the model's vertex Buffer Object
170
    gl.bindBuffer(gl.ARRAY_BUFFER, triangles_vertex_buffer_id);
171
172
    // Bind the vertices Buffer Object to the 'a_Vertex' shader variable
173
    gl.vertexAttribPointer(a_Vertex_location, 3, gl.FLOAT, false, 0, 0);
174
    gl.enableVertexAttribArray(a_Vertex_location);
175
176
    // Activate the model's color Buffer Object
177
    gl.bindBuffer(gl.ARRAY_BUFFER, triangles_color_buffer_id);
178
179
    // Bind the color Buffer Object to the 'a_Color' shader variable
180
    gl.vertexAttribPointer(a_Color_location, 3, gl.FLOAT, false, 0, 0);
181
    gl.enableVertexAttribArray(a_Color_location);
182
183
    // Draw all of the triangles
184
    gl.drawArrays(gl.TRIANGLES, 0, number_triangles * 3);
185
  };
186
187
};
188
189
./simple_pyramid_color_triangle/simple_pyramid_color_triangle3.html

A simple, 3D model where each triangle has a different color.

Please use a browser that supports "canvas"
Animate
Open this webgl program in a new tab or window

Access to Shader Variables

Since your variables have changed in the shader programs, you need to modify your rendering code to get the location of the shader variables. Lines 135-138 of the demo code above get the shader variable locations:

// Get the location of the shader variables
u_Transform_location = gl.getUniformLocation(program, 'u_Transform');

a_Vertex_location    = gl.getAttribLocation(program, 'a_Vertex');
a_Color_location     = gl.getAttribLocation(program, 'a_Color');

Linking a Buffer Object to an Attribute Variable

We now have two buffer objects to link variables to when we render the model. Lines 170-181 in the above demo performs the linkage:

// Activate the model's vertex Buffer Object
gl.bindBuffer(gl.ARRAY_BUFFER, triangles_vertex_buffer_id);

// Bind the vertices Buffer Object to the 'a_Vertex' shader variable
gl.vertexAttribPointer(a_Vertex_location, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Vertex_location);

// Activate the model's color Buffer Object
gl.bindBuffer(gl.ARRAY_BUFFER, triangles_color_buffer_id);

// Bind the color Buffer Object to the 'a_Color' shader variable
gl.vertexAttribPointer(a_Color_location, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Color_location);

Notice that you have to make a buffer object active using the gl.bindBuffer() function before linking it to a variable with the gl.vertexAttribPointer() function.

Rendering

We can now render the entire model with a single call to gl.drawArrays(). Study the rendering function in the example above in lines 164-185.

It should be noted that we can no longer render the triangle edges as we did in the previous lesson. Why? The shader program now requires a color from a buffer object for each vertex. Using the shader program we have defined above, we could render the triangle edges by creating a 3rd buffer object and repeating the color black for each vertex. We could then connect the a_Color variable to this new buffer and render the edges as we did in the previous lesson. This is very wasteful with memory. Another option would be to have two separate shader programs: draw the faces with one shader program, activate a different shader program, and then render the edges. There are trade-offs for both options.

You can change the active shader program like this:

gl.useProgram(program);

Changing your active shader program changes the rendering context, which takes time, which slows down rendering. Therefore, you should switch between shader programs as few times as possible.

Summary

To use a different color for each triangle of a model, we had to modify the model’s definition, the shader programs, the buffer objects, and the rendering code. It is all interdependent. This makes code development difficult because we had to make so many changes in so many different places. It is so important that you understand the “big picture” as you modify your rendering code.

Next Section - 5.7 - Example 3: One Color per Vertex