/*
This file is part of Mitsuba, a physically based rendering system.
Copyright (c) 2007-2012 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 .
*/
#pragma once
#if !defined(__MITSUBA_CORE_SPECTRUM_H_)
#define __MITSUBA_CORE_SPECTRUM_H_
#include
#if !defined(SPECTRUM_SAMPLES)
#error The desired number of spectral samples must be \
specified in the configuration file!
#endif
#define SPECTRUM_MIN_WAVELENGTH 360
#define SPECTRUM_MAX_WAVELENGTH 830
#define SPECTRUM_RANGE \
(SPECTRUM_MAX_WAVELENGTH-SPECTRUM_MIN_WAVELENGTH)
MTS_NAMESPACE_BEGIN
/**
* \brief Abstract continous spectral power distribution data type,
* which supports evaluation at arbitrary wavelengths.
*
* Here, the term 'continous' doesn't necessarily mean that the
* underlying spectrum is continous, but rather emphasizes the fact
* that it is a function over the reals (as opposed to the discrete
* spectrum, which only stores samples for a discrete set of wavelengths).
*
* \ingroup libpython
* \ingroup libcore
*/
class MTS_EXPORT_CORE ContinuousSpectrum {
public:
/**
* Evaluate the value of the spectral power distribution
* at the given wavelength.
*
* \param lambda A wavelength in nanometers
*/
virtual Float eval(Float lambda) const = 0;
/**
* \brief Integrate the spectral power distribution
* over a given interval and return the average value
*
* Unless overridden in a subclass, the integration is done
* using adaptive Gauss-Lobatto quadrature.
*
* \param lambdaMin
* The lower interval bound in nanometers
*
* \param lambdaMax
* The upper interval bound in nanometers
*
* \remark If \c lambdaMin >= \c lambdaMax, the
* implementation will return zero.
*/
virtual Float average(Float lambdaMin, Float lambdaMax) const;
/// \brief Return a string representation
virtual std::string toString() const = 0;
/// Virtual destructor
virtual ~ContinuousSpectrum() { }
};
/**
* \brief Spectral power distribution based on Planck's black body law
*
* Computes the spectral power distribution of a black body of the
* specified temperature.
*
* \ingroup libcore
*/
class MTS_EXPORT_CORE BlackBodySpectrum : public ContinuousSpectrum {
public:
/**
* \brief Construct a new black body spectrum given the emitter's
* temperature in Kelvin.
*/
inline BlackBodySpectrum(Float temperature) {
m_temperature = temperature;
}
virtual ~BlackBodySpectrum() { }
/** \brief Return the value of the spectral power distribution
* at the given wavelength.
*
* The units are Watts per unit surface area (m^-2)
* per unit wavelength (nm^-1) per steradian (sr^-1)
*/
virtual Float eval(Float lambda) const;
/// Return a string representation
std::string toString() const;
private:
Float m_temperature;
};
/**
* \brief Spectral distribution for rendering participating media
* with Rayleigh scattering.
*
* This distribution captures the 1/lambda^4 wavelength dependence
* of Rayleigh scattering. It can provide both the scattering and
* extinction coefficient needed for simulating planetary
* atmospheres with participating media.
*
* \ingroup libcore
*/
class MTS_EXPORT_CORE RayleighSpectrum : public ContinuousSpectrum {
public:
enum EMode {
/// Compute the scattering coefficient
ESigmaS,
/// Compute the extinction coefficient
ESigmaT
};
/**
* \brief Create a Rayleigh spectrum instance
*
* \param mode Specifies the requested type of spectrum
* \param eta Refractive index of the medium (e.g. air)
* \param height Height above sea level (in meters)
*/
RayleighSpectrum(EMode mode, Float eta = 1.000277f, Float height = 0);
virtual ~RayleighSpectrum() { }
/** \brief Evaluate the extinction/scattering coefficient for
* a specified wavelength.
*
* The returned value is in units of 1/meter.
*/
virtual Float eval(Float lambda) const;
/// Return a string representation
std::string toString() const;
private:
Float m_precomp;
};
/**
* \brief This spectral power distribution is defined as the
* product of two other continuous spectra.
*/
class MTS_EXPORT_CORE ProductSpectrum : public ContinuousSpectrum {
public:
/** \brief Return the value of the spectral power distribution
* at the given wavelength.
*/
ProductSpectrum(const ContinuousSpectrum &s1,
const ContinuousSpectrum &s2) : m_spec1(s1),
m_spec2(s2) { }
/** \brief Return the value of the spectral power distribution
* at the given wavelength.
*/
virtual Float eval(Float lambda) const;
/// Virtual destructor
virtual ~ProductSpectrum() { }
/// Return a string representation
std::string toString() const;
private:
const ContinuousSpectrum &m_spec1;
const ContinuousSpectrum &m_spec2;
};
/**
* \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. Outside of the
* specified range, the spectrum is assumed to be zero. Hence,
* at least two entries are required to produce a nonzero
* spectrum.
*
* \ingroup libcore
* \ingroup libpython
*/
class MTS_EXPORT_CORE InterpolatedSpectrum : public ContinuousSpectrum {
public:
/**
* \brief Create a new interpolated spectrum with space
* for the specified number of samples
*/
InterpolatedSpectrum(size_t size = 0);
/**
* \brief Create a interpolated spectrum instance from
* a float array
*/
InterpolatedSpectrum(const Float *wavelengths,
const Float *values, size_t nEntries);
/**
* \brief Read an interpolated spectrum from a simple
* ASCII format.
*
* Each line of the file should contain an entry of the form
* \verbatim
*
* \endverbatim
* Comments preceded by '#' are also valid.
*/
InterpolatedSpectrum(const fs::path &path);
/**
* \brief Append an entry to the spectral power distribution.
*
* Entries must be added in order of increasing wavelength
*/
void append(Float lambda, Float value);
/**
* \brief This function adds a zero entry before and after
* the stored wavelength range.
*
* This is useful when handling datasets that don't fall
* off to zero at the ends. The spacing of the added entries
* is determined by computing the average spacing of the
* existing samples.
*/
void zeroExtend();
/// Clear all stored entries
void clear();
/**
* \brief Return the value of the spectral power distribution
* at the given wavelength.
*/
Float eval(Float lambda) const;
/**
* \brief Integrate the spectral power distribution
* over a given interval and return the average value
*
* This method overrides the implementation in
* \ref ContinousSpectrum, since the integral can be
* analytically computed for linearly interpolated spectra.
*
* \param lambdaMin
* The lower interval bound in nanometers
*
* \param lambdaMax
* The upper interval bound in nanometers
*
* \remark If \c lambdaMin >= \c lambdaMax, the
* implementation will return zero.
*/
Float average(Float lambdaMin, Float lambdaMax) const;
/// \brief Return a string representation
std::string toString() const;
/// Virtual destructor
virtual ~InterpolatedSpectrum() { }
protected:
std::vector m_wavelengths, m_values;
};
/**
* \brief Abstract spectral power distribution data type
*
* This class defines a vector-like data type that can be used for
* computations involving radiance. A concrete instantiation for the
* precision and spectral discretization chosen at compile time is
* given by the \ref Spectrum data type.
*
* \ingroup libcore
*/
template struct TSpectrum {
public:
typedef T Scalar;
/// Number of dimensions
const static int dim = N;
/// Create a new spectral power distribution, but don't initialize the contents
#if !defined(MTS_DEBUG_UNINITIALIZED)
inline TSpectrum() { }
#else
inline TSpectrum() {
for (int i=0; i::quiet_NaN();
}
#endif
/// Create a new spectral power distribution with all samples set to the given value
explicit inline TSpectrum(Scalar v) {
for (int i=0; ireadArray(s, N);
}
/// Initialize with a TSpectrum data type based on a alternate representation
template explicit TSpectrum(const TSpectrum &v) {
for (int i=0; iwriteArray(s, N);
}
std::string toString() const {
std::ostringstream oss;
oss << "[";
for (int i=0; i {
public:
typedef TSpectrum Parent;
/// Create a new color value, but don't initialize the contents
#if !defined(MTS_DEBUG_UNINITIALIZED)
inline Color3() { }
#else
inline Color3() {
for (int i=0; i<3; i++)
s[i] = std::numeric_limits::quiet_NaN();
}
#endif
/// Copy constructor
inline Color3(const Parent &s) : Parent(s) { }
/// Initialize to a constant value
inline Color3(Float value) : Parent(value) { }
/// Initialize to the given RGB value
inline Color3(Float r, Float g, Float b) {
s[0] = r; s[1] = g; s[2] = b;
}
};
/** \brief Discrete spectral power distribution based on a number
* of wavelength bins over the 360-830 nm range.
*
* This class defines a vector-like data type that can be used for
* computations involving radiance.
*
* When configured for spectral rendering (i.e. when the compile-time flag
* \c SPECTRUM_SAMPLES is set to a value != 3), the implementation discretizes
* the visible spectrum of light into a set of intervals, where the
* distribution within each bin is modeled as being uniform.
*
* When SPECTRUM_SAMPLES == 3, the class reverts to a simple linear
* RGB-based internal representation.
*
* The implementation of this class is based on PBRT.
*
* \ingroup libcore
* \ingroup libpython
*/
struct MTS_EXPORT_CORE Spectrum : public TSpectrum {
public:
typedef TSpectrum Parent;
/**
* \brief When converting from RGB reflectance values to
* discretized color spectra, the following `intent' flag
* can be provided to improve the results of this highly
* under-constrained problem.
*/
enum EConversionIntent {
/// Unitless reflectance data is converted
EReflectance,
/// Radiance-valued illumination data is converted
EIlluminant
};
/// Create a new spectral power distribution, but don't initialize the contents
#if !defined(MTS_DEBUG_UNINITIALIZED)
inline Spectrum() { }
#else
inline Spectrum() {
for (int i=0; i::quiet_NaN();
}
#endif
/// Construct from a TSpectrum instance
inline Spectrum(const Parent &s) : Parent(s) { }
/// Initialize with a TSpectrum data type based on a alternate representation
template explicit Spectrum(const TSpectrum &v) {
for (int i=0; i getBinCoverage(size_t index);
/// Return the luminance in candelas.
#if SPECTRUM_SAMPLES == 3
inline Float getLuminance() const {
return s[0] * 0.212671f + s[1] * 0.715160f + s[2] * 0.072169f;
}
#else
Float getLuminance() const;
#endif
/**
* \brief Convert from a spectral power distribution to XYZ
* tristimulus values
*
* In the Python API, this function returns a 3-tuple
* with the result of the operation.
*/
void toXYZ(Float &x, Float &y, Float &z) const;
/**
* \brief Convert XYZ tristimulus into a plausible spectral
* power distribution
*
* The \ref EConversionIntent parameter can be used to provide more
* information on how to solve this highly under-constrained problem.
* The default is \ref EReflectance.
*/
void fromXYZ(Float x, Float y, Float z,
EConversionIntent intent = EReflectance);
#if SPECTRUM_SAMPLES == 3
/**
* \brief Convert to linear RGB
*
* In the Python API, this function returns a 3-tuple
* with the result of the operation.
*/
inline void toLinearRGB(Float &r, Float &g, Float &b) const {
/* Nothing to do -- the renderer is in RGB mode */
r = s[0]; g = s[1]; b = s[2];
}
/// Convert from linear RGB
inline void fromLinearRGB(Float r, Float g, Float b,
EConversionIntent intent = EReflectance /* unused */) {
/* Nothing to do -- the renderer is in RGB mode */
s[0] = r; s[1] = g; s[2] = b;
}
#else
/**
* \brief Convert to linear RGB
*
* In the Python API, this function returns a 3-tuple
* with the result of the operation.
*/
void toLinearRGB(Float &r, Float &g, Float &b) const;
/**
* \brief Convert linear RGB colors into a plausible
* spectral power distribution
*
* The \ref EConversionIntent parameter can be used to provide more
* information on how to solve this highly under-constrained problem.
* The default is \ref EReflectance.
*/
void fromLinearRGB(Float r, Float g, Float b,
EConversionIntent intent = EReflectance);
#endif
/**
* \brief Convert to sRGB
*
* In the Python API, this function returns a 3-tuple
* with the result of the operation.
*/
void toSRGB(Float &r, Float &g, Float &b) const;
/**
* \brief Convert sRGB color values into a plausible spectral
* power distribution
*
* Note that compared to \ref fromLinearRGB, no \c intent parameter
* is available. For sRGB colors, it is assumed that the intent is
* always \ref EReflectance.
*/
void fromSRGB(Float r, Float g, Float b);
/**
* \brief Convert linear RGBE colors into a plausible
* spectral power distribution
*
* Based on code by Bruce Walter and Greg ward.
*
* The \ref EConversionIntent parameter can be used to provide more
* information on how to solve this highly under-constrained problem.
* For RGBE values, the default is \ref EIlluminant.
*/
void fromRGBE(const uint8_t rgbe[4], EConversionIntent intent = EIlluminant);
/// Linear RGBE conversion based on Bruce Walter's and Greg Ward's code
void toRGBE(uint8_t rgbe[4]) const;
/// Initialize with spectral values from a smooth spectrum representation
void fromContinuousSpectrum(const ContinuousSpectrum &smooth);
/// Equality test
inline bool operator==(const Spectrum &val) const {
for (int i=0; i