The Javascript ecosystem offers exciting ways to render 3D environments. I've wanted to look into this area for some time. Here I take my first steps with WebGL, build a UI with basic controls and use them to interact with a 3D scene

My starting point is a MDN WebGL tutorial the results of which are embedded above – a spinning cube, covered with an image, rendered on an HTML Canvas using WebGL and a library called gl-matrix.

Having inherited this code from the tutorial I'd like to make it interactive.

Interactive features

  • Rotate the cube
  • Zoom-in on the cube

Simplest thing first

Now a checkbox toggles automatic rotation. How?

Let's get into the code

A function called render runs every time a new animation frame is available...

function render(now) {
const deltaTime = now - then;
then = now;
drawScene(gl, programInfo, buffers, texture, deltaTime);

A measure of time-since-the-last-frame is passed into drawScene every time we [re]render

There is a lot going on in drawScene. We are interested in some state called cubeRotation, which is calculated based on the time delta (the more time has passed the further the cube has rotated)

See the mat4.rotate method from gl-matrix below:

modelViewMatrix, // destination matrix
modelViewMatrix, // matrix to rotate
cubeRotation, // amount to rotate in radians
[0, 0, 1]
); // axis to rotate around (Z)
modelViewMatrix, // destination matrix
modelViewMatrix, // matrix to rotate
cubeRotation * 0.7, // amount to rotate in radians
[0, 1, 0]
); // axis to rotate around (X)

To make this a bit easier I do some clean up:

  • Rotate around the more intuitive X and Y axis, rather than X and Z
  • Use a separate variables for cubeRotationX and cubeRotationYno more random * 0.7
  • Replace comments with named variables
const xAxis = [0, 1, 0];
mat4.rotate(modelViewMatrix, modelViewMatrix, cubeRotationX, xAxis);
const yAxis = [1, 0, 0];
mat4.rotate(modelViewMatrix, modelViewMatrix, cubeRotationY, yAxis);

Now to make rotation conditional

We simply put an if block around the code that updates cubeRotationX and cubeRotationY

if (document.getElementById("autoRotation").checked) {
cubeRotationX += deltaTime / 2000;
cubeRotationY += deltaTime / 1400;

Our checkbox toggles automatic rotation 🎉

Controlling X and Y rotation

We adding some more inputs to the HTML

<label htmlFor="cubeRotationX">X</label>
<input type="number" id="cubeRotationX" name="cubeRotationX" step="0.05" />
<label htmlFor="cubeRotationX">Y</label>
<input type="number" id="cubeRotationY" name="cubeRotationY" step="0.05" />

And use their values when not auto-rotating

if (document.getElementById("autoRotation").checked === false) {
cubeRotationY = document.getElementById("cubeRotationY").value;
cubeRotationX = document.getElementById("cubeRotationX").value;

Control the zoom

Imagining a camera is pointing at our 3D cube. There are at least three ways to achieve a "zoom" effect, relatively speaking:

  1. Move the cube closer to the camera
  2. Move the camera closer to the cube
  3. Change the magnification of the camera
  4. Increase the size of the cube

I've picked variables related to two or three of these concepts out of the code:

Cube positiondrawingPositionZA starting point for drawing the cube
Camera positionn/aEverything is relative. See cube position
Camera magnificationfieldOfViewInRadiansNarrows or widens the view
Cube sizen/aNot simple to vary atm in this code

This concludes this first exploration into making some interactive WebGL.

Take home points

  1. We re-render the scene every time an animation frame is available and the browser handles it well
  2. It is simple to including variables from HTML inputs in the drawScene function
  3. In this code the easiest way to add a zoom effect was to move the scene closer to the "perspective". Although this won't work in all other scenarios it's an option that works. WebGL Fundamentals has a great page about "cameras" that has more about this topic.

Next I'll take a look at some libraries (e.g. ThreeJS, A-Frame, Clay-GL). I've found this list handy.

Spotted a typo? Edit on Github

Unless otherwise stated I am the creator of all the text, images and code.

Generally I'm happy to share but let's talk first if you want to license or commercialise any of it

© 2023 Laurence Lord