696 lines
28 KiB
C++
696 lines
28 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <mitsuba/core/plugin.h>
|
|
#include <mitsuba/render/common.h>
|
|
#include <mitsuba/render/gatherproc.h>
|
|
#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}]
|
|
* <integrator type="irrcache">
|
|
* <integrator type="photonmapper"/>
|
|
* </integrator>
|
|
* \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<Scheduler> 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<Shape> &shapes = scene->getShapes();
|
|
for (size_t i=0; i<shapes.size(); ++i) {
|
|
const BSDF *bsdf = shapes[i]->getBSDF();
|
|
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<Scheduler> sched = Scheduler::getInstance();
|
|
ref<Sampler> sampler = static_cast<Sampler *> (PluginManager::getInstance()->
|
|
createObject(MTS_CLASS(Sampler), Properties("halton")));
|
|
/* Create a sampler instance for every core */
|
|
std::vector<SerializableObject *> samplers(sched->getCoreCount());
|
|
for (size_t i=0; i<sched->getCoreCount(); ++i) {
|
|
ref<Sampler> clonedSampler = sampler->clone();
|
|
clonedSampler->incRef();
|
|
samplers[i] = clonedSampler.get();
|
|
}
|
|
int qmcSamplerID = sched->registerMultiResource(samplers);
|
|
for (size_t i=0; i<samplers.size(); ++i)
|
|
samplers[i]->decRef();
|
|
|
|
const ref_vector<Medium> &media = scene->getMedia();
|
|
for (ref_vector<Medium>::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<GatherPhotonProcess> 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<PhotonMap> 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<GatherPhotonProcess> 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<PhotonMap> 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<GatherPhotonProcess> 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<PhotonMap> 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<SamplingIntegrator *>(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<std::string, SerializableObject *> ¶ms) {
|
|
if (!m_globalPhotonMap.get() && params.find("globalPhotonMap") != params.end())
|
|
m_globalPhotonMap = static_cast<PhotonMap *>(params["globalPhotonMap"]);
|
|
if (!m_causticPhotonMap.get() && params.find("causticPhotonMap") != params.end())
|
|
m_causticPhotonMap = static_cast<PhotonMap *>(params["causticPhotonMap"]);
|
|
if (!m_bre.get() && params.find("bre") != params.end())
|
|
m_bre = static_cast<BeamRadianceEstimator *>(params["bre"]);
|
|
|
|
if (parent && parent->getClass()->derivesFrom(MTS_CLASS(SamplingIntegrator)))
|
|
m_parentIntegrator = static_cast<SamplingIntegrator *>(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; i<compCount; i++) {
|
|
unsigned int type = bsdf->getType(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; i<numEmitterSamples; ++i) {
|
|
int interactions = m_maxDepth - rRec.depth - 1;
|
|
Spectrum value = scene->sampleAttenuatedEmitterDirect(
|
|
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<const Emitter *>(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; i<numBSDFSamples; ++i) {
|
|
/* Sample BSDF * cos(theta) */
|
|
BSDFSamplingRecord bRec(its, rRec.sampler, ERadiance);
|
|
if (!hasSpecular || exhaustiveSpecular)
|
|
bRec.typeMask = BSDF::ESmooth;
|
|
|
|
Float bsdfPdf;
|
|
Spectrum bsdfVal = bsdf->sample(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<PhotonMap> m_globalPhotonMap;
|
|
ref<PhotonMap> m_causticPhotonMap;
|
|
ref<PhotonMap> m_volumePhotonMap;
|
|
ref<BeamRadianceEstimator> m_bre;
|
|
ref<ParallelProcess> 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
|