5.5 - Example 1: One Color per Model

We will render our simple pyramid model using a single color for all its faces. We need to examine the following issues:

The Shader Programs

Examine the shader programs in the following demo and then study their descriptions below.

Show: Code Canvas Run Info
./simple_pyramid/simple_pyramid.html

A simple, manually defined 3D model. You can spin the model with your mouse.

Please use a browser that supports "canvas"
Animate
Shader errors, Shader warnings, Javascript info
Open this webgl program in a new tab or window

Vertex Shader

Lines Description
7-8 The model will be rendered with the same transformation matrix for all vertices. And all faces will be rendered with the same color. These are uniform values that will be set once before a rendering starts. Notice the convention to start their names with a u_. This will help you track the types of your variables. (WebGL shaders could care less what you name your variables. This convention is for you - the programmer!)
7 a mat4 is a 4x4 matrix; 16 floating point values.
8 a vec4 is a vector of 4 floating point values; in this case a color defined as RGBA (red, green, blue, alpha).
10 The vertices will change for every triangle that is rendered. Therefore the variable, a_Vertex, is an attribute variable. It will get its values from a buffer object. A vec3 is an array of 3 floating point values; (x,y,z).
12-15 The main() function is always the entry point for a shader. This function will be executed once for each vertex in the model.
14 This shader executes a single command on each vertex. It is multiplying a 4x4 matrix times a 4x1 vector using linear algebra matrix mulplication. WebGL shaders have matrix math built-in! Notice that the vertex has only three values (x,y,z) but 4 values are required for the matrix multiplication. The vertex is converted to a 4-component array, with the last component, the w component, set to 1.0.

Fragment Shader

Lines Description
7 The color of the pixels will be the same, so we have one uniform value, u_Color. Because the variable is global and has the same name as a global vertex shader variable, the variable is linked to the vertex shader‘s variable of the same name.
9-11 The main() function is always the entry point for a shader. This function will be executed once for each fragment of the rendered primitive (either a point, line or triangle.)
10 This shader executes a single command on each fragment. It is setting the color of the fragment.

The Buffer Object(s)

The only attribute that is changing to render this model is its vertices. Therefore, we only need one buffer object. Remember that a buffer object is a 1-dimensional, homogeneous array of floating point values. We need to gather up all of the vertices that define the triangles into a single array, and the order of the vertices matter. Before you create the array you need to decide how the triangles will be rendered. There are three choices: gl.TRIANGLE, gl.TRIANGLE_STRIP, or g.TRIANGLE_FAN. In addition, the vertices of each triangle must be in counter-clockwise order when looking at the front of the face. Our pyramid model can be rendered in a single ‘pass’ through the buffer object using either the gl.TRIANGLE or the gl.TRIANGLE_STRIP mode. Let’s keep it simple and use the gl.TRIANGLE mode for this example. (If you changed rendering modes, you would have to change the ordering of the vertices.)

Creating the buffer object is a pre-processing step which needs to happen only once. Let’s break the task into 2 parts:

  • Get the triangle vertices into a 1D array in the correct order. This is done by the _buildBufferObjectData() function in the code shown below.
  • Create and upload the vertices into the GPU’s memory. This is done by the _createBufferObject() function in the code shown below.

Note that these two functions are private functions of the JavaScript class definition and they are only called once when the constructor code is executed.

The 1D array that is created to fill the buffer object contains the data below for this example. Note that each consecutive three values represent one vertex.

[0.5, -0.25, 0.25, 0, 0.25, 0, -0.5, -0.25, 0.25, -0.5, -0.25, 0.25, 0, 0.25, 0, 0, -0.25, -0.5, 0, -0.25, -0.5, 0, 0.25, 0, 0.5, -0.25, 0.25, 0, -0.25, -0.5, 0.5, -0.25, 0.25, -0.5, -0.25, 0.25]
Show: Code Canvas Run Info
./simple_pyramid/simple_pyramid2.html

A simple, manually defined 3D model. You can spin the model with your mouse.

Please use a browser that supports "canvas"
Animate
Shader errors, Shader warnings, Javascript info
Open this webgl program in a new tab or window

Access to Shader Variables

To render a model, you will set some values in your shader program and then issue the gl.drawArrays() command. But you have a problem. Your data is in RAM in your JavaScript program, while your shader program is a compiled program on the GPU. How can you pass data to the shader? This is a 2 step process:

  • Pre-processing: Get the location of a variable in the shader program. This is equivalent to an index that could be used as an array lookup. The location of a variable in a shader program will never change, so the location only needs to be retrieved once.
  • Use a variable’s location in a shader program to update it.

To get a variable’s location in a shader program, use one of the appropriate WebGL functions:

uint getUniformLocation(Object shader_program, string variable_name);
ulong getAttribLocation(Object shader_program, string variable_name);

Examples of these functions can be found in lines 133-135 of the demo code above.

To set a variable’s value in a shader program, use one of the appropriate WebGL functions:

void uniform[1234][fi](uint location, value1, value2, value3, ...);
void uniform[1234][fi]v(uint location, Array values);
void uniformMatrix[234]fv(uint location, bool transpose, Array matrix);

The characters in brackets, [234], represent the number of values to be set. The [fi] means you are setting either a floating point value, f, or an integer value, i. You can send values as discrete variables, or as multiple values in a single array. The functions that use arrays end in a v. Examples of setting the values of shader program variables can be found at lines 158, 161, and 176 in the above demo code.

Linking a Buffer Object to an Attribute Variable

An attribute variable pulls its values from an array. The array will typically be stored in a buffer object. The positions in the array that are used is determined by the 2nd and 3rd parameters in your call to gl.drawArrays(mode, start, count). The start parameter gives the starting array index, while the count parameter specifies how many vertices to use. You must enable the use of ‘vertex arrays’ before you can access values from a buffer object. Linking our a_Vertex attribute variable in our shader program to the buffer object looks like this:

gl.bindBuffer(gl.ARRAY_BUFFER, triangles_vertex_buffer_id);

gl.enableVertexAttribArray(a_Vertex_location);

gl.vertexAttribPointer(a_Vertex_location, 3, gl.FLOAT, false, 0, 0);

First, you must make a particular buffer object active. This is done with the bindBuffer command. Then you enable getting data from buffer objects with the enableVertexAttribArray function. Then you describe the organization of the values in the buffer object using a call to vertexAttribPointer. The 1st parameter, a_Vertex_location, is the location in the compiled shader program of the variable that will be set from the buffer’s values. Let’s delay a formal description of the other parameters until later. For now, the other parameters tell the shader that the vertex data has 3 values per vertex and the values are all floating point.

Rendering

In the example code, the gl.drawArrays(mode, start, count) command is called 5 times. (See lines 171 and 180.) Let’s discuss why.

The shader is written to render with a single color. We want to render a red pyramid with black edges. Therefore we must call drawArrays() at least twice: once when the color is set to red, and a second time after the color has been changed to black. Examine the demo code. Notice that the color is set to red in line 161 and then the triangles are rendered in line 171. Then the color is changed to black in line 176 (the edge_color), and then the same vertices in the buffer object are used to draw the triangle edges using the gl.LINE_LOOP mode. However, this can’t be done with a single draw command because the vertices are not organized that way in the buffer object. So we have to step through the buffer and change the starting index each time.

Summary

In the next few lessons we will modify our model, our shader programs, and our rendering code to produce more sophisticated graphics. It is important that you understand the concepts presented above before proceeding to the next lesson. So make a second pass through this lesson before proceeding.

Next Section - 5.6 - Example 2: One Color per Triangle