Recursive Raytracing, on GPU Hardware without Recursion
CS 481 Lecture, Dr. Lawlor
It's really surprisingly easy to add true reflections to a
raytracer. If we start with the global "find_color" function,
originally something like this:
// Look up the color of the world viewed along this ray
vec4 find_color(vec3 rayStart,vec3 rayDir) {
ray_intersection i = closest_hit(rayStart,rayDir); // geometric search
return light_intersection(i); // diffuse + specular
}
To support perfect mirror reflection, you just add a recursive call to "find_color", that searches along the reflected ray:
// Recursively look up the color of the world viewed along this ray
vec4 find_color(vec3 rayStart,vec3 rayDir) {
ray_intersection i = closest_hit(rayStart,rayDir); // geometric search
vec4 local = light_intersection(i); // diffuse + specular
vec4 remote = find_color(i.hit_location,reflect(rayDir,i.normal)); // recurse!
return local*(1.0-i.mirror) + remote*i.mirror;
}
Unfortunately, the current generation of GPU hardware doesn't support
recursive functions. Until 2010 (Fermi) this was a hardware
limitation due to the
fact that functions are inlined, so there isn't a runtime call
stack. Fermi and newer cards have a runtime call stack, but it's
not supported in GLSL yet.
The Tail Recursion Trick
But luckily, we can usually transform recursion into iteration using a trick called tail recursion. For example, here's a typical recursive call:
int f(int i) {
if (i<1) return 1;
else return f(i-1)*i;
}
int foo(void) {
for (int i=0;i<10;i++) std::cout<<"f("<<i<<") = "<<f(i)<<"\n";
return 0;
}
(Try this in NetRun now!)
At runtime, "return f(i-1)*i;" has to push the return address, go off
to the i-1 work, and when it returns, finally multiply by i. It's
a lot more space and time efficient to get everything done first, then
just jump back to f, like so:
int f(int i,int theAnswer) {
if (i<1) return theAnswer;
else return f(i-1,theAnswer*i);
}
In fact, many compilers will optimize the above "tail call" into a jump:
int f(int i,int theAnswer) {
start:
if (i<1) return theAnswer;
else {
theAnswer=theAnswer*i;
i=i-1;
goto start;
}
}
Swapping the goto with a while, this is basically just a loop:
int f(int i,int theAnswer) {
while (!(i<1)) {
theAnswer=theAnswer*i;
i=i-1;
}
return theAnswer;
}
Hey! We just transformed recursion into iteration!
Recursion to Iteration in Raytracer
We can apply this same trick to transform a recursive raytracer into an iterative loop over ray bounces.
// Recursively look up the color of the world viewed along this ray
vec4 find_color(vec3 rayStart,vec3 rayDir) {
ray_intersection i = closest_hit(rayStart,rayDir); // geometric search
vec4 local = light_intersection(i); // diffuse + specular
vec4 remote = find_color(i.hit_location,reflect(rayDir,i.normal)); // recurse!
return local*(1.0-i.mirror) + remote*i.mirror;
}
Transformed to iteration, we need a "frac" variable to account
for all the recursive "i.mirror" factors from previous bounces:
// Iteratively look up the color of the world viewed along this ray
vec4 find_color(vec3 rayStart,vec3 rayDir) {
vec4 finalColor=vec4(0.0);
float frac=1.0; // fraction of my color to add to finalColor
for (int raybounce=0;raybounce<max_bounces;raybounce++) {
ray_intersection i = closest_hit(rayStart,rayDir); // geometric search
vec4 local = light_intersection(i); // diffuse + specular
finalColor += local*(1.0-i.mirror)*frac;
frac *= i.mirror; // <- scale down all subsequent rays
rayStart=i.hit_location; // change ray origin for next bounce
rayDir=reflect(rayDir,i.normal);
}
return finalColor;
}
This iterative version works great on the GPU, and the cumulative
"frac" scale factor even suggests an optimization: stop looping
when the cumulative impact drops below some quality threshold:
// Iteratively look up the color of the world viewed along this ray
vec4 find_color(vec3 rayStart,vec3 rayDir) {
... as before ...
for (int raybounce=0;raybounce<max_bounces;raybounce++) {
... as before ...
if (frac<0.05) break; // give up--not much impact on final color
}
return finalColor;
}
See the example code for a worked-out example.