diff --git a/include/mitsuba/core/chisquare.h b/include/mitsuba/core/chisquare.h
new file mode 100644
index 00000000..b506abff
--- /dev/null
+++ b/include/mitsuba/core/chisquare.h
@@ -0,0 +1,278 @@
+/*
+ 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 .
+*/
+
+#if !defined(__CHI_SQUARE_TEST_H)
+#define __CHI_SQUARE_TEST_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+MTS_NAMESPACE_BEGIN
+
+/**
+ * \brief Chi-square goodness-of-fit test on the sphere
+ *
+ * This class performs a chi-square goodness-of-fit test of the null hypothesis
+ * that a specified sampling procedure produces samples that are distributed
+ * according to a supplied density function. This is very useful to verify BRDF
+ * and phase function sampling codes for their correctness.
+ *
+ * This implementation works by generating a large batch of samples, which are
+ * then accumulated into rectangular bins in spherical coordinates. To obtain
+ * reference bin counts, the provided density function is numerically
+ * integrated over the area of each bin. Comparing the actual and reference
+ * bin counts yields the desired test statistic.
+ *
+ * Given a BSDF/phase function with the following interface
+ *
+ * class MyDistribution {
+ * Vector sample(const Point2 &uniformSample) const;
+ * Float pdf(const Vector &direction) const;
+ * };
+ *
+ *
+ * the code in this class might be used as follows
+ *
+ *
+ * MyDistribution myDistrInstance;
+ * ChiSquareTest chiSqr;
+ *
+ * // Initialize the tables used by the chi-square test
+ * chiSqr.fill(
+ * boost::bind(&MyDistribution::sample, myDistrInstance, _1),
+ * boost::bind(&MyDistribution::pdf, myDistrInstance, _1)
+ * );
+ *
+ * // Optional: dump the tables to a MATLAB file for external analysis
+ * chiSqr.dumpTables("debug.m");
+ *
+ * // (the folowing assumes that the distribution has 1 parameter, e.g. exponent value)
+ * if (!chiSqr.runTest(1))
+ * Log(EError, "Uh oh -- test failed, the implementation is probably incorrect!");
+ *
+ */
+class ChiSquareTest {
+public:
+ /**
+ * \brief Create a new Chi-square test instance with the given
+ * resolution and sample count
+ *
+ * \param thetaBins
+ * Number of bins wrt. latitude. The default is 10
+ *
+ * \param phiBins
+ * Number of bins wrt. azimuth. The default is to use
+ * twice the number of \c thetaBins
+ *
+ * \param sampleCount
+ * Number of samples to be used when computing the bin
+ * values. The default is \c thetaBins*phiBins*1000
+ */
+ ChiSquareTest(int thetaBins = 10, int phiBins = 0, size_t sampleCount = 0)
+ : m_thetaBins(thetaBins), m_phiBins(phiBins), m_sampleCount(sampleCount) {
+ if (m_phiBins == 0)
+ m_phiBins = 2*m_thetaBins;
+ if (m_sampleCount == 0)
+ m_sampleCount = m_thetaBins * m_phiBins * 1000;
+ m_table = new uint32_t[m_thetaBins*m_phiBins];
+ m_refTable = new Float[m_thetaBins*m_phiBins];
+ }
+
+ /// Release all memory
+ ~ChiSquareTest() {
+ delete[] m_table;
+ delete[] m_refTable;
+ }
+
+ /**
+ * \brief Fill the actual and reference bin counts
+ *
+ * Please see the class documentation for a description
+ * on how to invoke this function
+ */
+ void fill(
+ const boost::function &sampleFn,
+ const boost::function &pdfFn) {
+ ref random = new Random();
+ memset(m_table, 0, m_thetaBins*m_phiBins*sizeof(uint32_t));
+
+ SLog(EInfo, "Accumulating " SIZE_T_FMT " samples into a %ix%i"
+ " contingency table", m_sampleCount, m_thetaBins, m_phiBins);
+ Point2 factor(m_thetaBins / M_PI, m_phiBins / (2*M_PI));
+
+ ref timer = new Timer();
+ for (size_t i=0; inextFloat(), random->nextFloat());
+ Point2 sphCoords = toSphericalCoordinates(sampleFn(sample));
+
+ int thetaBin = std::min(std::max(0,
+ floorToInt(sphCoords.x * factor.x)), m_thetaBins-1);
+ int phiBin = std::min(std::max(0,
+ floorToInt(sphCoords.y * factor.y)), m_phiBins-1);
+
+ ++m_table[thetaBin * m_phiBins + phiBin];
+ }
+ SLog(EInfo, "Done, took %i ms.", timer->getMilliseconds());
+ factor = Point2(M_PI / m_thetaBins, (2*M_PI) / m_phiBins);
+
+ SLog(EInfo, "Integrating reference contingency table");
+ timer->reset();
+ Float min[2], max[2];
+ size_t idx = 0;
+
+ NDIntegrator integrator(1, 2, 50000, 0, 1e-6f);
+ Float maxError = 0, integral = 0;
+ for (int i=0; igetMilliseconds(), maxError, integral);
+ }
+
+ /**
+ * \brief Dump the bin counts to a file using MATLAB format
+ */
+ void dumpTables(const fs::path &filename) {
+ fs::ofstream out(filename);
+ out << "tbl_counts = [ ";
+ for (int i=0; i 0) {
+ SLog(EInfo, "Pooled %i cells with an expected "
+ "number of < 5 entries!", pooledCells);
+ if (pooledRef < 5) {
+ SLog(EWarn, "Even after pooling, the expected "
+ "number of entries is < 5 (%f), expect badness!",
+ pooledRef);
+ }
+ Float diff = pooledCounts - pooledRef;
+ chsq += (diff*diff) / pooledRef;
+ ++df;
+ }
+
+ df -= distParams + 1;
+ SLog(EInfo, "Chi-square statistic = %e (df=%i)", chsq, df);
+ boost::math::chi_squared chSqDist(df);
+ /* Probability of obtaining a test statistic at least
+ as extreme as the one observed under the assumption
+ that the distributions match */
+ Float pval = 1 - boost::math::cdf(chSqDist, chsq);
+ SLog(EInfo, "P-value = %e", pval);
+
+ if (pval < pvalThresh) {
+ SLog(EWarn, "Rejecting the null hypothesis");
+ return false;
+ }
+ return true;
+
+ }
+protected:
+ static void integrand(
+ const boost::function &pdfFn,
+ size_t nPts, const Float *in, Float *out) {
+ #pragma omp parallel for
+ for (size_t i=0; i class aligned_allocator {
-public:
- typedef size_t size_type;
- typedef ptrdiff_t difference_type;
- typedef T* pointer;
- typedef const T* const_pointer;
- typedef T& reference;
- typedef const T& const_reference;
- typedef T value_type;
-
- /// \cond
- template struct rebind {
- typedef aligned_allocator other;
- };
- /// \endcond
-
- pointer address (reference value) const {
- return &value;
- }
-
- const_pointer address (const_reference value) const {
- return &value;
- }
-
- aligned_allocator() throw() { }
-
- aligned_allocator(const aligned_allocator&) throw() { }
-
- template aligned_allocator (const aligned_allocator&) throw() { }
-
- ~aligned_allocator() throw() { }
-
- size_type max_size () const throw() {
- return INT_MAX;
- }
-
-
- T* __restrict allocate (size_type num, const_pointer *hint = 0) {
- return (T *) mitsuba::allocAligned(num*sizeof(T));
- }
-
- void construct (pointer p, const T& value) {
- *p=value;
- }
-
- void destroy (pointer p) {
- p->~T();
- };
-
- void deallocate (pointer p, size_type num) {
- freeAligned(p);
- }
-};
-
-MTS_NAMESPACE_END
-
#if defined(WIN32)
inline bool mts_isnan(float f) {
int classification = ::_fpclass(f);
diff --git a/src/libcore/util.cpp b/src/libcore/util.cpp
index 9b1e3b3f..088b7256 100644
--- a/src/libcore/util.cpp
+++ b/src/libcore/util.cpp
@@ -616,10 +616,13 @@ Point2 squareToTriangle(const Point2 &sample) {
}
Point2 toSphericalCoordinates(const Vector &v) {
- return Point2(
+ Point2 result(
std::acos(v.z),
std::atan2(v.y, v.x)
);
+ if (result.y < 0)
+ result.y += 2*M_PI;
+ return result;
}
Point2 squareToDiskConcentric(const Point2 &sample) {
diff --git a/src/medium/heterogeneous-flake.cpp b/src/medium/heterogeneous-flake.cpp
deleted file mode 100644
index e7c72c06..00000000
--- a/src/medium/heterogeneous-flake.cpp
+++ /dev/null
@@ -1,736 +0,0 @@
-/*
- 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 .
-*/
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-/*
- * When inverting an integral of the form f(t):=\int_0^t [...] dt
- * using composite Simpson's rule quadrature, the following constant
- * specified how many times the step size will be reduced when we
- * have almost reached the desired value.
- */
-#define NUM_REFINES 8
-
-/**
- * Allow to stop integrating densities when the resulting segment
- * has a throughput of less than 'Epsilon'
- */
-#define HET_EARLY_EXIT 1
-
-/// Header identification record for phase function caches
-#define PHASE_FUNCTION_HEADER 0x40044004
-
-#define USE_REJECTION_SAMPLING 1
-#define USE_COSTHETA_DISTR 1
-
-MTS_NAMESPACE_BEGIN
-
-static StatsCounter totalSamples("Micro-flake model", "Sample generations");
-static StatsCounter avgSampleIterations("Micro-flake model", "Average rejection sampling iterations", EAverage);
-static StatsCounter brentSolves("Micro-flake model", "Brent solver calls");
-static StatsCounter avgBrentFunEvals("Micro-flake model", "Average Brent solver function evaluations", EAverage);
-
-struct AbsCos {
- Float operator()(const Vector &w) const { return std::abs(w.z); }
-};
-
-#define USE_COSTHETA_DISTR 1
-
-enum EMode {
- ESinP = 0,
- ECosP
-};
-
-struct FlakeDistr {
- Float exp;
- EMode mode;
-
- FlakeDistr() { }
- FlakeDistr(EMode mode, Float exp) : exp(exp), mode(mode) { }
-
- inline Float operator()(const Vector &w) const {
- switch (mode) {
- case ESinP:
- return std::pow(1-std::pow(w.z, 2), exp/2.0f);
- case ECosP:
- return std::pow(std::abs(w.z), exp);
- default:
- SLog(EError, "Invalid flake distribution!");
- return 0.0f;
- }
- }
-};
-
-template struct FlakePhaseFunctor {
- Distr d;
-
- FlakePhaseFunctor(Distr d) : d(d) { }
-
- inline Float operator()(const Vector &wi, const Vector &wo) const {
- Vector H = normalize(wi + wo);
- return d(H) + d(-H);
- }
-};
-
-class HeterogeneousFlakeMedium;
-
-
-class FlakePhaseFunction : public PhaseFunction {
-public:
- FlakePhaseFunction(const SHVector &sigmaT, int samplingRecursions, const SHVector4D *phaseExpansion,
- Float exponent, Float normalization, EMode mode, HeterogeneousFlakeMedium *medium) : PhaseFunction(Properties()),
- m_sigmaT(sigmaT), m_samplingRecursions(samplingRecursions), m_distr(mode, exponent), m_phaseExpansion(phaseExpansion),
- m_exponent(exponent), m_normalization(normalization), m_mode(mode), m_medium(medium) {
- initialize();
- }
-
- FlakePhaseFunction(Stream *stream, InstanceManager *manager) :
- PhaseFunction(stream, manager) {
- /* Not implemented */
- }
-
- void serialize(Stream *stream, InstanceManager *manager) const {
- PhaseFunction::serialize(stream, manager);
- /* Not implemented */
- }
-
- void initialize() {
- m_shSampler = new SHSampler(m_phaseExpansion->getBands(), m_samplingRecursions);
- Log(EInfo, "Constructing a SH sampler: %s", m_shSampler->toString().c_str());
- }
-
- virtual ~FlakePhaseFunction() { }
-
- /* Implementations are below */
- Spectrum f(const PhaseFunctionQueryRecord &pRec) const;
-
- Spectrum sample(PhaseFunctionQueryRecord &pRec,
- Float &pdf, Sampler *sampler) const;
-
- Spectrum sample(PhaseFunctionQueryRecord &pRec,
- Sampler *sampler) const;
-
- Float pdf(const PhaseFunctionQueryRecord &pRec) const;
-
- std::string toString() const {
- return "FlakePhaseFunction[]";
- }
-
- MTS_DECLARE_CLASS()
-private:
- SHVector m_sigmaT;
- ref m_shSampler;
- int m_samplingRecursions;
- FlakeDistr m_distr;
- const SHVector4D *m_phaseExpansion;
- mutable PrimitiveThreadLocal m_temp;
- Float m_exponent;
- Float m_normalization;
- EMode m_mode;
- const HeterogeneousFlakeMedium *m_medium;
-};
-
-class HeterogeneousFlakeMedium : public Medium {
-public:
- HeterogeneousFlakeMedium(const Properties &props)
- : Medium(props) {
- m_stepSize = props.getFloat("stepSize", 0);
-
- /* Flake model configuration */
- int bands = props.getInteger("bands", 7);
- int phaseBands = props.getInteger("phaseBands", bands);
- Assert(bands > 0 && phaseBands > 0);
-
- m_samplingRecursions = props.getInteger("samplingRecursions", 10);
- std::string mode = props.getString("mode", "sinp");
-
- if (mode == "sinp") {
- m_mode = ESinP;
- } else if (mode == "cosp") {
- m_mode = ECosP;
- } else {
- Log(EError, "Unknown mode: %s!", mode.c_str());
- }
-
- const int numSamples = 80;
- ref stream;
-
- SHVector absCos = SHVector(bands);
- absCos.project(AbsCos(), numSamples);
- Float absCosError = absCos.l2Error(AbsCos(), numSamples);
-
- m_exponent = props.getFloat("exponent", 4);
- FlakeDistr distrFunc(m_mode, m_exponent);
-
- m_D = SHVector(bands);
- m_D.project(distrFunc, numSamples);
- m_normalization = 1 / (m_D(0, 0) * 2 * std::sqrt(M_PI));
- Float dError = m_D.l2Error(distrFunc, numSamples);
- m_D.normalize();
-
- Log(EInfo, "======= Flake medium properties =======");
- Log(EInfo, " distribution type = %s", mode.c_str());
- Log(EInfo, " distribution exponent = %f", m_exponent);
- Log(EInfo, " SH bands (D, sigmaT) = %i", bands);
- Log(EInfo, " SH bands (phase fct) = %i", phaseBands);
- Log(EInfo, " |cos| projection error = %f", absCosError);
- Log(EInfo, " D projection error = %f", dError);
- Log(EInfo, " sampling recursions = %i", m_samplingRecursions);
-
- bool computePhaseProjection = true;
- if (fs::exists("flake-phase.dat")) {
- /* Avoid recomputing this every time */
- stream = new FileStream("flake-phase.dat", FileStream::EReadOnly);
- unsigned int header = stream->readUInt();
- int nBands = stream->readInt();
- int mode = stream->readInt();
- Float exponent = stream->readFloat();
- if (header == PHASE_FUNCTION_HEADER && nBands == phaseBands
- && exponent == m_exponent && mode == m_mode) {
- m_phaseExpansion = SHVector4D(stream);
- computePhaseProjection = false;
- }
- stream->close();
- }
-
- if (computePhaseProjection) {
- int res = 50;
- FlakePhaseFunctor phaseFunctor(distrFunc);
- m_phaseExpansion = SHVector4D(res, 2*res, phaseBands);
- Log(EDebug, "Phase function parameterization: %s", m_phaseExpansion.toString().c_str());
- m_phaseExpansion.project(phaseFunctor, numSamples);
- m_phaseExpansion.normalize();
- stream = new FileStream("flake-phase.dat", FileStream::ETruncReadWrite);
- stream->writeInt(PHASE_FUNCTION_HEADER);
- stream->writeInt(phaseBands);
- stream->writeInt(m_mode);
- stream->writeFloat(m_exponent);
- m_phaseExpansion.serialize(stream);
- stream->close();
- }
-
- for (int i=0; ireadUInt();
- for (size_t i=0; i(manager->getInstance(stream)));
- m_densities = static_cast(manager->getInstance(stream));
- m_albedo = static_cast(manager->getInstance(stream));
- m_orientations = static_cast(manager->getInstance(stream));
- m_stepSize = stream->readFloat();
- m_exponent = stream->readFloat();
- m_normalization = stream->readFloat();
- m_mode = (EMode) stream->readInt();
- m_D = SHVector(stream);
- m_sigmaT = SHVector(stream);
- m_phaseExpansion = SHVector4D(stream);
- m_samplingRecursions = stream->readInt();
- configure();
- }
-
- virtual ~HeterogeneousFlakeMedium() {
- for (size_t i=0; idecRef();
- }
-
- /* Serialize the volume to a binary data stream */
- void serialize(Stream *stream, InstanceManager *manager) const {
- Medium::serialize(stream, manager);
- stream->writeUInt((uint32_t) m_shapes.size());
- for (size_t i=0; iserialize(stream, m_shapes[i]);
- manager->serialize(stream, m_densities.get());
- manager->serialize(stream, m_albedo.get());
- manager->serialize(stream, m_orientations.get());
- stream->writeFloat(m_stepSize);
- stream->writeFloat(m_exponent);
- stream->writeFloat(m_normalization);
- stream->writeInt(m_mode);
- m_D.serialize(stream);
- m_sigmaT.serialize(stream);
- m_phaseExpansion.serialize(stream);
- stream->writeInt(m_samplingRecursions);
- }
-
- void configure() {
- Medium::configure();
- if (m_densities.get() == NULL)
- Log(EError, "No densities specified!");
- if (m_albedo.get() == NULL)
- Log(EError, "No albedo specified!");
- if (m_orientations.get() == NULL)
- Log(EError, "No orientations specified!");
-
- if (m_stepSize == 0) {
- m_stepSize = std::min(std::min(
- m_densities->getStepSize(),
- m_albedo->getStepSize()),
- m_orientations->getStepSize());
-
- if (m_stepSize == std::numeric_limits::infinity())
- Log(EError, "Unable to infer step size, please specify!");
- }
-
- m_phaseFunction = new FlakePhaseFunction(m_sigmaT, m_samplingRecursions,
- &m_phaseExpansion, m_exponent, m_normalization, m_mode, this);
-
- if (m_shapes.size() > 0) {
- m_kdTree->build();
- m_aabb = m_kdTree->getAABB();
- } else {
- m_aabb = m_densities->getAABB();
- }
- }
-
- void setParent(ConfigurableObject *parent) {
- if (parent->getClass()->derivesFrom(MTS_CLASS(Shape)))
- Log(EError, "Medium cannot be a parent of a shape");
- }
-
-
- void addChild(const std::string &name, ConfigurableObject *child) {
- if (child->getClass()->derivesFrom(MTS_CLASS(Shape))) {
- Shape *shape = static_cast(child);
- if (shape->isCompound()) {
- int ctr = 0;
- while (true) {
- ref childShape = shape->getElement(ctr++);
- if (!childShape)
- break;
- addChild("", childShape);
- }
- } else {
- m_kdTree->addShape(shape);
- shape->incRef();
- m_shapes.push_back(shape);
- }
- } else if (child->getClass()->derivesFrom(MTS_CLASS(VolumeDataSource))) {
- VolumeDataSource *volume = static_cast(child);
-
- if (name == "albedo") {
- Assert(volume->supportsSpectrumLookups());
- m_albedo = volume;
- } else if (name == "density") {
- Assert(volume->supportsFloatLookups());
- m_densities = volume;
- } else if (name == "orientations") {
- Assert(volume->supportsVectorLookups());
- m_orientations = volume;
- }
- } else {
- Medium::addChild(name, child);
- }
- }
-
- /**
- * \brief Intersect a ray with the stencil and
- * determine whether it started on the inside
- */
- inline bool rayIntersect(const Ray &ray, Float &t, bool &inside) const {
- if (m_shapes.size() != 0) {
- Intersection its;
- if (!m_kdTree->rayIntersect(ray, its))
- return false;
- inside = dot(its.geoFrame.n, ray.d) > 0;
- t = its.t;
- } else {
- Float mint, maxt;
- if (!m_aabb.rayIntersect(ray, mint, maxt))
- return false;
- if (mint <= ray.mint && maxt <= ray.mint)
- return false;
- inside = mint <= 0 && maxt > 0;
- t = (mint <= 0) ? maxt : mint;
- }
- return true;
- }
-
- inline Vector lookupOrientation(const Point &p) const {
- Vector orientation = m_orientations->lookupVector(p);
- Float lengthSqr = orientation.lengthSquared();
- if (lengthSqr != 0)
- return orientation / std::sqrt(lengthSqr);
- else
- return Vector(0.0f);
- }
-
- inline Float sigmaT(const Point &p, const Vector &d) const {
- /**
- * Double the densities so that a uniform flake distribution
- * reproduces isotropic scattering
- */
- Float density = 2 * m_densities->lookupFloat(p) * m_densityMultiplier;
- if (density == 0)
- return 0.0f;
- Vector orientation = lookupOrientation(p);
- if (orientation == Vector(0.0f))
- return 0.0f;
-
- Vector localD = Frame(orientation).toLocal(d);
-
- return density * m_sigmaT.evalAzimuthallyInvariant(localD);
- }
-
- /// Integrate densities using the composite Simpson's rule
- inline Float integrateDensity(Ray ray, Float length) const {
- if (length == 0)
- return 0.0f;
- int nParts = (int) std::ceil(length/m_stepSize);
- nParts += nParts % 2;
- const Float stepSize = length/nParts;
- const Vector increment = ray.d * stepSize;
-
- Float m = 4;
- Float accumulatedDensity =
- sigmaT(ray.o, ray.d) + sigmaT(ray(length), ray.d);
-#ifdef HET_EARLY_EXIT
- const Float stopAfterDensity = -std::log(Epsilon);
- const Float stopValue = stopAfterDensity*3/stepSize;
-#endif
-
- ray.o += increment;
- for (int i=1; i stopValue) // Stop early
- return std::numeric_limits::infinity();
-#endif
- m = 6 - m;
- }
- accumulatedDensity *= stepSize/3;
-
- return accumulatedDensity;
- }
-
- /**
- * \brief Integrate the density covered by the specified ray
- * segment, while clipping the volume to the underlying
- * stencil volume.
- */
- inline Float integrateDensity(const Ray &r) const {
- Ray ray(r(r.mint), r.d,
- 0, std::numeric_limits::infinity(), 0.0f);
- Float integral = 0, remaining = r.maxt - r.mint;
- int iterations = 0;
- bool inside = false;
- Float t = 0;
-
- while (remaining > 0 && rayIntersect(ray, t, inside)) {
- if (inside)
- integral += integrateDensity(ray, std::min(remaining, t));
-
- remaining -= t;
- ray.o = ray(t);
- ray.mint = Epsilon;
-
- if (++iterations > 100) {
- /// Just a precaution..
- Log(EWarn, "integrateDensity(): round-off error issues?");
- break;
- }
- }
- return integral;
- }
-
- /**
- * Attempts to solve the following 1D integral equation for 't'
- * \int_0^t density(ray.o + x * ray.d) * dx + accumulatedDensity == desiredDensity.
- * When no solution can be found in [0, maxDist] the function returns
- * false. For convenience, the function returns the current values of sigmaT
- * and the albedo, as well as the 3D position 'ray.o+t*ray.d' upon
- * success.
- */
- bool invertDensityIntegral(Ray ray, Float maxDist,
- Float &accumulatedDensity, Float desiredDensity,
- Float ¤tSigmaT, Spectrum ¤tAlbedo,
- Point ¤tPoint) const {
- if (maxDist == 0)
- return std::numeric_limits::infinity();
- int nParts = (int) std::ceil(maxDist/m_stepSize);
- Float stepSize = maxDist/nParts;
- Vector fullIncrement = ray.d * stepSize,
- halfIncrement = fullIncrement * .5f;
- Float t = 0;
- int numRefines = 0;
- bool success = false;
- Float node1 = sigmaT(ray.o, ray.d);
-
- while (t <= maxDist) {
- Float node2 = sigmaT(ray.o + halfIncrement, ray.d);
- Float node3 = sigmaT(ray.o + fullIncrement, ray.d);
- const Float newDensity = accumulatedDensity +
- (node1+node2*4+node3) * (stepSize/6);
-
- if (newDensity > desiredDensity) {
- if (t+stepSize/2 <= maxDist) {
- /* Record the last "good" scattering event */
- success = true;
- if (EXPECT_TAKEN(node2 != 0)) {
- currentPoint = ray.o + halfIncrement;
- currentSigmaT = node2;
- } else if (node3 != 0 && t+stepSize <= maxDist) {
- currentPoint = ray.o + fullIncrement;
- currentSigmaT = node3;
- } else if (node1 != 0) {
- currentPoint = ray.o;
- currentSigmaT = node1;
- }
- }
-
- if (++numRefines > NUM_REFINES)
- break;
-
- stepSize *= .5f;
- fullIncrement = halfIncrement;
- halfIncrement *= .5f;
- continue;
- }
-
- if (ray.o+fullIncrement == ray.o) /* Unable to make forward progress - stop */
- break;
-
- accumulatedDensity = newDensity;
- node1 = node3;
- t += stepSize;
- ray.o += fullIncrement;
- }
-
- if (success)
- currentAlbedo = m_albedo->lookupSpectrum(currentPoint);
-
- return success;
- }
-
- /// Evaluate the transmittance along a ray segment
- Spectrum tau(const Ray &ray) const {
- return Spectrum(std::exp(-integrateDensity(ray)));
- }
-
- bool sampleDistance(const Ray &r, MediumSamplingRecord &mRec,
- Sampler *sampler) const {
- Ray ray(r(r.mint), r.d, 0.0f);
- Float distSurf = r.maxt - r.mint,
- desiredDensity = -std::log(1-sampler->next1D()),
- accumulatedDensity = 0.0f,
- currentSigmaT = 0.0f;
- Spectrum currentAlbedo(0.0f);
- int iterations = 0;
- bool inside = false, success = false;
- Float t = 0;
- Float sigmaTOrigin = sigmaT(ray.o, ray.d);
-
- while (rayIntersect(ray, t, inside)) {
- if (inside) {
- Point currentPoint(0.0f);
- success = invertDensityIntegral(ray, std::min(t, distSurf),
- accumulatedDensity, desiredDensity, currentSigmaT, currentAlbedo,
- currentPoint);
- if (success) {
- /* A medium interaction occurred */
- mRec.p = currentPoint;
- mRec.t = (mRec.p-r.o).length();
- success = true;
- break;
- }
- }
-
- distSurf -= t;
-
- if (distSurf < 0) {
- /* A surface interaction occurred */
- break;
- }
- ray.o = ray(t);
- ray.mint = Epsilon;
-
- if (++iterations > 100) {
- /// Just a precaution..
- Log(EWarn, "sampleDistance(): round-off error issues?");
- break;
- }
- }
- Float expVal = std::max(Epsilon, std::exp(-accumulatedDensity));
-
- if (success)
- mRec.orientation = lookupOrientation(mRec.p);
-
- mRec.pdfFailure = expVal;
- mRec.pdfSuccess = expVal * std::max((Float) Epsilon, currentSigmaT);
- mRec.pdfSuccessRev = expVal * std::max((Float) Epsilon, sigmaTOrigin);
- mRec.transmittance = Spectrum(expVal);
-
- if (!success)
- return false;
-
- mRec.sigmaS = currentAlbedo * currentSigmaT;
- mRec.sigmaA = Spectrum(currentSigmaT) - mRec.sigmaS;
- mRec.albedo = currentAlbedo.max();
- mRec.medium = this;
- return true;
- }
-
- void pdfDistance(const Ray &ray, Float t, MediumSamplingRecord &mRec) const {
- Float expVal = std::exp(-integrateDensity(Ray(ray, 0, t)));
-
- mRec.transmittance = Spectrum(expVal);
- mRec.pdfFailure = expVal;
- mRec.pdfSuccess = expVal * std::max((Float) Epsilon,
- sigmaT(ray(t), ray.d));
- mRec.pdfSuccessRev = expVal * std::max((Float) Epsilon,
- sigmaT(ray(ray.mint), ray.d));
- }
-
- std::string toString() const {
- std::ostringstream oss;
- oss << "HeterogeneousFlakeMedium[" << endl
- << " albedo=" << indent(m_albedo.toString()) << "," << endl
- << " orientations=" << indent(m_orientations.toString()) << "," << endl
- << " densities=" << indent(m_densities.toString()) << "," << endl
- << " stepSize=" << m_stepSize << "," << endl
- << " densityMultiplier=" << m_densityMultiplier << endl
- << "]";
- return oss.str();
- }
- MTS_DECLARE_CLASS()
-private:
- ref m_densities;
- ref m_albedo;
- ref m_orientations;
- ref m_kdTree;
- std::vector m_shapes;
- Float m_stepSize;
- /* Flake model-related */
- SHVector m_D, m_sigmaT;
- SHVector4D m_phaseExpansion;
- int m_samplingRecursions;
- EMode m_mode;
- Float m_exponent, m_normalization;
-};
-
-Spectrum FlakePhaseFunction::f(const PhaseFunctionQueryRecord &pRec) const {
- Frame frame(pRec.mRec.orientation);
- Vector wi = frame.toLocal(pRec.wi);
- Vector wo = frame.toLocal(pRec.wo);
-#if defined(FLAKE_EVAL_APPROXIMATION)
- /* Evaluate spherical harmonics representation - might be inaccurate */
- SHVector temp(m_phaseExpansion->getBands());
- m_phaseExpansion->lookup(wi, temp);
- Float result = std::max((Float) 0, temp.eval(wo));
- return Spectrum(result);
-#else
- /* Evaluate the real phase function */
- Float sigmaT = m_sigmaT.evalAzimuthallyInvariant(wi);
- Vector H = wi + wo;
- Float length = H.length();
-
- if (length == 0)
- return Spectrum(0.0f);
- else
- H /= length;
-
- Float result = m_distr(H) / (2*sigmaT);
- result *= m_normalization;
- return Spectrum(result);
-#endif
-}
-
-Spectrum FlakePhaseFunction::sample(PhaseFunctionQueryRecord &pRec,
- Float &_pdf, Sampler *sampler) const {
-#if defined(FLAKE_SAMPLE_UNIFORM)
- /* Uniform sampling */
- pRec.wo = squareToSphere(sampler->next2D());
- _pdf = 1/(4*M_PI);
- return f(pRec);
-#else
- Point2 sample(sampler->next2D());
- Frame frame(pRec.mRec.orientation);
- Vector wi = frame.toLocal(pRec.wi);
- /* Importance sampling using the interpolated phase function */
- SHVector &temp = m_temp.get();
- if (EXPECT_NOT_TAKEN(temp.getBands() == 0))
- temp = SHVector(m_phaseExpansion->getBands());
- m_phaseExpansion->lookup(wi, temp);
- _pdf = m_shSampler->warp(temp, sample);
-
- Vector wo = sphericalDirection(sample.x, sample.y);
- pRec.wo = frame.toWorld(wo);
- #if defined(FLAKE_EVAL_APPROXIMATION)
- return Spectrum(std::max((Float) 0, temp.eval(sample.x, sample.y)));
- #else
- return f(pRec);
- #endif
-#endif
-}
-
-Spectrum FlakePhaseFunction::sample(PhaseFunctionQueryRecord &pRec,
- Sampler *sampler) const {
- Float pdf;
- Spectrum result = FlakePhaseFunction::sample(pRec, pdf, sampler);
- if (!result.isZero())
- return result / pdf;
- else
- return Spectrum(0.0f);
-}
-
-Float FlakePhaseFunction::pdf(const PhaseFunctionQueryRecord &pRec) const {
-#if defined(FLAKE_SAMPLE_UNIFORM)
- return 1/(4*M_PI);
-#else
- Frame frame(pRec.mRec.orientation);
- Vector wi = frame.toLocal(pRec.wi);
- Vector wo = frame.toLocal(pRec.wo);
- SHVector temp(m_phaseExpansion->getBands());
- m_phaseExpansion->lookup(wi, temp);
-
- return std::max((Float) 0, temp.eval(wo));
-#endif
-}
-
-MTS_IMPLEMENT_CLASS_S(HeterogeneousFlakeMedium, false, Medium)
-MTS_IMPLEMENT_CLASS_S(FlakePhaseFunction, false, PhaseFunction)
-MTS_EXPORT_PLUGIN(HeterogeneousFlakeMedium, "Heterogeneous micro-flake medium");
-MTS_NAMESPACE_END
-
diff --git a/src/medium/heterogeneous.cpp b/src/medium/heterogeneous.cpp
index db1e3e3b..e93a2b5b 100644
--- a/src/medium/heterogeneous.cpp
+++ b/src/medium/heterogeneous.cpp
@@ -356,7 +356,7 @@ public:
x -= fx/dfx;
- if (x <= a || x >= b || dfx == 0)
+ if (EXPECT_NOT_TAKEN(x <= a || x >= b || dfx == 0))
x = 0.5f * (b + a);
/* Integrated version of the above Lagrange polynomial */
diff --git a/src/phase/microflake.cpp b/src/phase/microflake.cpp
index 6c3aeecf..0b428b80 100644
--- a/src/phase/microflake.cpp
+++ b/src/phase/microflake.cpp
@@ -16,16 +16,24 @@
along with this program. If not, see .
*/
+#include
#include
#include
-#include "microflake.h"
+#include "microflake_fiber.h"
MTS_NAMESPACE_BEGIN
class MicroflakePhaseFunction : public PhaseFunction {
public:
- MicroflakePhaseFunction(const Properties &props)
- : PhaseFunction(props) {
+ MicroflakePhaseFunction(const Properties &props) : PhaseFunction(props) {
+ m_fiberDistr = GaussianFiberDistribution(props.getFloat("stddev"));
+ ChiSquareTest test(7);
+ test.fill(
+ boost::bind(&GaussianFiberDistribution::sample, m_fiberDistr, _1),
+ boost::bind(&GaussianFiberDistribution::pdf, m_fiberDistr, _1)
+ );
+ test.dumpTables("test.m");
+ test.runTest(1);
}
MicroflakePhaseFunction(Stream *stream, InstanceManager *manager)
@@ -60,10 +68,16 @@ public:
}
std::string toString() const {
- return "MicroflakePhaseFunction[]";
+ std::ostringstream oss;
+ oss << "MicroflakePhaseFunction[" << endl
+ << " fiberDistr = " << indent(m_fiberDistr.toString()) << endl
+ << "]";
+ return oss.str();
}
MTS_DECLARE_CLASS()
+private:
+ GaussianFiberDistribution m_fiberDistr;
};
diff --git a/src/phase/microflake.h b/src/phase/microflake_fiber.h
similarity index 88%
rename from src/phase/microflake.h
rename to src/phase/microflake_fiber.h
index 05d8a033..dfd3f8ff 100644
--- a/src/phase/microflake.h
+++ b/src/phase/microflake_fiber.h
@@ -16,20 +16,30 @@
along with this program. If not, see .
*/
-#if !defined(__MICROFLAKE_SIGMA_T_H)
-#define __MICROFLAKE_SIGMA_T_H
+#if !defined(__MICROFLAKE_FIBER_DIST_H)
+#define __MICROFLAKE_FIBER_DIST_H
#include
#include
-#include
#include
+#include
MTS_NAMESPACE_BEGIN
-#define SIGMA_T_RANGE_MIN 4e-08
-#define SIGMA_T_RANGE_MAX 4
-#define SIGMA_T_ELEMENTS 100
-#define SIGMA_T_COEFFS 10
+FINLINE Float mts_erf(Float arg) {
+ #if defined(__GNUC__) && defined(SINGLE_PRECISION)
+ return erff(arg);
+ #elif defined(__GNUC__) && defined(DOUBLE_PRECISION)
+ return erf(arg);
+ #else
+ return boost::math::erf(arg);
+ #endif
+}
+
+#define FIBERDIST_STDDEV_MIN 4e-08
+#define FIBERDIST_STDDEV_MAX 4
+#define FIBERDIST_SIGMA_T_ELEMENTS 100
+#define FIBERDIST_SIGMA_T_COEFFS 10
/**
* Each row of this table table stores an expansion of \sigma_t in terms of
@@ -37,11 +47,11 @@ MTS_NAMESPACE_BEGIN
* fiber axis and the incident direction. The standard deviation of the
* underlying distribution increases from row to row using a nonlinear
* mapping to capture the `interesting' parts more efficiently (see
- * \ref sigmaT_fiberGaussian())
+ * \ref sigmaT_fiberSigmaT())
*
* The implementation here has an average absolute error of 3.17534e-05.
*/
-const double fiberGaussianCoeffs[SIGMA_T_ELEMENTS][SIGMA_T_COEFFS] = {
+const double fiberSigmaTCoeffs[FIBERDIST_SIGMA_T_ELEMENTS][FIBERDIST_SIGMA_T_COEFFS] = {
{ 2.5037347588631811e-08, 6.3661860705430207e-01, 1.7827351092236654e-05, -1.3015084365974872e-04, 5.2848718115683369e-04, -1.2810825925271274e-03, 1.8970200528656278e-03, -1.6818874867112754e-03, 8.1991128831759852e-04, -1.6898472595983094e-04 },
{ 4.0059757909494120e-07, 6.3660112034736627e-01, 2.8523763553955916e-04, -2.0824136415171779e-03, 8.4557955473769653e-03, -2.0497323170502568e-02, 3.0352323203715059e-02, -2.6910202150020268e-02, 1.3118581833396092e-02, -2.7037558688078889e-03 },
{ 2.0280252492606374e-06, 6.3652534713591225e-01, 1.4440155345667449e-03, -1.0542219099960448e-02, 4.2807465147063795e-02, -1.0376769900017280e-01, 1.5365863698661997e-01, -1.3623289910560743e-01, 6.6412820861387445e-02, -1.3687764171763206e-02 },
@@ -144,27 +154,27 @@ const double fiberGaussianCoeffs[SIGMA_T_ELEMENTS][SIGMA_T_COEFFS] = {
{ 4.9740122861160618e-01, 4.9788891152147130e-06, 3.7427454517739989e-03, 1.2662117029549336e-03, -3.7139444921194809e-03, 2.9874687143092160e-03, 6.6568975580594270e-03, -1.6932579956119298e-02, 1.3976501563774946e-02, -4.0922961870819563e-03 }
};
-double sigmaT_fiberGaussian(double stddev, double sinTheta) {
+double sigmaT_fiberDist(double stddev, double sinTheta) {
/* Undo the nonlinear mapping */
- const double factor = SIGMA_T_ELEMENTS / std::pow(SIGMA_T_RANGE_MAX, 0.25);
+ const double factor = FIBERDIST_SIGMA_T_ELEMENTS / std::pow(FIBERDIST_STDDEV_MAX, 0.25);
double pos = std::pow(stddev, 0.25) * factor - 1;
/* Clamp to the supported parameter range */
- pos = std::min(std::max(0.0, pos), (double) (SIGMA_T_ELEMENTS-1));
+ pos = std::min(std::max(0.0, pos), (double) (FIBERDIST_SIGMA_T_ELEMENTS-1));
/* Linearly interpolate coefficients using \c alpha */
int idx0 = (int) std::floor(pos), idx1 = (int) std::ceil(pos);
double alpha = pos - idx0, base = 1, result = 0;
SAssert(
- idx0 >= 0 && idx0 < SIGMA_T_ELEMENTS &&
- idx1 >= 0 && idx1 < SIGMA_T_ELEMENTS
+ idx0 >= 0 && idx0 < FIBERDIST_SIGMA_T_ELEMENTS &&
+ idx1 >= 0 && idx1 < FIBERDIST_SIGMA_T_ELEMENTS
);
- /* Compute the expansion */
- for (int i=0; i(1/(SQRT_TWO * m_stddev)));
- m_c1 = 1.0f/boost::math::erf(1/(SQRT_TWO * m_stddev));
+ m_normalization = 1/(std::pow(2*M_PI, 3.0f/2.0f) * m_stddev *
+ mts_erf(1/(SQRT_TWO * m_stddev)));
+ m_c1 = 1.0f/mts_erf(1/(SQRT_TWO * m_stddev));
+
+ if (stddev < FIBERDIST_STDDEV_MIN || stddev > FIBERDIST_STDDEV_MAX)
+ SLog(EError, "Standard deviation parameter is out of range (must "
+ "be in [%f, %f])!", FIBERDIST_STDDEV_MIN, FIBERDIST_STDDEV_MAX);
+
+ /* Determine expansion coefficients of sigma_t for a fixed stddev */
+ Float pos = std::pow(stddev / FIBERDIST_STDDEV_MAX, 0.25) *
+ FIBERDIST_SIGMA_T_ELEMENTS - 1;
+
+ pos = std::min(std::max((Float) 0, pos), (Float) (FIBERDIST_SIGMA_T_ELEMENTS-1));
+
+ int idx0 = (int) std::floor(pos), idx1 = (int) std::ceil(pos);
+ Float alpha = pos - idx0;
+
+ for (int i=0; i(std::cos(theta)/(SQRT_TWO * m_stddev))*m_c1);
+ /// Evaluate the density as a function of direction
+ inline Float pdf(const Vector &d) const {
+ return std::exp(-d.z*d.z
+ / (2*m_stddev*m_stddev)) * m_normalization;
}
/**
- * Apply the inversion method to sample \theta given
- * a uniformly distributed \xi on [0, 1]
+ * \brief Apply the inversion method to sample \cos\theta given
+ * a uniformly distributed r.v. \xi on [0, 1]
*/
- Float sample(Float xi) const {
+ Vector sample(const Point2 &sample) const {
BrentSolver brentSolver(100, 1e-6f);
BrentSolver::Result result = brentSolver.solve(
- boost::bind(&GaussianFiberDistribution::cdfFunctor, this, xi, _1), 0, M_PI);
+ boost::bind(&GaussianFiberDistribution::cdfFunctor,
+ this, sample.x, _1), -1, 1);
SAssert(result.success);
avgBrentFunEvals.incrementBase();
++brentSolves;
- return result.x;
- }
-private:
- Float cdfFunctor(Float theta, Float xi) const {
- ++avgBrentFunEvals;
- return cdf(theta)-xi;
+
+ Float cosTheta = result.x,
+ sinTheta = std::sqrt(std::max((Float) 0, 1-cosTheta*cosTheta)),
+ phi = 2 * M_PI * sample.y,
+ sinPhi = std::sin(phi), cosPhi = std::cos(phi);
+
+ return Vector(sinTheta * cosPhi, sinTheta * sinPhi, cosTheta);
}
+ std::string toString() const {
+ std::ostringstream oss;
+ oss << "GaussianFiberDistribution[stddev="
+ << m_stddev << "]";
+ return oss.str();
+ }
+protected:
+ /// Evaluate the longitudinal CDF as a function of \cos\theta
+ inline Float cdf(Float cosTheta) const {
+ return 0.5f * (1.0f -
+ mts_erf(cosTheta / (SQRT_TWO * m_stddev)) * m_c1);
+ }
+
+ Float cdfFunctor(Float xi, Float cosTheta) const {
+ ++avgBrentFunEvals;
+ return cdf(cosTheta)-xi;
+ }
+
+protected:
Float m_stddev;
Float m_normalization;
Float m_c1;
+ Float m_coeffs[FIBERDIST_SIGMA_T_COEFFS];
};
-
MTS_NAMESPACE_END
-#endif /* __MICROFLAKE_SIGMA_T_H */
+#endif /* __MICROFLAKE_FIBER_DIST_H */