Uniforms

In which you learn how to send parameters to a shader.

The fragment shader's job is to set the color of any pixel that is part of a point, line, or triangle. In the applications you've written so far, the color has been hardcoded in the shader using a line like this:

fragmentColor = vec4(0.0, 0.0, 1.0, 1.0);

Every pixel drawn by this shader will be blue. Suppose you want to provide a user interface that allows the user to change the color. You could embed JavaScript expressions inside the shader program, recompile the source code, and reupload the shader program to the GPU. But that's a lot like building a new house just to make it a different color. A cheaper solution is to use uniforms.

A uniform is a parameter that is sent to the shader instead of hardcoded inside it. You add a uniform by declaring a global variable in either your vertex or fragment shader and tagging it with the keyword uniform. This fragment shader, for example, receives a uniform named color:

uniform vec3 color;
out vec4 fragmentColor;

void main() {
  fragmentColor = vec4(color, 1.0);
}

They are called uniforms because they hold steady across all pixels touched by a draw call.

The color uniform in this shader must be set before you issue a draw call. The ShaderProgram class provides a handful of methods for setting uniforms of different types. To set a vec3, which in GLSL is a collection of three floats, you call the setUniform3f method:

// Draw cyan triangles.
shaderProgram.setUniform3f('color', 0, 1, 1);
vao.drawSequence(gl.TRIANGLES);

Though the uniform value will be the same across all pixels of a drawn object, the uniform can be changed between draw calls. For example, this code draws an object first with red triangles and then with blue triangles:

// Draw red triangles.
shaderProgram.setUniform3f('color', 1, 0, 0);
vao.drawSequence(gl.TRIANGLES);

// Draw blue triangles.
shaderProgram.setUniform3f('color', 0, 0, 1);
vao.drawSequence(gl.TRIANGLES);

Exercises

Get uniforms working in your hello-triangles application by completing the following exercises.

Switch the fragment shader to use a color uniform.
Allow the user-developer to test out different colors by adding this function to main.js:
function setColor(r, g, b) {
  shaderProgram.bind();
  shaderProgram.setUniform3f('color', r, g, b);
  shaderProgram.unbind();
  render();
}

// Assignment to window makes a value globally accessible. Without
// it, calling the setColor function from the browser's developer
// tools is difficult.
window.setColor = setColor;
Open your browser's developer tools and call setColor with three parameters in [0, 1].