378 lines
15 KiB
C++
378 lines
15 KiB
C++
#include <mitsuba/core/plugin.h>
|
|
#include <mitsuba/render/gatherproc.h>
|
|
|
|
MTS_NAMESPACE_BEGIN
|
|
|
|
/**
|
|
* Parallel photon gathering, SSE-accelerated lookups, RGBE encoding
|
|
* Numbers for caustic/volume are ignored when no specular objects or participating
|
|
* media are present.
|
|
*/
|
|
class PhotonMapIntegrator : public SampleIntegrator {
|
|
public:
|
|
PhotonMapIntegrator(const Properties &props) : SampleIntegrator(props) {
|
|
/* Number of luminaire samples for direct illumination */
|
|
m_directSamples = props.getInteger("directSamples", 1);
|
|
/* 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", 10);
|
|
/* Depth cutoff when tracing photons */
|
|
m_maxDepth = props.getInteger("maxDepth", 40);
|
|
/* Depth cutoff when recursively tracing specular materials */
|
|
m_maxSpecularDepth = props.getInteger("maxSpecularDepth", 6);
|
|
/* Granularity of photon tracing work units (in shot particles) */
|
|
m_granularity = props.getInteger("granularity", 1000);
|
|
|
|
/* Number of photons to collect for the global photon map */
|
|
m_globalPhotons = (size_t) props.getLong("globalPhotons", 200000);
|
|
/* Number of photons to collect for the caustic photon map */
|
|
m_causticPhotons = (size_t) props.getLong("causticPhotons", 200000);
|
|
/* Number of photons to collect for the volumetric photon map */
|
|
m_volumePhotons = (size_t) props.getLong("volumePhotons", 200000);
|
|
/* Radius of lookups in the global photon map (relative to the scene size) */
|
|
m_globalLookupRadiusRel = props.getFloat("globalLookupRadius", 0.05f);
|
|
/* Radius of lookups in the caustic photon map (relative to the scene size) */
|
|
m_causticLookupRadiusRel = props.getFloat("causticLookupRadius", 0.0125f);
|
|
/* Radius of lookups in the volumetric photon map (relative to the scene size) */
|
|
m_volumeLookupRadiusRel = props.getFloat("volumeLookupRadius", 0.05f);
|
|
/* Minimum amount of photons to consider a global photon map lookup valid */
|
|
m_globalMinPhotons = props.getInteger("globalMinPhotons", 8);
|
|
/* Minimum amount of photons to consider a caustic photon map lookup valid */
|
|
m_causticMinPhotons = props.getInteger("causticMinPhotons", 100);
|
|
/* Minimum amount of photons to consider a volumetric photon map lookup valid */
|
|
m_volumeMinPhotons = props.getInteger("volumeMinPhotons", 8);
|
|
/* Maximum number of results for global photon map lookups */
|
|
m_globalLookupSize = props.getInteger("globalLookupSize", 200);
|
|
/* Maximum number of results for caustic photon map lookups */
|
|
m_causticLookupSize = props.getInteger("causticLookupSize", 200);
|
|
/* Maximum number of results for volumetric photon map lookups */
|
|
m_volumeLookupSize = props.getInteger("volumeLookupSize", 200);
|
|
}
|
|
|
|
/// Unserialize from a binary data stream
|
|
PhotonMapIntegrator(Stream *stream, InstanceManager *manager)
|
|
: SampleIntegrator(stream, manager) {
|
|
m_directSamples = stream->readInt();
|
|
m_glossySamples = stream->readInt();
|
|
m_maxSpecularDepth = stream->readInt();
|
|
m_globalPhotons = (size_t) stream->readULong();
|
|
m_causticPhotons = (size_t) stream->readULong();
|
|
m_volumePhotons = (size_t) stream->readULong();
|
|
m_globalLookupRadius = stream->readFloat();
|
|
m_causticLookupRadius = stream->readFloat();
|
|
m_volumeLookupRadius = stream->readFloat();
|
|
m_globalMinPhotons = stream->readInt();
|
|
m_causticMinPhotons = stream->readInt();
|
|
m_volumeMinPhotons = stream->readInt();
|
|
m_globalLookupSize = stream->readInt();
|
|
m_causticLookupSize = stream->readInt();
|
|
m_volumeLookupSize = stream->readInt();
|
|
}
|
|
|
|
void serialize(Stream *stream, InstanceManager *manager) const {
|
|
SampleIntegrator::serialize(stream, manager);
|
|
stream->writeInt(m_directSamples);
|
|
stream->writeInt(m_glossySamples);
|
|
stream->writeInt(m_maxSpecularDepth);
|
|
stream->writeULong(m_globalPhotons);
|
|
stream->writeULong(m_causticPhotons);
|
|
stream->writeULong(m_volumePhotons);
|
|
stream->writeFloat(m_globalLookupRadius);
|
|
stream->writeFloat(m_causticLookupRadius);
|
|
stream->writeFloat(m_volumeLookupRadius);
|
|
stream->writeInt(m_globalMinPhotons);
|
|
stream->writeInt(m_causticMinPhotons);
|
|
stream->writeInt(m_volumeMinPhotons);
|
|
stream->writeInt(m_globalLookupSize);
|
|
stream->writeInt(m_causticLookupSize);
|
|
stream->writeInt(m_volumeLookupSize);
|
|
}
|
|
|
|
/// Configure the sampler for a specified amount of direct illumination samples
|
|
void configureSampler(Sampler *sampler) {
|
|
if (m_directSamples > 1)
|
|
sampler->request2DArray(m_directSamples);
|
|
sampler->request2DArray(m_glossySamples);
|
|
}
|
|
|
|
void preprocess(const Scene *scene, RenderQueue *queue, const RenderJob *job,
|
|
int sceneResID, int cameraResID, int samplerResID) {
|
|
SampleIntegrator::preprocess(scene, queue, job, sceneResID, cameraResID, samplerResID);
|
|
/* Create a deterministic sampler for the photon gathering step */
|
|
ref<Scheduler> sched = Scheduler::getInstance();
|
|
ref<Sampler> sampler = static_cast<Sampler *> (PluginManager::getInstance()->
|
|
createObject(Sampler::m_theClass, Properties("halton")));
|
|
int qmcSamplerID = sched->registerResource(sampler);
|
|
|
|
/* Don't create a caustic photon map if the scene does not contain specular materials */
|
|
const std::vector<Shape *> &shapes = scene->getShapes();
|
|
bool foundSpecular = false;
|
|
for (size_t i=0; i<shapes.size(); ++i) {
|
|
if (shapes[i]->getBSDF()->getType() & BSDF::EDelta) {
|
|
foundSpecular = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundSpecular)
|
|
m_causticPhotons = 0;
|
|
|
|
/* Don't create a volumetric photon map if there are no participating media */
|
|
if (!scene->hasMedia())
|
|
m_volumePhotons = 0;
|
|
|
|
if (m_globalPhotonMap.get() == NULL && m_globalPhotons > 0) {
|
|
/* Adapt to scene extents */
|
|
m_globalLookupRadius = m_globalLookupRadiusRel * scene->getBSphere().radius;
|
|
|
|
/* Generate the global photon map */
|
|
ref<GatherPhotonProcess> proc = new GatherPhotonProcess(
|
|
GatherPhotonProcess::ESurfacePhotons, m_globalPhotons,
|
|
m_granularity, m_maxDepth, m_rrDepth, job);
|
|
|
|
proc->bindResource("scene", sceneResID);
|
|
proc->bindResource("sampler", qmcSamplerID);
|
|
|
|
sched->schedule(proc);
|
|
sched->wait(proc);
|
|
|
|
m_globalPhotonMap = proc->getPhotonMap();
|
|
m_globalPhotonMap->setScale(1 / (Float) proc->getShotPhotons());
|
|
m_globalPhotonMap->setMinPhotons(m_globalMinPhotons);
|
|
m_globalPhotonMap->balance();
|
|
Log(EDebug, "Global photon map full. Shot " SIZE_T_FMT " photons, excess due to parallelism: "
|
|
SIZE_T_FMT, proc->getShotPhotons(), proc->getExcess());
|
|
m_globalPhotonMapID = sched->registerResource(m_globalPhotonMap);
|
|
}
|
|
|
|
if (m_causticPhotonMap.get() == NULL && m_causticPhotons > 0) {
|
|
/* Adapt to scene extents */
|
|
m_causticLookupRadius = m_causticLookupRadiusRel * scene->getBSphere().radius;
|
|
|
|
/* Generate the caustic photon map */
|
|
ref<GatherPhotonProcess> proc = new GatherPhotonProcess(
|
|
GatherPhotonProcess::ECausticPhotons, m_causticPhotons,
|
|
m_granularity, 2, m_rrDepth, job);
|
|
|
|
proc->bindResource("scene", sceneResID);
|
|
proc->bindResource("sampler", qmcSamplerID);
|
|
|
|
sched->schedule(proc);
|
|
sched->wait(proc);
|
|
|
|
m_causticPhotonMap = proc->getPhotonMap();
|
|
m_causticPhotonMap->setScale(1 / (Float) proc->getShotPhotons());
|
|
m_causticPhotonMap->setMinPhotons(m_causticMinPhotons);
|
|
m_causticPhotonMap->balance();
|
|
Log(EDebug, "Caustic photon map - excess photons due to parallelism: "
|
|
SIZE_T_FMT, proc->getExcess());
|
|
m_causticPhotonMapID = sched->registerResource(m_causticPhotonMap);
|
|
}
|
|
|
|
if (m_volumePhotonMap.get() == NULL && m_volumePhotons > 0) {
|
|
/* Adapt to scene extents */
|
|
m_volumeLookupRadius = m_volumeLookupRadiusRel * scene->getBSphere().radius;
|
|
|
|
/* Generate the volume photon map */
|
|
ref<GatherPhotonProcess> proc = new GatherPhotonProcess(
|
|
GatherPhotonProcess::EVolumePhotons, m_volumePhotons,
|
|
m_granularity, m_maxDepth, m_rrDepth, job);
|
|
|
|
proc->bindResource("scene", sceneResID);
|
|
proc->bindResource("sampler", qmcSamplerID);
|
|
|
|
sched->schedule(proc);
|
|
sched->wait(proc);
|
|
|
|
m_volumePhotonMap = proc->getPhotonMap();
|
|
m_volumePhotonMap->setScale(1 / (Float) proc->getShotPhotons());
|
|
m_volumePhotonMap->setMinPhotons(m_volumeMinPhotons);
|
|
m_volumePhotonMap->balance();
|
|
Log(EDebug, "Volumetric photon map - excess photons due to parallelism: "
|
|
SIZE_T_FMT, proc->getExcess());
|
|
m_volumePhotonMapID = sched->registerResource(m_volumePhotonMap);
|
|
}
|
|
|
|
sched->unregisterResource(qmcSamplerID);
|
|
}
|
|
|
|
/// 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_volumePhotonMap.get())
|
|
proc->bindResource("volumePhotonMap", m_volumePhotonMapID);
|
|
}
|
|
|
|
/// Connect to globally shared resources
|
|
void wakeup(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_volumePhotonMap.get() && params.find("volumetricPhotonMap") != params.end())
|
|
m_volumePhotonMap = static_cast<PhotonMap *>(params["volumetricPhotonMap"]);
|
|
|
|
if (getParent() != NULL && getParent()->getClass()->derivesFrom(SampleIntegrator::m_theClass))
|
|
m_parentIntegrator = static_cast<SampleIntegrator *>(getParent());
|
|
else
|
|
m_parentIntegrator = this;
|
|
|
|
}
|
|
|
|
Spectrum Li(const RayDifferential &ray, RadianceQueryRecord &rRec) const {
|
|
Spectrum Li(0.0f), LiVol(0.0f);
|
|
Intersection &its = rRec.its;
|
|
LuminaireSamplingRecord lRec;
|
|
|
|
/* Perform the first ray intersection (or ignore if the
|
|
intersection has already been provided). */
|
|
rRec.rayIntersect(ray);
|
|
|
|
if (!its.isValid()) {
|
|
/* If no intersection could be found, possibly return
|
|
attenuated radiance from a background luminaire */
|
|
if (rRec.type & RadianceQueryRecord::EEmittedRadiance)
|
|
Li += rRec.scene->LeBackgroundAttenuated(ray);
|
|
return Li;
|
|
}
|
|
|
|
/* Possibly include emitted radiance if requested */
|
|
if (its.isLuminaire() && (rRec.type & RadianceQueryRecord::EEmittedRadiance))
|
|
Li += its.Le(-ray.d);
|
|
|
|
/* Include radiance from a subsurface integrator if requested */
|
|
if (its.hasSubsurface() && (rRec.type & RadianceQueryRecord::ESubsurfaceRadiance))
|
|
Li += its.LoSub(-ray.d);
|
|
|
|
const BSDF *bsdf = its.getBSDF(ray);
|
|
int bsdfType = bsdf->getType();
|
|
|
|
Point2 *sampleArray, sample;
|
|
int numDirectSamples = (rRec.depth == 1) ? m_directSamples : 1;
|
|
/* When this integrator is used recursively by another integrator,
|
|
Be less accurate as this sample will not be directly observed. */
|
|
if (numDirectSamples > 1) {
|
|
sampleArray = rRec.sampler->next2DArray(numDirectSamples);
|
|
} else {
|
|
sample = rRec.nextSample2D();
|
|
sampleArray = &sample;
|
|
}
|
|
|
|
/* Estimate the direct illumination if this is requested */
|
|
if (rRec.type & RadianceQueryRecord::EDirectRadiance) {
|
|
Float weight = 1 / (Float) numDirectSamples;
|
|
|
|
for (int i=0; i<numDirectSamples; ++i) {
|
|
if (rRec.scene->sampleLuminaireAttenuated(its, lRec, sampleArray[i])) {
|
|
/* Allocate a record for querying the BSDF */
|
|
const BSDFQueryRecord bRec(rRec, its, its.toLocal(-lRec.d));
|
|
|
|
/* Evaluate BSDF * cos(theta) */
|
|
const Spectrum bsdfVal = bsdf->fCos(bRec);
|
|
|
|
Li += lRec.Le * bsdfVal * weight;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if (rRec.type & RadianceQueryRecord:EVolumeRadiance) {
|
|
/* Ray marching */
|
|
// }
|
|
|
|
|
|
if (bsdfType == BSDF::EDiffuseReflection) {
|
|
/* Hit a diffuse material - do a direct photon map visualization. */
|
|
if (rRec.type & RadianceQueryRecord::EIndirectRadiance)
|
|
Li += m_globalPhotonMap->estimateIrradianceFiltered(its.p,
|
|
its.shFrame.n, m_globalLookupRadius, m_globalLookupSize)
|
|
* bsdf->getDiffuseReflectance(its) * INV_PI;
|
|
if (rRec.type & RadianceQueryRecord::ECausticRadiance && m_causticPhotonMap.get())
|
|
Li += m_causticPhotonMap->estimateIrradianceFiltered(its.p,
|
|
its.shFrame.n, m_causticLookupRadius, m_causticLookupSize)
|
|
* bsdf->getDiffuseReflectance(its) * INV_PI;
|
|
} else if ((bsdfType & BSDF::EDelta) != 0
|
|
&& (bsdfType & ~BSDF::EDelta) == 0 && !rRec.extra) {
|
|
RadianceQueryRecord rRec2;
|
|
RayDifferential recursiveRay;
|
|
/* Ideal specular material -> recursive ray tracing.
|
|
Deliberately risk exponential ray growth by spawning
|
|
several child rays. The impact on the final image is huge
|
|
and well worth the extra computation. */
|
|
if (rRec.depth+1 < m_maxSpecularDepth) {
|
|
int compCount = bsdf->getComponentCount();
|
|
for (int i=0; i<compCount; i++) {
|
|
/* Sample the BSDF and recurse */
|
|
BSDFQueryRecord bRec(its, Point2(0,0));
|
|
bRec.component = i;
|
|
Spectrum bsdfVal = bsdf->sampleCos(bRec);
|
|
if (bsdfVal.isBlack())
|
|
continue;
|
|
|
|
rRec2.recursiveQuery(rRec, RadianceQueryRecord::ERadiance);
|
|
recursiveRay = Ray(its.p, its.toWorld(bRec.wo));
|
|
Li += m_parentIntegrator->Li(recursiveRay, rRec2) * bsdfVal;
|
|
}
|
|
}
|
|
} else if (rRec.depth == 1 && (bsdf->getType() & BSDF::EGlossy)) {
|
|
/* Hit a glossy material - MC integration over the hemisphere
|
|
using BSDF importance sampling */
|
|
sampleArray = rRec.sampler->next2DArray(m_glossySamples);
|
|
RadianceQueryRecord rRec2;
|
|
RayDifferential recursiveRay;
|
|
Float weight = 1 / (Float) m_glossySamples;
|
|
|
|
for (int i=0; i<m_glossySamples; ++i) {
|
|
BSDFQueryRecord bRec(rRec, its, sampleArray[i]);
|
|
Spectrum bsdfVal = bsdf->sampleCos(bRec);
|
|
|
|
rRec2.recursiveQuery(rRec, RadianceQueryRecord::ERadianceNoEmission);
|
|
recursiveRay = Ray(its.p, its.toWorld(bRec.wo));
|
|
Li += m_parentIntegrator->Li(recursiveRay, rRec2) * bsdfVal * weight;
|
|
}
|
|
} else {
|
|
Li += m_globalPhotonMap->estimateRadianceFiltered(its,
|
|
m_globalLookupRadius, m_globalLookupSize);
|
|
}
|
|
|
|
return Li * rRec.attenuation + LiVol;
|
|
}
|
|
|
|
std::string toString() const {
|
|
std::ostringstream oss;
|
|
oss << "PhotonMapIntegrator[" << std::endl
|
|
<< " directSamples = " << m_directSamples << "," << std::endl
|
|
<< " glossySamples = " << m_glossySamples << "," << std::endl
|
|
<< " globalPhotons = " << m_globalPhotons << "," << std::endl
|
|
<< " causticPhotons = " << m_causticPhotons << "," << std::endl
|
|
<< " volumePhotons = " << m_volumePhotons << std::endl
|
|
<< "]";
|
|
return oss.str();
|
|
}
|
|
|
|
MTS_DECLARE_CLASS()
|
|
private:
|
|
ref<PhotonMap> m_globalPhotonMap;
|
|
ref<PhotonMap> m_causticPhotonMap;
|
|
ref<PhotonMap> m_volumePhotonMap;
|
|
SampleIntegrator *m_parentIntegrator;
|
|
int m_globalPhotonMapID, m_causticPhotonMapID, m_volumePhotonMapID;
|
|
size_t m_globalPhotons, m_causticPhotons, m_volumePhotons;
|
|
int m_globalMinPhotons, m_globalLookupSize;
|
|
int m_causticMinPhotons, m_causticLookupSize;
|
|
int m_volumeMinPhotons, m_volumeLookupSize;
|
|
Float m_globalLookupRadiusRel, m_globalLookupRadius;
|
|
Float m_causticLookupRadiusRel, m_causticLookupRadius;
|
|
Float m_volumeLookupRadiusRel, m_volumeLookupRadius;
|
|
int m_granularity;
|
|
int m_directSamples, m_glossySamples;
|
|
int m_rrDepth;
|
|
int m_maxDepth, m_maxSpecularDepth;
|
|
};
|
|
|
|
MTS_IMPLEMENT_CLASS_S(PhotonMapIntegrator, false, SampleIntegrator)
|
|
MTS_EXPORT_PLUGIN(PhotonMapIntegrator, "Photon map integrator");
|
|
MTS_NAMESPACE_END
|