Skip to content
52 changes: 42 additions & 10 deletions SeeSharp/Geometry/SurfacePoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,70 @@
/// Represents a point on the surface of a mesh in the scene. Wrapper around <see cref="Hit"/> with
/// additional SeeSharp specific material information.
/// </summary>
public struct SurfacePoint {
public struct SurfacePoint
{
/// <summary>
/// Position in world space
/// </summary>
public Vector3 Position { get => hit.Position; set => hit.Position = value; }
public Vector3 Position
{
get => hit.Position;
set => hit.Position = value;
}

/// <summary>
/// Face normal at the point (i.e., actual geometric normal, not the shading normal)
/// </summary>
public Vector3 Normal { get => hit.Normal; set => hit.Normal = value; }
public Vector3 Normal
{
get => hit.Normal;
set => hit.Normal = value;
}

/// <summary>
/// Barycentric coordinates within the primitive
/// </summary>
public Vector2 BarycentricCoords { get => hit.BarycentricCoords; set => hit.BarycentricCoords = value; }
public Vector2 BarycentricCoords
{
get => hit.BarycentricCoords;
set => hit.BarycentricCoords = value;
}

/// <summary>
/// The mesh on which this point lies
/// </summary>
public Mesh Mesh { get => hit.Mesh as Mesh; set => hit.Mesh = value; }
public Mesh Mesh
{
get => hit.Mesh as Mesh;
set => hit.Mesh = value;
}

/// <summary>
/// Index of the primitive within the mesh
/// </summary>
public uint PrimId { get => hit.PrimId; set => hit.PrimId = value; }
public uint PrimId
{
get => hit.PrimId;
set => hit.PrimId = value;
}

/// <summary>
/// Offset that should be used to avoid self-intersection during ray tracing
/// </summary>
public float ErrorOffset { get => hit.ErrorOffset; set => hit.ErrorOffset = value; }
public float ErrorOffset
{
get => hit.ErrorOffset;
set => hit.ErrorOffset = value;
}

/// <summary>
/// Distance from a previous point if this is a ray intersection
/// </summary>
public float Distance { get => hit.Distance; set => hit.Distance = value; }
public float Distance
{
get => hit.Distance;
set => hit.Distance = value;
}

/// <summary>
/// Checks if the point is valid
Expand All @@ -54,7 +83,8 @@ public struct SurfacePoint {
/// Implicit cast from a TinyEmbree hit object for convenience
/// </summary>
/// <param name="hit"></param>
public static implicit operator SurfacePoint(Hit hit) {
public static implicit operator SurfacePoint(Hit hit)
{
return new SurfacePoint { hit = hit };
}

Expand All @@ -73,5 +103,7 @@ public static implicit operator SurfacePoint(Hit hit) {
/// </summary>
public Material Material => Mesh.Material;

public static SurfacePoint Invalid => new() { Mesh = null };

Hit hit;
}
}
42 changes: 25 additions & 17 deletions SeeSharp/Integrators/Bidir/BidirBase.Camera.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,25 +325,34 @@ protected virtual RgbColor BidirConnections(in SurfaceShader shader, ref RNG rng
protected virtual (Emitter, SurfaceSample) SampleNextEvent(SurfacePoint from, ref RNG rng) {
var (light, lightProb) = SelectLight(from, ref rng);
var lightSample = light.SampleUniformArea(rng.NextFloat2D());
lightSample.Pdf *= lightProb;
lightSample.Pdf *= lightProb * NumShadowRays;
return (light, lightSample);
}

/// <summary>
/// Computes the pdf used by <see cref="SampleNextEvent" />
/// Computes the pdf used by <see cref="SampleNextEvent" /> for an area light source. Assumes that `to` is a valid point on an emitter.
/// </summary>
/// <param name="from">The shading point</param>
/// <param name="to">The point on the light source</param>
/// <returns>PDF of next event estimation</returns>
protected virtual float NextEventPdf(SurfacePoint from, SurfacePoint to) {
public virtual float NextEventPdf(SurfacePoint from, SurfacePoint to) {
// Switch to background case if "to" is a position in free space, not on a mesh
if (!to) return NextEventPdf(from, to.Position - from.Position);

float backgroundProbability = ComputeNextEventBackgroundProbability(/*hit*/);
if (to.Mesh == null) { // Background
var direction = to.Position - from.Position;
return Scene.Background.DirectionPdf(direction) * backgroundProbability;
} else { // Emissive object
var emitter = Scene.QueryEmitter(to);
return emitter.PdfUniformArea(to) * SelectLightPmf(from, emitter) * (1 - backgroundProbability);
}
var emitter = Scene.QueryEmitter(to);
return emitter.PdfUniformArea(to) * SelectLightPmf(from, emitter) * (1 - backgroundProbability) * NumShadowRays;
}

/// <summary>
/// Computes the pdf used by <see cref="SampleNextEvent" /> for a background ray direction.
/// </summary>
/// <param name="from">The shading point</param>
/// <param name="direction">The direction from the shading point to the background</param>
/// <returns>PDF of next event estimation</returns>
public virtual float NextEventPdf(SurfacePoint from, Vector3 direction) {
float backgroundProbability = ComputeNextEventBackgroundProbability(/*hit*/);
return Scene.Background.DirectionPdf(direction) * backgroundProbability * NumShadowRays;
}

/// <summary>
Expand Down Expand Up @@ -383,8 +392,8 @@ protected virtual RgbColor PerformNextEventEstimation(in SurfaceShader shader, r
return RgbColor.Black; // There is no background

var sample = Scene.Background.SampleDirection(rng.NextFloat2D());
sample.Pdf *= backgroundProbability;
sample.Weight /= backgroundProbability;
sample.Pdf *= backgroundProbability * NumShadowRays;
sample.Weight /= backgroundProbability * NumShadowRays;

if (sample.Pdf == 0) // Prevent NaN
return RgbColor.Black;
Expand Down Expand Up @@ -503,7 +512,7 @@ protected virtual RgbColor OnEmitterHit(Emitter emitter, SurfacePoint hit, Vecto

// Compute pdf values
float pdfEmit = ComputeEmitterPdf(emitter, hit, outDir, reversePdfJacobian);
float pdfNextEvent = NextEventPdf(new SurfacePoint(), hit); // TODO get the actual previous point!
float pdfNextEvent = NextEventPdf(SurfacePoint.Invalid, hit); // TODO get the actual previous point!

int numPdfs = path.Vertices.Count;
int lastCameraVertexIdx = numPdfs - 1;
Expand Down Expand Up @@ -536,10 +545,9 @@ protected virtual RgbColor OnBackgroundHit(Ray ray, ref CameraPath path) {
// Compute the pdf of sampling the previous point by emission from the background
float pdfEmit = ComputeBackgroundPdf(ray.Origin, -ray.Direction);

// Compute the pdf of sampling the same connection via next event estimation
float pdfNextEvent = Scene.Background.DirectionPdf(ray.Direction);
float backgroundProbability = ComputeNextEventBackgroundProbability(/*hit*/);
pdfNextEvent *= backgroundProbability;
// Compute the pdf of sampling the same connection via next event estimation.
// TODO get the actual previous point (need the mesh, not just the position)
float pdfNextEvent = NextEventPdf(SurfacePoint.Invalid, ray.Direction);

int numPdfs = path.Vertices.Count;
int lastCameraVertexIdx = numPdfs - 1;
Expand Down
6 changes: 4 additions & 2 deletions SeeSharp/Integrators/Bidir/BidirBase.Light.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ void ConnectLightVertexToCamera(in PathVertex vertex, in PathVertex ancestor, Ve
pathPdfs.PdfsCameraToLight[0] = response.PdfEmit;
pathPdfs.PdfsCameraToLight[1] = pdfReverse;
if (vertex.Depth == 1)
pathPdfs.PdfNextEvent = NextEventPdf(vertex.Point, ancestor.Point);
pathPdfs.PdfNextEvent = ancestor.Point
? NextEventPdf(vertex.Point, ancestor.Point)
: NextEventPdf(vertex.Point, ancestor.Point.Position - vertex.Point.Position);

float misWeight = LightTracerMis(vertex, pathPdfs, response.Pixel, distToCam);

Expand Down Expand Up @@ -201,7 +203,7 @@ public virtual void TraceLightPaths(uint seed, uint iter) {
else
PathCache.Clear();

LightPathWalk walkModifier = new(PathCache, (to, from, _) => NextEventPdf(from, to));
LightPathWalk walkModifier = new(PathCache, (to, from, _) => to ? NextEventPdf(from, to) : NextEventPdf(from, to.Position - from.Position));

Parallel.For(0, NumLightPaths, idx => {
var rng = new RNG(seed, (uint)idx, iter);
Expand Down
6 changes: 6 additions & 0 deletions SeeSharp/Integrators/Bidir/BidirBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ public abstract partial class BidirBase<CameraPayloadType> : Integrator
/// </summary>
public int NumLightPaths { get; set; } = -1;

/// <summary>
/// Number of shadow rays (next event estimation samples) per camera vertex. Zero disables the technique.
/// Must only be changed in-between rendering iterations. Otherwise: mayhem.
/// </summary>
public int NumShadowRays = 1;

/// <summary>
/// The base seed to generate camera paths.
/// </summary>
Expand Down
Loading
Loading