NOTE: This book is a work-in-progress, and will be finished by late July, 2026. Consider setting a reminder to check back then.


In the final few months of my undergraduate degree, I decided I wanted to simulate and render black holes. In the process, I built a C++ graphics engine, and that process is what I'd like to share with you today.

[FIGURE 0: INLINE IMAGE: PHONG -> BLACK HOLE]

You don't need any technical knowledge to understand this article, although some programming knowledge couldn't hurt. This is meant as a fun, visual exploration of the high-level concepts involved in computer graphics, so I will focus on making the ideas intuitive while avoiding most technical details.

Join me on this layman's voyage from drawing basic circles, through the sea of computer graphics, to eventually rendering black holes.

I've made all graphics engine code public here on GitHub. The code is self-contained and purely CPU-based, so anyone can download it and generate their own images with essentially zero setup.

In computer graphics, there are two main ways to draw, or render, 3D worlds onto a 2D screen: rasterization and ray tracing.

Rasterization is a rendering model that takes all the objects that make up a 3D scene, and shoves them through a series of math operations that directly converts them to a 2D image. The math involved is linear algebra, but the problem for us is that linear algebra is linear, or straight, but black holes actually curve the space around them. So rasterization won't do.

Ray tracing, on the other hand, is a rendering model based on real-world physics, capable of producing photorealistic images. It does this by simulating rays of light as they bounce around the shapes on a scene, and into a camera, rather than trying to smush the 3D world into two dimensions like rasterization.

Because of its foundation in physics, we'll be using ray tracing to render our black holes. On our way there, I'll take you through the basics of ray tracing, lighting, reflection, and features like defocus blur to build your intuition. After these mini-lessons, we'll finally be equipped to tackle black holes.

Ray tracing is the idea of simulating light, in the form of lines called “rays,” as they move through a scene. So, understanding the basics of how light works is very important.

In the real world, light emanates from sources like the sun, or a lightbulb. It travels in straight lines until it hits an object, which usually causes it to bounce off and scatter about. Objects have colour because some colours of light are absorbed while others are scattered. A banana looks yellow not because it is yellow, per-se, but because it absorbs any non-yellow light, so when we look at it, only yellow light is bounced into our eyes.

[FIGURE 1: how light actually bounces around the scene and hits a banana]

Humans have known this about light for a while, so when we invented computers and gave them screens, some smart humans thought we could simulate this light behaviour using them. There was just one little issue: computers are really, really slow. At least, compared to physical light.

Since it's impossible to simulate trillions of light rays hitting octillions of atoms every picosecond, those smart humans had to come up with a way to massively decrease the amount of computing the computers had to do to simulate the rays.

What they came up with is very simple and elegant. Instead of imagining light coming from light sources, bouncing around, and having a very small chance of landing in our camera lens, why not imagine light coming out of the camera, bouncing around, and eventually hitting a light source? Pretending light travels from the viewer to the source keeps the physics essentially unchanged, while dramatically reducing any waste on light rays that don't arrive at our camera.

[FIGURE 2: how light bounces around a scene in a ray tracer.]

This idea of sending rays out from a camera lens into the world, like probes, is the foundation of our ray tracer. Everything else in this article will build on that core idea.

Let's dive in.

Ray tracing involves following light rays as they move about a 3D scene.

Our camera will be the thing that sends rays outward, in straight lines, and we'll want to check what each outgoing ray hits. For now, if it hits a blue object, that ray should be blue. We will add more features as we go.

Since our camera represents our computer screen, you can think of it as a rectangle made of pixels, just like the screen itself. From each pixel, a ray is sent out into the scene, which bounces around and reports back to the camera the resulting colour. Finally, the camera sets each pixel colour to the colour of the corresponding ray.

With this, we are able to produce our very first rendered image.

Behold, a black rectangle. For my money, that's the most realistic rendering of a black hole on the internet.

A pure black rectangle.

It would be nice if there were objects in our scene, so something interesting can appear on screen. Luckily, that isn't too hard.

Our rays are straight lines represented by numbers, so we can use math — specifically, linear algebra — to calculate if a ray is hitting an object. If we have the equation that represents a sphere, and the equation for our ray, setting them equal to each other will give us all intersections.

Sphere intersection pseudocode goes here.

With the sphere intersection points in hand, we can give any rays that hit the sphere a colour. We still don't have a system for lighting, so our sphere will look like a flat circle for the time being.

[FIGURE 4: Blue sphere]

Just like there is an equation that represents a sphere, there are equations for cubes, donuts, and many other shapes.

[FIGURE 5: Blue sphere, purple stretched cube, green torus, teal stellated dodecahedron]

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

Coming soon, by the end of July 2026.

The basis for this project was built as part of University of Waterloo's CS488: Introduction to Computer Graphics course. The goal of simulating black holes was inspired by this video, and this article is inspired by Ray Tracing in One Weekend.

Special thanks to Gladimir Baranoski for teaching me most of what this raytracer does. Thank you to Owen Gallagher and Wasay Saeed for joining me on the wild journey of building a graphics engine. Lastly, thank you to all the generous souls on the internet like Jacco Bikker who have worked so hard to put world-class materials at my fingertips.