WebGL - Cube Rotation

Hello there, future WebGL wizards! Today, we're going to embark on an exciting journey into the world of 3D graphics. By the end of this tutorial, you'll be able to create a rotating cube using WebGL. Isn't that cool? Let's dive in!

WebGL - Cube Rotation

Understanding the Basics

Before we start spinning cubes like a DJ, let's get our heads around some fundamental concepts.

What is WebGL?

WebGL (Web Graphics Library) is a JavaScript API that allows us to render 3D graphics in a web browser. It's like giving your browser a pair of 3D glasses!

Why a Cube?

You might wonder, "Why are we starting with a cube?" Well, my dear students, a cube is like the "Hello World" of 3D graphics. It's simple enough to understand but complex enough to teach us important concepts. Plus, who doesn't love a good cube?

Setting Up Our WebGL Environment

The HTML Canvas

First things first, we need a stage for our cube to perform on. In WebGL, this stage is called a canvas. Let's set it up:

<canvas id="glcanvas" width="640" height="480">
    Your browser doesn't support HTML5 canvas
</canvas>

This creates a 640x480 pixel canvas. If you can't see it, don't worry - it's like an invisible dance floor right now.

Initializing WebGL

Now, let's get WebGL ready to party:

var canvas = document.getElementById('glcanvas');
var gl = canvas.getContext('webgl');

if (!gl) {
    console.log('WebGL not supported, falling back on experimental-webgl');
    gl = canvas.getContext('experimental-webgl');
}

if (!gl) {
    alert('Your browser does not support WebGL');
}

This code gets our WebGL context. If your browser doesn't support WebGL, it's like trying to play a DVD on a VHS player - it just won't work!

Creating Our 3D Cube

Defining Vertices

A cube has 8 corners (vertices). We need to tell WebGL where these corners are:

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

Each set of three numbers represents a corner of our cube in 3D space. It's like giving WebGL a map of our cube!

Creating the Buffer

Now we need to send this data to the GPU:

var vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

This is like packing our cube into a suitcase (buffer) and sending it to the GPU.

Shaders - The Magicians of WebGL

Vertex Shader

The vertex shader positions our vertices:

var vertexShaderSource = `
    attribute vec3 aVertexPosition;
    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;
    void main(void) {
        gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
    }
`;

This shader takes each vertex and applies our transformation matrices. It's like a magician moving the corners of our cube around!

Fragment Shader

The fragment shader colors our cube:

var fragmentShaderSource = `
    void main(void) {
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
`;

This simple shader just colors everything white. It's like painting our cube!

Compiling and Linking Shaders

Now we need to compile our shaders and link them into a program:

function getShader(gl, source, type) {
    var 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));
        return null;
    }

    return shader;
}

var vertexShader = getShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
var fragmentShader = getShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);

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

gl.useProgram(shaderProgram);

This is like teaching our magicians (shaders) their tricks and then putting them on stage!

Making Our Cube Rotate

Setting Up the Rotation

To make our cube rotate, we need to update its rotation angle over time:

var cubeRotation = 0.0;

function render(now) {
    now *= 0.001;  // convert to seconds
    const deltaTime = now - then;
    then = now;

    cubeRotation += deltaTime;

    drawScene();

    requestAnimationFrame(render);
}

This function gets called repeatedly, updating our cube's rotation each time.

Drawing the Scene

Now let's put it all together and draw our rotating cube:

function drawScene() {
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    const projectionMatrix = mat4.create();
    mat4.perspective(projectionMatrix, 45 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.1, 100.0);

    const modelViewMatrix = mat4.create();
    mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -6.0]);
    mat4.rotate(modelViewMatrix, modelViewMatrix, cubeRotation, [0, 1, 1]);

    gl.uniformMatrix4fv(gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'), false, projectionMatrix);
    gl.uniformMatrix4fv(gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'), false, modelViewMatrix);

    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 36);
}

This function clears the canvas, sets up our perspective, applies our rotation, and finally draws our cube.

Conclusion

And there you have it, folks! We've created a rotating 3D cube using WebGL. Remember, mastering WebGL is like learning to juggle - it takes practice, but once you get it, you can do amazing things!

Here's a table summarizing the main methods we used:

Method Description
gl.createBuffer() Creates a new buffer object
gl.bindBuffer() Binds a buffer object to a target
gl.bufferData() Creates and initializes a buffer object's data store
gl.createShader() Creates a shader object
gl.shaderSource() Sets the source code of a shader object
gl.compileShader() Compiles a shader object
gl.createProgram() Creates a program object
gl.attachShader() Attaches a shader object to a program object
gl.linkProgram() Links a program object
gl.useProgram() Sets the specified program as part of the current rendering state
gl.clear() Clears buffers to preset values
gl.uniformMatrix4fv() Specifies the value of a uniform variable for the current program object
gl.drawArrays() Renders primitives from array data

Keep practicing, keep coding, and soon you'll be creating amazing 3D graphics in your browser. Happy coding!

Credits: Image by storyset