First, the code chops the screen into buckets and dumps them into a vector. Then it creates multiple threads (ideally one per CPU) that will simultaneously grab a bucket and render it. Once a thread finishes a bucket, it grabs the next until all the buckets are done. This allows the program to use as many CPUs as available.
To render a bucket, the thread simply iterates over each pixel and finds its incident light by calling the getColor() method.
//if more buckets, get one
while(buckets.size() > 0){
//get the next bucket from the queque
RenderBucket bucket =
(RenderBucket)buckets.elementAt((int)random(0,buckets.size()));
buckets.remove(bucket);
for (int i = bucket.minX; i < bucket.maxX; i++){
for (int j= bucket.minY; j < bucket.maxY; j++){
rendered.pixels[j*width + i] = getColor(i,j);
}
}
}
The getColor() method gets the incident light for a pixel. First, it calls the getRay() method, which will generate a ray from the camera into the scene. It also jitters the sample over the dimension of a single pixel -- this will antialias the pixel. Specifically, if the pixel is subdivided between two triangles, the pixel color will be an average of the two shades. Note that oversampling is done as a square of the user set value.
for (int i =0; i < overSamples.getVal()*overSamples.getVal(); i++){
dx = random(-.5,.5);
dy = random(-.5,.5);
Tuple3f direction = cam.getRay(x + dx,y + dy);
...
}
The ray is then cast into the scene. If it intersects anything, we need to evaluate the reflectance function to get a color. If not, we simply return the background color.
//prepare the picking ray
Ray pick = new Ray(cam.pos, direction);
//intersect the camera ray with the scene
tracer.accelerator.findNearest(pick);
//if ray intersects anything, get the incident light
if (pick.occluder != null){
//get incident light
}
else{
//background color
}
//pick whether to cast a light sample or an ambient occlusion sample
if (random(0,1) > hemiPower.getVal()){
ao = lightSize.getVal();
//get a ray towards the light
lmp.getSample(shadow,null);
}
//ambient occlusion
else{
ao = 1;
//get the triangle normal -- we'll perturb it later
shadow.direction = ((Triangle)pick.occluder).gNormal.getCopy();
}
shadow.direction.x += random(-ao,ao);
shadow.direction.y += random(-ao,ao);
shadow.direction.z += random(-ao,ao);
shadow.direction.normalize();
shadow.minDist = .01f;
tracer.accelerator.findNearest(shadow);
samples++;
if (shadow.occluder == null){
tritemp = (Triangle)pick.occluder;
float k = abs(shadow.direction.dot(tritemp.gNormal));
accum.plusEquals(tritemp.shader.fillColor.times(255*k));
}
accum.timesEquals(1.f/samples); return doColor(accum.x, accum.y,accum.z);
The raytracer here only performs one "bounce." If we had the time and processing power, any shadow rays that intersected other surface could have spawned a new set of shadow rays. This can become computationally cumbersome very quickly. There are methods to address this, but they are beyond the scope of this page.