/*
This file is part of Mitsuba, a physically based rendering system.
Copyright (c) 2007-2010 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 "irrcache_proc.h"
MTS_NAMESPACE_BEGIN
/**
* Irradiance caching integrator - forwards all radiance computations
* to an arbitrary sampling-based sub-integrator - with one exception:
* whenever a Lambertian surface is intersected, an internal irradiance
* cache is queried for the indirect illumination at the surface position in
* question. If this query is successful, the sub-integrator is only
* used to compute the remaining types of radiance (direct, in-scatter,
* emission) and their sum is returned afterwards.
* When a query is unsuccessful, a new data point is generated by a final
* gathering step.
*
* The generality of this implementation allows it to be used in conjunction
* with photon mapping (the most likely application) as well as all other
* sampling-based integrators in Mitsuba. Several optimizations are used to
* improve the achieved interpolation quality, namely irradiance gradients
* [Ward et al.], neighbor clamping [Krivanek et al.], a screen-space
* clamping metric and an improved error function [Tabellion et al.].
* By default, this integrator also performs a distributed overture pass before
* rendering, which is recommended to avoid artifacts resulting from the
* addition of samples as rendering proceeds.
*/
class IrradianceCacheIntegrator : public SampleIntegrator {
friend class OvertureThread;
public:
IrradianceCacheIntegrator(const Properties &props) : SampleIntegrator(props) {
/* Elevational resolution of the stratified final gather hemisphere.
The azimuthal resolution is three times this value. Default:
14x(3*14)=588 samples */
m_resolution = props.getInteger("resolution", 14);
/* If set to true, the irradiance cache will be filled by a
parallel overture pass before the main rendering process starts.
This is strongly recommended. */
m_overture = props.getBoolean("overture", true);
/* Quality setting (\kappa in the [Tabellion et al.] paper).
A value of 1 should be adequate in most cases. */
m_quality = props.getFloat("quality", 1.0f);
/* Multiplicative factor for the quality parameter following an
overture pass. This can be used to interpolate amongst more
samples, creating a visually smoother result. Must be
1 or less. */
m_qualityAdjustment = props.getFloat("qualityAdjustment", .5f);
/* If set to true, sample locations will be visually highlighted */
m_debug = props.getBoolean("debug", false);
/* Should irradiance gradients be used? Generally, this will
significantly improve the interpolation quality.*/
m_gradients = props.getBoolean("gradients", true);
/* Should neighbor clamping [Krivanek et al.] be used? This
propagates geometry information amongst close-by samples
and generally leads to better sample placement. */
m_clampNeighbor = props.getBoolean("clampNeighbor", true);
/* If set to true, the influence region of samples will be clamped
using the screen-space metric by [Tabellion et al.]?
Turning this off may lead to excessive sample placement. */
m_clampScreen = props.getBoolean("clampScreen", true);
/* Minimum influence region of an irradiance sample (relative to scene size, in [0,1]) */
m_influenceMin = props.getFloat("influenceMin", 0.005f);
/* Maximum influence region of an irradiance sample (default=64*min) */
m_influenceMax = props.getFloat("influenceMax", 64*m_influenceMin);
/* If set to false, direct illumination will be suppressed -
useful for checking the interpolation quality */
m_direct = props.getBoolean("direct", true);
Assert(m_influenceMax > m_influenceMin);
Assert(m_influenceMax > 0 && m_influenceMax < 1);
Assert(m_influenceMin > 0 && m_influenceMin < 1);
Assert(m_qualityAdjustment > 0 && m_qualityAdjustment <= 1);
}
IrradianceCacheIntegrator(Stream *stream, InstanceManager *manager)
: SampleIntegrator(stream, manager) {
m_irrCache = static_cast(manager->getInstance(stream));
m_subIntegrator = static_cast(manager->getInstance(stream));
m_resolution = stream->readInt();
m_influenceMin = stream->readFloat();
m_influenceMax = stream->readFloat();
m_quality = stream->readFloat();
m_qualityAdjustment = stream->readFloat();
m_clampScreen = stream->readBool();
m_clampNeighbor = stream->readBool();
m_overture = stream->readBool();
m_gradients = stream->readBool();
m_debug = stream->readBool();
m_direct = stream->readBool();
}
void serialize(Stream *stream, InstanceManager *manager) const {
SampleIntegrator::serialize(stream, manager);
manager->serialize(stream, m_irrCache.get());
manager->serialize(stream, m_subIntegrator.get());
stream->writeInt(m_resolution);
stream->writeFloat(m_influenceMin);
stream->writeFloat(m_influenceMax);
stream->writeFloat(m_quality);
stream->writeFloat(m_qualityAdjustment);
stream->writeBool(m_clampScreen);
stream->writeBool(m_clampNeighbor);
stream->writeBool(m_overture);
stream->writeBool(m_gradients);
stream->writeBool(m_debug);
stream->writeBool(m_direct);
}
void configureSampler(Sampler *sampler) {
m_subIntegrator->configureSampler(sampler);
}
void bindUsedResources(ParallelProcess *proc) const {
m_subIntegrator->bindUsedResources(proc);
}
void wakeup(std::map ¶ms) {
m_subIntegrator->wakeup(params);
}
void addChild(const std::string &name, ConfigurableObject *child) {
const Class *cClass = child->getClass();
if (cClass->derivesFrom(Integrator::m_theClass)) {
if (!cClass->derivesFrom(SampleIntegrator::m_theClass))
Log(EError, "The sub-integrator must be derived from the class SampleIntegrator");
m_subIntegrator = static_cast(child);
} else {
Integrator::addChild(name, child);
}
}
bool preprocess(const Scene *scene, RenderQueue *queue, const RenderJob *job,
int sceneResID, int cameraResID, int samplerResID) {
if (!SampleIntegrator::preprocess(scene, queue, job, sceneResID, cameraResID, samplerResID))
return false;
if (m_subIntegrator == NULL)
Log(EError, "No sub-integrator was specified!");
if (!m_subIntegrator->preprocess(scene, queue, job, sceneResID, cameraResID, samplerResID))
return false;
ref sched = Scheduler::getInstance();
m_irrCache = new IrradianceCache(scene->getAABB());
m_irrCache->clampNeighbor(m_clampNeighbor);
m_irrCache->clampScreen(m_clampScreen);
m_irrCache->clampInfluence(m_influenceMin, m_influenceMax);
m_irrCache->useGradients(m_gradients);
m_irrCache->setQuality(m_quality);
std::string irrCacheStatus;
if (m_overture)
irrCacheStatus += "overture, ";
if (m_debug)
irrCacheStatus += "debug, ";
if (m_gradients)
irrCacheStatus += "gradients, ";
if (m_clampNeighbor)
irrCacheStatus += "clampNeighbor, ";
if (m_clampScreen)
irrCacheStatus += "clampScreen, ";
irrCacheStatus += formatString("clampWorld(%.3f,%.3f)", m_influenceMin, m_influenceMax);
Log(EDebug, "Irradiance cache status : %s", irrCacheStatus.c_str());
Log(EDebug, " - Gather resolution : %ix%i = %i samples", m_resolution, 3*m_resolution, 3*m_resolution*m_resolution);
Log(EDebug, " - Quality setting : %.2f (adjustment: %.2f)", m_quality, m_qualityAdjustment);
if (m_overture) {
int subIntegratorResID = sched->registerResource(m_subIntegrator);
ref proc = new OvertureProcess(job, m_resolution, m_gradients,
m_clampNeighbor, m_clampScreen, m_influenceMin, m_influenceMax, m_quality);
m_proc = proc;
proc->bindResource("scene", sceneResID);
proc->bindResource("camera", cameraResID);
proc->bindResource("subIntegrator", subIntegratorResID);
bindUsedResources(proc);
sched->schedule(proc);
sched->unregisterResource(subIntegratorResID);
sched->wait(proc);
m_proc = NULL;
if (proc->getReturnStatus() != ParallelProcess::ESuccess) {
Log(EWarn, "The overture pass did not complete sucessfully!");
return false;
}
ref vec = proc->getSamples();
Log(EDebug, "Overture pass generated %i irradiance samples", vec->size());
for (size_t i=0; isize(); ++i)
m_irrCache->insert(new IrradianceCache::Record((*vec)[i]));
m_irrCache->setQuality(m_quality * m_qualityAdjustment);
}
return true;
}
void cancel() {
if (m_proc) {
Scheduler::getInstance()->cancel(m_proc);
} else {
SampleIntegrator::cancel();
m_subIntegrator->cancel();
}
}
Spectrum Li(const RayDifferential &ray, RadianceQueryRecord &rRec) const {
Intersection &its = rRec.its;
if (!m_direct && (rRec.type & RadianceQueryRecord::EDirectRadiance))
rRec.type ^= RadianceQueryRecord::EDirectRadiance;
if (rRec.rayIntersect(ray)) {
const BSDF *bsdf = its.getBSDF(ray);
if (bsdf->getType() == BSDF::EDiffuseReflection &&
(rRec.type & RadianceQueryRecord::EIndirectRadiance)) {
Spectrum E;
if (!m_irrCache->get(its, E)) {
handleMiss(ray, rRec, E);
if (m_debug)
E.fromLinearRGB(1e3, 0, 0);
}
rRec.type ^= RadianceQueryRecord::EIndirectRadiance;
return E * bsdf->getDiffuseReflectance(its) * INV_PI +
m_subIntegrator->Li(ray, rRec);
}
}
return m_subIntegrator->Li(ray, rRec);
}
void handleMiss(const RayDifferential &ray, const RadianceQueryRecord &rRec,
Spectrum &E) const {
/* Handle an irradiance cache miss */
HemisphereSampler *hs = m_hemisphereSampler.get();
Sampler *sampler = m_sampleGenerator.get();
RadianceQueryRecord rRec2;
if (hs == NULL) {
Properties props("independent");
props.setInteger("sampleCount", m_resolution * 3 * m_resolution);
sampler = static_cast (PluginManager::getInstance()->
createObject(Sampler::m_theClass, props));
hs = new HemisphereSampler(m_resolution, 3 * m_resolution);
m_hemisphereSampler.set(hs);
m_sampleGenerator.set(sampler);
}
/* Generate stratified cosine-weighted samples and compute
rotational + translational gradients */
hs->generateDirections(rRec.its, sampler);
sampler->generate();
for (unsigned int j=0; jgetM(); j++) {
for (unsigned int k=0; kgetN(); k++) {
HemisphereSampler::SampleEntry &entry = (*hs)(j, k);
entry.dist = std::numeric_limits::infinity();
rRec2.recursiveQuery(rRec,
RadianceQueryRecord::ERadianceNoEmission | RadianceQueryRecord::EDistance);
rRec2.extra = 1;
rRec2.sampler = sampler;
entry.L = m_subIntegrator->Li(RayDifferential(rRec.its.p, entry.d, ray.time), rRec2);
entry.dist = rRec2.dist;
sampler->advance();
}
}
hs->process(rRec.its);
m_irrCache->put(ray, rRec.its, *hs);
E = hs->getIrradiance();
}
Spectrum E(const Scene *scene, const Point &p, const Normal &n, Float time, Sampler *sampler) const {
Spectrum EDir(0.0f), EIndir(0.0f);
RadianceQueryRecord rRec(scene, sampler);
LuminaireSamplingRecord lRec;
/* Direct illumination */
for (unsigned int i=0; isampleLuminaireAttenuated(p, lRec, time, rRec.nextSample2D())) {
Float dp = dot(lRec.d, n);
if (dp < 0)
EDir -= lRec.Le * dp;
}
}
rRec.its.p = p;
rRec.its.geoFrame = rRec.its.shFrame = Frame(n);
if (!m_irrCache->get(rRec.its, EIndir))
handleMiss(RayDifferential(), rRec, EIndir);
return (EDir / (Float) m_irrSamples) + EIndir;
}
const Integrator *getSubIntegrator() const {
return m_subIntegrator.get();
}
std::string toString() const {
std::ostringstream oss;
oss << "IrradianceCacheIntegrator[" << std::endl
<< " subIntegrator = " << indent(m_subIntegrator->toString()) << "," << std::endl
<< " resolution = " << m_resolution << "," << std::endl
<< " irrCache = " << indent(m_irrCache->toString()) << std::endl
<< "]";
return oss.str();
}
MTS_DECLARE_CLASS()
private:
mutable ThreadLocal m_hemisphereSampler;
mutable ThreadLocal m_sampleGenerator;
mutable ref m_irrCache;
ref m_subIntegrator;
ref m_proc;
int m_resolution;
Float m_influenceMin, m_influenceMax;
Float m_quality, m_qualityAdjustment;
bool m_clampScreen, m_clampNeighbor;
bool m_overture, m_gradients, m_debug, m_direct;
};
MTS_IMPLEMENT_CLASS_S(IrradianceCacheIntegrator, false, SampleIntegrator)
MTS_EXPORT_PLUGIN(IrradianceCacheIntegrator, "Irradiance cache");
MTS_NAMESPACE_END