327 lines
9.1 KiB
C++
327 lines
9.1 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <mitsuba/render/scene.h>
|
|
#include "maxexp.h"
|
|
|
|
MTS_NAMESPACE_BEGIN
|
|
|
|
/**
|
|
* Homogeneous participating medium. An arbitrary (manifold) shape
|
|
* must be specified as a child object.
|
|
*/
|
|
class HomogeneousMedium : public Medium {
|
|
public:
|
|
enum ESamplingStrategy {
|
|
ERandom,
|
|
ESingle,
|
|
EBalance,
|
|
EManual,
|
|
EMaximum
|
|
};
|
|
|
|
HomogeneousMedium(const Properties &props)
|
|
: Medium(props) {
|
|
std::string strategy = props.getString("strategy", "random");
|
|
|
|
if (strategy == "random")
|
|
m_strategy = ERandom;
|
|
else if (strategy == "single")
|
|
m_strategy = ESingle;
|
|
else if (strategy == "balance")
|
|
m_strategy = EBalance;
|
|
else if (strategy == "maximum")
|
|
m_strategy = EMaximum;
|
|
else if (strategy == "manual")
|
|
m_strategy = EManual;
|
|
else
|
|
Log(EError, "Specified an unknown sampling strategy");
|
|
|
|
m_channel = props.getInteger("channel", 0);
|
|
m_sigma = props.getFloat("sigma", -1); // for the manual strategy
|
|
Assert(m_channel >= 0 && m_channel < SPECTRUM_SAMPLES);
|
|
if (props.getBoolean("mono", false)) {
|
|
m_sigmaA = Spectrum(m_sigmaA[m_channel]);
|
|
m_sigmaS = Spectrum(m_sigmaS[m_channel]);
|
|
m_sigmaT = m_sigmaA + m_sigmaS;
|
|
}
|
|
|
|
std::vector<Float> coeffs(SPECTRUM_SAMPLES);
|
|
for (int i=0; i<SPECTRUM_SAMPLES; ++i)
|
|
coeffs[i] = m_sigmaT[i];
|
|
|
|
m_maxExpDist = new MaxExpDist(coeffs);
|
|
m_kdTree = new KDTree();
|
|
}
|
|
|
|
HomogeneousMedium(Stream *stream, InstanceManager *manager)
|
|
: Medium(stream, manager) {
|
|
m_kdTree = new KDTree();
|
|
size_t shapeCount = stream->readUInt();
|
|
for (size_t i=0; i<shapeCount; ++i)
|
|
addChild("", static_cast<Shape *>(manager->getInstance(stream)));
|
|
m_channel = stream->readInt();
|
|
m_strategy = (ESamplingStrategy) stream->readInt();
|
|
m_sigma = stream->readFloat();
|
|
|
|
std::vector<Float> coeffs(SPECTRUM_SAMPLES);
|
|
for (int i=0; i<SPECTRUM_SAMPLES; ++i)
|
|
coeffs[i] = m_sigmaT[i];
|
|
|
|
configure();
|
|
m_maxExpDist = new MaxExpDist(coeffs);
|
|
}
|
|
|
|
virtual ~HomogeneousMedium() {
|
|
for (size_t i=0; i<m_shapes.size(); ++i)
|
|
m_shapes[i]->decRef();
|
|
delete m_maxExpDist;
|
|
}
|
|
|
|
void serialize(Stream *stream, InstanceManager *manager) const {
|
|
Medium::serialize(stream, manager);
|
|
stream->writeUInt((uint32_t) m_shapes.size());
|
|
for (size_t i=0; i<m_shapes.size(); ++i)
|
|
manager->serialize(stream, m_shapes[i]);
|
|
stream->writeInt(m_channel);
|
|
stream->writeInt(m_strategy);
|
|
stream->writeFloat(m_sigma);
|
|
}
|
|
|
|
void configure() {
|
|
Medium::configure();
|
|
if (m_shapes.size() == 0)
|
|
Log(EError, "This medium requires one or more Shape instance as a child");
|
|
m_kdTree->build();
|
|
m_aabb = m_kdTree->getAABB();
|
|
}
|
|
|
|
bool isInside(const Ray &r) const {
|
|
Ray ray(r(r.mint + Epsilon), r.d, 0.0f);
|
|
Intersection its;
|
|
if (!m_kdTree->rayIntersect(ray, its))
|
|
return false;
|
|
return dot(ray.d, its.geoFrame.n) > 0;
|
|
}
|
|
|
|
Spectrum tau(const Ray &r) const {
|
|
Float dLength = r.d.length();
|
|
Ray ray(r(r.mint), r.d / dLength, Epsilon, std::numeric_limits<Float>::infinity(), 0.0f);
|
|
Float coveredLength = 0, remaining = (r.maxt - r.mint) * dLength;
|
|
bool inside = isInside(r);
|
|
Intersection its;
|
|
int iterations = 0;
|
|
|
|
while (remaining > 0 && m_kdTree->rayIntersect(ray, its)) {
|
|
if (inside)
|
|
coveredLength += std::min(remaining, its.t);
|
|
remaining -= its.t;
|
|
inside = !inside;
|
|
ray.o = its.p;
|
|
|
|
if (++iterations > 20) {
|
|
/// Just a precaution..
|
|
Log(EWarn, "tau(): round-off error issues?");
|
|
break;
|
|
}
|
|
}
|
|
|
|
return m_sigmaT * coveredLength;
|
|
}
|
|
|
|
bool sampleDistance(const Ray &theRay, Float distSurf,
|
|
MediumSamplingRecord &mRec, Sampler *sampler) const {
|
|
Intersection its;
|
|
Ray ray(theRay.o, theRay.d, 0.0f);
|
|
int iterations = 0;
|
|
|
|
/* Check if the start of the ray is already inside the medium */
|
|
bool inside = isInside(ray);
|
|
Point orig(theRay.o);
|
|
|
|
/* Remaining distance in the medium */
|
|
Float distMed;
|
|
Float sigmaT;
|
|
|
|
switch (m_strategy) {
|
|
case EManual: {
|
|
sigmaT = m_sigma;
|
|
Float desiredAttenuation = 1 - sampler->next1D();
|
|
distMed = -std::log(desiredAttenuation) / sigmaT;
|
|
mRec.pdf = sigmaT * std::exp(-sigmaT * distMed);
|
|
mRec.miWeight = 1;
|
|
}
|
|
break;
|
|
case ESingle: {
|
|
sigmaT = m_sigmaT[m_channel];
|
|
Float desiredAttenuation = 1 - sampler->next1D();
|
|
distMed = -std::log(desiredAttenuation) / sigmaT;
|
|
mRec.pdf = sigmaT * std::exp(-sigmaT * distMed);
|
|
mRec.miWeight = 1;
|
|
}
|
|
break;
|
|
case ERandom: {
|
|
Float rv = sampler->next1D();
|
|
int channel = std::min(
|
|
(int) (rv * SPECTRUM_SAMPLES), SPECTRUM_SAMPLES-1);
|
|
sigmaT = m_sigmaT[channel];
|
|
Float desiredAttenuation = 1 - sampler->next1D();
|
|
distMed = -std::log(desiredAttenuation) / sigmaT;
|
|
mRec.pdf = sigmaT * std::exp(-sigmaT * distMed);
|
|
mRec.miWeight = 1;
|
|
}
|
|
break;
|
|
case EBalance: {
|
|
Float rv = sampler->next1D();
|
|
int channel = std::min(
|
|
(int) (rv * SPECTRUM_SAMPLES), SPECTRUM_SAMPLES-1);
|
|
sigmaT = m_sigmaT[channel];
|
|
Float desiredAttenuation = 1 - sampler->next1D();
|
|
distMed = -std::log(desiredAttenuation) / sigmaT;
|
|
Spectrum pdf = m_sigmaT * (Spectrum(-distMed) * m_sigmaT).exp();
|
|
mRec.pdf = pdf[channel] / SPECTRUM_SAMPLES;
|
|
|
|
Float sum = 0;
|
|
for (int i=0; i<SPECTRUM_SAMPLES; ++i)
|
|
sum += pdf[i];
|
|
mRec.miWeight = pdf[channel]/sum;
|
|
}
|
|
break;
|
|
case EMaximum: {
|
|
Float rv = sampler->next1D();
|
|
distMed = m_maxExpDist->sample(rv, mRec.pdf);
|
|
mRec.miWeight = 1;
|
|
sigmaT = -1; // make the compiler happy
|
|
};
|
|
break;
|
|
default:
|
|
Log(EError, "Unknown sampling strategy!");
|
|
return false;
|
|
}
|
|
|
|
Float traveled = 0, // Traveled ray distance
|
|
covered = 0; // Distance covered by the medium
|
|
|
|
while (m_kdTree->rayIntersect(ray, its)) {
|
|
if (inside) {
|
|
/* Moving through the medium */
|
|
if (its.t > distMed && distMed < distSurf) {
|
|
/* A medium interaction occurred */
|
|
mRec.p = ray(distMed);
|
|
mRec.sigmaA = m_sigmaA;
|
|
mRec.sigmaS = m_sigmaS;
|
|
mRec.albedo = m_albedo;
|
|
mRec.medium = this;
|
|
mRec.attenuation = (m_sigmaT * (-covered-distMed)).exp();
|
|
mRec.t = traveled + distMed;
|
|
return true;
|
|
} else if (its.t > distSurf) {
|
|
/* A surface interaction occurred */
|
|
covered += distSurf;
|
|
traveled += distSurf;
|
|
break;
|
|
} else {
|
|
/* Still moving through the medium */
|
|
covered += its.t;
|
|
distMed -= its.t;
|
|
}
|
|
} else {
|
|
/* Moving through space outside of the medium */
|
|
if (its.t > distSurf) {
|
|
/* A surface interaction occurred */
|
|
traveled += distSurf;
|
|
break;
|
|
}
|
|
}
|
|
traveled += its.t;
|
|
distSurf -= its.t;
|
|
inside = !inside;
|
|
ray.o = its.p;
|
|
|
|
if (++iterations > 10) {
|
|
/// Just a precaution..
|
|
Log(EWarn, "selectDistance(): round-off error issues?");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* There was no medium interaction inside the permitted ray interval.
|
|
This occurred with the probability tau[channel](0, maxDist) */
|
|
if (m_strategy != EMaximum)
|
|
mRec.pdf = std::exp(-sigmaT * covered);
|
|
else
|
|
mRec.pdf = 1-m_maxExpDist->cdf(covered);
|
|
mRec.t = traveled;
|
|
mRec.attenuation = (m_sigmaT * (-covered)).exp();
|
|
mRec.miWeight = 1;
|
|
|
|
return false;
|
|
}
|
|
|
|
void setParent(ConfigurableObject *parent) {
|
|
if (parent->getClass()->derivesFrom(Shape::m_theClass))
|
|
Log(EError, "Medium cannot be a parent of a shape");
|
|
}
|
|
|
|
void addChild(const std::string &name, ConfigurableObject *child) {
|
|
if (child->getClass()->derivesFrom(Shape::m_theClass)) {
|
|
Shape *shape = static_cast<Shape *>(child);
|
|
if (shape->isCompound()) {
|
|
int ctr = 0;
|
|
while (true) {
|
|
ref<Shape> childShape = shape->getElement(ctr++);
|
|
if (!childShape)
|
|
break;
|
|
addChild("", childShape);
|
|
}
|
|
} else {
|
|
m_kdTree->addShape(shape);
|
|
shape->incRef();
|
|
m_shapes.push_back(shape);
|
|
}
|
|
} else {
|
|
Medium::addChild(name, child);
|
|
}
|
|
}
|
|
|
|
std::string toString() const {
|
|
std::ostringstream oss;
|
|
oss << "HomogeneousMedium[" << endl
|
|
<< " sigmaA = " << m_sigmaA.toString() << "," << std::endl
|
|
<< " sigmaS = " << m_sigmaS.toString() << "," << std::endl
|
|
<< " sigmaT = " << m_sigmaT.toString() << "," << std::endl
|
|
<< " phase = " << indent(m_phaseFunction->toString()) << "," << std::endl
|
|
<< " shapes = " << indent(listToString(m_shapes)) << std::endl
|
|
<< "]";
|
|
return oss.str();
|
|
}
|
|
|
|
MTS_DECLARE_CLASS()
|
|
private:
|
|
ref<KDTree> m_kdTree;
|
|
std::vector<Shape *> m_shapes;
|
|
int m_channel;
|
|
Float m_sigma;
|
|
ESamplingStrategy m_strategy;
|
|
MaxExpDist *m_maxExpDist;
|
|
};
|
|
|
|
MTS_IMPLEMENT_CLASS_S(HomogeneousMedium, false, Medium)
|
|
MTS_EXPORT_PLUGIN(HomogeneousMedium, "Homogeneous medium");
|
|
MTS_NAMESPACE_END
|