Spectrum GridDensityMedium::Sample(const Ray &_ray, Sampler &sampler, MemoryArena &arena, MediumInteraction *mi) const { // Transform the ray into local coordinates and determine overlap interval // [_tMin, tMax_] const Bounds3f dataBounds(Point3f(0.f, 0.f, 0.f), Point3f(1.f, 1.f, 1.f)); Ray ray = WorldToMedium( Ray(_ray.o, Normalize(_ray.d), _ray.tMax * _ray.d.Length())); Float tMin, tMax; if (!dataBounds.IntersectP(ray, &tMin, &tMax)) return Spectrum(1.f); tMin = std::max(tMin, (Float)0.f); tMax = std::min(tMax, ray.tMax); if (tMin >= tMax) return Spectrum(1.f); // Run Delta-Tracking iterations to sample a medium interaction Float t = tMin; while (true) { t -= std::log(1 - sampler.Get1D()) * invMaxDensity; if (t >= tMax) break; Float density = Density(ray(t)); if (density * invMaxDensity * sigma_t > sampler.Get1D()) { // Populate _mi_ with medium interaction information and return PhaseFunction *phase = ARENA_ALLOC(arena, HenyeyGreenstein)(g); *mi = MediumInteraction(_ray(t), -_ray.d, _ray.time, this, phase); return sigma_s / sigma_t; } } return Spectrum(1.0f); }
Spectrum GridDensityMedium::Sample(const Ray &rWorld, Sampler &sampler, MemoryArena &arena, MediumInteraction *mi) const { Ray ray = WorldToMedium( Ray(rWorld.o, Normalize(rWorld.d), rWorld.tMax * rWorld.d.Length())); // Compute $[\tmin, \tmax]$ interval of _ray_'s overlap with medium bounds const Bounds3f b(Point3f(0, 0, 0), Point3f(1, 1, 1)); Float tMin, tMax; if (!b.IntersectP(ray, &tMin, &tMax)) return Spectrum(1.f); // Run delta-tracking iterations to sample a medium interaction Float t = tMin; while (true) { t -= std::log(1 - sampler.Get1D()) * invMaxDensity; if (t >= tMax) break; if (Density(ray(t)) * invMaxDensity * sigma_t > sampler.Get1D()) { // Populate _mi_ with medium interaction information and return PhaseFunction *phase = ARENA_ALLOC(arena, HenyeyGreenstein)(g); *mi = MediumInteraction(rWorld(t), -rWorld.d, rWorld.time, this, phase); return sigma_s / sigma_t; } } return Spectrum(1.f); }
Spectrum UniformSampleOneLight(const Interaction &it, const Scene &scene, MemoryArena &arena, Sampler &sampler, bool handleMedia) { ProfilePhase p(Prof::DirectLighting); // Randomly choose a single light to sample, _light_ int nLights = int(scene.lights.size()); if (nLights == 0) return Spectrum(0.f); int lightNum = std::min((int)(sampler.Get1D() * nLights), nLights - 1); const std::shared_ptr<Light> &light = scene.lights[lightNum]; Point2f uLight = sampler.Get2D(); Point2f uScattering = sampler.Get2D(); return (Float)nLights * EstimateDirect(it, uScattering, *light, uLight, scene, sampler, arena, handleMedia); }
Spectrum GridDensityMedium::Tr(const Ray &rWorld, Sampler &sampler) const { Ray ray = WorldToMedium( Ray(rWorld.o, Normalize(rWorld.d), rWorld.tMax * rWorld.d.Length())); // Compute $[\tmin, \tmax]$ interval of _ray_'s overlap with medium bounds const Bounds3f b(Point3f(0, 0, 0), Point3f(1, 1, 1)); Float tMin, tMax; if (!b.IntersectP(ray, &tMin, &tMax)) return Spectrum(1.f); // Perform ratio tracking to estimate the transmittance value Float Tr = 1, t = tMin; while (true) { t += -std::log(1 - sampler.Get1D()) * invMaxDensity; if (t >= tMax) break; Float density = Density(ray(t)); Tr *= 1 - std::max((Float)0, sigma_t * density * invMaxDensity); } return Spectrum(Tr); }
int GenerateLightSubpath( const Scene &scene, Sampler &sampler, MemoryArena &arena, int maxDepth, Float time, const Distribution1D &lightDistr, const std::unordered_map<const Light *, size_t> &lightToIndex, Vertex *path) { if (maxDepth == 0) return 0; // Sample initial ray for light subpath Float lightPdf; int lightNum = lightDistr.SampleDiscrete(sampler.Get1D(), &lightPdf); const std::shared_ptr<Light> &light = scene.lights[lightNum]; RayDifferential ray; Normal3f nLight; Float pdfPos, pdfDir; Spectrum Le = light->Sample_Le(sampler.Get2D(), sampler.Get2D(), time, &ray, &nLight, &pdfPos, &pdfDir); if (pdfPos == 0 || pdfDir == 0 || Le.IsBlack()) return 0; // Generate first vertex on light subpath and start random walk path[0] = Vertex::CreateLight(light.get(), ray, nLight, Le, pdfPos * lightPdf); Spectrum beta = Le * AbsDot(nLight, ray.d) / (lightPdf * pdfPos * pdfDir); VLOG(2) << "Starting light subpath. Ray: " << ray << ", Le " << Le << ", beta " << beta << ", pdfPos " << pdfPos << ", pdfDir " << pdfDir; int nVertices = RandomWalk(scene, ray, sampler, arena, beta, pdfDir, maxDepth - 1, TransportMode::Importance, path + 1); // 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 = 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(scene, lightDistr, lightToIndex, ray.d); } return nVertices + 1; }
int GenerateCameraSubpath(const Scene &scene, Sampler &sampler, MemoryArena &arena, int maxDepth, const Camera &camera, Point2f &pFilm, Vertex *path) { if (maxDepth == 0) return 0; // Sample initial ray for camera subpath CameraSample cameraSample; cameraSample.pFilm = pFilm; cameraSample.time = sampler.Get1D(); cameraSample.pLens = sampler.Get2D(); RayDifferential ray; Spectrum beta = camera.GenerateRayDifferential(cameraSample, &ray); ray.ScaleDifferentials(1 / std::sqrt(sampler.samplesPerPixel)); // 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(scene, ray, sampler, arena, beta, pdfDir, maxDepth - 1, TransportMode::Radiance, path + 1) + 1; }
int GenerateCameraSubpath(const Scene &scene, Sampler &sampler, MemoryArena &arena, int maxdepth, const Camera &camera, Point2f &rasterPos, Vertex *path) { if (maxdepth == 0) return 0; // Sample initial ray for camera subpath CameraSample cameraSample; cameraSample.pFilm = rasterPos; cameraSample.time = sampler.Get1D(); cameraSample.pLens = sampler.Get2D(); RayDifferential ray; Spectrum rayWeight(camera.GenerateRayDifferential(cameraSample, &ray)); ray.ScaleDifferentials(1.f / std::sqrt(sampler.samplesPerPixel)); // Generate first vertex on camera subpath and start random walk path[0] = Vertex(VertexType::Camera, EndpointInteraction(&camera, ray), Spectrum(1.0f)); return RandomWalk(scene, ray, sampler, arena, rayWeight, camera.Pdf(path[0].ei, ray.d), maxdepth - 1, TransportMode::Radiance, path + 1) + 1; }
Spectrum GridDensityMedium::T(const Ray &_ray, Sampler &sampler) const { // Transform the ray into local coordinates and determine overlap interval // [_tMin, tMax_] const Bounds3f dataBounds(Point3f(0.f, 0.f, 0.f), Point3f(1.f, 1.f, 1.f)); Ray ray = WorldToMedium( Ray(_ray.o, Normalize(_ray.d), _ray.tMax * _ray.d.Length())); Float tMin, tMax; if (!dataBounds.IntersectP(ray, &tMin, &tMax)) return Spectrum(1.f); tMin = std::max(tMin, (Float)0.f); tMax = std::min(tMax, ray.tMax); if (tMin >= tMax) return Spectrum(1.f); Float tr = 1.f; // Perform ratio tracking to estimate the transmittance value Float t = tMin; while (true) { t -= std::log(1 - sampler.Get1D()) * invMaxDensity; if (t >= tMax) break; Float density = Density(ray(t)); tr *= 1.f - std::max((Float)0, sigma_t * density * invMaxDensity); } return Spectrum(tr); }
int GenerateLightSubpath(const Scene &scene, Sampler &sampler, MemoryArena &arena, int maxdepth, Float time, const Distribution1D &lightDistr, Vertex *path) { if (maxdepth == 0) return 0; // Sample initial ray for light subpath Float lightPdf; int lightNum = lightDistr.SampleDiscrete(sampler.Get1D(), &lightPdf); const std::shared_ptr<Light> &light = scene.lights[lightNum]; RayDifferential ray; Normal3f Nl; Float pdfPos, pdfDir; Spectrum Le = light->Sample_L(sampler.Get2D(), sampler.Get2D(), time, &ray, &Nl, &pdfPos, &pdfDir); if (pdfPos == 0.f || pdfDir == 0.f || Le.IsBlack()) return 0; // Generate first vertex on light subpath and start random walk Spectrum weight = Le * AbsDot(Nl, ray.d) / (lightPdf * pdfPos * pdfDir); path[0] = Vertex(VertexType::Light, EndpointInteraction(light.get(), ray, Nl), Le); path[0].pdfFwd = pdfPos * lightPdf; int nvertices = RandomWalk(scene, ray, sampler, arena, weight, pdfDir, maxdepth - 1, TransportMode::Importance, path + 1); // Correct sampling densities for infinite area lights if (path[0].IsInfiniteLight()) { // Set positional density of _path[1]_ if (nvertices > 0) { path[1].pdfFwd = pdfPos; if (path[1].IsOnSurface()) path[1].pdfFwd *= AbsDot(ray.d, path[1].GetGeoNormal()); } // Set positional density of _path[0]_ path[0].pdfFwd = InfiniteLightDensity(scene, lightDistr, ray.d); } return nvertices + 1; }
// VolPathIntegrator Method Definitions Spectrum VolPathIntegrator::Li(const RayDifferential &r, const Scene &scene, Sampler &sampler, MemoryArena &arena, int depth) const { ProfilePhase p(Prof::SamplerIntegratorLi); Spectrum L(0.f), alpha(1.f); RayDifferential ray(r); bool specularBounce = false; for (int bounces = 0;; ++bounces) { // Store intersection into _isect_ SurfaceInteraction isect; bool foundIntersection = scene.Intersect(ray, &isect); // Sample the participating medium, if present MediumInteraction mi; if (ray.medium) alpha *= ray.medium->Sample(ray, sampler, arena, &mi); if (alpha.IsBlack()) break; // Handle an interaction with a medium or a surface if (mi.IsValid()) { // Handle medium scattering case Vector3f wo = -ray.d, wi; L += alpha * UniformSampleOneLight(mi, scene, sampler, arena, true); Point2f phaseSample = sampler.Get2D(); mi.phase->Sample_p(wo, &wi, phaseSample); ray = mi.SpawnRay(wi); } else { // Handle surface scattering case // Possibly add emitted light and terminate if (bounces == 0 || specularBounce) { // Add emitted light at path vertex or from the environment if (foundIntersection) L += alpha * isect.Le(-ray.d); else for (const auto &light : scene.lights) L += alpha * light->Le(ray); } if (!foundIntersection || bounces >= maxDepth) break; // Compute scattering functions and skip over medium boundaries isect.ComputeScatteringFunctions(ray, arena, true); if (!isect.bsdf) { ray = isect.SpawnRay(ray.d); bounces--; continue; } // Sample illumination from lights to find attenuated path // contribution L += alpha * UniformSampleOneLight(isect, scene, sampler, arena, true); // Sample BSDF to get new path direction Vector3f wo = -ray.d, wi; Float pdf; BxDFType flags; Spectrum f = isect.bsdf->Sample_f(wo, &wi, sampler.Get2D(), &pdf, BSDF_ALL, &flags); if (f.IsBlack() || pdf == 0.f) break; alpha *= f * AbsDot(wi, isect.shading.n) / pdf; Assert(std::isinf(alpha.y()) == false); specularBounce = (flags & BSDF_SPECULAR) != 0; ray = isect.SpawnRay(wi); // Account for attenuated subsurface scattering, if applicable if (isect.bssrdf && (flags & BSDF_TRANSMISSION)) { // Importance sample the BSSRDF SurfaceInteraction pi; Spectrum S = isect.bssrdf->Sample_S( scene, sampler.Get1D(), sampler.Get2D(), arena, &pi, &pdf); #ifndef NDEBUG Assert(std::isinf(alpha.y()) == false); #endif if (S.IsBlack() || pdf == 0) break; alpha *= S / pdf; // Account for the attenuated direct subsurface scattering // component L += alpha * UniformSampleOneLight(pi, scene, sampler, arena, true); // Account for the indirect subsurface scattering component Spectrum f = pi.bsdf->Sample_f(pi.wo, &wi, sampler.Get2D(), &pdf, BSDF_ALL, &flags); if (f.IsBlack() || pdf == 0.f) break; alpha *= f * AbsDot(wi, pi.shading.n) / pdf; #ifndef NDEBUG Assert(std::isinf(alpha.y()) == false); #endif specularBounce = (flags & BSDF_SPECULAR) != 0; ray = pi.SpawnRay(wi); } } // Possibly terminate the path if (bounces > 3) { Float continueProbability = std::min((Float).5, alpha.y()); if (sampler.Get1D() > continueProbability) break; alpha /= continueProbability; Assert(std::isinf(alpha.y()) == false); } } return L; }
// PathIntegrator Method Definitions Spectrum PathIntegrator::Li(const RayDifferential &r, const Scene &scene, Sampler &sampler, MemoryArena &arena) const { Spectrum L(0.f); // Declare common path integration variables RayDifferential ray(r); Spectrum pathThroughput = Spectrum(1.f); bool specularBounce = false; for (int bounces = 0;; ++bounces) { // Store intersection into _isect_ SurfaceInteraction isect; bool foundIntersection = scene.Intersect(ray, &isect); // Possibly add emitted light and terminate if (bounces == 0 || specularBounce) { // Add emitted light at path vertex or from the environment if (foundIntersection) L += pathThroughput * isect.Le(-ray.d); else for (const auto &light : scene.lights) L += pathThroughput * light->Le(ray); } if (!foundIntersection || bounces >= maxDepth) break; // Compute scattering functions and skip over medium boundaries isect.ComputeScatteringFunctions(ray, arena, true); if (!isect.bsdf) { ray = isect.SpawnRay(ray.d); bounces--; continue; } // Sample illumination from lights to find path contribution L += pathThroughput * UniformSampleOneLight(isect, scene, sampler, arena); // Sample BSDF to get new path direction Vector3f wo = -ray.d, wi; Float pdf; BxDFType flags; Spectrum f = isect.bsdf->Sample_f(wo, &wi, sampler.Get2D(), &pdf, BSDF_ALL, &flags); if (f.IsBlack() || pdf == 0.f) break; pathThroughput *= f * AbsDot(wi, isect.shading.n) / pdf; #ifndef NDEBUG Assert(std::isinf(pathThroughput.y()) == false); #endif specularBounce = (flags & BSDF_SPECULAR) != 0; ray = isect.SpawnRay(wi); // Account for subsurface scattering, if applicable if (isect.bssrdf && (flags & BSDF_TRANSMISSION)) { // Importance sample the BSSRDF BSSRDFSample bssrdfSample; bssrdfSample.uDiscrete = sampler.Get1D(); bssrdfSample.pos = sampler.Get2D(); SurfaceInteraction isect_out = isect; pathThroughput *= isect.bssrdf->Sample_f( isect_out, scene, ray.time, bssrdfSample, arena, &isect, &pdf); #ifndef NDEBUG Assert(std::isinf(pathThroughput.y()) == false); #endif if (pathThroughput.IsBlack()) break; // Account for the direct subsurface scattering component isect.wo = Vector3f(isect.shading.n); // Sample illumination from lights to find path contribution L += pathThroughput * UniformSampleOneLight(isect, scene, sampler, arena); // Account for the indirect subsurface scattering component Spectrum f = isect.bsdf->Sample_f(isect.wo, &wi, sampler.Get2D(), &pdf, BSDF_ALL, &flags); if (f.IsBlack() || pdf == 0.f) break; pathThroughput *= f * AbsDot(wi, isect.shading.n) / pdf; #ifndef NDEBUG Assert(std::isinf(pathThroughput.y()) == false); #endif specularBounce = (flags & BSDF_SPECULAR) != 0; ray = isect.SpawnRay(wi); } // Possibly terminate the path if (bounces > 3) { Float continueProbability = std::min((Float).5, pathThroughput.y()); if (sampler.Get1D() > continueProbability) break; pathThroughput /= continueProbability; Assert(std::isinf(pathThroughput.y()) == false); } } return L; }
Spectrum VolPathIntegrator::Li(const RayDifferential &r, const Scene &scene, Sampler &sampler, MemoryArena &arena, int depth) const { ProfilePhase p(Prof::SamplerIntegratorLi); Spectrum L(0.f), beta(1.f); RayDifferential ray(r); bool specularBounce = false; int bounces; // Added after book publication: etaScale tracks the accumulated effect // of radiance scaling due to rays passing through refractive // boundaries (see the derivation on p. 527 of the third edition). We // track this value in order to remove it from beta when we apply // Russian roulette; this is worthwhile, since it lets us sometimes // avoid terminating refracted rays that are about to be refracted back // out of a medium and thus have their beta value increased. Float etaScale = 1; for (bounces = 0;; ++bounces) { // Intersect _ray_ with scene and store intersection in _isect_ SurfaceInteraction isect; bool foundIntersection = scene.Intersect(ray, &isect); // Sample the participating medium, if present MediumInteraction mi; if (ray.medium) beta *= ray.medium->Sample(ray, sampler, arena, &mi); if (beta.IsBlack()) break; // Handle an interaction with a medium or a surface if (mi.IsValid()) { // Terminate path if ray escaped or _maxDepth_ was reached if (bounces >= maxDepth) break; ++volumeInteractions; // Handle scattering at point in medium for volumetric path tracer const Distribution1D *lightDistrib = lightDistribution->Lookup(mi.p); L += beta * UniformSampleOneLight(mi, scene, arena, sampler, true, lightDistrib); Vector3f wo = -ray.d, wi; mi.phase->Sample_p(wo, &wi, sampler.Get2D()); ray = mi.SpawnRay(wi); } else { ++surfaceInteractions; // Handle scattering at point on surface for volumetric path tracer // Possibly add emitted light at intersection if (bounces == 0 || specularBounce) { // Add emitted light at path vertex or from the environment if (foundIntersection) L += beta * isect.Le(-ray.d); else for (const auto &light : scene.infiniteLights) L += beta * light->Le(ray); } // Terminate path if ray escaped or _maxDepth_ was reached if (!foundIntersection || bounces >= maxDepth) break; // Compute scattering functions and skip over medium boundaries isect.ComputeScatteringFunctions(ray, arena, true); if (!isect.bsdf) { ray = isect.SpawnRay(ray.d); bounces--; continue; } // Sample illumination from lights to find attenuated path // contribution const Distribution1D *lightDistrib = lightDistribution->Lookup(isect.p); L += beta * UniformSampleOneLight(isect, scene, arena, sampler, true, lightDistrib); // Sample BSDF to get new path direction Vector3f wo = -ray.d, wi; Float pdf; BxDFType flags; Spectrum f = isect.bsdf->Sample_f(wo, &wi, sampler.Get2D(), &pdf, BSDF_ALL, &flags); if (f.IsBlack() || pdf == 0.f) break; beta *= f * AbsDot(wi, isect.shading.n) / pdf; DCHECK(std::isinf(beta.y()) == false); specularBounce = (flags & BSDF_SPECULAR) != 0; if ((flags & BSDF_SPECULAR) && (flags & BSDF_TRANSMISSION)) { Float eta = isect.bsdf->eta; // Update the term that tracks radiance scaling for refraction // depending on whether the ray is entering or leaving the // medium. etaScale *= (Dot(wo, isect.n) > 0) ? (eta * eta) : 1 / (eta * eta); } ray = isect.SpawnRay(ray, wi, flags, isect.bsdf->eta); // Account for attenuated subsurface scattering, if applicable if (isect.bssrdf && (flags & BSDF_TRANSMISSION)) { // Importance sample the BSSRDF SurfaceInteraction pi; Spectrum S = isect.bssrdf->Sample_S( scene, sampler.Get1D(), sampler.Get2D(), arena, &pi, &pdf); DCHECK(std::isinf(beta.y()) == false); if (S.IsBlack() || pdf == 0) break; beta *= S / pdf; // Account for the attenuated direct subsurface scattering // component L += beta * UniformSampleOneLight(pi, scene, arena, sampler, true, lightDistribution->Lookup(pi.p)); // Account for the indirect subsurface scattering component Spectrum f = pi.bsdf->Sample_f(pi.wo, &wi, sampler.Get2D(), &pdf, BSDF_ALL, &flags); if (f.IsBlack() || pdf == 0) break; beta *= f * AbsDot(wi, pi.shading.n) / pdf; DCHECK(std::isinf(beta.y()) == false); specularBounce = (flags & BSDF_SPECULAR) != 0; ray = pi.SpawnRay(wi); } } // Possibly terminate the path with Russian roulette // Factor out radiance scaling due to refraction in rrBeta. Spectrum rrBeta = beta * etaScale; if (rrBeta.MaxComponentValue() < rrThreshold && bounces > 3) { Float q = std::max((Float).05, 1 - rrBeta.MaxComponentValue()); if (sampler.Get1D() < q) break; beta /= 1 - q; DCHECK(std::isinf(beta.y()) == false); } } ReportValue(pathLength, bounces); return L; }
Spectrum ConnectBDPT(const Scene &scene, Vertex *lightSubpath, Vertex *cameraSubpath, int s, int t, const Distribution1D &lightDistr, const Camera &camera, Sampler &sampler, Point2f *rasterPos, Float *misWeight) { Spectrum weight(0.f); // Ignore invalid connections related to infinite area lights if (t > 1 && s != 0 && cameraSubpath[t - 1].type == VertexType::Light) return Spectrum(0.f); // Perform connection and write contribution to _weight_ Vertex sampled; if (s == 0) { // Interpret the camera subpath as a complete path const Vertex &pt = cameraSubpath[t - 1]; if (pt.IsLight()) { const Vertex &ptMinus = cameraSubpath[t - 2]; weight = pt.Le(scene, ptMinus) * pt.weight; } } else if (t == 1) { // Sample a point on the camera and connect it to the light subpath const Vertex &qs = lightSubpath[s - 1]; if (qs.IsConnectible()) { VisibilityTester vis; Vector3f wi; Float pdf; Spectrum cameraWeight = camera.Sample_We(qs.GetInteraction(), sampler.Get2D(), &wi, &pdf, rasterPos, &vis); if (pdf > 0 && !cameraWeight.IsBlack()) { // Initialize dynamically sampled vertex and _weight_ sampled = Vertex(VertexType::Camera, EndpointInteraction(vis.P1(), &camera), cameraWeight); weight = qs.weight * qs.f(sampled) * vis.T(scene, sampler) * sampled.weight; if (qs.IsOnSurface()) weight *= AbsDot(wi, qs.GetShadingNormal()); } } } else if (s == 1) { // Sample a point on a light and connect it to the camera subpath const Vertex &pt = cameraSubpath[t - 1]; if (pt.IsConnectible()) { Float lightPdf; VisibilityTester vis; Vector3f wi; Float pdf; int lightNum = lightDistr.SampleDiscrete(sampler.Get1D(), &lightPdf); const std::shared_ptr<Light> &light = scene.lights[lightNum]; Spectrum lightWeight = light->Sample_L( pt.GetInteraction(), sampler.Get2D(), &wi, &pdf, &vis); if (pdf > 0 && !lightWeight.IsBlack()) { sampled = Vertex(VertexType::Light, EndpointInteraction(vis.P1(), light.get()), lightWeight / (pdf * lightPdf)); sampled.pdfFwd = sampled.PdfLightOrigin(scene, pt, lightDistr); weight = pt.weight * pt.f(sampled) * vis.T(scene, sampler) * sampled.weight; if (pt.IsOnSurface()) weight *= AbsDot(wi, pt.GetShadingNormal()); } } } else { // Handle all other cases const Vertex &qs = lightSubpath[s - 1], &pt = cameraSubpath[t - 1]; if (qs.IsConnectible() && pt.IsConnectible()) { weight = qs.weight * qs.f(pt) * GeometryTerm(scene, sampler, qs, pt) * pt.f(qs) * pt.weight; } } // Compute MIS weight for connection strategy *misWeight = weight.IsBlack() ? 0.f : MISWeight(scene, lightSubpath, cameraSubpath, sampled, s, t, lightDistr); return weight; }
Spectrum ConnectBDPT( const Scene &scene, Vertex *lightVertices, Vertex *cameraVertices, int s, int t, const Distribution1D &lightDistr, const std::unordered_map<const Light *, size_t> &lightToIndex, const Camera &camera, Sampler &sampler, Point2f *pRaster, Float *misWeightPtr) { Spectrum L(0.f); // Ignore invalid connections related to infinite area lights if (t > 1 && s != 0 && cameraVertices[t - 1].type == VertexType::Light) return Spectrum(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(scene, cameraVertices[t - 2]) * 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()) { VisibilityTester vis; Vector3f wi; Float pdf; Spectrum Wi = camera.Sample_Wi(qs.GetInteraction(), sampler.Get2D(), &wi, &pdf, pRaster, &vis); if (pdf > 0 && !Wi.IsBlack()) { // Initialize dynamically sampled vertex and _L_ for $t=1$ case sampled = Vertex::CreateCamera(&camera, vis.P1(), Wi / pdf); L = qs.beta * qs.f(sampled, TransportMode::Importance) * sampled.beta; if (qs.IsOnSurface()) L *= AbsDot(wi, qs.ns()); DCHECK(!L.HasNaNs()); // Only check visibility after we know that the path would // make a non-zero contribution. if (!L.IsBlack()) L *= vis.Tr(scene, sampler); } } } 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()) { Float lightPdf; VisibilityTester vis; Vector3f wi; Float pdf; int lightNum = lightDistr.SampleDiscrete(sampler.Get1D(), &lightPdf); const std::shared_ptr<Light> &light = scene.lights[lightNum]; Spectrum lightWeight = light->Sample_Li( pt.GetInteraction(), sampler.Get2D(), &wi, &pdf, &vis); if (pdf > 0 && !lightWeight.IsBlack()) { EndpointInteraction ei(vis.P1(), light.get()); sampled = Vertex::CreateLight(ei, lightWeight / (pdf * lightPdf), 0); sampled.pdfFwd = sampled.PdfLightOrigin(scene, pt, lightDistr, lightToIndex); L = pt.beta * pt.f(sampled, TransportMode::Radiance) * sampled.beta; if (pt.IsOnSurface()) L *= AbsDot(wi, pt.ns()); // Only check visibility if the path would carry radiance. if (!L.IsBlack()) L *= vis.Tr(scene, sampler); } } } 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: " << s << ", t: " << t << " qs: " << qs << ", pt: " << pt << ", qs.f(pt): " << qs.f(pt, TransportMode::Importance) << ", pt.f(qs): " << pt.f(qs, TransportMode::Radiance) << ", G: " << G(scene, sampler, qs, pt) << ", dist^2: " << DistanceSquared(qs.p(), pt.p()); if (!L.IsBlack()) L *= G(scene, sampler, qs, pt); } } ++totalPaths; if (L.IsBlack()) ++zeroRadiancePaths; ReportValue(pathLength, s + t - 2); // Compute MIS weight for connection strategy Float misWeight = L.IsBlack() ? 0.f : MISWeight(scene, lightVertices, cameraVertices, sampled, s, t, lightDistr, lightToIndex); VLOG(2) << "MIS weight for (s,t) = (" << s << ", " << t << ") connection: " << misWeight; DCHECK(!std::isnan(misWeight)); L *= misWeight; if (misWeightPtr) *misWeightPtr = misWeight; return L; }
Spectrum ConnectBDPT(const Scene &scene, Vertex *lightVertices, Vertex *cameraVertices, int s, int t, const Distribution1D &lightDistr, const Camera &camera, Sampler &sampler, Point2f *pRaster, Float *misWeightPtr) { Spectrum L(0.f); // Ignore invalid connections related to infinite area lights if (t > 1 && s != 0 && cameraVertices[t - 1].type == VertexType::Light) return Spectrum(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(scene, cameraVertices[t - 2]) * pt.beta; } 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()) { VisibilityTester vis; Vector3f wi; Float pdf; Spectrum Wi = camera.Sample_Wi(qs.GetInteraction(), sampler.Get2D(), &wi, &pdf, pRaster, &vis); if (pdf > 0 && !Wi.IsBlack()) { // Initialize dynamically sampled vertex and _L_ for $t=1$ case sampled = Vertex::CreateCamera(&camera, vis.P1(), Wi / pdf); L = qs.beta * qs.f(sampled) * vis.Tr(scene, sampler) * sampled.beta; if (qs.IsOnSurface()) L *= AbsDot(wi, qs.ns()); } } } 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()) { Float lightPdf; VisibilityTester vis; Vector3f wi; Float pdf; int lightNum = lightDistr.SampleDiscrete(sampler.Get1D(), &lightPdf); const std::shared_ptr<Light> &light = scene.lights[lightNum]; Spectrum lightWeight = light->Sample_Li( pt.GetInteraction(), sampler.Get2D(), &wi, &pdf, &vis); if (pdf > 0 && !lightWeight.IsBlack()) { EndpointInteraction ei(vis.P1(), light.get()); sampled = Vertex::CreateLight(ei, lightWeight / (pdf * lightPdf), 0); sampled.pdfFwd = sampled.PdfLightOrigin(scene, pt, lightDistr); L = pt.beta * pt.f(sampled) * vis.Tr(scene, sampler) * sampled.beta; if (pt.IsOnSurface()) L *= AbsDot(wi, pt.ns()); } } } 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) * pt.f(qs) * pt.beta; if (!L.IsBlack()) L *= G(scene, sampler, qs, pt); } } // Compute MIS weight for connection strategy Float misWeight = L.IsBlack() ? 0.f : MISWeight(scene, lightVertices, cameraVertices, sampled, s, t, lightDistr); L *= misWeight; if (misWeightPtr) *misWeightPtr = misWeight; return L; }
Spectrum PathIntegrator::Li(const RayDifferential &r, const Scene &scene, Sampler &sampler, MemoryArena &arena, int depth) const { ProfilePhase p(Prof::SamplerIntegratorLi); Spectrum L(0.f), beta(1.f); RayDifferential ray(r); bool specularBounce = false; int bounces; for (bounces = 0;; ++bounces) { // Find next path vertex and accumulate contribution VLOG(2) << "Path tracer bounce " << bounces << ", current L = " << L << ", beta = " << beta; // Intersect _ray_ with scene and store intersection in _isect_ SurfaceInteraction isect; bool foundIntersection = scene.Intersect(ray, &isect); // Possibly add emitted light at intersection if (bounces == 0 || specularBounce) { // Add emitted light at path vertex or from the environment if (foundIntersection) { L += beta * isect.Le(-ray.d); VLOG(2) << "Added Le -> L = " << L; } else { for (const auto &light : scene.infiniteLights) L += beta * light->Le(ray); VLOG(2) << "Added infinite area lights -> L = " << L; } } // Terminate path if ray escaped or _maxDepth_ was reached if (!foundIntersection || bounces >= maxDepth) break; // Compute scattering functions and skip over medium boundaries isect.ComputeScatteringFunctions(ray, arena, true); if (!isect.bsdf) { VLOG(2) << "Skipping intersection due to null bsdf"; ray = isect.SpawnRay(ray.d); bounces--; continue; } const Distribution1D *distrib = lightDistribution->Lookup(isect.p); // Sample illumination from lights to find path contribution. // (But skip this for perfectly specular BSDFs.) if (isect.bsdf->NumComponents(BxDFType(BSDF_ALL & ~BSDF_SPECULAR)) > 0) { ++totalPaths; Spectrum Ld = beta * UniformSampleOneLight(isect, scene, arena, sampler, false, distrib); VLOG(2) << "Sampled direct lighting Ld = " << Ld; if (Ld.IsBlack()) ++zeroRadiancePaths; CHECK_GE(Ld.y(), 0.f); L += Ld; } // Sample BSDF to get new path direction Vector3f wo = -ray.d, wi; Float pdf; BxDFType flags; Spectrum f = isect.bsdf->Sample_f(wo, &wi, sampler.Get2D(), &pdf, BSDF_ALL, &flags); VLOG(2) << "Sampled BSDF, f = " << f << ", pdf = " << pdf; if (f.IsBlack() || pdf == 0.f) break; beta *= f * AbsDot(wi, isect.shading.n) / pdf; VLOG(2) << "Updated beta = " << beta; CHECK_GE(beta.y(), 0.f); DCHECK(!std::isinf(beta.y())); specularBounce = (flags & BSDF_SPECULAR) != 0; ray = isect.SpawnRay(wi); // Account for subsurface scattering, if applicable if (isect.bssrdf && (flags & BSDF_TRANSMISSION)) { // Importance sample the BSSRDF SurfaceInteraction pi; Spectrum S = isect.bssrdf->Sample_S( scene, sampler.Get1D(), sampler.Get2D(), arena, &pi, &pdf); DCHECK(!std::isinf(beta.y())); if (S.IsBlack() || pdf == 0) break; beta *= S / pdf; // Account for the direct subsurface scattering component L += beta * UniformSampleOneLight(pi, scene, arena, sampler, false, lightDistribution->Lookup(pi.p)); // Account for the indirect subsurface scattering component Spectrum f = pi.bsdf->Sample_f(pi.wo, &wi, sampler.Get2D(), &pdf, BSDF_ALL, &flags); if (f.IsBlack() || pdf == 0) break; beta *= f * AbsDot(wi, pi.shading.n) / pdf; DCHECK(!std::isinf(beta.y())); specularBounce = (flags & BSDF_SPECULAR) != 0; ray = pi.SpawnRay(wi); } // Possibly terminate the path with Russian roulette if (beta.y() < rrThreshold && bounces > 3) { Float q = std::max((Float).05, 1 - beta.MaxComponentValue()); VLOG(2) << "RR termination probability q = " << q; if (sampler.Get1D() < q) break; beta /= 1 - q; VLOG(2) << "After RR survival, beta = " << beta; DCHECK(!std::isinf(beta.y())); } } ReportValue(pathLength, bounces); return L; }
// PathIntegrator Method Definitions Spectrum PathIntegrator::Li(const RayDifferential &r, const Scene &scene, Sampler &sampler, MemoryArena &arena, int depth) const { ProfilePhase p(Prof::SamplerIntegratorLi); Spectrum L(0.f), beta(1.f); RayDifferential ray(r); bool specularBounce = false; for (int bounces = 0;; ++bounces) { // Find next path vertex and accumulate contribution // Intersect _ray_ with scene and store intersection in _isect_ SurfaceInteraction isect; bool foundIntersection = scene.Intersect(ray, &isect); // Possibly add emitted light at intersection if (bounces == 0 || specularBounce) { // Add emitted light at path vertex or from the environment if (foundIntersection) L += beta * isect.Le(-ray.d); else for (const auto &light : scene.lights) L += beta * light->Le(ray); } // Terminate path if ray escaped or _maxDepth_ was reached if (!foundIntersection || bounces >= maxDepth) break; // Compute scattering functions and skip over medium boundaries isect.ComputeScatteringFunctions(ray, arena, true); if (!isect.bsdf) { ray = isect.SpawnRay(ray.d); bounces--; continue; } // Sample illumination from lights to find path contribution L += beta * UniformSampleOneLight(isect, scene, arena, sampler); // Sample BSDF to get new path direction Vector3f wo = -ray.d, wi; Float pdf; BxDFType flags; Spectrum f = isect.bsdf->Sample_f(wo, &wi, sampler.Get2D(), &pdf, BSDF_ALL, &flags); if (f.IsBlack() || pdf == 0.f) break; beta *= f * AbsDot(wi, isect.shading.n) / pdf; Assert(std::isinf(beta.y()) == false); specularBounce = (flags & BSDF_SPECULAR) != 0; ray = isect.SpawnRay(wi); // Account for subsurface scattering, if applicable if (isect.bssrdf && (flags & BSDF_TRANSMISSION)) { // Importance sample the BSSRDF SurfaceInteraction pi; Spectrum S = isect.bssrdf->Sample_S( scene, sampler.Get1D(), sampler.Get2D(), arena, &pi, &pdf); #ifndef NDEBUG Assert(std::isinf(beta.y()) == false); #endif if (S.IsBlack() || pdf == 0) break; beta *= S / pdf; // Account for the direct subsurface scattering component L += beta * UniformSampleOneLight(pi, scene, arena, sampler); // Account for the indirect subsurface scattering component Spectrum f = pi.bsdf->Sample_f(pi.wo, &wi, sampler.Get2D(), &pdf, BSDF_ALL, &flags); if (f.IsBlack() || pdf == 0) break; beta *= f * AbsDot(wi, pi.shading.n) / pdf; #ifndef NDEBUG Assert(std::isinf(beta.y()) == false); #endif specularBounce = (flags & BSDF_SPECULAR) != 0; ray = pi.SpawnRay(wi); } // Possibly terminate the path with Russian roulette if (bounces > 3) { Float continueProbability = std::min((Float).95, beta.y()); if (sampler.Get1D() > continueProbability) break; beta /= continueProbability; Assert(std::isinf(beta.y()) == false); } } return L; }