Open In App

How to Implement WebGL Model View Projection?

Last Updated : 31 Jul, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

It's an amazing tool (Web Graphics Library) that can help you create intricate 3D models and objects directly on your web browser with no need for any third-party plugins. It presents a JavaScript API, empowering you to craft and control 3D graphics within a web setting.

In today’s rapidly evolving world of 3D visualization, gaming, and virtual reality (VR), WebGL plays a crucial role. It enables the creation of 3D graphics and interactive visualizations directly in the browser, making these technologies more accessible and engaging.

Understanding the WebGL Model

In WebGL, a model is a 3d model represented by vertices, indices, colors, and textures that define shape, appearance, shaders, etc.

Components of a WebGL Model

  • Vertices: These are the points in a 3D environment that define the shape of the model
  • IndicesDetail: They are like helper they refer to vertices that define the model's faces.
  • Colors: As we Know any colors are defined by RGB Values they are used to assign the RGB values to vertices and faces.
  • Texture: To add more detail to the model's surface images are applied to them.

Loading and Creating Models in WebGL

Models can be loaded from local systems or external files or they can also be created programmatically using arrays of vertices and other components.

Coordinate Systems in WebGL

We have followed this Coordinate Systems in WebGL

  • Local Space(also Model Space): This Coordinate System is responsible for the model's position, rotation, and scale within the world.
  • World Space: It is the coordinate system for the entire Scene as it defines the relative positions of different models,
  • View Space(Formerly Camera Space): It is the coordinate system from the perspective of the camera means it handles the Camera.
  • Clip Space(NDC Space): It is used for the final Touch Which is used for Final Rendering.

A) Model Transformation

To make the model do something we need to change things like Translation, Rotation, Scaling, and Creating Transformation Matrices

  • Transformation: Very basics used to move the object(models) in 3d space along the X, Y, and Z axes.
  • Rotation: Used for rotation of models.
  • Scaling: Changes the size of the mode along the X, Y, and Z axes.
  • Creating Transformation Matrices: At the end, we are using matrices to define the Transformation, Rotation, and Scaling. Combining this thing things we have a thing that we call a Model Matrix.

B) View Transformation

  • Understanding the Camera in WebGL: The Camera is used in WebGL to define the viewer's position and orientation in the scene. It affects how the models are going to be projected onto the 2d screen. This is the most complex part.
  • Creating the View Matrix: This transforms world coordinates to the camera's view. It is created using the camera's position, target point, and Up-vector
  • Positioning and Orienting the Camera: This affects the part of the scene and how it is visible. This can be set using Translation, and rotation matrices.

C) Projection Transformation

  • Orthographic Projection: They are used to maintain the parallel lines and is used for 2d renderings and technical drawings.
  • Perspective Projection: They are used to define the depth of the field and makes the distant object smaller which is obvious for reason.
  • Creating Projection Matrix: They are created using the desired projection method and defines how the 3D models get projected on 2D screens.

Combining Model, View and Projection Matrices

Multiplying Matrices

To Transform the model coordinates to screen coordinates, the Model, View and Projection Matrices are multiplied to form a MVP matrix which is used further.

Applying Transformation in the Shader

The MVP matrix created before is used to the vertex shader to apply required transformations during rendering.

WebGL Shader Programming

Introduction to GLSL

GLSL(OpenGL Shading Language) is used to write vertex and fragment shaders that run on the GPU. Formerly it is a high level C-like programming used for creating shaders, which are small programs exectued on Graphical Processing Unit(GPUs).

They also have different Variables and Data Types:

  • float, vec2, vec3, vec4 for vectors and scalars
  • mat2, mat3, mat4 for matrices
  • bool for boolean values
  • sampler2D, sampler3D for textures.

Vertex Shader

It is used for processing each vertex, applying transformation and passing it to fragment shader further

Fragment Shader

It determines the color of each pixel and apply the light effect with addition of texture effects whenever required.

Sending Matrices to shaders

Matrices finally sent to shaders as uniform varibles, allowing the GPU to apply necessary transformation during render.

Rendering the Scene

  • Setting Up the WebGL Context: Initializes the WebGL context, creating a canvas for rendering.
  • Clearing the Canvas: Clears the canvas to prepare for a new frame.
  • Drawing the Model with Transformations: Binds buffers, sets shader attributes, and draws the model using the MVP matrix.

Approach

To set up a rotating cube with WebGL, we start by creating an HTML document with a canvas element and include the gl-matrix library. We then compile vertex and fragment shaders, link them into a shader program, and handle any errors. We define and upload the position and index data for the cube's faces. Next, we initialize shaders and buffers by loading, compiling, and linking the shaders, and setting up buffers for vertex positions and colors. We define perspective and model-view matrices, passing them to the shader. We then upload the vertex positions, colors, and indices to the GPU. Finally, we draw the scene by clearing the canvas, setting up matrices, binding buffers, applying shaders, and rendering the cube. This process results in a rotating, multi-colored cube rendered on the canvas.

Example: This example shows the implementation of the above-explained approach.

HTML
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" 
          content="width=device-width,
                   initial-scale=1.0">
    <title>GFG WebGL Implementation</title>
    <link rel="stylesheet" href="./style.css">
</head>

<body>
    <canvas id="glCanvas" width="640" height="480"></canvas>
    <script src=
"https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
    <script src="./script.js"></script>
</body>

</html>
CSS
body {
    margin: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #20232a;
    color: white;
    font-family: Arial, sans-serif;
}

canvas {
    border: 1px solid white;
}
JavaScript
const canvas = document.getElementById("glCanvas");
const gl = canvas.getContext("webgl");

if (!gl) {
    alert(
        "Unable to initialize WebGL. Your browser or machine may not support it.");
}

const vsSource = `
                    attribute vec4 aVertexPosition;
                    attribute vec4 aVertexColor;
                    uniform mat4 uModelViewMatrix;
                    uniform mat4 uProjectionMatrix;
                    varying lowp vec4 vColor;
                    void main(void) {
                        gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
                        vColor = aVertexColor;
                    }
                `;

const fsSource = `
                    varying lowp vec4 vColor;
                    void main(void) {
                        gl_FragColor = vColor;
                    }
                `;

function loadShader(gl, type, source) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error(
            "An error occurred compiling the shaders: "
            + gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }

    return shader;
}

const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram,
    loadShader(gl, gl.VERTEX_SHADER, vsSource));
gl.attachShader(
    shaderProgram,
    loadShader(gl, gl.FRAGMENT_SHADER, fsSource));
gl.linkProgram(shaderProgram);

if (!gl.getProgramParameter(shaderProgram,
    gl.LINK_STATUS)) {
    console.error(
        "Unable to initialize the shader program: "
        + gl.getProgramInfoLog(shaderProgram));
}

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"),
    },
};

const buffers = initBuffers(gl);

function initBuffers(gl) {
    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

    const positions = [
        // 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,
    ];

    gl.bufferData(gl.ARRAY_BUFFER,
        new Float32Array(positions),
        gl.STATIC_DRAW);

    const faceColors = [
        [1.0, 1.0, 1.0, 1.0], // Front face: white
        [1.0, 0.0, 0.0, 1.0], // Back face: red
        [0.0, 1.0, 0.0, 1.0], // Top face: green
        [0.0, 0.0, 1.0, 1.0], // Bottom face: blue
        [1.0, 1.0, 0.0, 1.0], // Right face: yellow
        [1.0, 0.0, 1.0, 1.0], // Left face: purple
    ];

    let colors = [];

    for (let j = 0; j < faceColors.length; ++j) {
        const c = faceColors[j];

        colors = colors.concat(c, c, c, c);
    }

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

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

    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,
        new Uint16Array(indices), gl.STATIC_DRAW);

    return {
        position: positionBuffer,
        color: colorBuffer,
        indices: indexBuffer,
    };
}

function drawScene(gl, programInfo, buffers, deltaTime) {
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clearDepth(1.0);
    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    const fieldOfView = (45 * Math.PI) / 180;
    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();
    mat4.translate(modelViewMatrix, modelViewMatrix,
        [-0.0, 0.0, -6.0]);
    mat4.rotate(modelViewMatrix, modelViewMatrix,
        cubeRotation, [0, 0, 1]);
    mat4.rotate(modelViewMatrix, modelViewMatrix,
        cubeRotation * 0.7, [0, 1, 0]);

    {
        const numComponents = 3;
        const type = gl.FLOAT;
        const normalize = false;
        const stride = 0;
        const offset = 0;
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
        gl.vertexAttribPointer(
            programInfo.attribLocations.vertexPosition,
            numComponents, type, normalize, stride, offset);
        gl.enableVertexAttribArray(
            programInfo.attribLocations.vertexPosition);
    }

    {
        const numComponents = 4;
        const type = gl.FLOAT;
        const normalize = false;
        const stride = 0;
        const offset = 0;
        gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color);
        gl.vertexAttribPointer(
            programInfo.attribLocations.vertexColor,
            numComponents, type, normalize, stride, offset);
        gl.enableVertexAttribArray(
            programInfo.attribLocations.vertexColor);
    }

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);

    gl.useProgram(programInfo.program);

    gl.uniformMatrix4fv(
        programInfo.uniformLocations.projectionMatrix,
        false, projectionMatrix);
    gl.uniformMatrix4fv(
        programInfo.uniformLocations.modelViewMatrix, false,
        modelViewMatrix);

    {
        const vertexCount = 36;
        const type = gl.UNSIGNED_SHORT;
        const offset = 0;
        gl.drawElements(gl.TRIANGLES, vertexCount, type,
            offset);
    }
}

let cubeRotation = 0.0;
let then = 0;

function render(now) {
    now *= 0.001;
    const deltaTime = now - then;
    then = now;

    cubeRotation += deltaTime;

    drawScene(gl, programInfo, buffers, deltaTime);

    requestAnimationFrame(render);
}

requestAnimationFrame(render);

Output:

out1-(9)
Output

Next Article

Similar Reads