Esempio n. 1
0
        public override EmitterSample SampleRay(Vector2 primaryPos, Vector2 primaryDir)
        {
            var posSample = SampleArea(primaryPos);

            // Transform primary to cosine hemisphere (z is up)
            // We add one to the exponent, to importance sample the cosine term from the jacobian also
            var local = SampleWarp.ToCosineLobe(exponent + 1, primaryDir);

            // Transform to world space direction
            var     normal = posSample.Point.ShadingNormal;
            Vector3 tangent, binormal;

            ShadingSpace.ComputeBasisVectors(normal, out tangent, out binormal);
            Vector3 dir = local.Direction.Z * normal
                          + local.Direction.X * tangent
                          + local.Direction.Y * binormal;

            float cosine = local.Direction.Z;
            var   weight = radiance * MathF.Pow(cosine, exponent + 1) * normalizationFactor;

            return(new EmitterSample {
                Point = posSample.Point,
                Direction = dir,
                Pdf = local.Pdf * posSample.Pdf,
                Weight = weight / posSample.Pdf / local.Pdf
            });
        }
Esempio n. 2
0
        public override (Ray, RgbColor, float) SampleRay(Vector2 primaryPos, Vector2 primaryDir)
        {
            // Sample a direction from the scene to the background
            var dirSample = SampleDirection(primaryDir);

            // Sample a point on the unit disc
            var unitDiscPoint = SampleWarp.ToConcentricDisc(primaryPos);

            // And transform it to the scene spanning disc orthogonal to the selected direction
            Vector3 tangent, binormal;

            ShadingSpace.ComputeBasisVectors(dirSample.Direction, out tangent, out binormal);
            var pos = SceneCenter + SceneRadius * (dirSample.Direction            // offset outside of the scene
                                                   + tangent * unitDiscPoint.X    // remap unit disc x coordinate
                                                   + binormal * unitDiscPoint.Y); // remap unit disc y coordinate

            // Compute the pdf: uniform sampling of a disc with radius "SceneRadius"
            float discJacobian = SampleWarp.ToConcentricDiscJacobian();
            float posPdf       = discJacobian / (SceneRadius * SceneRadius);

            // Compute the final result
            var ray = new Ray {
                Origin = pos, Direction = -dirSample.Direction, MinDistance = 0
            };
            var weight = dirSample.Weight / posPdf;
            var pdf    = posPdf * dirSample.Pdf;

            return(ray, weight, pdf);
        }
Esempio n. 3
0
        float ComputeOneDir(Vector3 outDir, Vector3 inDir)
        {
            // Compute the half vector
            float   eta = ShadingSpace.CosTheta(outDir) > 0 ? (insideIOR / outsideIOR) : (outsideIOR / insideIOR);
            Vector3 wh  = outDir + inDir * eta;

            if (wh == Vector3.Zero)
            {
                return(0);                    // Prevent NaN if outDir and inDir exactly align
            }
            wh = Vector3.Normalize(wh);

            if (Vector3.Dot(outDir, wh) * Vector3.Dot(inDir, wh) > 0)
            {
                return(0);
            }

            // Compute change of variables _dwh\_dinDir_ for microfacet transmission
            float sqrtDenom = Vector3.Dot(outDir, wh) + eta * Vector3.Dot(inDir, wh);

            if (sqrtDenom == 0)
            {
                return(0);                // Prevent NaN in corner case
            }
            float dwh_dinDir = Math.Abs((eta * eta * Vector3.Dot(inDir, wh)) / (sqrtDenom * sqrtDenom));

            float result = distribution.Pdf(outDir, wh) * dwh_dinDir;

            Debug.Assert(float.IsFinite(result));
            return(result);
        }
Esempio n. 4
0
        /// <summary>
        /// Creates a cone oriented such that it connects two points like an arrow tip
        /// </summary>
        /// <param name="baseCenter">The point in the center of the cone's base</param>
        /// <param name="tip">The position of the tip in world space</param>
        /// <param name="radius">Radius at the base of the cone</param>
        /// <param name="numSegments">Number of triangles used to build the side surface</param>
        public static Mesh MakeCone(Vector3 baseCenter, Vector3 tip, float radius, int numSegments)
        {
            ShadingSpace.ComputeBasisVectors(Vector3.Normalize(tip - baseCenter), out var tan, out var binorm);

            List <Vector3> vertices = new();
            List <int>     indices  = new();

            vertices.Add(tip);
            for (int i = 0; i < numSegments; ++i)
            {
                float angle = i * 2 * MathF.PI / numSegments;
                float y     = MathF.Sin(angle) * radius;
                float x     = MathF.Cos(angle) * radius;

                vertices.Add(baseCenter + tan * x + binorm * y);

                indices.AddRange(new List <int>()
                {
                    0, i, (i == numSegments - 1) ? 1 : (i + 1)
                });
            }

            Mesh result = new(vertices.ToArray(), indices.ToArray());

            return(result);
        }
Esempio n. 5
0
 public (float, float) Pdf(Vector3 outDir, Vector3 inDir, bool isOnLightSubpath)
 {
     if (ShadingSpace.SameHemisphere(outDir, inDir))
     {
         return(0, 0);
     }
     return(ComputeOneDir(outDir, inDir), ComputeOneDir(inDir, outDir));
 }
        public void WorldToShade_ShouldBeXY()
        {
            var normal   = new Vector3(1, 1, 0);
            var worldDir = new Vector3(0, 0, 1);
            var shadeDir = ShadingSpace.WorldToShading(normal, worldDir);

            Assert.Equal(0.0f, shadeDir.Z);
        }
        public void WorldToShade_ShouldBeZAxis()
        {
            var normal   = Vector3.Normalize(new Vector3(1, 1, 0));
            var worldDir = Vector3.Normalize(new Vector3(-1, -1, 0));
            var shadeDir = ShadingSpace.WorldToShading(normal, worldDir);

            Assert.Equal(0.0f, shadeDir.X);
            Assert.Equal(0.0f, shadeDir.Y);
            Assert.Equal(-1.0f, shadeDir.Z, 4);
        }
        public void WorldToShade_AndBack_ShouldBeOriginalNormalized()
        {
            var normal    = Vector3.Normalize(new Vector3(1, 5, 0));
            var worldDir  = Vector3.Normalize(new Vector3(1, 6, 2));
            var shadeDir  = ShadingSpace.WorldToShading(normal, worldDir);
            var worldDir2 = ShadingSpace.ShadingToWorld(normal, shadeDir);

            Assert.Equal(worldDir.X / worldDir.Length(), worldDir2.X, 4);
            Assert.Equal(worldDir.Y / worldDir.Length(), worldDir2.Y, 4);
            Assert.Equal(worldDir.Z / worldDir.Length(), worldDir2.Z, 4);
        }
Esempio n. 9
0
        /// <summary>
        /// Warps the given primary sample to follow the pdf computed by <see cref="Pdf(Vector3, Vector3)"/>.
        /// </summary>
        /// <returns>The direction that corresponds to the given primary sample.</returns>
        public Vector3 Sample(Vector3 outDir, Vector2 primary)
        {
            bool flip = ShadingSpace.CosTheta(outDir) < 0;
            var  wh   = TrowbridgeReitzSample(flip ? -outDir : outDir, AlphaX, AlphaY, primary.X, primary.Y);

            if (flip)
            {
                wh = -wh;
            }
            return(wh);
        }
Esempio n. 10
0
        /// <summary>
        /// Computes the ratio of self-masked area to visible area. Used by <see cref="MaskingShadowing(Vector3)"/>.
        /// </summary>
        /// <param name="normal">Normal of the microfacets, in shading space.</param>
        /// <returns>Ratio of self-masked area to visible area.</returns>
        public float MaskingRatio(Vector3 normal)
        {
            float absTanTheta = MathF.Abs(ShadingSpace.TanTheta(normal));

            if (float.IsInfinity(absTanTheta))
            {
                return(0);
            }
            float alpha           = MathF.Sqrt(ShadingSpace.CosPhiSqr(normal) * AlphaX * AlphaX + ShadingSpace.SinPhiSqr(normal) * AlphaY * AlphaY);
            float alpha2Tan2Theta = alpha * absTanTheta * (alpha * absTanTheta);

            return((-1 + MathF.Sqrt(1 + alpha2Tan2Theta)) / 2);
        }
Esempio n. 11
0
        public override (Vector2, Vector2) SampleRayInverse(Vector3 dir, Vector3 pos)
        {
            var primaryDir = SampleDirectionInverse(-dir);

            // Project the point onto the plane with normal "dir"
            Vector3 tangent, binormal;

            ShadingSpace.ComputeBasisVectors(-dir, out tangent, out binormal);
            var   offset     = pos - SceneCenter;
            float x          = Vector3.Dot(offset, tangent) / SceneRadius;
            float y          = Vector3.Dot(offset, binormal) / SceneRadius;
            var   primaryPos = SampleWarp.FromConcentricDisc(new(x, y));

            return(primaryPos, primaryDir);
        }
Esempio n. 12
0
        public override (Vector2, Vector2) SampleRayInverse(SurfacePoint point, Vector3 direction)
        {
            var posPrimary = SampleAreaInverse(point);

            // Transform from world space to sampling space
            var     normal = point.ShadingNormal;
            Vector3 tangent, binormal;

            ShadingSpace.ComputeBasisVectors(normal, out tangent, out binormal);
            float z = Vector3.Dot(normal, direction);
            float x = Vector3.Dot(tangent, direction);
            float y = Vector3.Dot(binormal, direction);

            var dirPrimary = SampleWarp.FromCosineLobe(exponent + 1, new(x, y, z));

            return(posPrimary, dirPrimary);
        }
Esempio n. 13
0
        /// <summary>
        /// Computes the distribution of microfacets with the given normal.
        /// </summary>
        /// <param name="normal">The normal vector of the microfacets, in shading space.</param>
        /// <returns>The fraction of microfacets that are oriented with the given normal.</returns>
        public float NormalDistribution(Vector3 normal)
        {
            float tan2Theta = ShadingSpace.TanThetaSqr(normal);

            if (float.IsInfinity(tan2Theta))
            {
                return(0);
            }

            float cos4Theta = ShadingSpace.CosThetaSqr(normal) * ShadingSpace.CosThetaSqr(normal);

            float e = tan2Theta * (
                ShadingSpace.CosPhiSqr(normal) / (AlphaX * AlphaX)
                + ShadingSpace.SinPhiSqr(normal) / (AlphaY * AlphaY)
                );

            return(1 / (MathF.PI * AlphaX * AlphaY * cos4Theta * (1 + e) * (1 + e)));
        }
Esempio n. 14
0
        public Vector3?Sample(Vector3 outDir, bool isOnLightSubpath, Vector2 primarySample)
        {
            if (outDir.Z == 0)
            {
                return(null);
            }

            Vector3 wh = distribution.Sample(outDir, primarySample);

            if (Vector3.Dot(outDir, wh) < 0)
            {
                return(null);
            }

            float eta   = ShadingSpace.CosTheta(outDir) > 0 ? (outsideIOR / insideIOR) : (insideIOR / outsideIOR);
            var   inDir = ShadingSpace.Refract(outDir, wh, eta);

            return(inDir);
        }
Esempio n. 15
0
        public RgbColor Evaluate(Vector3 outDir, Vector3 inDir, bool isOnLightSubpath)
        {
            if (ShadingSpace.SameHemisphere(outDir, inDir))
            {
                return(RgbColor.Black);                                             // transmission only
            }
            float cosThetaO = ShadingSpace.CosTheta(outDir);
            float cosThetaI = ShadingSpace.CosTheta(inDir);

            if (cosThetaI == 0 || cosThetaO == 0)
            {
                return(RgbColor.Black);
            }

            // Compute the half vector
            float   eta = ShadingSpace.CosTheta(outDir) > 0 ? (insideIOR / outsideIOR) : (outsideIOR / insideIOR);
            Vector3 wh  = Vector3.Normalize(outDir + inDir * eta);

            if (ShadingSpace.CosTheta(wh) < 0)
            {
                wh = -wh;
            }
            if (Vector3.Dot(outDir, wh) * Vector3.Dot(inDir, wh) > 0)
            {
                return(RgbColor.Black);
            }

            var F = new RgbColor(FresnelDielectric.Evaluate(Vector3.Dot(outDir, wh), outsideIOR, insideIOR));

            float sqrtDenom = Vector3.Dot(outDir, wh) + eta * Vector3.Dot(inDir, wh);
            float factor    = isOnLightSubpath ? (1 / eta) : 1;

            var numerator = distribution.NormalDistribution(wh) * distribution.MaskingShadowing(outDir, inDir);

            numerator *= eta * eta * Math.Abs(Vector3.Dot(inDir, wh)) * Math.Abs(Vector3.Dot(outDir, wh));
            numerator *= factor * factor;

            var denom = (cosThetaI * cosThetaO * sqrtDenom * sqrtDenom);

            Debug.Assert(float.IsFinite(denom));
            return((RgbColor.White - F) * transmittance * Math.Abs(numerator / denom));
        }
Esempio n. 16
0
        public static void BenchComputeBasisVectors(int numTrials)
        {
            Random rng = new(1337);

            Vector3 NextVector() => new (
                (float)rng.NextDouble(),
                (float)rng.NextDouble(),
                (float)rng.NextDouble());

            Vector3 avg = Vector3.Zero;

            Stopwatch stop = Stopwatch.StartNew();

            for (int i = 0; i < numTrials; ++i)
            {
                Vector3 tan, binorm;
                ShadingSpace.ComputeBasisVectors(NextVector(), out tan, out binorm);
                avg += (tan + binorm) / numTrials * 0.5f;
            }
            Console.WriteLine($"Computing {numTrials} basis vectors took {stop.ElapsedMilliseconds}ms - {avg.Length()}");
        }
Esempio n. 17
0
        public (float, float) Pdf(Vector3 outDir, Vector3 inDir, bool isOnLightSubpath)
        {
            if (!ShadingSpace.SameHemisphere(outDir, inDir))
            {
                return(0, 0);
            }

            var halfVector = outDir + inDir;

            // catch NaN causing corner cases
            if (halfVector == Vector3.Zero)
            {
                return(0, 0);
            }

            halfVector = Vector3.Normalize(halfVector);
            var pdfForward = distribution.Pdf(outDir, halfVector) / Math.Abs(4 * Vector3.Dot(outDir, halfVector));
            var pdfReverse = distribution.Pdf(inDir, halfVector) / Math.Abs(4 * Vector3.Dot(inDir, halfVector));

            return(pdfForward, pdfReverse);
        }
Esempio n. 18
0
        /// <summary>
        /// Creates a cylinder that connects two given points like a pipe
        /// </summary>
        /// <param name="from">The first point, the center of one cylinder disc</param>
        /// <param name="to">The second point, the center of the other cylinder disc</param>
        /// <param name="radius">Radius of the cylinder</param>
        /// <param name="numSegments">Number of quads used to build the outer surface</param>
        public static Mesh MakeCylinder(Vector3 from, Vector3 to, float radius, int numSegments)
        {
            ShadingSpace.ComputeBasisVectors(Vector3.Normalize(to - from), out var tan, out var binorm);

            List <Vector3> vertices = new();
            List <int>     indices  = new();

            for (int i = 0; i < numSegments; ++i)
            {
                float angle = i * 2 * MathF.PI / numSegments;
                float y     = MathF.Sin(angle) * radius;
                float x     = MathF.Cos(angle) * radius;

                vertices.Add(from + tan * x + binorm * y);
                vertices.Add(to + tan * x + binorm * y);

                // Indices of the last edge are the first one (close the circle)
                int nextA = 2 * i + 2;
                int nextB = 2 * i + 3;
                if (i == numSegments - 1)
                {
                    nextA = 0;
                    nextB = 1;
                }

                indices.AddRange(new List <int>()
                {
                    2 * i,
                    2 * i + 1,
                    nextA,
                    nextA,
                    2 * i + 1,
                    nextB
                });
            }

            Mesh result = new(vertices.ToArray(), indices.ToArray());

            return(result);
        }
Esempio n. 19
0
        public Vector3?Sample(Vector3 outDir, bool isOnLightSubpath, Vector2 primarySample)
        {
            if (outDir.Z == 0)
            {
                return(null);
            }

            var halfVector = distribution.Sample(outDir, primarySample);

            if (Vector3.Dot(halfVector, outDir) < 0)
            {
                return(null);
            }

            var inDir = ShadingSpace.Reflect(outDir, halfVector);

            if (!ShadingSpace.SameHemisphere(outDir, inDir))
            {
                return(null);
            }

            return(inDir);
        }
Esempio n. 20
0
        public override EmitterSample SampleRay(Vector2 primaryPos, Vector2 primaryDir)
        {
            var posSample = SampleArea(primaryPos);

            // Transform primary to cosine hemisphere (z is up)
            var local = SampleWarp.ToCosHemisphere(primaryDir);

            // Transform to world space direction
            var     normal = posSample.Point.ShadingNormal;
            Vector3 tangent, binormal;

            ShadingSpace.ComputeBasisVectors(normal, out tangent, out binormal);
            Vector3 dir = local.Direction.Z * normal
                          + local.Direction.X * tangent
                          + local.Direction.Y * binormal;

            return(new EmitterSample {
                Point = posSample.Point,
                Direction = dir,
                Pdf = local.Pdf * posSample.Pdf,
                Weight = Radiance / posSample.Pdf * MathF.PI // cosine cancels out with the directional pdf
            });
        }
Esempio n. 21
0
        public RgbColor Evaluate(Vector3 outDir, Vector3 inDir, bool isOnLightSubpath)
        {
            if (!ShadingSpace.SameHemisphere(outDir, inDir))
            {
                return(RgbColor.Black);
            }

            float   cosThetaO  = ShadingSpace.AbsCosTheta(outDir);
            float   cosThetaI  = ShadingSpace.AbsCosTheta(inDir);
            Vector3 halfVector = inDir + outDir;

            // Handle degenerate cases for microfacet reflection
            if (cosThetaI == 0 || cosThetaO == 0)
            {
                return(RgbColor.Black);
            }
            if (halfVector.X == 0 && halfVector.Y == 0 && halfVector.Z == 0)
            {
                return(RgbColor.Black);
            }

            // For the Fresnel call, make sure that wh is in the same hemisphere
            // as the surface normal, so that total internal reflection is handled correctly.
            halfVector = Vector3.Normalize(halfVector);
            if (ShadingSpace.CosTheta(halfVector) < 0)
            {
                halfVector = -halfVector;
            }

            var cosine = Vector3.Dot(inDir, halfVector);
            var f      = fresnel.Evaluate(cosine);

            var nd = distribution.NormalDistribution(halfVector);
            var ms = distribution.MaskingShadowing(outDir, inDir);

            return(tint * nd * ms * f / (4 * cosThetaI * cosThetaO));
        }
Esempio n. 22
0
        static Vector3 TrowbridgeReitzSample(Vector3 wi, float alpha_x,
                                             float alpha_y, float U1, float U2)
        {
            // 1. stretch wi
            Vector3 wiStretched = Vector3.Normalize(new Vector3(alpha_x * wi.X, alpha_y * wi.Y, wi.Z));

            // 2. simulate P22_{wi}(x_slope, y_slope, 1, 1)
            float slope_x, slope_y;

            TrowbridgeReitzSample11(ShadingSpace.CosTheta(wiStretched), U1, U2, out slope_x, out slope_y);

            // 3. rotate
            float tmp = ShadingSpace.CosPhi(wiStretched) * slope_x - ShadingSpace.SinPhi(wiStretched) * slope_y;

            slope_y = ShadingSpace.SinPhi(wiStretched) * slope_x + ShadingSpace.CosPhi(wiStretched) * slope_y;
            slope_x = tmp;

            // 4. unstretch
            slope_x = alpha_x * slope_x;
            slope_y = alpha_y * slope_y;

            // 5. compute normal
            return(Vector3.Normalize(new Vector3(-slope_x, -slope_y, 1)));
        }
Esempio n. 23
0
 /// <summary>
 /// The Pdf that is used for importance sampling microfacet normals from this distribution.
 /// This usually importance samples the portion of normals that are in the hemisphere of the outgoing direction.
 /// </summary>
 /// <param name="outDir">The outgoing direction in shading space.</param>
 /// <param name="inDir">The incoming direction in shading space.</param>
 /// <returns>The pdf value.</returns>
 public float Pdf(Vector3 outDir, Vector3 normal) =>
 NormalDistribution(normal) * MaskingShadowing(outDir) * MathF.Abs(Vector3.Dot(outDir, normal))
 / ShadingSpace.AbsCosTheta(outDir);