Raytracing Shadows

Koto picture Koto · Sep 19, 2014 · Viewed 9.1k times · Source

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. enter image description here

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. enter image description here 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: enter image description here It even works with multiple lights: enter image description here

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. enter image description here

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.

Answer

XDnl picture XDnl · Sep 24, 2014

Problem found

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:

enter image description here

Other suggestions

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