/* 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