microflake bugfixes

metadata
Wenzel Jakob 2011-04-14 14:19:08 +02:00
parent d3ad21f0da
commit 9f58f35623
10 changed files with 131 additions and 65 deletions

View File

@ -22,7 +22,6 @@
#include <mitsuba/core/fresolver.h> #include <mitsuba/core/fresolver.h>
#include <mitsuba/core/quad.h> #include <mitsuba/core/quad.h>
#include <mitsuba/core/timer.h> #include <mitsuba/core/timer.h>
#include <mitsuba/core/random.h>
#include <boost/bind.hpp> #include <boost/bind.hpp>
#include <boost/math/distributions/chi_squared.hpp> #include <boost/math/distributions/chi_squared.hpp>
@ -42,10 +41,16 @@ MTS_NAMESPACE_BEGIN
* integrated over the area of each bin. Comparing the actual and reference * integrated over the area of each bin. Comparing the actual and reference
* bin counts yields the desired test statistic. * bin counts yields the desired test statistic.
* *
* Given a BSDF/phase function with the following interface * Given a probability distribution with the following interface
*
* <code> * <code>
* class MyDistribution { * class MyDistribution {
* Vector sample(const Point2 &uniformSample) const; * // Sample a (optionally weighted) direction. A non-unity weight
* // in the return value is needed when the sampling distribution
* // doesn't exactly match the implementation in pdf()
* std::pair<Vector, Float> generateSample() const;
*
* /// Compute the probability density for the specified direction
* Float pdf(const Vector &direction) const; * Float pdf(const Vector &direction) const;
* }; * };
* </code> * </code>
@ -58,7 +63,7 @@ MTS_NAMESPACE_BEGIN
* *
* // Initialize the tables used by the chi-square test * // Initialize the tables used by the chi-square test
* chiSqr.fill( * chiSqr.fill(
* boost::bind(&MyDistribution::sample, myDistrInstance, _1), * boost::bind(&MyDistribution::generateSample, myDistrInstance),
* boost::bind(&MyDistribution::pdf, myDistrInstance, _1) * boost::bind(&MyDistribution::pdf, myDistrInstance, _1)
* ); * );
* *
@ -85,7 +90,7 @@ public:
* *
* \param sampleCount * \param sampleCount
* Number of samples to be used when computing the bin * Number of samples to be used when computing the bin
* values. The default is \c thetaBins*phiBins*1000 * values. The default is \c thetaBins*phiBins*5000
*/ */
ChiSquareTest(int thetaBins = 10, int phiBins = 0, size_t sampleCount = 0) ChiSquareTest(int thetaBins = 10, int phiBins = 0, size_t sampleCount = 0)
: m_thetaBins(thetaBins), m_phiBins(phiBins), m_sampleCount(sampleCount) { : m_thetaBins(thetaBins), m_phiBins(phiBins), m_sampleCount(sampleCount) {
@ -93,7 +98,7 @@ public:
m_phiBins = 2*m_thetaBins; m_phiBins = 2*m_thetaBins;
if (m_sampleCount == 0) if (m_sampleCount == 0)
m_sampleCount = m_thetaBins * m_phiBins * 1000; m_sampleCount = m_thetaBins * m_phiBins * 1000;
m_table = new uint32_t[m_thetaBins*m_phiBins]; m_table = new Float[m_thetaBins*m_phiBins];
m_refTable = new Float[m_thetaBins*m_phiBins]; m_refTable = new Float[m_thetaBins*m_phiBins];
} }
@ -110,10 +115,9 @@ public:
* on how to invoke this function * on how to invoke this function
*/ */
void fill( void fill(
const boost::function<Vector (const Point2 &)> &sampleFn, const boost::function<std::pair<Vector, Float>()> &sampleFn,
const boost::function<Float (const Vector &)> &pdfFn) { const boost::function<Float (const Vector &)> &pdfFn) {
ref<Random> random = new Random(); memset(m_table, 0, m_thetaBins*m_phiBins*sizeof(Float));
memset(m_table, 0, m_thetaBins*m_phiBins*sizeof(uint32_t));
SLog(EInfo, "Accumulating " SIZE_T_FMT " samples into a %ix%i" SLog(EInfo, "Accumulating " SIZE_T_FMT " samples into a %ix%i"
" contingency table", m_sampleCount, m_thetaBins, m_phiBins); " contingency table", m_sampleCount, m_thetaBins, m_phiBins);
@ -121,15 +125,15 @@ public:
ref<Timer> timer = new Timer(); ref<Timer> timer = new Timer();
for (size_t i=0; i<m_sampleCount; ++i) { for (size_t i=0; i<m_sampleCount; ++i) {
Point2 sample(random->nextFloat(), random->nextFloat()); std::pair<Vector, Float> sample = sampleFn();
Point2 sphCoords = toSphericalCoordinates(sampleFn(sample)); Point2 sphCoords = toSphericalCoordinates(sample.first);
int thetaBin = std::min(std::max(0, int thetaBin = std::min(std::max(0,
floorToInt(sphCoords.x * factor.x)), m_thetaBins-1); floorToInt(sphCoords.x * factor.x)), m_thetaBins-1);
int phiBin = std::min(std::max(0, int phiBin = std::min(std::max(0,
floorToInt(sphCoords.y * factor.y)), m_phiBins-1); floorToInt(sphCoords.y * factor.y)), m_phiBins-1);
++m_table[thetaBin * m_phiBins + phiBin]; m_table[thetaBin * m_phiBins + phiBin] += sample.second;
} }
SLog(EInfo, "Done, took %i ms.", timer->getMilliseconds()); SLog(EInfo, "Done, took %i ms.", timer->getMilliseconds());
factor = Point2(M_PI / m_thetaBins, (2*M_PI) / m_phiBins); factor = Point2(M_PI / m_thetaBins, (2*M_PI) / m_phiBins);
@ -139,7 +143,7 @@ public:
Float min[2], max[2]; Float min[2], max[2];
size_t idx = 0; size_t idx = 0;
NDIntegrator integrator(1, 2, 50000, 0, 1e-6f); NDIntegrator integrator(1, 2, 100000, 0, 1e-6f);
Float maxError = 0, integral = 0; Float maxError = 0, integral = 0;
for (int i=0; i<m_thetaBins; ++i) { for (int i=0; i<m_thetaBins; ++i) {
min[0] = i * factor.x; min[0] = i * factor.x;
@ -269,7 +273,7 @@ protected:
private: private:
int m_thetaBins, m_phiBins; int m_thetaBins, m_phiBins;
size_t m_sampleCount; size_t m_sampleCount;
uint32_t *m_table; Float *m_table;
Float *m_refTable; Float *m_refTable;
}; };

View File

@ -72,7 +72,7 @@ public:
* \brief Evaluate the phase function for an outward-pointing * \brief Evaluate the phase function for an outward-pointing
* pair of directions (wi, wo) * pair of directions (wi, wo)
*/ */
virtual Spectrum f(const PhaseFunctionQueryRecord &pRec) const = 0; virtual Float f(const PhaseFunctionQueryRecord &pRec) const = 0;
/** /**
* \brief Importance sample the phase function. * \brief Importance sample the phase function.
@ -83,7 +83,7 @@ public:
* Weight value equal to the throughput divided by * Weight value equal to the throughput divided by
* the probability of the sampled direction. * the probability of the sampled direction.
*/ */
virtual Spectrum sample(PhaseFunctionQueryRecord &pRec, virtual Float sample(PhaseFunctionQueryRecord &pRec,
Sampler *sampler) const = 0; Sampler *sampler) const = 0;
/** /**
@ -95,7 +95,7 @@ public:
* \return * \return
* Phase function value for the direction pair (wi, wo) * Phase function value for the direction pair (wi, wo)
*/ */
virtual Spectrum sample(PhaseFunctionQueryRecord &pRec, virtual Float sample(PhaseFunctionQueryRecord &pRec,
Float &pdf, Sampler *sampler) const = 0; Float &pdf, Sampler *sampler) const = 0;
/** /**

View File

@ -81,9 +81,9 @@ public:
if (rRec.type & RadianceQueryRecord::EDirectMediumRadiance && if (rRec.type & RadianceQueryRecord::EDirectMediumRadiance &&
scene->sampleAttenuatedLuminaire(mRec.p, ray.time, rRec.medium, lRec, rRec.nextSample2D())) { scene->sampleAttenuatedLuminaire(mRec.p, ray.time, rRec.medium, lRec, rRec.nextSample2D())) {
/* Evaluate the phase function */ /* Evaluate the phase function */
Spectrum phaseVal = phase->f(PhaseFunctionQueryRecord(mRec, -ray.d, -lRec.d)); Float phaseVal = phase->f(PhaseFunctionQueryRecord(mRec, -ray.d, -lRec.d));
if (!phaseVal.isZero()) { if (phaseVal != 0) {
/* Calculate prob. of having sampled that direction using /* Calculate prob. of having sampled that direction using
phase function sampling */ phase function sampling */
Float phasePdf = (lRec.luminaire->isIntersectable() Float phasePdf = (lRec.luminaire->isIntersectable()
@ -102,8 +102,8 @@ public:
Float phasePdf; Float phasePdf;
PhaseFunctionQueryRecord pRec(mRec, -ray.d); PhaseFunctionQueryRecord pRec(mRec, -ray.d);
Spectrum phaseVal = phase->sample(pRec, phasePdf, rRec.sampler); Float phaseVal = phase->sample(pRec, phasePdf, rRec.sampler);
if (phaseVal.isZero()) if (phaseVal == 0)
break; break;
phaseVal /= phasePdf; phaseVal /= phasePdf;

View File

@ -89,8 +89,8 @@ public:
/* ==================================================================== */ /* ==================================================================== */
PhaseFunctionQueryRecord pRec(mRec, -ray.d); PhaseFunctionQueryRecord pRec(mRec, -ray.d);
Spectrum phaseVal = phase->sample(pRec, rRec.sampler); Float phaseVal = phase->sample(pRec, rRec.sampler);
if (phaseVal.isZero()) if (phaseVal == 0)
break; break;
/* Trace a ray in this direction */ /* Trace a ray in this direction */

View File

@ -15,7 +15,7 @@ std::string PhaseFunctionQueryRecord::toString() const {
} }
Float PhaseFunction::pdf(const PhaseFunctionQueryRecord &pRec) const { Float PhaseFunction::pdf(const PhaseFunctionQueryRecord &pRec) const {
return f(pRec)[0]; return f(pRec);
} }
MTS_IMPLEMENT_CLASS(PhaseFunction, true, ConfigurableObject) MTS_IMPLEMENT_CLASS(PhaseFunction, true, ConfigurableObject)

View File

@ -50,7 +50,7 @@ public:
stream->writeFloat(m_g); stream->writeFloat(m_g);
} }
inline Spectrum sample(PhaseFunctionQueryRecord &pRec, inline Float sample(PhaseFunctionQueryRecord &pRec,
Sampler *sampler) const { Sampler *sampler) const {
Point2 sample(sampler->next2D()); Point2 sample(sampler->next2D());
@ -71,21 +71,19 @@ public:
cosTheta); cosTheta);
pRec.wo = Frame(-pRec.wi).toWorld(dir); pRec.wo = Frame(-pRec.wi).toWorld(dir);
return Spectrum(1.0f); return 1.0f;
} }
Spectrum sample(PhaseFunctionQueryRecord &pRec, Float sample(PhaseFunctionQueryRecord &pRec,
Float &pdf, Sampler *sampler) const { Float &pdf, Sampler *sampler) const {
HGPhaseFunction::sample(pRec, sampler); HGPhaseFunction::sample(pRec, sampler);
return HGPhaseFunction::f(pRec);
pdf = HGPhaseFunction::f(pRec)[0];
return Spectrum(pdf);
} }
Spectrum f(const PhaseFunctionQueryRecord &pRec) const { Float f(const PhaseFunctionQueryRecord &pRec) const {
return Spectrum(1/(4*M_PI) * (1 - m_g*m_g) / return 1/(4*M_PI) * (1 - m_g*m_g) /
std::pow(1.f + m_g*m_g - 2.f * m_g * dot(-pRec.wi, pRec.wo), (Float) 1.5f)); std::pow(1.f + m_g*m_g - 2.f * m_g * dot(-pRec.wi, pRec.wo), (Float) 1.5f);
} }
std::string toString() const { std::string toString() const {

View File

@ -41,22 +41,22 @@ public:
PhaseFunction::serialize(stream, manager); PhaseFunction::serialize(stream, manager);
} }
Spectrum sample(PhaseFunctionQueryRecord &pRec, Float sample(PhaseFunctionQueryRecord &pRec,
Sampler *sampler) const { Sampler *sampler) const {
Point2 sample(sampler->next2D()); Point2 sample(sampler->next2D());
pRec.wo = squareToSphere(sample); pRec.wo = squareToSphere(sample);
return Spectrum(1.0f); return 1.0f;
} }
Spectrum sample(PhaseFunctionQueryRecord &pRec, Float sample(PhaseFunctionQueryRecord &pRec,
Float &pdf, Sampler *sampler) const { Float &pdf, Sampler *sampler) const {
pRec.wo = squareToSphere(sampler->next2D()); pRec.wo = squareToSphere(sampler->next2D());
pdf = 1/(4 * (Float) M_PI); pdf = 1/(4 * (Float) M_PI);
return Spectrum(pdf); return pdf;
} }
Spectrum f(const PhaseFunctionQueryRecord &pRec) const { Float f(const PhaseFunctionQueryRecord &pRec) const {
return Spectrum(1/(4 * (Float) M_PI)); return 1/(4 * (Float) M_PI);
} }
std::string toString() const { std::string toString() const {

View File

@ -79,13 +79,13 @@ public:
stream->writeFloat(m_exponent); stream->writeFloat(m_exponent);
} }
Spectrum sample(PhaseFunctionQueryRecord &pRec, Float sample(PhaseFunctionQueryRecord &pRec,
Sampler *sampler) const { Sampler *sampler) const {
pRec.wo = squareToSphere(sampler->next2D()); pRec.wo = squareToSphere(sampler->next2D());
return f(pRec) * (4 * M_PI); return f(pRec) * (4 * M_PI);
} }
Spectrum sample(PhaseFunctionQueryRecord &pRec, Float sample(PhaseFunctionQueryRecord &pRec,
Float &pdf, Sampler *sampler) const { Float &pdf, Sampler *sampler) const {
pRec.wo = squareToSphere(sampler->next2D()); pRec.wo = squareToSphere(sampler->next2D());
pdf = 1/(4 * M_PI); pdf = 1/(4 * M_PI);
@ -96,9 +96,9 @@ public:
return 1/(4 * M_PI); return 1/(4 * M_PI);
} }
Spectrum f(const PhaseFunctionQueryRecord &pRec) const { Float f(const PhaseFunctionQueryRecord &pRec) const {
if (pRec.mRec.orientation.length() == 0) if (pRec.mRec.orientation.length() == 0)
return Spectrum(m_kd / (4*M_PI)); return m_kd / (4*M_PI);
Frame frame(normalize(pRec.mRec.orientation)); Frame frame(normalize(pRec.mRec.orientation));
Vector reflectedLocal = frame.toLocal(pRec.wo); Vector reflectedLocal = frame.toLocal(pRec.wo);
@ -110,8 +110,8 @@ public:
reflectedLocal.x *= a; reflectedLocal.x *= a;
Vector R = frame.toWorld(reflectedLocal); Vector R = frame.toWorld(reflectedLocal);
return Spectrum((std::pow(std::max((Float) 0, dot(R, pRec.wo)), m_exponent)) return std::pow(std::max((Float) 0, dot(R, pRec.wo)), m_exponent)
* m_normalization * m_ks + m_kd / (4*M_PI)); * m_normalization * m_ks + m_kd / (4*M_PI);
} }
std::string toString() const { std::string toString() const {

View File

@ -17,20 +17,33 @@
*/ */
#include <mitsuba/core/chisquare.h> #include <mitsuba/core/chisquare.h>
#include <mitsuba/core/frame.h>
#include <mitsuba/render/phase.h> #include <mitsuba/render/phase.h>
#include <mitsuba/render/medium.h>
#include <mitsuba/render/sampler.h> #include <mitsuba/render/sampler.h>
#define MICROFLAKE_STATISTICS 1
#include "microflake_fiber.h" #include "microflake_fiber.h"
#include <mitsuba/core/plugin.h>///XXX
MTS_NAMESPACE_BEGIN MTS_NAMESPACE_BEGIN
#if defined(MICROFLAKE_STATISTICS)
static StatsCounter avgSampleIterations("Micro-flake model",
"Average rejection sampling iterations", EAverage);
#endif
class MicroflakePhaseFunction : public PhaseFunction { class MicroflakePhaseFunction : public PhaseFunction {
public: public:
MicroflakePhaseFunction(const Properties &props) : PhaseFunction(props) { MicroflakePhaseFunction(const Properties &props) : PhaseFunction(props) {
m_fiberDistr = GaussianFiberDistribution(props.getFloat("stddev")); m_fiberDistr = GaussianFiberDistribution(props.getFloat("stddev"));
ChiSquareTest test(7); ChiSquareTest test(7);
Sampler *sampler = static_cast<Sampler *> (PluginManager::getInstance()->
createObject(MTS_CLASS(Sampler), Properties("independent")));
test.fill( test.fill(
boost::bind(&GaussianFiberDistribution::sample, m_fiberDistr, _1), boost::bind(&MicroflakePhaseFunction::testSample, this, sampler),
boost::bind(&GaussianFiberDistribution::pdf, m_fiberDistr, _1) boost::bind(&MicroflakePhaseFunction::testF, this, _1)
); );
test.dumpTables("test.m"); test.dumpTables("test.m");
test.runTest(1); test.runTest(1);
@ -49,22 +62,69 @@ public:
void configure() { void configure() {
} }
Spectrum sample(PhaseFunctionQueryRecord &pRec, Float f(const PhaseFunctionQueryRecord &pRec) const {
Sampler *sampler) const { if (pRec.mRec.orientation.isZero())
Point2 sample(sampler->next2D()); return 0.0f;
pRec.wo = squareToSphere(sample);
return Spectrum(1.0f); Frame frame(pRec.mRec.orientation);
Vector wi = frame.toLocal(pRec.wi);
Vector wo = frame.toLocal(pRec.wo);
Vector H = wi + wo;
Float length = H.length();
if (length == 0)
return 0.0f;
Float cosThetaH = H.z/length;
return 0.5 * m_fiberDistr.pdfCosTheta(cosThetaH)
/ m_fiberDistr.sigmaT(Frame::cosTheta(wi));
} }
Spectrum sample(PhaseFunctionQueryRecord &pRec, inline Float sample(PhaseFunctionQueryRecord &pRec, Sampler *sampler) const {
if (pRec.mRec.orientation.isZero())
return 0.0f;
Frame frame(pRec.mRec.orientation);
Vector wi = frame.toLocal(pRec.wi);
#if defined(MICROFLAKE_STATISTICS)
avgSampleIterations.incrementBase();
#endif
int iterations = 0, maxIterations = 1000;
while (true) {
Vector H = m_fiberDistr.sample(sampler->next2D());
#if defined(MICROFLAKE_STATISTICS)
++avgSampleIterations;
#endif
++iterations;
if (sampler->next1D() < absDot(wi, H)) {
Vector wo = H*(2*dot(wi, H)) - wi;
pRec.wo = frame.toWorld(wo);
break;
}
if (iterations >= maxIterations) {
Log(EWarn, "Sample generation unsuccessful after %i iterations"
" (dp=%f, fiberOrientation=%s, wi=%s)", iterations,
absDot(pRec.wi, pRec.mRec.orientation),
pRec.mRec.orientation.toString().c_str(),
pRec.wi.toString().c_str());
return 0.0f;
}
}
return 1.0f;
}
Float sample(PhaseFunctionQueryRecord &pRec,
Float &pdf, Sampler *sampler) const { Float &pdf, Sampler *sampler) const {
pRec.wo = squareToSphere(sampler->next2D()); if (sample(pRec, sampler) == 0) {
pdf = 1/(4 * (Float) M_PI); pdf = 0;
return Spectrum(pdf); return 0.0f;
} }
pdf = f(pRec);
Spectrum f(const PhaseFunctionQueryRecord &pRec) const { return pdf;
return Spectrum(1/(4 * (Float) M_PI));
} }
std::string toString() const { std::string toString() const {

View File

@ -181,10 +181,10 @@ double sigmaT_fiberDist(double stddev, double sinTheta) {
return result; return result;
} }
static StatsCounter brentSolves("Micro-flake model", #if defined(MICROFLAKE_STATISTICS)
"Brent solver calls");
static StatsCounter avgBrentFunEvals("Micro-flake model", static StatsCounter avgBrentFunEvals("Micro-flake model",
"Average Brent solver function evaluations", EAverage); "Average Brent solver function evaluations", EAverage);
#endif
class GaussianFiberDistribution { class GaussianFiberDistribution {
public: public:
@ -250,8 +250,10 @@ public:
boost::bind(&GaussianFiberDistribution::cdfFunctor, boost::bind(&GaussianFiberDistribution::cdfFunctor,
this, sample.x, _1), -1, 1); this, sample.x, _1), -1, 1);
SAssert(result.success); SAssert(result.success);
avgBrentFunEvals.incrementBase();
++brentSolves; #if defined(MICROFLAKE_STATISTICS)
avgBrentFunEvals.incrementBase();
#endif
Float cosTheta = result.x, Float cosTheta = result.x,
sinTheta = std::sqrt(std::max((Float) 0, 1-cosTheta*cosTheta)), sinTheta = std::sqrt(std::max((Float) 0, 1-cosTheta*cosTheta)),
@ -275,7 +277,9 @@ protected:
} }
Float cdfFunctor(Float xi, Float cosTheta) const { Float cdfFunctor(Float xi, Float cosTheta) const {
++avgBrentFunEvals; #if defined(MICROFLAKE_STATISTICS)
++avgBrentFunEvals;
#endif
return cdf(cosTheta)-xi; return cdf(cosTheta)-xi;
} }