Skip to content

WebGL - Rotation

Rotation is another fundamental transformation used to orient objects in 3D space. This chapter demonstrates how to rotate a simple object (a triangle) around an axis using WebGL.

Rotation involves changing the orientation of an object around a specific axis (X, Y, or Z, or an arbitrary axis) by a certain angle. Like scaling and translation, rotation is implemented in WebGL using matrix multiplication.

Separate rotation matrices exist for rotating around the X, Y, and Z axes. For example, a rotation by angle θ around the Z-axis is represented by:

cos(θ) -sin(θ) 0 0
sin(θ) cos(θ) 0 0
0 0 1 0
0 0 0 1

Matrices for X and Y rotations have similar structures involving sine and cosine of the angle. These matrices are multiplied by the vertex position vectors in the vertex shader.

In typical 3D applications, rotation is part of the Model matrix, which transforms vertices from the object’s local space (Model Space) into the scene’s global space (World Space). This Model matrix (often containing scale, rotation, and translation) is then combined with the View matrix (representing the camera’s position and orientation) and the Projection matrix (defining the camera’s lens properties, like field-of-view and perspective).

The final vertex position in the vertex shader is calculated as: gl_Position = ProjectionMatrix * ViewMatrix * ModelMatrix * VertexPosition;

  • Setup: Initialize WebGL, define geometry (vertices, indices, potentially colors), create buffers.
  • Shaders:
    • Vertex Shader: Define attribute(s) for position (and color, etc.). Define uniform mat4 variables for Model, View, and Projection matrices. Calculate gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aPosition, 1.0);. Pass color (if used) to fragment shader via a varying.
    • Fragment Shader: Receive interpolated color (if used) and set gl_FragColor.
  • Attribute/Uniform Locations: Get locations for attributes and the MVP matrix uniforms.
  • Matrix Creation (JS): Define Projection matrix (e.g., perspective). Define View matrix (camera position/target). Define Model matrix, initially often an identity matrix. Create rotation functions (e.g., rotateZ(matrix, angle)) that modify the Model matrix.
  • Animation Loop: Use requestAnimationFrame for smooth animation.
    • Inside the loop: Calculate time delta (dt). Update rotation angle based on dt.
    • Apply rotation to the Model matrix using your rotation function.
    • Update Uniforms: Send the updated Model, View, and Projection matrices to the shader using gl.uniformMatrix4fv.
    • Draw: Clear canvas, bind buffers, and call gl.drawElements or gl.drawArrays.

This example rotates a white triangle around the Z-axis.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebGL Rotation Example</title>
<style>
body { margin: 0; overflow: hidden; background-color: #111; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="webgl-canvas" width="500" height="500"></canvas>
<script>
// --- Shader Sources ---
const vsSource = `
attribute vec3 a_position;
attribute vec3 a_color; // Added color attribute
uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;
varying lowp vec4 v_color; // Pass color to fragment shader
void main() {
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * vec4(a_position, 1.0);
v_color = vec4(a_color, 1.0);
}
`;
const fsSource = `
precision mediump float;
varying lowp vec4 v_color; // Receive color
void main() {
gl_FragColor = v_color;
}
`;
// --- Utilities: createShader, createProgram (Assume defined) ---
function createShader(gl, type, source) { /* ... */
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(`Shader compilation error (${type === gl.VERTEX_SHADER ? 'Vertex' : 'Fragment'}):`, gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, vsSource, fsSource) { /* ... */
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
if (!vertexShader || !fragmentShader) return null;
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Program linking error:', gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
// --- Matrix Utility Functions (Simplified - Use gl-matrix for production) ---
function createIdentityMatrix() {
return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}
function createPerspectiveMatrix(fieldOfViewRadians, aspect, near, far) {
const f = 1.0 / Math.tan(fieldOfViewRadians / 2);
const rangeInv = 1 / (near - far);
return new Float32Array([
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (near + far) * rangeInv, -1,
0, 0, near * far * rangeInv * 2, 0
]);
}
function translateMatrix(matrix, tx, ty, tz) {
// Simple translation - Creates a *new* matrix for this example
// In place modification is more common in libraries
const result = matrix.slice(); // Copy
result[12] = matrix[0] * tx + matrix[4] * ty + matrix[8] * tz + matrix[12];
result[13] = matrix[1] * tx + matrix[5] * ty + matrix[9] * tz + matrix[13];
result[14] = matrix[2] * tx + matrix[6] * ty + matrix[10] * tz + matrix[14];
result[15] = matrix[3] * tx + matrix[7] * ty + matrix[11] * tz + matrix[15];
return result;
}
function rotateZMatrix(matrix, angleRadians) {
// Simple Z rotation - Creates a *new* matrix
const result = matrix.slice(); // Copy
const c = Math.cos(angleRadians);
const s = Math.sin(angleRadians);
const m00 = result[0], m01 = result[1], m04 = result[4], m05 = result[5];
result[0] = m00 * c + m04 * s;
result[1] = m01 * c + m05 * s;
result[4] = m00 * -s + m04 * c;
result[5] = m01 * -s + m05 * c;
return result;
}
// --- Main WebGL ---
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');
if (!gl) { alert('WebGL not supported.'); throw new Error('WebGL not supported'); }
const shaderProgram = createProgram(gl, vsSource, fsSource);
if (!shaderProgram) throw new Error('Failed program creation.');
// --- Geometry & Color Data ---
const triangleVertices = new Float32Array([
0.0, 0.6, 0.0, // Top vertex
-0.5, -0.4, 0.0, // Bottom left
0.5, -0.4, 0.0 // Bottom right
]);
const triangleColors = new Float32Array([
1.0, 1.0, 1.0, // White
1.0, 1.0, 1.0, // White
1.0, 1.0, 1.0 // White
]);
const triangleIndices = new Uint16Array([0, 1, 2]);
// --- Buffers ---
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, triangleVertices, gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, triangleColors, gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, triangleIndices, gl.STATIC_DRAW);
// --- Attribute Setup ---
const positionAttribLocation = gl.getAttribLocation(shaderProgram, 'a_position');
const colorAttribLocation = gl.getAttribLocation(shaderProgram, 'a_color');
if (positionAttribLocation < 0 || colorAttribLocation < 0) console.error("Attribute not found");
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttribLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.vertexAttribPointer(colorAttribLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(colorAttribLocation);
// --- Uniform Locations ---
const projMatrixLocation = gl.getUniformLocation(shaderProgram, 'u_projectionMatrix');
const viewMatrixLocation = gl.getUniformLocation(shaderProgram, 'u_viewMatrix');
const modelMatrixLocation = gl.getUniformLocation(shaderProgram, 'u_modelMatrix');
if (!projMatrixLocation || !viewMatrixLocation || !modelMatrixLocation) console.error("Uniform not found");
// --- Matrices ---
const fieldOfView = 45 * Math.PI / 180; // 45 degrees in radians
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.1;
const zFar = 100.0;
const projectionMatrix = createPerspectiveMatrix(fieldOfView, aspect, zNear, zFar);
let viewMatrix = createIdentityMatrix();
viewMatrix = translateMatrix(viewMatrix, 0.0, 0.0, -3.0); // Move camera back
let modelMatrix = createIdentityMatrix();
// --- Animation Loop ---
let lastTime = 0;
let rotationAngle = 0;
function render(currentTime) {
currentTime *= 0.001; // Convert time to seconds
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
// Update rotation
rotationAngle += deltaTime * 1.0; // Rotate 1 radian per second
// Recalculate model matrix (start from identity each frame for simplicity here)
modelMatrix = createIdentityMatrix();
modelMatrix = rotateZMatrix(modelMatrix, rotationAngle);
// --- Drawing ---
gl.clearColor(0.05, 0.05, 0.1, 1.0); // Dark blue background
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(shaderProgram); // Use program
// Set matrices
gl.uniformMatrix4fv(projMatrixLocation, false, projectionMatrix);
gl.uniformMatrix4fv(viewMatrixLocation, false, viewMatrix);
gl.uniformMatrix4fv(modelMatrixLocation, false, modelMatrix);
// Bind index buffer and draw
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render); // Start the loop
</script>
</body>
</html>

Expected Output:

The code will display a white triangle centered on a dark blue background. The triangle will continuously rotate counter-clockwise around its center (which coincides with the Z-axis in this setup).

  • Create functions for X and Y rotation and apply them.
  • Change the axis of rotation by applying translations before and after the rotation in the Model matrix calculation.
  • Use a math library like gl-matrix for more robust and efficient matrix operations.
  • Rotate a more complex object, like the cube from the next chapter.