Raytracing shadows
So, I read about raytracing on the net and started writing Raytracer from scratch in my spare time. I am using C ++ which I have been studying for about a month now. I've read about the theory of raytracing on the web and it has worked quite well so far. Its just a basic Raytracer that uses no models or textures.
He first made a Raycaster and was very pleased with the result.
So, I tried several objects and it worked too. I just used diffuse shading in this implementation and added light to the color of the object at the points that it is not shaded. Unfortunately this code didn't work for multiple lights. So I started rewriting my code to support multiple lights. I also read about Phong lighting and got to work: It even works with multiple light sources:
So far I'm happy, but now I'm kind of stuck. I've tried fixing this for quite a while, but I haven't come up with anything. When I add a second Orb, or even a third, only the last Orb will light up. By the last, I mean the object in my array where I store all the objects. See below code.
It is clear that the violet Sphere should have similar illumination, since their center lies in the same plane. To my surprise, the sphere only has ambient lighting → shaded, which shouldn't be.
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;
}
The rendering function is shown below:
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 intersection 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 class Object. Every ball, light, etc. It 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 am using a global array of object pointers to store all lights, spheres, etc. the world:
Object *objects[objSize];
I know my code is a mess, but if anyone has any idea what is going on, I would really appreciate it.
EDIT 1 I've added photos.
EDIT 2 Updated code, minor bug fixed. There is still no solution.
Update: Added render code that creates rays.
source to share
Problem detected
I was able to debug your ray tracer using Linux and gcc.
As for the problem, ok ... as soon as I found it, I felt the desire again
Vector intersect = r.getOrigin() + r.getDirection()*t;
When you calculate the intersection point, you use t
instead t_min
.
The fix is to change the specified line:
Vector intersect = r.getOrigin() + r.getDirection()*t_min;
The correct conclusion is as follows:
Other offers
I think the problem is with 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 detected, you set the flag shadowed
to true
BUT you keep looping: this is inefficient and wrong. When an intersection is found, there is no need to look for another. I am assuming your flag shadow
is set to false again because you are not stopping the loop. Also, when t >= t_light
you break the loop, which is also wrong (it's just how 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:
-
Build your rendering code by adding a function that gives a ray that finds the closest / first intersection with the scene. This avoids duplication of code.
-
Don't overload operators for dot product and normalization: use special functions instead.
-
Whenever possible, try to keep your variables as small as possible: this improves the readability of your code.
-
Keep exploring raytracing because it's awesome: D
source to share