The Javascript ecosystem offers exciting ways to render 3D environments. I've wanted to look into this area for some time. Here, I'm taking my first steps with WebGL. I want to build a UI with basic controls and an interactive 3D canvas

Screencapture of a decorated cube spinning in 3D space

There's an excellent WebGL tutorial on MDN that gets you to render a spinning cube, covered with an image, on an HTML Canvas, using WebGL and a library called gl-matrix.

Below are my result from the tutorial:

Next, I'd like to add some controls to the UI to make the 3D rendering interactive.

Interactive features to add

  • I want to be able to rotate the cube myself
  • I want to be able to zoom-in on the cube

Doing the simplest thing first, let's add a control that stops the cube rotating by itself.

Let's get into the code

Currently function called render runs everytime a new animation frame is available:

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

We measure how much time has passed since the last animation frame and pass that into the drawScene function.

There is a lot going on inside drawScene. The bit we're interested in is where we update some state called cubeRotation based on the time delta.

This value gets used inside two heavily commented function calls to mat4.rotate from the gl-matrix library:

mat4.rotate(
modelViewMatrix, // destination matrix
modelViewMatrix, // matrix to rotate
cubeRotation, // amount to rotate in radians
[0, 0, 1]
); // axis to rotate around (Z)
mat4.rotate(
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 to work with let's do the following clean up:

  • let's rotate around the more intuitive X and Y axis, rather than X and Z
  • let's use a separate variable for each axis
  • let's clean up these comments
const xAxis = [0, 1, 0];
mat4.rotate(modelViewMatrix, modelViewMatrix, cubeRotationX, xAxis);
const yAxis = [1, 0, 0];
mat4.rotate(modelViewMatrix, modelViewMatrix, cubeRotationY, yAxis);

Pause the automatic rotation

Now we're ready to stop the cube rotating by itself. We can introduce a toggle and check it's value before updating cubeRotationX and cubeRotationY.

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

Control the rotation

Let's add some more controls to the HTML

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

And use those values if we're not rotating automatically

// each time we run render
cubeRotationY = document.getElementById("cubeRotationY").value;
cubeRotationX = document.getElementById("cubeRotationX").value;

This is great for keyboard users and for the purpose of my experimentation.

For touch screens and the mouse users we'd want to replace the form inputs with a more efficient interface. Maybe we'll do that later.

Control the zoom

Imagining a camera is pointing at our 3D cubethere are at least three ways to achieve a "zoom" effect, depending on what the user has in mind:

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

I'm interested in being able to control all of these. I'm going to re-read my code, which was mostly copied from the tutorial, and try to capture these four concepts in variables.

...


Want more work done on this page?

Let me know what you'd like via the issues tracker for this website



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

© 2021 Laurence Lord