So, I read about raytracing on the net and started writing a Raytracer from scratch in my spare time. I'm using C++, which I've been learning for about a month now. I have read up on the theory of raytracing on the net and so far its working quite well. Its just a basic Raytracer, that uses neither models or textures.
At first it made a Raycaster and was quite pleased with the result.
So then I tried multiple objects and it worked too. I just used diffuse shading in this implementation and added to the colour of the light to the object colour at the points it is not shaded. Unfortunately this Code did not work for multiple light sources. So then I started to rewrite my code, so that it supports multiple lights. I also read up on Phong illumination and got to work: It even works with multiple lights:
So far I'm pleased, but now my I'm kinda stuck. I've tried fixing this for quite some time but I came up with nothing. When I add a second Sphere or even a third, only the last Sphere is illuminated. By last I mean the object in my array where I store all objects.See code below.
Clearly the purple Sphere should have a similar illumination, since their center lie in the same plane. To my surprise the sphere only has the ambient illumination --> shaded, which should not be the case.
So now my trace function:
Colour raytrace(const Ray &r, const int &depth)
{
//first find the nearest intersection of a ray with an object
//go through all objects an find the one with the lowest parameter
double t, t_min = INFINITY;
int index_nearObj = -1;//-1 to know if obj found
for(int i = 0; i < objSize; i++)
{
//skip light src
if(!dynamic_cast<Light *>(objects[i]))
{
t = objects[i]->findParam(r);
if(t > 0 && t < t_min) //if there is an intersection and
//its distance is lower than the current min --> new min
{
t_min = t;
index_nearObj = i;
}
}
}
//now that we have the nearest intersection, calc the intersection point
//and the normal at that point
//r.position + t * r.direction
if(t_min < 0 || t_min == INFINITY) //no intersection --> return background Colour
return White;
Vector intersect = r.getOrigin() + r.getDirection()*t;
Vector normal = objects[index_nearObj]->NormalAtIntersect(intersect);
//then calculate light ,shading and colour
Ray shadowRay;
Ray rRefl;//reflected ray
bool shadowed;
double t_light = -1;
Colour finalColour = White;
Colour objectColor = objects[index_nearObj]->getColour();
Colour localColour;
Vector tmpv;
//get material properties
double ka = 0.1; //ambient coefficient
double kd; //diffuse coefficient
double ks; //specular coefficient
Colour ambient = ka * objectColor; //ambient component
//the minimum Colour the obj has, even if object is not hit by light
Colour diffuse, specular;
double brightness;
int index = -1;
localColour = ambient;
//look if the object is in shadow or light
//do this by casting a ray from the obj and
// check if there is an intersection with another obj
for(int i = 0; i < objSize; i++)
{
if(dynamic_cast<Light *>(objects[i])) //if object is a light
{//for each light
shadowed = false;
//create Ray to light
//its origin is the intersection point
//its direction is the position of the light - intersection
tmpv = objects[i]->getPosition() - intersect;
shadowRay = Ray(intersect + (!tmpv) * BIAS, tmpv);
//the ray construcor automatically normalizes its direction
t_light = objects[i]->findParam(shadowRay);
if(t_light < 0) //no imtersect, which is quite impossible
continue;
//then we check if that Ray intersects one object that is not a light
for(int j = 0; j < objSize; j++)
{
if(!dynamic_cast<Light *>(objects[j]))//if obj is not a light
{
//we compute the distance to the object and compare it
//to the light distance, for each light seperately
//if it is smaller we know the light is behind the object
//--> shadowed by this light
t = objects[j]->findParam(shadowRay);
if(t < 0) // no intersection
continue;
if(t < t_light) // intersection that creates shadow
shadowed = true;
else
{
shadowed = false;
index = j;//not using the index now,maybe later
break;
}
}
}
//we know if intersection is shadowed or not
if(!shadowed)// if obj is not shadowed
{
rRefl = objects[index_nearObj]->calcReflectingRay(shadowRay, intersect); //reflected ray from ligh src, for ks
kd = maximum(0.0, (normal|shadowRay.getDirection()));
ks = pow(maximum(0.0, (r.getDirection()|rRefl.getDirection())), objects[index_nearObj]->getMaterial().shininess);
diffuse = kd * objectColor;// * objects[i]->getColour();
specular = ks * objects[i]->getColour();//not sure if obj needs specular colour
brightness = 1 /(1 + t_light * DISTANCE_DEPENDENCY_LIGHT);
localColour += brightness * (diffuse + specular);
}
}
}
//handle reflection
//handle transmission
//combine colours
//localcolour+reflectedcolour*refl_coeff + transmittedcolor*transmission coeff
finalColour = localColour; //+reflcol+ transmcol
return finalColour;
}
Next is the render function:
for(uint32_t y = 0; y < h; y++)
{
for(uint32_t x = 0; x < w; x++)
{
//pixel coordinates for the scene, depends on implementation...here camera on z axis
pixel.X() = ((x+0.5)/w-0.5)*aspectRatio *angle;
pixel.Y() = (0.5 - (y+0.5)/w)*angle;
pixel.Z() = look_at.getZ();//-1, cam at 0,0,0
rTmp = Ray(cam.getOrigin(), pixel - cam.getOrigin());
cTmp = raytrace(rTmp, depth);//depth == 0
pic.setPixel(y, x, cTmp);//writes colour of pixel in picture
}
}
So here are my intersect functions:
double Sphere::findParam(const Ray &r) const
{
Vector rorig = r.getOrigin();
Vector rdir = r.getDirection();
Vector center = getPosition();
double det, t0 , t1; // det is the determinant of the quadratic equation: B² - 4AC;
double a = (rdir|rdir);
//one could optimize a away cause rdir is a unit vector
double b = ((rorig - center)|rdir)*2;
double c = ((rorig - center)|(rorig - center)) - radius*radius;
det = b*b - 4*c*a;
if(det < 0)
return -1;
t0 = (-b - sqrt(det))/(2*a);
if(det == 0)//one ontersection, no need to compute the second param!, is same
return t0;
t1 = (-b + sqrt(det))/(2*a);
//two intersections, if t0 or t1 is neg, the intersection is behind the origin!
if(t0 < 0 && t1 < 0) return -1;
else if(t0 > 0 && t1 < 0) return t0;
else if(t0 < 0 && t1 > 0) return t1;
if(t0 < t1)
return t0;
return t1;
}
Ray Sphere::calcReflectingRay(const Ray &r, const Vector &intersection)const
{
Vector rdir = r.getDirection();
Vector normal = NormalAtIntersect(intersection);
Vector dir = rdir - 2 * (rdir|normal) * normal;
return Ray(intersection, !dir);
}
//Light intersection(point src)
double Light::findParam(const Ray &r) const
{
double t;
Vector rorig = r.getOrigin();
Vector rdir = r.getDirection();
t = (rorig - getPosition())|~rdir; //~inverts a Vector
//if I dont do this, then both spheres are not illuminated-->ambient
if(t > 0)
return t;
return -1;
}
Here is the abstract Object class. Each sphere, light, etc. is an object.
class Object
{
Colour color;
Vector pos;
//Colour specular;not using right now
Material_t mat;
public:
Object();
Object(const Colour &);
Object(const Colour &, const Vector &);
Object(const Colour &, const Vector &, const Material_t &);
virtual ~Object()=0;
virtual double findParam(const Ray &) const =0;
virtual Ray calcReflectingRay(const Ray &, const Vector &)const=0;
virtual Ray calcRefractingRay(const Ray &, const Vector &)const=0;
virtual Vector NormalAtIntersect(const Vector &)const=0;
Colour getColour()const {return color;}
Colour & colour() {return color;}
Vector getPosition()const {return pos;}
Vector & Position() {return pos;}
Material_t getMaterial()const {return mat;}
Material_t & material() {return mat;}
friend bool operator!=(const Object &obj1, const Object &obj2)
{//compares only references!
if(&obj1 != &obj2)
return true;
return false;
}
};
I use a global array of Object Pointers to store all lights, spheres, etc of a world:
Object *objects[objSize];
I know my code is a mess, but if anybody has an idea what is going on, I'd be very grateful.
EDIT 1 I added pictures.
EDIT 2 Updated code, fixed a minor Error. Still no solution.
Update: Added render code, which creates rays.
I managed to debug your ray tracer using Linux and gcc.
Regarding the problem, well... once I found it I felt the urge to bash my head repeatedly against my keyboard. :)
Your algorithm is correct, except for a little sneaky detail:
Vector intersect = r.getOrigin() + r.getDirection()*t;
When you calculate the intersection point, you use t
instead of t_min
.
The fix consists in changing the above line to:
Vector intersect = r.getOrigin() + r.getDirection()*t_min;
The correct output is the following:
I think the problem lies in your shadow ray loop:
//then we check if that Ray intersects one object that is not a light
for(int j = 0; j < objSize; j++)
{
if(!dynamic_cast<Light *>(objects[j]))//if obj is not a light
{
//we compute the distance to the object and compare it
//to the light distance, for each light seperately
//if it is smaller we know the light is behind the object
//--> shadowed by this light
t = objects[j]->findParam(shadowRay);
if(t < 0) // no intersection
continue;
if(t < t_light) // intersection that creates shadow
shadowed = true;
else
{
shadowed = false;
index = j;//not using the index now,maybe later
break;
}
}
}
Basically, when an intersection is found, you set the shadowed
flag to true
, BUT you continue the cycle: this is both inefficient and incorrect.
When an intersection is found, there is no need to search for another.
My guess is that your shadow
flag is set to false again because you don't stop the loop.
Also, when t >= t_light
you interrupt the cycle, which is also incorrect (it's just like t < 0
).
I would change the code to the following:
//then we check if that Ray intersects one object that is not a light
for (int j = 0; j < objSize; j++)
{
// Exclude lights AND the closest object found
if(j != index_nearObj && !dynamic_cast<Light *>(objects[j]))
{
//we compute the distance to the object and compare it
//to the light distance, for each light seperately
//if it is smaller we know the light is behind the object
//--> shadowed by this light
t = objects[j]->findParam(shadowRay);
// If the intersection point lies between the object and the light source,
// then the object is in shadow!
if (t >= 0 && t < t_light)
{
// Set the flag and stop the cycle
shadowed = true;
break;
}
}
}
Some other suggestions:
Refactor your rendering code by adding a function which given a ray finds the closest/first intersection with the scene. This avoids code duplication.
Don't overload operators for dot product and normalization: use instead dedicated functions.
Try to reduce the scope of your variables as much as possible: this improves code readability.
Keep exploring raytracing stuff because it's awesome :D