WebGL - Rotation
WebGL - Rotation
Section titled “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.
Understanding Rotation
Section titled “Understanding Rotation”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 0sin(θ) cos(θ) 0 0 0 0 1 0 0 0 0 1Matrices 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.
Model-View-Projection (MVP) Matrices
Section titled “Model-View-Projection (MVP) Matrices”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;
Core Steps for Rotation
Section titled “Core Steps for Rotation”- Setup: Initialize WebGL, define geometry (vertices, indices, potentially colors), create buffers.
- Shaders:
-
- Vertex Shader: Define attribute(s) for position (and color, etc.). Define
uniform mat4variables for Model, View, and Projection matrices. Calculategl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aPosition, 1.0);. Pass color (if used) to fragment shader via avarying.
- Vertex Shader: Define attribute(s) for position (and color, etc.). Define
-
- Fragment Shader: Receive interpolated color (if used) and set
gl_FragColor.
- Fragment Shader: Receive interpolated color (if used) and set
- 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
requestAnimationFramefor smooth animation. -
- Inside the loop: Calculate time delta (
dt). Update rotation angle based ondt.
- Inside the loop: Calculate time delta (
-
- 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.
- Update Uniforms: Send the updated Model, View, and Projection matrices to the shader using
-
- Draw: Clear canvas, bind buffers, and call
gl.drawElementsorgl.drawArrays.
- Draw: Clear canvas, bind buffers, and call
Example: Rotating a Triangle
Section titled “Example: Rotating a Triangle”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).
Further Exploration:
Section titled “Further Exploration:”- 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-matrixfor more robust and efficient matrix operations. - Rotate a more complex object, like the cube from the next chapter.