Search code examples
c++raytracing

RayTracing wrong color compute in C++


I'm implementing a rayTracing framework, but I'm having some error enter image description here

The image should look like this, but in my case looks like: enter image description here

As you can see, spheres have some black dots and not are smooth. Any idea where this error could come from?

Compute color, here I create all the rebounds of the rays. I applied the epsilon to the light ray:

vec3 ComputeColor(const Ray& ray, const world& scene, int depth, float pH, float pW) {
    if (depth > scene.maxDepth)
        return vec3(0.0f);

    const Object* hitObject;
    vec3 hitPoint;
    if (!Intersection(ray, scene, hitObject, &hitPoint))
        return vec3(0.0f);

    vec3 color = hitObject->materials.ambient + hitObject->materials.emission;

    for (const PointLightSource& light : scene.lights) {
        Ray lightRay;
        if (light.type == PointLightSource::point) {
            lightRay = Ray(light.pos, glm::normalize(hitPoint - light.pos));
        }
        else {
            lightRay = Ray(light.pos,glm::normalize(- lightRay.direction));
        }
        const float epsilon = 1e-4;
        lightRay.o = lightRay.o * epsilon;
        const Object* obj;
        vec3 lightHit;
        if (!Intersection(lightRay, scene, obj, &lightHit) || IsSameVector(hitPoint, lightHit)) {
            color += Phong(light, hitObject, ray, hitPoint, scene.attenuation);
        }
    }

    if (glm::any(glm::greaterThan(hitObject->materials.specular, vec3(0.0f)))) {
        vec3 unit_normal = glm::normalize(hitObject->InterpolatePointNormal(hitPoint));
        vec3 reflect_dir = ray.direction - (unit_normal * (2.0f * glm::dot(ray.direction, unit_normal)));
        Ray reflect_ray(hitPoint, reflect_dir);

        vec3 temp_color = temp_color+ComputeColor(reflect_ray, scene, depth + 1, pH, pW);
        color += hitObject->materials.specular * temp_color;
    }

    return color;
}

Phong light, this is my function where I compute the light:

vec3 Phong(const PointLightSource& light, const Object* hitObject, const Ray& ray, const vec3& hitPoint, const float* attenuation) {
    vec3 lightDirection;
    if (light.type == PointLightSource::point) {
        lightDirection = glm::normalize(light.pos - hitPoint);
    }
    else {
        lightDirection = glm::normalize(light.pos);
    }

    vec3 normal = glm::normalize(hitObject->InterpolatePointNormal(hitPoint));
    // Diffuse light
    const Materials& materials = hitObject->materials;
    float nDotL = max(glm::dot(normal, lightDirection), 0.0f);
    vec3 diffuse = materials.diffuse * light.color * nDotL;

    // Specular
    vec3 viewDirection = glm::normalize(-ray.direction);
    vec3 halfvec = glm::normalize(lightDirection + viewDirection);
    float nDotH = max(glm::dot(normal, halfvec), 0.0f);
    vec3 specular = materials.specular * light.color * pow(nDotH, materials.shininess);

    vec3 result = diffuse + specular;
    if (light.type == PointLightSource::point) {
        float r = glm::length(light.pos - hitPoint);
        float attenuationFactor = 1.0f / (attenuation[2] * r * r + attenuation[1] * r + attenuation[0]);
        result *= attenuationFactor;
    }
    return result;
}

Intersection, I use this function to compute the intersection with all kind of objects, in this case spheres and triangles:

bool Intersection(const Ray& ray, const world& scene, const Object*& hitObject, vec3* hitPoint) {
    double minDistance = INFINITY;
    hitObject = nullptr;

    for (const Object* obj : scene.objects) {
        Ray rayObject = Transform(ray, obj);
        float dist;
        if (obj->Intersect(rayObject, &dist)) {
            vec3 localPoint = rayObject.o + rayObject.direction * dist;
            vec4 localPointHom(localPoint, 1.0);
            localPointHom = localPointHom * obj->transform;
            vec3 hitWorld = vec3(localPointHom.x / localPointHom.w, localPointHom.y / localPointHom.w, localPointHom.z / localPointHom.w);

            dist = glm::dot(hitWorld - ray.o, ray.direction);
            if (dist > 1e-4) {
                if (dist < minDistance) {
                    minDistance = dist;
                    hitObject = obj;
                    *hitPoint = hitWorld;
                }
            }

        
            
        }
    }

    return (hitObject != nullptr);
}

EDIT: When moving the light ray I tested this:

lightRay.o = lightRay.o + (epsilon * lightRay.direction);//(this one does note solve "cancer issue" but do not destroy other scenes )
        lightRay.o = lightRay.o *(epsilon);//This one solves the issue but others scenes are not correct.

Solution

  • This is known as "cancer", which is an artifact caused by shooting shadow rays off your surface, but having the ray intersect with that surface again. The result is you determine something is blocking the light, without knowing the object itself did that at the source of the shadow ray. It happens as a natural result of floating point precision error.

    One of the simplest ways to solve it is to introduce an "epsilon" value and do not count intersections with a distance below that threshold. This allows self-intersection of more complex geometry but is a little grimey.

    More generally, you may decide to exclude the object that generated this shadow ray from being considered in the light hit-test. In a more generalized 3D scene where everything is triangles, you can exclude that triangle. But in heavily tessellated scenes, you may run into the issue again as you hit neighboring triangles.

    Another approach is to ignore the intersection if the surface normal points in the opposite direction to the shadow ray. As in, only count the intersection if the dot product of the two is negative. But you should be careful with that one if you have single-sided geometry in the scene that can actually block light.

    In practice, a combination of the above measures will typically solve this problem, but you can get by with selecting only one.

    Note that the same issue can occur with reflected rays, not just shadow rays. So you really need to build this logic carefully into your core ray-tracing algorithm.