1
$\begingroup$

I've been working on a new ray tracer recently, but after adding reflections I'm seeing these strange small highlights on my models, particularly on the ear of the silver rabbit below.

I've read that these can be caused by my reflection rays re-colliding with the object they're reflection from, but I believe I'm properly accounting for this by slightly translating the outgoing rays away from the intersect point. They also seem to consistently occur on the same parts of the model (the ears, and "creases") regardless of the scene. I am taking 8 random samples per pixel, which seems to help a bit, but not entirely.

Any thoughts on what could be causing this? is it possible that I'm simply not taking enough samples or structuring them well enough?

Rendering

EDIT:

I've added the code for my recursive coloring/reflectance function and my shadow function below, sorry if it's messy

public Color getIntersectColor(Scene theScene, Vector3D theNormal, Vector3D theIntersectPoint, Vector3D theRay, int aDepth)
{
    // Initialize to ambient colors
    double aRed = myKa * myDiffuseColor.getDecimalRed();
    double aBlue = myKa * myDiffuseColor.getDecimalBlue();
    double aGreen = myKa * myDiffuseColor.getDecimalGreen();

    final Color aColor = new Color(aRed, aGreen, aBlue);

    theScene.getLights().parallelStream().forEach(theLight ->
    {
        Vector3D L = theLight.getL(theIntersectPoint);
        Vector3D V = theScene.getCamera().getPosition().subtract(theIntersectPoint).normalize();
        Vector3D H = L.add(V).scale((1.0 / (L.add(V).getLength()))).normalize();

        Color aLC = theLight.getColor();
        Double aI = Math.abs(theNormal.dot(L));
        Double aR = Math.abs(theNormal.dot(H));

       double aLightDistance = theLight.getDistance(theIntersectPoint);

       double aShadow = 1.0;

       if (hasShadows)
       {
           aShadow = getShadow(theIntersectPoint, L, aLightDistance, theScene);
       }

        double aR = aLC.getDecimalRed() * aShadow *
                                  ((myKd * myDiffuseColor.getDecimalRed() * aI) +
                                      (myKs * mySpecularColor.getDecimalRed() * (Math.pow(aR, myShininess)))
                                  );

        double aG = aLC.getDecimalGreen() * aShadow *
                                    ((myKd * myDiffuseColor.getDecimalGreen() * aI) +
                                        (myKs * mySpecularColor.getDecimalGreen() * (Math.pow(aR, myShininess)))
                                    );

        double aB = aLC.getDecimalBlue() * aShadow *
                                    ((myKd * myDiffuseColor.getDecimalBlue() * aI) +
                                        (myKs * mySpecularColor.getDecimalBlue() * (Math.pow(aR, myShininess)))
                                    );

        aColor.setRed(aColor.getDecimalRed() + aR);
        aColor.setBlue(aColor.getDecimalBlue() + aB);
        aColor.setGreen(aColor.getDecimalGreen() + aG);

        if (isReflective && aDepth > 0)
        {
            double aReflectionAngle = theRay.dot(theNormal);

            Vector3D aReflectionVector = theRay.subtract(theNormal.scale(2.0 * aReflectionAngle)).normalize();

            double F0 = ((myIndexOfRefraction - 1)  /(1 + myIndexOfRefraction)) *
                    ((myIndexOfRefraction - 1) / (1 + myIndexOfRefraction));

            double FR = F0 + (1 - F0) * ((1 - Math.cos(aReflectionAngle)) * (1 - Math.cos(aReflectionAngle)) *
                                         (1 - Math.cos(aReflectionAngle)) * (1 - Math.cos(aReflectionAngle))
                                            * (1 - Math.cos(aReflectionAngle)));

            Vector3D anAdjustedIntersect = theIntersectPoint.add(aReflectionVector.scale(.00001));

            Color aRefColor = theScene.getKDFilteredObjects(aReflectionVector, anAdjustedIntersect)
                .parallelStream()
                .unordered()
                .map(thePObject -> new Pair<>(thePObject, thePObject.intersects(aReflectionVector, anAdjustedIntersect)))
                .filter(thePair -> thePair.getValue() != null)
                .reduce((thePair, theOtherPair) -> thePair.getValue() >= theOtherPair.getValue() ? theOtherPair : thePair)
                .map(theIntersect ->
                {
                    Vector3D aIntersectPoint = theIntersectPoint.add(aReflectionVector.scale(theIntersect.getValue()));
                    return theIntersect.getKey().getMaterial().getIntersectColor(theScene, theIntersect.getKey().getIntersectNormal(aIntersectPoint), aIntersectPoint, aReflectionVector, aDepth - 1);
                })
                .orElse(theScene.getBackgroundColor());

            aColor.setRed(aColor.getDecimalRed() + (FR * aRefColor.getDecimalRed()));
            aColor.setBlue(aColor.getDecimalBlue() + (FR * aRefColor.getDecimalBlue()));
            aColor.setGreen(aColor.getDecimalGreen() + (FR * aRefColor.getDecimalGreen()));
        }
    });

    return aColor;
}

// TODO: factor in opacity
public double getShadow(Vector3D theOrigin, Vector3D theLightDirection, double theLightDistance, Scene theScene)
{
    // Avoid self-collision
    Vector3D aAdjustedOrigin = theOrigin.add(theLightDirection.scale(.00001));

    return theScene.getKDFilteredObjects(theLightDirection, aAdjustedOrigin)
            .parallelStream()
            .unordered()
            .map(theObject -> theObject.intersects(theLightDirection, aAdjustedOrigin))
            .filter(Objects::nonNull)
            .filter(theT -> theT > 0 && theT < theLightDistance)
            .findAny()
            .map(theT -> 0.0)
            .orElse(1.0);
}
$\endgroup$
4
  • $\begingroup$ Are your triangles one sided? Your artefacts looks a lot like rays starting inside of a 1 sided triangle mesh, which usually occur were were you get AO, because of the V channels and your epsilon method it is easier to penetrate the surface and start a ray inside the mesh. If you are using 1 sided triangles then you can make them 2 sided so any rays starting from the inside get trapped in there and contribute something useful to AO :) $\endgroup$
    – PaulHK
    Commented Aug 23, 2018 at 7:09
  • $\begingroup$ I'm properly accounting for this by slightly translating the outgoing rays How do you offset your ray origins? Do you use an absolute or relative offset along the ray? $\endgroup$
    – Matthias
    Commented Aug 23, 2018 at 14:48
  • $\begingroup$ As there is more than one possible cause for these artefacts, it would help answerers to be able to see your code. Are you happy to include it here? $\endgroup$ Commented Aug 23, 2018 at 19:49
  • $\begingroup$ @Matthias added code, i'm current translating like this Vector3D anAdjustedIntersect = theIntersectPoint.add(aReflectionVector.scale(.00001)); --- @trichoplax I've added my code to the original post, sorry if it's a little hard to follow, it's kind of a first pass. --- @PaulHK I thought that I was, I will double check my intersection code to make sure $\endgroup$
    – Cluny
    Commented Aug 23, 2018 at 22:57

1 Answer 1

1
$\begingroup$

So this does in fact seem to be a self-collision issue. I was able to determine this by giving each triangle/sphere an ID and filtering that ID out when checking collisions.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.