/* 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