diff --git a/SeeSharp/Geometry/SurfacePoint.cs b/SeeSharp/Geometry/SurfacePoint.cs
index 09377522..c2dccfbf 100644
--- a/SeeSharp/Geometry/SurfacePoint.cs
+++ b/SeeSharp/Geometry/SurfacePoint.cs
@@ -4,41 +4,70 @@
/// Represents a point on the surface of a mesh in the scene. Wrapper around with
/// additional SeeSharp specific material information.
///
-public struct SurfacePoint {
+public struct SurfacePoint
+{
///
/// Position in world space
///
- public Vector3 Position { get => hit.Position; set => hit.Position = value; }
+ public Vector3 Position
+ {
+ get => hit.Position;
+ set => hit.Position = value;
+ }
///
/// Face normal at the point (i.e., actual geometric normal, not the shading normal)
///
- public Vector3 Normal { get => hit.Normal; set => hit.Normal = value; }
+ public Vector3 Normal
+ {
+ get => hit.Normal;
+ set => hit.Normal = value;
+ }
///
/// Barycentric coordinates within the primitive
///
- public Vector2 BarycentricCoords { get => hit.BarycentricCoords; set => hit.BarycentricCoords = value; }
+ public Vector2 BarycentricCoords
+ {
+ get => hit.BarycentricCoords;
+ set => hit.BarycentricCoords = value;
+ }
///
/// The mesh on which this point lies
///
- public Mesh Mesh { get => hit.Mesh as Mesh; set => hit.Mesh = value; }
+ public Mesh Mesh
+ {
+ get => hit.Mesh as Mesh;
+ set => hit.Mesh = value;
+ }
///
/// Index of the primitive within the mesh
///
- public uint PrimId { get => hit.PrimId; set => hit.PrimId = value; }
+ public uint PrimId
+ {
+ get => hit.PrimId;
+ set => hit.PrimId = value;
+ }
///
/// Offset that should be used to avoid self-intersection during ray tracing
///
- public float ErrorOffset { get => hit.ErrorOffset; set => hit.ErrorOffset = value; }
+ public float ErrorOffset
+ {
+ get => hit.ErrorOffset;
+ set => hit.ErrorOffset = value;
+ }
///
/// Distance from a previous point if this is a ray intersection
///
- public float Distance { get => hit.Distance; set => hit.Distance = value; }
+ public float Distance
+ {
+ get => hit.Distance;
+ set => hit.Distance = value;
+ }
///
/// Checks if the point is valid
@@ -54,7 +83,8 @@ public struct SurfacePoint {
/// Implicit cast from a TinyEmbree hit object for convenience
///
///
- public static implicit operator SurfacePoint(Hit hit) {
+ public static implicit operator SurfacePoint(Hit hit)
+ {
return new SurfacePoint { hit = hit };
}
@@ -73,5 +103,7 @@ public static implicit operator SurfacePoint(Hit hit) {
///
public Material Material => Mesh.Material;
+ public static SurfacePoint Invalid => new() { Mesh = null };
+
Hit hit;
-}
+}
\ No newline at end of file
diff --git a/SeeSharp/Integrators/Bidir/BidirBase.Camera.cs b/SeeSharp/Integrators/Bidir/BidirBase.Camera.cs
index fabbfa36..b90e0cd0 100644
--- a/SeeSharp/Integrators/Bidir/BidirBase.Camera.cs
+++ b/SeeSharp/Integrators/Bidir/BidirBase.Camera.cs
@@ -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);
}
///
- /// Computes the pdf used by
+ /// Computes the pdf used by for an area light source. Assumes that `to` is a valid point on an emitter.
///
/// The shading point
/// The point on the light source
/// PDF of next event estimation
- 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;
+ }
+
+ ///
+ /// Computes the pdf used by for a background ray direction.
+ ///
+ /// The shading point
+ /// The direction from the shading point to the background
+ /// PDF of next event estimation
+ public virtual float NextEventPdf(SurfacePoint from, Vector3 direction) {
+ float backgroundProbability = ComputeNextEventBackgroundProbability(/*hit*/);
+ return Scene.Background.DirectionPdf(direction) * backgroundProbability * NumShadowRays;
}
///
@@ -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;
@@ -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;
@@ -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;
diff --git a/SeeSharp/Integrators/Bidir/BidirBase.Light.cs b/SeeSharp/Integrators/Bidir/BidirBase.Light.cs
index 6dfa4271..f0197e6d 100644
--- a/SeeSharp/Integrators/Bidir/BidirBase.Light.cs
+++ b/SeeSharp/Integrators/Bidir/BidirBase.Light.cs
@@ -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);
@@ -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);
diff --git a/SeeSharp/Integrators/Bidir/BidirBase.cs b/SeeSharp/Integrators/Bidir/BidirBase.cs
index 8a90b526..d5a237df 100644
--- a/SeeSharp/Integrators/Bidir/BidirBase.cs
+++ b/SeeSharp/Integrators/Bidir/BidirBase.cs
@@ -21,6 +21,12 @@ public abstract partial class BidirBase : Integrator
///
public int NumLightPaths { get; set; } = -1;
+ ///
+ /// 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.
+ ///
+ public int NumShadowRays = 1;
+
///
/// The base seed to generate camera paths.
///
diff --git a/SeeSharp/Integrators/Bidir/CameraStoringVCM.cs b/SeeSharp/Integrators/Bidir/CameraStoringVCM.cs
index 365710a9..4cd96b10 100644
--- a/SeeSharp/Integrators/Bidir/CameraStoringVCM.cs
+++ b/SeeSharp/Integrators/Bidir/CameraStoringVCM.cs
@@ -163,115 +163,121 @@ public virtual void BuildImportonAccel() {
public override void Render(Scene scene) => Render(scene, 0);
- public void Render(Scene scene, int startAtIteration) {
- Scene = scene;
- IsolatedPixel = null;
-
- if (NumLightPaths < 0)
- NumLightPaths = scene.FrameBuffer.Width * scene.FrameBuffer.Height;
+ public void Render(Scene scene, int startAtIteration)
+ {
+ try
+ {
+ Scene = scene;
+ IsolatedPixel = null;
- if (EnableDenoiser)
- DenoiseBuffers = new(scene.FrameBuffer);
+ if (NumLightPaths < 0)
+ NumLightPaths = scene.FrameBuffer.Width * scene.FrameBuffer.Height;
- OnBeforeRender();
-
- if (RenderTechniquePyramid && MaxDepth > 10) {
- Logger.Warning("MaxDepth is set above 10, but a technique pyramid was requested (RenderTechniquePyramid == true). To avoid excessive memory consumption, the RenderTechniquePyramid flag will be ignored.");
- RenderTechniquePyramid = false;
- }
-
- if (RenderTechniquePyramid) {
- TechPyramidRaw = new TechPyramid(scene.FrameBuffer.Width, scene.FrameBuffer.Height,
- minDepth: 1, maxDepth: MaxDepth, merges: EnableMerging);
- TechPyramidWeighted = new TechPyramid(scene.FrameBuffer.Width, scene.FrameBuffer.Height,
- minDepth: 1, maxDepth: MaxDepth, merges: EnableMerging);
- }
+ if (EnableDenoiser)
+ DenoiseBuffers = new(scene.FrameBuffer);
- CameraPaths = new(scene.FrameBuffer.Width * scene.FrameBuffer.Height, Math.Min(MaxDepth + 1, 10));
- photonMap ??= new();
+ OnBeforeRender();
- ProgressBar progressBar = new(prefix: "Rendering...");
- progressBar.Start(NumIterations);
- RenderTimer timer = new();
- Stopwatch lightTracerTimer = new();
- Stopwatch pathTracerTimer = new();
- Stopwatch accelBuildTimer = new();
- ShadingStatCounter.Reset();
- scene.Raytracer.ResetStats();
- for (uint iter = (uint)startAtIteration; iter - startAtIteration < NumIterations; ++iter) {
- long nextIterTime = timer.RenderTime + timer.PerIterationCost;
- if (MaximumRenderTimeMs.HasValue && nextIterTime > MaximumRenderTimeMs.Value) {
- Logger.Log("Maximum render time exhausted.");
- // if (EnableDenoiser) DenoiseBuffers.Denoise();
- progressBar.Terminate();
- break;
+ if (RenderTechniquePyramid && MaxDepth > 10) {
+ Logger.Warning("MaxDepth is set above 10, but a technique pyramid was requested (RenderTechniquePyramid == true). To avoid excessive memory consumption, the RenderTechniquePyramid flag will be ignored.");
+ RenderTechniquePyramid = false;
}
- timer.StartIteration();
-
- scene.FrameBuffer.StartIteration();
- timer.EndFrameBuffer();
-
- OnStartIteration(iter);
- try {
- pathTracerTimer.Start();
- Parallel.For(0, Scene.FrameBuffer.Height, row => {
- for (uint col = 0; col < Scene.FrameBuffer.Width; ++col) {
- uint pixelIndex = (uint)(row * Scene.FrameBuffer.Width + col);
- var rng = new RNG(BaseSeedCamera, pixelIndex, iter);
- TraceCameraPath((uint)row, col, ref rng);
- }
- });
- pathTracerTimer.Stop();
-
- accelBuildTimer.Start();
- if (EnableMerging)
- BuildImportonAccel();
- accelBuildTimer.Stop();
-
- lightTracerTimer.Start();
- TraceLightPaths(iter);
- lightTracerTimer.Stop();
- } catch {
- Logger.Log($"Exception in iteration {iter} out of {NumIterations}.", Verbosity.Error);
- throw;
+ if (RenderTechniquePyramid) {
+ TechPyramidRaw = new TechPyramid(scene.FrameBuffer.Width, scene.FrameBuffer.Height,
+ minDepth: 1, maxDepth: MaxDepth, merges: EnableMerging);
+ TechPyramidWeighted = new TechPyramid(scene.FrameBuffer.Width, scene.FrameBuffer.Height,
+ minDepth: 1, maxDepth: MaxDepth, merges: EnableMerging);
}
- OnEndIteration(iter);
- CameraPaths.Clear();
- timer.EndRender();
- // if (iter == NumIterations - 1 && EnableDenoiser)
- // DenoiseBuffers.Denoise();
+ CameraPaths = new(scene.FrameBuffer.Width * scene.FrameBuffer.Height, Math.Min(MaxDepth + 1, 10));
+ photonMap ??= new();
+
+ ProgressBar progressBar = new(prefix: "Rendering...");
+ progressBar.Start(NumIterations);
+ RenderTimer timer = new();
+ Stopwatch lightTracerTimer = new();
+ Stopwatch pathTracerTimer = new();
+ Stopwatch accelBuildTimer = new();
+ ShadingStatCounter.Reset();
+ scene.Raytracer.ResetStats();
+ for (uint iter = (uint)startAtIteration; iter - startAtIteration < NumIterations; ++iter) {
+ long nextIterTime = timer.RenderTime + timer.PerIterationCost;
+ if (MaximumRenderTimeMs.HasValue && nextIterTime > MaximumRenderTimeMs.Value) {
+ Logger.Log("Maximum render time exhausted.");
+ // if (EnableDenoiser) DenoiseBuffers.Denoise();
+ progressBar.Terminate();
+ break;
+ }
- scene.FrameBuffer.EndIteration();
- timer.EndFrameBuffer();
+ timer.StartIteration();
+
+ scene.FrameBuffer.StartIteration();
+ timer.EndFrameBuffer();
+
+ OnStartIteration(iter);
+ try {
+ pathTracerTimer.Start();
+ Parallel.For(0, Scene.FrameBuffer.Height, row => {
+ for (uint col = 0; col < Scene.FrameBuffer.Width; ++col) {
+ uint pixelIndex = (uint)(row * Scene.FrameBuffer.Width + col);
+ var rng = new RNG(BaseSeedCamera, pixelIndex, iter);
+ TraceCameraPath((uint)row, col, ref rng);
+ }
+ });
+ pathTracerTimer.Stop();
+
+ accelBuildTimer.Start();
+ if (EnableMerging)
+ BuildImportonAccel();
+ accelBuildTimer.Stop();
+
+ lightTracerTimer.Start();
+ TraceLightPaths(iter);
+ lightTracerTimer.Stop();
+ } catch {
+ Logger.Log($"Exception in iteration {iter} out of {NumIterations}.", Verbosity.Error);
+ throw;
+ }
+ OnEndIteration(iter);
+ CameraPaths.Clear();
+ timer.EndRender();
- progressBar.ReportDone(1);
- timer.EndIteration();
- }
+ // if (iter == NumIterations - 1 && EnableDenoiser)
+ // DenoiseBuffers.Denoise();
- scene.FrameBuffer.MetaData["RenderTime"] = timer.RenderTime;
- scene.FrameBuffer.MetaData["FrameBufferTime"] = timer.FrameBufferTime;
- scene.FrameBuffer.MetaData["PathTracerTime"] = pathTracerTimer.ElapsedMilliseconds;
- scene.FrameBuffer.MetaData["LightTracerTime"] = lightTracerTimer.ElapsedMilliseconds;
- scene.FrameBuffer.MetaData["ShadingStats"] = ShadingStatCounter.Current;
- scene.FrameBuffer.MetaData["RayTracerStats"] = scene.Raytracer.Stats;
- scene.FrameBuffer.MetaData["BaseSeed"] = BaseSeed;
+ scene.FrameBuffer.EndIteration();
+ timer.EndFrameBuffer();
- OnAfterRender();
+ progressBar.ReportDone(1);
+ timer.EndIteration();
+ }
- if (RenderTechniquePyramid) {
- TechPyramidRaw.Normalize(1.0f / Scene.FrameBuffer.CurIteration);
- if (!string.IsNullOrEmpty(scene.FrameBuffer.Basename))
- TechPyramidRaw.WriteToFiles(Path.Join(scene.FrameBuffer.Basename, "techs-raw"));
+ scene.FrameBuffer.MetaData["RenderTime"] = timer.RenderTime;
+ scene.FrameBuffer.MetaData["FrameBufferTime"] = timer.FrameBufferTime;
+ scene.FrameBuffer.MetaData["PathTracerTime"] = pathTracerTimer.ElapsedMilliseconds;
+ scene.FrameBuffer.MetaData["LightTracerTime"] = lightTracerTimer.ElapsedMilliseconds;
+ scene.FrameBuffer.MetaData["ShadingStats"] = ShadingStatCounter.Current;
+ scene.FrameBuffer.MetaData["RayTracerStats"] = scene.Raytracer.Stats;
+ scene.FrameBuffer.MetaData["BaseSeed"] = BaseSeed;
- TechPyramidWeighted.Normalize(1.0f / Scene.FrameBuffer.CurIteration);
- if (!string.IsNullOrEmpty(scene.FrameBuffer.Basename))
- TechPyramidWeighted.WriteToFiles(Path.Join(scene.FrameBuffer.Basename, "techs-weighted"));
- }
+ OnAfterRender();
- photonMap.Dispose();
- photonMap = null;
+ if (RenderTechniquePyramid) {
+ TechPyramidRaw.Normalize(1.0f / Scene.FrameBuffer.CurIteration);
+ if (!string.IsNullOrEmpty(scene.FrameBuffer.Basename))
+ TechPyramidRaw.WriteToFiles(Path.Join(scene.FrameBuffer.Basename, "techs-raw"));
+
+ TechPyramidWeighted.Normalize(1.0f / Scene.FrameBuffer.CurIteration);
+ if (!string.IsNullOrEmpty(scene.FrameBuffer.Basename))
+ TechPyramidWeighted.WriteToFiles(Path.Join(scene.FrameBuffer.Basename, "techs-weighted"));
+ }
+ }
+ finally
+ {
+ photonMap?.Dispose();
+ photonMap = null;
+ }
}
///
@@ -413,10 +419,8 @@ public virtual (float MISWeight, RgbColor UnweightedContrib) OnMissCameraPath(Ra
// 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.
+ float pdfNextEvent = NextEventPdf(state.Vertices[^1].Point, ray.Direction);
var pathPdfs = new BidirPathPdfs(stackalloc float[state.Depth], stackalloc float[state.Depth]);
pathPdfs.GatherCameraPdfs(state, state.Depth - 2);
@@ -493,20 +497,29 @@ public virtual (Emitter, SurfaceSample) SampleNextEvent(SurfacePoint from, float
}
///
- /// Computes the pdf used by
+ /// Computes the pdf used by for an area light source. Assumes that `to` is a valid point on an emitter.
///
/// The shading point
/// The point on the light source
/// PDF of next event estimation
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 * NumShadowRays;
- } else { // Emissive object
- var emitter = Scene.QueryEmitter(to);
- return emitter.PdfUniformArea(to) * SelectLightPmf(from, emitter) * (1 - backgroundProbability) * NumShadowRays;
- }
+ var emitter = Scene.QueryEmitter(to);
+ return emitter.PdfUniformArea(to) * SelectLightPmf(from, emitter) * (1 - backgroundProbability) * NumShadowRays;
+ }
+
+ ///
+ /// Computes the pdf used by for a background ray direction.
+ ///
+ /// The shading point
+ /// The direction from the shading point to the background
+ /// PDF of next event estimation
+ public virtual float NextEventPdf(SurfacePoint from, Vector3 direction) {
+ float backgroundProbability = ComputeNextEventBackgroundProbability(/*hit*/);
+ return Scene.Background.DirectionPdf(direction) * backgroundProbability * NumShadowRays;
}
///
@@ -539,8 +552,8 @@ public virtual RgbColor PerformNextEventEstimation(in SurfaceShader shader, in C
return RgbColor.Black; // There is no background
var sample = Scene.Background.SampleDirection(state.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;
diff --git a/SeeSharp/Integrators/Bidir/ClassicBidir.cs b/SeeSharp/Integrators/Bidir/ClassicBidir.cs
index f91eec03..a8efe50e 100644
--- a/SeeSharp/Integrators/Bidir/ClassicBidir.cs
+++ b/SeeSharp/Integrators/Bidir/ClassicBidir.cs
@@ -29,27 +29,9 @@ public class ClassicBidirBase : BidirBase
///
public bool EnableLightTracer = true;
- ///
- /// Number of shadow rays to use for next event estimation along the camera path. If set to zero,
- /// no next event estimation is performed.
- ///
- public int NumShadowRays = 1;
-
TechPyramid techPyramidRaw;
TechPyramid techPyramidWeighted;
- ///
- protected override float NextEventPdf(SurfacePoint from, SurfacePoint to) {
- return base.NextEventPdf(from, to) * NumShadowRays;
- }
-
- ///
- protected override (Emitter, SurfaceSample) SampleNextEvent(SurfacePoint from, ref RNG rng) {
- var (light, sample) = base.SampleNextEvent(from, ref rng);
- sample.Pdf *= NumShadowRays;
- return (light, sample);
- }
-
///
protected override void RegisterSample(RgbColor weight, float misWeight, Pixel pixel,
int cameraPathLength, int lightPathLength, int fullLength) {
diff --git a/SeeSharp/Integrators/Bidir/VertexCacheBidir.cs b/SeeSharp/Integrators/Bidir/VertexCacheBidir.cs
index 3f13e11a..6b0a6a05 100644
--- a/SeeSharp/Integrators/Bidir/VertexCacheBidir.cs
+++ b/SeeSharp/Integrators/Bidir/VertexCacheBidir.cs
@@ -18,11 +18,6 @@ public class VertexCacheBidirBase : BidirBase
public int NumConnections = 1;
- ///
- /// Number of shadow rays to use for next event. Disabled if zero.
- ///
- public int NumShadowRays = 1;
-
///
/// Set to false to disable connections between light vertices and the camera
///
@@ -47,18 +42,6 @@ public class VertexCacheBidirBase : BidirBase
protected VertexSelector vertexSelector;
- ///
- protected override float NextEventPdf(SurfacePoint from, SurfacePoint to) {
- return base.NextEventPdf(from, to) * NumShadowRays;
- }
-
- ///
- protected override (Emitter, SurfaceSample) SampleNextEvent(SurfacePoint from, ref RNG rng) {
- var (light, sample) = base.SampleNextEvent(from, ref rng);
- sample.Pdf *= NumShadowRays;
- return (light, sample);
- }
-
///
protected override (int, int, float) SelectBidirPath(SurfacePoint cameraPoint, Vector3 outDir,
Pixel pixel, ref RNG rng) {
diff --git a/SeeSharp/Integrators/Bidir/VertexConnectionAndMerging.cs b/SeeSharp/Integrators/Bidir/VertexConnectionAndMerging.cs
index 4890f435..d403f097 100644
--- a/SeeSharp/Integrators/Bidir/VertexConnectionAndMerging.cs
+++ b/SeeSharp/Integrators/Bidir/VertexConnectionAndMerging.cs
@@ -240,7 +240,15 @@ public override void Render(Scene scene)
if (photonMap == null)
photonMap = new();
- base.Render(scene);
+ try
+ {
+ base.Render(scene);
+ }
+ finally
+ {
+ photonMap?.Dispose();
+ photonMap = null;
+ }
// Store the technique pyramids
if (RenderTechniquePyramid)
@@ -257,9 +265,6 @@ public override void Render(Scene scene)
Path.Join(scene.FrameBuffer.Basename, "techs-weighted")
);
}
-
- photonMap.Dispose();
- photonMap = null;
}
Stopwatch mergeBuildTimer;
@@ -926,4 +931,4 @@ in CorrelAwareRatios correlRatio
sumReciprocals += nextReciprocal; // Hitting the emitter directly
return sumReciprocals;
}
-}
+}
\ No newline at end of file
diff --git a/SeeSharp/Integrators/PathTracer.cs b/SeeSharp/Integrators/PathTracer.cs
index 4689fe23..0a0b5850 100644
--- a/SeeSharp/Integrators/PathTracer.cs
+++ b/SeeSharp/Integrators/PathTracer.cs
@@ -41,6 +41,13 @@ public class PathTracerBase : Integrator {
TechPyramid techPyramidRaw;
TechPyramid techPyramidWeighted;
+ ThreadLocal totalCamPathLen;
+
+ ///
+ /// Average number of camera path vertices per pixel, of the last finished iteration.
+ ///
+ public float AverageCameraPathLength { get; private set; }
+
protected DenoiseBuffers denoiseBuffers;
///
@@ -220,6 +227,7 @@ public override void Render(Scene scene) {
scene.FrameBuffer.StartIteration();
timer.EndFrameBuffer();
+ totalCamPathLen = new(true);
OnPreIteration(sampleIndex);
Parallel.For(0, scene.FrameBuffer.Height, row => {
for (uint col = 0; col < scene.FrameBuffer.Width; ++col) {
@@ -228,6 +236,9 @@ public override void Render(Scene scene) {
RenderPixel((uint)row, col, ref rng, null);
}
});
+ ulong totalVertices = 0;
+ foreach (ulong v in totalCamPathLen.Values) totalVertices += v;
+ AverageCameraPathLength = totalVertices / (float)(scene.FrameBuffer.Width * scene.FrameBuffer.Height);
OnPostIteration(sampleIndex);
timer.EndRender();
@@ -305,6 +316,7 @@ protected virtual RgbColor RenderPixel(uint row, uint col, ref RNG rng, PathGrap
protected virtual RgbColor EstimateIncidentRadiance(Ray ray, ref PathState state, PathGraphNode graphVertex = null) {
RgbColor radianceEstimate = RgbColor.Black;
+ ulong numVertices = 0;
while (state.Depth <= MaxDepth) {
var hit = scene.Raytracer.Trace(ray);
@@ -320,6 +332,7 @@ protected virtual RgbColor EstimateIncidentRadiance(Ray ray, ref PathState state
break;
}
+ numVertices++;
OnHit(ray, hit, ref state);
SurfaceShader shader = new(hit, -ray.Direction, false);
@@ -369,6 +382,8 @@ protected virtual RgbColor EstimateIncidentRadiance(Ray ray, ref PathState state
state.PreviousSurvivalProbability = survivalProb;
}
+ totalCamPathLen?.Value += numVertices;
+
return radianceEstimate;
}