492 lines
16 KiB
C++
492 lines
16 KiB
C++
/*
|
|
This file is part of Mitsuba, a physically based rendering system.
|
|
|
|
Copyright (c) 2007-2011 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/render/scene.h>
|
|
#include <mitsuba/render/volume.h>
|
|
#include <mitsuba/core/statistics.h>
|
|
|
|
MTS_NAMESPACE_BEGIN
|
|
|
|
/**
|
|
* \brief When the following line is uncommented, the medium implementation
|
|
* stops integrating density when it is determined that the segment has a
|
|
* throughput of less than 'Epsilon' (see \c mitsuba/core/constants.h)
|
|
*/
|
|
#define HETVOL_EARLY_EXIT 1
|
|
|
|
/// Generate a few statistics related to the implementation?
|
|
// #define HETVOL_STATISTICS 1
|
|
|
|
#if defined(HETVOL_STATISTICS)
|
|
static StatsCounter avgNewtonIterations("Heterogeneous volume",
|
|
"Avg. # of Newton-Bisection iterations", EAverage);
|
|
static StatsCounter avgRayMarchingStepsTransmittance("Heterogeneous volume",
|
|
"Avg. # of ray marching steps (transmittance)", EAverage);
|
|
static StatsCounter avgRayMarchingStepsSampling("Heterogeneous volume",
|
|
"Avg. # of ray marching steps (sampling)", EAverage);
|
|
static StatsCounter earlyExits("Heterogeneous volume",
|
|
"Number of early exits", EPercentage);
|
|
#endif
|
|
|
|
/**
|
|
* Flexible heterogeneous medium implementation, which acquires its data from
|
|
* nested \ref Volume instances. These can be constant, use a procedural
|
|
* function, or fetch data from disk, e.g. using a memory-mapped density grid.
|
|
*
|
|
* Instead of allowing separate volumes to be provided for the scattering
|
|
* parameters sigma_s and sigma_t, this class instead takes the approach of
|
|
* enforcing a spectrally uniform sigma_t, which must be provided using a
|
|
* nested scalar-valued volume named 'density'.
|
|
*
|
|
* Another nested spectrum-valued 'albedo' volume must also be provided, which is
|
|
* used to compute the parameter sigma_s using the expression
|
|
* "sigma_s = density * albedo" (i.e. 'albedo' contains the single-scattering
|
|
* albedo of the medium).
|
|
*
|
|
* Optionally, one can also provide an vector-valued 'orientation' volume,
|
|
* which contains local particle orientation that will be passed to
|
|
* scattering models such as a the Micro-flake or Kajiya-Kay phase functions.
|
|
*
|
|
* \author Wenzel Jakob
|
|
*/
|
|
class HeterogeneousMedium : public Medium {
|
|
public:
|
|
HeterogeneousMedium(const Properties &props)
|
|
: Medium(props) {
|
|
m_stepSize = props.getFloat("stepSize", 0);
|
|
if (props.hasProperty("sigmaS") || props.hasProperty("sigmaA"))
|
|
Log(EError, "The 'sigmaS' and 'sigmaA' properties are only supported by "
|
|
"homogeneous media. Please use nested volume instances to supply "
|
|
"these parameters");
|
|
}
|
|
|
|
/* Unserialize from a binary data stream */
|
|
HeterogeneousMedium(Stream *stream, InstanceManager *manager)
|
|
: Medium(stream, manager) {
|
|
m_density = static_cast<VolumeDataSource *>(manager->getInstance(stream));
|
|
m_albedo = static_cast<VolumeDataSource *>(manager->getInstance(stream));
|
|
m_orientation = static_cast<VolumeDataSource *>(manager->getInstance(stream));
|
|
m_stepSize = stream->readFloat();
|
|
configure();
|
|
}
|
|
|
|
/* Serialize the volume to a binary data stream */
|
|
void serialize(Stream *stream, InstanceManager *manager) const {
|
|
Medium::serialize(stream, manager);
|
|
manager->serialize(stream, m_density.get());
|
|
manager->serialize(stream, m_albedo.get());
|
|
manager->serialize(stream, m_orientation.get());
|
|
stream->writeFloat(m_stepSize);
|
|
}
|
|
|
|
void configure() {
|
|
Medium::configure();
|
|
if (m_density.get() == NULL)
|
|
Log(EError, "No density specified!");
|
|
if (m_albedo.get() == NULL)
|
|
Log(EError, "No albedo specified!");
|
|
m_densityAABB = m_density->getAABB();
|
|
m_directionallyVaryingCoefficients =
|
|
m_phaseFunction->needsDirectionallyVaryingCoefficients();
|
|
|
|
if (m_stepSize == 0) {
|
|
m_stepSize = std::min(
|
|
m_density->getStepSize(), m_albedo->getStepSize());
|
|
if (m_orientation != NULL)
|
|
m_stepSize = std::min(m_stepSize,
|
|
m_orientation->getStepSize());
|
|
|
|
if (m_stepSize == std::numeric_limits<Float>::infinity())
|
|
Log(EError, "Unable to infer a suitable step size, please specify one "
|
|
"manually using the 'stepSize' parameter.");
|
|
}
|
|
|
|
if (m_directionallyVaryingCoefficients && m_orientation.get() == NULL)
|
|
Log(EError, "Cannot use anisotropic phase function: "
|
|
"did not specify a particle orientation field!");
|
|
}
|
|
|
|
void addChild(const std::string &name, ConfigurableObject *child) {
|
|
if (child->getClass()->derivesFrom(MTS_CLASS(VolumeDataSource))) {
|
|
VolumeDataSource *volume = static_cast<VolumeDataSource *>(child);
|
|
|
|
if (name == "albedo") {
|
|
Assert(volume->supportsSpectrumLookups());
|
|
m_albedo = volume;
|
|
} else if (name == "density") {
|
|
Assert(volume->supportsFloatLookups());
|
|
m_density = volume;
|
|
} else if (name == "orientation") {
|
|
Assert(volume->supportsVectorLookups());
|
|
m_orientation = volume;
|
|
} else {
|
|
Medium::addChild(name, child);
|
|
}
|
|
} else {
|
|
Medium::addChild(name, child);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function uses Simpson quadrature to compute following
|
|
* integral:
|
|
*
|
|
* \int_{ray.mint}^{ray.maxt} density(ray(x)) dx
|
|
*
|
|
* The integration proceeds by splitting the function into
|
|
* approximately \c (ray.maxt-ray.mint)/m_stepSize segments,
|
|
* each of which are then approximated by a quadratic polynomial.
|
|
* The step size must be chosen so that this approximation is
|
|
* valid given the behavior of the integrand.
|
|
*
|
|
* \param ray
|
|
* Ray segment to be used for the integration
|
|
*
|
|
* \return
|
|
* The integrated density
|
|
*/
|
|
Float integrateDensity(const Ray &ray) const {
|
|
/* Determine the ray segment, along which the
|
|
density integration should take place */
|
|
Float mint, maxt;
|
|
if (!m_densityAABB.rayIntersect(ray, mint, maxt))
|
|
return 0.0f;
|
|
|
|
mint = std::max(mint, ray.mint);
|
|
maxt = std::min(maxt, ray.maxt);
|
|
Float length = maxt-mint, maxComp = 0;
|
|
|
|
Point p = ray(mint), pLast = ray(maxt);
|
|
|
|
/* Ignore degenerate path segments */
|
|
for (int i=0; i<3; ++i)
|
|
maxComp = std::max(std::max(maxComp,
|
|
std::abs(p[i])), std::abs(pLast[i]));
|
|
if (length < 1e-6f * maxComp)
|
|
return 0.0f;
|
|
|
|
/* Compute a suitable step size */
|
|
uint32_t nSteps = (uint32_t) std::ceil(length / m_stepSize)*2;
|
|
const Float stepSize = length/nSteps;
|
|
const Vector increment = ray.d * stepSize;
|
|
|
|
#if defined(HETVOL_STATISTICS)
|
|
avgRayMarchingStepsTransmittance.incrementBase();
|
|
earlyExits.incrementBase();
|
|
#endif
|
|
|
|
/* Perform lookups at the first and last node */
|
|
Float integratedDensity = lookupDensity(p, ray.d)
|
|
+ lookupDensity(pLast, ray.d);
|
|
|
|
#if defined(HETVOL_EARLY_EXIT)
|
|
const Float stopAfterDensity = -std::log(Epsilon);
|
|
const Float stopValue = stopAfterDensity*3.0f/(stepSize
|
|
* m_densityMultiplier);
|
|
#endif
|
|
|
|
p += increment;
|
|
|
|
Float m = 4;
|
|
for (uint32_t i=1; i<nSteps; ++i) {
|
|
integratedDensity += m * lookupDensity(p, ray.d);
|
|
m = 6 - m;
|
|
|
|
#if defined(HETVOL_STATISTICS)
|
|
++avgRayMarchingStepsTransmittance;
|
|
#endif
|
|
|
|
#if defined(HETVOL_EARLY_EXIT)
|
|
if (integratedDensity > stopValue) {
|
|
// Reached the threshold -- stop early
|
|
#if defined(HETVOL_STATISTICS)
|
|
++earlyExits;
|
|
#endif
|
|
return std::numeric_limits<Float>::infinity();
|
|
}
|
|
#endif
|
|
|
|
Point next = p + increment;
|
|
if (p == next) {
|
|
Log(EWarn, "integrateDensity(): unable to make forward progress -- "
|
|
"round-off error issues? The step size was %e, mint=%f, "
|
|
"maxt=%f, nSteps=%i, ray=%s", stepSize, mint, maxt, nSteps,
|
|
ray.toString().c_str());
|
|
break;
|
|
}
|
|
p = next;
|
|
}
|
|
|
|
return integratedDensity * m_densityMultiplier
|
|
* stepSize * (1.0f / 3.0f);
|
|
}
|
|
|
|
/**
|
|
* This function uses composite Simpson quadrature to solve the
|
|
* following integral equation for \a t:
|
|
*
|
|
* \int_{ray.mint}^t density(ray(x)) dx == desiredDensity
|
|
*
|
|
* The integration proceeds by splitting the function into
|
|
* approximately \c (ray.maxt-ray.mint)/m_stepSize segments,
|
|
* each of which are then approximated by a quadratic polynomial.
|
|
* The step size must be chosen so that this approximation is
|
|
* valid given the behavior of the integrand.
|
|
*
|
|
* \param ray
|
|
* Ray segment to be used for the integration
|
|
*
|
|
* \param desiredDensity
|
|
* Right hand side of the above equation
|
|
*
|
|
* \param integratedDensity
|
|
* Contains the final integrated density. Upon success, this value
|
|
* should closely match \c desiredDensity. When the equation could
|
|
* \a not be solved, the parameter contains the integrated density
|
|
* from \c ray.mint to \c ray.maxt (which, in this case, must be
|
|
* less than \c desiredDensity).
|
|
*
|
|
* \param t
|
|
* After calling this function, \c t will store the solution of the above
|
|
* equation. When there is no solution, it will be set to zero.
|
|
*
|
|
* \param densityAtMinT
|
|
* After calling this function, \c densityAtMinT will store the
|
|
* underlying density function evaluated at \c ray(ray.mint).
|
|
*
|
|
* \param densityAtT
|
|
* After calling this function, \c densityAtT will store the
|
|
* underlying density function evaluated at \c ray(t). When
|
|
* there is no solution, it will be set to zero.
|
|
*
|
|
* \return
|
|
* When no solution can be found in [ray.mint, ray.maxt] the
|
|
* function returns \c false.
|
|
*/
|
|
bool invertDensityIntegral(const Ray &ray, Float desiredDensity,
|
|
Float &integratedDensity, Float &t, Float &densityAtMinT,
|
|
Float &densityAtT) const {
|
|
integratedDensity = densityAtMinT = densityAtT = 0.0f;
|
|
|
|
/* Determine the ray segment, along which the
|
|
density integration should take place */
|
|
Float mint, maxt;
|
|
if (!m_densityAABB.rayIntersect(ray, mint, maxt))
|
|
return false;
|
|
mint = std::max(mint, ray.mint);
|
|
maxt = std::min(maxt, ray.maxt);
|
|
Float length = maxt - mint, maxComp = 0;
|
|
Point p = ray(mint), pLast = ray(maxt);
|
|
|
|
/* Ignore degenerate path segments */
|
|
for (int i=0; i<3; ++i)
|
|
maxComp = std::max(std::max(maxComp,
|
|
std::abs(p[i])), std::abs(pLast[i]));
|
|
if (length < 1e-6f * maxComp)
|
|
return 0.0f;
|
|
|
|
/* Compute a suitable step size */
|
|
uint32_t nSteps = (uint32_t) std::ceil(length / m_stepSize);
|
|
Float stepSize = length / nSteps,
|
|
multiplier = (1.0f / 6.0f) * stepSize
|
|
* m_densityMultiplier;
|
|
Vector fullStep = ray.d * stepSize,
|
|
halfStep = fullStep * .5f;
|
|
|
|
Float node1 = lookupDensity(p, ray.d);
|
|
|
|
if (ray.mint == mint)
|
|
densityAtMinT = node1 * m_densityMultiplier;
|
|
else
|
|
densityAtMinT = 0.0f;
|
|
|
|
#if defined(HETVOL_STATISTICS)
|
|
avgRayMarchingStepsSampling.incrementBase();
|
|
#endif
|
|
|
|
for (uint32_t i=0; i<nSteps; ++i) {
|
|
Float node2 = lookupDensity(p + halfStep, ray.d),
|
|
node3 = lookupDensity(p + fullStep, ray.d),
|
|
newDensity = integratedDensity + multiplier *
|
|
(node1+node2*4+node3);
|
|
#if defined(HETVOL_STATISTICS)
|
|
++avgRayMarchingStepsSampling;
|
|
#endif
|
|
if (newDensity >= desiredDensity) {
|
|
/* The integrated density of the last segment exceeds the desired
|
|
amount -- now use the Simpson quadrature expression and
|
|
Newton-Bisection to find the precise location of the scattering
|
|
event. Note that no further density queries are performed after
|
|
this point; instead, the density are modeled based on a
|
|
quadratic polynomial that is fit to the last three lookups */
|
|
|
|
Float a = 0, b = stepSize, x = a,
|
|
fx = integratedDensity - desiredDensity,
|
|
stepSizeSqr = stepSize * stepSize,
|
|
temp = m_densityMultiplier / stepSizeSqr;
|
|
int it = 1;
|
|
|
|
#if defined(HETVOL_STATISTICS)
|
|
avgNewtonIterations.incrementBase();
|
|
#endif
|
|
while (true) {
|
|
#if defined(HETVOL_STATISTICS)
|
|
++avgNewtonIterations;
|
|
#endif
|
|
/* Lagrange polynomial from the Simpson quadrature */
|
|
Float dfx = temp * (node1 * stepSizeSqr
|
|
- (3*node1 - 4*node2 + node3)*stepSize*x
|
|
+ 2*(node1 - 2*node2 + node3)*x*x);
|
|
#if 0
|
|
cout << "Iteration " << it << ": a=" << a << ", b=" << b
|
|
<< ", x=" << x << ", fx=" << fx << ", dfx=" << dfx << endl;
|
|
#endif
|
|
|
|
x -= fx/dfx;
|
|
|
|
if (EXPECT_NOT_TAKEN(x <= a || x >= b || dfx == 0))
|
|
x = 0.5f * (b + a);
|
|
|
|
/* Integrated version of the above Lagrange polynomial */
|
|
Float intval = integratedDensity + temp * (1.0f / 6.0f) * (x *
|
|
(6*node1*stepSizeSqr - 3*(3*node1 - 4*node2 + node3)*stepSize*x
|
|
+ 4*(node1 - 2*node2 + node3)*x*x));
|
|
fx = intval-desiredDensity;
|
|
|
|
if (std::abs(fx) < 1e-6f) {
|
|
t = mint + stepSize * i + x;
|
|
integratedDensity = intval;
|
|
densityAtT = temp * (node1 * stepSizeSqr
|
|
- (3*node1 - 4*node2 + node3)*stepSize*x
|
|
+ 2*(node1 - 2*node2 + node3)*x*x);
|
|
return true;
|
|
} else if (++it > 30) {
|
|
Log(EWarn, "invertDensityIntegral(): stuck in Newton-Bisection -- "
|
|
"round-off error issues? The step size was %e, fx=%f, dfx=%f, "
|
|
"a=%f, b=%f", stepSize, fx, dfx, a, b);
|
|
return false;
|
|
}
|
|
|
|
if (fx > 0)
|
|
b = x;
|
|
else
|
|
a = x;
|
|
}
|
|
}
|
|
|
|
Point next = p + fullStep;
|
|
if (p == next) {
|
|
Log(EWarn, "invertDensityIntegral(): unable to make forward progress -- "
|
|
"round-off error issues? The step size was %e", stepSize);
|
|
break;
|
|
}
|
|
integratedDensity = newDensity;
|
|
node1 = node3;
|
|
p = next;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Spectrum getTransmittance(const Ray &ray) const {
|
|
return Spectrum(std::exp(-integrateDensity(ray)));
|
|
}
|
|
|
|
bool sampleDistance(const Ray &ray, MediumSamplingRecord &mRec,
|
|
Sampler *sampler) const {
|
|
Float desiredDensity = -std::log(1-sampler->next1D());
|
|
Float integratedDensity, densityAtMinT, densityAtT;
|
|
bool success = false;
|
|
|
|
if (invertDensityIntegral(ray, desiredDensity, integratedDensity,
|
|
mRec.t, densityAtMinT, densityAtT)) {
|
|
mRec.p = ray(mRec.t);
|
|
success = true;
|
|
Spectrum albedo = m_albedo->lookupSpectrum(mRec.p);
|
|
mRec.sigmaS = albedo * densityAtT;
|
|
mRec.sigmaA = Spectrum(densityAtT) - mRec.sigmaS;
|
|
mRec.albedo = albedo.max();
|
|
mRec.orientation = m_orientation != NULL
|
|
? m_orientation->lookupVector(mRec.p) : Vector(0.0f);
|
|
}
|
|
|
|
Float expVal = std::exp(-integratedDensity);
|
|
mRec.pdfFailure = expVal;
|
|
mRec.pdfSuccess = expVal * densityAtT;
|
|
mRec.pdfSuccessRev = expVal * densityAtMinT;
|
|
mRec.transmittance = Spectrum(expVal);
|
|
|
|
return success && mRec.pdfSuccess > 0;
|
|
}
|
|
|
|
void pdfDistance(const Ray &ray, MediumSamplingRecord &mRec) const {
|
|
Float expVal = std::exp(-integrateDensity(ray));
|
|
|
|
mRec.transmittance = Spectrum(expVal);
|
|
mRec.pdfFailure = expVal;
|
|
mRec.pdfSuccess = expVal *
|
|
lookupDensity(ray(ray.maxt), ray.d) * m_densityMultiplier;
|
|
mRec.pdfSuccessRev = expVal *
|
|
lookupDensity(ray(ray.mint), ray.d) * m_densityMultiplier;
|
|
}
|
|
|
|
bool isHomogeneous() const {
|
|
return false;
|
|
}
|
|
|
|
std::string toString() const {
|
|
std::ostringstream oss;
|
|
oss << "HeterogeneousMedium[" << endl
|
|
<< " density = " << indent(m_density.toString()) << "," << endl
|
|
<< " albedo = " << indent(m_albedo.toString()) << "," << endl
|
|
<< " orientation = " << indent(m_orientation.toString()) << "," << endl
|
|
<< " stepSize = " << m_stepSize << "," << endl
|
|
<< " densityMultiplier = " << m_densityMultiplier << endl
|
|
<< "]";
|
|
return oss.str();
|
|
}
|
|
|
|
MTS_DECLARE_CLASS()
|
|
protected:
|
|
inline Float lookupDensity(const Point &p, const Vector &d) const {
|
|
Float density = m_density->lookupFloat(p);
|
|
if (m_directionallyVaryingCoefficients && density != 0) {
|
|
Vector orientation = m_orientation->lookupVector(p);
|
|
if (!orientation.isZero())
|
|
density *= m_phaseFunction->sigmaDir(dot(d, orientation));
|
|
///////// HACKY WORKAROUND FOR ZERO DENSITIES /////////
|
|
else
|
|
return 0;
|
|
///////// HACKY WORKAROUND FOR ZERO DENSITIES /////////
|
|
|
|
}
|
|
return density;
|
|
}
|
|
protected:
|
|
ref<VolumeDataSource> m_density;
|
|
ref<VolumeDataSource> m_albedo;
|
|
ref<VolumeDataSource> m_orientation;
|
|
Float m_stepSize;
|
|
AABB m_densityAABB;
|
|
bool m_directionallyVaryingCoefficients;
|
|
};
|
|
|
|
MTS_IMPLEMENT_CLASS_S(HeterogeneousMedium, false, Medium)
|
|
MTS_EXPORT_PLUGIN(HeterogeneousMedium, "Heterogeneous medium");
|
|
MTS_NAMESPACE_END
|