Reputation: 41
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:
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);
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
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