documentation updates, the chi-square test now also handles delta components, addes support for loading interpolated color spectra from disk

metadata
Wenzel Jakob 2011-07-05 13:24:22 +02:00
parent 54fb516737
commit 4a6d69df32
10 changed files with 285 additions and 109 deletions

View File

@ -2,7 +2,7 @@
Mitsuba uses a very simple and general XML-based format to represent scenes.
Since the framework's philosophy is to represent discrete blocks of functionality as plugins,
a scene file can essentially be interpreted as description that determines which
plugins should be instantiated and how they should be interface with each other.
plugins should be instantiated and how they should interface with each other.
In the following, we'll look at a few examples to get a feeling for the scope of the
format.
@ -21,15 +21,15 @@ create the scene. This information allows Mitsuba to always correctly process th
file irregardless of any potential future changes in the scene description language.
This example already contains the most important things to know about format: you can have
\emph{objects} (such as the objects instantiated by the \code{scene} or \code{shape} tags), which are allowed to be nested within
each other. Each object optionally accepts \emph{properties} (such as the \code{string} tag),
which further characterize its behavior. All objects except for the root object (the \code{scene})
cause the renderer to load and instantiate a plugin, hence you must provide the plugin name using
\code{type=".."} parameter.
\emph{objects} (such as the objects instantiated by the \code{scene} or \code{shape} tags),
which are allowed to be nested within each other. Each object optionally accepts \emph{properties}
(such as the \code{string} tag), which further characterize its behavior. All objects except
for the root object (the \code{scene}) cause the renderer to search and load a plugin from disk,
hence you must provide the plugin name using \code{type=".."} parameter.
The object tags also let the renderer know \emph{what kind} of object is to be instantiated: for instance,
any plugin loaded using the \code{shape} tag must conform to the \emph{Shape} interface, which
the certainly case for the plugin named \code{obj} (it contains a WaveFront OBJ loader).
any plugin loaded using the \code{shape} tag must conform to the \emph{Shape} interface, which is
certainly the case for the plugin named \code{obj} (it contains a WaveFront OBJ loader).
Similarly, you could write
\begin{xml}
<?xml version="1.0" encoding="utf-8"?>
@ -41,16 +41,20 @@ Similarly, you could write
\end{xml}
This loads a different plugin (\code{sphere}) which is still a \emph{Shape}, but instead represents
a sphere configured with a radius of 10 world-space units. Mitsuba ships with
a large number of plugins; please refer to the next chapter for a reference.
a large number of plugins; please refer to the next chapter for a detailed
overview of them.
The most common scene setup is to declare an integrator, some geometry, a camera, a film, a sampler
and one or more luminaires. Here is a more complex example:
\begin{xml}
<?xml version="1.0" encoding="utf-8"?>
<scene version=$\MtsVer$>
<integrator type="path"> <!-- Path trace an 8-bounce GI solution -->
<integrator type="path">
<!-- Path trace with a max. path length of 8 -->
<integer name="maxDepth" value="8"/>
</integrator>
<!-- Instantiate a perspective camera with 45 degrees field of view -->
<camera type="perspective">
<!-- Rotate the camera around the Y axis by 180 degrees -->
@ -72,17 +76,18 @@ and one or more luminaires. Here is a more complex example:
</film>
</camera>
<!-- Add a dragon mesh made of rough glass (stored as OBJ) -->
<!-- Add a dragon mesh made of rough glass (stored as OBJ file) -->
<shape type="obj">
<string name="filename" value="dragon.obj"/>
<bsdf type="roughglass">
<bsdf type="roughdielectric">
<!-- Tweak the roughness parameter of the material -->
<float name="alphaB" value="0.01"/>
<float name="alpha" value="0.01"/>
</bsdf>
</shape>
<!-- Add a mesh stored using a more compact representation -->
<!-- Add another mesh -- this time, stored using Mitsuba's own
(compact) binary representation -->
<shape type="serialized">
<string name="filename" value="lightsource.serialized"/>
<transform name="toWorld">
@ -134,18 +139,41 @@ these values are directly used. The renderer can also be configured to sample th
number of samples, in which case the RGB values are first converted into spectra using a simple heuristic.
You can also directly supply the spectral color samples that Mitsuba internally uses if spectral rendering is
active. This unfortunately closely couples the interpretation of a scene to how Mitsuba is compiled, which can be a disadvantage.
For instance, the below example assumes that 6 spectral samples are being used:
active. This unfortunately closely couples the interpretation of a scene to how Mitsuba is compiled, which
can be a disadvantage.
For instance, the below example assumes that 6 spectral samples in the 400-700nm range are being used:
\begin{xml}
<spectrum name="spectrumProperty" value="0.2, 0.8, 0.4, 0.6, 0.1, 0.9"/>
\end{xml}
A safer way is to specify a linearly interpolated spectral power distribution, which is converted into
the internal spectral representation as the scene is loaded.
the internal spectral representation when the scene is loaded.
\begin{xml}
<spectrum name="spectrumProperty" value="400:0.56, 500:0.18, 600:0.58, 700:0.24"/>
\end{xml}
This is essentially a mapping from wavelength in nanometers (before the colon) to a reflectance or intensity value (after the colon). Missing values are linearly interpolated from the two closest neighbors.
To specify a constant spectrum, simply provide just one value
This is essentially a mapping from wavelength in nanometers (before the colon) to a reflectance or
intensity value (after the colon). Missing values are linearly interpolated from the two closest neighbors.
When spectral power distributions are obtained from measurements (e.g. at 10nm intervals), they are
usually quite unwiedy and can clutter the scene description. For this reason,
there is yet another way to pass a spectrum by loading it from an external
file:
\begin{xml}
<spectrum name="spectrumProperty" filename="measuredSpectrum.spd"/>
\end{xml}
The file should contain a single measurement per line, with the corresponding
wavelength in nanometers and the measured value separated by a space. Here is
an example:
\begin{xml}
406.13 0.703313
413.88 0.744563
422.03 0.791625
430.62 0.822125
435.09 0.834000
...
\end{xml}
It is also possible to specify a completely flat spectral power distribution by specifying
just one value:
\begin{xml}
<spectrum name="spectrumProperty" value="0.56"/>
\end{xml}
@ -163,8 +191,8 @@ Points and vectors can be specified as follows:
It is important that whatever you choose as world-space units (meters, inches, etc.) is
used consistently in all places.
\subsubsection{Transformations}
Transformations are the only kind of property, which require more than a single tag. The idea is that, starting
with the identity, you build up a transformation using nested commands. For instance, a transformation that
Transformations are the only kind of property that require more than a single tag. The idea is that, starting
with the identity, one can build up a transformation using a sequence of commands. For instance, a transformation that
does a translation followed by a rotation might be written like this:
\begin{xml}
<transform name="trafoProperty">
@ -211,9 +239,9 @@ of how this works:
<string name="filename" value="textures/myImage.jpg"/>
</texture>
<bsdf type="lambertian" id="myMaterial">
<bsdf type="diffuse" id="myMaterial">
<!-- Reference the texture named myImage and pass it
to the BRDF as the reflectance channel -->
to the BRDF as the reflectance parameter -->
<ref name="reflectance" id="myImage"/>
</bsdf>
@ -225,14 +253,28 @@ of how this works:
</shape>
</scene>
\end{xml}
Note that this feature cannot yet be used to do geometry instancing.
By providing a unique \texttt{id} attribute in the
object declaration, the object is bound to that identifier
upon instantiation.
Referencing this identifier at a later point (using the \texttt{<ref id="..."/>} tag)
will add the instance to the parent object, with no further memory
allocation taking place. Note that some plugins expect their child objects
to be named\footnote{For instance, material plugins such as \pluginref{diffuse} require that
nested texture instances explicitly specify the parameter to which they want to bind (e.g. ``\texttt{reflectance}'').}.
For this reason, a name can also be associated with the reference.
Note that while this feature is meant to efficiently handle materials,
textures, and participating media that are referenced from multiple places,
it cannot be used to instantiate geometry---if this functionality is needed,
take a look at the \pluginref{instance} plugin.
\subsection{Including external files}
A scene can be split into multiple pieces for better readability.
to include an external file, please use the following command:
\begin{xml}
<include filename="nested-scene.xml"/>
\end{xml}
In this case, the file \code{nested-scene.xml} must still be a proper scene file with a \code{<scene>} tag at the root.
In this case, the file \code{nested-scene.xml} must be a proper scene file with a \code{<scene>} tag at the root.
This feature is sometimes very convenient in conjunction with the \code{-D key=value} flag of the \code{mitsuba} command line renderer (see the previous section for details).
This lets you include different parts of a scene configuration by changing the command line parameters (and without having to touch the XML file):
\begin{xml}

View File

@ -1,6 +1,6 @@
\section{Introduction}
\textbf{Disclaimer:} This is manual is currently a work in progress --
please bear with me as things are coming together.
\textbf{Disclaimer:} This is manual is currently a work in progress---please
bear with me while things are coming together.
\subsection{About Mitsuba}
Mitsuba is an extensible rendering framework written in portable C++. It implements unbiased
@ -11,5 +11,40 @@ In comparison to other open source renderers, Mitsuba places a strong emphasis o
rendering techniques, such as path-based formulations of Metropolis Light Transport and volumetric
modeling approaches.
\paragraph{Performance:}
One important goal of Mitsuba is to provide optimized implementations of the most commonly
used rendering algorithms. By virtue of running on a shared foundation, comparisons between them can
better highlight the merits and limitations of different approaches. This is in contrast to, say,
comparing two completely different rendering products, where technical information on the underlying
implementation is often intentionally not provided.
\paragraph{Robustness:}
In many cases, physically-based rendering packages force the user to model scenes with the underlying
algorithm (specifically: its convergence behavior) in mind. For instance, glass windows are routinely
replaced with light portals, photons must be manually guided to the relevant parts of a scene, and
interactions with complex materials are taboo, since they cannot be importance sampled exactly.
One focus of Mitsuba will be to develop path-space light transport algorithms, which handle such
cases more gracefully.
\paragraph{Scalability:} Mitsuba instances can be merged into large clusters, which transparently distribute and
jointly execute tasks assigned to them using only node-to-node communcation. It has successfully
scaled to large-scale renderings that involved 1024 cores working on a single image.
Most algorithms in Mitsuba are written using a generic parallelization layer, which can tap
into this cluster-wide parallelism. The principle is that if any component of the renderer produces
work that takes longer than a second or so, it at least ought to use all of the processing power
it can get.
The renderer also tries to be very conservative in its use of memory, which lets it handle
large scenes (>30 million triangles) and multi-gigabyte heterogeneous volumes on consumer hardware.
\paragraph{Usability:}
Mitsuba comes with a graphical user interface to interactively explore scenes. Once a suitable
viewpoint has been found, it is straightforward to perform renderings using any of the
implemented rendering techniques, while tweaking their parameters to find the most suitable
settings. Experimental integration into Blender 2.5 is also available.
\subsection{License}
Mitsuba is free software and can be redistributed and modified under the terms of the GNU General Public License (Version 3) as provided by the Free Software Foundation.
Mitsuba is free software and can be redistributed and modified under the terms of the GNU General
Public License (Version 3) as provided by the Free Software Foundation.

View File

@ -101,7 +101,9 @@
keywords= [1] {
shape,bsdf,scene,texture,phase,integer,float,
string,transform,ref,rgb,srgb,spectrum,blackbody,
medium,camera,film,sampler,integrator
medium,camera,film,sampler,integrator,luminaire,
translate,rotate,scale,lookAt,point,vector,matrix,
include
},
}
@ -140,7 +142,7 @@
\include{introduction}
%\include{compiling}
%\include{basics}
%\include{format}
\include{format}
\IfFileExists{plugins_generated.tex}{\include{plugins_generated}}{}
%\include{import}
%\include{development}

View File

@ -20,6 +20,8 @@
#define __CHI_SQUARE_TEST_H
#include <mitsuba/core/fresolver.h>
#include <mitsuba/render/common.h>
#include <boost/tuple/tuple.hpp>
#include <boost/function.hpp>
MTS_NAMESPACE_BEGIN
@ -33,7 +35,8 @@ MTS_NAMESPACE_BEGIN
* 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.
* and phase function sampling codes for their correctness. Currently, it
* supports both 2D and discrete sampling methods and mixtures thereof.
*
* This implementation works by generating a large batch of samples, which are
* then accumulated into rectangular bins in spherical coordinates. To obtain
@ -48,10 +51,10 @@ MTS_NAMESPACE_BEGIN
* // 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;
* boost::tuple<Vector, Float, EMeasure> generateSample() const;
*
* /// Compute the probability density for the specified direction
* Float pdf(const Vector &direction) const;
* /// Compute the probability density for the specified direction and measure
* Float pdf(const Vector &direction, EMeasure) const;
* };
* </code>
*
@ -64,14 +67,13 @@ MTS_NAMESPACE_BEGIN
* // Initialize the tables used by the chi-square test
* chiSqr.fill(
* boost::bind(&MyDistribution::generateSample, myDistrInstance),
* boost::bind(&MyDistribution::pdf, myDistrInstance, _1)
* boost::bind(&MyDistribution::pdf, myDistrInstance, _1, _2)
* );
*
* // Optional: dump the tables to a MATLAB file for external analysis
* chiSqr.dumpTables("debug.m");
*
* // (the following assumes that the distribution has 1 parameter, e.g. exponent value)
* if (!chiSqr.runTest(1))
* if (!chiSqr.runTest())
* Log(EError, "Uh oh -- test failed, the implementation is probably incorrect!");
* </code>
*/
@ -139,8 +141,8 @@ public:
* on how to invoke this function
*/
void fill(
const boost::function<std::pair<Vector, Float>()> &sampleFn,
const boost::function<Float (const Vector &)> &pdfFn);
const boost::function<boost::tuple<Vector, Float, EMeasure>()> &sampleFn,
const boost::function<Float (const Vector &, EMeasure)> &pdfFn);
/**
* \brief Dump the bin counts to a file using MATLAB format
@ -150,11 +152,6 @@ public:
/**
* \brief Perform the actual chi-square test
*
* \param distParams
* Number of parameters of the distribution in question.
* Anything such as lobe width, indices of refraction, etc.
* should be counted.
*
* \param pvalThresh
* The implementation will reject the null hypothesis
* when the computed p-value lies below this parameter
@ -162,7 +159,7 @@ public:
*
* \return A status value of type \ref ETestResult
*/
ETestResult runTest(int distParams, Float pvalThresh = 0.01f);
ETestResult runTest(Float pvalThresh = 0.01f);
MTS_DECLARE_CLASS()
protected:
@ -171,11 +168,12 @@ protected:
/// Functor to evaluate the pdf values in parallel using OpenMP
static void integrand(
const boost::function<Float (const Vector &)> &pdfFn,
const boost::function<Float (const Vector &, EMeasure)> &pdfFn,
size_t nPts, const Float *in, Float *out) {
#pragma omp parallel for
for (int i=0; i<(int) nPts; ++i)
out[i] = pdfFn(sphericalDirection(in[2*i], in[2*i+1])) * std::sin(in[2*i]);
out[i] = pdfFn(sphericalDirection(in[2*i], in[2*i+1]), ESolidAngle)
* std::sin(in[2*i]);
}
private:
ELogLevel m_logLevel;

View File

@ -20,6 +20,9 @@
#define __SPECTRUM_H
#include <mitsuba/mitsuba.h>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
#define SPECTRUM_MIN_WAVELENGTH 400
#define SPECTRUM_MAX_WAVELENGTH 700
@ -76,6 +79,10 @@ private:
/**
* \brief Linearly interpolated spectral power distribution
*
* This class implements a linearly interpolated spectral
* power distribution that is defined over a discrete set of
* measurements at different wavelengths.
*
* \ingroup libcore
*/
class MTS_EXPORT_CORE InterpolatedSpectrum : public SmoothSpectrum {
@ -89,6 +96,12 @@ public:
m_value.reserve(size);
}
/**
* Read an interpolated spectrum from a simple
* ASCII format
*/
InterpolatedSpectrum(const fs::path &path);
/**
* \brief Append an entry to the spectral power distribution.
*

View File

@ -21,7 +21,7 @@
<xsd:element name="boolean" type="boolean"/>
<xsd:element name="transform" type="transform"/>
<xsd:element name="string" type="string"/>
<xsd:element name="spectrum" type="string"/>
<xsd:element name="spectrum" type="spectrum"/>
<xsd:element name="rgb" type="string"/>
<xsd:element name="srgb" type="string"/>
<xsd:element name="blackbody" type="blackbody"/>
@ -41,7 +41,7 @@
<xsd:element name="boolean" type="boolean"/>
<xsd:element name="transform" type="transform"/>
<xsd:element name="string" type="string"/>
<xsd:element name="spectrum" type="string"/>
<xsd:element name="spectrum" type="spectrum"/>
<xsd:element name="rgb" type="string"/>
<xsd:element name="srgb" type="string"/>
<xsd:element name="blackbody" type="blackbody"/>
@ -276,6 +276,12 @@
<xsd:attribute name="value" type="xsd:string" use="required"/>
</xsd:complexType>
<xsd:complexType name="spectrum">
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="filename" type="xsd:string" use="optional"/>
<xsd:attribute name="value" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:complexType name="include">
<xsd:attribute name="filename" type="xsd:string" use="required"/>
</xsd:complexType>

View File

@ -21,8 +21,26 @@
#include <mitsuba/core/timer.h>
#include <boost/math/distributions/chi_squared.hpp>
#include <boost/bind.hpp>
#include <set>
MTS_NAMESPACE_BEGIN
/* Simple ordering for storing vectors in a set */
struct VectorOrder {
inline int compare(const Vector &v1, const Vector &v2) const {
if (v1.x < v2.x) return -1;
else if (v1.x > v2.x) return 1;
if (v1.y < v2.y) return -1;
else if (v1.y > v2.y) return 1;
if (v1.z < v2.z) return -1;
else if (v1.z > v2.z) return 1;
return 0;
}
bool operator()(const Vector &v1, const Vector &v2) const {
return compare(v1, v2) < 0;
}
};
ChiSquare::ChiSquare(int thetaBins, int phiBins, int numTests,
size_t sampleCount) : m_logLevel(EInfo), m_thetaBins(thetaBins),
@ -69,29 +87,53 @@ void ChiSquare::dumpTables(const fs::path &filename) {
}
void ChiSquare::fill(
const boost::function<std::pair<Vector, Float>()> &sampleFn,
const boost::function<Float (const Vector &)> &pdfFn) {
const boost::function<boost::tuple<Vector, Float, EMeasure>()> &sampleFn,
const boost::function<Float (const Vector &, EMeasure measure)> &pdfFn) {
memset(m_table, 0, m_thetaBins*m_phiBins*sizeof(Float));
memset(m_refTable, 0, m_thetaBins*m_phiBins*sizeof(Float));
Log(m_logLevel, "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));
std::set<Vector, VectorOrder> discreteDirections;
ref<Timer> timer = new Timer();
for (size_t i=0; i<m_sampleCount; ++i) {
std::pair<Vector, Float> sample = sampleFn();
Point2 sphCoords = toSphericalCoordinates(sample.first);
boost::tuple<Vector, Float, EMeasure> sample = sampleFn();
Point2 sphCoords = toSphericalCoordinates(boost::get<0>(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] += sample.second;
m_table[thetaBin * m_phiBins + phiBin] += boost::get<1>(sample);
if (boost::get<1>(sample) > 0 && boost::get<2>(sample) == EDiscrete)
discreteDirections.insert(boost::get<0>(sample));
}
if (discreteDirections.size() > 0) {
Log(EDebug, "Incorporating the disrete density over "
SIZE_T_FMT " direction(s) into the contingency table", discreteDirections.size());
for (std::set<Vector, VectorOrder>::const_iterator it = discreteDirections.begin();
it != discreteDirections.end(); ++it) {
const Vector &direction = *it;
Point2 sphCoords = toSphericalCoordinates(direction);
Float pdf = pdfFn(direction, EDiscrete);
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_refTable[thetaBin * m_phiBins + phiBin] += pdf * m_sampleCount;
}
}
factor = Point2(M_PI / m_thetaBins, (2*M_PI) / m_phiBins);
Log(m_logLevel, "Done, took %i ms. Integrating reference contingency table ..", timer->getMilliseconds());
Log(m_logLevel, "Done, took %i ms. Integrating reference "
"contingency table ..", timer->getMilliseconds());
timer->reset();
Float min[2], max[2];
size_t idx = 0;
@ -113,10 +155,11 @@ void ChiSquare::fill(
);
integral += result;
m_refTable[idx++] = result * m_sampleCount;
m_refTable[idx++] += result * m_sampleCount;
maxError = std::max(maxError, error);
}
}
Log(m_logLevel, "Done, took %i ms (max error = %f, integral=%f).",
timer->getMilliseconds(), maxError, integral);
}
@ -132,7 +175,7 @@ struct SortedCellFunctor {
}
};
ChiSquare::ETestResult ChiSquare::runTest(int distParams, Float pvalThresh) {
ChiSquare::ETestResult ChiSquare::runTest(Float pvalThresh) {
/* Compute the chi-square statistic */
Float pooledCounts = 0, pooledRef = 0, chsq = 0.0f;
int pooledCells = 0, df = 0;
@ -188,12 +231,14 @@ ChiSquare::ETestResult ChiSquare::runTest(int distParams, Float pvalThresh) {
++df;
}
df -= distParams + 1;
/* All parameters are assumed to be known, so there is no
DF reduction due to model parameters */
df -= 1;
Log(m_logLevel, "Chi-square statistic = %e (df=%i)", chsq, df);
if (df <= 0) {
Log(EWarn, "The number of degrees of freedom (%i) is too low!", df);
Log(m_logLevel, "The number of degrees of freedom (%i) is too low!", df);
return ELowDoF;
}

View File

@ -17,6 +17,7 @@
*/
#include <mitsuba/mitsuba.h>
#include <boost/filesystem/fstream.hpp>
MTS_NAMESPACE_BEGIN
@ -47,7 +48,7 @@ void Spectrum::staticInitialization() {
}
void Spectrum::staticShutdown() {
/* Do nothing */
/* Nothing to do */
}
void Spectrum::fromSmoothSpectrum(const SmoothSpectrum *smooth) {
@ -260,9 +261,24 @@ Float BlackBodySpectrum::eval(Float l) const {
/ (1e9*1e4*(std::exp((h/k)*c/(lambda*m_temperature)) - 1.0));
return (Float) I;
}
InterpolatedSpectrum::InterpolatedSpectrum(const fs::path &path) {
fs::ifstream is(path);
if (is.bad() || is.fail())
SLog(EError, "InterpolatedSpectrum: could not open \"%s\"",
path.file_string().c_str());
while (is.good() && !is.eof()) {
Float lambda, value;
is >> lambda >> value;
appendSample(lambda, value);
}
}
void InterpolatedSpectrum::appendSample(Float lambda, Float value) {
SAssert(m_wavelength.size() == 0 || m_wavelength[m_wavelength.size()-1] < lambda);
if (m_wavelength.size() != 0 && m_wavelength[m_wavelength.size()-1] >= lambda)
SLog(EError, "InterpolatedSpectrum: spectral power distribution values must "
"be provided in order of increasing wavelength!");
m_wavelength.push_back(lambda);
m_value.push_back(value);
}

View File

@ -344,36 +344,50 @@ void SceneHandler::endElement(const XMLCh* const xmlName) {
discrete.fromSmoothSpectrum(&bb);
context.parent->properties.setSpectrum(context.attributes["name"], discrete);
} else if (name == "spectrum") {
std::vector<std::string> tokens = tokenize(
context.attributes["value"], ", ");
Float value[SPECTRUM_SAMPLES];
if (tokens.size() == 1) {
value[0] = parseFloat(name, tokens[0]);
context.parent->properties.setSpectrum(context.attributes["name"],
Spectrum(value[0]));
} else {
if (tokens[0].find(':') != std::string::npos) {
InterpolatedSpectrum interp(tokens.size());
/* Wavelength -> Value mapping */
for (size_t i=0; i<tokens.size(); i++) {
std::vector<std::string> tokens2 = tokenize(tokens[i], ":");
if (tokens2.size() != 2)
XMLLog(EError, "Invalid spectrum->value mapping specified");
Float wavelength = parseFloat(name, tokens2[0]);
Float value = parseFloat(name, tokens2[1]);
interp.appendSample(wavelength, value);
}
Spectrum discrete;
discrete.fromSmoothSpectrum(&interp);
bool hasValue = context.attributes.find("value") != context.attributes.end();
bool hasFilename = context.attributes.find("filename") != context.attributes.end();
if (hasValue == hasFilename) {
SLog(EError, "Spectrum: please provide one of 'value' or 'filename'");
} else if (hasFilename) {
FileResolver *resolver = Thread::getThread()->getFileResolver();
fs::path path = resolver->resolve(context.attributes["filename"]);
InterpolatedSpectrum interp(path);
Spectrum discrete;
discrete.fromSmoothSpectrum(&interp);
context.parent->properties.setSpectrum(context.attributes["name"], discrete);
} else if (hasValue) {
std::vector<std::string> tokens = tokenize(
context.attributes["value"], ", ");
Float value[SPECTRUM_SAMPLES];
if (tokens.size() == 1) {
value[0] = parseFloat(name, tokens[0]);
context.parent->properties.setSpectrum(context.attributes["name"],
discrete);
Spectrum(value[0]));
} else {
if (tokens.size() != SPECTRUM_SAMPLES)
XMLLog(EError, "Invalid spectrum value specified (incorrect length)");
for (int i=0; i<SPECTRUM_SAMPLES; i++)
value[i] = parseFloat(name, tokens[i]);
context.parent->properties.setSpectrum(context.attributes["name"],
Spectrum(value));
if (tokens[0].find(':') != std::string::npos) {
InterpolatedSpectrum interp(tokens.size());
/* Wavelength -> Value mapping */
for (size_t i=0; i<tokens.size(); i++) {
std::vector<std::string> tokens2 = tokenize(tokens[i], ":");
if (tokens2.size() != 2)
XMLLog(EError, "Invalid spectrum->value mapping specified");
Float wavelength = parseFloat(name, tokens2[0]);
Float value = parseFloat(name, tokens2[1]);
interp.appendSample(wavelength, value);
}
Spectrum discrete;
discrete.fromSmoothSpectrum(&interp);
context.parent->properties.setSpectrum(context.attributes["name"],
discrete);
} else {
if (tokens.size() != SPECTRUM_SAMPLES)
XMLLog(EError, "Invalid spectrum value specified (incorrect length)");
for (int i=0; i<SPECTRUM_SAMPLES; i++)
value[i] = parseFloat(name, tokens[i]);
context.parent->properties.setSpectrum(context.attributes["name"],
Spectrum(value));
}
}
}
} else if (name == "transform") {

View File

@ -101,7 +101,7 @@ public:
m_fakeSampler = new FakeSampler(m_sampler);
}
std::pair<Vector, Float> generateSample() {
boost::tuple<Vector, Float, EMeasure> generateSample() {
Point2 sample(m_sampler->next2D());
Intersection its;
BSDFQueryRecord bRec(its);
@ -136,7 +136,7 @@ public:
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
#endif
return std::make_pair(bRec.wo, 0.0f);
return boost::make_tuple(bRec.wo, 0.0f, ESolidAngle);
} else if (sampled.isZero()) {
if (!f.isZero() && pdfVal != 0)
Log(EWarn, "Inconsistency (2): f=%s, pdf=%f, sampled f/pdf=%s, bRec=%s",
@ -144,14 +144,14 @@ public:
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
#endif
return std::make_pair(bRec.wo, 0.0f);
return boost::make_tuple(bRec.wo, 0.0f, ESolidAngle);
}
Spectrum sampled2 = f/pdfVal;
if (!sampled.isValid() || !sampled2.isValid()) {
Log(EWarn, "Ooops: f=%s, pdf=%f, sampled f/pdf=%s, bRec=%s",
f.toString().c_str(), pdfVal, sampled.toString().c_str(), bRec.toString().c_str());
return std::make_pair(bRec.wo, 0.0f);
return boost::make_tuple(bRec.wo, 0.0f, ESolidAngle);
}
bool mismatch = false;
@ -175,10 +175,11 @@ public:
disableFPExceptions();
#endif
return std::make_pair(bRec.wo, 1.0f);
return boost::make_tuple(bRec.wo, 1.0f,
BSDF::getMeasure(bRec.sampledType));
}
Float pdf(const Vector &wo) {
Float pdf(const Vector &wo, EMeasure measure) {
Intersection its;
BSDFQueryRecord bRec(its);
bRec.component = m_component;
@ -191,9 +192,10 @@ public:
enableFPExceptions();
#endif
if (m_bsdf->eval(bRec).isZero())
if (m_bsdf->eval(bRec, measure).isZero())
return 0.0f;
Float result = m_bsdf->pdf(bRec);
Float result = m_bsdf->pdf(bRec, measure);
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
@ -220,7 +222,7 @@ public:
: m_mRec(mRec), m_phase(phase), m_sampler(sampler), m_wi(wi),
m_largestWeight(0) { }
std::pair<Vector, Float> generateSample() {
boost::tuple<Vector, Float, EMeasure> generateSample() {
Point2 sample(m_sampler->next2D());
PhaseFunctionQueryRecord pRec(m_mRec, m_wi);
@ -240,7 +242,7 @@ public:
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
#endif
return std::make_pair(pRec.wo, 0.0f);
return boost::make_tuple(pRec.wo, 0.0f, ESolidAngle);
} else if (sampled == 0) {
if (f != 0 && pdfVal != 0)
Log(EWarn, "Inconsistency: f=%f, pdf=%f, sampled f/pdf=%f",
@ -248,7 +250,7 @@ public:
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
#endif
return std::make_pair(pRec.wo, 0.0f);
return boost::make_tuple(pRec.wo, 0.0f, ESolidAngle);
}
Float sampled2 = f/pdfVal;
@ -271,10 +273,13 @@ public:
#if defined(MTS_DEBUG_FP)
disableFPExceptions();
#endif
return std::make_pair(pRec.wo, 1.0f);
return boost::make_tuple(pRec.wo, 1.0f, ESolidAngle);
}
Float pdf(const Vector &wo) const {
Float pdf(const Vector &wo, EMeasure measure) const {
if (measure != ESolidAngle)
return 0.0f;
PhaseFunctionQueryRecord pRec(m_mRec, m_wi, wo);
#if defined(MTS_DEBUG_FP)
enableFPExceptions();
@ -351,11 +356,11 @@ public:
// Initialize the tables used by the chi-square test
chiSqr->fill(
boost::bind(&BSDFAdapter::generateSample, &adapter),
boost::bind(&BSDFAdapter::pdf, &adapter, _1)
boost::bind(&BSDFAdapter::pdf, &adapter, _1, _2)
);
// (the following assumes that the distribution has 1 parameter, e.g. exponent value)
ChiSquare::ETestResult result = chiSqr->runTest(1, SIGNIFICANCE_LEVEL);
ChiSquare::ETestResult result = chiSqr->runTest(SIGNIFICANCE_LEVEL);
if (result == ChiSquare::EReject) {
std::string filename = formatString("failure_%i.m", failureCount++);
chiSqr->dumpTables(filename);
@ -394,11 +399,11 @@ public:
// Initialize the tables used by the chi-square test
chiSqr->fill(
boost::bind(&BSDFAdapter::generateSample, &adapter),
boost::bind(&BSDFAdapter::pdf, &adapter, _1)
boost::bind(&BSDFAdapter::pdf, &adapter, _1, _2)
);
// (the following assumes that the distribution has 1 parameter, e.g. exponent value)
ChiSquare::ETestResult result = chiSqr->runTest(1, SIGNIFICANCE_LEVEL);
ChiSquare::ETestResult result = chiSqr->runTest(SIGNIFICANCE_LEVEL);
if (result == ChiSquare::EReject) {
std::string filename = formatString("failure_%i.m", failureCount++);
chiSqr->dumpTables(filename);
@ -460,11 +465,11 @@ public:
// Initialize the tables used by the chi-square test
chiSqr->fill(
boost::bind(&PhaseFunctionAdapter::generateSample, &adapter),
boost::bind(&PhaseFunctionAdapter::pdf, &adapter, _1)
boost::bind(&PhaseFunctionAdapter::pdf, &adapter, _1, _2)
);
// (the following assumes that the distribution has 1 parameter, e.g. exponent value)
ChiSquare::ETestResult result = chiSqr->runTest(1, SIGNIFICANCE_LEVEL);
ChiSquare::ETestResult result = chiSqr->runTest(SIGNIFICANCE_LEVEL);
if (result == ChiSquare::EReject) {
std::string filename = formatString("failure_%i.m", failureCount++);
chiSqr->dumpTables(filename);