Open In App

How to Use Matrices for Transformations in WebGL?

Last Updated : 21 Aug, 2024
Comments
Improve
Suggest changes
1 Likes
Like
Report

Matrices in WebGL are essential for performing geometric transformations, such as translation, rotation, and scaling, on objects. They enable you to manipulate the position, orientation, and size of objects within a scene by applying linear transformations through matrix multiplication. Understanding how to use and combine these matrices is important for creating dynamic and interactive graphics.

These are the following approaches:

Manually Computing Transformation Matrices

  • Select the canvas element and get the WebGL context (gl). Check if WebGL is supported; log an error if not.
  • Vertex Shader: Determines vertex positions using a transformation matrix (u_matrix).
  • Fragment Shader: Sets the color of each pixel (u_color). Compile vertex and fragment shaders.
  • Create a WebGL program by attaching and linking these shaders. Define vertices for a square and store them in a Float32Array.
  • Create and bind a buffer, then upload the vertex data. Retrieve locations for a_position, u_matrix, and u_color in the shader program.
  • Set the color (u_color) and configure vertex attributes. Create a transformation matrix using translateX and translateY values.
  • Send the matrix to the shader and redraw the square. Call updateMatrix to draw the square initially.
  • Add event listeners to update the matrix and redraw the square when translateX or translateY values change.

Example: The below example performs Manually Computing Transformation Matrices.

HTML
<!DOCTYPE html>
<html>

<head>
    <title>WebGL Matrix Transformations</title>
    <style>
        body {
            margin: 0;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-color: black;
            color: white;
            text-align: center;
        }

        h1 {
            color: green;
        }

        canvas {
            border: 1px solid white;
        }

        .controls {
            margin-top: 20px;
        }

        .control-label {
            margin-right: 10px;
        }
    </style>
</head>

<body>
    <div>
        <h1>GeeksforGeeks</h1>
        <h3>Manually Computing Transformation Matrices</h3>
        <canvas id="webgl-canvas" width="400" height="400"></canvas>
        <div class="controls">
            <div>
                <label class="control-label">Translate X:</label>
                <input type="range"
                       id="translateX" min="-1" 
                       max="1" step="0.01"
                       value="0">
                <span id="translateXValue">0</span>
            </div>
            <div>
                <label class="control-label">Translate Y:</label>
                <input type="range" id="translateY"
                       min="-1" max="1" step="0.01"
                       value="0">
                <span id="translateYValue">0</span>
            </div>
        </div>
    </div>
    <script src="script.js"></script>
</body>

</html>
JavaScript
// Get the canvas element and initialize WebGL context
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');

// Check if WebGL is supported
if (!gl) {
    console.error('WebGL not supported');
}

// Vertex shader source code: this 
// determines the position of each vertex
const vsSource = `
    attribute vec4 a_position;
    uniform mat4 u_matrix;
    void main() {
        // Multiply the position by the 
        // matrix to transform the vertices
        gl_Position = u_matrix * a_position;
    }
`;

// Fragment shader source code:
// this determines the color of each pixel
const fsSource = `
    precision mediump float;
    uniform vec4 u_color;
    void main() {
        // Set the fragment color to the uniform value
        gl_FragColor = u_color;
    }
`;

// Function to compile a shader from its source code
function compileShader(gl, source, type) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    // Check for compilation errors
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error(gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

// Function to create a WebGL program 
//  with a vertex and fragment shader
function createProgram(gl, vsSource, fsSource) {
    const vertexShader = compileShader(gl,
        vsSource, gl.VERTEX_SHADER);
    const fragmentShader = compileShader(gl,
        fsSource, gl.FRAGMENT_SHADER);
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    // Check for linking errors
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        console.error(gl.getProgramInfoLog(program));
        return null;
    }
    return program;
}

// Create and use the WebGL program
const program = createProgram(gl, vsSource, fsSource);
gl.useProgram(program);

// Define the vertices of a square (two triangles)
const vertices = new Float32Array([
    -0.5, -0.5,
    0.5, -0.5,
    -0.5, 0.5,
    0.5, 0.5,
]);

// Create a buffer to hold the vertex data
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

// Get the location of the attributes and uniforms in the shader program
const positionLocation = gl.getAttribLocation(program, 'a_position');
const matrixLocation = gl.getUniformLocation(program, 'u_matrix');
const colorLocation = gl.getUniformLocation(program, 'u_color');

// Set the color uniform to red
gl.uniform4f(colorLocation, 1, 0, 0, 1);

// Bind the vertex buffer and set up the attribute pointers
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);

// Function to update the transformation
// matrix and redraw the scene
function updateMatrix() {
    // Get the translation values from the input elements
    const translateX = parseFloat(document.
        getElementById('translateX').value);
    const translateY = parseFloat(document.
        getElementById('translateY').value);

    // Create a translation matrix
    const matrix = new Float32Array([
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        translateX, translateY, 0, 1
    ]);

    // Set the matrix uniform in the shader
    gl.uniformMatrix4fv(matrixLocation, false, matrix);

    // Clear the canvas and redraw the square
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}

// Initial draw of the scene
updateMatrix();

// Add event listeners to update the matrix when the sliders change
document.getElementById('translateX')
    .addEventListener('input', (event) => {
        document.getElementById('translateXValue')
            .textContent = event.target.value;
        updateMatrix();
    });

document.getElementById('translateY')
    .addEventListener('input', (event) => {
        document.getElementById('translateYValue')
            .textContent = event.target.value;
        updateMatrix();
    });

Output:

1
Output

Using gl-matrix for Complex Transformations

  • Get the canvas and WebGL context (gl). Check if WebGL is supported. Vertex shader determines vertex positions using a transformation matrix (u_matrix).
  • Fragment shader sets pixel color using a uniform value (u_color). Compile vertex and fragment shaders.
  • Create and link a WebGL program. Define square vertices using two triangles. Create a buffer for vertex data and upload it.
  • Retrieve locations for attributes (a_position) and uniforms (u_matrix, u_color).
  • Set the color uniform to green. Get translation, scale, and rotation values from user inputs.
  • Create and apply transformations to the matrix. Update the matrix uniform in the shader and redraw the square.
  • Call updateMatrix to draw the square initially. Add event listeners to update the matrix and redraw the scene when user inputs change.

Example: The below example uses gl-matrix for Complex Transformations.

HTML
<!DOCTYPE html>
<html>
<head>
    <title>WebGL Matrix Transformations with gl-matrix</title>
    <style>
        body {
            margin: 0;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-color: black;
            color: white;
            text-align: center;
        }

        h1 {
            color: green;
        }

        canvas {
            border: 1px solid white;
        }

        .controls {
            margin-top: 20px;
        }

        .control-label {
            margin-right: 10px;
        }
    </style>
</head>

<body>
    <div>
        <h1>GeeksforGeeks</h1>
        <h3>Using gl-matrix for Complex Transformations</h3>
        <canvas id="webgl-canvas" width="400"
                height="400"></canvas>
        <div class="controls">
            <div>
                <label class="control-label">Translate X:</label>
                <input type="range" id="translateX"
                       min="-1" max="1" step="0.01" value="0">
                <span id="translateXValue">0</span>
            </div>
            <div>
                <label class="control-label">Translate Y:</label>
                <input type="range" id="translateY"
                       min="-1" max="1" step="0.01" value="0">
                <span id="translateYValue">0</span>
            </div>
            <div>
                <label class="control-label">Scale:</label>
                <input type="range" id="scale"
                       min="0.1" max="2" 
                       step="0.01" value="1">
                <span id="scaleValue">1</span>
            </div>
            <div>
                <label class="control-label">Rotation:</label>
                <input type="range" id="rotation"
                       min="0" max="360" step="1" value="0">
                <span id="rotationValue">0</span>
            </div>
        </div>
    </div>
    <script src=
"https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.4.0/gl-matrix.js"></script>
    <script src="script.js"></script>
</body>

</html>
JavaScript
// Get the canvas element and
//  initialize the WebGL context
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl');

// Check if WebGL is supported
if (!gl) {
    console.error('WebGL not supported');
}

// Vertex shader source code: 
// determines the position of each vertex
const vsSource = `
    attribute vec4 a_position;
    uniform mat4 u_matrix;
    void main() {
        // Transform the vertex position by the matrix
        gl_Position = u_matrix * a_position;
    }
`;

// Fragment shader source code:
// determines the color of each pixel
const fsSource = `
    precision mediump float;
    uniform vec4 u_color;
    void main() {
        // Set the fragment color to the uniform value
        gl_FragColor = u_color;
    }
`;

// Function to compile a shader from its source code
function compileShader(gl, source, type) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    // Check for compilation errors
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error(gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

// Function to create a WebGL
// program with a vertex and fragment shader
function createProgram(gl, vsSource, fsSource) {
    const vertexShader = compileShader(gl,
        vsSource, gl.VERTEX_SHADER);
    const fragmentShader = compileShader(gl,
        fsSource, gl.FRAGMENT_SHADER);
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    // Check for linking errors
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        console.error(gl.getProgramInfoLog(program));
        return null;
    }
    return program;
}

// Create and use the WebGL program
const program = createProgram(gl, vsSource, fsSource);
gl.useProgram(program);

// Define the vertices of a square (two triangles)
const vertices = new Float32Array([
    -0.5, -0.5, // Bottom-left
    0.5, -0.5,  // Bottom-right
    -0.5, 0.5,  // Top-left
    0.5, 0.5,   // Top-right
]);

// Create a buffer to hold the vertex data
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

// Get the location of the attributes 
// and uniforms in the shader program
const positionLocation = gl.getAttribLocation(program, 'a_position');
const matrixLocation = gl.getUniformLocation(program, 'u_matrix');
const colorLocation = gl.getUniformLocation(program, 'u_color');

// Set the color uniform to green
gl.uniform4f(colorLocation, 0, 1, 0, 1);

// Bind the vertex buffer and 
// set up the attribute pointers
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);

// Function to update the 
// transformation matrix and redraw the scene
function updateMatrix() {
    // Get the transformation 
    // values from the input elements
    const translateX = parseFloat(document
        .getElementById('translateX').value);
    const translateY = parseFloat(document.
        getElementById('translateY').value);
    const scale = parseFloat(document.
        getElementById('scale').value);
    const rotation = parseFloat(document.
        getElementById('rotation').value) * Math.PI / 180;

    // Create an identity matrix 
    // and apply transformations
    const matrix = mat4.create();
    // Apply translation
    mat4.translate(matrix, matrix, [translateX, translateY, 0]);
    // Apply rotation
    mat4.rotateZ(matrix, matrix, rotation);
    // Apply scaling
    mat4.scale(matrix, matrix, [scale, scale, 1]);

    // Set the matrix uniform in the shader
    gl.uniformMatrix4fv(matrixLocation, false, matrix);

    // Clear the canvas and redraw the square
    // Set the background color to black
    gl.clearColor(0, 0, 0, 1);
    // Clear the color buffer
    gl.clear(gl.COLOR_BUFFER_BIT);
    // Draw the square
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}

// Initial draw of the scene
updateMatrix();

// Add event listeners to update
// the matrix when the sliders change
document.getElementById('translateX')
    .addEventListener('input', (event) => {
        document.getElementById('translateXValue')
            .textContent = event.target.value;
        updateMatrix();
    });

document.getElementById('translateY')
    .addEventListener('input', (event) => {
        document.getElementById('translateYValue')
            .textContent = event.target.value;
        updateMatrix();
    });

document.getElementById('scale')
    .addEventListener('input', (event) => {
        document.getElementById('scaleValue')
            .textContent = event.target.value;
        updateMatrix();
    });

document.getElementById('rotation')
    .addEventListener('input', (event) => {
        document.getElementById('rotationValue')
            .textContent = event.target.value;
        updateMatrix();
    });

Output:

2
Output

Explore