/* This file is part of Mitsuba, a physically based rendering system. Copyright (c) 2007-2012 by Wenzel Jakob and others. Mitsuba is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License Version 3 as published by the Free Software Foundation. Mitsuba is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "bre.h" MTS_NAMESPACE_BEGIN /*!\plugin{photonmapper}{Photon map integrator} * \order{6} * \parameters{ * \parameter{directSamples}{\Integer}{Number of samples used for the * direct illumination component \default{16}} * \parameter{glossySamples}{\Integer}{Number of samples used for the indirect * illumination component of glossy materials \default{32}} * \parameter{maxDepth}{\Integer}{Specifies the longest path depth * in the generated output image (where \code{-1} corresponds to $\infty$). * A value of \code{1} will only render directly visible light sources. * \code{2} will lead to single-bounce (direct-only) illumination, * and so on. \default{\code{-1}} * } * \parameter{globalPhotons}{\Integer}{Number of photons that will be collected for the global photon map\default{250000}} * \parameter{causticPhotons}{\Integer}{Number of photons that will be collected for the caustic photon map\default{250000}} * \parameter{volumePhotons}{\Integer}{Number of photons that will be collected for the volumetric photon map\default{250000}} * \parameter{globalLookup\showbreak Radius}{\Float}{Maximum radius of photon lookups in the global photon map (relative to the scene size)\default{0.05}} * \parameter{causticLookup\showbreak Radius}{\Float}{Maximum radius of photon lookups in the caustic photon map (relative to the scene size)\default{0.0125}} * \parameter{lookupSize}{\Integer}{Number of photons that should be fetched in photon map queries\default{120}} * \parameter{granularity}{\Integer}{ * Granularity of photon tracing work units for the purpose * of parallelization (in \# of shot particles) \default{0, i.e. decide automatically} * } * \parameter{hideEmitters}{\Boolean}{Hide directly visible emitters? * See page~\pageref{sec:hideemitters} for details. * \default{no, i.e. \code{false}} * } * \parameter{rrDepth}{\Integer}{Specifies the minimum path depth, after * which the implementation will start to use the ``russian roulette'' * path termination criterion. \default{\code{5}} * } * } * This plugin implements the two-pass photon mapping algorithm as proposed by Jensen \cite{Jensen1996Global}. * The implementation partitions the illumination into three different classes (diffuse, caustic, and volumetric), * and builds a separate photon map for each class. * * Following this, a standard recursive ray tracing pass is started which performs kernel density estimation * using these photon maps. Since the photon maps are visualized directly, the result will appear ``blotchy'' (\figref{pmap-blotchy}) * unless an extremely large number of photons is used. A simple remedy is to combine the photon mapper with * an irradiance cache, which performs \emph{final gathering} to remove these artifacts. Due to its caching nature, * the rendering process will be faster as well. * \begin{xml}[caption={Instantiation of a photon mapper with irradiance caching}] * * * * \end{xml} * \renderings{ * \rendering{Rendered using plain photon mapping}{integrator_photonmapper_1} * \rendering{Rendered using photon mapping together with irradiance caching}{integrator_photonmapper_2} * \vspace{-2mm} * \caption{\label{fig:pmap-blotchy}Sponza atrium illuminated by a point light and rendered using 5 million photons. * Irradiance caching significantly accelerates the rendering time and eliminates the ``blotchy'' * kernel density estimation artifacts. Model courtesy of Marko Dabrovic.} * } * * When the scene contains participating media, the Beam Radiance Estimate \cite{Jarosz2008Beam} * by Jarosz et al. is used to estimate the illumination due to volumetric scattering. * * \remarks{ * \item Currently, only homogeneous participating media are supported by this implementation * } */ class PhotonMapIntegrator : public SamplingIntegrator { public: PhotonMapIntegrator(const Properties &props) : SamplingIntegrator(props), m_parentIntegrator(NULL) { /* Number of lsamples for direct illumination */ m_directSamples = props.getInteger("directSamples", 16); /* Number of BSDF samples when intersecting a glossy material */ m_glossySamples = props.getInteger("glossySamples", 32); /* Depth to start using russian roulette when tracing photons */ m_rrDepth = props.getInteger("rrDepth", 5); /* Longest visualized path length (\c -1 = infinite). A value of \c 1 will visualize only directly visible light sources. \c 2 will lead to single-bounce (direct-only) illumination, and so on. */ m_maxDepth = props.getInteger("maxDepth", -1); /** * When encountering an ideally specular material, the photon mapper places * a sample on each lobe (e.g. reflection *and* transmission). This leads * to an exponential growth in running time but greatly reduces variance and * is therefore usually worth it. This parameter specifies after how many * bounces this behavior should be stopped. */ m_maxSpecularDepth = props.getInteger("maxSpecularDepth", 4); /* Granularity of photon tracing work units (in shot particles, 0 => decide automatically) */ m_granularity = props.getInteger("granularity", 0); /* Number of photons to collect for the global photon map */ m_globalPhotons = props.getSize("globalPhotons", 250000); /* Number of photons to collect for the caustic photon map */ m_causticPhotons = props.getSize("causticPhotons", 250000); /* Number of photons to collect for the volumetric photon map */ m_volumePhotons = props.getSize("volumePhotons", 250000); /* Max. radius of lookups in the global photon map (relative to the scene size) */ m_globalLookupRadiusRel = props.getFloat("globalLookupRadius", 0.05f); /* Max. radius of lookups in the caustic photon map (relative to the scene size) */ m_causticLookupRadiusRel = props.getFloat("causticLookupRadius", 0.0125f); /* Minimum amount of photons to consider a photon map lookup valid */ int lookupSize = props.getInteger("lookupSize", 120); /* Minimum amount of photons to consider a volumetric photon map lookup valid */ m_globalLookupSize = props.getInteger("globalLookupSize", lookupSize); /* Maximum number of results for caustic photon map lookups */ m_causticLookupSize = props.getInteger("causticLookupSize", lookupSize); /* Approximate number of volume photons to be used in a lookup */ m_volumeLookupSize = props.getInteger("volumeLookupSize", lookupSize); /* Should photon gathering steps exclusively run on the local machine? */ m_gatherLocally = props.getBoolean("gatherLocally", true); /* Indicates if the gathering steps should be canceled if not enough photons are generated. */ m_autoCancelGathering = props.getBoolean("autoCancelGathering", true); /* When this flag is set to true, contributions from directly * visible emitters will not be included in the rendered image */ m_hideEmitters = props.getBoolean("hideEmitters", false); if (m_maxDepth == 0) { Log(EError, "maxDepth must be greater than zero!"); } else if (m_maxDepth == -1) { /** * An infinite depth is currently not supported, since * the photon tracing step uses a Halton sequence * that is based on a finite-sized prime number table */ m_maxDepth = 128; } m_causticPhotonMapID = m_globalPhotonMapID = m_breID = 0; } /// Unserialize from a binary data stream PhotonMapIntegrator(Stream *stream, InstanceManager *manager) : SamplingIntegrator(stream, manager), m_parentIntegrator(NULL) { m_directSamples = stream->readInt(); m_glossySamples = stream->readInt(); m_maxDepth = stream->readInt(); m_maxSpecularDepth = stream->readInt(); m_rrDepth = stream->readInt(); m_globalPhotons = stream->readSize(); m_causticPhotons = stream->readSize(); m_volumePhotons = stream->readSize(); m_globalLookupRadius = stream->readFloat(); m_causticLookupRadius = stream->readFloat(); m_globalLookupSize = stream->readInt(); m_causticLookupSize = stream->readInt(); m_volumeLookupSize = stream->readInt(); m_gatherLocally = stream->readBool(); m_autoCancelGathering = stream->readBool(); m_hideEmitters = stream->readBool(); m_causticPhotonMapID = m_globalPhotonMapID = m_breID = 0; configure(); } virtual ~PhotonMapIntegrator() { ref sched = Scheduler::getInstance(); if (m_globalPhotonMapID) sched->unregisterResource(m_globalPhotonMapID); if (m_causticPhotonMapID) sched->unregisterResource(m_causticPhotonMapID); if (m_breID) sched->unregisterResource(m_breID); } void serialize(Stream *stream, InstanceManager *manager) const { SamplingIntegrator::serialize(stream, manager); stream->writeInt(m_directSamples); stream->writeInt(m_glossySamples); stream->writeInt(m_maxDepth); stream->writeInt(m_maxSpecularDepth); stream->writeInt(m_rrDepth); stream->writeSize(m_globalPhotons); stream->writeSize(m_causticPhotons); stream->writeSize(m_volumePhotons); stream->writeFloat(m_globalLookupRadius); stream->writeFloat(m_causticLookupRadius); stream->writeInt(m_globalLookupSize); stream->writeInt(m_causticLookupSize); stream->writeInt(m_volumeLookupSize); stream->writeBool(m_gatherLocally); stream->writeBool(m_autoCancelGathering); stream->writeBool(m_hideEmitters); } /// Configure the sampler for a specified amount of direct illumination samples void configureSampler(const Scene *scene, Sampler *sampler) { SamplingIntegrator::configureSampler(scene, sampler); if (m_directSamples > 1) sampler->request2DArray(m_directSamples); int bsdfSamples = std::max(m_directSamples, m_glossySamples); if (bsdfSamples > 1) sampler->request2DArray(bsdfSamples); bool hasDelta = false; const ref_vector &shapes = scene->getShapes(); for (size_t i=0; igetBSDF(); if (bsdf && bsdf->getType() & BSDF::EDelta) hasDelta = true; } if (!hasDelta) m_causticPhotons = 0; } void configure() { m_invEmitterSamples = 1.0f / m_directSamples; m_invGlossySamples = 1.0f / m_glossySamples; } bool preprocess(const Scene *scene, RenderQueue *queue, const RenderJob *job, int sceneResID, int sensorResID, int samplerResID) { SamplingIntegrator::preprocess(scene, queue, job, sceneResID, sensorResID, samplerResID); /* Create a deterministic sampler for the photon gathering step */ ref sched = Scheduler::getInstance(); ref sampler = static_cast (PluginManager::getInstance()-> createObject(MTS_CLASS(Sampler), Properties("halton"))); /* Create a sampler instance for every core */ std::vector samplers(sched->getCoreCount()); for (size_t i=0; igetCoreCount(); ++i) { ref clonedSampler = sampler->clone(); clonedSampler->incRef(); samplers[i] = clonedSampler.get(); } int qmcSamplerID = sched->registerMultiResource(samplers); for (size_t i=0; idecRef(); const ref_vector &media = scene->getMedia(); for (ref_vector::const_iterator it = media.begin(); it != media.end(); ++it) { if (!(*it)->isHomogeneous()) Log(EError, "Inhomogeneous media are currently not supported by the photon mapper!"); } if (m_globalPhotonMap.get() == NULL && m_globalPhotons > 0) { /* Generate the global photon map */ ref proc = new GatherPhotonProcess( GatherPhotonProcess::ESurfacePhotons, m_globalPhotons, m_granularity, m_maxDepth-1, m_rrDepth, m_gatherLocally, m_autoCancelGathering, job); proc->bindResource("scene", sceneResID); proc->bindResource("sensor", sensorResID); proc->bindResource("sampler", qmcSamplerID); m_proc = proc; sched->schedule(proc); sched->wait(proc); m_proc = NULL; if (proc->getReturnStatus() != ParallelProcess::ESuccess) return false; ref globalPhotonMap = proc->getPhotonMap(); if (globalPhotonMap->isFull()) { Log(EDebug, "Global photon map full. Shot " SIZE_T_FMT " particles, excess photons due to parallelism: " SIZE_T_FMT, proc->getShotParticles(), proc->getExcessPhotons()); m_globalPhotonMap = globalPhotonMap; m_globalPhotonMap->setScaleFactor(1 / (Float) proc->getShotParticles()); m_globalPhotonMap->build(); m_globalPhotonMapID = sched->registerResource(m_globalPhotonMap); } } if (m_causticPhotonMap.get() == NULL && m_causticPhotons > 0) { /* Generate the caustic photon map */ ref proc = new GatherPhotonProcess( GatherPhotonProcess::ECausticPhotons, m_causticPhotons, m_granularity, m_maxDepth-1, m_rrDepth, m_gatherLocally, m_autoCancelGathering, job); proc->bindResource("scene", sceneResID); proc->bindResource("sensor", sensorResID); proc->bindResource("sampler", qmcSamplerID); m_proc = proc; sched->schedule(proc); sched->wait(proc); m_proc = NULL; if (proc->getReturnStatus() != ParallelProcess::ESuccess) return false; ref causticPhotonMap = proc->getPhotonMap(); if (causticPhotonMap->isFull()) { Log(EDebug, "Caustic photon map full. Shot " SIZE_T_FMT " particles, excess photons due to parallelism: " SIZE_T_FMT, proc->getShotParticles(), proc->getExcessPhotons()); m_causticPhotonMap = causticPhotonMap; m_causticPhotonMap->setScaleFactor(1 / (Float) proc->getShotParticles()); m_causticPhotonMap->build(); m_causticPhotonMapID = sched->registerResource(m_causticPhotonMap); } } size_t volumePhotons = scene->getMedia().size() == 0 ? 0 : m_volumePhotons; if (m_volumePhotonMap.get() == NULL && volumePhotons > 0) { /* Generate the volume photon map */ ref proc = new GatherPhotonProcess( GatherPhotonProcess::EVolumePhotons, volumePhotons, m_granularity, m_maxDepth-1, m_rrDepth, m_gatherLocally, m_autoCancelGathering, job); proc->bindResource("scene", sceneResID); proc->bindResource("sensor", sensorResID); proc->bindResource("sampler", qmcSamplerID); m_proc = proc; sched->schedule(proc); sched->wait(proc); m_proc = NULL; if (proc->getReturnStatus() != ParallelProcess::ESuccess) return false; ref volumePhotonMap = proc->getPhotonMap(); if (volumePhotonMap->isFull()) { Log(EDebug, "Volume photon map full. Shot " SIZE_T_FMT " particles, excess photons due to parallelism: " SIZE_T_FMT, proc->getShotParticles(), proc->getExcessPhotons()); volumePhotonMap->setScaleFactor(1 / (Float) proc->getShotParticles()); volumePhotonMap->build(); m_bre = new BeamRadianceEstimator(volumePhotonMap, m_volumeLookupSize); m_breID = sched->registerResource(m_bre); } } /* Adapt to scene extents */ m_globalLookupRadius = m_globalLookupRadiusRel * scene->getBSphere().radius; m_causticLookupRadius = m_causticLookupRadiusRel * scene->getBSphere().radius; sched->unregisterResource(qmcSamplerID); return true; } void setParent(ConfigurableObject *parent) { if (parent->getClass()->derivesFrom(MTS_CLASS(SamplingIntegrator))) m_parentIntegrator = static_cast(parent); else m_parentIntegrator = this; } /// Specify globally shared resources void bindUsedResources(ParallelProcess *proc) const { if (m_globalPhotonMap.get()) proc->bindResource("globalPhotonMap", m_globalPhotonMapID); if (m_causticPhotonMap.get()) proc->bindResource("causticPhotonMap", m_causticPhotonMapID); if (m_bre.get()) proc->bindResource("bre", m_breID); } /// Connect to globally shared resources void wakeup(ConfigurableObject *parent, std::map ¶ms) { if (!m_globalPhotonMap.get() && params.find("globalPhotonMap") != params.end()) m_globalPhotonMap = static_cast(params["globalPhotonMap"]); if (!m_causticPhotonMap.get() && params.find("causticPhotonMap") != params.end()) m_causticPhotonMap = static_cast(params["causticPhotonMap"]); if (!m_bre.get() && params.find("bre") != params.end()) m_bre = static_cast(params["bre"]); if (parent && parent->getClass()->derivesFrom(MTS_CLASS(SamplingIntegrator))) m_parentIntegrator = static_cast(parent); else m_parentIntegrator = this; } void cancel() { SamplingIntegrator::cancel(); if (m_proc) Scheduler::getInstance()->cancel(m_proc); } Spectrum Li(const RayDifferential &ray, RadianceQueryRecord &rRec) const { Spectrum LiSurf(0.0f), LiMedium(0.0f), transmittance(1.0f); Intersection &its = rRec.its; const Scene *scene = rRec.scene; bool cacheQuery = (rRec.extra & RadianceQueryRecord::ECacheQuery); bool adaptiveQuery = (rRec.extra & RadianceQueryRecord::EAdaptiveQuery); /* Perform the first ray intersection (or ignore if the intersection has already been provided). */ rRec.rayIntersect(ray); if (rRec.medium) { Ray mediumRaySegment(ray, 0, its.t); transmittance = rRec.medium->evalTransmittance(mediumRaySegment); mediumRaySegment.mint = ray.mint; if (rRec.type & RadianceQueryRecord::EVolumeRadiance && (rRec.depth < m_maxDepth || m_maxDepth < 0) && m_bre.get() != NULL) LiMedium = m_bre->query(mediumRaySegment, rRec.medium); } if (!its.isValid()) { /* If no intersection could be found, possibly return attenuated radiance from a background luminaire */ if ((rRec.type & RadianceQueryRecord::EEmittedRadiance) && !m_hideEmitters) LiSurf = scene->evalEnvironment(ray); return LiSurf * transmittance + LiMedium; } /* Possibly include emitted radiance if requested */ if (its.isEmitter() && (rRec.type & RadianceQueryRecord::EEmittedRadiance) && !m_hideEmitters) LiSurf += its.Le(-ray.d); /* Include radiance from a subsurface scattering model if requested */ if (its.hasSubsurface() && (rRec.type & RadianceQueryRecord::ESubsurfaceRadiance)) LiSurf += its.LoSub(scene, rRec.sampler, -ray.d, rRec.depth); const BSDF *bsdf = its.getBSDF(ray); if (rRec.depth >= m_maxDepth && m_maxDepth > 0) return LiSurf * transmittance + LiMedium; unsigned int bsdfType = bsdf->getType() & BSDF::EAll; /* Irradiance cache query -> treat as diffuse */ bool isDiffuse = (bsdfType == BSDF::EDiffuseReflection) || cacheQuery; bool hasSpecular = bsdfType & BSDF::EDelta; /* Exhaustively recurse into all specular lobes? */ bool exhaustiveSpecular = rRec.depth < m_maxSpecularDepth && !cacheQuery; if (isDiffuse && (dot(its.shFrame.n, ray.d) < 0 || (bsdf->getType() & BSDF::EBackSide))) { /* 1. Diffuse indirect */ int maxDepth = m_maxDepth == -1 ? INT_MAX : (m_maxDepth-rRec.depth); if (rRec.type & RadianceQueryRecord::EIndirectSurfaceRadiance && m_globalPhotonMap.get()) LiSurf += m_globalPhotonMap->estimateIrradiance(its.p, its.shFrame.n, m_globalLookupRadius, maxDepth, m_globalLookupSize) * bsdf->getDiffuseReflectance(its) * INV_PI; if (rRec.type & RadianceQueryRecord::ECausticRadiance && m_causticPhotonMap.get()) LiSurf += m_causticPhotonMap->estimateIrradiance(its.p, its.shFrame.n, m_causticLookupRadius, maxDepth, m_causticLookupSize) * bsdf->getDiffuseReflectance(its) * INV_PI; } if (hasSpecular && exhaustiveSpecular && (rRec.type & RadianceQueryRecord::EIndirectSurfaceRadiance)) { /* 1. Specular indirect */ int compCount = bsdf->getComponentCount(); RadianceQueryRecord rRec2; for (int i=0; igetType(i); if (!(type & BSDF::EDelta)) continue; /* Sample the BSDF and recurse */ BSDFSamplingRecord bRec(its, rRec.sampler, ERadiance); bRec.component = i; Spectrum bsdfVal = bsdf->sample(bRec, Point2(0.5f)); if (bsdfVal.isZero()) continue; rRec2.recursiveQuery(rRec, RadianceQueryRecord::ERadiance); RayDifferential bsdfRay(its.p, its.toWorld(bRec.wo), ray.time); if (its.isMediumTransition()) rRec2.medium = its.getTargetMedium(bsdfRay.d); LiSurf += bsdfVal * m_parentIntegrator->Li(bsdfRay, rRec2); } } /* Estimate the direct illumination if this is requested */ int numEmitterSamples = m_directSamples, numBSDFSamples; Float weightLum, weightBSDF; Point2 *sampleArray; Point2 sample; if (rRec.depth > 1 || cacheQuery || adaptiveQuery) { /* This integrator is used recursively by another integrator. Be less accurate as this sample will not directly be observed. */ numBSDFSamples = numEmitterSamples = 1; weightLum = weightBSDF = 1.0f; } else { if (isDiffuse) { numBSDFSamples = m_directSamples; weightBSDF = weightLum = m_invEmitterSamples; } else { numBSDFSamples = m_glossySamples; weightLum = m_invEmitterSamples; weightBSDF = m_invGlossySamples; } } if ((bsdfType & BSDF::ESmooth) && (rRec.type & RadianceQueryRecord::EDirectSurfaceRadiance)) { DirectSamplingRecord dRec(its); if (numEmitterSamples > 1) { sampleArray = rRec.sampler->next2DArray(m_directSamples); } else { sample = rRec.nextSample2D(); sampleArray = &sample; } for (int i=0; isampleAttenuatedEmitterDirect( dRec, its, rRec.medium, interactions, sampleArray[i], rRec.sampler); /* Estimate the direct illumination if this is requested */ if (!value.isZero()) { const Emitter *emitter = static_cast(dRec.object); /* Allocate a record for querying the BSDF */ BSDFSamplingRecord bRec(its, its.toLocal(dRec.d)); /* Evaluate BSDF * cos(theta) */ const Spectrum bsdfVal = bsdf->eval(bRec); if (!bsdfVal.isZero()) { /* Calculate prob. of having sampled that direction using BSDF sampling */ if (!hasSpecular || exhaustiveSpecular) bRec.typeMask = BSDF::ESmooth; Float bsdfPdf = (emitter->isOnSurface() && dRec.measure == ESolidAngle && interactions == 0) ? bsdf->pdf(bRec) : (Float) 0.0f; /* Weight using the power heuristic */ const Float weight = miWeight(dRec.pdf * numEmitterSamples, bsdfPdf * numBSDFSamples) * weightLum; LiSurf += value * bsdfVal * weight; } } } } /* ==================================================================== */ /* BSDF sampling */ /* ==================================================================== */ /* Sample direct compontent via BSDF sampling if this is generally requested AND the BSDF is smooth, or there is a delta component that was not handled by the exhaustive sampling loop above */ bool bsdfSampleDirect = (rRec.type & RadianceQueryRecord::EDirectSurfaceRadiance) && ((bsdfType & BSDF::ESmooth) || (hasSpecular && !exhaustiveSpecular)); /* Sample indirect component via BSDF sampling if this is generally requested AND the BSDF is non-diffuse (diffuse is handled by the global photon map) or there is a delta component that was not handled by the exhaustive sampling loop above. */ bool bsdfSampleIndirect = (rRec.type & RadianceQueryRecord::EIndirectSurfaceRadiance) && !isDiffuse && ((bsdfType & BSDF::ESmooth) || (hasSpecular && !exhaustiveSpecular)); if (bsdfSampleDirect || bsdfSampleIndirect) { if (numBSDFSamples > 1) { sampleArray = rRec.sampler->next2DArray( std::max(m_directSamples, m_glossySamples)); } else { sample = rRec.nextSample2D(); sampleArray = &sample; } RadianceQueryRecord rRec2; Intersection &bsdfIts = rRec2.its; DirectSamplingRecord dRec(its); for (int i=0; isample(bRec, bsdfPdf, sampleArray[i]); if (bsdfVal.isZero()) continue; /* Trace a ray in this direction */ RayDifferential bsdfRay(its.p, its.toWorld(bRec.wo), ray.time); Spectrum value; bool hitEmitter = false; if (scene->rayIntersect(bsdfRay, bsdfIts)) { /* Intersected something - check if it was a luminaire */ if (bsdfIts.isEmitter() && bsdfSampleDirect) { value = bsdfIts.Le(-bsdfRay.d); dRec.setQuery(bsdfRay, bsdfIts); hitEmitter = true; } } else if (bsdfSampleDirect) { /* Intersected nothing -- perhaps there is an environment map? */ const Emitter *env = scene->getEnvironmentEmitter(); if (env) { value = env->evalEnvironment(bsdfRay); if (env->fillDirectSamplingRecord(dRec, bsdfRay)) hitEmitter = true; } } if (hitEmitter) { const Float emitterPdf = scene->pdfEmitterDirect(dRec); Spectrum transmittance = rRec2.medium ? rRec2.medium->evalTransmittance(Ray(bsdfRay, 0, bsdfIts.t)) : Spectrum(1.0f); const Float weight = miWeight(bsdfPdf * numBSDFSamples, emitterPdf * numEmitterSamples) * weightBSDF; LiSurf += value * bsdfVal * weight * transmittance; } /* Recurse */ if (bsdfSampleIndirect) { rRec2.recursiveQuery(rRec, RadianceQueryRecord::ERadianceNoEmission); rRec2.type ^= RadianceQueryRecord::EIntersection; if (its.isMediumTransition()) rRec2.medium = its.getTargetMedium(bsdfRay.d); LiSurf += bsdfVal * m_parentIntegrator->Li(bsdfRay, rRec2) * weightBSDF; } } } return LiSurf * transmittance + LiMedium; } std::string toString() const { std::ostringstream oss; oss << "PhotonMapIntegrator[" << endl << " maxDepth = " << m_maxDepth << "," << endl << " maxSpecularDepth = " << m_maxSpecularDepth << "," << endl << " rrDepth = " << m_rrDepth << "," << endl << " directSamples = " << m_directSamples << "," << endl << " glossySamples = " << m_glossySamples << "," << endl << " globalPhotons = " << m_globalPhotons << "," << endl << " causticPhotons = " << m_causticPhotons << "," << endl << " volumePhotons = " << m_volumePhotons << "," << endl << " gatherLocally = " << m_gatherLocally << "," << endl << " globalLookupRadius = " << m_globalLookupRadius << "," << endl << " causticLookupRadius = " << m_causticLookupRadius << "," << endl << " globalLookupSize = " << m_globalLookupSize << "," << endl << " causticLookupSize = " << m_causticLookupSize << "," << endl << " volumeLookupSize = " << m_volumeLookupSize << endl << "]"; return oss.str(); } inline Float miWeight(Float pdfA, Float pdfB) const { pdfA *= pdfA; pdfB *= pdfB; return pdfA / (pdfA + pdfB); } MTS_DECLARE_CLASS() private: ref m_globalPhotonMap; ref m_causticPhotonMap; ref m_volumePhotonMap; ref m_bre; ref m_proc; SamplingIntegrator *m_parentIntegrator; int m_globalPhotonMapID, m_causticPhotonMapID, m_breID; size_t m_globalPhotons, m_causticPhotons, m_volumePhotons; int m_globalLookupSize, m_causticLookupSize, m_volumeLookupSize; Float m_globalLookupRadiusRel, m_globalLookupRadius; Float m_causticLookupRadiusRel, m_causticLookupRadius; Float m_invEmitterSamples, m_invGlossySamples; int m_granularity, m_directSamples, m_glossySamples; int m_rrDepth, m_maxDepth, m_maxSpecularDepth; bool m_gatherLocally, m_autoCancelGathering; bool m_hideEmitters; }; MTS_IMPLEMENT_CLASS_S(PhotonMapIntegrator, false, SamplingIntegrator) MTS_EXPORT_PLUGIN(PhotonMapIntegrator, "Photon map integrator"); MTS_NAMESPACE_END