Coda
Coda

Reputation: 41

Raytracing - lighting equations

I'm trying to write a raytracer in C++, but the result so far is not as I expected, so I guess there is an error in the lighting equations. This is what I've got so far:

Picture of my result. In the upper line what I've got, in the bottom line what I would like to get

I'm using the Blinn(-Phong) model and assume that all the lights are point lights. This is my code:

vec3f raytrace_ray(Scene* scene, ray3f ray) {
// get scene intersection
auto intersection = intersect_surfaces(scene, ray);

// if not hit, return background
if (!intersection.hit) {
    return scene->background;
}

// accumulate color starting with ambient
vec3f c = zero3f;
c += scene->ambient*intersection.mat->kd;

//Add emission
c += intersection.mat->ke;

// foreach light
for (Light* light : scene->lights) {
    // compute light response
    auto lightRadiance = light->intensity / distSqr(light->frame.o, intersection.pos);
    if(lightRadiance == zero3f) continue;

    // compute light direction
    auto l = normalize(light->frame.o - intersection.pos);

    auto v = normalize(intersection.pos - ray.e);

    auto h = normalize(l+v); //bisector

    auto n = intersection.mat->n;
    auto normal = intersection.norm;

    // compute the material response (brdf*cos)
    auto brdf = intersection.mat->ks*pow(max(0.0f, dot(normal,h)), intersection.mat->n);

    // check for shadows and accumulate if needed
    auto shadowRay = ray3f(intersection.pos, l, 0.001f, length(light->frame.o - intersection.pos));

    float visibleTerm;
    auto shadowIntersect = intersect_surfaces(scene, shadowRay);
    if (shadowIntersect.hit) {
        visibleTerm = 0;
    }
    else {
        visibleTerm = 1;
    }

    //Accumulate
    c += lightRadiance*visibleTerm*(intersection.mat->kd + brdf)*abs(dot(normal, l));
}

// if the material has reflections
    // create the reflection ray
    // accumulate the reflected light (recursive call) scaled by the material reflection

// return the accumulated color∫
return c;
}

Now my questions are:

  1. Where the equation is wrong?
  2. From the last edit I also added shadows, by creating a ray (the last two parameters of the ray constructor are tmin and tmax) and check if it hit something. If yes I set the visibileTerm to zero (I also updated the screenshots). I'm not sure if this is right.
  3. How can I go further to add also reflection and refraction? I know I have to call recursively the function, but first what I have to compute and in which way? I tried with this code for reflection within the for-loop of lights, but it doesn't work, in some test infinite recursion:

    ray3f reflectionRay = ray3f(intersection.pos, -l + 2 * dot(l, normal)*normal);
    c += intersection.mat->kr*raytrace_ray(scene, reflectionRay);
    
  4. From the second test of the picture, there is also a problem in the intersection, but I don't know where. I tried also to change the normal calculation with the inverse in this way:

    auto normal = transform_normal_inverse(scene->camera->frame, surface->frame.z);
    

    But I get only another inclination of the plane without solve the second test as well. This is my code of the intersection (I used only Quads and Spheres):

    intersection3f intersect_surfaces(Scene* scene, ray3f ray) {
      auto intersection = intersection3f();
    
      float currentDistance = INFINITY;
      float t;
    
     // foreach surface
    for (Surface* surface : scene->surfaces) {
    // if it is a quad
    if (surface->isquad) {
    
        /// compute ray intersection (and ray parameter), continue if not hit
        auto normal = transform_normal(scene->camera->frame, surface->frame.z);
    
        if (dot(ray.d, normal) == 0) {
            continue;
        }
        t = dot(surface->frame.o - ray.e, normal)/dot(ray.d,normal);
    
        // check if computed param is within ray.tmin and ray.tmax
        if (t < ray.tmin or t > ray.tmax) continue;
    
        // check if this is the closest intersection, continue if not
        auto p = ray.eval(t);//Intersection point
    
        if (dist(ray.e, p) >= currentDistance) {
            continue;
        }
    
        currentDistance = dist(ray.e, p);
    
        // if hit, set intersection record values
        intersection.ray_t = t;
        intersection.pos = p;
        intersection.norm = normalize(ray.e - p);
        intersection.mat = surface->mat;
        intersection.hit = true;
    }
    // if it is a sphere
    else {
        // compute ray intersection (and ray parameter), continue if not hit
        auto a = lengthSqr(ray.d);
        auto b = 2 * dot(ray.d, ray.e - surface->frame.o);
        auto c = lengthSqr(ray.e - surface->frame.o) - surface->radius*surface->radius;
        auto det = b*b - 4 * a*c;
    
        if (det < 0) {
            continue;
        }
    
        float t1 = (-b - sqrt(det)) / (2 * a);
        float t2 = (-b + sqrt(det)) / (2 * a);
    
        // check if computed param is within ray.tmin and ray.tmax
        if (t1 >= ray.tmin && t1 <= ray.tmax) t = t1;
        else if (t2 >= ray.tmin && t2 <= ray.tmax) t = t2;
        else continue;
    
        auto p = ray.eval(t); //Intersection point
    
        // check if this is the closest intersection, continue if not
        if (dist(ray.e, p) > currentDistance) {
            continue;
        }
    
        currentDistance = dist(ray.e, p);
    
        // if hit, set intersection record values
        intersection.ray_t = t;
        intersection.pos = p;
        intersection.norm = normalize(ray.e - p);
        intersection.mat = surface->mat;
        intersection.hit = true;
    
        intersection.ray_t = 2;
    
    
       }
    }
    return intersection;
    }
    

Thanks in advance.

Upvotes: 4

Views: 5354

Answers (1)

Koto
Koto

Reputation: 511

About the phong shading model: It is a model to describe the scattering of light on a surface. Therfore each surface has different properties, which are represented by the ambient, diffuse and specular coefficients. Depending on what surface you have, they vary(eg. Glass, Wood,..)

The ambient colour of a surface is like its minimum colour the object has, even if it is shadowed by another object. Basically each object has a colour represented either by float (0 - 1) or int (0 - 255). To apply the first part of the phong model you assume the object is shadowed, since the light will not reach every inch of the object. To apply ambient shading you just multiply the ambient coefficient(it is a surface property, normally between 0 and 1) with the object's colour.

Now we need to check if the object is shadowed. If the object is not shadowed(and only then) you need to apply the other two parts of the phong model, since they are only relevant when the object is hit by light.

The diffuse coefficient is a measure of how much the light is scattered.The specular component is a measure of shininess. There are some aproximation formulas to efficiently calculate both of these coefficients.

I added an extract from my code to guide you. I cut a lot of code that were calculations of intersections and reflecting rays. I left the comments to make the steps clear.

BY THE LOOKS OF YOUR SCREENSHOTS IT SEEMS THAT YOU FORGOT TO ADD THE SPECULAR COMPONENTS.

If you haven't heard of http://scratchapixel.com, then you should definately check it out. It was a great help to me when I wrote my raytracer. Note: I am not an expert, but I myself wrote a raytracer in C++ in my free time, so I am trying to share my experience since I experienced the same issues as posted in your screenshots.

    double ka = 0.1; //ambient coefficient
    double kd; //diffuse coefficient
    double ks; //specular coefficient

    /****** First handle ambient shading ******/
    Colour ambient = ka * objectColor; //ambient component

    Colour diffuse, specular; // automatically set to 0
    double brightness;
    localColour = ambient; //localColour is the current colour of the object
    /************ Next check wether 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++)
    {//iterate over all lights
        if(dynamic_cast<Light *>(objects[i])) //if object is a light
        {
            //for each light
            //create a Ray to light
            //its origin is the intersection point
            //its direction is the position of the light - intersection
            //check for an intersection with a light
            //if there is no intersection then we don't need to apply the specular and the diffuse components
            if(t_light < 0) //no intersect, which is quite impossible
                continue;

            //then we check if that Ray intersects one object that 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

            //we know if inersection is shadowed or not
            if(!shadowed)// if obj is not shadowed
            {
//------->      //The important part. Adding the diffuse and the specular components.
                rRefl = objects[index_nearObj]->calcReflectingRay(rShadow, intersect, normal); //reflected ray from light source, for ks
                kd = maximum(0.0, (normal|rShadow.getDirection())); // calculate the diffuse coefficient
                //the max of 0 and the cosine of the angle between the direction of the light source and 
                //the surface normal at the intersection point
                ks = pow(maximum(0.0, (r.getDirection()|rRefl.getDirection())), objects[index_nearObj]->getShiny()); //calc the specular component
                //the cosine of the angle between the incoming ray and the reflected ray to the power of a material property
                diffuse = kd * objectColor; //diffuse colour
                specular = ks * objects[i]->getColor(); //specular colour
                brightness = 1 /(1 + t_light * DISTANCE_DEPENDENCY_LIGHT); 
                //Determines the brightness by how far the light source is apart from the object
                // 1/(1 + distance to light * parameter)... change the parameter to have a stronger or weaker distance dependency                
                localColour += brightness * (diffuse + specular); //ADD the diffuse and the specular components to the object
            }
        }
    }

As for reflection and refraction:

Since you need to call the function recursively, you need to define a maximum trace depth to tell the computer to stop reflecting. Otherwise you may end up in an endless loop. Again a code snippet from my raytracer(also modified into more readable pseudocode:

    Ray rRefl, rRefr; //reflected and refracted Ray
    Colour reflectionColour = finalColour, refractionColour = finalColour; //reflected and refrated objects colour;
    //initialize them to the background colour
    //if there is no intersection of the reflected ray,
    //then the backgroundcolour should be returned
    double reflectance = 0, transmittance = 0;
    //check if obj is reflective, and calc reflection
    if(object->isReflective && depth < MAX_TRACE_DEPTH)
    {
        //handle reflection
        rRefl = object->calcReflectingRay(r, intersect, normal);
        if(REFL_COLOUR_ADD)//if the reflected colour is added to the object's colour
        {
            reflectionColour = raytrace(rRefl, depth + 1);//trace the reflected ray
            reflectance = 1;
        }
        else   objectColor = raytrace(rRefl, depth + 1);//otherwise set the object's clolour to the reflected colour(eg: Glass)
    
    }
    
    if(object->isRefractive() && depth < MAX_TRACE_DEPTH)
    {
        //handle transmission
        rRefr = object->calcRefractingRay(r, intersect, normal, reflectance, transmittance);
        if(rRefr.getDirection() != NullVector) // ignore total inner refl
            refractionColour = raytrace(rRefr, depth + 1);//trace the refracted ray
    }
    
    //then calculate light ,shading and colour with the phong illumination model
    
    //in the end add the reflected and refracted colours
    
    finalColour = phongColour + transmittance * refractionColour + reflectance * reflectionColour;//Add phong, refraction and reflection
    //if transmittance is 0 the object will not let light pass through, if reflectance is 0 the object will not reflect
    return finalColour;

Sorry for the short answer on this one, but it is getting late and I am becoming lazy. When I have more time I will check in again. I hope I could help a little.

Upvotes: 3

Related Questions