2
votes

EDIT: I found out that all the pixels were upside down because of the difference between screen and world coordinates, so that is no longer a problem.
EDIT: After following a suggestion from @TheVee (using absolute values), my image got much better, but I'm still seeing issues with color.

I having a little trouble with ray-tracing triangles. This is a follow-up to my previous question about the same topic. The answers to that question made me realize that I needed to take a different approach. The new approach I took worked much better, but I'm seeing a couple of issues with my raytracer now:

  1. There is one triangle that never renders in color (it is always black, even though it's color is supposed to be yellow).

Here is what I am expecting to see:
Expected

But here is what I am actually seeing:
Actual

  1. Addressing debugging the first problem, even if I remove all other objects (including the blue triangle), the yellow triangle is always rendered black, so I don't believe that it is an issues with my shadow rays that I am sending out. I suspect that it has to do with the angle that the triangle/plane is at relative to the camera.

Here is my process for ray-tracing triangles which is based off of the process in this website.

  1. Determine if the ray intersects the plane.
  2. If it does, determine if the ray intersects inside of the triangle (using parametric coordinates).

Here is the code for determining if the ray hits the plane:

private Vector getPlaneIntersectionVector(Ray ray)
{
    double epsilon = 0.00000001;
    Vector w0 = ray.getOrigin().subtract(getB());
    double numerator = -(getPlaneNormal().dotProduct(w0));
    double denominator = getPlaneNormal().dotProduct(ray.getDirection());
    //ray is parallel to triangle plane
    if (Math.abs(denominator) < epsilon)
    {
        //ray lies in triangle plane
        if (numerator == 0)
        {
            return null;
        }
        //ray is disjoint from plane
        else
        {
            return null;
        }
    }
    double intersectionDistance = numerator / denominator;

    //intersectionDistance < 0 means the "intersection" is behind the ray (pointing away from plane), so not a real intersection
    return (intersectionDistance >= 0) ? ray.getLocationWithMagnitude(intersectionDistance) : null;
}

And once I have determined that the ray intersects the plane, here is the code to determine if the ray is inside the triangle:

private boolean isIntersectionVectorInsideTriangle(Vector planeIntersectionVector)
{
    //Get edges of triangle
    Vector u = getU(); 
    Vector v = getV();

    //Pre-compute unique five dot-products
    double uu = u.dotProduct(u);
    double uv = u.dotProduct(v);
    double vv = v.dotProduct(v);
    Vector w = planeIntersectionVector.subtract(getB());
    double wu = w.dotProduct(u);
    double wv = w.dotProduct(v);
    double denominator = (uv * uv) - (uu * vv);

    //get and test parametric coordinates
    double s = ((uv * wv) - (vv * wu)) / denominator;
    if (s < 0 || s > 1)
    {
        return false;
    }
    double t = ((uv * wu) - (uu * wv)) / denominator;
    if (t < 0 || (s + t) > 1)
    {
        return false;
    }

    return true;
}

Is think that I am having some issue with my coloring. I think that it has to do with the normals of the various triangles. Here is the equation I am considering when I am building my lighting model for spheres and triangles:
Lighting model

Now, here is the code that does this:

public Color calculateIlluminationModel(Vector normal, boolean isInShadow, Scene scene, Ray ray, Vector intersectionPoint)
{
    //c = cr * ca + cr * cl * max(0, n \dot l)) + cl * cp * max(0, e \dot r)^p
    Vector lightSourceColor = getColorVector(scene.getLightColor()); //cl
    Vector diffuseReflectanceColor = getColorVector(getMaterialColor()); //cr
    Vector ambientColor = getColorVector(scene.getAmbientLightColor()); //ca
    Vector specularHighlightColor = getColorVector(getSpecularHighlight()); //cp
    Vector directionToLight = scene.getDirectionToLight().normalize(); //l
    double angleBetweenLightAndNormal = directionToLight.dotProduct(normal);
    Vector reflectionVector = normal.multiply(2).multiply(angleBetweenLightAndNormal).subtract(directionToLight).normalize(); //r

    double visibilityTerm = isInShadow ? 0 : 1;
    Vector ambientTerm = diffuseReflectanceColor.multiply(ambientColor);

    double lambertianComponent = Math.max(0, angleBetweenLightAndNormal);
    Vector diffuseTerm = diffuseReflectanceColor.multiply(lightSourceColor).multiply(lambertianComponent).multiply(visibilityTerm);

    double angleBetweenEyeAndReflection = scene.getLookFrom().dotProduct(reflectionVector);
    angleBetweenEyeAndReflection = Math.max(0, angleBetweenEyeAndReflection);
    double phongComponent = Math.pow(angleBetweenEyeAndReflection, getPhongConstant());
    Vector phongTerm = lightSourceColor.multiply(specularHighlightColor).multiply(phongComponent).multiply(visibilityTerm);

    return getVectorColor(ambientTerm.add(diffuseTerm).add(phongTerm));
}

I am seeing that the dot product between the normal and the light source is -1 for the yellow triangle, and about -.707 for the blue triangle, so I'm not sure if the normal being the wrong way is the problem. Regardless, when I added made sure the angle between the light and the normal was positive (Math.abs(directionToLight.dotProduct(normal));), it caused the opposite problem:
Absolute value of dot products

I suspect that it will be a small typo/bug, but I need another pair of eyes to spot what I couldn't.

Note: My triangles have vertices(a,b,c), and the edges (u,v) are computed using a-b and c-b respectively (also, those are used for calculating the plane/triangle normal). A Vector is made up of an (x,y,z) point, and a Ray is made up of a origin Vector and a normalized direction Vector.

Here is how I am calculating normals for all triangles:

private Vector getPlaneNormal()
{
    Vector v1 = getU();
    Vector v2 = getV();
    return v1.crossProduct(v2).normalize();
}

Please let me know if I left out anything that you think is important for solving these issues.

EDIT: After help from @TheVee, this is what I have at then end: "Working" image

There are still problems with z-buffering, And with phong highlights with the triangles, but the problem I was trying to solve here was fixed.

1
Try taking an absolute value in if (denominator < epsilon). The symptoms look like a typical issue with front- and back- facing polygons. Remember that dot products may very easily be negative and a negative number is surely smaller than your epsilon even though "large".The Vee
@TheVee That helped the image look better, but only with shadows. I've edited the question to include your suggestion. Any other ideas?Cache Staheli
Well, you're getting the colour wrong but none of the code you exposed here mentions colour calculation. It will be the same, trying to get a negative shade of yellow somewhere.The Vee
Maybe the light components just add to a too high sum after the fix. (Note that before you had been getting a flat blue triangle with no shading whatsoever, which is probably just the diffuse term, but the scene you're trying to get to has a clear Phong over that.) Try rerendering with ambient only / diffuse only / specular only and you may identify the culprit.The Vee
I've been writing one anyway.The Vee

1 Answers

1
votes

It is an usual problem in ray tracing of scenes including planar objects that we hit them from a wrong side. The formulas containing the dot product are presented with an inherent assumption that light is incident at the object from a direction to which the outer-facing normal is pointing. This can be true only for half the possible orientations of your triangle and you've been in bad luck to orient it with its normal facing away from the light.

Technically speaking, in a physical world your triangle would not have zero volume. It's composed of some layer of material which is just thin. On either side it has a proper normal that points outside. Assigning a single normal is a simplification that's fair to take because the two only differ in sign.

However, if we made a simplification we need to account for it. Having what technically is an inwards facing normal in our formulas gives negative dot products, which case they are not made for. It's like light was coming from the inside of the object or that it hit a surface could not possibly be in its way. That's why they give an erroneous result. The negative value will subtract light from other sources, and depending on the magnitude and implementation may result in darkening, full black, or numerical underflow.

But because we know the correct normal is either what we're using or its negative, we can simply fix the cases at once by taking a preventive absolute value where a positive dot product is implicitly assumed (in your code, that's angleBetweenLightAndNormal). Some libraries like OpenGL do that for you, and on top use the additional information (the sign) to choose between two different materials (front and back) you may provide if desired. Alternatively, they can be set to not draw the back faces for solid object at all because they will be overdrawn by front faces in solid objects anyway (known as face culling), saving about half of the numerical work.