Alok Menghrajani

Previously: security engineer at Square, co-author of HackLang, put the 's' in https at Facebook. Maker of CTFs.


This blog does not use any tracking cookies and does not serve any ads. Enjoy your anonymity; I have no idea who you are, where you came from, and where you are headed to. Let's dream of an Internet from times past.


Home | Contact me | Github | RSS feed | Consulting services | Tools & games

When you want to render a 3D model of a world, you need to project every element onto a 2D plane (the computer screen). If you have a single planner object (say a triangle or a square), it's pretty easy to compute the projection. Things get a little complicated when you have more than one oject, where objects can partially hide each other. This problem is called the "rendering" problem.

There are several rendering algorithms: you can paint every object individually and keep track of the depth of each pixel. You can then filter these pixels and only keep the cloest one. This is called z-buffering.

Another way to render a 3D scene is to break each model into triangles. You can then use some efficient algorithm to render the triangles (a technique used by most graphics cards).

Finally, you can simulate the physics of light. This technique is called ray tracing. Ray tracing is interesting to implement just for fun, because it's pretty simple and gives nice results. The downside of ray tracing is that it computationally expensive. This means it's a slow rendering algorithm. It can be used when creating animated movies, but is not very suitable for real time applications.

This page explains how I implemented a very simple ray tracer in PHP (around 1500 lines of code). The code is open source and available on my github.

The world (world.php)

The world is a containers for various kinds of objects:

  • a camera
    defines how the world is projected. A camera contains a position (where you are in the world), a direction (where you are looking at), an angle (how wide is your vision) and what is the up-right angle (i.e. are you standing on your feet or are you standing on your hands). (camera.php)
  • one or more lights
    There are various kinds of lights (ambient or sun light, where all the rays are parallel), spot lights, directional lights, colored lights, etc. To keep things simple, I only implemented a point light: A light defined by its position in the world. (point_light.php)
  • one or more objects
    These are the things we want to render. I implemented two kinds of objects: spheres and planes. Both of these objects have a position in the 3D world, a color and a name (for debugging purpose). Spheres are further defined by their radius while planes are defined by their normal. (object.php | sphere.php | plane.php)
  • a renderer
    Ray tracers don't actually implement the entire physics of light -- things are simplified. This implies we can implement various subsets: the simplest renderer (no shadows, no lighting, no reflections) (simple_renderer.php), a flat renderer (shadows, no lighting, no reflections) (flat_renderer.php), diffuse rendering (shadows, lighting, no reflections) (diffuse_renderer.php) or phong rendering (shadows, lighting and reflections) (phong_renderer.php)

How the renderers work

All the renderers share some common logic (renderer.php). The core of the ray tracer is the render() method, which "casts" rays from the camera towards the plane of projection. For each ray, we call rayIntersection() to see if the ray intersects with any object.

The renderer returns a color, which is used to mark the pixel at the specific x,y coordinate on the projection plane.

Once we have computed all the pixels, we save the result to an image file. I implemented a bmp encoder for fun (bmp_encoder.php), but the GD library provides similar functionality (gd_encoder.php).

Sphere-ray intersection

I organized the code in a way that lets me add new kinds of objects. Each object needs to implement its formula for computing intersections with rays.

The sphere-ray intersection code therefore lives in the sphere class (sphere.php). The intersection is computed using two dot products on Vectors.

Plane-ray intersection

In a similar way, the plane-ray interscetion code lives in the plane class (plane.php). The intersection is computed using three dot products on Vectors.

Results

Simple rendering of two spheres on a floor (sample_01.php). Took ~3.5 seconds to render.


Flat rendering of two spheres on a floor (sample_03.php). Took ~6 seconds to render.


Diffuse rendering of two spheres on a floor (sample_05.php). Took ~7 seconds to render.


Phong rendering of two spheres on a floor (sample_07.php). Took ~7.5 seconds to render.

Next steps?

Having more kinds of objects (boxes, cones, cylinders, lathe, torus, triangle, discs, etc.) would allow the rendering of more interesting scenes.

A fun next step would be to extend this simple ray tracer to compute refraction too (i.e. have transparent objects).

Links