Skip to content
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
136 lines (89 sloc) 6.66 KB

Ray Tracing in One Weekend in Python

Link Followed :

NOTE : Each step is pushed under a separate branch. Master has the latest merged code

Step 1:

Setting up the Python 3 project and creating my first ppm output image

  • I chose to use pipenv since that's becoming a standard and it's a much easier way to maintain dependencies
  • I also wanted to learn Python3 and get comfortable with it after 3 years of Python2 experience

So here is my first image!

Hello World Image Output

Step 2:

The second step was to setup a Vector utility class that will be used by pretty much all the code moving forward Some decisions I had to make while write this class:

  • I tried to have Vector3 as a subclass of numpy.ndarray but chose to write it from scratch because it was easier
  • I used a decorator for some utility functions like cross,dot and unit_vector so that I can't pass around non-vectors

Step 3:

This taught be what a Ray class would look like since Rays over here is an essential entity to navigate from camera to objects

  • This took me some time to get my code working because I realised I had to override the add sub properties of the list Vector3 class to perform any sort of operations
  • I also learnt that after overriding the methods the operator order mattered. vector x 2 worked since I had defined the method that way but, 2 x vector doesn't work

But yes finally got to the lerp blue image!

Lerp Ray Camera Image

Step 4:

After a bit of Vector algebra brush-up I learnt about how to represent a sphere in a vector form and then it was pretty straightforward

  • Check if the ray is hitting the sphere: This is done by solving the quadratic dot(p(t) - C, p(t) - C) = R*R
  • All we had to check was to see if the discriminant was positive to confirm that there is a solution
  • Once this function is written we color the pixel red instead of the gradient and we get a beautiful centre sphere


Step 5

The first part of this step is to generate a way to calculate and visualize the normals

  • We start by calculating the actual determinant -> use that to calculate the normal vector -> make it it's unit vector -> map the component values to R/G/B to visualize the normals on the sphere
  • Also realised in Python3 the magic method for division should be __truediv__ instead of just __div__


  • The next part of this step was to get an abstract class working to so that we can define what a hittable object needs to implement. Mainly the "hit" function
  • We then have a Hittable list class which allows us to define a list of "hittable" objects (in our example case -> two spheres)
  • This list is then used to render out the normals for all the spheres in the scene. I went ahead and created two scenes -> one as explained in the tutorial and another with a sphere at +102.5 and that's how it looks. Fun!

Two Spheres Three Spheres

Step 6

Anti Aliasing! I took a long break before this section and so I had to read back everything I had done before to carry on the momentum

  • It was straightforward to make a Camera class and abstract what was going on in the into it's own
  • Anti Aliasing was about averaging out the color value by sending in multiple rays for the same pixel : Thankfully the random function in Python was doing it's job of giving me [0, 1) real numbers so that was great!
  • I got stuck at this step for a while because the += operator wasn't working because of the vec3 _add_ override. I had to expand it and it worked fine. I also learnt that I could override the __iadd__ method to make it work but I kept it this way for now
  • Also : Python is acting up already with just 3 spheres and samples = 100. It takes a solid 30s for this to render, but hey it did render!

Anti Aliasing

Step 7

Diffuse material! This step was to mimic a diffuse material which basically has a color of its own and take on the color of the environment Light can bounce off randomly at any direction after hitting the object

  • Learnt the interesting and hacky Rejection method for calculating a random point inside a unit sphere
  • The point of contact between two surfaces as we can see from the image is dark / shadow since it's mostly absorbed
  • With a 100 samples : The image with gamma took 133.6 seconds to render (after removing the extra sphere in the scene)

Diffuse without Gamma Diffuse With Gamma

Step 8

Materials This step I learnt how to decouple material code into an a separate and class and define the scene with the material that the sphere is coupled with

  • Some vector math came to use here to define how a metal material would work
  • Learnt the interesting approach to defining a fuzzy metal look that could use the same random unit sphere concept to get a more rough look of the same metal material
  • The final frame took 269s to render after I put 4 spheres into the scene and applied different materials
  • Added the depth parameter to restrict how deep we would want to the rays to keep bouncing

3 Spheres with Material Class 4 Spheres with metal

Step 9

Positionable camera

Learnt how we can define a movable camera in the scene and it's important parameters

  • Writing the camera that was used till now (origin and looking at -z) in the positionable camera form gave me a good understanding of what the variables were
  • Playing around with FOV I guessed it correct for what Peter Shirley had put as a closeup image!

Initial Camera Test Camera position 1

Much Lesser FOV

Step 10

Defocus blur (Depth of field)

Simulating a thin lens for getting a defocus blur

  • Used a similar random_unit_in_disc trick to augment the lens get_ray function

Defocus Blur

Final Renders!

Even though it took 99 minutes for the last 1200x800 render it was worth it I think!

Here are three renders with one in 1200x800 res

First Second


And the final render which took a ridiculous 21 hrs

Final Render

NOTE : Haven't implemented Dielectric materials yet. For another day!

You can’t perform that action at this time.