WebGL Essentials

Uniform Variables

In the previous article we have learnt how to specify transformation matrices in the vertex shader: the GLSL code contained all the data required for 3D transformations - six parameters of the viewing volume, an angle of rotation for the model matrix and a translation vector for the view matrix. Now we'll consider the second scenario: the uniform storage qualifier will be used to declare a global variable in the shader code, and JavaScript will pass a value of the variable to the shader.

Let's rewrite our demo shader. This time the angle of rotation is not initialized in GLSL: instead, It will await its value from JavaScript.

<script id="vertex-shader" type="x-shader/x-vertex">
 attribute vec3 position;
 attribute vec3 color;
 varying highp vec3 colour;

 mat4 projectionMatrix;
 mat4 viewMatrix;
 mat4 modelMatrix;

 uniform float angle;

 // function declarations
 mat4 computeProjectionMatrix();
 mat4 computeViewMatrix();
 mat4 computeModelMatrix();

 void main() {
  projectionMatrix = computeProjectionMatrix();
  viewMatrix = computeViewMatrix();
  modelMatrix = computeModelMatrix();
  gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
  colour = color;
 }

 . . . function definitions . . .

</script>

GLSL functions for projection and view matrices remain the same. The routine for computing the model matrix has changed: the angle variable is not local any more.

mat4 computeModelMatrix() {
 float m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44;

 m11 = cos(radians(angle));
 m12 = sin(radians(angle));
 m13 = 0.0;
 m14 = 0.0;

 m21 = -sin(radians(angle));
 m22 = cos(radians(angle));
 m23 = 0.0;
 m24 = 0.0;

 m31 = 0.0;
 m32 = 0.0;
 m33 = 1.0;
 m34 = 0.0;

 m41 = 0.0;
 m42 = 0.0;
 m43 = 0.0;
 m44 = 1.0;

 return mat4(
  vec4(m11, m12, m13, m14),
  vec4(m21, m22, m23, m24),
  vec4(m31, m32, m33, m34),
  vec4(m41, m42, m43, m44)
 );
}

JavaScript code will require modification, too. First the client script finds out the location of the uniform variable within the program object, then the uniform() command passes the value of the angle to the program:

gl.uniform1f(gl.getUniformLocation(program, 'angle'), 45.0);

Two supplementary commands can be used to obtain information about uniforms. The getActiveUniform() method returns a WebGLActiveInfo object describing uniform variables declared in the shader:

// "angle"
console.log(gl.getActiveUniform(program, 0).name);

The value of the named uniform is retrieved by the getUniform() function:

// 45
console.log(gl.getUniform(program, gl.getUniformLocation(program, 'angle')));

Application Interactivity

Uniforms are essential for creating interactive 3D scenes: the WebGL canvas can react to the user action by changing the value of a uniform variable; as a result, 3D objects move, rotate or change their dimensions dynamically.

For demonstration, the code below increases the angle of rotation by 10 degrees with every click. The click event handler is an arrow function:

// initial value
gl.uniform1f(gl.getUniformLocation(program, 'angle'), 0.0);

gl.canvas.addEventListener(
 'click',
 () => {
  var angle = gl.getUniform(program, gl.getUniformLocation(program, 'angle'));
  angle = angle + 10.0;
  if (angle > 360.0){
   angle = angle - 360.0;
  }
  gl.uniform1f(gl.getUniformLocation(program, 'angle'), angle);
  renderScene();
 },
 false
);

Animation Effects

In animated 3D scenes, uniform variables are usually combined with JavaScript timers. Besides, a special API for requesting animation frames can be brought into play to optimize application performance.

To illustrate the use of uniforms in animation cycles, our demo app will rotate the triangle about the y axis. The animation effect will be repeated until the user clicks the canvas.

Rotation about the y ray requires redefinition of the computeModelMatrix() function in GLSL:

mat4 computeModelMatrix() {
 float m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44;

 m11 = cos(radians(angle));
 m12 = 0.0;
 m13 = -sin(radians(angle));
 m14 = 0.0;

 m21 = 0.0;
 m22 = 1.0;
 m23 = 0.0;
 m24 = 0.0;

 m31 = sin(radians(angle));
 m32 = 0.0;
 m33 = cos(radians(angle));
 m34 = 0.0;

 m41 = 0.0;
 m42 = 0.0;
 m43 = 0.0;
 m44 = 1.0;

 return mat4(
  vec4(m11, m12, m13, m14),
  vec4(m21, m22, m23, m24),
  vec4(m31, m32, m33, m34),
  vec4(m41, m42, m43, m44)
 );
}

A global handle variable is declared in JavaScript to store a value identifying the entry in the list of animation frame request callbacks. The prepareScene() function is also modified: It specifies the primary value of the uniform variable and creates an anonymous event handler for cancelling animation frames. In addition, the function binds the previously created vertex buffer object:

var handle;
. . .

function prepareScene() {
 gl.clearColor(0.0, 0.0, 0.0, 0.0);
 gl.clearDepth(1.0);
 gl.enable(gl.DEPTH_TEST);
 gl.depthFunc(gl.LEQUAL);
 gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
 gl.uniform1f(gl.getUniformLocation(program, 'angle'), 0.0);
 gl.canvas.addEventListener(
  'click',
  () => cancelAnimationFrame(handle),
  false
 );
 renderScene();
}

The renderScene() function gets the value of the uniform variable, increases it and sends it back to the vertex shader:

function renderScene() {
 gl.clear(gl.COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);
 gl.drawArrays(gl.TRIANGLES, 0, 3);
 var angle = gl.getUniform(program, gl.getUniformLocation(program, 'angle'));
 angle = angle + 1.0;
 if (angle > 360.0) {
  angle = angle - 360.0;
 }
 gl.uniform1f(gl.getUniformLocation(program, 'angle'), angle);
 handle = requestAnimationFrame(renderScene);
}

rotation about the y axis: animation frames