Skip to content

JSOBJ: A lesson in how software 3D renderers are made

Evan
Published date:

Learning shader programming as a game developer can be hard. There are a lot of new terms and concepts, many of which any normal game designer need not delve into, let alone most programmers, but, despite its complexity, where shader programming remains one of the few practices in which math and visual art coalesce, its fundamentals aren’t that hard to understand. It is this property of art resulting from math that we have modern 3D renderers and game engines a-like, but to those who want to learn how to create 3D renderers themselves, or create shaders as a professional career, the process of learning shader programming can seem daunting. So where is the best “entry point” to get started with what seems to be such an abstract practice?

Most people will face the high learning curve of shader programming head on, learning shaders within the specific context they intend to use them, like Unity or Unreal Engine. But to get a real foundational understanding, the best way to understand the render pipeline, and prepare shader programming as a result, is to build it yourself.

As it turns out, its not that difficult.




How it works

Any basic 3D renderer will take in a set of points and render them to the screen, or in other words, imprint a 3D space onto a 2D plane. If you think of it like this, understanding how 3D rendering systems work becomes quite easy. You can start by trying to render a 3D object using only its X and Y coodinates. But, if you try to render a cube, for example, you get no sense of depth: it just looks like a square. So the question is, what sort of math can we do to the X and Y coodinates we print on the screen that considers the Z value, or depth, of the point? As it turns out, its actually really simple. When we render the point to the screen, instead of directly placing the point at it’s respective X/Y coordinate, let’s divide the X/Y values of the points by their Z values, before placing them on the screen. If you do this, assuming that the middle of the screen is the origin, all points further away from the camera will shrink into the origin, which mimicks a vanishing point. With this, if we render a cube, we can see the points that were previously obstructed by those in front.

Rendering points to the screen like this is actually the perfect function for most basic programming languages, as they often have libraries built to draw shapes onto a canvas, like Python’s tKinter (and PyGame) or Javascript’s CTX. Using the formula described above, we can render any set of points to the screen, with considered depth just like a proper 3D engine. And by any set of points… I mean any! Even that of an OBJ file.

See, .OBJ files are plain text files, which means that you can read and edit them using a text editor. You could even create your own 3D model using just the built in notepad app on your computer. If we tell our renderer how to take in the vertex information from a .OBJ file, we can essentially render any object we want from a file, in vertex mode. Since the vertices are listed with their X, Y, and Z values in a consistent format, we can grab the data and interpret it as a struct or a list of vector 3 variables. Then we just have to pass that into the system we defined above, dividing the X and Y values by the Z value, and then print it onto the screen.

OBJ files also contain more than just the vertex data. They also tell you UV mapping information, normals, and information on how the faces are drawn. Similar to the vertex interpreter, we can write a function that reads the face information, and then we can write another function that fills in the faces using the pen tools of our canvas.

This 2D “3D” renderer is only a single Javascript file and can be run on basically any browser. You can even make a game using this system by implementing controls that update variables in the renderer. Probably the coolest thing about this technique, though, is that it is really easy to write shader-like code that can affect both the position and look of the faces/vertices in our object.

Previous
Creating Water in Blender