Skip to content

Commit

Permalink
fewer recomputations
Browse files Browse the repository at this point in the history
  • Loading branch information
pgrit committed Jan 18, 2024
1 parent f3bba1e commit 608d664
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 106 deletions.
57 changes: 27 additions & 30 deletions SeeSharp/Integrators/PathTracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ protected virtual void OnHit(in Ray ray, in Hit hit, PathState state) { }
/// <summary>
/// Called whenever direct illumination was estimated via next event estimation
/// </summary>
protected virtual void OnNextEventResult(in Ray ray, in SurfacePoint point, PathState state,
protected virtual void OnNextEventResult(in SurfaceShader shader, PathState state,
float misWeight, RgbColor estimate) { }

/// <summary>
Expand Down Expand Up @@ -287,8 +287,10 @@ protected virtual RgbColor EstimateIncidentRadiance(Ray ray, PathState state) {

OnHit(ray, hit, state);

SurfaceShader shader = new(hit, -ray.Direction, false);

if (state.Depth == 1 && EnableDenoiser) {
var albedo = ((SurfacePoint)hit).Material.GetScatterStrength(hit);
var albedo = shader.GetScatterStrength();
denoiseBuffers.LogPrimaryHit(state.Pixel, albedo, hit.ShadingNormal);
}

Expand All @@ -307,14 +309,14 @@ protected virtual RgbColor EstimateIncidentRadiance(Ray ray, PathState state) {
if (state.Depth + 1 >= MinDepth) {
RgbColor nextEventContrib = RgbColor.Black;
for (int i = 0; i < NumShadowRays; ++i) {
nextEventContrib += PerformBackgroundNextEvent(ray, hit, state);
nextEventContrib += PerformNextEventEstimation(ray, hit, state);
nextEventContrib += PerformBackgroundNextEvent(shader, state);
nextEventContrib += PerformNextEventEstimation(shader, state);
}
radianceEstimate += state.PrefixWeight * nextEventContrib / survivalProb;
}

// Sample a direction to continue the random walk
(ray, float bsdfPdf, var bsdfSampleWeight, var approxReflectance) = SampleDirection(ray, hit, state);
(ray, float bsdfPdf, var bsdfSampleWeight, var approxReflectance) = SampleDirection(shader, state);
if (bsdfPdf == 0 || bsdfSampleWeight == RgbColor.Black)
break;

Expand Down Expand Up @@ -347,8 +349,7 @@ protected virtual RgbColor OnBackgroundHit(in Ray ray, PathState state) {
return misWeight * emission;
}

protected virtual RgbColor OnLightHit(in Ray ray, in SurfacePoint hit, PathState state,
Emitter light) {
protected virtual RgbColor OnLightHit(in Ray ray, in SurfacePoint hit, PathState state, Emitter light) {
float misWeight = 1.0f;
float pdfNextEvt;
if (state.Depth > 1) { // directly visible emitters are not explicitely connected
Expand All @@ -369,15 +370,14 @@ protected virtual RgbColor OnLightHit(in Ray ray, in SurfacePoint hit, PathState
return misWeight * emission;
}

protected virtual RgbColor PerformBackgroundNextEvent(in Ray ray, in SurfacePoint hit, PathState state) {
protected virtual RgbColor PerformBackgroundNextEvent(in SurfaceShader shader, PathState state) {
if (scene.Background == null)
return RgbColor.Black; // There is no background

var sample = scene.Background.SampleDirection(state.Rng.NextFloat2D());
if (scene.Raytracer.LeavesScene(hit, sample.Direction)) {
var bsdfTimesCosine = hit.Material.EvaluateWithCosine(
hit, -ray.Direction, sample.Direction, false);
var pdfBsdf = DirectionPdf(hit, -ray.Direction, sample.Direction, state);
if (scene.Raytracer.LeavesScene(shader.Point, sample.Direction)) {
var bsdfTimesCosine = shader.EvaluateWithCosine(sample.Direction);
var pdfBsdf = DirectionPdf(shader, sample.Direction, state);

// Prevent NaN / Inf
if (pdfBsdf == 0 || sample.Pdf == 0)
Expand All @@ -391,13 +391,13 @@ protected virtual RgbColor PerformBackgroundNextEvent(in Ray ray, in SurfacePoin
Debug.Assert(float.IsFinite(misWeight));

RegisterSample(state.Pixel, contrib * state.PrefixWeight, misWeight, state.Depth + 1, true);
OnNextEventResult(ray, hit, state, misWeight, contrib);
OnNextEventResult(shader, state, misWeight, contrib);
return misWeight * contrib;
}
return RgbColor.Black;
}

protected virtual RgbColor PerformNextEventEstimation(in Ray ray, in SurfacePoint hit, PathState state) {
protected virtual RgbColor PerformNextEventEstimation(in SurfaceShader shader, PathState state) {
if (scene.Emitters.Count == 0)
return RgbColor.Black;

Expand All @@ -408,19 +408,19 @@ protected virtual RgbColor PerformNextEventEstimation(in Ray ray, in SurfacePoin

// Sample a point on the light source
var lightSample = light.SampleUniformArea(state.Rng.NextFloat2D());
Vector3 lightToSurface = Vector3.Normalize(hit.Position - lightSample.Point.Position);
Vector3 lightToSurface = Vector3.Normalize(shader.Point.Position - lightSample.Point.Position);

if (!scene.Raytracer.IsOccluded(hit, lightSample.Point)) {
if (!scene.Raytracer.IsOccluded(shader.Point, lightSample.Point)) {
var emission = light.EmittedRadiance(lightSample.Point, lightToSurface);

// Compute the jacobian for surface area -> solid angle
// (Inverse of the jacobian for solid angle pdf -> surface area pdf)
float jacobian = SampleWarp.SurfaceAreaToSolidAngle(hit, lightSample.Point);
var bsdfCos = hit.Material.EvaluateWithCosine(hit, -ray.Direction, -lightToSurface, false);
float jacobian = SampleWarp.SurfaceAreaToSolidAngle(shader.Point, lightSample.Point);
var bsdfCos = shader.EvaluateWithCosine(-lightToSurface);

// Compute surface area PDFs
float pdfNextEvt = lightSample.Pdf * lightSelectProb * NumShadowRays;
float pdfBsdfSolidAngle = DirectionPdf(hit, -ray.Direction, -lightToSurface, state);
float pdfBsdfSolidAngle = DirectionPdf(shader, -lightToSurface, state);
float pdfBsdf = pdfBsdfSolidAngle * jacobian;

// Avoid Inf / NaN
Expand All @@ -435,7 +435,7 @@ protected virtual RgbColor PerformNextEventEstimation(in Ray ray, in SurfacePoin
var pdf = lightSample.Pdf / jacobian * lightSelectProb * NumShadowRays;
RegisterSample(state.Pixel, emission / pdf * bsdfCos * state.PrefixWeight, misWeight,
state.Depth + 1, true);
OnNextEventResult(ray, hit, state, misWeight, emission / pdf * bsdfCos);
OnNextEventResult(shader, state, misWeight, emission / pdf * bsdfCos);
return misWeight * emission / pdf * bsdfCos;
}
return RgbColor.Black;
Expand All @@ -444,20 +444,18 @@ protected virtual RgbColor PerformNextEventEstimation(in Ray ray, in SurfacePoin
/// <summary>
/// Computes the solid angle pdf that <see cref="SampleDirection"/> is using
/// </summary>
/// <param name="hit">The surface point</param>
/// <param name="outDir">Direction the path was coming from</param>
/// <param name="shader">Shading context at the hit point where the path is resumed</param>
/// <param name="sampledDir">Direction that could have been sampled</param>
/// <param name="state">The current state of the path</param>
/// <returns>Pdf of sampling "sampledDir" when coming from "outDir".</returns>
protected virtual float DirectionPdf(in SurfacePoint hit, Vector3 outDir, Vector3 sampledDir,
protected virtual float DirectionPdf(in SurfaceShader shader, Vector3 sampledDir,
PathState state)
=> hit.Material.Pdf(hit, outDir, sampledDir, false).Item1;
=> shader.Pdf(sampledDir).Item1;

/// <summary>
/// Samples a direction to continue the path
/// </summary>
/// <param name="ray">Previous ray</param>
/// <param name="hit">Current hit point</param>
/// <param name="shader">Shading context at the hit point where the path is resumed</param>
/// <param name="state">Current state of the path</param>
/// <returns>
/// The next ray, its pdf, and the contribution (bsdf * cosine / pdf).
Expand All @@ -466,11 +464,10 @@ protected virtual float DirectionPdf(in SurfacePoint hit, Vector3 outDir, Vector
/// BSDF importance sampling is perfect, this is BSDF_value / BSDF_pdf. This is returned here so the
/// integrator does not have to recompute the BSDF pdf. Useful for path guiding applications.
/// </returns>
protected virtual (Ray, float, RgbColor, RgbColor) SampleDirection(in Ray ray, in SurfacePoint hit,
PathState state) {
protected virtual (Ray, float, RgbColor, RgbColor) SampleDirection(in SurfaceShader shader, PathState state) {
var primary = state.Rng.NextFloat2D();
var bsdfSample = hit.Material.Sample(hit, -ray.Direction, false, primary);
var bsdfRay = Raytracer.SpawnRay(hit, bsdfSample.Direction);
var bsdfSample = shader.Sample(primary);
var bsdfRay = Raytracer.SpawnRay(shader.Point, bsdfSample.Direction);
return (bsdfRay, bsdfSample.Pdf, bsdfSample.Weight, bsdfSample.Weight);
}
}
55 changes: 20 additions & 35 deletions SeeSharp/Shading/Materials/DiffuseMaterial.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using SeeSharp.Shading.Bsdfs;

namespace SeeSharp.Shading.Materials;
namespace SeeSharp.Shading.Materials;

/// <summary>
/// A simple Lambertian material that either only reflects or only transmits.
Expand Down Expand Up @@ -40,64 +38,55 @@ public override RgbColor GetScatterStrength(in SurfacePoint hit)
=> MaterialParameters.BaseColor.Lookup(hit.TextureCoordinates);

/// <returns>1/pi * baseColor, or zero if the directions are not in the right hemispheres</returns>
public override RgbColor Evaluate(in SurfacePoint hit, Vector3 outDir, Vector3 inDir, bool isOnLightSubpath) {
public override RgbColor Evaluate(in ShadingContext context, Vector3 inDir) {
ShadingStats.NotifyEvaluate();

bool shouldReflect = ShouldReflect(hit, outDir, inDir);

// Transform directions to shading space and normalize
var normal = hit.ShadingNormal;
outDir = WorldToShading(normal, outDir);
inDir = WorldToShading(normal, inDir);
bool shouldReflect = ShouldReflect(context.Point, context.OutDirWorld, inDir);
inDir = context.WorldToShading(inDir);

var baseColor = MaterialParameters.BaseColor.Lookup(hit.TextureCoordinates);
var baseColor = MaterialParameters.BaseColor.Lookup(context.Point.TextureCoordinates);
if (MaterialParameters.Transmitter && !shouldReflect) {
return new DiffuseTransmission(baseColor).Evaluate(outDir, inDir, isOnLightSubpath);
return new DiffuseTransmission(baseColor).Evaluate(context.OutDir, inDir, context.IsOnLightSubpath);
} else if (shouldReflect) {
return new DiffuseBsdf(baseColor).Evaluate(outDir, inDir, isOnLightSubpath);
return new DiffuseBsdf(baseColor).Evaluate(context.OutDir, inDir, context.IsOnLightSubpath);
} else
return RgbColor.Black;
}

/// <summary>
/// Importance samples the cosine hemisphere
/// </summary>
public override BsdfSample Sample(in SurfacePoint hit, Vector3 outDir, bool isOnLightSubpath,
Vector2 primarySample, ref ComponentWeights componentWeights) {
public override BsdfSample Sample(in ShadingContext context, Vector2 primarySample, ref ComponentWeights componentWeights) {
ShadingStats.NotifySample();

var normal = hit.ShadingNormal;
outDir = WorldToShading(normal, outDir);

var baseColor = MaterialParameters.BaseColor.Lookup(hit.TextureCoordinates);
var baseColor = MaterialParameters.BaseColor.Lookup(context.Point.TextureCoordinates);
Vector3? sample;
if (MaterialParameters.Transmitter) {
// Pick either transmission or reflection
if (primarySample.X < 0.5f) {
var remapped = primarySample;
remapped.X = Math.Min(primarySample.X / 0.5f, 1);
sample = new DiffuseTransmission(baseColor).Sample(outDir, isOnLightSubpath, remapped);
sample = new DiffuseTransmission(baseColor).Sample(context.OutDir, context.IsOnLightSubpath, remapped);
} else {
var remapped = primarySample;
remapped.X = Math.Min((primarySample.X - 0.5f) / 0.5f, 1);
sample = new DiffuseBsdf(baseColor).Sample(outDir, isOnLightSubpath, remapped);
sample = new DiffuseBsdf(baseColor).Sample(context.OutDir, context.IsOnLightSubpath, remapped);
}
} else {
sample = new DiffuseBsdf(baseColor).Sample(outDir, isOnLightSubpath, primarySample);
sample = new DiffuseBsdf(baseColor).Sample(context.OutDir, context.IsOnLightSubpath, primarySample);
}

// Terminate if no valid direction was sampled
if (!sample.HasValue)
return BsdfSample.Invalid;

var sampledDir = ShadingToWorld(normal, sample.Value);
var sampledDir = ShadingToWorld(context.Normal, sample.Value);

// Evaluate all components
var outWorld = ShadingToWorld(normal, outDir);
var value = EvaluateWithCosine(hit, outWorld, sampledDir, isOnLightSubpath);
var value = EvaluateWithCosine(context, sampledDir);

// Compute all pdfs
var (pdfFwd, pdfRev) = Pdf(hit, outWorld, sampledDir, isOnLightSubpath, ref componentWeights);
var (pdfFwd, pdfRev) = Pdf(context, sampledDir, ref componentWeights);
if (pdfFwd == 0)
return BsdfSample.Invalid;

Expand All @@ -111,19 +100,15 @@ public override BsdfSample Sample(in SurfacePoint hit, Vector3 outDir, bool isOn
}

/// <returns>PDF value used by <see cref="Sample"/></returns>
public override (float, float) Pdf(in SurfacePoint hit, Vector3 outDir, Vector3 inDir, bool isOnLightSubpath,
ref ComponentWeights componentWeights) {
public override (float, float) Pdf(in ShadingContext context, Vector3 inDir, ref ComponentWeights componentWeights) {
ShadingStats.NotifyPdfCompute();

// Transform directions to shading space and normalize
var normal = hit.ShadingNormal;
outDir = WorldToShading(normal, outDir);
inDir = WorldToShading(normal, inDir);
inDir = context.WorldToShading(inDir);

var baseColor = MaterialParameters.BaseColor.Lookup(hit.TextureCoordinates);
var reflectPdf = new DiffuseBsdf(baseColor).Pdf(outDir, inDir, isOnLightSubpath);
var baseColor = MaterialParameters.BaseColor.Lookup(context.Point.TextureCoordinates);
var reflectPdf = new DiffuseBsdf(baseColor).Pdf(context.OutDir, inDir, context.IsOnLightSubpath);
if (MaterialParameters.Transmitter) {
var transmitPdf = new DiffuseTransmission(baseColor).Pdf(outDir, inDir, isOnLightSubpath);
var transmitPdf = new DiffuseTransmission(baseColor).Pdf(context.OutDir, inDir, context.IsOnLightSubpath);
float pdfFwd = (reflectPdf.Item1 + transmitPdf.Item1) * 0.5f;
float pdfRev = (reflectPdf.Item2 + transmitPdf.Item2) * 0.5f;

Expand Down
Loading

0 comments on commit 608d664

Please sign in to comment.