Search code examples
c#c++raytracing

Raytracer light reflection bug


I'm trying to port the smallpt: Global Illumination in 99 lines of C++ to C# and I'm getting this weird bug when the light reflects of a diffuse surface. Does anyone have an idea where the problem might be coming from?

This what I'm getting with 40 samples

This is what it's supposed to look like

This my code for diffuse surfaces:

if(sphere.Reflection == Sphere.ReflectionType.DIFFUSE)
        {
            double angleRand = random.NextDouble(seed) *2f*Math.PI;
            double distanceRand = random.NextDouble(seed);
            double distanceRandSqtr = Math.Sqrt(distanceRand);

            Vector3 w = surfaceNormal;
            Vector3 u = Vector3.Normalize(Vector3.Cross(Math.Abs(w.X) > .1 ? new Vector3(0f, 1f, 0f) : new Vector3(1f, 0f, 0f), w));
            Vector3 v = Vector3.Cross(w, u);

            Vector3 ref1 = Vector3.Multiply(u, (float)Math.Cos(angleRand));
            ref1 = Vector3.Multiply(ref1, (float)distanceRandSqtr);
            Vector3 ref2 = Vector3.Multiply(v, (float)Math.Sin(angleRand));
            ref2 = Vector3.Multiply(ref2, (float)distanceRandSqtr);
            Vector3 ref3 = Vector3.Multiply(w, (float)Math.Sqrt(1 - distanceRand));
            Vector3 ref4 = Vector3.Add(ref1, ref2);
            ref4 = Vector3.Add(ref4, ref3);

            Vector3 reflectionRayRand = Vector3.Normalize(ref4);

            Vector3 nextRadiance = ComputeRadiance(new Ray(intersectionPoint, reflectionRayRand), depth, seed);

            Vector3 result = Vector3.Multiply(color, nextRadiance);
            result = Vector3.Add(sphere.Emission, result);

            if (float.IsNaN(result.X) || float.IsNaN(result.Y) || float.IsNaN(result.Z))
            {
                throw new Exception();
            } 

            return result;
        }

And this is the original:

if (obj.refl == DIFF){                  // Ideal DIFFUSE reflection
double r1=2*M_PI*erand48(Xi), r2=erand48(Xi), r2s=sqrt(r2);
Vec w=nl, u=((fabs(w.x)>.1?Vec(0,1):Vec(1))%w).norm(), v=w%u;
Vec d = (u*cos(r1)*r2s + v*sin(r1)*r2s + w*sqrt(1-r2)).norm();
return obj.e + f.mult(radiance(Ray(x,d),depth,Xi));}

Solution

  • The effect you are getting reminds me a bit of shadow acne. Usually this is a more circular pattern, which is why I'm not sure. Shadow acne happens due to float inaccuracies. When you scatter or reflect, you make a new ray from a certain origin on a surface in a direction. The origin will sometimes shift under/above the surface depending on the float inaccuracy. That's why you often offset the ray with a small number EPSILON in the direction of the normal. So your new origin becomes: intersection + intersection.normal * EPSILON. You should test different values for epsilon, but usually it's around 0.01 to 0.02 or something around that. In your code you are still using intersectionpoint, which I assume does not have the offset. I'm not sure if that will work, since your result looks a bit different from the shadow acne I'm used to, but it's worth a try right?