Attacking Pixels - Adam Robinson

Hi, I’m Adam Robinson, a software engineer and maker based in London

Take a look at my projects and experiments.

Search 17 Posts

Getting Started with 3D in Javascript

Posted 1 day ago - 7 min read

Tags : 3DGetting StartedThree.js

Web-enabled devices are becoming increasingly capable of representing some pretty wild 3D experiences in the browser. With open-source projects available to you now it’s possible to build 3D tools, art, games or even experiment with VR/AR in ways that can be shared instantly across the web, work seamlessly across devices and don’t require local installation on the client. With future web standards such as WebGPU on the horizon, this is just the tip of the iceberg! But before you can start coding the Matrix, I’ll try and explain how the principles of these tools work and what you’ve got at your disposal today.

What is WebGL?

WebGL is a JavaScript API which allows you to render interactive 2D and 3D graphics within any compatible web browser without the use of plug-ins. No need to install the program you’ve built locally or rely on a third party games engine! WebGL is fully integrated with other web standards, allowing GPU-accelerated usage of physics and image processing and effects as part of the web page canvas. You can test your web browser support for WebGL here: https://get.webgl.org/ . WebGL is really low level and handles things like points, lines and triangles.

What is a Canvas?

A canvas is an HTML element <canvas> which is part of the HTML5 specification used to draw and display graphics, on the fly, via JavaScript. The <canvas> element is only a container for graphics. You must use JavaScript to actually draw the graphics, this is where we’ll display all the fancy stuff we make in the browser.

What is Three.js?

Three.js a JavaScript library, started by Mr.doob. It’s best to think of the Three.js library as your bridge between WebGL and the html canvas. Via JavaScript, three.js will allow you you to easily interreact with lower level WebGL API in an HTML5 canvas and manipulate 3D objects directly in the browser. Three.js vastly simplifies the code needed to do whatever you want in 3D. You could write JavaScript for WebGL directly… but you’d have to be a bit of a savage and why re-invent the wheel?

In summary, three.js is a JavaScript library which wraps a JavaScript API (WebGL) which carries out graphics rendering in an HTML5 component. If you follow this then we’re off to the races!

How do we represent ‘stuff’ in a 3D world?

To get your head around how to build your 3D world at a high level in Three.js its best to think of yourself as a director.

The Scene is the container of your 3D world. It’s the place where you’re going to introduce and arrange your objects. You’re going to create the objects you want in your scene as meshes.

Meshes can be imported from 3D files via loaders, or can be built from primitive shapes offered in three.js. Meshes are the wireframes of objects and are naked. In order to see your meshes, you are going to need to first give them a skin in the form of a material.

Materials can be a solid colour or an image texture file. Materials are the surface which determines how light interacts with an object. That’s a point our scene has no light so we can’t see anything!

Lights illuminate the scene. Best add some ambient light to the scene so our camera can see what we’re doing. We can add some more focused light, such as spotlights for shadows.

The camera is the POV for our scene. From the cameras POV, all objects in the scene between the frustum can be seen. The frustum of the camera is defined from the camera to a plane in the distance… think of this as the back wall on a stage. You’re not going to be filming the dressing room from here you cheeky monkey! It’s what you see in this frustum of this camera that will be sent to the renderer.

The Renderer is arguably the main object of three.js. It takes the scene and the camera as parameters and it renders (draws) the portion of the 3D scene that is inside the frustum of the camera, as a 2D image in the HTML5 canvas. The renderer will produce an image at the refresh rate of your browser. In general, 60 frames per second giving life and depth to your scene!

Three.js Hello World Example Code

I’m we’re going to make this abstract overview a little more useful with some code in the form of a simple three.js ‘hello world’ demonstration. The following Codesandbox example I’ve put together is based on the following intro tutorial https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene in the three.js documentation, albeit with a few subtle, but important differences.

Stepping Through The Code

const scene = new THREE.Scene()
const renderer = new THREE.WebGLRenderer()
// new THREE.PerspectiveCamera(fov, aspect, near, far)
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
  • You start by declaring the scene, without this your objects will have nowhere to go!
  • Then you declare the WebGL renderer for the scene so you can send your graphics to the canvas.
  • Then we want a camera to look at the scene. There are a handful of cameras available in Three.js but for this example we’ll use the perspective camera. The parameters passed to the perspective camera define the frustum of the camera. Only objects within the near and far plane will be rendered.
const ambient = new THREE.AmbientLight(0xffffff, 0.25)
const directionalLight = new THREE.DirectionalLight(0xdddddd, 2.5)
scene.add(ambient)
scene.add(directionalLight)
  • Add lighting to the scene. These parameters define the colour and the intensity. Ambient light intensity of 1 will blow out the colour of the scene with full luminance so best to keep this < 1.
const geometry = new THREE.TorusKnotBufferGeometry(10, 1.6, 300, 100, 2, 3)
const material = new THREE.MeshPhongMaterial({ color: 0xffff00 })
const torusKnot = new THREE.Mesh(geometry, material)
scene.add(torusKnot)
// Moves the camera back a bit
camera.position.z = 30
  • Let us add an object to the scene. Here I’m using a Torus Knot which is one of the Three.js primitive shapes. There are Buffer and Non Buffer variants of the primitive geometries. When in doubt use a buffer geometries as they improve three.js performance
  • Make a material for skinning the mesh. Here we can define the colour of the object and the degree of reflection of the surface. MeshPhongMaterial allows the mesh to receive shadows whereas MeshBasicMaterial does not.
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
  • Then, we’re going to mount the renderer to the dom in full screen and add it in the HTML page via the HTML5 canvas!
function animate() {
  cylinder.rotation.x += 0.01
  cylinder.rotation.y += 0.01
  renderer.render(scene, camera)
  requestAnimationFrame(animate)
}
animate()
  • Finally, we’re going to define an animate function which will be called infinitely in the render loop of the renderer, incrementally changing the rotation between every frame.

If you’ve followed this example, then in less than 20 lines of vanilla JavaScript you’ve made a 3D world and started rendering it in the browser and you could share it with almost anyone. It really is this simple! Check out my portfolio which was built in vanilla JavaScript and Three.js @ adamrobinson.dev

The next road I’ll take you down is one further abstraction using the react-three-fibre library which allows you to build scene graphs declaratively with re-usable components, makes dealing with three.js easier and brings order and sanity to your codebase.


Adam G Robinson
Crafter. Explorer. Coder. 🇬🇧