Toy raytracer in Python

This page is a mirrored copy of an article originally posted on the LShift blog; see the archive index here.

I spent a few days in Wellington recently with my 12-year-old brother. We somehow got to talking about ray tracing, and so we ran through a bit of linear algebra (vectors, normals, and dot products, basically) and built ourselves a raytracer in Python as a lark. We collaborated on the Vector and Point classes, and I filled in the rest of the program. There’s still a bug in the checkerboard code (see the odd horizontal lines in the reflection of the checkerboard in the yellow sphere?) but it’s not too awful for an afternoon’s idle hacking.

The code is contained within raytrace.py, and here’s the output it generates:

Raytracer output

Wouldn’t it be nice to have classes like Vector and Point as part of the standard distribution of Python? Even better, though, and perhaps less subject to bike-shed debates, would be the inclusion of a simple, small PNG-writing module like this one as part of standalone base Python!


On 29 October, 2008 at 4:00 pm, Rui Carmo wrote:

Pretty neat. Y’know, I’ve done a PNG Canvas library for a few years now:


It might be interesting to put all of them together somehow.

On 29 October, 2008 at 4:43 pm, tonyg wrote:

Absolutely! I’d love to see a basic Canvas interface grow up, backed by (say) PNG as well as mappable to surfaces offered by the various GUI libraries out there. Sadly, even systems as beginner-friendly as Python still make it very, very difficult to get pixels on screen. A simple Canvas would be a big step in the right direction.

On 18 February, 2009 at 11:07 pm, TuckerBeck wrote:

Both things you are looking for are already available.

Points, Vectors - > Numpy’s array objects (support vector math and very fast too )

PNG writer -> Python Imaging Library

Both are ridiculously easy to implement for this sort of thing.

On 19 February, 2009 at 2:14 am, tonyg wrote:

TuckerBeck, readily available yes, but not part of base python.

On 7 July, 2009 at 7:05 am, Frank wrote:

Hi tonyg,

that bug you describe concerning the lines of the checkerboard is probably not a bug, but something one calls the aliasing effect. Aliasing happens when you sample something with a too low frequency. You shouldn’t worry too much about this, however, since you’ve just dived into the matter, as you said. Why not try some fancy other raytracing stuff instead of dealing with the aliasing? :)

Keep on,

On 7 July, 2009 at 10:39 am, Peters Linkschleuder – Der Schockwellenreiter wrote:

[...] hat jemand einen Toy-Raytracer mit Python [...]

On 1 August, 2009 at 3:19 pm, Peter wrote:

If you just want to play about with graphics, Nodebox is a great way to do it in python with all of that stuff built in.

On 1 August, 2009 at 6:49 pm, tonyg wrote:

Peter: Nodebox looks lovely! It does seem to be OS X specific, though. I’d love for there to be a consistent, cross-platform approach to simple graphics in some reasonable programming language. (See: squeak smalltalk.)

Frank: It doesn’t look like an aliasing effect. It looks more like some kind of sign or rounding problem. Re: slightly fancier raytracing: the low hanging fruit, I’d say, would be CSG… :-)

On 4 December, 2009 at 1:32 pm, Victor wrote:

Hi tonyg, I’m trying to understand how intersection works in your code, but fail till now. Can you please explain what happens in intersectionTime function of Sphere class, and maybe outline how it would look for another primitive like Cone?

On 4 December, 2009 at 1:59 pm, Paul Crowley wrote:

Victor: how is your vector math, are you familiar with dot products and the like? It’s hard to explain without diagrams, but if you try to figure out how you would implement intersectionTime then this implementation may become clearer.

On 4 December, 2009 at 3:02 pm, Paul Crowley wrote:

Over a year later, I found that bug - you don’t take either the origin of the ray or of the plane into account when computing the intersection distance, only the dot product of their direction vectors. Have fixed in local version, shall I push to hg?

On 4 December, 2009 at 4:02 pm, Victor wrote:

Hi Paul,
I didn’t find a bug yet, so it would be nice to see a fix :) I’m ok with vector math, but still I don’t understand why the sphere works and my cone - not :) to be more specific - here’s mine implementation (it renders something, which is definitively not cone :(:

On 4 December, 2009 at 4:04 pm, Victor wrote:

oops the code is stripped out from the comment. you can find it here: http://etherpad.com/51Zq0cdWhf

On 4 December, 2009 at 5:02 pm, Victor wrote:

I may be even wrong and it works, but I cant position it properly. Anyway - example output:

On 7 December, 2009 at 5:11 pm, tonyg wrote:

@Paul: yes please!

@Victor: intersectionTime is meant (if I remember rightly!) to return how far along the given ray, if at all, an intersection between the ray and the surface of the object happens, in units of multiples of ray.vector (so a value of n returned from intersectionTime indicates that the first intersection happens at a point (ray.point + n * ray.vector)). I haven’t looked closely at your code, but from the presence of the quadratic equation it looks like you’re on the right track :-) What does it render currently?

On 7 December, 2009 at 6:21 pm, Victor wrote:

actually looks like cone when reflected in the yelow sphere (I didn’t change the scene). I never managed to position the camer properly though :) And my code fails simple tests in 2d, which shall work fine, so I suppose the equation is wrong somehow.