Ejemplo 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
            });
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Performs the inverse of the projection done by <see cref="Sample"/>
        /// </summary>
        /// <param name="point">A point on the surface of this mesh</param>
        /// <returns>The primary sample space point that would have been projected there</returns>
        public Vector2 SampleInverse(SurfacePoint point)
        {
            var   local = SampleWarp.FromUniformTriangle(point.BarycentricCoords);
            float x     = triangleDistribution.SampleInverse((int)point.PrimId, local.X);

            return(new(x, local.Y));
        }
Ejemplo n.º 3
0
        public void FromSphere_ShouldBeInverse()
        {
            var primary = new Vector2(0.41f, 0.123f);
            var dir     = SampleWarp.ToUniformSphere(primary).Direction;
            var p2      = SampleWarp.FromUniformSphere(dir);

            Assert.Equal(primary.X, p2.X, 4);
            Assert.Equal(primary.Y, p2.Y, 4);

            primary = new Vector2(0.91f, 0.00123f);
            dir     = SampleWarp.ToUniformSphere(primary).Direction;
            p2      = SampleWarp.FromUniformSphere(dir);

            Assert.Equal(primary.X, p2.X, 4);
            Assert.Equal(primary.Y, p2.Y, 4);

            primary = new Vector2(0.091f, 0.00123f);
            dir     = SampleWarp.ToUniformSphere(primary).Direction;
            p2      = SampleWarp.FromUniformSphere(dir);

            Assert.Equal(primary.X, p2.X, 4);
            Assert.Equal(primary.Y, p2.Y, 4);

            primary = new Vector2(0.91f, 0.823f);
            dir     = SampleWarp.ToUniformSphere(primary).Direction;
            p2      = SampleWarp.FromUniformSphere(dir);

            Assert.Equal(primary.X, p2.X, 4);
            Assert.Equal(primary.Y, p2.Y, 4);
        }
Ejemplo n.º 4
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);
        }
Ejemplo n.º 5
0
        public void CosHemisphere_Inverse()
        {
            var sample = SampleWarp.ToCosHemisphere(new(0.43f, 0.793f));
            var prim   = SampleWarp.FromCosHemisphere(sample.Direction);

            Assert.Equal(0.43f, prim.X, 3);
            Assert.Equal(0.793f, prim.Y, 3);
        }
Ejemplo n.º 6
0
        public void Inverse_ShouldBeZero()
        {
            var bary = SampleWarp.ToUniformTriangle(new(0.1f, 0.1f));
            var prim = SampleWarp.FromUniformTriangle(bary);

            Assert.Equal(0.1f, prim.X, 4);
            Assert.Equal(0.1f, prim.Y, 4);
        }
Ejemplo n.º 7
0
        public void ConcentricDisc_Inverse()
        {
            var sample = SampleWarp.ToConcentricDisc(new(0.315f, -0.3154f));
            var prim   = SampleWarp.FromConcentricDisc(sample);

            Assert.Equal(0.315f, prim.X, 3);
            Assert.Equal(-0.3154f, prim.Y, 3);
        }
Ejemplo n.º 8
0
        public override float RayPdf(Vector3 point, Vector3 direction)
        {
            float dirPdf       = DirectionPdf(-direction);
            float discJacobian = SampleWarp.ToConcentricDiscJacobian();
            float posPdf       = discJacobian / (SceneRadius * SceneRadius);

            return(posPdf * dirPdf);
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Computes the change of area when mapping a direction from the hemisphere around the camera
        /// to the image. Given by our transformation to spherical coordinates, followed by the scaling to the
        /// desired resolution.
        /// </summary>
        /// <param name="pos">Position in world space of a point towards which the direction points</param>
        /// <returns>
        ///     Jacobian determinant that describes how much larger an area on the image plane is than
        ///     the corresponding solid angle.
        /// </returns>
        public override float SolidAngleToPixelJacobian(Vector3 pos)
        {
            var dir = Vector3.Normalize(pos - position);

            dir = Shading.ShadingSpace.WorldToShading(upVector, dir);
            float theta = SampleWarp.CartesianToSpherical(dir).Y;

            return(1 / (2 * MathF.PI * MathF.PI * MathF.Sin(theta)) * width * height);
        }
Ejemplo n.º 10
0
        Vector3 WorldToFilm(Vector3 pos)
        {
            Debug.Assert(width != 0 && height != 0);

            var   dir      = pos - position;
            float distance = dir.Length();

            dir = Shading.ShadingSpace.WorldToShading(upVector, dir / distance);
            var spherical = SampleWarp.CartesianToSpherical(dir);

            return(new(
                       width * spherical.X / (2 * MathF.PI),
                       height *spherical.Y / MathF.PI,
                       distance
                       ));
        }
Ejemplo n.º 11
0
        /// <summary>
        /// Generates a ray from a position in the image into the scene
        /// </summary>
        /// <param name="filmPos">
        ///     Position on the film plane: integer pixel coordinates and fractional position within
        /// </param>
        /// <param name="rng">
        ///     Random number generator used to sample additional decisions (lens position for depth of field)
        /// </param>
        /// <returns>The sampled camera ray and related information like PDF and contribution</returns>
        public override CameraRaySample GenerateRay(Vector2 filmPos, RNG rng)
        {
            Debug.Assert(Width != 0 && Height != 0);

            // Transform the direction from film to world space.
            // The view space is vertically flipped compared to the film.
            var view     = new Vector3(2 * filmPos.X / Width - 1, 1 - 2 * filmPos.Y / Height, 0);
            var localDir = Vector3.Transform(view, viewToCamera);
            var dirHomo  = Vector4.Transform(new Vector4(localDir, 0), cameraToWorld);
            var dir      = new Vector3(dirHomo.X, dirHomo.Y, dirHomo.Z);

            // Compute the camera position
            var pos = Vector3.Transform(new Vector3(0, 0, 0), cameraToWorld);

            var ray = new Ray {
                Direction = Vector3.Normalize(dir), MinDistance = 0, Origin = pos
            };

            // Sample depth of field
            float pdfLens = 1;

            if (lensRadius > 0)
            {
                var lensSample = rng.NextFloat2D();
                var lensPos    = lensRadius * SampleWarp.ToConcentricDisc(lensSample);

                // Intersect ray with focal plane
                var focalPoint = ray.ComputePoint(focalDistance / ray.Direction.Z);

                // Update the ray
                ray.Origin    = new Vector3(lensPos, 0);
                ray.Direction = Vector3.Normalize(focalPoint - ray.Origin);

                pdfLens = 1 / (MathF.PI * lensRadius * lensRadius);
            }

            return(new CameraRaySample {
                Ray = ray,
                Weight = RgbColor.White,
                Point = new SurfacePoint {
                    Position = Position, Normal = Direction
                },
                PdfRay = SolidAngleToPixelJacobian(pos + dir) * pdfLens,
                PdfConnect = pdfLens
            });
        }
Ejemplo 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);
        }
Ejemplo n.º 13
0
        /// <summary>
        /// Samples a point uniformly distributed on the mesh surface
        /// </summary>
        /// <param name="primarySample">
        ///     A primary sample space value that is projected onto the surface of the mesh.
        /// </param>
        /// <returns>A point and associated surface area pdf</returns>
        public SurfaceSample Sample(Vector2 primarySample)
        {
            var(faceIdx, newX) = triangleDistribution.Sample(primarySample.X);
            var barycentric = SampleWarp.ToUniformTriangle(new Vector2(newX, primarySample.Y));

            return(new SurfaceSample {
                Point = new SurfacePoint {
                    BarycentricCoords = barycentric,
                    PrimId = (uint)faceIdx,
                    Normal = FaceNormals[faceIdx],
                    Position = ComputePosition(faceIdx, barycentric),
                    ErrorOffset = ComputeErrorOffset(faceIdx, barycentric),
                    Mesh = this
                },
                Pdf = 1.0f / SurfaceArea
            });
        }
Ejemplo n.º 14
0
        /// <summary>
        /// Samples a point on the camera lens that sees the given surface point. Returns an invalid
        /// sample if there is no such point.
        /// </summary>
        /// <param name="scenePoint">A point on a scene surface that might be seen by the camera</param>
        /// <param name="rng">RNG used to sample the lens. Can be null if the lens radius is zero.</param>
        /// <returns>The pixel coordinates and weights, or an invalid sample</returns>
        public override CameraResponseSample SampleResponse(SurfacePoint scenePoint, RNG rng)
        {
            Debug.Assert(Width != 0 && Height != 0);

            // Sample a point on the lens
            Vector3 lensPoint = Position;

            if (lensRadius > 0)
            {
                var lensSample   = rng.NextFloat2D();
                var lensPosCam   = lensRadius * SampleWarp.ToConcentricDisc(lensSample);
                var lensPosWorld = Vector4.Transform(new Vector4(lensPosCam.X, lensPosCam.Y, 0, 1), cameraToWorld);
                lensPoint = new(lensPosWorld.X, lensPosWorld.Y, lensPosWorld.Z);
            }

            // Map the scene point to the film
            var filmPos = WorldToFilm(scenePoint.Position);

            if (!filmPos.HasValue)
            {
                return(CameraResponseSample.Invalid);
            }

            // Compute the change of variables from scene surface to pixel area
            float jacobian = SolidAngleToPixelJacobian(scenePoint.Position);

            jacobian *= SurfaceAreaToSolidAngleJacobian(scenePoint.Position, scenePoint.Normal);

            // Compute the pdfs
            float invLensArea = 1;

            if (lensRadius > 0)
            {
                invLensArea = 1 / (MathF.PI * lensRadius * lensRadius);
            }
            float pdfConnect = invLensArea;
            float pdfEmit    = invLensArea * jacobian;

            return(new CameraResponseSample {
                Pixel = new(filmPos.Value.X, filmPos.Value.Y),
                Position = lensPoint,
                Weight = jacobian * RgbColor.White,
                PdfConnect = pdfConnect,
                PdfEmit = pdfEmit
            });
Ejemplo n.º 15
0
        public void SphericalInverse()
        {
            // Y axis
            var spherical = SampleWarp.CartesianToSpherical(Vector3.UnitY);
            var dir       = SampleWarp.SphericalToCartesian(spherical);

            Assert.Equal(0, dir.X, 4);
            Assert.Equal(1, dir.Y, 4);
            Assert.Equal(0, dir.Z, 4);

            spherical = SampleWarp.CartesianToSpherical(-Vector3.UnitY);
            dir       = SampleWarp.SphericalToCartesian(spherical);
            Assert.Equal(0, dir.X, 4);
            Assert.Equal(-1, dir.Y, 4);
            Assert.Equal(0, dir.Z, 4);

            // x axis
            spherical = SampleWarp.CartesianToSpherical(Vector3.UnitX);
            dir       = SampleWarp.SphericalToCartesian(spherical);
            Assert.Equal(1, dir.X, 4);
            Assert.Equal(0, dir.Y, 4);
            Assert.Equal(0, dir.Z, 4);

            spherical = SampleWarp.CartesianToSpherical(-Vector3.UnitX);
            dir       = SampleWarp.SphericalToCartesian(spherical);
            Assert.Equal(-1, dir.X, 4);
            Assert.Equal(0, dir.Y, 4);
            Assert.Equal(0, dir.Z, 4);

            // z axis
            spherical = SampleWarp.CartesianToSpherical(Vector3.UnitZ);
            dir       = SampleWarp.SphericalToCartesian(spherical);
            Assert.Equal(0, dir.X, 4);
            Assert.Equal(0, dir.Y, 4);
            Assert.Equal(1, dir.Z, 4);

            spherical = SampleWarp.CartesianToSpherical(-Vector3.UnitZ);
            dir       = SampleWarp.SphericalToCartesian(spherical);
            Assert.Equal(0, dir.X, 4);
            Assert.Equal(0, dir.Y, 4);
            Assert.Equal(-1, dir.Z, 4);
        }
Ejemplo n.º 16
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
            });
        }
Ejemplo n.º 17
0
        public override float PdfRay(SurfacePoint point, Vector3 direction)
        {
            float cosine = Vector3.Dot(point.ShadingNormal, direction) / direction.Length();

            return(PdfArea(point) * SampleWarp.ToCosineLobeJacobian(exponent + 1, cosine));
        }
Ejemplo n.º 18
0
        RgbColor ContinueWalk(Ray ray, SurfacePoint previousPoint, float pdfDirection, RgbColor throughput,
                              int depth)
        {
            // Terminate if the maximum depth has been reached
            if (depth >= maxDepth)
            {
                OnTerminate();
                return(RgbColor.Black);
            }

            var hit = scene.Raytracer.Trace(ray);

            if (!hit)
            {
                var result = OnInvalidHit(ray, pdfDirection, throughput, depth);
                OnTerminate();
                return(result);
            }

            // Convert the PDF of the previous hemispherical sample to surface area
            float pdfFromAncestor = pdfDirection * SampleWarp.SurfaceAreaToSolidAngle(previousPoint, hit);

            // Geometry term might be zero due to, e.g., shading normal issues
            // Avoid NaNs in that case by terminating early
            if (pdfFromAncestor == 0)
            {
                OnTerminate();
                return(RgbColor.Black);
            }

            RgbColor estimate = OnHit(ray, hit, pdfFromAncestor, throughput, depth,
                                      SampleWarp.SurfaceAreaToSolidAngle(hit, previousPoint));

            // Terminate with Russian roulette
            float survivalProb = ComputeSurvivalProbability(hit, ray, throughput, depth);

            if (rng.NextFloat() > survivalProb)
            {
                OnTerminate();
                return(estimate);
            }

            // Continue based on the splitting factor
            int numSplits = ComputeSplitFactor(hit, ray, throughput, depth);

            for (int i = 0; i < numSplits; ++i)
            {
                // Sample the next direction and convert the reverse pdf
                var(pdfNext, pdfReverse, weight, direction) = SampleNextDirection(hit, ray, throughput, depth);
                float pdfToAncestor = pdfReverse * SampleWarp.SurfaceAreaToSolidAngle(hit, previousPoint);

                if (pdfToAncestor == 0)
                {
                    Debug.Assert(pdfNext == 0);
                }

                OnContinue(pdfToAncestor, depth);

                if (pdfNext == 0 || weight == RgbColor.Black)
                {
                    OnTerminate();
                    continue;
                }

                // Account for splitting and roulette in the weight
                weight *= 1.0f / (survivalProb * numSplits);

                // Continue the path with the next ray
                var nextRay = Raytracer.SpawnRay(hit, direction);
                estimate += ContinueWalk(nextRay, hit, pdfNext, throughput * weight, depth + 1);
            }

            return(estimate);
        }