size_t generateVPLs(const Scene *scene, Random *random, size_t offset, size_t count, int maxDepth, bool prune, std::deque<VPL> &vpls) { if (maxDepth <= 1) return 0; static Sampler *sampler = NULL; if (!sampler) { Properties props("halton"); props.setInteger("scramble", 0); sampler = static_cast<Sampler *> (PluginManager::getInstance()-> createObject(MTS_CLASS(Sampler), props)); sampler->configure(); } const Sensor *sensor = scene->getSensor(); Float time = sensor->getShutterOpen() + 0.5f * sensor->getShutterOpenTime(); const Frame stdFrame(Vector(1,0,0), Vector(0,1,0), Vector(0,0,1)); while (vpls.size() < count) { sampler->setSampleIndex(++offset); PositionSamplingRecord pRec(time); DirectionSamplingRecord dRec; Spectrum weight = scene->sampleEmitterPosition(pRec, sampler->next2D()); size_t start = vpls.size(); /* Sample an emitted particle */ const Emitter *emitter = static_cast<const Emitter *>(pRec.object); if (!emitter->isEnvironmentEmitter() && emitter->needsDirectionSample()) { VPL lumVPL(EPointEmitterVPL, weight); lumVPL.its.p = pRec.p; lumVPL.its.shFrame = pRec.n.isZero() ? stdFrame : Frame(pRec.n); lumVPL.emitter = emitter; appendVPL(scene, random, lumVPL, prune, vpls); weight *= emitter->sampleDirection(dRec, pRec, sampler->next2D()); } else { /* Hack to get the proper information for directional VPLs */ DirectSamplingRecord diRec( scene->getKDTree()->getAABB().getCenter(), pRec.time); Spectrum weight2 = emitter->sampleDirect(diRec, sampler->next2D()) / scene->pdfEmitterDiscrete(emitter); if (weight2.isZero()) continue; VPL lumVPL(EDirectionalEmitterVPL, weight2); lumVPL.its.p = Point(0.0); lumVPL.its.shFrame = Frame(-diRec.d); lumVPL.emitter = emitter; appendVPL(scene, random, lumVPL, false, vpls); dRec.d = -diRec.d; Point2 offset = Warp::squareToUniformDiskConcentric(sampler->next2D()); Vector perpOffset = Frame(diRec.d).toWorld(Vector(offset.x, offset.y, 0)); BSphere geoBSphere = scene->getKDTree()->getAABB().getBSphere(); pRec.p = geoBSphere.center + (perpOffset - dRec.d) * geoBSphere.radius; weight = weight2 * M_PI * geoBSphere.radius * geoBSphere.radius; } int depth = 2; Ray ray(pRec.p, dRec.d, time); Intersection its; while (!weight.isZero() && (depth < maxDepth || maxDepth == -1)) { if (!scene->rayIntersect(ray, its)) break; const BSDF *bsdf = its.getBSDF(); BSDFSamplingRecord bRec(its, sampler, EImportance); Spectrum bsdfVal = bsdf->sample(bRec, sampler->next2D()); if (bsdfVal.isZero()) break; /* Assuming that BSDF importance sampling is perfect, the following should equal the maximum albedo over all spectral samples */ Float approxAlbedo = std::min((Float) 0.95f, bsdfVal.max()); if (sampler->next1D() > approxAlbedo) break; else weight /= approxAlbedo; VPL vpl(ESurfaceVPL, weight); vpl.its = its; if (BSDF::getMeasure(bRec.sampledType) == ESolidAngle) appendVPL(scene, random, vpl, prune, vpls); weight *= bsdfVal; Vector wi = -ray.d, wo = its.toWorld(bRec.wo); ray = Ray(its.p, wo, 0.0f); /* Prevent light leaks due to the use of shading normals -- [Veach, p. 158] */ Float wiDotGeoN = dot(its.geoFrame.n, wi), woDotGeoN = dot(its.geoFrame.n, wo); if (wiDotGeoN * Frame::cosTheta(bRec.wi) <= 0 || woDotGeoN * Frame::cosTheta(bRec.wo) <= 0) break; /* Disabled for now -- this increases VPL weights and accuracy is not really a big requirement */ #if 0 /* Adjoint BSDF for shading normals -- [Veach, p. 155] */ weight *= std::abs( (Frame::cosTheta(bRec.wi) * woDotGeoN)/ (Frame::cosTheta(bRec.wo) * wiDotGeoN)); #endif ++depth; } size_t end = vpls.size(); for (size_t i=start; i<end; ++i) vpls[i].emitterScale = 1.0f / (end - start); } return offset; }
Spectrum Li(const RayDifferential &r, RadianceQueryRecord &rRec) const { /* Some aliases and local variables */ const Scene *scene = rRec.scene; Intersection &its = rRec.its; RayDifferential ray(r); Spectrum Li(0.0f); /* Perform the first ray intersection (or ignore if the intersection has already been provided). */ rRec.rayIntersect(ray); ray.mint = Epsilon; Spectrum pathThroughput(1.0f); while (rRec.depth <= m_maxDepth || m_maxDepth < 0) { if (!its.isValid()) { /* If no intersection could be found, potentially return radiance from a background luminaire if it exists */ if (rRec.type & RadianceQueryRecord::EEmittedRadiance) Li += pathThroughput * scene->LeBackground(ray); break; } const BSDF *bsdf = its.getBSDF(ray); if (EXPECT_NOT_TAKEN(bsdf == NULL)) { /* The MI path tracer doesn't support surfaces without a BSDF (e.g. medium transitions) -- give up. */ break; } /* Possibly include emitted radiance if requested */ if (its.isLuminaire() && (rRec.type & RadianceQueryRecord::EEmittedRadiance)) Li += pathThroughput * its.Le(-ray.d); /* Include radiance from a subsurface integrator if requested */ if (its.hasSubsurface() && (rRec.type & RadianceQueryRecord::ESubsurfaceRadiance)) Li += pathThroughput * its.LoSub(scene, rRec.sampler, -ray.d, rRec.depth); if (m_maxDepth > 0 && rRec.depth >= m_maxDepth) break; /* ==================================================================== */ /* Luminaire sampling */ /* ==================================================================== */ /* Prevent light leaks due to the use of shading normals */ Float wiDotGeoN = -dot(its.geoFrame.n, ray.d), wiDotShN = Frame::cosTheta(its.wi); if (wiDotGeoN * wiDotShN < 0 && m_strictNormals) break; /* Estimate the direct illumination if this is requested */ LuminaireSamplingRecord lRec; if (rRec.type & RadianceQueryRecord::EDirectSurfaceRadiance && scene->sampleLuminaire(its.p, ray.time, lRec, rRec.nextSample2D())) { /* Allocate a record for querying the BSDF */ const Vector wo = -lRec.d; const BSDFQueryRecord bRec(its, its.toLocal(wo)); /* Evaluate BSDF * cos(theta) */ const Spectrum bsdfVal = bsdf->fCos(bRec); Float woDotGeoN = dot(its.geoFrame.n, wo); /* Prevent light leaks due to the use of shading normals */ if (!bsdfVal.isZero() && (!m_strictNormals || woDotGeoN * Frame::cosTheta(bRec.wo) > 0)) { /* Calculate prob. of having sampled that direction using BSDF sampling */ Float bsdfPdf = (lRec.luminaire->isIntersectable() || lRec.luminaire->isBackgroundLuminaire()) ? bsdf->pdf(bRec) : 0; /* Weight using the power heuristic */ const Float weight = miWeight(lRec.pdf, bsdfPdf); Li += pathThroughput * lRec.value * bsdfVal * weight; } } /* ==================================================================== */ /* BSDF sampling */ /* ==================================================================== */ /* Sample BSDF * cos(theta) */ BSDFQueryRecord bRec(its); Float bsdfPdf; Spectrum bsdfVal = bsdf->sampleCos(bRec, bsdfPdf, rRec.nextSample2D()); if (bsdfVal.isZero()) break; bsdfVal /= bsdfPdf; /* Prevent light leaks due to the use of shading normals */ const Vector wo = its.toWorld(bRec.wo); Float woDotGeoN = dot(its.geoFrame.n, wo); if (woDotGeoN * Frame::cosTheta(bRec.wo) <= 0 && m_strictNormals) break; /* Trace a ray in this direction */ ray = Ray(its.p, wo, ray.time); bool hitLuminaire = false; if (scene->rayIntersect(ray, its)) { /* Intersected something - check if it was a luminaire */ if (its.isLuminaire()) { lRec = LuminaireSamplingRecord(its, -ray.d); lRec.value = its.Le(-ray.d); hitLuminaire = true; } } else { /* No intersection found. Possibly, there is a background luminaire such as an environment map? */ if (scene->hasBackgroundLuminaire()) { lRec.luminaire = scene->getBackgroundLuminaire(); lRec.value = lRec.luminaire->Le(ray); lRec.d = -ray.d; hitLuminaire = true; } else { rRec.depth++; break; } } /* If a luminaire was hit, estimate the local illumination and weight using the power heuristic */ if (hitLuminaire && (rRec.type & RadianceQueryRecord::EDirectSurfaceRadiance)) { /* Prob. of having generated this sample using luminaire sampling */ const Float lumPdf = (!(bRec.sampledType & BSDF::EDelta)) ? scene->pdfLuminaire(ray.o, lRec) : 0; const Float weight = miWeight(bsdfPdf, lumPdf); Li += pathThroughput * lRec.value * bsdfVal * weight; } /* ==================================================================== */ /* Indirect illumination */ /* ==================================================================== */ /* Set the recursive query type */ /* Stop if no surface was hit by the BSDF sample or if indirect illumination was not requested */ if (!its.isValid() || !(rRec.type & RadianceQueryRecord::EIndirectSurfaceRadiance)) break; rRec.type = RadianceQueryRecord::ERadianceNoEmission; /* Russian roulette - Possibly stop the recursion. Don't do this when dealing with a transmission component, since solid angle compression factors cause problems with the heuristic below */ if (rRec.depth >= m_rrDepth && !(bRec.sampledType & BSDF::ETransmission)) { /* Assuming that BSDF importance sampling is perfect, 'bsdfVal.max()' should equal the maximum albedo over all spectral samples */ Float approxAlbedo = std::min((Float) 0.9f, bsdfVal.max()); if (rRec.nextSample1D() > approxAlbedo) break; else pathThroughput /= approxAlbedo; } pathThroughput *= bsdfVal; rRec.depth++; } /* Store statistics */ avgPathLength.incrementBase(); avgPathLength += rRec.depth; return Li; }