Uniforms
Sometimes we want data that doesn't vary across a triangle like vertex properties do. For instance, perhaps we want a triangle that is a solid color. Bad news: there's no way to define data on a per-triangle basis with WebGL. However, we can define data on a per-draw basis using uniforms. They are called uniforms because they don't vary; their values are uniform across all vertices and fragments of the rendered model.
Suppose we want to render a triangle that is sometimes all red and sometimes all blue. In the fragment shaders we've written so far, we've assigned fragmentColor
in two different ways. It was either hardcoded, like this:
out vec4 fragmentColor;
void main() {
fragmentColor = vec4(0.0, 0.0, 1.0, 1.0);
}
out vec4 fragmentColor; void main() { fragmentColor = vec4(0.0, 0.0, 1.0, 1.0); }
Or it was assigned an interpolated value:
in vec3 mixColor;
out vec4 fragmentColor;
void main() {
fragmentColor = vec4(mixColor, 1.0);
}
in vec3 mixColor; out vec4 fragmentColor; void main() { fragmentColor = vec4(mixColor, 1.0); }
The problem with hardcoding the color is that we can't change it without editing the source code and recompiling the shader. The problems with interpolating are that we need to redundantly store an identical color for each vertex, and we waste computation on blending identical values. Uniforms avoid all these problems. They parameterize the shaders, consume only a small amount of space, and are passed directly to both shaders without any interpolation.
We add a uniform by declaring a global variable in either the 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);
}
uniform vec3 color; out vec4 fragmentColor; void main() { fragmentColor = vec4(color, 1.0); }
The vertex shader doesn't need to declare the uniform or pass it through for the fragment shader to receive it.
The color
uniform must be set in the renderer before we issue a draw call. The ShaderProgram
class provides a handful of methods for setting uniforms of different types. To set a vec3
, we call the setUniform3f
method:
// Draw cyan triangles.
shader.bind();
shader.setUniform3f('color', 0, 1, 1);
// ...
vao.drawSequence(gl.TRIANGLES);
// Draw cyan triangles. shader.bind(); shader.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 object can be drawn again with different values for its uniforms. For example, this code draws an object first with red triangles and then with blue triangles:
// Draw red triangles.
shader.setUniform3f('color', 1, 0, 0);
vao.drawSequence(gl.TRIANGLES);
// Draw blue triangles.
shader.setUniform3f('color', 0, 0, 1);
vao.drawSequence(gl.TRIANGLES);
// Draw red triangles. shader.setUniform3f('color', 1, 0, 0); vao.drawSequence(gl.TRIANGLES); // Draw blue triangles. shader.setUniform3f('color', 0, 0, 1); vao.drawSequence(gl.TRIANGLES);