Skip to content

WebGL - Scaling

This chapter demonstrates how to scale (resize) objects, specifically a triangle, using WebGL transformations.

Scaling involves changing the size of an object by multiplying its vertex coordinates by scaling factors. For instance, multiplying the vertices [x, y, z] by a factor of 2 results in [2x, 2y, 2z], doubling the object’s size. Scaling can be uniform (same factor for x, y, z) or non-uniform (different factors).

In WebGL, scaling is typically achieved using a transformation matrix. We create a 4x4 matrix containing the desired scaling factors along its diagonal and multiply the object’s vertex positions by this matrix within the vertex shader. This operation modifies the final position (gl_Position) of each vertex.

A typical scaling matrix looks like this:

Sx 0 0 0
0 Sy 0 0
0 0 Sz 0
0 0 0 1

Where Sx, Sy, and Sz are the scaling factors along the X, Y, and Z axes, respectively.

Implementing scaling in WebGL involves these key steps:

Step 1: Setup Canvas and WebGL Context

Obtain the HTML canvas element and acquire the WebGL rendering context using canvas.getContext('webgl'). Add checks to ensure the context is successfully obtained.

Step 2: Define Geometry and Buffers

Define the vertex coordinates for the object (e.g., a triangle) using JavaScript arrays. Create WebGL buffer objects (VBOs) and load the vertex data into them using gl.bufferData with Float32Array.

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
]);

Step 3: Create and Compile Shaders

Write, compile, and link the vertex and fragment shaders into a WebGL program. Include error checking for compilation and linking.

  • Vertex Shader: Define an attribute for vertex coordinates (e.g., a_position) and a uniform for the scaling matrix (e.g., u_scaleMatrix). Multiply the position by the matrix: gl_Position = u_scaleMatrix * a_position;. Remember that vertex positions are often vec3, but need to be vec4 for matrix multiplication (use vec4(a_position, 1.0)).
// Vertex Shader (GLSL ES 1.00)
attribute vec3 a_position;
uniform mat4 u_scaleMatrix;
void main() {
gl_Position = u_scaleMatrix * vec4(a_position, 1.0);
}
  • Fragment Shader: Set the output color using gl_FragColor. Define precision using precision mediump float;.
// Fragment Shader (GLSL ES 1.00)
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.5, 0.0, 1.0); // Orange color
}

Step 4: Link Shaders and Buffers

Get the location of the attribute (a_position) and the uniform (u_scaleMatrix). Enable the vertex attribute array and configure it using gl.vertexAttribPointer to read data from the bound VBO.

Step 5: Define and Apply Scaling Matrix

Define the scaling factors (e.g., scaleX, scaleY, scaleZ). Create the 4x4 scaling matrix as a Float32Array in column-major order. Pass this matrix to the vertex shader using gl.uniformMatrix4fv.

Step 6: Draw the Object

Clear the canvas, set the viewport, and use gl.drawArrays(gl.TRIANGLES, 0, 3) to render the scaled triangle.

This example demonstrates scaling a triangle to be 1.5 times taller (Y-axis scaling).

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebGL Scaling Example</title>
<style>
body { margin: 0; overflow: hidden; background-color: #555; }
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_scaleMatrix;
void main() {
gl_Position = u_scaleMatrix * vec4(a_position, 1.0);
}
`;
const fsSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(0.0, 0.7, 0.2, 1.0); // Green color
}
`;
// --- Main WebGL Initialization ---
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');
if (!gl) {
alert('WebGL is not supported by your browser.');
throw new Error('WebGL not supported');
}
// --- Shader Compilation & Linking ---
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:', 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;
}
const shaderProgram = createProgram(gl, vsSource, fsSource);
if (!shaderProgram) throw new Error('Failed to create shader program.');
// --- Geometry and Buffers ---
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
]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// --- Attribute Location and Configuration ---
const positionAttribLocation = gl.getAttribLocation(shaderProgram, 'a_position');
if (positionAttribLocation < 0) {
console.error("Attribute 'a_position' not found");
}
gl.enableVertexAttribArray(positionAttribLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // Re-bind necessary before vertexAttribPointer
gl.vertexAttribPointer(
positionAttribLocation, // Attribute location
3, // Number of components per attribute (x, y, z)
gl.FLOAT, // Type of data
false, // Normalized?
0, // Stride (0 = auto)
0 // Offset (0 = start)
);
// --- Uniform Location and Scaling Matrix ---
const scaleMatrixLocation = gl.getUniformLocation(shaderProgram, 'u_scaleMatrix');
if (!scaleMatrixLocation) {
console.error("Uniform 'u_scaleMatrix' not found");
}
const scaleX = 1.0;
const scaleY = 1.5; // Scale Y by 1.5
const scaleZ = 1.0;
// Scaling Matrix (Column Major Order for WebGL)
const scaleMatrix = new Float32Array([
scaleX, 0.0, 0.0, 0.0,
0.0, scaleY, 0.0, 0.0,
0.0, 0.0, scaleZ, 0.0,
0.0, 0.0, 0.0, 1.0
]);
gl.useProgram(shaderProgram); // Activate the program *before* setting uniforms
gl.uniformMatrix4fv(scaleMatrixLocation, false, scaleMatrix);
// --- Drawing ---
gl.clearColor(0.1, 0.1, 0.1, 1.0); // Dark gray background
gl.enable(gl.DEPTH_TEST); // Enable depth testing for 3D
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.drawArrays(
gl.TRIANGLES, // Primitive type
0, // Starting index
3 // Number of vertices to draw
);
</script>
</body>
</html>

Expected Output:

The code will render a green triangle centered horizontally. Compared to an unscaled triangle (which would fit roughly within a square from y=-0.5 to y=0.5), this triangle will appear stretched vertically, extending from y = -0.75 to y = 0.75 due to the scaleY = 1.5 factor.

  • Try changing the scaleX, scaleY, and scaleZ values to see different scaling effects.
  • Combine scaling with translation and rotation by multiplying their respective matrices. Remember that matrix multiplication order matters! (Typically Scale -> Rotate -> Translate).
  • For complex transformations, consider using a math library like gl-matrix (https://github.com/toji/gl-matrix) to simplify matrix operations.