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. |
A simple, 3D model where each triangle has a different color.
Animate
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.
A simple, 3D model where each triangle has a different color.
Animate
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.
A simple, 3D model where each triangle has a different color.
Animate
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.