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?
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);
}
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$