4.2.2  The idea and the code

The idea is to raytrace a simple scene consisting of spheres and light sources into a 2-dimensional array containing color vectors which represents our "screen".

After this we just have to put those colors on the actual scene for POV-Ray to show them. This is made by creating a flat colored triangle mesh. The mesh is just flat like a plane with a color map on it. We could as well have written the result to a format like PPM and then read it and apply it as an image map to a plane, but this way we avoid a temporary file.

The following image is done with the raytracer SDL. It calculated the image at a resolution of 160x120 pixels and then raytraced an 512x384 image from it. This causes the image to be blurred and jagged (because it's practically "zoomed in" by a factor of 3.2). Calculating the image at 320x240 gives a much nicer result, but it's also much slower:

Some spheres raytraced by the SDL at 160x120
Some spheres raytraced by the SDL at 160x120

Note that there are no real spheres nor light sources here ("real" from the point of view of POV-Ray), just a flat colored triangle mesh (like a plane with a pigment on it) and a camera, nothing else.

Here is the source code of the raytracer; we will look it part by part through this tutorial. You can also get the source file through this link

#declare ImageWidth 
= 160;
#declare ImageHeight = 120;
#declare MaxRecLev = 5;
#declare AmbientLight = <.2,.2,.2>;
#declare BGColor = <0,0,0>;

// Sphere information.
// Values are:
// Center, <Radius, Reflection, 0>, Color, <phong_size, amount, 0>
#declare Coord = array[5][4]
{ {<-1.05,0,4>, <1,.5,0>, <1,.5,.25>, <40, .8, 0>}
{<1.05,0,4>, <1,.5,0>, <.5,1,.5>, <40, .8, 0>}
{<0,-3,5>, <2,.5,0>, <.25,.5,1>, <30, .4, 0>}
{<-1,2.3,9>, <2,.5,0>, <.5,.3,.1>, <30, .4, 0>}
{<1.3,2.6,9>, <1.8,.5,0>, <.1,.3,.5>, <30, .4, 0>}
}

// Light source directions and colors:
#declare LVect = array[3][2]
{ {<-1, 0, -.5>, <.8,.4,.1>}
{<1, 1, -.5>, <1,1,1>}
{<0,1,0>, <.1,.2,.5>}
}



//==========================================================================
// Raytracing calculations:
//==========================================================================
#declare MaxDist = 1e5;
#declare ObjAmnt = dimension_size(Coord, 1);
#declare LightAmnt = dimension_size(LVect, 1);

#declare Ind = 0;
#while(Ind < LightAmnt)
#declare LVect[Ind][0] = vnormalize(LVect[Ind][0]);
#declare Ind = Ind+1;
#end

#macro calcRaySphereIntersection(P, D, sphereInd)
#local V = P-Coord[sphereInd][0];
#local R = Coord[sphereInd][1].x;

#local DV = vdot(D, V);
#local D2 = vdot(D, D);
#local SQ = DV*DV-D2*(vdot(V, V)-R*R);
#if(SQ < 0) #local Result = -1;
#else
#local SQ = sqrt(SQ);
#local T1 = (-DV+SQ)/D2;
#local T2 = (-DV-SQ)/D2;
#local Result = (T1<T2 ? T1 : T2);
#end
Result
#end

#macro Trace(P, D, recLev)
#local minT = MaxDist;
#local closest = ObjAmnt;

// Find closest intersection:
#local Ind = 0;
#while(Ind < ObjAmnt)
#local T = calcRaySphereIntersection(P, D, Ind);
#if(T>0 & T<minT)
#local minT = T;
#local closest = Ind;
#end
#local Ind = Ind+1;
#end

// If not found, return background color:
#if(closest = ObjAmnt)
#local Pixel = BGColor;
#else
// Else calculate the color of the intersection point:
#local IP = P+minT*D;
#local R = Coord[closest][1].x;
#local Normal = (IP-Coord[closest][0])/R;

#local V = P-IP;
#local Refl = 2*Normal*(vdot(Normal, V)) - V;

// Lighting:
#local Pixel = AmbientLight;
#local Ind = 0;
#while(Ind < LightAmnt)
#local L = LVect[Ind][0];

// Shadowtest:
#local Shadowed = false;
#local Ind2 = 0;
#while(Ind2 < ObjAmnt)
#if(Ind2!=closest & calcRaySphereIntersection(IP, L, Ind2)>0)
#local Shadowed = true;
#local Ind2 = ObjAmnt;
#end
#local Ind2 = Ind2+1;
#end

#if(!Shadowed)
// Diffuse:
#local Factor = vdot(Normal, L);
#if(Factor > 0)
#local Pixel = Pixel + LVect[Ind][1]*Coord[closest][2]*Factor;
#end

// Specular:
#local Factor = vdot(vnormalize(Refl), L);
#if(Factor > 0)
#local Pixel = Pixel +
LVect[Ind][1]*pow(Factor, Coord[closest][3].x)*
Coord[closest][3].y;
#end
#end
#local Ind = Ind+1;
#end

// Reflection:
#if(recLev < MaxRecLev & Coord[closest][1].y > 0)
#local Pixel = Pixel + Trace(IP, Refl, recLev+1)*Coord[closest][1].y;
#end
#end

Pixel
#end


#debug "Rendering...\n\n"
#declare Image = array[ImageWidth][ImageHeight]
#declare IndY = 0;
#while(IndY < ImageHeight)
#declare CoordY = IndY/(ImageHeight-1)*2-1;
#declare IndX = 0;
#while(IndX < ImageWidth)
#declare CoordX = (IndX/(ImageWidth-1)-.5)*2*ImageWidth/ImageHeight;
#declare Image[IndX][IndY] =
Trace(-z*3, <CoordX, CoordY, 3>, 1);
#declare IndX = IndX+1;
#end
#declare IndY = IndY+1;
#debug concat("\rDone ", str(100*IndY/ImageHeight, 0, 1),
"% (line ", str(IndY,0,0), " out of ", str(ImageHeight,0,0), ")")
#end
#debug "\n"


//==========================================================================
// Image creation (colored mesh):
//==========================================================================
#default { finish { ambient 1 } }

#debug "Creating colored mesh to show image...\n"
mesh2
{
vertex_vectors
{
ImageWidth*ImageHeight*2,
#declare IndY = 0;
#while(IndY < ImageHeight)
#declare IndX = 0;
#while(IndX < ImageWidth)
<(IndX/(ImageWidth-1)-.5)*ImageWidth/ImageHeight*2,
IndY/(ImageHeight-1)*2-1, 0>,
<((
IndX+.5)/(ImageWidth-1)-.5)*ImageWidth/ImageHeight*2,
(
IndY+.5)/(ImageHeight-1)*2-1, 0>
#declare IndX = IndX+1;
#end
#declare IndY = IndY+1;
#end
}
texture_list
{ ImageWidth*ImageHeight*2,
#declare IndY = 0;
#while(IndY < ImageHeight)
#declare IndX = 0;
#while(IndX < ImageWidth)
texture { pigment { rgb Image[IndX][IndY] } }
#if(IndX < ImageWidth-1 & IndY < ImageHeight-1)
texture { pigment { rgb
(
Image[IndX][IndY]+Image[IndX+1][IndY]+
Image[IndX][IndY+1]+Image[IndX+1][IndY+1])/4 } }
#else
texture { pigment { rgb 0 } }
#end
#declare IndX = IndX+1;
#end
#declare IndY = IndY+1;
#end
}
face_indices
{ (ImageWidth-1)*(ImageHeight-1)*4,
#declare IndY = 0;
#while(IndY < ImageHeight-1)
#declare IndX = 0;
#while(IndX < ImageWidth-1)
<IndX*2+ IndY *(ImageWidth*2),
IndX*2+2+IndY *(ImageWidth*2),
IndX*2+1+IndY *(ImageWidth*2)>,
IndX*2+ IndY *(ImageWidth*2),
IndX*2+2+IndY *(ImageWidth*2),
IndX*2+1+IndY *(ImageWidth*2),

<
IndX*2+ IndY *(ImageWidth*2),
IndX*2+ (IndY+1)*(ImageWidth*2),
IndX*2+1+IndY *(ImageWidth*2)>,
IndX*2+ IndY *(ImageWidth*2),
IndX*2+ (IndY+1)*(ImageWidth*2),
IndX*2+1+IndY *(ImageWidth*2),

<
IndX*2+ (IndY+1)*(ImageWidth*2),
IndX*2+2+(IndY+1)*(ImageWidth*2),
IndX*2+1+IndY *(ImageWidth*2)>,
IndX*2+ (IndY+1)*(ImageWidth*2),
IndX*2+2+(IndY+1)*(ImageWidth*2),
IndX*2+1+IndY *(ImageWidth*2),

<
IndX*2+2+IndY *(ImageWidth*2),
IndX*2+2+(IndY+1)*(ImageWidth*2),
IndX*2+1+IndY *(ImageWidth*2)>,
IndX*2+2+IndY *(ImageWidth*2),
IndX*2+2+(IndY+1)*(ImageWidth*2),
IndX*2+1+IndY *(ImageWidth*2)
#declare IndX = IndX+1;
#end
#declare IndY = IndY+1;
#end
}
}

camera { location -z*2 look_at 0 orthographic }