Raytracing
Fabien Sanglard's Website
September 20th, 2013
Decyphering the Business Card Raytracer
I recently came across Paul Heckbert's business card raytracer.<br>For those that have never heard of it: It is a very famous challenge in the Computer Graphics field that<br>started on May 4th, 1984 via a post on comp.graphics by Paul Heckbert ( More about this in his article "A Minimal Ray Tracer" from the book Graphics Gems IV).
The goal was to produce the source code for a raytracer...that would fit on the back of a business card.
Andrew Kensler's version is mesmerizing and one of the<br>greatest hack that I have seen. Since I am curious, I decided to deconstruct it: Here is what I understood.
Edit : Andrew Kensler himself commented on Hacker News and adressed/elaborated on my observations: Thanks you so much Andrew :) !
The Business Card Code
#include // card > aek.ppm<br>#include<br>#include<br>typedef int i;typedef float f;struct v{<br>f x,y,z;v operator+(v r){return v(x+r.x<br>,y+r.y,z+r.z);}v operator*(f r){return<br>v(x*r,y*r,z*r);}f operator%(v r){return<br>x*r.x+y*r.y+z*r.z;}v(){}v operator^(v r<br>){return v(y*r.z-z*r.y,z*r.x-x*r.z,x*r.<br>y-y*r.x);}v(f a,f b,f c){x=a;y=b;z=c;}v<br>operator!(){return*this*(1/sqrt(*this%*<br>this));}};i G[]={247570,280596,280600,<br>249748,18578,18577,231184,16,16};f R(){<br>return(f)rand()/RAND_MAX;}i T(v o,v d,f<br>&t,v&n){t=1e9;i m=0;f p=-o.z/d.z;if(.01<br>){f s=-b-sqrt(q);if(s.01)t=s,n=!(<br>p+d*t),m=2;}}return m;}v S(v o,v d){f t<br>;v n;i m=T(o,d,t,n);if(!m)return v(.7,<br>.6,1)*pow(1-d.z,4);v h=o+d*t,l=!(v(9+R(<br>),9+R(),16)+h*-1),r=d+n*(n%d*-2);f b=l%<br>n;if(b0),99);if(m&1){h=h*.2;return((i)(ceil(<br>h.x)+ceil(h.y))&1?v(3,1,1):v(3,3,3))*(b<br>*.2+.1);}return v(p,p,p)+S(h,r)*.5;}i<br>main(){printf("P6 512 512 255 ");v g=!v<br>(-6,-16,0),a=!(v(0,0,1)^g)*.002,b=!(g^a<br>)*.002,c=(a+b)*-256+g;for(i y=512;y--;)<br>for(i x=512;x--;){v p(13,13,13);for(i r<br>=64;r--;){v t=a*(R()-.5)*99+b*(R()-.5)*<br>99;p=S(v(17,16,8)+t,!(t*-1+(a*(R()+x)+b<br>*(y+R())+c)*16))*3.5+p;}printf("%c%c%c"<br>,(i)p.x,(i)p.y,(i)p.z);}}
The code looks confusing but it compiles and run flawlessly! On my MacBook Air, it takes 27 seconds to generate the image on the right. If you want to run it yourself, save the code in a file named card.cpp, open a Terminal window and type:
c++ -O3 -o card card.cpp<br>./card > card.ppm
Business Card Raycaster features
The list of features is impressive :
The world is made of spheres which are carefully organized.
There is a floor and it is textured.
There is a sky and it has a nice gradient.
Shadows are soft shadows.
OMG, Depth of View blur: Are you kidding me ?
All that fitting on the back of a business card ! Let's see how it works !
Vector class
#include // card > aek.ppm<br>#include<br>#include<br>typedef int i;typedef float f;struct v{<br>f x,y,z;v operator+(v r){return v(x+r.x<br>,y+r.y,z+r.z);}v operator*(f r){return<br>v(x*r,y*r,z*r);}f operator%(v r){return<br>x*r.x+y*r.y+z*r.z;}v(){}v operator^(v r<br>){return v(y*r.z-z*r.y,z*r.x-x*r.z,x*r.<br>y-y*r.x);}v(f a,f b,f c){x=a;y=b;z=c;}v<br>operator!(){return*this*(1/sqrt(*this%*<br>this));}};i G[]={247570,280596,280600,<br>249748,18578,18577,231184,16,16};f R(){<br>return(f)rand()/RAND_MAX;}i T(v o,v d,f<br>&t,v&n){t=1e9;i m=0;f p=-o.z/d.z;if(.01<br>){f s=-b-sqrt(q);if(s.01)t=s,n=!(<br>p+d*t),m=2;}}return m;}v S(v o,v d){f t<br>;v n;i m=T(o,d,t,n);if(!m)return v(.7,<br>.6,1)*pow(1-d.z,4);v h=o+d*t,l=!(v(9+R(<br>),9+R(),16)+h*-1),r=d+n*(n%d*-2);f b=l%<br>n;if(b0),99);if(m&1){h=h*.2;return((i)(ceil(<br>h.x)+ceil(h.y))&1?v(3,1,1):v(3,3,3))*(b<br>*.2+.1);}return v(p,p,p)+S(h,r)*.5;}i<br>main(){printf("P6 512 512 255 ");v g=!v<br>(-6,-16,0),a=!(v(0,0,1)^g)*.002,b=!(g^a<br>)*.002,c=(a+b)*-256+g;for(i y=512;y--;)<br>for(i x=512;x--;){v p(13,13,13);for(i r<br>=64;r--;){v t=a*(R()-.5)*99+b*(R()-.5)*<br>99;p=S(v(17,16,8)+t,!(t*-1+(a*(R()+x)+b<br>*(y+R())+c)*16))*3.5+p;}printf("%c%c%c"<br>,(i)p.x,(i)p.y,(i)p.z);}}
The main trick that helps reducing the code size dramatically it to use two define :
Define i as int.
Define f as float.
The other big space saver is the 'v' C++ class which is used for vector but also pixel processing.
Here is the code formated, highlighted and commented:
#include // card > aek.ppm<br>#include<br>#include
typedef int i; //Save space by using 'i' instead of 'int'<br>typedef float f; //Save even more space by using 'f' instead of 'float'
//Define a vector class with constructor and operator: 'v'<br>struct v{<br>f x,y,z; // Vector has three float attributes.<br>v operator+(v r){return v(x+r.x,y+r.y,z+r.z);} //Vector add<br>v operator*(f r){return v(x*r,y*r,z*r);} //Vector scaling<br>f operator%(v r){return x*r.x+y*r.y+z*r.z;} //Vector dot product<br>v(){} //Empty constructor<br>v operator^(v r){return v(y*r.z-z*r.y,z*r.x-x*r.z,x*r.y-y*r.x);} //Cross-product<br>v(f a,f b,f c){x=a;y=b;z=c;} //Constructor<br>v operator!(){return *this*(1 /sqrt(*this%*this));} // Used later for normalizing<br>};
Random() and World database
#include // card > aek.ppm<br>#include<br>#include<br>typedef int i;typedef float f;struct v{<br>f x,y,z;v operator+(v r){return v(x+r.x<br>,y+r.y,z+r.z);}v operator*(f...