pbrt-v4/src/pbrt/cpu/integrators.cpp
2020-08-17 16:17:05 -07:00

3281 lines
137 KiB
C++

// pbrt is Copyright(c) 1998-2020 Matt Pharr, Wenzel Jakob, and Greg Humphreys.
// The pbrt source code is licensed under the Apache License, Version 2.0.
// SPDX: Apache-2.0
#include <pbrt/cpu/integrators.h>
#include <pbrt/bsdf.h>
#include <pbrt/bssrdf.h>
#include <pbrt/cameras.h>
#include <pbrt/film.h>
#include <pbrt/filters.h>
#include <pbrt/interaction.h>
#include <pbrt/lights.h>
#include <pbrt/media.h>
#include <pbrt/options.h>
#include <pbrt/paramdict.h>
#include <pbrt/samplers.h>
#include <pbrt/shapes.h>
#include <pbrt/util/bluenoise.h>
#include <pbrt/util/check.h>
#include <pbrt/util/color.h>
#include <pbrt/util/colorspace.h>
#include <pbrt/util/display.h>
#include <pbrt/util/error.h>
#include <pbrt/util/file.h>
#include <pbrt/util/hash.h>
#include <pbrt/util/image.h>
#include <pbrt/util/lowdiscrepancy.h>
#include <pbrt/util/math.h>
#include <pbrt/util/memory.h>
#include <pbrt/util/parallel.h>
#include <pbrt/util/print.h>
#include <pbrt/util/progressreporter.h>
#include <pbrt/util/pstd.h>
#include <pbrt/util/rng.h>
#include <pbrt/util/sampling.h>
#include <pbrt/util/spectrum.h>
#include <pbrt/util/stats.h>
#include <pbrt/util/string.h>
namespace pbrt {
STAT_COUNTER("Integrator/Camera rays traced", nCameraRays);
// RandomWalkIntegrator Method Definitions
std::unique_ptr<RandomWalkIntegrator> RandomWalkIntegrator::Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc) {
int maxDepth = parameters.GetOneInt("maxdepth", 5);
return std::make_unique<RandomWalkIntegrator>(maxDepth, camera, sampler, aggregate,
lights);
}
std::string RandomWalkIntegrator::ToString() const {
return StringPrintf("[ RandomWalkIntegrator maxDepth: %d ]", maxDepth);
}
SampledSpectrum RandomWalkIntegrator::Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler,
ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const {
return RandomWalk(ray, lambda, sampler, scratchBuffer, 0);
}
SampledSpectrum RandomWalkIntegrator::RandomWalk(RayDifferential ray,
SampledWavelengths &lambda,
SamplerHandle sampler,
ScratchBuffer &scratchBuffer,
int depth) const {
SampledSpectrum L(0.f);
// Intersect ray with scene and return if no intersection
pstd::optional<ShapeIntersection> si = Intersect(ray);
if (!si) {
// Return emitted light from infinite light sources
for (LightHandle light : infiniteLights)
L += light.Le(ray, lambda);
return L;
}
SurfaceInteraction &isect = si->intr;
// Get emitted radiance at surface intersection
L = isect.Le(-ray.d, lambda);
// Terminate random walk if maximum depth has been reached
if (depth == maxDepth)
return L;
// Compute BSDF at random walk intersection point
BSDF bsdf = isect.GetBSDF(ray, lambda, camera, scratchBuffer, sampler);
if (!bsdf)
return L;
// Randomly sample direction leaving surface for random walk
Point2f u = sampler.Get2D();
Vector3f wi = SampleUniformSphere(u);
// Evaluate BSDF at surface for sampled direction
Vector3f wo = -ray.d;
SampledSpectrum beta = bsdf.f(wo, wi) * AbsDot(wi, isect.shading.n) / (1 / (4 * Pi));
if (!beta)
return L;
// Recursively trace ray to estimate incident radiance at surface
ray = isect.SpawnRay(wi);
return L + beta * RandomWalk(ray, lambda, sampler, scratchBuffer, depth + 1);
}
// Integrator Method Definitions
Integrator::~Integrator() {}
// ImageTileIntegrator Method Definitions
void ImageTileIntegrator::Render() {
// Handle debugStart, if set
if (!Options->debugStart.empty()) {
pstd::optional<std::vector<int>> c = SplitStringToInts(Options->debugStart, ',');
if (!c)
ErrorExit("Didn't find integer values after --debugstart: %s",
Options->debugStart);
if (c->size() != 3)
ErrorExit("Didn't find three integer values after --debugstart: %s",
Options->debugStart);
Point2i pPixel((*c)[0], (*c)[1]);
int sampleIndex = (*c)[2];
ScratchBuffer scratchBuffer(65536);
SamplerHandle tileSampler = samplerPrototype.Clone(1, Allocator())[0];
tileSampler.StartPixelSample(pPixel, sampleIndex);
EvaluatePixelSample(pPixel, sampleIndex, tileSampler, scratchBuffer);
return;
}
thread_local Point2i threadPixel;
thread_local int threadSampleIndex;
CheckCallbackScope _([&]() {
return StringPrintf("Rendering failed at pixel (%d, %d) sample %d. Debug with "
"\"--debugstart %d,%d,%d\"\n",
threadPixel.x, threadPixel.y, threadSampleIndex,
threadPixel.x, threadPixel.y, threadSampleIndex);
});
// Declare common variables for rendering image in tiles
Bounds2i pixelBounds = camera.GetFilm().PixelBounds();
int spp = samplerPrototype.SamplesPerPixel();
int startWave = 0, endWave = 1, waveDelta = 1;
std::vector<ScratchBuffer> scratchBuffers;
for (int i = 0; i < MaxThreadIndex(); ++i)
scratchBuffers.push_back(ScratchBuffer(65536));
std::vector<SamplerHandle> samplers =
samplerPrototype.Clone(MaxThreadIndex(), Allocator());
ProgressReporter progress(int64_t(spp) * pixelBounds.Area(), "Rendering",
Options->quiet);
if (Options->recordPixelStatistics)
StatsEnablePixelStats(pixelBounds,
RemoveExtension(camera.GetFilm().GetFilename()));
// Handle MSE referene image, if provided
pstd::optional<Image> referenceImage;
FILE *mseOutFile = nullptr;
if (!Options->mseReferenceImage.empty()) {
auto mse = Image::Read(Options->mseReferenceImage);
referenceImage = mse.image;
Bounds2i msePixelBounds =
mse.metadata.pixelBounds
? *mse.metadata.pixelBounds
: Bounds2i(Point2i(0, 0), referenceImage->Resolution());
if (!Inside(pixelBounds, msePixelBounds))
ErrorExit("Output image pixel bounds %s aren't inside the MSE "
"image's pixel bounds %s.",
pixelBounds, msePixelBounds);
// Transform the pixelBounds of the image we're rendering to the
// coordinate system with msePixelBounds.pMin at the origin, which
// in turn gives us the section of the MSE image to crop. (This is
// complicated by the fact that Image doesn't support pixel
// bounds...)
Bounds2i cropBounds(Point2i(pixelBounds.pMin - msePixelBounds.pMin),
Point2i(pixelBounds.pMax - msePixelBounds.pMin));
*referenceImage = referenceImage->Crop(cropBounds);
CHECK_EQ(referenceImage->Resolution(), Point2i(pixelBounds.Diagonal()));
mseOutFile = fopen(Options->mseReferenceOutput.c_str(), "w");
if (!mseOutFile)
ErrorExit("%s: %s", Options->mseReferenceOutput, ErrorString());
}
// Connect to display server if needed
if (!Options->displayServer.empty()) {
FilmHandle film = camera.GetFilm();
DisplayDynamic(film.GetFilename(), Point2i(pixelBounds.Diagonal()),
{"R", "G", "B"},
[=](Bounds2i b, pstd::span<pstd::span<Float>> displayValue) {
int index = 0;
for (Point2i p : b) {
RGB rgb = film.GetPixelRGB(pixelBounds.pMin + p);
for (int c = 0; c < 3; ++c)
displayValue[c][index] = rgb[c];
++index;
}
});
}
while (startWave < spp) {
// Render image tiles in parallel
ParallelFor2D(pixelBounds, [&](Bounds2i tileBounds) {
// Render image tile given by _tileBounds_
ScratchBuffer &scratchBuffer = scratchBuffers[ThreadIndex];
SamplerHandle &sampler = samplers[ThreadIndex];
VLOG(1, "Starting image tile %s startWave %d, endWave %d", tileBounds,
startWave, endWave);
for (Point2i pPixel : tileBounds) {
StatsReportPixelStart(pPixel);
threadPixel = pPixel;
// Render samples in pixel _pPixel_
for (int sampleIndex = startWave; sampleIndex < endWave; ++sampleIndex) {
threadSampleIndex = sampleIndex;
sampler.StartPixelSample(pPixel, sampleIndex);
EvaluatePixelSample(pPixel, sampleIndex, sampler, scratchBuffer);
scratchBuffer.Reset();
}
StatsReportPixelEnd(pPixel);
}
VLOG(1, "Finished image tile %s", tileBounds);
progress.Update((endWave - startWave) * tileBounds.Area());
});
// Update start and end wave
startWave = endWave;
endWave = std::min(spp, endWave + waveDelta);
if (!referenceImage)
waveDelta = std::min(2 * waveDelta, 64);
// Write current image to disk
LOG_VERBOSE("Writing image with spp = %d", startWave);
ImageMetadata metadata;
metadata.renderTimeSeconds = progress.ElapsedSeconds();
metadata.samplesPerPixel = startWave;
if (referenceImage) {
ImageMetadata filmMetadata;
Image filmImage = camera.GetFilm().GetImage(&filmMetadata, 1.f / startWave);
ImageChannelValues mse =
filmImage.MSE(filmImage.AllChannelsDesc(), *referenceImage);
fprintf(mseOutFile, "%d, %.9g\n", startWave, mse.Average());
metadata.MSE = mse.Average();
fflush(mseOutFile);
}
camera.InitMetadata(&metadata);
camera.GetFilm().WriteImage(metadata, 1.0f / startWave);
}
if (mseOutFile)
fclose(mseOutFile);
progress.Done();
LOG_VERBOSE("Rendering finished");
}
// RayIntegrator Method Definitions
void RayIntegrator::EvaluatePixelSample(const Point2i &pPixel, int sampleIndex,
SamplerHandle sampler,
ScratchBuffer &scratchBuffer) {
// Initialize _CameraSample_ for current sample
FilterHandle filter = camera.GetFilm().GetFilter();
CameraSample cameraSample = GetCameraSample(sampler, pPixel, filter);
// Sample wavelengths for the ray
Float lu = RadicalInverse(1, sampleIndex) + BlueNoise(47, pPixel.x, pPixel.y);
if (lu >= 1)
lu -= 1;
if (Options->disableWavelengthJitter)
lu = 0.5;
SampledWavelengths lambda = camera.GetFilm().SampleWavelengths(lu);
// Generate camera ray for current sample
pstd::optional<CameraRayDifferential> cameraRay =
camera.GenerateRayDifferential(cameraSample, lambda);
SampledSpectrum L(0.);
VisibleSurface visibleSurface;
bool initializeVisibleSurface = camera.GetFilm().UsesVisibleSurface();
// Trace _cameraRay_ if valid
if (cameraRay) {
// Double check that the ray's direction is normalized.
DCHECK_GT(Length(cameraRay->ray.d), .999f);
DCHECK_LT(Length(cameraRay->ray.d), 1.001f);
// Scale camera ray differentials based on sampling rate
Float rayDiffScale =
std::max<Float>(.125, 1 / std::sqrt((Float)sampler.SamplesPerPixel()));
if (!Options->disablePixelJitter)
cameraRay->ray.ScaleDifferentials(rayDiffScale);
++nCameraRays;
// Evaluate radiance along camera ray
L = cameraRay->weight * Li(cameraRay->ray, lambda, sampler, scratchBuffer,
initializeVisibleSurface ? &visibleSurface : nullptr);
// Issue warning if unexpected radiance value is returned
if (L.HasNaNs()) {
LOG_ERROR("Not-a-number radiance value returned for pixel (%d, "
"%d), sample %d. "
"Setting to black.",
pPixel.x, pPixel.y, sampleIndex);
L = SampledSpectrum(0.f);
} else if (std::isinf(L.y(lambda))) {
LOG_ERROR("Infinite radiance value returned for pixel (%d, %d), "
"sample %d. "
"Setting to black.",
pPixel.x, pPixel.y, sampleIndex);
L = SampledSpectrum(0.f);
}
if (cameraRay)
VLOG(2, "Camera sample: %s -> ray %s -> L = %s, visibleSurface %s",
cameraSample, cameraRay->ray, L,
(visibleSurface ? visibleSurface.ToString() : "(none)"));
else
VLOG(2, "Camera sample: %s -> no ray generated", cameraSample);
}
// Add camera ray's contribution to image
camera.GetFilm().AddSample(pPixel, L, lambda, &visibleSurface, cameraSample.weight);
}
// Integrator Utility Functions
STAT_COUNTER("Intersections/Regular ray intersection tests", nIntersectionTests);
STAT_COUNTER("Intersections/Shadow ray intersection tests", nShadowTests);
// Integrator Method Definitions
pstd::optional<ShapeIntersection> Integrator::Intersect(const Ray &ray,
Float tMax) const {
++nIntersectionTests;
DCHECK_NE(ray.d, Vector3f(0, 0, 0));
if (aggregate)
return aggregate.Intersect(ray, tMax);
else
return {};
}
bool Integrator::IntersectP(const Ray &ray, Float tMax) const {
++nShadowTests;
DCHECK_NE(ray.d, Vector3f(0, 0, 0));
if (aggregate)
return aggregate.IntersectP(ray, tMax);
else
return false;
}
std::string Integrator::ToString() const {
std::string s = StringPrintf("[ Scene aggregate: %s sceneBounds: %s lights[%d]: [ ",
aggregate, sceneBounds, lights.size());
for (const auto &l : lights)
s += StringPrintf("%s, ", l.ToString());
s += StringPrintf("] infiniteLights[%d]: [ ", infiniteLights.size());
for (const auto &l : infiniteLights)
s += StringPrintf("%s, ", l.ToString());
return s + " ]";
}
SampledSpectrum Integrator::Tr(const Interaction &p0, const Interaction &p1,
const SampledWavelengths &lambda, RNG &rng) const {
auto rescale = [](SampledSpectrum &Tr, SampledSpectrum &pdf) {
if (Tr.MaxComponentValue() > 0x1p24f || pdf.MaxComponentValue() > 0x1p24f) {
Tr /= 0x1p24f;
pdf /= 0x1p24f;
}
};
// :-(
Ray ray =
p0.IsSurfaceInteraction() ? p0.AsSurface().SpawnRayTo(p1) : p0.SpawnRayTo(p1);
SampledSpectrum Tr(1.f), pdf(1.f);
if (LengthSquared(ray.d) == 0)
return Tr;
while (true) {
pstd::optional<ShapeIntersection> si = Intersect(ray, 1 - ShadowEpsilon);
// Handle opaque surface along ray's path
if (si && si->intr.material)
return SampledSpectrum(0.0f);
// Update transmittance for current ray segment
if (ray.medium != nullptr) {
Point3f pExit = ray(si ? si->tHit : (1 - ShadowEpsilon));
ray.d = pExit - ray.o;
ray.medium.SampleTmaj(ray, 1.f, rng, lambda,
[&](const MediumSample &ms) -> bool {
const SampledSpectrum &Tmaj = ms.Tmaj;
if (!ms.intr) {
Tr *= Tmaj;
return false;
}
const MediumInteraction &intr = *ms.intr;
SampledSpectrum sigma_n = intr.sigma_n();
// ratio-tracking: only evaluate null scattering
Tr *= Tmaj * sigma_n;
pdf *= Tmaj * intr.sigma_maj;
if (!Tr)
return false;
rescale(Tr, pdf);
return true;
});
}
// Generate next ray segment or return final transmittance
if (!si)
break;
ray = si->intr.SpawnRayTo(p1);
}
VLOG(2, "Tr from %s to %s = %s", p0.pi, p1.pi, Tr);
return Tr / pdf.Average();
}
// SimplePathIntegrator Method Definitions
SimplePathIntegrator::SimplePathIntegrator(int maxDepth, bool sampleLights,
bool sampleBSDF, CameraHandle camera,
SamplerHandle sampler,
PrimitiveHandle aggregate,
std::vector<LightHandle> lights)
: RayIntegrator(camera, sampler, aggregate, lights),
maxDepth(maxDepth),
sampleLights(sampleLights),
sampleBSDF(sampleBSDF),
lightSampler(lights, Allocator()) {}
SampledSpectrum SimplePathIntegrator::Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler,
ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const {
SampledSpectrum L(0.f), beta(1.f);
bool specularBounce = true;
int depth = 0;
while (beta) {
// Find next _SimplePathIntegrator_ path vertex and accumulate contribution
// Intersect _ray_ with scene
pstd::optional<ShapeIntersection> si = Intersect(ray);
// Account for infinite lights if ray has no intersection
if (!si) {
if (!sampleLights || specularBounce)
for (const auto &light : infiniteLights)
L += beta * light.Le(ray, lambda);
break;
}
// Account for emsisive surface if light wasn't sampled
SurfaceInteraction &isect = si->intr;
if (!sampleLights || specularBounce)
L += beta * isect.Le(-ray.d, lambda);
// End path if maximum depth reached
if (depth++ == maxDepth)
break;
// Compute scattering functions and skip over medium boundaries
BSDF bsdf = isect.GetBSDF(ray, lambda, camera, scratchBuffer, sampler);
if (!bsdf) {
isect.SkipIntersection(&ray, si->tHit);
continue;
}
// Sample direct illumination if _sampleLights_ is true
Vector3f wo = -ray.d;
if (sampleLights) {
pstd::optional<SampledLight> sampledLight =
lightSampler.Sample(sampler.Get1D());
if (sampledLight) {
// Sample point on _sampledLight_ to estimate direct illumination
Point2f uLight = sampler.Get2D();
LightLiSample ls = sampledLight->light.SampleLi(isect, uLight, lambda);
if (ls && ls.L) {
// Evaluate BSDF for light and possibly add scattered radiance
Vector3f wi = ls.wi;
SampledSpectrum f = bsdf.f(wo, wi) * AbsDot(wi, isect.shading.n);
if (f && Unoccluded(isect, ls.pLight))
L += beta * f * ls.L / (sampledLight->pdf * ls.pdf);
}
}
}
// Sample outoing direction at intersection to continue path
if (sampleBSDF) {
// Sample BSDF for new path direction
Float u = sampler.Get1D();
BSDFSample bs = bsdf.Sample_f(wo, u, sampler.Get2D());
if (!bs)
break;
beta *= bs.f * AbsDot(bs.wi, isect.shading.n) / bs.pdf;
specularBounce = bs.IsSpecular();
ray = isect.SpawnRay(bs.wi);
} else {
// Uniformly sample sphere or hemisphere to get new path direction
Float pdf;
Vector3f wi;
if (bsdf.HasReflection() && bsdf.HasTransmission()) {
wi = SampleUniformSphere(sampler.Get2D());
pdf = UniformSpherePDF();
} else {
wi = SampleUniformHemisphere(sampler.Get2D());
pdf = UniformHemispherePDF();
if (bsdf.HasReflection() && Dot(wo, isect.n) * Dot(wi, isect.n) < 0)
wi = -wi;
else if (bsdf.HasTransmission() &&
Dot(wo, isect.n) * Dot(wi, isect.n) > 0)
wi = -wi;
}
beta *= bsdf.f(wo, wi) * AbsDot(wi, isect.shading.n) / pdf;
specularBounce = false;
ray = isect.SpawnRay(wi);
}
CHECK_GE(beta.y(lambda), 0.f);
DCHECK(!std::isinf(beta.y(lambda)));
}
return L;
}
std::string SimplePathIntegrator::ToString() const {
return StringPrintf("[ SimplePathIntegrator maxDepth: %d sampleLights: %s "
"sampleBSDF: %s ]",
maxDepth, sampleLights, sampleBSDF);
}
std::unique_ptr<SimplePathIntegrator> SimplePathIntegrator::Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc) {
int maxDepth = parameters.GetOneInt("maxdepth", 5);
bool sampleLights = parameters.GetOneBool("samplelights", true);
bool sampleBSDF = parameters.GetOneBool("samplebsdf", true);
return std::make_unique<SimplePathIntegrator>(maxDepth, sampleLights, sampleBSDF,
camera, sampler, aggregate, lights);
}
// LightPathIntegrator Method Definitions
LightPathIntegrator::LightPathIntegrator(int maxDepth, CameraHandle camera,
SamplerHandle sampler, PrimitiveHandle aggregate,
std::vector<LightHandle> lights)
: ImageTileIntegrator(camera, sampler, aggregate, lights), maxDepth(maxDepth) {
lightSampler = std::make_unique<PowerLightSampler>(lights, Allocator());
}
void LightPathIntegrator::EvaluatePixelSample(const Point2i &pPixel, int sampleIndex,
SamplerHandle sampler,
ScratchBuffer &scratchBuffer) {
// Eat the first two samples since they're "special"...
(void)sampler.Get2D();
// Sample wavelengths for the ray
Float lu = RadicalInverse(1, sampleIndex) + BlueNoise(47, pPixel.x, pPixel.y);
if (lu >= 1)
lu -= 1;
if (Options->disableWavelengthJitter)
lu = 0.5;
SampledWavelengths lambda = camera.GetFilm().SampleWavelengths(lu);
// Sample a light
pstd::optional<SampledLight> sampledLight = lightSampler->Sample(sampler.Get1D());
if (!sampledLight)
return;
LightHandle light = sampledLight->light;
Float lightPDF = sampledLight->pdf;
Float time = camera.SampleTime(sampler.Get1D());
LightLeSample les = light.SampleLe(sampler.Get2D(), sampler.Get2D(), lambda, time);
if (!les || les.pdfPos == 0 || les.pdfDir == 0 || !les.L)
return;
RayDifferential ray(les.ray);
SampledSpectrum beta =
les.L * les.AbsCosTheta(ray.d) / (lightPDF * les.pdfPos * les.pdfDir);
// Is the light sample directly visible?
if (les.intr) {
pstd::optional<CameraWiSample> cs =
camera.SampleWi(*les.intr, sampler.Get2D(), lambda);
if (cs && cs->pdf != 0) {
Float pdf = light.PDF_Li(cs->pLens, cs->wi);
if (pdf > 0) {
SampledSpectrum Le =
light.L(les.intr->p(), les.intr->n, les.intr->uv, cs->wi, lambda);
if (Le && Unoccluded(cs->pRef, cs->pLens)) {
SampledSpectrum L = Le * les.AbsCosTheta(cs->wi) * cs->Wi /
(lightPDF * pdf * cs->pdf);
camera.GetFilm().AddSplat(cs->pRaster, L, lambda);
}
}
}
}
for (int depth = 0; depth < maxDepth && beta; ++depth) {
pstd::optional<ShapeIntersection> si = Intersect(ray);
if (!si)
break;
// Compute scattering functions for _mode_ and skip over medium
// boundaries
SurfaceInteraction &isect = si->intr;
BSDF bsdf = isect.GetBSDF(ray, lambda, camera, scratchBuffer, sampler);
if (!bsdf) {
isect.SkipIntersection(&ray, si->tHit);
--depth;
continue;
}
Vector3f wo = isect.wo;
// Try to splat into the film
pstd::optional<CameraWiSample> cs =
camera.SampleWi(isect, sampler.Get2D(), lambda);
if (cs && cs->pdf != 0) {
SampledSpectrum L = beta * bsdf.f(wo, cs->wi, TransportMode::Importance) *
AbsDot(cs->wi, isect.shading.n) * cs->Wi / cs->pdf;
if (L && Unoccluded(cs->pRef, cs->pLens))
camera.GetFilm().AddSplat(cs->pRaster, L, lambda);
}
// Sample the BSDF...
Float u = sampler.Get1D();
BSDFSample bs = bsdf.Sample_f(wo, u, sampler.Get2D(), TransportMode::Importance);
if (!bs)
break;
beta *= bs.f * AbsDot(bs.wi, isect.shading.n) / bs.pdf;
ray = isect.SpawnRay(ray, bsdf, bs.wi, bs.flags);
}
}
std::string LightPathIntegrator::ToString() const {
return StringPrintf("[ LightPathIntegrator maxDepth: %d lightSampler: %s ]", maxDepth,
lightSampler);
}
std::unique_ptr<LightPathIntegrator> LightPathIntegrator::Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc) {
int maxDepth = parameters.GetOneInt("maxdepth", 5);
return std::make_unique<LightPathIntegrator>(maxDepth, camera, sampler, aggregate,
lights);
}
STAT_PERCENT("Integrator/Zero-radiance paths", zeroRadiancePaths, totalPaths);
STAT_PERCENT("Integrator/Regularized BSDFs", regularizedBSDFs, totalBSDFs);
STAT_INT_DISTRIBUTION("Integrator/Path length", pathLength);
// PathIntegrator Method Definitions
PathIntegrator::PathIntegrator(int maxDepth, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights,
Float rrThreshold, const std::string &lightSampleStrategy,
bool regularize)
: RayIntegrator(camera, sampler, aggregate, lights),
maxDepth(maxDepth),
rrThreshold(rrThreshold),
lightSampler(LightSamplerHandle::Create(lightSampleStrategy, lights, Allocator())),
regularize(regularize) {}
SampledSpectrum PathIntegrator::Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const {
SampledSpectrum L(0.f), beta(1.f);
bool specularBounce = false, anyNonSpecularBounces = false;
int depth = 0;
Float etaScale = 1, bsdfPDF;
SurfaceInteraction prevIntr;
while (true) {
// Find next path vertex and accumulate contribution
pstd::optional<ShapeIntersection> si = Intersect(ray);
// Add emitted light at path vertex or from the environment
if (!si) {
// Incorporate emission from infinite lights for escaped ray
for (const auto &light : infiniteLights) {
SampledSpectrum Le = light.Le(ray, lambda);
if (depth == 0 || specularBounce)
L += beta * Le;
else {
// Compute MIS weight for infinite light
Float lightPDF =
lightSampler.PDF(prevIntr, light) *
light.PDF_Li(prevIntr, ray.d, LightSamplingMode::WithMIS);
Float weight = PowerHeuristic(1, bsdfPDF, 1, lightPDF);
L += beta * weight * Le;
}
}
break;
}
// Incorporate emission from emissive surface hit by ray
SampledSpectrum Le = si->intr.Le(-ray.d, lambda);
if (Le) {
if (depth == 0 || specularBounce)
L += beta * Le;
else {
// Compute MIS weight for area light
LightHandle areaLight(si->intr.areaLight);
Float lightPDF =
lightSampler.PDF(prevIntr, areaLight) *
areaLight.PDF_Li(prevIntr, ray.d, LightSamplingMode::WithMIS);
Float weight = PowerHeuristic(1, bsdfPDF, 1, lightPDF);
L += beta * weight * Le;
}
}
SurfaceInteraction &isect = si->intr;
// Compute scattering functions and skip over medium boundaries
BSDF bsdf = isect.GetBSDF(ray, lambda, camera, scratchBuffer, sampler);
if (!bsdf) {
isect.SkipIntersection(&ray, si->tHit);
continue;
}
// Initialize _visibleSurface_ at first intersection
if (depth == 0 && visibleSurface != nullptr) {
// Estimate BSDF's albedo
constexpr int nRhoSamples = 16;
SampledSpectrum rho(0.f);
for (int i = 0; i < nRhoSamples; ++i) {
// Generate sample for hemispherical-directional reflectance
Float uc = RadicalInverse(0, i + 1);
Point2f u(RadicalInverse(1, i + 1), RadicalInverse(2, i + 1));
// Estimate one term of $\rho_\roman{hd}$
auto bs = bsdf.Sample_f(si->intr.wo, uc, u);
if (bs && bs.pdf > 0)
rho += bs.f * AbsDot(bs.wi, si->intr.shading.n) / bs.pdf;
}
SampledSpectrum albedo = rho / nRhoSamples;
*visibleSurface =
VisibleSurface(si->intr, camera.GetCameraTransform(), albedo, lambda);
}
// End path if maximum depth reached
if (depth++ == maxDepth)
break;
// Possibly regularize the BSDF
if (regularize && anyNonSpecularBounces) {
++regularizedBSDFs;
bsdf.Regularize();
}
++totalBSDFs;
// Sample direct illumination from the light sources
if (bsdf.IsNonSpecular()) {
++totalPaths;
SampledSpectrum Ld = SampleLd(isect, bsdf, lambda, sampler);
if (!Ld)
++zeroRadiancePaths;
L += beta * Ld;
}
// Sample BSDF to get new path direction
Vector3f wo = -ray.d;
Float u = sampler.Get1D();
BSDFSample bs = bsdf.Sample_f(wo, u, sampler.Get2D());
if (!bs)
break;
// Update path state variables for after surface scattering
beta *= bs.f * AbsDot(bs.wi, isect.shading.n) / bs.pdf;
bsdfPDF = bsdf.SampledPDFIsProportional() ? bsdf.PDF(wo, bs.wi) : bs.pdf;
DCHECK(!std::isinf(beta.y(lambda)));
specularBounce = bs.IsSpecular();
anyNonSpecularBounces |= !bs.IsSpecular();
if (bs.IsTransmission())
etaScale *= Sqr(bsdf.eta);
prevIntr = si->intr;
ray = isect.SpawnRay(ray, bsdf, bs.wi, bs.flags);
// Possibly terminate the path with Russian roulette
SampledSpectrum rrBeta = beta * etaScale;
if (rrBeta.MaxComponentValue() < rrThreshold && depth > 1) {
Float q = std::max<Float>(0, 1 - rrBeta.MaxComponentValue());
if (sampler.Get1D() < q)
break;
beta /= 1 - q;
DCHECK(!std::isinf(beta.y(lambda)));
}
}
ReportValue(pathLength, depth);
return L;
}
SampledSpectrum PathIntegrator::SampleLd(const SurfaceInteraction &intr, const BSDF &bsdf,
SampledWavelengths &lambda,
SamplerHandle sampler) const {
// Choose a light source for the direct lighting calculation
pstd::optional<SampledLight> sampledLight =
lightSampler.Sample(intr, sampler.Get1D());
Point2f uLight = sampler.Get2D();
if (!sampledLight)
return {};
LightHandle light = sampledLight->light;
DCHECK(light != nullptr && sampledLight->pdf > 0);
// Sample a point on the light source for direct lighting
LightLiSample ls = light.SampleLi(intr, uLight, lambda, LightSamplingMode::WithMIS);
if (!ls || !ls.L)
return {};
// Evaluate BSDF for light sample and check light visibility
Vector3f wo = intr.wo, wi = ls.wi;
SampledSpectrum f = bsdf.f(wo, wi) * AbsDot(wi, intr.shading.n);
if (!f || !Unoccluded(intr, ls.pLight))
return {};
// Return light's contribution to reflected radiance
Float lightPDF = sampledLight->pdf * ls.pdf;
if (IsDeltaLight(light.Type()))
return f * ls.L / lightPDF;
else {
Float bsdfPDF = bsdf.PDF(wo, wi);
CHECK_RARE(1e-6, bsdf.SampledPDFIsProportional() == false && bsdfPDF == 0);
Float weight = PowerHeuristic(1, lightPDF, 1, bsdfPDF);
return f * ls.L * weight / lightPDF;
}
}
std::string PathIntegrator::ToString() const {
return StringPrintf("[ PathIntegrator maxDepth: %d rrThreshold: %f "
"lightSampler: %s regularize: %s ]",
maxDepth, rrThreshold, lightSampler, regularize);
}
std::unique_ptr<PathIntegrator> PathIntegrator::Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc) {
int maxDepth = parameters.GetOneInt("maxdepth", 5);
Float rrThreshold = parameters.GetOneFloat("rrthreshold", 1.);
std::string lightStrategy = parameters.GetOneString("lightsampler", "bvh");
bool regularize = parameters.GetOneBool("regularize", false);
return std::make_unique<PathIntegrator>(maxDepth, camera, sampler, aggregate, lights,
rrThreshold, lightStrategy, regularize);
}
// SimpleVolPathIntegrator Method Definitions
SimpleVolPathIntegrator::SimpleVolPathIntegrator(int maxDepth, CameraHandle camera,
SamplerHandle sampler,
PrimitiveHandle aggregate,
std::vector<LightHandle> lights)
: RayIntegrator(camera, sampler, aggregate, lights), maxDepth(maxDepth) {
for (LightHandle light : lights) {
if (IsDeltaLight(light.Type()))
ErrorExit("SimpleVolPathIntegrator only supports area and infinite light "
"sources");
}
}
SampledSpectrum SimpleVolPathIntegrator::Li(RayDifferential ray,
SampledWavelengths &lambda,
SamplerHandle sampler,
ScratchBuffer &scratchBuffer,
VisibleSurface *) const {
SampledSpectrum L(0.f), beta(1.f);
int numScatters = 0;
lambda.TerminateSecondary();
while (true) {
// Estimate radiance for ray path using delta tracking
pstd::optional<ShapeIntersection> si = Intersect(ray);
bool scattered = false, terminated = false;
if (ray.medium) {
// Sample medium scattering for _SimpleVolPathIntegrator_
Float tMax = si ? si->tHit : Infinity;
RNG rng(Hash(sampler.Get1D()), Hash(sampler.Get1D()));
ray.medium.SampleTmaj(ray, tMax, rng, lambda, [&](const MediumSample &ms) {
// Update delta-tracking estimator for path sample
if (!ms.intr)
return false;
const MediumInteraction &intr = *ms.intr;
const SampledSpectrum &sigma_a = intr.sigma_a, &sigma_s = intr.sigma_s;
// Compute medium event probabilities for interaction
Float pAbsorb = sigma_a[0] / intr.sigma_maj[0];
Float pScatter = sigma_s[0] / intr.sigma_maj[0];
Float pNull = std::max<Float>(0, 1 - pAbsorb - pScatter);
// Randomly sample medium scattering event for delta-tracking
Float u = sampler.Get1D();
int mode = SampleDiscrete({pAbsorb, pScatter, pNull}, u);
if (mode == 0) {
// Handle absorption event for delta-tracking
// absorbed; done
L += intr.Le;
terminated = true;
return false;
} else if (mode == 1) {
// Handle scattering event for delta-tracking
if (numScatters++ >= maxDepth) {
terminated = true;
return false;
}
Vector3f wi = SampleUniformSphere(sampler.Get2D());
beta *= intr.phase.p(-ray.d, wi) / UniformSpherePDF();
ray = intr.SpawnRay(wi);
scattered = true;
return false;
} else {
// Handle null scattering event for delta-tracking
// null -- keep going...
return true;
}
});
}
if (terminated)
break;
if (!scattered) {
// Add emission to un-scattered ray
if (!si) {
for (const auto &light : infiniteLights)
L += beta * light.Le(ray, lambda);
return L;
}
SurfaceInteraction &isect = si->intr;
L += beta * isect.Le(-ray.d, lambda);
// Handle surface intersection for _SimpleVolPathIntegrator_
BSDF bsdf = isect.GetBSDF(ray, lambda, camera, scratchBuffer, sampler);
if (!bsdf)
isect.SkipIntersection(&ray, si->tHit);
else if (bsdf.Sample_f(-ray.d, sampler.Get1D(), sampler.Get2D()))
ErrorExit(
"SimpleVolPathIntegrator doesn't support scattering from surfaces");
else
break;
}
}
return L;
}
std::string SimpleVolPathIntegrator::ToString() const {
return StringPrintf("[ SimpleVolPathIntegrator maxDepth: %d ] ", maxDepth);
}
std::unique_ptr<SimpleVolPathIntegrator> SimpleVolPathIntegrator::Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc) {
int maxDepth = parameters.GetOneInt("maxdepth", 5);
return std::make_unique<SimpleVolPathIntegrator>(maxDepth, camera, sampler, aggregate,
lights);
}
STAT_COUNTER("Integrator/Volume interactions", volumeInteractions);
STAT_COUNTER("Integrator/Surface interactions", surfaceInteractions);
// VolPathIntegrator Method Definitions
SampledSpectrum VolPathIntegrator::Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const {
// Declare state variables for volumetric path
// NOTE: beta means something different here...
SampledSpectrum L(0.f), beta(1.f), pdfUni(1.f), pdfNEE(1.f);
bool specularBounce = false, anyNonSpecularBounces = false;
Float etaScale = 1;
pstd::optional<SurfaceInteraction> prevSurfaceIntr;
pstd::optional<MediumInteraction> prevMediumIntr;
int depth = 0;
while (true) {
// Sample segment of volumetric scattering path
VLOG(2, "Path tracer depth %d, current L = %s, beta = %s", depth, L, beta);
pstd::optional<ShapeIntersection> si = Intersect(ray);
bool scattered = false, terminated = false;
if (ray.medium) {
// Sample the participating medium
Float tMax = si ? si->tHit : Infinity;
RNG rng(Hash(sampler.Get1D()), Hash(sampler.Get1D()));
ray.medium.SampleTmaj(
ray, tMax, rng, lambda, [&](const MediumSample &mediumSample) {
// Handle medium scattering event for ray
if (!mediumSample.intr) {
// Update _beta_ and _pdfUni_ for ray that escaped the medium
// FIXME: review this, esp the pdf...
beta *= mediumSample.Tmaj;
pdfUni *= mediumSample.Tmaj;
return false;
}
++volumeInteractions;
const MediumInteraction &intr = *mediumSample.intr;
const SampledSpectrum &sigma_a = intr.sigma_a,
&sigma_s = intr.sigma_s;
const SampledSpectrum &Tmaj = mediumSample.Tmaj;
// Add emission from medium scattering event
if (depth < maxDepth)
L += beta * intr.Le * sigma_a /
(intr.sigma_maj[0] * pdfUni.Average());
// Compute medium event probabilities for interaction
Float pAbsorb = sigma_a[0] / intr.sigma_maj[0];
Float pScatter = sigma_s[0] / intr.sigma_maj[0];
Float pNull = std::max<Float>(0, 1 - pAbsorb - pScatter);
CHECK_GE(1 - pAbsorb - pScatter, -1e-6);
// Sample medium scattering event type and update path
Float um = rng.Uniform<Float>();
int mode = SampleDiscrete({pAbsorb, pScatter, pNull}, um);
if (mode == 0) {
// Handle absorption along ray path
// beta *= Tmaj * sigma_a;
// pdfUni *= Tmaj * sigma_a;
terminated = true;
return false;
} else if (mode == 1) {
// Handle scattering along ray path
if (depth++ >= maxDepth) {
terminated = true;
return false;
}
beta *= Tmaj * sigma_s;
pdfUni *= Tmaj * sigma_s;
// Sample direct lighting at volume scattering event
L += SampleLd(intr, nullptr, lambda, sampler, beta, pdfUni);
// Sample indirect lighting at volume scattering event
PhaseFunctionSample ps =
intr.phase.Sample_p(-ray.d, sampler.Get2D());
if (!ps) {
terminated = true;
return false;
}
// Update ray path state for indirect volume scattering
beta *= ps.p;
pdfNEE = pdfUni;
pdfUni *= ps.pdf;
prevMediumIntr = intr;
prevSurfaceIntr.reset();
scattered = true;
ray = intr.SpawnRay(ps.wi);
specularBounce = false;
anyNonSpecularBounces = true;
return false;
} else {
// Handle null scattering along ray path
SampledSpectrum sigma_n = intr.sigma_n();
beta *= Tmaj * sigma_n;
pdfUni *= Tmaj * sigma_n;
pdfNEE *= Tmaj * intr.sigma_maj;
rescale(beta, pdfUni, pdfNEE);
return true;
}
});
}
if (terminated)
return L;
if (scattered)
continue;
// Handle scattering at point on surface for volumetric path tracer
++surfaceInteractions;
if (depth > 0)
CHECK(prevSurfaceIntr.has_value() ^ prevMediumIntr.has_value());
// Add emitted light at volume path vertex or from the environment
if (!si) {
// Accumulate contributions from infinite light sources
for (const auto &light : infiniteLights) {
SampledSpectrum Le = light.Le(ray, lambda);
if (Le) {
if (depth == 0 || specularBounce)
L += beta * Le / pdfUni.Average();
else {
// Add infinite light contribution using both PDFs with MIS
LightSampleContext prevIntrContext;
if (prevSurfaceIntr)
prevIntrContext = LightSampleContext(*prevSurfaceIntr);
else
prevIntrContext = LightSampleContext(*prevMediumIntr);
Float lightPDF = lightSampler.PDF(prevIntrContext, light) *
light.PDF_Li(prevIntrContext, ray.d,
LightSamplingMode::WithMIS);
pdfNEE *= lightPDF;
L += beta * Le / (pdfUni + pdfNEE).Average();
}
}
}
break;
}
SurfaceInteraction &isect = si->intr;
SampledSpectrum Le = isect.Le(-ray.d, lambda);
if (Le) {
// Add contribution of emission from intersected surface
if (depth == 0 || specularBounce)
L += beta * Le / pdfUni.Average();
else {
// Add surface light contribution using both PDFs with MIS
LightHandle areaLight(isect.areaLight);
LightSampleContext prevIntrContext;
if (prevSurfaceIntr)
prevIntrContext = LightSampleContext(*prevSurfaceIntr);
else
prevIntrContext = LightSampleContext(*prevMediumIntr);
Float lightPDF =
lightSampler.PDF(prevIntrContext, areaLight) *
areaLight.PDF_Li(prevIntrContext, ray.d, LightSamplingMode::WithMIS);
pdfNEE *= lightPDF;
L += beta * Le / (pdfUni + pdfNEE).Average();
}
}
// Compute scattering functions and skip over medium boundaries
BSDF bsdf = isect.GetBSDF(ray, lambda, camera, scratchBuffer, sampler);
if (!bsdf) {
isect.SkipIntersection(&ray, si->tHit);
continue;
}
prevSurfaceIntr = isect;
prevMediumIntr.reset();
// Terminate path if maximum depth reached
if (depth++ >= maxDepth)
return L;
// Possibly regularize BSDF
if (regularize && anyNonSpecularBounces) {
++regularizedBSDFs;
bsdf.Regularize();
}
++totalBSDFs;
// Sample illumination from lights to find attenuated path contribution
if (bsdf.IsNonSpecular()) {
L += SampleLd(isect, &bsdf, lambda, sampler, beta, pdfUni);
DCHECK(std::isinf(L.y(lambda)) == false);
}
// Sample BSDF to get new volumetric path direction
Vector3f wo = -ray.d;
Float u = sampler.Get1D();
BSDFSample bs = bsdf.Sample_f(wo, u, sampler.Get2D());
if (!bs)
break;
// Update _beta_ and PDFs for BSDF scattering
beta *= bs.f * AbsDot(bs.wi, isect.shading.n);
pdfNEE = pdfUni;
if (bsdf.SampledPDFIsProportional()) {
Float pdf = bsdf.PDF(wo, bs.wi);
beta *= pdf / bs.pdf;
pdfUni *= pdf;
} else
pdfUni *= bs.pdf;
rescale(beta, pdfUni, pdfNEE);
VLOG(2, "Sampled BSDF, f = %s, pdf = %f -> beta = %s", bs.f, bs.pdf, beta);
DCHECK(std::isinf(beta.y(lambda)) == false);
specularBounce = bs.IsSpecular();
anyNonSpecularBounces |= !bs.IsSpecular();
if (bs.IsTransmission())
etaScale *= Sqr(bsdf.eta);
ray = isect.SpawnRay(ray, bsdf, bs.wi, bs.flags);
// Account for attenuated subsurface scattering, if applicable
BSSRDFHandle bssrdf = isect.GetBSSRDF(ray, lambda, camera, scratchBuffer);
if (bssrdf && bs.IsTransmission()) {
// Sample BSSRDF probe segment to find exit point
BSSRDFProbeSegment probeSeg = bssrdf.Sample(sampler.Get1D(), sampler.Get2D());
if (!probeSeg)
break;
// Sample random intersection along BSSRDF probe segment
uint64_t seed = MixBits(FloatToBits(sampler.Get1D()));
WeightedReservoirSampler<SubsurfaceInteraction> interactionSampler(seed);
// Intersect BSSRDF sampling ray against the scene geometry
Interaction base(probeSeg.p0, probeSeg.time, (MediumHandle) nullptr);
while (true) {
Ray r = base.SpawnRayTo(probeSeg.p1);
if (r.d == Vector3f(0, 0, 0))
break;
pstd::optional<ShapeIntersection> si = Intersect(r, 1);
if (!si)
break;
base = si->intr;
if (si->intr.material == isect.material)
interactionSampler.Add(SubsurfaceInteraction(si->intr), 1.f);
}
if (!interactionSampler.HasSample())
break;
// Convert probe intersection to _BSSRDFSample_ and update _beta_
SubsurfaceInteraction ssi = interactionSampler.GetSample();
BSSRDFSample bssrdfSample =
bssrdf.ProbeIntersectionToSample(ssi, scratchBuffer);
if (!bssrdfSample.S || bssrdfSample.pdf == 0)
break;
// Can ignore path pdf here as well since bssrdfSample.pdf
// is non-spectral.
beta *= bssrdfSample.S * interactionSampler.WeightSum() / bssrdfSample.pdf;
SurfaceInteraction pi = ssi;
BSDF &bsdf = bssrdfSample.bsdf;
pi.wo = bssrdfSample.wo;
// Possibly regularize subsurface BSDF and update _prevSurfaceIntr_
anyNonSpecularBounces = true;
if (regularize) {
++regularizedBSDFs;
bsdf.Regularize();
} else
++totalBSDFs;
prevSurfaceIntr = pi;
CHECK(!prevMediumIntr.has_value());
// Account for attenuated direct subsurface scattering
L += SampleLd(pi, &bsdf, lambda, sampler, beta, pdfUni);
// Sample ray for indirect subsurface scattering
Float u = sampler.Get1D();
BSDFSample bs = bsdf.Sample_f(pi.wo, u, sampler.Get2D());
if (!bs)
break;
beta *= bs.f * AbsDot(bs.wi, pi.shading.n);
pdfNEE = pdfUni;
pdfUni *= bs.pdf;
// don't increment depth this time...
DCHECK(!std::isinf(beta.y(lambda)));
specularBounce = bs.IsSpecular();
ray = RayDifferential(pi.SpawnRay(bs.wi));
}
// Possibly terminate volumetric path with Russian roulette
if (!beta)
break;
SampledSpectrum rrBeta = beta * etaScale / pdfUni.Average();
VLOG(2, "etaScale %f -> rrBeta %s", etaScale, rrBeta);
if (rrBeta.MaxComponentValue() < rrThreshold && depth > 1) {
Float q = std::max<Float>(0, 1 - rrBeta.MaxComponentValue());
if (sampler.Get1D() < q)
break;
pdfUni *= 1 - q;
pdfNEE *= 1 - q;
}
}
return L;
}
SampledSpectrum VolPathIntegrator::SampleLd(const Interaction &intr, const BSDF *bsdf,
SampledWavelengths &lambda,
SamplerHandle sampler,
const SampledSpectrum &beta,
const SampledSpectrum &pathPDF) const {
// Sample a light source using _lightSampler_
Float u = sampler.Get1D();
pstd::optional<SampledLight> sampledLight =
bsdf ? lightSampler.Sample(intr.AsSurface(), u) : lightSampler.Sample(intr, u);
Point2f uLight = sampler.Get2D();
if (!sampledLight)
return SampledSpectrum(0.f);
LightHandle light = sampledLight->light;
CHECK(light != nullptr && sampledLight->pdf != 0);
// Sample a point on the light source
LightSampleContext ctx;
if (bsdf)
ctx = LightSampleContext(intr.AsSurface());
else
ctx = LightSampleContext(intr);
LightLiSample ls = light.SampleLi(ctx, uLight, lambda, LightSamplingMode::WithMIS);
if (!ls || !ls.L)
return SampledSpectrum(0.f);
Float lightPDF = sampledLight->pdf * ls.pdf;
// Evaluate BSDF or phase function for light sample direction
Float scatterPDF;
SampledSpectrum betaLight = beta;
Vector3f wo = intr.wo, wi = ls.wi;
if (bsdf) {
// Update _bsdfLight_ and _scatterPDF_ accounting for the BSDF
betaLight *= bsdf->f(wo, wi) * AbsDot(wi, intr.AsSurface().shading.n);
scatterPDF = bsdf->PDF(wo, wi);
} else {
// Update _bsdfLight_ and _scatterPDF_ accounting for the phase function
CHECK(intr.IsMediumInteraction());
PhaseFunctionHandle phase = intr.AsMedium().phase;
betaLight *= phase.p(wo, wi);
scatterPDF = phase.PDF(wo, wi);
}
if (!betaLight)
return SampledSpectrum(0.f);
// Declare path state variables for ray to light source
Ray lightRay = intr.SpawnRayTo(ls.pLight);
SampledSpectrum pdfLight = pathPDF * lightPDF; // p_nee in paper
SampledSpectrum pdfUni = pathPDF * scatterPDF; // p_uni
RNG rng(Hash(lightRay.o), Hash(lightRay.d));
while (true) {
// Trace ray through media to estimate transmittance
pstd::optional<ShapeIntersection> si = Intersect(lightRay, 1 - ShadowEpsilon);
// Handle opaque surface along ray's path
if (si && si->intr.material)
return SampledSpectrum(0.f);
// Update transmittance for current ray segment
if (lightRay.medium != nullptr) {
Float tMax = si ? si->tHit : (1 - ShadowEpsilon);
lightRay.medium.SampleTmaj(
lightRay, tMax, rng, lambda, [&](const MediumSample &mediumSample) {
// Account for medium scattering event along shadow ray
const SampledSpectrum &Tmaj = mediumSample.Tmaj;
if (!mediumSample.intr) {
// CO betaLight *= Tmaj;
return false;
}
const MediumInteraction &intr = *mediumSample.intr;
// Update _betaLight_ and PDFs using ratio-tracking estimator
SampledSpectrum sigma_n = intr.sigma_n();
// ratio-tracking: only evaluate null scattering
betaLight *= Tmaj * sigma_n;
pdfLight *= Tmaj * intr.sigma_maj;
pdfUni *= Tmaj * sigma_n;
if (!betaLight)
return false;
rescale(betaLight, pdfLight, pdfUni);
return true;
});
}
// Generate next ray segment or return final transmittance
if (!si)
break;
lightRay = si->intr.SpawnRayTo(ls.pLight);
}
// Return weighted light contribution to direct lighting
if (IsDeltaLight(light.Type()))
// pdfUni unused...
return betaLight * ls.L / pdfLight.Average();
else
return betaLight * ls.L / (pdfLight + pdfUni).Average();
}
std::string VolPathIntegrator::ToString() const {
return StringPrintf("[ VolPathIntegrator maxDepth: %d rrThreshold: %f "
"lightSampler: %s regularize: %s ]",
maxDepth, rrThreshold, lightSampler, regularize);
}
std::unique_ptr<VolPathIntegrator> VolPathIntegrator::Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc) {
int maxDepth = parameters.GetOneInt("maxdepth", 5);
Float rrThreshold = parameters.GetOneFloat("rrthreshold", 1.);
std::string lightStrategy = parameters.GetOneString("lightsampler", "bvh");
bool regularize = parameters.GetOneBool("regularize", false);
return std::make_unique<VolPathIntegrator>(maxDepth, camera, sampler, aggregate,
lights, rrThreshold, lightStrategy,
regularize);
}
// AOIntegrator Method Definitions
AOIntegrator::AOIntegrator(bool cosSample, Float maxDist, CameraHandle camera,
SamplerHandle sampler, PrimitiveHandle aggregate,
std::vector<LightHandle> lights, SpectrumHandle illuminant)
: RayIntegrator(camera, sampler, aggregate, lights),
cosSample(cosSample),
maxDist(maxDist),
illuminant(illuminant) {}
SampledSpectrum AOIntegrator::Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const {
SampledSpectrum L(0.f);
// Intersect _ray_ with scene and store intersection in _isect_
pstd::optional<ShapeIntersection> si;
retry:
si = Intersect(ray);
if (si) {
SurfaceInteraction &isect = si->intr;
BSDF bsdf = isect.GetBSDF(ray, lambda, camera, scratchBuffer, sampler);
if (!bsdf) {
isect.SkipIntersection(&ray, si->tHit);
goto retry;
}
// Compute coordinate frame based on true geometry, not shading
// geometry.
Normal3f n = FaceForward(isect.n, -ray.d);
Vector3f s = Normalize(isect.dpdu);
Vector3f t = Cross(isect.n, s);
Vector3f wi;
Float pdf;
Point2f u = sampler.Get2D();
if (cosSample) {
wi = SampleCosineHemisphere(u);
pdf = CosineHemispherePDF(std::abs(wi.z));
} else {
wi = SampleUniformHemisphere(u);
pdf = UniformHemispherePDF();
}
if (pdf == 0)
return SampledSpectrum(0.);
Frame f = Frame::FromZ(n);
wi = f.FromLocal(wi);
// Divide by pi so that fully visible is one.
Ray r = isect.SpawnRay(wi);
if (!IntersectP(r, maxDist))
return illuminant.Sample(lambda) * SampledSpectrum(Dot(wi, n) / (Pi * pdf));
}
return SampledSpectrum(0.);
}
std::string AOIntegrator::ToString() const {
return StringPrintf("[ AOIntegrator cosSample: %s maxDist: %f illuminant: %s ]",
cosSample, maxDist, illuminant);
}
std::unique_ptr<AOIntegrator> AOIntegrator::Create(
const ParameterDictionary &parameters, SpectrumHandle illuminant, CameraHandle camera,
SamplerHandle sampler, PrimitiveHandle aggregate, std::vector<LightHandle> lights,
const FileLoc *loc) {
bool cosSample = parameters.GetOneBool("cossample", true);
Float maxDist = parameters.GetOneFloat("maxdistance", Infinity);
return std::make_unique<AOIntegrator>(cosSample, maxDist, camera, sampler, aggregate,
lights, illuminant);
}
// BDPT Utility Function Declarations
int RandomWalk(const Integrator &integrator, SampledWavelengths &lambda,
RayDifferential ray, SamplerHandle sampler, CameraHandle camera,
ScratchBuffer &scratchBuffer, SampledSpectrum beta, Float pdf,
int maxDepth, TransportMode mode, Vertex *path, bool regularize);
SampledSpectrum ConnectBDPT(const Integrator &integrator, SampledWavelengths &lambda,
Vertex *lightVertices, Vertex *cameraVertices, int s, int t,
LightSamplerHandle lightSampler, CameraHandle camera,
SamplerHandle sampler, pstd::optional<Point2f> *pRaster,
Float *misWeightPtr = nullptr);
Float InfiniteLightDensity(const std::vector<LightHandle> &infiniteLights,
LightSamplerHandle lightSampler, const Vector3f &w);
// VertexType Definition
enum class VertexType { Camera, Light, Surface, Medium };
// ScopedAssignment Definition
template <typename Type>
class ScopedAssignment {
public:
// ScopedAssignment Public Methods
ScopedAssignment(Type *target = nullptr, Type value = Type()) : target(target) {
if (target) {
backup = *target;
*target = value;
}
}
~ScopedAssignment() {
if (target)
*target = backup;
}
ScopedAssignment(const ScopedAssignment &) = delete;
ScopedAssignment &operator=(const ScopedAssignment &) = delete;
ScopedAssignment &operator=(ScopedAssignment &&other) {
target = other.target;
backup = other.backup;
other.target = nullptr;
return *this;
}
private:
Type *target, backup;
};
// EndpointInteraction Definition
struct EndpointInteraction : Interaction {
union {
CameraHandle camera;
LightHandle light;
};
// EndpointInteraction Public Methods
EndpointInteraction() : Interaction(), light(nullptr) {}
EndpointInteraction(const Interaction &it, CameraHandle camera)
: Interaction(it), camera(camera) {}
EndpointInteraction(CameraHandle camera, const Ray &ray)
: Interaction(ray.o, ray.time, ray.medium), camera(camera) {}
EndpointInteraction(const EndpointInteraction &ei)
: Interaction(ei), camera(ei.camera) {
static_assert(sizeof(LightHandle) == sizeof(CameraHandle),
"Expect both union members have same size");
}
EndpointInteraction(LightHandle light, const Ray &r, const Interaction &intr)
: Interaction(intr), light(light) {}
EndpointInteraction(LightHandle light, const Ray &r)
: Interaction(r.o, r.time, r.medium), light(light) {}
EndpointInteraction(const Interaction &it, LightHandle light)
: Interaction(it), light(light) {}
EndpointInteraction(const Ray &ray)
: Interaction(ray(1), Normal3f(-ray.d), ray.time, ray.medium), light(nullptr) {}
};
// BDPT Vertex Definition
struct Vertex {
// Vertex Public Members
VertexType type;
SampledSpectrum beta;
union {
EndpointInteraction ei;
MediumInteraction mi;
SurfaceInteraction si;
};
BSDF bsdf;
bool delta = false;
Float pdfFwd = 0, pdfRev = 0;
// Vertex Public Methods
// Need to define these two to make compilers happy with the non-POD
// objects in the anonymous union above.
Vertex(const Vertex &v) { memcpy(this, &v, sizeof(Vertex)); }
Vertex &operator=(const Vertex &v) {
memcpy(this, &v, sizeof(Vertex));
return *this;
}
Vertex() : ei() {}
Vertex(VertexType type, const EndpointInteraction &ei, const SampledSpectrum &beta)
: type(type), beta(beta), ei(ei) {}
Vertex(const SurfaceInteraction &si, const BSDF &bsdf, const SampledSpectrum &beta)
: type(VertexType::Surface), beta(beta), si(si), bsdf(bsdf) {}
static inline Vertex CreateCamera(CameraHandle camera, const Ray &ray,
const SampledSpectrum &beta);
static inline Vertex CreateCamera(CameraHandle camera, const Interaction &it,
const SampledSpectrum &beta);
static inline Vertex CreateLight(LightHandle light, const Ray &ray,
const SampledSpectrum &Le, Float pdf);
static inline Vertex CreateLight(LightHandle light, const Ray &ray,
const Interaction &intr, const SampledSpectrum &Le,
Float pdf);
static inline Vertex CreateLight(const EndpointInteraction &ei,
const SampledSpectrum &beta, Float pdf);
static inline Vertex CreateMedium(const MediumInteraction &mi,
const SampledSpectrum &beta, Float pdf,
const Vertex &prev);
static inline Vertex CreateSurface(const SurfaceInteraction &si, const BSDF &bsdf,
const SampledSpectrum &beta, Float pdf,
const Vertex &prev);
Vertex(const MediumInteraction &mi, const SampledSpectrum &beta)
: type(VertexType::Medium), beta(beta), mi(mi) {}
const Interaction &GetInteraction() const {
switch (type) {
case VertexType::Medium:
return mi;
case VertexType::Surface:
return si;
default:
return ei;
}
}
Point3f p() const { return GetInteraction().p(); }
Float time() const { return GetInteraction().time; }
const Normal3f &ng() const { return GetInteraction().n; }
const Normal3f &ns() const {
if (type == VertexType::Surface)
return si.shading.n;
else
return GetInteraction().n;
}
bool IsOnSurface() const { return ng() != Normal3f(); }
SampledSpectrum f(const Vertex &next, TransportMode mode) const {
Vector3f wi = next.p() - p();
if (LengthSquared(wi) == 0)
return {};
wi = Normalize(wi);
switch (type) {
case VertexType::Surface:
return bsdf.f(si.wo, wi, mode);
case VertexType::Medium:
return SampledSpectrum(mi.phase.p(mi.wo, wi));
default:
LOG_FATAL("Vertex::f(): Unimplemented");
return SampledSpectrum(0.f);
}
}
bool IsConnectible() const {
switch (type) {
case VertexType::Medium:
return true;
case VertexType::Light:
return ei.light.Type() != LightType::DeltaDirection;
case VertexType::Camera:
return true;
case VertexType::Surface:
return bsdf.IsNonSpecular();
}
LOG_FATAL("Unhandled vertex type in IsConnectable()");
}
bool IsLight() const {
return type == VertexType::Light || (type == VertexType::Surface && si.areaLight);
}
bool IsDeltaLight() const {
return type == VertexType::Light && ei.light &&
pbrt::IsDeltaLight(ei.light.Type());
}
bool IsInfiniteLight() const {
return type == VertexType::Light &&
(!ei.light || ei.light.Type() == LightType::Infinite ||
ei.light.Type() == LightType::DeltaDirection);
}
SampledSpectrum Le(const std::vector<LightHandle> &infiniteLights, const Vertex &v,
const SampledWavelengths &lambda) const {
if (!IsLight())
return SampledSpectrum(0.f);
Vector3f w = v.p() - p();
if (LengthSquared(w) == 0)
return SampledSpectrum(0.);
w = Normalize(w);
if (IsInfiniteLight()) {
// Return emitted radiance for infinite light sources
SampledSpectrum Le(0.f);
for (const auto &light : infiniteLights)
Le += light.Le(Ray(p(), -w), lambda);
return Le;
} else {
return si.areaLight ? si.areaLight.L(si.p(), si.n, si.uv, w, lambda)
: SampledSpectrum(0.);
}
}
std::string ToString() const {
std::string s = std::string("[ Vertex type: ");
switch (type) {
case VertexType::Camera:
s += "camera";
break;
case VertexType::Light:
s += "light";
break;
case VertexType::Surface:
s += "surface";
break;
case VertexType::Medium:
s += "medium";
break;
}
s += StringPrintf(" connectible: %s p: %s ng: %s pdfFwd: %f pdfRev: %f beta: %s",
IsConnectible(), p(), ng(), pdfFwd, pdfRev, beta);
switch (type) {
case VertexType::Camera:
// TODO
break;
case VertexType::Light:
// TODO
break;
case VertexType::Surface:
s += std::string("\n bsdf: ") + bsdf.ToString();
break;
case VertexType::Medium:
s += std::string("\n phase: ") + mi.phase.ToString();
break;
}
s += std::string(" ]");
return s;
}
Float ConvertDensity(Float pdf, const Vertex &next) const {
// Return solid angle density if _next_ is an infinite area light
if (next.IsInfiniteLight())
return pdf;
Vector3f w = next.p() - p();
if (LengthSquared(w) == 0)
return 0;
Float invDist2 = 1 / LengthSquared(w);
if (next.IsOnSurface())
pdf *= AbsDot(next.ng(), w * std::sqrt(invDist2));
return pdf * invDist2;
}
Float PDF(const Integrator &integrator, const Vertex *prev,
const Vertex &next) const {
if (type == VertexType::Light)
return PdfLight(integrator, next);
// Compute directions to preceding and next vertex
Vector3f wn = next.p() - p();
if (LengthSquared(wn) == 0)
return 0;
wn = Normalize(wn);
Vector3f wp;
if (prev) {
wp = prev->p() - p();
if (LengthSquared(wp) == 0)
return 0;
wp = Normalize(wp);
} else
CHECK(type == VertexType::Camera);
// Compute directional density depending on the vertex type
Float pdf = 0, unused;
if (type == VertexType::Camera)
ei.camera.PDF_We(ei.SpawnRay(wn), &unused, &pdf);
else if (type == VertexType::Surface)
pdf = bsdf.PDF(wp, wn);
else if (type == VertexType::Medium)
pdf = mi.phase.p(wp, wn);
else
LOG_FATAL("Vertex::PDF(): Unimplemented");
// Return probability per unit area at vertex _next_
return ConvertDensity(pdf, next);
}
Float PdfLight(const Integrator &integrator, const Vertex &v) const {
Vector3f w = v.p() - p();
Float invDist2 = 1 / LengthSquared(w);
w *= std::sqrt(invDist2);
Float pdf;
if (IsInfiniteLight()) {
// Compute planar sampling density for infinite light sources
Point3f worldCenter;
Float worldRadius;
integrator.SceneBounds().BoundingSphere(&worldCenter, &worldRadius);
pdf = 1 / (Pi * worldRadius * worldRadius);
} else if (IsOnSurface()) {
// Compute sampling density at emissive surface
if (type == VertexType::Light)
CHECK(ei.light.Is<DiffuseAreaLight>()); // since that's all we've
// got currently...
LightHandle light = (type == VertexType::Light) ? ei.light : si.areaLight;
Float pdfPos, pdfDir;
light.PDF_Le(ei, w, &pdfPos, &pdfDir);
pdf = pdfDir * invDist2;
} else {
// Get pointer _light_ to the light source at the vertex
CHECK(type == VertexType::Light);
CHECK(ei.light != nullptr);
LightHandle light = ei.light;
// Compute sampling density for non-infinite light sources
Float pdfPos, pdfDir;
light.PDF_Le(Ray(p(), w, time()), &pdfPos, &pdfDir);
pdf = pdfDir * invDist2;
}
if (v.IsOnSurface())
pdf *= AbsDot(v.ng(), w);
return pdf;
}
Float PdfLightOrigin(const std::vector<LightHandle> &infiniteLights, const Vertex &v,
LightSamplerHandle lightSampler) {
Vector3f w = v.p() - p();
if (LengthSquared(w) == 0)
return 0.;
w = Normalize(w);
if (IsInfiniteLight()) {
// Return solid angle density for infinite light sources
return InfiniteLightDensity(infiniteLights, lightSampler, w);
} else if (IsOnSurface()) {
// Return probability for emissive surface
if (type == VertexType::Light)
CHECK(ei.light.Is<DiffuseAreaLight>()); // since that's all we've
// got currently...
LightHandle light = (type == VertexType::Light) ? ei.light : si.areaLight;
Float pdfChoice = lightSampler.PDF(light);
Float pdfPos, pdfDir;
light.PDF_Le(ei, w, &pdfPos, &pdfDir);
return pdfPos * pdfChoice;
} else {
// Return solid angle density for non-infinite light sources
Float pdfPos, pdfDir;
CHECK(IsLight());
LightHandle light = type == VertexType::Light ? ei.light : si.areaLight;
CHECK(light != nullptr);
Float pdfChoice = lightSampler.PDF(light);
light.PDF_Le(Ray(p(), w, time()), &pdfPos, &pdfDir);
return pdfPos * pdfChoice;
}
}
};
// BDPT Vertex Inline Method Definitions
inline Vertex Vertex::CreateCamera(CameraHandle camera, const Ray &ray,
const SampledSpectrum &beta) {
return Vertex(VertexType::Camera, EndpointInteraction(camera, ray), beta);
}
inline Vertex Vertex::CreateCamera(CameraHandle camera, const Interaction &it,
const SampledSpectrum &beta) {
return Vertex(VertexType::Camera, EndpointInteraction(it, camera), beta);
}
inline Vertex Vertex::CreateLight(LightHandle light, const Ray &ray,
const SampledSpectrum &Le, Float pdf) {
Vertex v(VertexType::Light, EndpointInteraction(light, ray), Le);
v.pdfFwd = pdf;
return v;
}
inline Vertex Vertex::CreateLight(LightHandle light, const Ray &ray,
const Interaction &intr, const SampledSpectrum &Le,
Float pdf) {
Vertex v(VertexType::Light, EndpointInteraction(light, ray, intr), Le);
v.pdfFwd = pdf;
return v;
}
inline Vertex Vertex::CreateSurface(const SurfaceInteraction &si, const BSDF &bsdf,
const SampledSpectrum &beta, Float pdf,
const Vertex &prev) {
Vertex v(si, bsdf, beta);
v.pdfFwd = prev.ConvertDensity(pdf, v);
return v;
}
inline Vertex Vertex::CreateMedium(const MediumInteraction &mi,
const SampledSpectrum &beta, Float pdf,
const Vertex &prev) {
Vertex v(mi, beta);
v.pdfFwd = prev.ConvertDensity(pdf, v);
return v;
}
inline Vertex Vertex::CreateLight(const EndpointInteraction &ei,
const SampledSpectrum &beta, Float pdf) {
Vertex v(VertexType::Light, ei, beta);
v.pdfFwd = pdf;
return v;
}
// BDPT Utility Functions
inline int BufferIndex(int s, int t) {
int above = s + t - 2;
return s + above * (5 + above) / 2;
}
int GenerateCameraSubpath(const Integrator &integrator, const RayDifferential &ray,
SampledWavelengths &lambda, SamplerHandle sampler,
ScratchBuffer &scratchBuffer, int maxDepth, CameraHandle camera,
Vertex *path, bool regularize) {
if (maxDepth == 0)
return 0;
SampledSpectrum beta(1.f);
// Generate first vertex on camera subpath and start random walk
Float pdfPos, pdfDir;
path[0] = Vertex::CreateCamera(camera, ray, beta);
camera.PDF_We(ray, &pdfPos, &pdfDir);
return RandomWalk(integrator, lambda, ray, sampler, camera, scratchBuffer, beta,
pdfDir, maxDepth - 1, TransportMode::Radiance, path + 1,
regularize) +
1;
}
int GenerateLightSubpath(const Integrator &integrator, SampledWavelengths &lambda,
SamplerHandle sampler, CameraHandle camera,
ScratchBuffer &scratchBuffer, int maxDepth, Float time,
LightSamplerHandle lightSampler, Vertex *path, bool regularize) {
if (maxDepth == 0)
return 0;
// Sample initial ray for light subpath
pstd::optional<SampledLight> sampledLight = lightSampler.Sample(sampler.Get1D());
if (!sampledLight)
return 0;
LightHandle light = sampledLight->light;
Float lightPDF = sampledLight->pdf;
LightLeSample les = light.SampleLe(sampler.Get2D(), sampler.Get2D(), lambda, time);
if (!les || les.pdfPos == 0 || les.pdfDir == 0 || !les.L)
return 0;
RayDifferential ray(les.ray);
// Generate first vertex on light subpath and start random walk
path[0] = les.intr ? Vertex::CreateLight(light, ray, *les.intr, les.L,
les.pdfPos * lightPDF)
: Vertex::CreateLight(light, ray, les.L, les.pdfPos * lightPDF);
SampledSpectrum beta =
les.L * les.AbsCosTheta(ray.d) / (lightPDF * les.pdfPos * les.pdfDir);
VLOG(2, "Starting light subpath. Ray: %s, Le %s, beta %s, pdfPos %f, pdfDir %f", ray,
les.L, beta, les.pdfPos, les.pdfDir);
int nVertices = RandomWalk(integrator, lambda, ray, sampler, camera, scratchBuffer,
beta, les.pdfDir, maxDepth - 1, TransportMode::Importance,
path + 1, regularize);
// Correct subpath sampling densities for infinite area lights
if (path[0].IsInfiniteLight()) {
// Set spatial density of _path[1]_ for infinite area light
if (nVertices > 0) {
path[1].pdfFwd = les.pdfPos;
if (path[1].IsOnSurface())
path[1].pdfFwd *= AbsDot(ray.d, path[1].ng());
}
// Set spatial density of _path[0]_ for infinite area light
path[0].pdfFwd =
InfiniteLightDensity(integrator.infiniteLights, lightSampler, ray.d);
}
return nVertices + 1;
}
int RandomWalk(const Integrator &integrator, SampledWavelengths &lambda,
RayDifferential ray, SamplerHandle sampler, CameraHandle camera,
ScratchBuffer &scratchBuffer, SampledSpectrum beta, Float pdf,
int maxDepth, TransportMode mode, Vertex *path, bool regularize) {
if (maxDepth == 0)
return 0;
int bounces = 0;
bool anyNonSpecularBounces = false;
// Declare variables for forward and reverse probability densities
Float pdfFwd = pdf, pdfRev = 0;
while (true) {
// Attempt to create the next subpath vertex in _path_
VLOG(2, "Random walk. Bounces %d, beta %s, pdfFwd %f, pdfRef %f", bounces, beta,
pdfFwd, pdfRev);
if (!beta)
break;
// Trace a ray and sample the medium, if any
Vertex &vertex = path[bounces], &prev = path[bounces - 1];
pstd::optional<ShapeIntersection> si = integrator.Intersect(ray);
bool scattered = false, terminated = false;
if (ray.medium) {
Float tMax = si ? si->tHit : Infinity;
RNG rng(Hash(ray.d.x), Hash(ray.d.y));
ray.medium.SampleTmaj(
ray, tMax, rng, lambda, [&](const MediumSample &mediumSample) {
const SampledSpectrum &Tmaj = mediumSample.Tmaj;
if (!mediumSample.intr) {
beta *= Tmaj / Tmaj.Average();
return false; // onward to the surface path...
}
const MediumInteraction &intr = *mediumSample.intr;
const SampledSpectrum &sigma_a = intr.sigma_a;
const SampledSpectrum &sigma_s = intr.sigma_s;
Float pAbsorb = sigma_a[0] / intr.sigma_maj[0];
Float pScatter = sigma_s[0] / intr.sigma_maj[0];
Float pNull = std::max<Float>(0, 1 - pAbsorb - pScatter);
DCHECK_GE(1 - pAbsorb - pScatter, -1e-6);
Float um = sampler.Get1D();
int mode = SampleDiscrete({pAbsorb, pScatter, pNull}, um);
if (mode == 0) {
// absorption; done
terminated = true;
return false;
} else if (mode == 1) {
// scatter
beta *= Tmaj * sigma_s / (Tmaj * sigma_s).Average();
// Record medium interaction in _path_ and compute forward density
vertex = Vertex::CreateMedium(intr, beta, pdfFwd, prev);
if (++bounces >= maxDepth) {
terminated = true;
return false;
}
// Sample direction and compute reverse density at preceding
// vertex
PhaseFunctionSample ps =
intr.phase.Sample_p(-ray.d, sampler.Get2D());
if (!ps) {
terminated = true;
return false;
}
pdfFwd = pdfRev = ps.pdf;
beta *= ps.p / pdfFwd;
ray = intr.SpawnRay(ps.wi);
anyNonSpecularBounces = true;
// Compute reverse area density at preceding vertex
prev.pdfRev = vertex.ConvertDensity(pdfRev, prev);
scattered = true;
return false;
} else {
// null scatter
SampledSpectrum sigma_n = intr.sigma_n();
beta *= Tmaj * sigma_n / (Tmaj * sigma_n).Average();
return true;
}
});
}
if (terminated)
return bounces;
if (scattered)
continue;
// Handle surface interaction for path generation
if (!si) {
// Capture escaped rays when tracing from the camera
if (mode == TransportMode::Radiance) {
vertex = Vertex::CreateLight(EndpointInteraction(ray), beta, pdfFwd);
++bounces;
}
break;
}
SurfaceInteraction &isect = si->intr;
// Compute scattering functions and skip over medium boundaries
BSDF bsdf = isect.GetBSDF(ray, lambda, camera, scratchBuffer, sampler);
if (!bsdf) {
isect.SkipIntersection(&ray, si->tHit);
continue;
}
// Possibly regularize the BSDF
if (regularize && anyNonSpecularBounces) {
++regularizedBSDFs;
bsdf.Regularize();
}
++totalBSDFs;
// Initialize _vertex_ with surface intersection information
vertex = Vertex::CreateSurface(isect, bsdf, beta, pdfFwd, prev);
if (++bounces >= maxDepth)
break;
// Sample BSDF at current vertex and compute reverse probability
Vector3f wo = isect.wo;
Float u = sampler.Get1D();
BSDFSample bs = bsdf.Sample_f(wo, u, sampler.Get2D(), mode);
if (!bs)
break;
pdfFwd = bs.pdf;
anyNonSpecularBounces |= !bs.IsSpecular();
beta *= bs.f * AbsDot(bs.wi, isect.shading.n) / bs.pdf;
// TODO: confirm. I believe that ~mode is right. Interestingly,
// it makes no difference in the test suite either way.
pdfRev = bsdf.PDF(bs.wi, wo, ~mode);
if (bs.IsSpecular()) {
vertex.delta = true;
pdfRev = pdfFwd = 0;
}
VLOG(2, "Random walk beta after shading normal correction %s", beta);
ray = isect.SpawnRay(ray, bsdf, bs.wi, bs.flags);
// Compute reverse area density at preceding vertex
prev.pdfRev = vertex.ConvertDensity(pdfRev, prev);
}
return bounces;
}
SampledSpectrum G(const Integrator &integrator, SamplerHandle sampler, const Vertex &v0,
const Vertex &v1, const SampledWavelengths &lambda) {
Vector3f d = v0.p() - v1.p();
Float g = 1 / LengthSquared(d);
d *= std::sqrt(g);
if (v0.IsOnSurface())
g *= AbsDot(v0.ns(), d);
if (v1.IsOnSurface())
g *= AbsDot(v1.ns(), d);
RNG rng(Hash(v0.p()), Hash(v1.p()));
return g * integrator.Tr(v0.GetInteraction(), v1.GetInteraction(), lambda, rng);
}
Float MISWeight(const Integrator &integrator, Vertex *lightVertices,
Vertex *cameraVertices, Vertex &sampled, int s, int t,
LightSamplerHandle lightSampler) {
if (s + t == 2)
return 1;
Float sumRi = 0;
// Define helper function _remap0_ that deals with Dirac delta functions
auto remap0 = [](float f) -> Float { return f != 0 ? f : 1; };
// Temporarily update vertex properties for current strategy
// Look up connection vertices and their predecessors
Vertex *qs = s > 0 ? &lightVertices[s - 1] : nullptr,
*pt = t > 0 ? &cameraVertices[t - 1] : nullptr,
*qsMinus = s > 1 ? &lightVertices[s - 2] : nullptr,
*ptMinus = t > 1 ? &cameraVertices[t - 2] : nullptr;
// Update sampled vertex for $s=1$ or $t=1$ strategy
ScopedAssignment<Vertex> a1;
if (s == 1)
a1 = {qs, sampled};
else if (t == 1)
a1 = {pt, sampled};
// Mark connection vertices as non-degenerate
ScopedAssignment<bool> a2, a3;
if (pt)
a2 = {&pt->delta, false};
if (qs)
a3 = {&qs->delta, false};
// Update reverse density of vertex $\pt{}_{t-1}$
ScopedAssignment<Float> a4;
if (pt)
a4 = {&pt->pdfRev, s > 0 ? qs->PDF(integrator, qsMinus, *pt)
: pt->PdfLightOrigin(integrator.infiniteLights, *ptMinus,
lightSampler)};
// Update reverse density of vertex $\pt{}_{t-2}$
ScopedAssignment<Float> a5;
if (ptMinus)
a5 = {&ptMinus->pdfRev, s > 0 ? pt->PDF(integrator, qs, *ptMinus)
: pt->PdfLight(integrator, *ptMinus)};
// Update reverse density of vertices $\pq{}_{s-1}$ and $\pq{}_{s-2}$
ScopedAssignment<Float> a6;
if (qs)
a6 = {&qs->pdfRev, pt->PDF(integrator, ptMinus, *qs)};
ScopedAssignment<Float> a7;
if (qsMinus)
a7 = {&qsMinus->pdfRev, qs->PDF(integrator, pt, *qsMinus)};
// Consider hypothetical connection strategies along the camera subpath
Float ri = 1;
for (int i = t - 1; i > 0; --i) {
ri *= remap0(cameraVertices[i].pdfRev) / remap0(cameraVertices[i].pdfFwd);
if (!cameraVertices[i].delta && !cameraVertices[i - 1].delta)
sumRi += ri;
}
// Consider hypothetical connection strategies along the light subpath
ri = 1;
for (int i = s - 1; i >= 0; --i) {
ri *= remap0(lightVertices[i].pdfRev) / remap0(lightVertices[i].pdfFwd);
bool deltaLightvertex =
i > 0 ? lightVertices[i - 1].delta : lightVertices[0].IsDeltaLight();
if (!lightVertices[i].delta && !deltaLightvertex)
sumRi += ri;
}
return 1 / (1 + sumRi);
}
Float InfiniteLightDensity(const std::vector<LightHandle> &infiniteLights,
LightSamplerHandle lightSampler, const Vector3f &w) {
Float pdf = 0;
for (const auto &light : infiniteLights)
pdf += light.PDF_Li(Interaction(), -w) * lightSampler.PDF(light);
return pdf;
}
// BDPT Method Definitions
void BDPTIntegrator::Render() {
// Allocate buffers for debug visualization
if (visualizeStrategies || visualizeWeights) {
const int bufferCount = (1 + maxDepth) * (6 + maxDepth) / 2;
weightFilms.resize(bufferCount);
for (int depth = 0; depth <= maxDepth; ++depth) {
for (int s = 0; s <= depth + 2; ++s) {
int t = depth + 2 - s;
if (t == 0 || (s == 1 && t == 1))
continue;
std::string filename =
StringPrintf("bdpt_d%02i_s%02i_t%02i.exr", depth, s, t);
// FIXME: leaks
weightFilms[BufferIndex(s, t)] = new RGBFilm(
camera.GetFilm().FullResolution(),
Bounds2i(Point2i(0, 0), camera.GetFilm().FullResolution()),
new BoxFilter, // FIXME: leaks
camera.GetFilm().Diagonal() * 1000, filename, 1.f,
RGBColorSpace::sRGB);
}
}
}
RayIntegrator::Render();
// Write buffers for debug visualization
if (visualizeStrategies || visualizeWeights) {
const Float invSampleCount = 1.0f / samplerPrototype.SamplesPerPixel();
for (size_t i = 0; i < weightFilms.size(); ++i) {
ImageMetadata metadata;
if (weightFilms[i])
weightFilms[i].WriteImage(metadata, invSampleCount);
}
weightFilms.clear();
}
}
SampledSpectrum BDPTIntegrator::Li(RayDifferential ray, SampledWavelengths &lambda,
SamplerHandle sampler, ScratchBuffer &scratchBuffer,
VisibleSurface *visibleSurface) const {
// Trace the camera and light subpaths
Vertex *cameraVertices = scratchBuffer.Alloc<Vertex[]>(maxDepth + 2);
int nCamera = GenerateCameraSubpath(*this, ray, lambda, sampler, scratchBuffer,
maxDepth + 2, camera, cameraVertices, regularize);
Vertex *lightVertices = scratchBuffer.Alloc<Vertex[]>(maxDepth + 1);
int nLight = GenerateLightSubpath(*this, lambda, sampler, camera, scratchBuffer,
maxDepth + 1, cameraVertices[0].time(),
lightSampler, lightVertices, regularize);
SampledSpectrum L(0.f);
// Execute all BDPT connection strategies
for (int t = 1; t <= nCamera; ++t) {
for (int s = 0; s <= nLight; ++s) {
int depth = t + s - 2;
if ((s == 1 && t == 1) || depth < 0 || depth > maxDepth)
continue;
// Execute the $(s, t)$ connection strategy and update _L_
pstd::optional<Point2f> pFilmNew;
Float misWeight = 0.f;
SampledSpectrum Lpath =
ConnectBDPT(*this, lambda, lightVertices, cameraVertices, s, t,
lightSampler, camera, sampler, &pFilmNew, &misWeight);
VLOG(2, "Connect bdpt s: %d, t: %d, Lpath: %s, misWeight: %f", s, t, Lpath,
misWeight);
if (visualizeStrategies || visualizeWeights) {
SampledSpectrum value;
if (visualizeStrategies)
value = misWeight == 0 ? SampledSpectrum(0.) : Lpath / misWeight;
if (visualizeWeights)
value = Lpath;
CHECK(pFilmNew.has_value());
weightFilms[BufferIndex(s, t)].AddSplat(*pFilmNew, value, lambda);
}
if (t != 1)
L += Lpath;
else if (Lpath) {
CHECK(pFilmNew.has_value());
camera.GetFilm().AddSplat(*pFilmNew, Lpath, lambda);
}
}
}
return L;
}
SampledSpectrum ConnectBDPT(const Integrator &integrator, SampledWavelengths &lambda,
Vertex *lightVertices, Vertex *cameraVertices, int s, int t,
LightSamplerHandle lightSampler, CameraHandle camera,
SamplerHandle sampler, pstd::optional<Point2f> *pRaster,
Float *misWeightPtr) {
SampledSpectrum L(0.f);
// Ignore invalid connections related to infinite area lights
if (t > 1 && s != 0 && cameraVertices[t - 1].type == VertexType::Light)
return SampledSpectrum(0.f);
// Perform connection and write contribution to _L_
Vertex sampled;
if (s == 0) {
// Interpret the camera subpath as a complete path
const Vertex &pt = cameraVertices[t - 1];
if (pt.IsLight())
L = pt.Le(integrator.infiniteLights, cameraVertices[t - 2], lambda) * pt.beta;
DCHECK(!L.HasNaNs());
} else if (t == 1) {
// Sample a point on the camera and connect it to the light subpath
const Vertex &qs = lightVertices[s - 1];
if (qs.IsConnectible()) {
pstd::optional<CameraWiSample> cs =
camera.SampleWi(qs.GetInteraction(), sampler.Get2D(), lambda);
if (cs) {
*pRaster = cs->pRaster;
// Initialize dynamically sampled vertex and _L_ for $t=1$ case
sampled = Vertex::CreateCamera(camera, cs->pLens, cs->Wi / cs->pdf);
L = qs.beta * qs.f(sampled, TransportMode::Importance) * sampled.beta;
if (qs.IsOnSurface())
L *= AbsDot(cs->wi, qs.ns());
DCHECK(!L.HasNaNs());
// Only check visibility after we know that the path would
// make a non-zero contribution.
if (L) {
RNG rng(Hash(cs->pRaster), Hash(cs->pLens));
L *= integrator.Tr(cs->pRef, cs->pLens, lambda, rng);
}
}
}
} else if (s == 1) {
// Sample a point on a light and connect it to the camera subpath
const Vertex &pt = cameraVertices[t - 1];
if (pt.IsConnectible()) {
pstd::optional<SampledLight> sampledLight =
lightSampler.Sample(sampler.Get1D());
if (sampledLight) {
LightHandle light = sampledLight->light;
Float lightPDF = sampledLight->pdf;
LightSampleContext ctx;
if (pt.IsOnSurface())
ctx = LightSampleContext(pt.GetInteraction().AsSurface());
else
ctx = LightSampleContext(pt.GetInteraction());
LightLiSample lightWeight = light.SampleLi(ctx, sampler.Get2D(), lambda);
if (lightWeight) {
EndpointInteraction ei(lightWeight.pLight, light);
sampled = Vertex::CreateLight(
ei, lightWeight.L / (lightWeight.pdf * lightPDF), 0);
sampled.pdfFwd = sampled.PdfLightOrigin(integrator.infiniteLights, pt,
lightSampler);
L = pt.beta * pt.f(sampled, TransportMode::Radiance) * sampled.beta;
if (pt.IsOnSurface())
L *= AbsDot(lightWeight.wi, pt.ns());
// Only check visibility if the path would carry radiance.
if (L) {
RNG rng(Hash(ctx.p()), Hash(ctx.n));
L *= integrator.Tr(pt.GetInteraction(), lightWeight.pLight,
lambda, rng);
}
}
}
}
} else {
// Handle all other bidirectional connection cases
const Vertex &qs = lightVertices[s - 1], &pt = cameraVertices[t - 1];
if (qs.IsConnectible() && pt.IsConnectible()) {
L = qs.beta * qs.f(pt, TransportMode::Importance) *
pt.f(qs, TransportMode::Radiance) * pt.beta;
VLOG(2,
"General connect s: %d, t: %d, qs: %s, pt: %s, qs.f(pt): %s, "
"pt.f(qs): %s, G: %s, dist^2: %f",
s, t, qs, pt, qs.f(pt, TransportMode::Importance),
pt.f(qs, TransportMode::Radiance),
G(integrator, sampler, qs, pt, lambda), DistanceSquared(qs.p(), pt.p()));
if (L)
L *= G(integrator, sampler, qs, pt, lambda);
}
}
++totalPaths;
if (!L)
++zeroRadiancePaths;
ReportValue(pathLength, s + t - 2);
// Compute MIS weight for connection strategy
Float misWeight = L ? MISWeight(integrator, lightVertices, cameraVertices, sampled, s,
t, lightSampler)
: 0.f;
VLOG(2, "MIS weight for (s,t) = (%d, %d) connection: %f", s, t, misWeight);
DCHECK(!std::isnan(misWeight));
L *= misWeight;
if (misWeightPtr != nullptr)
*misWeightPtr = misWeight;
return L;
}
std::string BDPTIntegrator::ToString() const {
return StringPrintf("[ BDPTIntegrator maxDepth: %d visualizeStrategies: %s "
"visualizeWeights: %s lightSampleStrategy: %s regularize: %s "
"lightSampler: %s ]",
maxDepth, visualizeStrategies, visualizeWeights,
lightSampleStrategy, regularize, lightSampler);
}
std::unique_ptr<BDPTIntegrator> BDPTIntegrator::Create(
const ParameterDictionary &parameters, CameraHandle camera, SamplerHandle sampler,
PrimitiveHandle aggregate, std::vector<LightHandle> lights, const FileLoc *loc) {
int maxDepth = parameters.GetOneInt("maxdepth", 5);
bool visualizeStrategies = parameters.GetOneBool("visualizestrategies", false);
bool visualizeWeights = parameters.GetOneBool("visualizeweights", false);
if ((visualizeStrategies || visualizeWeights) && maxDepth > 5) {
Warning(loc, "visualizestrategies/visualizeweights was enabled, limiting "
"maxdepth to 5");
maxDepth = 5;
}
std::string lightStrategy = parameters.GetOneString("lightsampler", "power");
bool regularize = parameters.GetOneBool("regularize", false);
return std::make_unique<BDPTIntegrator>(camera, sampler, aggregate, lights, maxDepth,
visualizeStrategies, visualizeWeights,
lightStrategy, regularize);
}
STAT_PERCENT("Integrator/Acceptance rate", acceptedMutations, totalMutations);
// MLTIntegrator Method Definitions
SampledSpectrum MLTIntegrator::L(ScratchBuffer &scratchBuffer, MLTSampler &sampler,
int depth, Point2f *pRaster,
SampledWavelengths *lambda) {
sampler.StartStream(cameraStreamIndex);
// Determine the number of available strategies and pick a specific one
int s, t, nStrategies;
if (depth == 0) {
nStrategies = 1;
s = 0;
t = 2;
} else {
nStrategies = depth + 2;
s = std::min<int>(sampler.Get1D() * nStrategies, nStrategies - 1);
t = nStrategies - s;
}
// Sample wavelengths for MLT path
if (Options->disableWavelengthJitter)
*lambda = camera.GetFilm().SampleWavelengths(0.5);
else
*lambda = camera.GetFilm().SampleWavelengths(sampler.Get1D());
// Generate a camera subpath with exactly _t_ vertices
Vertex *cameraVertices = scratchBuffer.Alloc<Vertex[]>(t);
Bounds2f sampleBounds = camera.GetFilm().SampleBounds();
*pRaster = sampleBounds.Lerp(sampler.Get2D());
CameraSample cameraSample;
cameraSample.pFilm = *pRaster;
cameraSample.time = sampler.Get1D();
cameraSample.pLens = sampler.Get2D();
pstd::optional<CameraRayDifferential> crd =
camera.GenerateRayDifferential(cameraSample, *lambda);
if (!crd || !crd->weight)
return SampledSpectrum(0.f);
Float rayDiffScale =
std::max<Float>(.125, 1 / std::sqrt((Float)sampler.SamplesPerPixel()));
crd->ray.ScaleDifferentials(rayDiffScale);
if (GenerateCameraSubpath(*this, crd->ray, *lambda, &sampler, scratchBuffer, t,
camera, cameraVertices, regularize) != t)
return SampledSpectrum(0.f);
// Generate a light subpath with exactly _s_ vertices
sampler.StartStream(lightStreamIndex);
Vertex *lightVertices = scratchBuffer.Alloc<Vertex[]>(s);
if (GenerateLightSubpath(*this, *lambda, &sampler, camera, scratchBuffer, s,
cameraVertices[0].time(), lightSampler, lightVertices,
regularize) != s)
return SampledSpectrum(0.f);
// Execute connection strategy and return the radiance estimate
sampler.StartStream(connectionStreamIndex);
pstd::optional<Point2f> pRasterNew;
SampledSpectrum L = ConnectBDPT(*this, *lambda, lightVertices, cameraVertices, s, t,
lightSampler, camera, &sampler, &pRasterNew) *
nStrategies;
if (pRasterNew.has_value())
*pRaster = *pRasterNew;
return L;
}
void MLTIntegrator::Render() {
// Handle statistics and debugstart for MLTIntegrator
if (Options->recordPixelStatistics)
StatsEnablePixelStats(camera.GetFilm().PixelBounds(),
RemoveExtension(camera.GetFilm().GetFilename()));
if (!Options->debugStart.empty()) {
std::vector<std::string> c = SplitString(Options->debugStart, ',');
if (c.empty())
ErrorExit("Didn't find comma-separated values after --debugstart: %s",
Options->debugStart);
int depth;
if (!Atoi(c[0], &depth))
ErrorExit("Unable to decode first --debugstart value: %s", c[0]);
pstd::span<const std::string> span = pstd::MakeSpan(c);
span.remove_prefix(1);
DebugMLTSampler sampler = DebugMLTSampler::Create(span, nSampleStreams);
Point2f pRaster;
SampledWavelengths lambda;
ScratchBuffer scratchBuffer(65536);
(void)L(scratchBuffer, sampler, depth, &pRaster, &lambda);
return;
}
thread_local MLTSampler *threadSampler = nullptr;
thread_local int threadDepth;
CheckCallbackScope _([&]() -> std::string {
return StringPrintf("Rendering failed. Debug with --debugstart %d,%s\"\n",
threadDepth, threadSampler->DumpState());
});
// Generate bootstrap samples and compute normalization constant $b$
Timer timer;
int nBootstrapSamples = nBootstrap * (maxDepth + 1);
std::vector<Float> bootstrapWeights(nBootstrapSamples, 0);
if (!lights.empty()) {
// Allocate scratch buffers for bootstrap samples
std::vector<ScratchBuffer> bootstrapScratchBuffers;
for (int i = 0; i < MaxThreadIndex(); ++i)
bootstrapScratchBuffers.push_back(ScratchBuffer(65536));
ProgressReporter progress(nBootstrap, "Generating bootstrap paths",
Options->quiet);
ParallelFor(0, nBootstrap, [&](int64_t start, int64_t end) {
ScratchBuffer &scratchBuffer = bootstrapScratchBuffers[ThreadIndex];
for (int64_t i = start; i < end; ++i) {
// Generate _i_th bootstrap sample
for (int depth = 0; depth <= maxDepth; ++depth) {
int rngIndex = i * (maxDepth + 1) + depth;
MLTSampler sampler(mutationsPerPixel, rngIndex, sigma,
largeStepProbability, nSampleStreams);
threadSampler = &sampler;
threadDepth = depth;
Point2f pRaster;
SampledWavelengths lambda;
bootstrapWeights[rngIndex] =
L(scratchBuffer, sampler, depth, &pRaster, &lambda).Average();
scratchBuffer.Reset();
}
}
progress.Update(end - start);
});
progress.Done();
}
AliasTable bootstrapTable(bootstrapWeights);
Float b = std::accumulate(bootstrapWeights.begin(), bootstrapWeights.end(), 0.) /
bootstrapWeights.size() * (maxDepth + 1);
// Set up connection to display server, if enabled
if (!Options->displayServer.empty()) {
FilmHandle film = camera.GetFilm();
Bounds2i pixelBounds = film.PixelBounds();
DisplayDynamic(film.GetFilename(), Point2i(pixelBounds.Diagonal()),
{"R", "G", "B"},
[=](Bounds2i b, pstd::span<pstd::span<Float>> displayValue) {
int index = 0;
for (Point2i p : b) {
RGB rgb = film.GetPixelRGB(pixelBounds.pMin + p);
for (int c = 0; c < 3; ++c)
displayValue[c][index] = rgb[c];
++index;
}
});
}
// Run _nChains_ Markov chains in parallel
FilmHandle film = camera.GetFilm();
int64_t nTotalMutations =
(int64_t)mutationsPerPixel * (int64_t)film.SampleBounds().Area();
if (!lights.empty()) {
// Allocate scratch buffers for MLT Markov chains
std::vector<ScratchBuffer> threadScratchBuffers;
for (int i = 0; i < MaxThreadIndex(); ++i)
threadScratchBuffers.push_back(ScratchBuffer(65536));
ProgressReporter progress(nChains, "Rendering", Options->quiet);
ParallelFor(0, nChains, [&](int i) {
int64_t nChainMutations =
std::min((i + 1) * nTotalMutations / nChains, nTotalMutations) -
i * nTotalMutations / nChains;
// Follow {i}th Markov chain for _nChainMutations_
ScratchBuffer &scratchBuffer = threadScratchBuffers[ThreadIndex];
// Select initial state from the set of bootstrap samples
RNG rng(i);
int bootstrapIndex = bootstrapTable.Sample(rng.Uniform<Float>());
int depth = bootstrapIndex % (maxDepth + 1);
threadDepth = depth;
// Initialize local variables for selected state
MLTSampler sampler(mutationsPerPixel, bootstrapIndex, sigma,
largeStepProbability, nSampleStreams);
threadSampler = &sampler;
Point2f pCurrent;
SampledWavelengths lambdaCurrent;
SampledSpectrum LCurrent =
L(scratchBuffer, sampler, depth, &pCurrent, &lambdaCurrent);
// Run the Markov chain for _nChainMutations_ steps
for (int64_t j = 0; j < nChainMutations; ++j) {
StatsReportPixelStart(Point2i(pCurrent));
sampler.StartIteration();
Point2f pProposed;
SampledWavelengths lambdaProposed;
SampledSpectrum LProposed =
L(scratchBuffer, sampler, depth, &pProposed, &lambdaProposed);
// Compute acceptance probability for proposed sample
Float accept =
std::min<Float>(1, LProposed.Average() / LCurrent.Average());
// Splat both current and proposed samples to _film_
if (accept > 0)
film.AddSplat(pProposed, LProposed * accept / LProposed.Average(),
lambdaProposed);
film.AddSplat(pCurrent, LCurrent * (1 - accept) / LCurrent.Average(),
lambdaCurrent);
// Accept or reject the proposal
if (rng.Uniform<Float>() < accept) {
StatsReportPixelEnd(Point2i(pCurrent));
StatsReportPixelStart(Point2i(pProposed));
pCurrent = pProposed;
LCurrent = LProposed;
lambdaCurrent = lambdaProposed;
sampler.Accept();
++acceptedMutations;
} else
sampler.Reject();
++totalMutations;
scratchBuffer.Reset();
StatsReportPixelEnd(Point2i(pCurrent));
}
progress.Update(1);
});
progress.Done();
}
// Store final image computed with MLT
ImageMetadata metadata;
metadata.renderTimeSeconds = timer.ElapsedSeconds();
camera.InitMetadata(&metadata);
camera.GetFilm().WriteImage(metadata, b / mutationsPerPixel);
}
std::string MLTIntegrator::ToString() const {
return StringPrintf("[ MLTIntegrator camera: %s maxDepth: %d nBootstrap: %d "
"nChains: %d mutationsPerPixel: %d sigma: %f "
"largeStepProbability: %f lightSampler: %s regularize: %s ]",
camera, maxDepth, nBootstrap, nChains, mutationsPerPixel, sigma,
largeStepProbability, lightSampler, regularize);
}
std::unique_ptr<MLTIntegrator> MLTIntegrator::Create(
const ParameterDictionary &parameters, CameraHandle camera, PrimitiveHandle aggregate,
std::vector<LightHandle> lights, const FileLoc *loc) {
int maxDepth = parameters.GetOneInt("maxdepth", 5);
int nBootstrap = parameters.GetOneInt("bootstrapsamples", 100000);
int64_t nChains = parameters.GetOneInt("chains", 1000);
int mutationsPerPixel = parameters.GetOneInt("mutationsperpixel", 100);
Float largeStepProbability = parameters.GetOneFloat("largestepprobability", 0.3f);
Float sigma = parameters.GetOneFloat("sigma", .01f);
if (Options->quickRender) {
mutationsPerPixel = std::max(1, mutationsPerPixel / 16);
nBootstrap = std::max(1, nBootstrap / 16);
}
bool regularize = parameters.GetOneBool("regularize", false);
return std::make_unique<MLTIntegrator>(camera, aggregate, lights, maxDepth,
nBootstrap, nChains, mutationsPerPixel, sigma,
largeStepProbability, regularize);
}
STAT_RATIO("Stochastic Progressive Photon Mapping/Visible points checked per photon "
"intersection",
visiblePointsChecked, totalPhotonSurfaceInteractions);
STAT_COUNTER("Stochastic Progressive Photon Mapping/Photon paths followed", photonPaths);
STAT_INT_DISTRIBUTION(
"Stochastic Progressive Photon Mapping/Grid cells per visible point",
gridCellsPerVisiblePoint);
STAT_MEMORY_COUNTER("Memory/SPPM Pixels", pixelMemoryBytes);
STAT_MEMORY_COUNTER("Memory/SPPM BSDF and Grid Memory", sppmMemoryArenaBytes);
// SPPMPixel Definition
struct SPPMPixel {
// SPPMPixel Public Members
Float radius = 0;
RGB Ld;
struct VisiblePoint {
// VisiblePoint Public Methods
VisiblePoint() = default;
VisiblePoint(const Point3f &p, const Vector3f &wo, const BSDF &bsdf,
const SampledSpectrum &beta)
: p(p), wo(wo), bsdf(bsdf), beta(beta) {}
Point3f p;
Vector3f wo;
BSDF bsdf;
SampledSpectrum beta;
} vp;
AtomicFloat Phi[NSpectrumSamples];
std::atomic<int> M{0};
Float N = 0;
RGB tau;
};
// SPPMPixelListNode Definition
struct SPPMPixelListNode {
SPPMPixel *pixel;
SPPMPixelListNode *next;
};
// SPPM Utility Functions
static bool ToGrid(const Point3f &p, const Bounds3f &bounds, const int gridRes[3],
Point3i *pi) {
bool inBounds = true;
Vector3f pg = bounds.Offset(p);
for (int i = 0; i < 3; ++i) {
(*pi)[i] = (int)(gridRes[i] * pg[i]);
inBounds &= ((*pi)[i] >= 0 && (*pi)[i] < gridRes[i]);
(*pi)[i] = Clamp((*pi)[i], 0, gridRes[i] - 1);
}
return inBounds;
}
inline unsigned int hash(const Point3i &p, int hashSize) {
return Hash(p.x, p.y, p.z) % hashSize;
}
// SPPM Method Definitions
void SPPMIntegrator::Render() {
// Initialize local variables for _SPPMIntegrator::Render()_
if (Options->recordPixelStatistics)
StatsEnablePixelStats(camera.GetFilm().PixelBounds(),
RemoveExtension(camera.GetFilm().GetFilename()));
// Allocate samplers for SPPM rendering
std::unique_ptr<pstd::vector<DigitPermutation>> digitPermutations(
ComputeRadicalInversePermutations(digitPermutationsSeed));
HaltonSampler sampler(nIterations, camera.GetFilm().FullResolution());
std::vector<SamplerHandle> tileSamplers =
sampler.Clone(MaxThreadIndex(), Allocator());
// Initialize _pixelBounds_ and _pixels_ array for SPPM
Bounds2i pixelBounds = camera.GetFilm().PixelBounds();
CHECK(!pixelBounds.IsEmpty());
int nPixels = pixelBounds.Area();
Array2D<SPPMPixel> pixels(pixelBounds);
for (SPPMPixel &p : pixels)
p.radius = initialSearchRadius;
const Float invSqrtSPP = 1.f / std::sqrt(nIterations);
pixelMemoryBytes += pixels.size() * sizeof(SPPMPixel);
// Create light samplers for SPPM rendering
BVHLightSampler directLightSampler(lights, Allocator());
PowerLightSampler shootLightSampler(lights, Allocator());
ProgressReporter progress(2 * nIterations, "Rendering", Options->quiet);
std::vector<ScratchBuffer> perThreadScratchBuffers;
for (int i = 0; i < MaxThreadIndex(); ++i)
// TODO: size this
perThreadScratchBuffers.push_back(ScratchBuffer(nPixels * 1024));
for (int iter = 0; iter < nIterations; ++iter) {
// Generate SPPM visible points
// Sample wavelengths for SPPM pass
SampledWavelengths lambda =
Options->disableWavelengthJitter
? camera.GetFilm().SampleWavelengths(0.5)
: camera.GetFilm().SampleWavelengths(RadicalInverse(1, iter));
{
ParallelFor2D(pixelBounds, [&](Bounds2i tileBounds) {
ScratchBuffer &scratchBuffer = perThreadScratchBuffers[ThreadIndex];
SamplerHandle &tileSampler = tileSamplers[ThreadIndex];
// Follow camera paths for _tile_ in image for SPPM
for (Point2i pPixel : tileBounds) {
// Prepare _tileSampler_ for _pPixel_
tileSampler.StartPixelSample(pPixel, iter);
// Generate camera ray for pixel for SPPM
FilterHandle filter = camera.GetFilm().GetFilter();
CameraSample cameraSample =
GetCameraSample(tileSampler, pPixel, filter);
pstd::optional<CameraRayDifferential> crd =
camera.GenerateRayDifferential(cameraSample, lambda);
if (!crd || !crd->weight)
continue;
SampledSpectrum beta = crd->weight;
RayDifferential &ray = crd->ray;
if (!Options->disablePixelJitter)
ray.ScaleDifferentials(invSqrtSPP);
// Follow camera ray path until a visible point is created
SPPMPixel &pixel = pixels[pPixel];
Float etaScale = 1;
bool specularBounce = false, anyNonSpecularBounces = false;
for (int depth = 0; depth < maxDepth; ++depth) {
++totalPhotonSurfaceInteractions;
pstd::optional<ShapeIntersection> si = Intersect(ray);
if (!si) {
// Accumulate light contributions for ray with no intersection
if (depth == 0) {
for (const auto &light : infiniteLights) {
SampledSpectrum L = beta * light.Le(ray, lambda);
pixel.Ld += L.ToRGB(lambda, *colorSpace);
}
}
break;
}
// Process SPPM camera ray intersection
// Compute BSDF at SPPM camera ray intersection
SurfaceInteraction &isect = si->intr;
BSDF bsdf =
isect.GetBSDF(ray, lambda, camera, scratchBuffer, &sampler);
if (!bsdf) {
isect.SkipIntersection(&ray, si->tHit);
--depth;
continue;
}
// Possibly regularize the BSDF
if (regularize && anyNonSpecularBounces) {
++regularizedBSDFs;
bsdf.Regularize();
}
++totalBSDFs;
// Accumulate direct illumination at SPPM camera ray intersection
Vector3f wo = -ray.d;
if (depth == 0 || specularBounce) {
SampledSpectrum L = beta * isect.Le(wo, lambda);
pixel.Ld += L.ToRGB(lambda, *colorSpace);
}
SampledSpectrum Ld = SampleLd(isect, bsdf, lambda, tileSampler,
&directLightSampler);
pixel.Ld += (beta * Ld).ToRGB(lambda, *colorSpace);
// Possibly create visible point and end camera path
if (bsdf.IsDiffuse() ||
(bsdf.IsGlossy() && depth == maxDepth - 1)) {
pixel.vp = {isect.p(), wo, bsdf, beta};
break;
}
// Spawn ray from SPPM camera path vertex
if (depth < maxDepth - 1) {
Float u = tileSampler.Get1D();
BSDFSample bs = bsdf.Sample_f(wo, u, tileSampler.Get2D());
if (!bs)
break;
specularBounce = bs.IsSpecular();
anyNonSpecularBounces |= !bs.IsSpecular();
if (bs.IsTransmission())
etaScale *= Sqr(bsdf.eta);
beta *= bs.f * AbsDot(bs.wi, isect.shading.n) / bs.pdf;
SampledSpectrum rrBeta = beta * etaScale;
if (rrBeta.MaxComponentValue() < 1) {
Float q =
std::max<Float>(.05f, 1 - rrBeta.MaxComponentValue());
if (tileSampler.Get1D() < q)
break;
beta /= 1 - q;
}
ray = isect.SpawnRay(ray, bsdf, bs.wi, bs.flags);
}
}
}
});
}
progress.Update();
// Create grid of all SPPM visible points
// Allocate grid for SPPM visible points
const int hashSize = NextPrime(nPixels);
std::vector<std::atomic<SPPMPixelListNode *>> grid(hashSize);
// Compute grid bounds for SPPM visible points
Bounds3f gridBounds;
Float maxRadius = 0.;
for (const SPPMPixel &pixel : pixels) {
if (!pixel.vp.beta)
continue;
Bounds3f vpBound = Expand(Bounds3f(pixel.vp.p), pixel.radius);
gridBounds = Union(gridBounds, vpBound);
maxRadius = std::max(maxRadius, pixel.radius);
}
// Compute resolution of SPPM grid in each dimension
int gridRes[3];
Vector3f diag = gridBounds.Diagonal();
Float maxDiag = MaxComponentValue(diag);
int baseGridRes = (int)(maxDiag / maxRadius);
for (int i = 0; i < 3; ++i)
gridRes[i] = std::max<int>(baseGridRes * diag[i] / maxDiag, 1);
// Add visible points to SPPM grid
ParallelFor2D(pixelBounds, [&](Bounds2i tileBounds) {
ScratchBuffer &scratchBuffer = perThreadScratchBuffers[ThreadIndex];
for (Point2i pPixel : tileBounds) {
SPPMPixel &pixel = pixels[pPixel];
if (pixel.vp.beta) {
// Add pixel's visible point to applicable grid cells
Float radius = pixel.radius;
Point3i pMin, pMax;
ToGrid(pixel.vp.p - Vector3f(radius, radius, radius), gridBounds,
gridRes, &pMin);
ToGrid(pixel.vp.p + Vector3f(radius, radius, radius), gridBounds,
gridRes, &pMax);
for (int z = pMin.z; z <= pMax.z; ++z)
for (int y = pMin.y; y <= pMax.y; ++y)
for (int x = pMin.x; x <= pMax.x; ++x) {
// Add visible point to grid cell $(x, y, z)$
int h = hash(Point3i(x, y, z), hashSize);
SPPMPixelListNode *node =
scratchBuffer.Alloc<SPPMPixelListNode>();
node->pixel = &pixel;
// Atomically add _node_ to the start of _grid[h]_'s
// linked list
node->next = grid[h];
while (!grid[h].compare_exchange_weak(node->next, node))
;
}
ReportValue(gridCellsPerVisiblePoint, (1 + pMax.x - pMin.x) *
(1 + pMax.y - pMin.y) *
(1 + pMax.z - pMin.z));
}
}
});
// Trace photons and accumulate contributions
// Create per-thread scratch buffers for photon shooting
std::vector<ScratchBuffer> photonShootScratchBuffers;
for (int i = 0; i < MaxThreadIndex(); ++i)
photonShootScratchBuffers.push_back(ScratchBuffer(65536));
ParallelFor(0, photonsPerIteration, [&](int64_t start, int64_t end) {
ScratchBuffer &scratchBuffer = photonShootScratchBuffers[ThreadIndex];
for (int64_t photonIndex = start; photonIndex < end; ++photonIndex) {
// Follow photon path for _photonIndex_
// Define sampling lambda functions for photon shooting
uint64_t haltonIndex =
(uint64_t)iter * (uint64_t)photonsPerIteration + photonIndex;
int haltonDim = 0;
auto Sample1D = [&]() {
Float u = ScrambledRadicalInverse(haltonDim, haltonIndex,
(*digitPermutations)[haltonDim]);
++haltonDim;
return u;
};
auto Sample2D = [&]() {
Point2f u(
ScrambledRadicalInverse(haltonDim, haltonIndex,
(*digitPermutations)[haltonDim]),
ScrambledRadicalInverse(haltonDim + 1, haltonIndex,
(*digitPermutations)[haltonDim + 1]));
haltonDim += 2;
return u;
};
// Choose light to shoot photon from
pstd::optional<SampledLight> sampledLight =
shootLightSampler.Sample(Sample1D());
if (!sampledLight)
continue;
LightHandle light = sampledLight->light;
Float lightPDF = sampledLight->pdf;
// Compute sample values for photon ray leaving light source
Point2f uLight0 = Sample2D();
Point2f uLight1 = Sample2D();
Float uLightTime = camera.SampleTime(Sample1D());
// Generate _photonRay_ from light source and initialize _beta_
LightLeSample les = light.SampleLe(uLight0, uLight1, lambda, uLightTime);
if (!les || les.pdfPos == 0 || les.pdfDir == 0 || !les.L)
continue;
RayDifferential photonRay = RayDifferential(les.ray);
SampledSpectrum beta = (les.AbsCosTheta(photonRay.d) * les.L) /
(lightPDF * les.pdfPos * les.pdfDir);
if (!beta)
continue;
// Follow photon path through scene and record intersections
SurfaceInteraction isect;
for (int depth = 0; depth < maxDepth; ++depth) {
pstd::optional<ShapeIntersection> si = Intersect(photonRay);
if (!si)
break;
SurfaceInteraction &isect = si->intr;
++totalPhotonSurfaceInteractions;
if (depth > 0) {
// Add photon contribution to nearby visible points
Point3i photonGridIndex;
if (ToGrid(isect.p(), gridBounds, gridRes, &photonGridIndex)) {
int h = hash(photonGridIndex, hashSize);
// Add photon contribution to visible points in _grid[h]_
for (SPPMPixelListNode *node =
grid[h].load(std::memory_order_relaxed);
node != nullptr; node = node->next) {
++visiblePointsChecked;
SPPMPixel &pixel = *node->pixel;
Float radius = pixel.radius;
if (DistanceSquared(pixel.vp.p, isect.p()) >
radius * radius)
continue;
// Update _pixel_ $\Phi$ and $M$ for nearby photon
Vector3f wi = -photonRay.d;
SampledSpectrum Phi =
beta * pixel.vp.bsdf.f(pixel.vp.wo, wi);
for (int i = 0; i < NSpectrumSamples; ++i)
pixel.Phi[i].Add(Phi[i]);
++pixel.M;
}
}
}
// Sample new photon ray direction
// Compute BSDF at photon intersection point
BSDF photonBSDF =
isect.GetBSDF(photonRay, lambda, camera, scratchBuffer, &sampler);
if (!photonBSDF) {
isect.SkipIntersection(&photonRay, si->tHit);
--depth;
continue;
}
// Sample BSDF _fr_ and direction _wi_ for reflected photon
Vector3f wo = -photonRay.d;
Float bsdfSample = Sample1D();
Point2f bsdfSample2 = Sample2D();
BSDFSample bs = photonBSDF.Sample_f(wo, bsdfSample, bsdfSample2,
TransportMode::Importance);
if (!bs)
break;
SampledSpectrum bnew =
beta * bs.f * AbsDot(bs.wi, isect.shading.n) / bs.pdf;
// Possibly terminate photon path with Russian roulette
Float q = std::max<Float>(
0, 1 - (bnew.MaxComponentValue() / beta.MaxComponentValue()));
if (Sample1D() < q)
break;
beta = bnew / (1 - q);
photonRay = RayDifferential(isect.SpawnRay(bs.wi));
}
scratchBuffer.Reset();
}
});
// CAN CUT THIS??
for (ScratchBuffer &scratchBuffer : perThreadScratchBuffers)
scratchBuffer.Reset();
progress.Update();
photonPaths += photonsPerIteration;
// Update pixel values from this pass's photons
ParallelFor2D(pixelBounds, [&](Point2i pPixel) {
SPPMPixel &p = pixels[pPixel];
int M = p.M.load();
if (M > 0) {
// Update pixel photon count, search radius, and $\tau$ from photons
Float gamma = (Float)2 / (Float)3;
Float Nnew = p.N + gamma * M;
Float Rnew = p.radius * std::sqrt(Nnew / (p.N + M));
SampledSpectrum Phi;
for (int j = 0; j < NSpectrumSamples; ++j)
Phi[j] = p.Phi[j];
RGB rgb = (p.vp.beta * Phi).ToRGB(lambda, *colorSpace);
p.tau = (p.tau + rgb) * (Rnew * Rnew) / (p.radius * p.radius);
p.N = Nnew;
p.radius = Rnew;
p.M = 0;
for (int j = 0; j < NSpectrumSamples; ++j)
p.Phi[j] = (Float)0;
}
// Reset _VisiblePoint_ in pixel
p.vp.beta = SampledSpectrum(0.);
p.vp.bsdf = BSDF();
});
// Periodically store SPPM image in film and write image
if (iter + 1 == nIterations || (iter + 1 <= 64 && IsPowerOf2(iter + 1)) ||
((iter + 1) % 64 == 0)) {
uint64_t Np = (uint64_t)(iter + 1) * (uint64_t)photonsPerIteration;
Image rgbImage(PixelFormat::Float, Point2i(pixelBounds.Diagonal()),
{"R", "G", "B"});
ParallelFor2D(pixelBounds, [&](Point2i pPixel) {
// Compute radiance _L_ for SPPM pixel _pixel_
const SPPMPixel &pixel = pixels[pPixel];
RGB L = pixel.Ld / (iter + 1);
L += pixel.tau / (Np * Pi * pixel.radius * pixel.radius);
Point2i pImage = Point2i(pPixel - pixelBounds.pMin);
rgbImage.SetChannels(pImage, {L.r, L.g, L.b});
});
ImageMetadata metadata;
metadata.renderTimeSeconds = progress.ElapsedSeconds();
metadata.samplesPerPixel = iter + 1;
metadata.pixelBounds = pixelBounds;
metadata.fullResolution = camera.GetFilm().FullResolution();
metadata.colorSpace = colorSpace;
camera.InitMetadata(&metadata);
rgbImage.Write(camera.GetFilm().GetFilename(), metadata);
// Write SPPM radius image, if requested
if (getenv("SPPM_RADIUS") != nullptr) {
Image rimg(PixelFormat::Float, Point2i(pixelBounds.Diagonal()),
{"Radius"});
Float minrad = 1e30f, maxrad = 0;
for (const SPPMPixel &p : pixels) {
minrad = std::min(minrad, p.radius);
maxrad = std::max(maxrad, p.radius);
}
fprintf(stderr, "iterations: %d (%.2f s) radius range: %f - %f\n",
iter + 1, progress.ElapsedSeconds(), minrad, maxrad);
int offset = 0;
for (Point2i pPixel : pixelBounds) {
const SPPMPixel &p = pixels[pPixel];
Float v = 1.f - (p.radius - minrad) / (maxrad - minrad);
Point2i pImage = Point2i(pPixel - pixelBounds.pMin);
rimg.SetChannel(pImage, 0, v);
}
ImageMetadata metadata;
metadata.pixelBounds = pixelBounds;
metadata.fullResolution = camera.GetFilm().FullResolution();
rimg.Write("sppm_radius.png", metadata);
}
}
}
#if 0
// FIXME
sppmMemoryArenaBytes += std::accumulate(perThreadArenas.begin(), perThreadArenas.end(),
size_t(0), [&](size_t v, const MemoryArena &arena) {
return v + arena.BytesAllocated();
});
#endif
progress.Done();
}
SampledSpectrum SPPMIntegrator::SampleLd(const SurfaceInteraction &intr, const BSDF &bsdf,
SampledWavelengths &lambda,
SamplerHandle sampler,
LightSamplerHandle lightSampler) const {
// NOTE: share fragments from PathIntegrator::SampleLd here...
pstd::optional<SampledLight> sampledLight =
lightSampler.Sample(intr, sampler.Get1D());
Point2f uLight = sampler.Get2D();
SampledSpectrum Ld(0.f);
if (sampledLight) {
LightHandle light = sampledLight->light;
DCHECK(light != nullptr && sampledLight->pdf > 0);
// Sample light source with multiple importance sampling
LightLiSample ls =
light.SampleLi(intr, uLight, lambda, LightSamplingMode::WithMIS);
if (ls && ls.L) {
// Evaluate BSDF for light sampling strategy
Vector3f wo = intr.wo, wi = ls.wi;
SampledSpectrum f = bsdf.f(wo, wi) * AbsDot(wi, intr.shading.n);
if (f) {
SampledSpectrum Li = ls.L;
if (Unoccluded(intr, ls.pLight)) {
// Add light's contribution to reflected radiance
Float lightPDF = sampledLight->pdf * ls.pdf;
if (IsDeltaLight(light.Type()))
Ld = f * Li / lightPDF;
else {
Float bsdfPDF = bsdf.PDF(wo, wi);
CHECK_RARE(1e-6, bsdf.SampledPDFIsProportional() == false &&
bsdfPDF == 0);
Float weight = PowerHeuristic(1, lightPDF, 1, bsdfPDF);
Ld = f * Li * weight / lightPDF;
}
}
}
}
}
Float uScattering = sampler.Get1D();
BSDFSample bs = bsdf.Sample_f(intr.wo, uScattering, sampler.Get2D());
if (!bs || !bs.f)
return Ld;
Vector3f wi = bs.wi;
SampledSpectrum f = bs.f * AbsDot(wi, intr.shading.n);
Ray ray = intr.SpawnRay(wi);
pstd::optional<ShapeIntersection> si = Intersect(ray);
if (si) {
SampledSpectrum Le = si->intr.Le(-ray.d, lambda);
if (Le) {
if (bs.IsSpecular())
Ld += f * Le / bs.pdf;
else {
// Compute MIS pdf...
LightHandle areaLight(si->intr.areaLight);
Float lightPDF = lightSampler.PDF(intr, areaLight) *
areaLight.PDF_Li(intr, wi, LightSamplingMode::WithMIS);
Float bsdfPDF =
bsdf.SampledPDFIsProportional() ? bsdf.PDF(intr.wo, wi) : bs.pdf;
Float weight = PowerHeuristic(1, bsdfPDF, 1, lightPDF);
Ld += f * Le * weight / bs.pdf;
}
}
} else {
for (const auto &light : infiniteLights) {
SampledSpectrum Le = light.Le(ray, lambda);
if (bs.IsSpecular())
Ld += f * Le / bs.pdf;
else {
// Compute MIS pdf...
Float lightPDF = lightSampler.PDF(intr, light) *
light.PDF_Li(intr, wi, LightSamplingMode::WithMIS);
Float bsdfPDF =
bsdf.SampledPDFIsProportional() ? bsdf.PDF(intr.wo, wi) : bs.pdf;
Float weight = PowerHeuristic(1, bsdfPDF, 1, lightPDF);
Ld += f * Le * weight / bs.pdf;
}
}
}
return Ld;
}
std::string SPPMIntegrator::ToString() const {
return StringPrintf("[ SPPMIntegrator camera: %s initialSearchRadius: %f "
"nIterations: %d maxDepth: %d photonsPerIteration: %d "
"regularize: %s colorSpace: %s digitPermutations:(elided) ]",
camera, initialSearchRadius, nIterations, maxDepth,
photonsPerIteration, regularize, *colorSpace);
}
std::unique_ptr<SPPMIntegrator> SPPMIntegrator::Create(
const ParameterDictionary &parameters, const RGBColorSpace *colorSpace,
CameraHandle camera, PrimitiveHandle aggregate, std::vector<LightHandle> lights,
const FileLoc *loc) {
int nIterations = parameters.GetOneInt("iterations", 64);
int maxDepth = parameters.GetOneInt("maxdepth", 5);
int photonsPerIter = parameters.GetOneInt("photonsperiteration", -1);
Float radius = parameters.GetOneFloat("radius", 1.f);
if (Options->quickRender)
nIterations = std::max(1, nIterations / 16);
bool regularize = parameters.GetOneBool("regularize", false);
int seed = parameters.GetOneInt("seed", 0);
return std::make_unique<SPPMIntegrator>(camera, aggregate, lights, nIterations,
photonsPerIter, maxDepth, radius, regularize,
seed, colorSpace);
}
std::unique_ptr<Integrator> Integrator::Create(
const std::string &name, const ParameterDictionary &parameters, CameraHandle camera,
SamplerHandle sampler, PrimitiveHandle aggregate, std::vector<LightHandle> lights,
const RGBColorSpace *colorSpace, const FileLoc *loc) {
std::unique_ptr<Integrator> integrator;
if (name == "path")
integrator =
PathIntegrator::Create(parameters, camera, sampler, aggregate, lights, loc);
else if (name == "simplepath")
integrator = SimplePathIntegrator::Create(parameters, camera, sampler, aggregate,
lights, loc);
else if (name == "lightpath")
integrator = LightPathIntegrator::Create(parameters, camera, sampler, aggregate,
lights, loc);
else if (name == "simplevolpath")
integrator = SimpleVolPathIntegrator::Create(parameters, camera, sampler,
aggregate, lights, loc);
else if (name == "volpath")
integrator = VolPathIntegrator::Create(parameters, camera, sampler, aggregate,
lights, loc);
else if (name == "bdpt")
integrator =
BDPTIntegrator::Create(parameters, camera, sampler, aggregate, lights, loc);
else if (name == "mlt")
integrator = MLTIntegrator::Create(parameters, camera, aggregate, lights, loc);
else if (name == "ambientocclusion")
integrator = AOIntegrator::Create(parameters, &colorSpace->illuminant, camera,
sampler, aggregate, lights, loc);
else if (name == "randomwalk")
integrator = RandomWalkIntegrator::Create(parameters, camera, sampler, aggregate,
lights, loc);
else if (name == "sppm")
integrator = SPPMIntegrator::Create(parameters, colorSpace, camera, aggregate,
lights, loc);
else
ErrorExit(loc, "%s: integrator type unknown.", name);
if (!integrator)
ErrorExit(loc, "%s: unable to create integrator.", name);
parameters.ReportUnused();
return integrator;
}
} // namespace pbrt