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