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
X
andY
axis, rather thanX
andZ
- Use a separate variables for
cubeRotationX
andcubeRotationY
– 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
drawScene
function - 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)