volpath: reduced variance when dealing with index-matched medium transitions (perform luminaire and BSDF sampling connections through an arbitrary chain of ENull interactions)
parent
dd97df1368
commit
3a21619eec
|
@ -42,19 +42,29 @@ static StatsCounter avgPathLength("Volumetric path tracer", "Average path length
|
|||
* }
|
||||
*
|
||||
* This plugin provides a volumetric path tracer that can be used to
|
||||
* compute approximate solutions to the radiative transfer equation.
|
||||
* compute approximate solutions of the radiative transfer equation.
|
||||
* Its implementation makes use of multiple importance sampling to
|
||||
* combine BSDF and phase function sampling with direct illumination
|
||||
* sampling strategies. On surfaces, this integrator behaves exactly
|
||||
* sampling strategies. On surfaces, it behaves exactly
|
||||
* like the standard path tracer.
|
||||
*
|
||||
* This integrator has special support for \emph{index-matched} transmission
|
||||
* events (i.e. surface scattering events that do not change the direction
|
||||
* of light). As a consequence, particating media enclosed by a stencil shape (see
|
||||
* \secref{shapes} for details) are rendered considerably more efficiently when this
|
||||
* shape has \emph{no}\footnote{this is what signals to Mitsuba that the boundary is
|
||||
* index-matched and does not interact with light in any way. Alternatively,
|
||||
* the \pluginref{mask} and \pluginref{thindielectric} BSDF can be used to specify
|
||||
* index-matched boundaries that involve some amount of interaction.} BSDF assigned
|
||||
* to it (as compared to, say, a \pluginref{dielectric} or \pluginref{roughdielectric} BSDF).
|
||||
*
|
||||
* \remarks{
|
||||
* \item This integrator will generally perform poorly when rendering
|
||||
* participating media that have a different index of refraction compared
|
||||
* to the surrounding medium.
|
||||
* \item This integrator has poor convergence properties when rendering
|
||||
* caustics and similar effects. In this case, \pluginref{bdpt} or
|
||||
* one of the photon mappers may be preferable.
|
||||
* caustics and similar effects. In this case, \pluginref{bdpt} or
|
||||
* one of the photon mappers may be preferable.
|
||||
* }
|
||||
*/
|
||||
class VolumetricPathTracer : public MonteCarloIntegrator {
|
||||
|
@ -72,7 +82,6 @@ public:
|
|||
MediumSamplingRecord mRec;
|
||||
RayDifferential ray(r);
|
||||
Spectrum Li(0.0f);
|
||||
bool scattered = false;
|
||||
Float eta = 1.0f;
|
||||
|
||||
/* Perform the first ray intersection (or ignore if the
|
||||
|
@ -80,6 +89,7 @@ public:
|
|||
rRec.rayIntersect(ray);
|
||||
|
||||
Spectrum throughput(1.0f);
|
||||
bool scattered = false;
|
||||
|
||||
while (rRec.depth <= m_maxDepth || m_maxDepth < 0) {
|
||||
/* ==================================================================== */
|
||||
|
@ -140,43 +150,22 @@ public:
|
|||
Float phaseVal = phase->sample(pRec, phasePdf, rRec.sampler);
|
||||
if (phaseVal == 0)
|
||||
break;
|
||||
throughput *= phaseVal;
|
||||
|
||||
bool hitEmitter = false;
|
||||
Spectrum value;
|
||||
|
||||
/* Trace a ray in this direction */
|
||||
ray = Ray(mRec.p, pRec.wo, ray.time);
|
||||
ray.mint = 0;
|
||||
|
||||
if (scene->rayIntersect(ray, its)) {
|
||||
/* Intersected something - check if it was a luminaire */
|
||||
if (its.isEmitter()) {
|
||||
value = its.Le(-ray.d);
|
||||
dRec.setQuery(ray, its);
|
||||
hitEmitter = true;
|
||||
}
|
||||
} else {
|
||||
/* Intersected nothing -- perhaps there is an environment map? */
|
||||
const Emitter *env = scene->getEnvironmentEmitter();
|
||||
|
||||
if (env) {
|
||||
value = env->evalEnvironment(ray);
|
||||
if (!env->fillDirectSamplingRecord(dRec, ray))
|
||||
break;
|
||||
hitEmitter = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throughput *= phaseVal;
|
||||
Spectrum value(0.0f);
|
||||
rayIntersectAndLookForEmitter(scene, rRec.sampler, rRec.medium,
|
||||
m_maxDepth - rRec.depth - 1, ray, its, dRec, value);
|
||||
|
||||
/* If a luminaire was hit, estimate the local illumination and
|
||||
weight using the power heuristic */
|
||||
if (hitEmitter && (rRec.type & RadianceQueryRecord::EDirectMediumRadiance)) {
|
||||
Spectrum transmittance = rRec.medium->evalTransmittance(Ray(ray, 0, its.t));
|
||||
if (!value.isZero() && (rRec.type & RadianceQueryRecord::EDirectMediumRadiance)) {
|
||||
const Float emitterPdf = scene->pdfEmitterDirect(dRec);
|
||||
Li += throughput * value * transmittance * miWeight(phasePdf, emitterPdf);
|
||||
Li += throughput * value * miWeight(phasePdf, emitterPdf);
|
||||
}
|
||||
|
||||
/* ==================================================================== */
|
||||
|
@ -187,8 +176,6 @@ public:
|
|||
if (!(rRec.type & RadianceQueryRecord::EIndirectMediumRadiance))
|
||||
break;
|
||||
rRec.type = RadianceQueryRecord::ERadianceNoEmission;
|
||||
|
||||
scattered = true;
|
||||
} else {
|
||||
/* Sample
|
||||
tau(x, y) (Surface integral). This happens with probability mRec.pdfFailure
|
||||
|
@ -230,8 +217,8 @@ public:
|
|||
DirectSamplingRecord dRec(its);
|
||||
|
||||
/* Estimate the direct illumination if this is requested */
|
||||
if (rRec.type & RadianceQueryRecord::EDirectSurfaceRadiance &&
|
||||
(bsdf->getType() & BSDF::ESmooth)) {
|
||||
if ((rRec.type & RadianceQueryRecord::EDirectSurfaceRadiance) &&
|
||||
(bsdf->getType() & BSDF::ESmooth)) {
|
||||
int interactions = m_maxDepth - rRec.depth - 1;
|
||||
|
||||
Spectrum value = scene->sampleAttenuatedEmitterDirect(
|
||||
|
@ -253,8 +240,7 @@ public:
|
|||
/* Calculate prob. of having generated that direction
|
||||
using BSDF sampling */
|
||||
Float bsdfPdf = (emitter->isOnSurface()
|
||||
&& dRec.measure == ESolidAngle
|
||||
&& interactions == 0)
|
||||
&& dRec.measure == ESolidAngle)
|
||||
? bsdf->pdf(bRec) : (Float) 0.0f;
|
||||
|
||||
/* Weight using the power heuristic */
|
||||
|
@ -292,39 +278,27 @@ public:
|
|||
if (its.isMediumTransition())
|
||||
rRec.medium = its.getTargetMedium(ray.d);
|
||||
|
||||
bool hitEmitter = false;
|
||||
Spectrum value;
|
||||
|
||||
if (scene->rayIntersect(ray, its)) {
|
||||
/* Intersected something - check if it was a luminaire */
|
||||
if (its.isEmitter()) {
|
||||
value = its.Le(-ray.d);
|
||||
dRec.setQuery(ray, its);
|
||||
hitEmitter = true;
|
||||
}
|
||||
} else {
|
||||
/* Intersected nothing -- perhaps there is an environment map? */
|
||||
const Emitter *env = scene->getEnvironmentEmitter();
|
||||
|
||||
if (env) {
|
||||
value = env->evalEnvironment(ray);
|
||||
if (!env->fillDirectSamplingRecord(dRec, ray))
|
||||
break;
|
||||
hitEmitter = true;
|
||||
} else {
|
||||
/* Handle index-matched medium transitions specially */
|
||||
if (bRec.sampledType == BSDF::ENull) {
|
||||
if (!(rRec.type & RadianceQueryRecord::EIndirectSurfaceRadiance))
|
||||
break;
|
||||
}
|
||||
rRec.type = scattered ? RadianceQueryRecord::ERadianceNoEmission
|
||||
: RadianceQueryRecord::ERadiance;
|
||||
scene->rayIntersect(ray, its);
|
||||
rRec.depth++;
|
||||
continue;
|
||||
}
|
||||
|
||||
Spectrum value(0.0f);
|
||||
rayIntersectAndLookForEmitter(scene, rRec.sampler, rRec.medium,
|
||||
m_maxDepth - rRec.depth - 1, ray, its, dRec, value);
|
||||
|
||||
/* If a luminaire was hit, estimate the local illumination and
|
||||
weight using the power heuristic */
|
||||
if (hitEmitter && (rRec.type & RadianceQueryRecord::EDirectSurfaceRadiance)
|
||||
&& !((bRec.sampledType & BSDF::ENull) && scattered)) {
|
||||
Spectrum transmittance = rRec.medium ?
|
||||
rRec.medium->evalTransmittance(Ray(ray, 0, its.t)) : Spectrum(1.0f);
|
||||
if (!value.isZero() && (rRec.type & RadianceQueryRecord::EDirectSurfaceRadiance)) {
|
||||
const Float emitterPdf = (!(bRec.sampledType & BSDF::EDelta)) ?
|
||||
scene->pdfEmitterDirect(dRec) : 0;
|
||||
Li += throughput * value * transmittance * miWeight(bsdfPdf, emitterPdf);
|
||||
Li += throughput * value * miWeight(bsdfPdf, emitterPdf);
|
||||
}
|
||||
|
||||
/* ==================================================================== */
|
||||
|
@ -334,9 +308,8 @@ public:
|
|||
/* Stop if indirect illumination was not requested */
|
||||
if (!(rRec.type & RadianceQueryRecord::EIndirectSurfaceRadiance))
|
||||
break;
|
||||
rRec.type = RadianceQueryRecord::ERadianceNoEmission;
|
||||
|
||||
scattered |= bRec.sampledType != BSDF::ENull;
|
||||
rRec.type = RadianceQueryRecord::ERadianceNoEmission;
|
||||
}
|
||||
|
||||
if (rRec.depth++ >= m_rrDepth) {
|
||||
|
@ -350,12 +323,97 @@ public:
|
|||
break;
|
||||
throughput /= q;
|
||||
}
|
||||
|
||||
scattered = true;
|
||||
}
|
||||
avgPathLength.incrementBase();
|
||||
avgPathLength += rRec.depth;
|
||||
return Li;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called by the recursive ray tracing above after
|
||||
* having sampled a direction from a BSDF/phase function. Due to the
|
||||
* way in which this integrator deals with index-matched boundaries,
|
||||
* it is necessarily a bit complicated (though the improved performance
|
||||
* easily pays for the extra effort).
|
||||
*
|
||||
* This function
|
||||
*
|
||||
* 1. Intersects 'ray' against the scene geometry and returns the
|
||||
* *first* intersection via the '_its' argument.
|
||||
*
|
||||
* 2. It checks whether the intersected shape was an emitter, or if
|
||||
* the ray intersects nothing and there is an environment emitter.
|
||||
* In this case, it returns the attenuated emittance, as well as
|
||||
* a DirectSamplingRecord that can be used to query the hypothetical
|
||||
* sampling density at the emitter.
|
||||
*
|
||||
* 3. If current shape is an index-matched medium transition, the
|
||||
* integrator keeps on looking on whether a light source eventually
|
||||
* follows after a potential chain of index-matched medium transitions,
|
||||
* while respecting the specified 'maxDepth' limits. It then returns
|
||||
* the attenuated emittance of this light source, while accounting for
|
||||
* all attenuation that occurs on the wya.
|
||||
*/
|
||||
void rayIntersectAndLookForEmitter(const Scene *scene, Sampler *sampler,
|
||||
const Medium *medium, int maxInteractions, Ray ray, Intersection &_its,
|
||||
DirectSamplingRecord &dRec, Spectrum &value) const {
|
||||
Intersection its2, *its = &_its;
|
||||
Spectrum transmittance(1.0f);
|
||||
bool surface = false;
|
||||
int interactions = 0;
|
||||
|
||||
while (true) {
|
||||
surface = scene->rayIntersect(ray, *its);
|
||||
|
||||
if (surface && (interactions == maxInteractions ||
|
||||
!(its->getBSDF()->getType() & BSDF::ENull)))
|
||||
/* Encountered an occluder -- zero transmittance. */
|
||||
break;
|
||||
|
||||
if (medium)
|
||||
transmittance *= medium->evalTransmittance(Ray(ray, 0, its->t), sampler);
|
||||
|
||||
if (!surface)
|
||||
break;
|
||||
|
||||
if (transmittance.isZero())
|
||||
return;
|
||||
|
||||
if (its->isMediumTransition())
|
||||
medium = its->getTargetMedium(ray.d);
|
||||
|
||||
Vector wo = its->shFrame.toLocal(ray.d);
|
||||
BSDFSamplingRecord bRec(*its, -wo, wo, ERadiance);
|
||||
bRec.typeMask = BSDF::ENull;
|
||||
transmittance *= its->getBSDF()->eval(bRec, EDiscrete);
|
||||
|
||||
ray.o = ray(its->t);
|
||||
ray.mint = Epsilon;
|
||||
its = &its2;
|
||||
|
||||
if (++interactions > 100) { /// Just a precaution..
|
||||
Log(EWarn, "rayIntersectAndLookForEmitter(): round-off error issues?");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (surface) {
|
||||
/* Intersected something - check if it was a luminaire */
|
||||
if (its->isEmitter()) {
|
||||
dRec.setQuery(ray, *its);
|
||||
value = transmittance * its->Le(-ray.d);
|
||||
}
|
||||
} else {
|
||||
/* Intersected nothing -- perhaps there is an environment map? */
|
||||
const Emitter *env = scene->getEnvironmentEmitter();
|
||||
|
||||
if (env && env->fillDirectSamplingRecord(dRec, ray))
|
||||
value = transmittance * env->evalEnvironment(RayDifferential(ray));
|
||||
}
|
||||
}
|
||||
|
||||
inline Float miWeight(Float pdfA, Float pdfB) const {
|
||||
pdfA *= pdfA; pdfB *= pdfB;
|
||||
return pdfA / (pdfA + pdfB);
|
||||
|
|
Loading…
Reference in New Issue