public virtual RgbColor StartFromEmitter(EmitterSample emitterSample, RgbColor initialWeight) { isOnLightSubpath = true; Ray ray = Raytracer.SpawnRay(emitterSample.Point, emitterSample.Direction); return(ContinueWalk(ray, emitterSample.Point, emitterSample.Pdf, initialWeight, 1)); }
public virtual RgbColor StartFromBackground(Ray ray, RgbColor initialWeight, float pdf) { isOnLightSubpath = true; // Find the first actual hitpoint on scene geometry var hit = scene.Raytracer.Trace(ray); if (!hit) { return(OnInvalidHit(ray, pdf, initialWeight, 1)); } // Sample the next direction (required to know the reverse pdf) var(pdfNext, pdfReverse, weight, direction) = SampleNextDirection(hit, ray, initialWeight, 1); // Both pdfs have unit sr-1 float pdfFromAncestor = pdf; float pdfToAncestor = pdfReverse; RgbColor estimate = OnHit(ray, hit, pdfFromAncestor, initialWeight, 1, 1.0f); OnContinue(pdfToAncestor, 1); // Terminate if the maximum depth has been reached if (maxDepth <= 1) { return(estimate); } // Terminate absorbed paths and invalid samples if (pdfNext == 0 || weight == RgbColor.Black) { return(estimate); } // Continue the path with the next ray ray = Raytracer.SpawnRay(hit, direction); return(estimate + ContinueWalk(ray, hit, pdfNext, initialWeight * weight, 2)); }
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); }