Skip to content

WebGL - Translation

This chapter explores translation, a fundamental transformation that moves objects within the 3D scene without changing their orientation or size.

Translation involves adding specific distances (offsets) along the X, Y, and Z axes to the coordinates of every vertex in an object. If a vertex is at [x, y, z] and the desired translation is [tx, ty, tz], the new position becomes [x + tx, y + ty, z + tz].

In modern WebGL, translation is typically implemented using matrix multiplication. A translation matrix is created and multiplied with the vertex position vector in the vertex shader.

A standard 4x4 translation matrix looks like this:

1 0 0 tx
0 1 0 ty
0 0 1 tz
0 0 0 1

When a vertex position (x, y, z, 1) is multiplied by this matrix, the result is (x+tx, y+ty, z+tz, 1), effectively translating the point.

Implementing translation involves modifying the vertex shader and passing a translation matrix from JavaScript:

Step 1: Setup Canvas and WebGL Context

Standard initialization: get canvas, get webgl context, check for errors.

Step 2: Define Geometry and Buffers

Define the vertices for the object (e.g., a triangle) and load them into a VBO.

const vertices = new Float32Array([
-0.5, 0.5, 0.0, // Top-left
-0.5, -0.5, 0.0, // Bottom-left
0.5, -0.5, 0.0 // Bottom-right
]);
// ... (Create VBO and bufferData) ...

Step 3: Create and Compile Shaders

Write, compile, and link shaders with error checking.

  • Vertex Shader: Define an attribute for vertex position (a_position) and a uniform mat4 for the translation matrix (u_translationMatrix). Calculate the final position: gl_Position = u_translationMatrix * vec4(a_position, 1.0);.
  • Fragment Shader: Sets the fragment color (gl_FragColor).
// Vertex Shader (GLSL ES 1.00)
attribute vec3 a_position;
uniform mat4 u_translationMatrix;
void main() {
// Apply translation matrix to the vertex position
gl_Position = u_translationMatrix * vec4(a_position, 1.0);
}
// Fragment Shader (GLSL ES 1.00)
precision mediump float;
void main() {
gl_FragColor = vec4(0.2, 0.5, 0.8, 1.0); // Blue color
}

Step 4: Link Shaders and Buffers

Get the attribute location (a_position) and the uniform location (u_translationMatrix). Configure the attribute pointer.

Step 5: Define and Apply Translation Matrix

In JavaScript, define the translation distances (tx, ty, tz). Create the 4x4 translation matrix as a Float32Array (remembering WebGL uses column-major order). Get the uniform location and pass the matrix using gl.uniformMatrix4fv.

Step 6: Draw the Object

Clear the canvas, set viewport, and call gl.drawArrays to render the translated object.

This example draws a blue triangle translated 0.5 units right and 0.3 units up.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebGL Translation Example</title>
<style>
body { margin: 0; overflow: hidden; background-color: #eee; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="webgl-canvas" width="400" height="400"></canvas>
<script>
// --- Shader Sources ---
const vsSource = `
attribute vec3 a_position;
uniform mat4 u_translationMatrix;
void main() {
gl_Position = u_translationMatrix * vec4(a_position, 1.0);
}
`;
const fsSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(0.2, 0.5, 0.8, 1.0); // Blue color
}
`;
// --- Utilities: createShader, createProgram (Assume defined as before) ---
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;
}
// --- 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 ---
const vertices = new Float32Array([
-0.5, 0.5, 0.0,
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0
]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// --- Attribute Setup ---
const positionAttribLocation = gl.getAttribLocation(shaderProgram, 'a_position');
if (positionAttribLocation < 0) console.error("Attr 'a_position' not found");
gl.enableVertexAttribArray(positionAttribLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, 0, 0);
// --- Uniform Setup ---
const translationMatrixLocation = gl.getUniformLocation(shaderProgram, 'u_translationMatrix');
if (!translationMatrixLocation) console.error("Uniform 'u_translationMatrix' not found");
const tx = 0.5; // Translate 0.5 units right
const ty = 0.3; // Translate 0.3 units up
const tz = 0.0; // No translation in Z
// Translation Matrix (Column Major Order)
const translationMatrix = new Float32Array([
1.0, 0.0, 0.0, 0.0, // Column 1
0.0, 1.0, 0.0, 0.0, // Column 2
0.0, 0.0, 1.0, 0.0, // Column 3
tx, ty, tz, 1.0 // Column 4 (Translation values)
]);
gl.useProgram(shaderProgram); // Activate program
gl.uniformMatrix4fv(translationMatrixLocation, false, translationMatrix);
// --- Drawing ---
gl.clearColor(0.9, 0.9, 0.9, 1.0); // Light gray background
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 3);
</script>
</body>
</html>

Expected Output:

The code will render a blue triangle. Instead of being centered around the origin (like the raw vertex data), it will be shifted to the right by 0.5 units and upwards by 0.3 units in clip space.

When combining multiple transformations (like scaling, rotation, and translation), the order in which you multiply the matrices is critical. The standard order is typically Scale -> Rotate -> Translate, applied to the vertex as gl_Position = Projection * View * Translation * Rotation * Scale * VertexPosition;. Using a math library like gl-matrix can greatly simplify managing these transformations.

  • Experiment with different values for tx, ty, and tz.
  • Try animating the translation by updating the tx or ty value in a requestAnimationFrame loop and re-applying the uniform matrix before drawing each frame.
  • Combine this translation matrix with a scaling or rotation matrix (requires multiplying matrices together before sending the result as a single uniform).