3
votes

I'm working on a raytracer using C++, and so far I've been able to compute a lighting model based on diffuse, specular and ambient components. My problem appeared when I tried to add shadows to my scenes: the scenes get really messed up:

enter image description here

My code is divided as follows:

  • I have a base class "SceneObject", which has a virtual method intersect(), which takes a ray (defined by origin and direction) and outputs a boolean, as well as return arguments for the calculated t value, a hitpoint and the normal of the object.
  • The "Material" class contains a specular and diffuse colours (vectors) as well as a value for a phong exponent (int).
  • I have 3 derived classes from the above SceneObject: plane, triangle and sphere classes, each with it's own version of the intersect defined in the base class.
  • I have a function that calculates output colour for a given pixel using the normal of the object, the hitpoint, a source of light and the object material.
  • All my objects to render are stored in a vector.

Here is one of my derived classes, Triangle:

class Triangle: public SceneObject{
public:
    Triangle (vec3 a, vec3 b, vec3 c, Material mat){
        name = "Triangle";
        p0 = a;
        p1 = b;
        p2 = c;
        objectMaterial = mat;
        normal = normalize(cross(p0-p1, p0-p2));
    }

    //Möller-Trumbore algorithm
    bool intersect(Ray aRay, float &t, vec3 &hitPoint, vec3 &n){//we will use ray-plane intersection and barycentric coords:

        bool returnValue = false;
        //first we need to get a t of intersection between the passed ray and the triangle
        vec3 v0v1 = p1-p0;
        vec3 v0v2 = p2-p0;
        vec3 pvec = cross(aRay.getDirection(), v0v2);

        float det = dot(v0v1, pvec);

        if ( det >= 1e-6 ){ // Only draw if not backfacing

            float invDet = 1/det;

            float u = dot(-p0, pvec) * invDet;
            // No intersection if u < 0 or u > 1
            if (u >=0 && u <= 1) {
                vec3 qvec = cross(-p0, v0v1);
                float v = dot(aRay.getDirection(), qvec) * invDet;

                // No intersection if v < 0 or u + v > 1
                if (v >=0 && (u + v) <= 1){
                    t = dot(v0v2, qvec) * invDet;
                    returnValue = true;

                    hitPoint = aRay.getOrigin() + (t*aRay.getDirection());
                    n = normal;
                    //calculated_Out_Colour = calculateOutputColour(normal, aRay, lightSource, objectMaterial, t, hitPoint);

                }
            }
        }

        return returnValue; 
    }

private:
    vec3 p0;
    vec3 p1;
    vec3 p2;
    vec3 normal;
};

And this is my main loop where i generate all my rays for every pixel of my window, and determine the colour and if the current position is in shade or not:

for(int i=0;i<imageBuffer.Height();i++){
for(int j=0;j<imageBuffer.Width();j++){
    float currentX = ((float)i-256);
            float currentY = ((float)j-256);
            //cout << currentX << ", " << currentY << ", " << currentZ << endl;
            //make a ray for this pixel (i,j)
            glm::vec3 rayDirection = glm::normalize(glm::vec3(currentX, currentY, -d));
    //make a ray for this pixel (i,j)
    Ray currentRay(vec3(0,0,0), rayDirection);

    vec3 hitPoint;
    vec3 normalAtHit;
    float tnear = 999; // closest intersection, set to INFINITY to start with
    SceneObject* object = NULL;
    for (int k = 0; k < objects.size(); k++) {
        float t; // intersection to the current object if any
        if (objects[k]->intersect(currentRay, t, hitPoint, normalAtHit) && t < tnear) {
            object = objects[k].get();
            tnear = t;

            vec3 shadowRayDirection = normalize(light1.getLightOrigin()-hitPoint);
            Ray shadowRay(hitPoint+vec3(0.03, 0.03,0.03), shadowRayDirection);
            float shadowT;
            vec3 shadowHitPoint;
            vec3 shadowN;
            for (int m = 0; m < objects.size(); ++m) {
                if (objects[m]->intersect(shadowRay, shadowT, shadowHitPoint, shadowN)) {
                    imageBuffer.SetPixel(i, j, ambientColour*ambientIntensity); 
                    break;
                } else {
                    imageBuffer.SetPixel(i, j, calculateOutputColour(normalAtHit, currentRay, light1, objects[k]->getMaterial(), hitPoint));
                }
            }
            }
        }
    }
}

I'm honestly at a loss here and I have no clue why this is happening. I've tried using the algorithm described here, but it produces the same result shown in the image. For reference, if I take the loop and check for shadows, my scene looks like this:

enter image description here

I appreciate any help in trying to debug this thing. Thanks.

1
For your shadow ray, you should be using hitPoint+0.03*shadowRayDirection or hitPoint+0.03*normalAtHit for the ray's origin (either one can work; arguments can be made either way).Cornstalks
corrected it to float shadowOffset = 0.05; Ray shadowRay(hitPoint+shadowOffset*shadowRayDirection, shadowRayDirection); but it produces the same result :-(jjcastil
The way you calculate t might be wrong (but it might be right; I'm not sure). I do it differently in my ray tracer: vec3 n = cross(v0v1, v0v2); float num = dot(n, p0 - aRay.getOrigin()); float den = dot(n, aRay.getDirection()); t = num / den; (but check that den != 0 before dividing). You might want to try that. One thing you can do to test if your t value is right is output a depth image so you can see if your calculated intersection points match up with what you're expecting.Cornstalks

1 Answers

0
votes

Cornstalks is right but one thing important if you are using hitPoint+0.03*normalAtHit, you need to be sure that the normal is the one that matches the incident ray, from what we see in your code you just take the normal of your primitive but each face has 2 normals (one reversed from the other).

Depending on that you may have a ray that start from the wrong side and thus may cause invalid shadow.

To do this you can check the dot product of your incidentRay with the normal, depending on the result (greater than 0 or less than 0), you will know if you need to reverse your normal.