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);requestAnimationFrame(render);}
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:
mat4.rotate(modelViewMatrix, // destination matrixmodelViewMatrix, // matrix to rotatecubeRotation, // amount to rotate in radians[0, 0, 1]); // axis to rotate around (Z)mat4.rotate(modelViewMatrix, // destination matrixmodelViewMatrix, // matrix to rotatecubeRotation * 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
XandYaxis, rather thanXandZ - Use a separate variables for
cubeRotationXandcubeRotationY– no 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
<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 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:
- Move the cube closer to the camera
- Move the camera closer to the cube
- Change the magnification of the camera
- Increase the size of the cube
I've picked variables related to two or three of these concepts out of the code:
| Vary | Variable | Notes |
|---|---|---|
| Cube position | drawingPositionZ | A starting point for drawing the cube |
| Camera position | n/a | Everything is relative. See cube position |
| Camera magnification | fieldOfViewInRadians | Narrows or widens the view |
| Cube size | n/a | Not simple to vary atm in this code |
This concludes this first exploration into making some interactive WebGL.
Take home points
- We re-render the scene every time an animation frame is available and the browser handles it well
- It is simple to including variables from HTML inputs in the
drawScenefunction - 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.
- Here's a little something I went on to build with A-Frame (very quick learning curve)