uniformMatrix*()

Sending Matrices with uniformMatrix*()

uniformMatrix{2|3|4}fv(location, transpose, values) uploads a matrix; WebGL 2 adds non-square forms such as uniformMatrix2x3fv(). The values are column-major: a mat4's first four numbers are its first column, so a translation sits in elements 12 to 14. WebGL 1 requires transpose to be false (true raises INVALID_VALUE); WebGL 2 accepts true and reads rows instead. Transposing a rotation inverts it:

One rotation array uploaded column-major (left) and transposed (right)HTML
<canvas id="c" width="960" height="240" style="width:480px;height:120px"></canvas>
<script src="webgl-helper.js"></script>
<script>
const gl = GLH.context('c');
const { program, uniforms } = GLH.load(gl, `#version 300 es
  uniform mat4 uMatrix; uniform vec2 uOffset; out vec2 vUV;
  void main() {                                    // a quad without a buffer
    vUV = vec2[6](vec2(0), vec2(1, 0), vec2(1), vec2(0), vec2(1), vec2(0, 1))[gl_VertexID];
    vec4 p = uMatrix * vec4((vUV - 0.5) * vec2(0.7, 1.2), 0.0, 1.0);
    gl_Position = vec4(p.xy * vec2(0.25, 1.0) + uOffset, 0.0, 1.0);   // canvas is 4:1
  }`, `#version 300 es
  precision mediump float; in vec2 vUV; out vec4 c;
  void main() { c = vec4(vec3(0.71, 0.27, 0.18) * (vUV.x < 0.1 ? 0.6 : 1.0), 1.0); }`);
const a = 25 * Math.PI / 180, cos = Math.cos(a), sin = Math.sin(a);
const rotation = [cos, sin, 0, 0,  -sin, cos, 0, 0,  0, 0, 1, 0,  0, 0, 0, 1];   // 4 columns
for (const [transpose, x] of [[false, -0.5], [true, 0.5]]) {
  gl.uniformMatrix4fv(gl.getUniformLocation(program, 'uMatrix'), transpose, rotation);
  uniforms.uOffset([x, 0.0]);
  gl.drawArrays(gl.TRIANGLES, 0, 6);
}
window.__done = true;
</script>
Browser output of Listing 3.31
Browser output of 31

The left cover turns 25 degrees counterclockwise, as intended; the transposed upload turns it clockwise. Matrix literals typed row by row are a classic bug.

One rotation array uploaded column-major (left) and transposed (right)HTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 12px system-ui, sans-serif; background: #f7f4ee; color: #333; }
  canvas { display: block; width: 100%; max-width: 480px; height: auto; }
  .names { display: flex; max-width: 480px; text-align: center; font-family: monospace; font-size: 11px; }
  .names div { flex: 1; padding: 4px 0; }
</style>
<canvas id="c" width="960" height="240"></canvas>
<div class="names"><div>uniformMatrix4fv(loc, false, m)</div><div>uniformMatrix4fv(loc, true, m)</div></div>
<script>
const gl = document.getElementById('c').getContext('webgl2');
gl.clearColor(0.93, 0.91, 0.87, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, `#version 300 es
  uniform mat4 uMatrix; uniform vec2 uOffset; out vec2 vUV;
  void main() {                                    // a quad without a buffer
    vUV = vec2[6](vec2(0), vec2(1, 0), vec2(1), vec2(0), vec2(1), vec2(0, 1))[gl_VertexID];
    vec4 p = uMatrix * vec4((vUV - 0.5) * vec2(0.7, 1.2), 0.0, 1.0);
    gl_Position = vec4(p.xy * vec2(0.25, 1.0) + uOffset, 0.0, 1.0);   // canvas is 4:1
  }`], [gl.FRAGMENT_SHADER, `#version 300 es
  precision mediump float; in vec2 vUV; out vec4 c;
  void main() { c = vec4(vec3(0.71, 0.27, 0.18) * (vUV.x < 0.1 ? 0.6 : 1.0), 1.0); }`]]) {
  const s = gl.createShader(type);
  gl.shaderSource(s, src); gl.compileShader(s);
  if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
  gl.attachShader(program, s);
}
gl.linkProgram(program);
gl.useProgram(program);
gl.bindVertexArray(gl.createVertexArray());

const a = 25 * Math.PI / 180, cos = Math.cos(a), sin = Math.sin(a);
const rotation = [cos, sin, 0, 0,  -sin, cos, 0, 0,  0, 0, 1, 0,  0, 0, 0, 1];   // 4 columns
const uMatrix = gl.getUniformLocation(program, 'uMatrix');
const uOffset = gl.getUniformLocation(program, 'uOffset');
for (const [transpose, x] of [[false, -0.5], [true, 0.5]]) {
  gl.uniformMatrix4fv(uMatrix, transpose, rotation);   // WebGL 2 accepts transpose = true
  gl.uniform2f(uOffset, x, 0.0);
  gl.drawArrays(gl.TRIANGLES, 0, 6);
}
</script>