• Home
  • blog
  • Building a 3D Engine with JavaScript

Building a 3D Engine with JavaScript

Displaying images and other flat shapes in web pages is pretty easy. However, when it comes to displaying 3D shapes things become less easy, as 3D geometry is more complex than 2D geometry. To do that, you can use dedicated technologies and libraries, like WebGL and Three.js for example.

However, these technologies are not necessary if you just want to display some basic shapes, like a cube. Moreover, they won’t help you to understand how they work, and how we can display 3D shapes on a flat screen.

The aim of this tutorial is to explain how we can build a simple 3D engine for the web, without WebGL. We will first see how we can store 3D shapes. Then, we will see how to display these shapes, in two different views.

Storing and Transforming 3D Shapes

All Shapes Are Polyhedrons

The virtual world differs from the real one in one major way: nothing is continuous, and everything is discrete. For example, you can’t display a perfect circle on a screen. You can approach it by drawing a regular polygon with a lot of edges: the more edges you have, the more “perfect” is your circle.

In 3D, it’s the same thing and every shape must be approached with the 3D equivalent of a polygon: a polyhedron (a 3D shape in which we only find flat faces ant not curved sides as in a sphere). It’s not surprising when we talk about a shape that is already a polyhedron, like a cube, but it’s something to keep in mind when we want to display other shapes, like a sphere.


Storing a Polyhedron

To guess how to store a polyhedron, we have to remember how such a thing can be identified in maths. You surely already did some basic geometry during your school years. To identify a square, for example, you call it ABCD, with A, B, C and D referring to vertices that make up each corner of the square.

For our 3D engine, it will be the same. We will begin by storing each vertex of our shape. Then, this shape will list its faces, and each face will list its vertices.

To represent a vertex, we need the right structure. Here we create a class to store the coordinates of the vertex.

var Vertex = function(x, y, z) {
    this.x = parseFloat(x);
    this.y = parseFloat(y);
    this.z = parseFloat(z);

Now a vertex can be created like any other object:

var A = new Vertex(10, 20, 0.5);

Next, we create a class representing our polyhedron. Let’s take a cube as an example. The definition of the class is below, with the explanation right after.

var Cube = function(center, size) {
    // Generate the vertices
    var d = size / 2;

    this.vertices = [
        new Vertex(center.x - d, center.y - d, center.z + d),
        new Vertex(center.x - d, center.y - d, center.z - d),
        new Vertex(center.x + d, center.y - d, center.z - d),
        new Vertex(center.x + d, center.y - d, center.z + d),
        new Vertex(center.x + d, center.y + d, center.z + d),
        new Vertex(center.x + d, center.y + d, center.z - d),
        new Vertex(center.x - d, center.y + d, center.z - d),
        new Vertex(center.x - d, center.y + d, center.z + d)

    // Generate the faces
    this.faces = [
        [this.vertices[0], this.vertices[1], this.vertices[2], this.vertices[3]],
        [this.vertices[3], this.vertices[2], this.vertices[5], this.vertices[4]],
        [this.vertices[4], this.vertices[5], this.vertices[6], this.vertices[7]],
        [this.vertices[7], this.vertices[6], this.vertices[1], this.vertices[0]],
        [this.vertices[7], this.vertices[0], this.vertices[3], this.vertices[4]],
        [this.vertices[1], this.vertices[6], this.vertices[5], this.vertices[2]]

Using this class, we can create a virtual cube by indicating its center and the length of its edges.

var cube = new Cube(new Vertex(0, 0, 0), 200);

The constructor of the Cube class begins by generating the vertices of the cube, calculated from the position of the indicated center. A schema will be clearer, so see below the positions of the eight vertices we generate:


Then, we list the faces. Each face is a square, so we need to indicate four vertices for each face. Here I chose to represent a face with an array but, if you needed, you could create a dedicated class for that.

When we create a face, we use four vertices. We don’t need to indicate their position again, as they are stored in the this.vertices[i] object. It’s practical, but there is another reason why we did that.

By default, JavaScript tries to use the least amount of memory possible. To achieve that, it doesn’t copy the objects that are passed as functions arguments or even stored into arrays. For our case, it’s perfect behavior.

In fact, each vertex contains three numbers (their coordinates), plus several methods if we need to add them. If, for each face, we store a copy of the vertex, we will use a lot of memory, which is useless. Here, all we have are references: the coordinates (and other methods) are stored once, and only once. As each vertex is used by three different faces, by storing references and not copies, we divide the required memory by three (more or less)!

Do We Need Triangles?

If you have already played with 3D (with software like Blender for example, or with libraries like WebGL), maybe you have heard that we should use triangles. Here, I’ve chosen to not use triangles.

The reason behind this choice is that this article is an introduction to the topic and we will be displaying basic shapes like cubes. Using triangles to display squares would be more of a complication than anything else in our case.

However, if you plan to build a more complete renderer, then you need to know that, in general, triangles are preferred. There are two main reasons for this:

  1. Textures: to display images on faces we need triangles, for some mathematic reasons;
  2. Weird faces: three vertices are always in the same plane. However, you can add a fourth vertex that isn’t in the same plane, and you can create a face joining these four vertices. In such a case, to draw it, we don’t have choice: we must split it into two triangles (just try with a sheet of paper!). By using triangles, you keep the control and you choose where the split occurs (thanks Tim for the reminder!).

Continue reading %Building a 3D Engine with JavaScript%