WebGL - Cube Rotation
WebGL - Cube Rotation Example
Section titled “WebGL - Cube Rotation Example”This chapter demonstrates drawing a rotating 3D cube using WebGL, incorporating modern JavaScript practices and a standard math library.
Modern Approach: Using a Math Library
Section titled “Modern Approach: Using a Math Library”Manually coding matrix operations (projection, view, model transformations, rotations) is error-prone and verbose. In practice, developers use well-tested math libraries. A popular choice is gl-matrix. We’ll use it in this example. You’d typically include it via a script tag or import it if using a module bundler.
Example – Draw a Rotating 3D Cube
Section titled “Example – Draw a Rotating 3D Cube”The following HTML and JavaScript code set up a WebGL canvas and draw a continuously rotating colored cube.
<!doctype html><html lang="en"><head> <meta charset="utf-8"> <title>WebGL Rotating Cube</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.3.0/gl-matrix-min.js"></script> <style>canvas { border: 1px solid black; }</style></head><body> <canvas width="570" height="570" id="my_Canvas"></canvas>
<script> // Ensure glMatrix library is available const { mat4, vec3 } = glMatrix;
/*============= Creating a canvas =================*/ const canvas = document.getElementById('my_Canvas'); const gl = canvas.getContext('webgl'); // Use 'webgl' or 'webgl2'
if (!gl) { alert('WebGL not supported!'); throw new Error('WebGL not supported'); }
/*============ Defining and storing the geometry =========*/ // Define vertices for the cube faces const vertices = [ // Front face -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, // Back face -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, // Top face -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, // Bottom face -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, // Right face 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, // Left face -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, ];
// Define colors for each vertex (normalized R, G, B) const faceColors = [ [1.0, 0.0, 0.0], // Front face: red [0.0, 1.0, 0.0], // Back face: green [0.0, 0.0, 1.0], // Top face: blue [1.0, 1.0, 0.0], // Bottom face: yellow [1.0, 0.0, 1.0], // Right face: magenta [0.0, 1.0, 1.0], // Left face: cyan ];
// Convert face colors to per-vertex colors let colors = []; for (let j = 0; j < faceColors.length; ++j) { const c = faceColors[j]; // Repeat each color four times for the four vertices of the face colors = colors.concat(c, c, c, c); }
// Define indices for the cube triangles const indices = [ 0, 1, 2, 0, 2, 3, // front 4, 5, 6, 4, 6, 7, // back 8, 9, 10, 8, 10, 11, // top 12, 13, 14, 12, 14, 15, // bottom 16, 17, 18, 16, 18, 19, // right 20, 21, 22, 20, 22, 23, // left ];
// --- Create Buffers --- const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
/*=================== Shaders =========================*/ // Using GLSL ES 1.00 (standard for WebGL 1) const vsSource = ` attribute vec4 aVertexPosition; attribute vec3 aVertexColor;
uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix;
varying lowp vec3 vColor;
void main(void) { gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; vColor = aVertexColor; } `;
const fsSource = ` varying lowp vec3 vColor;
void main(void) { gl_FragColor = vec4(vColor, 1.0); } `;
// Helper function to compile shader function loadShader(gl, type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; }
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
// Create shader program const shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram)); throw new Error('Shader program linking failed'); }
// Store attribute and uniform locations const programInfo = { program: shaderProgram, attribLocations: { vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'), vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'), }, uniformLocations: { projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'), modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'), }, };
/* ====== Associating attributes to vertex shader =====*/ // Tell WebGL how to pull out the positions from the vertex buffer { const numComponents = 3; // pull out 3 values per iteration (x, y, z) const type = gl.FLOAT; // the data in the buffer is 32bit floats const normalize = false; // don't normalize const stride = 0; // how many bytes to get from one set of values to the next // 0 = use type and numComponents above const offset = 0; // how many bytes inside the buffer to start from gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.vertexAttribPointer( programInfo.attribLocations.vertexPosition, numComponents, type, normalize, stride, offset); gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition); }
// Tell WebGL how to pull out the colors from the color buffer { const numComponents = 3; // R, G, B const type = gl.FLOAT; const normalize = false; const stride = 0; const offset = 0; gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.vertexAttribPointer( programInfo.attribLocations.vertexColor, numComponents, type, normalize, stride, offset); gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor); }
// Tell WebGL which indices to use to index the vertices gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.useProgram(programInfo.program);
/*==================== MATRIX Setup =====================*/ const fieldOfView = 45 * Math.PI / 180; // in radians const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; const zNear = 0.1; const zFar = 100.0; const projectionMatrix = mat4.create(); mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
const modelViewMatrix = mat4.create(); // Move the drawing position a bit where we can see it mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -6.0]);
/*================= Drawing ===========================*/ let lastTime = 0; let cubeRotation = 0.0;
function render(now) { now *= 0.001; // convert to seconds const deltaTime = now - lastTime; lastTime = now;
// Rotate the cube mat4.rotate(modelViewMatrix, // destination matrix modelViewMatrix, // matrix to rotate cubeRotation, // amount to rotate in radians [0, 0, 1]); // axis to rotate around (Z) mat4.rotate(modelViewMatrix, modelViewMatrix, cubeRotation * .7, [0, 1, 0]); // Y mat4.rotate(modelViewMatrix, modelViewMatrix, cubeRotation * .3, [1, 0, 0]); // X
cubeRotation += deltaTime;
// --- Drawing --- gl.clearColor(0.5, 0.5, 0.5, 0.9); // Clear to grey, fully opaque gl.clearDepth(1.0); // Clear everything gl.enable(gl.DEPTH_TEST); // Enable depth testing gl.depthFunc(gl.LEQUAL); // Near things obscure far things
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Set the shader uniforms gl.uniformMatrix4fv( programInfo.uniformLocations.projectionMatrix, false, projectionMatrix); gl.uniformMatrix4fv( programInfo.uniformLocations.modelViewMatrix, false, modelViewMatrix);
{ const vertexCount = 36; // 6 faces * 2 triangles/face * 3 vertices/triangle const type = gl.UNSIGNED_SHORT; const offset = 0; gl.drawElements(gl.TRIANGLES, vertexCount, type, offset); }
requestAnimationFrame(render); }
requestAnimationFrame(render);
</script>
</body></html>Expected Result:
This code will render a 3D cube with different colored faces, continuously rotating around multiple axes in the center of the canvas.