Spectrum InfiniteAreaLightIS::Sample_L(const Point &p, float u1, float u2, Vector *wi, float *pdf, VisibilityTester *visibility) const { // Find floating-point $(u,v)$ sample coordinates float pdfs[2]; float fu = uDistrib->Sample(u1, &pdfs[0]); int u = Clamp(Float2Int(fu), 0, uDistrib->count-1); float fv = vDistribs[u]->Sample(u2, &pdfs[1]); if (pdfs[0] == 0.f || pdfs[1] == 0.f) { *pdf = 0.f; return Spectrum(0.f); } // Convert sample point to direction on the unit sphere float theta = fv * vDistribs[u]->invCount * M_PI; float phi = fu * uDistrib->invCount * 2.f * M_PI; float costheta = cos(theta), sintheta = sin(theta); if (sintheta == 0.f) return 0.f; float sinphi = sin(phi), cosphi = cos(phi); *wi = LightToWorld(Vector(sintheta * cosphi, sintheta * sinphi, costheta)); // Compute PDF for sampled direction *pdf = (pdfs[0] * pdfs[1]) / (2. * M_PI * M_PI * sintheta); // Return radiance value for direction visibility->SetRay(p, *wi); return Lbase * radianceMap->Lookup(fu * uDistrib->invCount, fv * vDistribs[u]->invCount); }
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 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; }
void IGIIntegrator::Preprocess(const Scene *scene, const Camera *camera, const Renderer *renderer) { if (scene->lights.size() == 0) return; MemoryArena arena; RNG rng; // Compute samples for emitted rays from lights vector<float> lightNum(nLightPaths * nLightSets); vector<float> lightSampPos(2 * nLightPaths * nLightSets, 0.f); vector<float> lightSampComp(nLightPaths * nLightSets, 0.f); vector<float> lightSampDir(2 * nLightPaths * nLightSets, 0.f); LDShuffleScrambled1D(nLightPaths, nLightSets, &lightNum[0], rng); LDShuffleScrambled2D(nLightPaths, nLightSets, &lightSampPos[0], rng); LDShuffleScrambled1D(nLightPaths, nLightSets, &lightSampComp[0], rng); LDShuffleScrambled2D(nLightPaths, nLightSets, &lightSampDir[0], rng); // Precompute information for light sampling densities Distribution1D *lightDistribution = ComputeLightSamplingCDF(scene); for (uint32_t s = 0; s < nLightSets; ++s) { for (uint32_t i = 0; i < nLightPaths; ++i) { // Follow path _i_ from light to create virtual lights int sampOffset = s*nLightPaths + i; // Choose light source to trace virtual light path from float lightPdf; int ln = lightDistribution->SampleDiscrete(lightNum[sampOffset], &lightPdf); Light *light = scene->lights[ln]; // Sample ray leaving light source for virtual light path RayDifferential ray; float pdf; LightSample ls(lightSampPos[2*sampOffset], lightSampPos[2*sampOffset+1], lightSampComp[sampOffset]); Normal Nl; Spectrum alpha = light->Sample_L(scene, ls, lightSampDir[2*sampOffset], lightSampDir[2*sampOffset+1], camera->shutterOpen, &ray, &Nl, &pdf); if (pdf == 0.f || alpha.IsBlack()) continue; alpha /= pdf * lightPdf; Intersection isect; while (scene->Intersect(ray, &isect) && !alpha.IsBlack()) { // Create virtual light and sample new ray for path alpha *= renderer->Transmittance(scene, RayDifferential(ray), NULL, rng, arena); Vector wo = -ray.d; BSDF *bsdf = isect.GetBSDF(ray, arena); // Create virtual light at ray intersection point Spectrum contrib = alpha * bsdf->rho(wo, rng) / M_PI; virtualLights[s].push_back(VirtualLight(isect.dg.p, isect.dg.nn, contrib, isect.rayEpsilon)); // Sample new ray direction and update weight for virtual light path Vector wi; float pdf; BSDFSample bsdfSample(rng); Spectrum fr = bsdf->Sample_f(wo, &wi, bsdfSample, &pdf); if (fr.IsBlack() || pdf == 0.f) break; Spectrum contribScale = fr * AbsDot(wi, bsdf->dgShading.nn) / pdf; // Possibly terminate virtual light path with Russian roulette float rrProb = min(1.f, contribScale.y()); if (rng.RandomFloat() > rrProb) break; alpha *= contribScale / rrProb; ray = RayDifferential(isect.dg.p, wi, ray, isect.rayEpsilon); } arena.FreeAll(); } } delete lightDistribution; }
void PhotonShootingTask::Run() { // Declare local variables for _PhotonShootingTask_ MemoryArena arena; RNG rng(31 * taskNum); vector<Photon> localDirectPhotons, localIndirectPhotons, localCausticPhotons; vector<RadiancePhoton> localRadiancePhotons; uint32_t totalPaths = 0; bool causticDone = (integrator->nCausticPhotonsWanted == 0); bool indirectDone = (integrator->nIndirectPhotonsWanted == 0); PermutedHalton halton(6, rng); vector<Spectrum> localRpReflectances, localRpTransmittances; while (true) { // Follow photon paths for a block of samples const uint32_t blockSize = 4096; for (uint32_t i = 0; i < blockSize; ++i) { float u[6]; halton.Sample(++totalPaths, u); // Choose light to shoot photon from float lightPdf; int lightNum = lightDistribution->SampleDiscrete(u[0], &lightPdf); const Light *light = scene->lights[lightNum]; // Generate _photonRay_ from light source and initialize _alpha_ RayDifferential photonRay; float pdf; LightSample ls(u[1], u[2], u[3]); Normal Nl; Spectrum Le = light->Sample_L(scene, ls, u[4], u[5], time, &photonRay, &Nl, &pdf); if (pdf == 0.f || Le.IsBlack()) continue; Spectrum alpha = (AbsDot(Nl, photonRay.d) * Le) / (pdf * lightPdf); if (!alpha.IsBlack()) { // Follow photon path through scene and record intersections PBRT_PHOTON_MAP_STARTED_RAY_PATH(&photonRay, &alpha); bool specularPath = true; Intersection photonIsect; int nIntersections = 0; while (scene->Intersect(photonRay, &photonIsect)) { ++nIntersections; // Handle photon/surface intersection alpha *= renderer->Transmittance(scene, photonRay, NULL, rng, arena); BSDF *photonBSDF = photonIsect.GetBSDF(photonRay, arena); BxDFType specularType = BxDFType(BSDF_REFLECTION | BSDF_TRANSMISSION | BSDF_SPECULAR); bool hasNonSpecular = (photonBSDF->NumComponents() > photonBSDF->NumComponents(specularType)); Vector wo = -photonRay.d; if (hasNonSpecular) { // Deposit photon at surface Photon photon(photonIsect.dg.p, alpha, wo); bool depositedPhoton = false; if (specularPath && nIntersections > 1) { if (!causticDone) { PBRT_PHOTON_MAP_DEPOSITED_CAUSTIC_PHOTON(&photonIsect.dg, &alpha, &wo); depositedPhoton = true; localCausticPhotons.push_back(photon); } } else { // Deposit either direct or indirect photon // stop depositing direct photons once indirectDone is true; don't // want to waste memory storing too many if we're going a long time // trying to get enough caustic photons desposited. if (nIntersections == 1 && !indirectDone && integrator->finalGather) { PBRT_PHOTON_MAP_DEPOSITED_DIRECT_PHOTON(&photonIsect.dg, &alpha, &wo); depositedPhoton = true; localDirectPhotons.push_back(photon); } else if (nIntersections > 1 && !indirectDone) { PBRT_PHOTON_MAP_DEPOSITED_INDIRECT_PHOTON(&photonIsect.dg, &alpha, &wo); depositedPhoton = true; localIndirectPhotons.push_back(photon); } } // Possibly create radiance photon at photon intersection point if (depositedPhoton && integrator->finalGather && rng.RandomFloat() < .125f) { Normal n = photonIsect.dg.nn; n = Faceforward(n, -photonRay.d); localRadiancePhotons.push_back(RadiancePhoton(photonIsect.dg.p, n)); Spectrum rho_r = photonBSDF->rho(rng, BSDF_ALL_REFLECTION); localRpReflectances.push_back(rho_r); Spectrum rho_t = photonBSDF->rho(rng, BSDF_ALL_TRANSMISSION); localRpTransmittances.push_back(rho_t); } } if (nIntersections >= integrator->maxPhotonDepth) break; // Sample new photon ray direction Vector wi; float pdf; BxDFType flags; Spectrum fr = photonBSDF->Sample_f(wo, &wi, BSDFSample(rng), &pdf, BSDF_ALL, &flags); if (fr.IsBlack() || pdf == 0.f) break; Spectrum anew = alpha * fr * AbsDot(wi, photonBSDF->dgShading.nn) / pdf; // Possibly terminate photon path with Russian roulette float continueProb = min(1.f, anew.y() / alpha.y()); if (rng.RandomFloat() > continueProb) break; alpha = anew / continueProb; specularPath &= ((flags & BSDF_SPECULAR) != 0); if (indirectDone && !specularPath) break; photonRay = RayDifferential(photonIsect.dg.p, wi, photonRay, photonIsect.rayEpsilon); } PBRT_PHOTON_MAP_FINISHED_RAY_PATH(&photonRay, &alpha); } arena.FreeAll(); } // Merge local photon data with data in _PhotonIntegrator_ { MutexLock lock(mutex); // Give up if we're not storing enough photons if (abortTasks) return; if (nshot > 500000 && (unsuccessful(integrator->nCausticPhotonsWanted, causticPhotons.size(), blockSize) || unsuccessful(integrator->nIndirectPhotonsWanted, indirectPhotons.size(), blockSize))) { Error("Unable to store enough photons. Giving up.\n"); causticPhotons.erase(causticPhotons.begin(), causticPhotons.end()); indirectPhotons.erase(indirectPhotons.begin(), indirectPhotons.end()); radiancePhotons.erase(radiancePhotons.begin(), radiancePhotons.end()); abortTasks = true; return; } progress.Update(localIndirectPhotons.size() + localCausticPhotons.size()); nshot += blockSize; // Merge indirect photons into shared array if (!indirectDone) { integrator->nIndirectPaths += blockSize; for (uint32_t i = 0; i < localIndirectPhotons.size(); ++i) indirectPhotons.push_back(localIndirectPhotons[i]); localIndirectPhotons.erase(localIndirectPhotons.begin(), localIndirectPhotons.end()); if (indirectPhotons.size() >= integrator->nIndirectPhotonsWanted) indirectDone = true; nDirectPaths += blockSize; for (uint32_t i = 0; i < localDirectPhotons.size(); ++i) directPhotons.push_back(localDirectPhotons[i]); localDirectPhotons.erase(localDirectPhotons.begin(), localDirectPhotons.end()); } // Merge direct, caustic, and radiance photons into shared array if (!causticDone) { integrator->nCausticPaths += blockSize; for (uint32_t i = 0; i < localCausticPhotons.size(); ++i) causticPhotons.push_back(localCausticPhotons[i]); localCausticPhotons.erase(localCausticPhotons.begin(), localCausticPhotons.end()); if (causticPhotons.size() >= integrator->nCausticPhotonsWanted) causticDone = true; } for (uint32_t i = 0; i < localRadiancePhotons.size(); ++i) radiancePhotons.push_back(localRadiancePhotons[i]); localRadiancePhotons.erase(localRadiancePhotons.begin(), localRadiancePhotons.end()); for (uint32_t i = 0; i < localRpReflectances.size(); ++i) rpReflectances.push_back(localRpReflectances[i]); localRpReflectances.erase(localRpReflectances.begin(), localRpReflectances.end()); for (uint32_t i = 0; i < localRpTransmittances.size(); ++i) rpTransmittances.push_back(localRpTransmittances[i]); localRpTransmittances.erase(localRpTransmittances.begin(), localRpTransmittances.end()); } // Exit task if enough photons have been found if (indirectDone && causticDone) break; } }
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 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; }