Pavel Pavlov
Pavel Pavlov

Reputation: 23

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

Upvotes: 1

Views: 118

Answers (2)

Pavel Pavlov
Pavel Pavlov

Reputation: 23

I gave up on Vector3 and used a custom class that works with doubles and not floats. And that fixed the problem. Thanks @AmberElferink for the help!

class Vec
{
    public double X { get; set; }
    public double Y { get; set; }

    public double Z { get; set; }

    public Vec(double x=0, double y=0, double z=0)
    {
        X = x;
        Y = y;
        Z = z;
    }

    public static Vec Normalize(Vec vec) { return vec.GetNormal(); }

    public static Vec Cross(Vec right, Vec left) { return right.CrossWith(left); }

    public static double Dot(Vec right, Vec left) { return right.DotWith(left); }

    public static Vec Multiply(Vec right, Vec left) { return right * left; }

    public static Vec Multiply(Vec right, double left) { return right * left; }

    public static Vec Add(Vec right, Vec left) { return right + left; }

    public static Vec Subtract(Vec right, Vec left) { return right - left; }

    public static Vec operator+(Vec right, Vec left) { return new Vec(right.X + left.X, right.Y + left.Y, right.Z + left.Z); }
    public static Vec operator-(Vec right, Vec left) { return new Vec(right.X - left.X, right.Y - left.Y, right.Z - left.Z); }

    public static Vec operator *(Vec right, Vec left) { return new Vec(right.X * left.X, right.Y * left.Y, right.Z * left.Z); }

    public static Vec operator *(Vec right, double left) { return new Vec(right.X * left, right.Y * left, right.Z * left); }

    public Vec GetNormal() { return this * (1 / Math.Sqrt(X * X + Y * Y + Z * Z)); }

    public double DotWith(Vec b) { return X * b.X + Y * b.Y + Z * b.Z; }

    public Vec CrossWith(Vec b) { return new Vec(Y * b.Z - Z * b.Y, Z * b.X - X * b.Z, X * b.Y - Y * b.X); }
}

Upvotes: 1

Amber Elferink
Amber Elferink

Reputation: 152

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?

Upvotes: 0

Related Questions