612 lines
22 KiB
C++
612 lines
22 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <mitsuba/render/scene.h>
|
|
#include <mitsuba/core/util.h>
|
|
#include <mitsuba/core/bitmap.h>
|
|
#include <mitsuba/core/fstream.h>
|
|
|
|
#define SAMPLE_UNIFORMLY 1
|
|
|
|
MTS_NAMESPACE_BEGIN
|
|
|
|
/*!\plugin{sky}{Skylight luminaire}
|
|
* \parameters{
|
|
* \parameter{turbidity}{\Float}{
|
|
* This parameter determines the amount of scattering particles (or
|
|
* `haze') in the atmosphere. Smaller values ($\sim 2$) produce a
|
|
* clear blue sky, larger values ($\sim 8$) lead to an overcast sky,
|
|
* and a very high values ($\sim 20$) cause a color shift towards
|
|
* orange and red. \default{3}
|
|
* }
|
|
* \parameter{day}{\Integer}{Solar day used to compute the sun's position.
|
|
* Must be in the range between 1 and 365. \default{180}}
|
|
* \parameter{time}{\Float}{Fractional time used to compute the sun's
|
|
* position. A time of 4:15 PM corresponds to 16.25. \default{15.00}}
|
|
* \parameter{latitude, longitude}{\Float}{
|
|
* These two parameters specify the oberver's latitude and longitude
|
|
* in degrees, which are required to compute the sun's position.
|
|
* \default{35.6894, 139.6917 --- Tokyo, Japan}
|
|
* }
|
|
* \parameter{standardMeridian}{\Integer}{Denotes the
|
|
* standard meridian of the time zone for finding
|
|
* the sun's position \default{135 --- Japan standard time}
|
|
* }
|
|
* \parameter{sunDirection}{\Vector}{Allows to manually
|
|
* override the sun direction in world space. When this value
|
|
* is provided, parameters pertaining to the computation
|
|
* of the sun direction (\code{day, time, latitude, longitude,}
|
|
* and \code{standardMeridian}) are unnecessary. \default{none}
|
|
* }
|
|
* \parameter{extend}{\Boolean}{
|
|
* Extend luminaire below the horizon? \default{\code{false}}
|
|
* }
|
|
* \parameter{resolution}{\Integer}{Specifies the resolution of the precomputed
|
|
* image that is used to represent the sky environment map
|
|
* \default{256}}
|
|
* \parameter{scale}{\Float}{
|
|
* This parameter can be used to scale the the amount of illumination
|
|
* emitted by the sky luminaire, for instance to change its units. To
|
|
* switch from photometric ($\nicefrac{W}{m^2\cdot sr}$)
|
|
* to arbitrary but convenient units in the $[0,1]$ range, set
|
|
* this parameter to \code{1e-5}.\default{1}.
|
|
* }
|
|
* }
|
|
*
|
|
* \renderings{
|
|
* \tinyrendering{6AM}{preetham_06}
|
|
* \tinyrendering{8AM}{preetham_08}
|
|
* \tinyrendering{10AM}{preetham_10}
|
|
* \tinyrendering{12PM}{preetham_12}
|
|
* \tinyrendering{2PM}{preetham_14}
|
|
* \tinyrendering{4PM}{preetham_16}
|
|
* \tinyrendering{6PM}{preetham_18}
|
|
* \tinyrendering{8PM}{preetham_20}\hfill
|
|
* \vspace{-3mm}
|
|
* \caption{Time series with the default settings (visualized by
|
|
* projecting the sky onto a disk)}
|
|
* }
|
|
*
|
|
* This plugin implements the physically-based skylight model proposed by
|
|
* Preetham et al. \cite{Preetham1999Practical}. It can be used for realistic
|
|
* daylight renderings of scenes under clear and overcast skies, assuming
|
|
* that the sky is observed from a position either on or close to the surface
|
|
* of the earth.
|
|
*
|
|
* Numerous parameters allow changing the both the position on Earth, as
|
|
* well as the time of observation. These are used to compute the sun
|
|
* direction which, together with \code{turbidity}, constitutes the main
|
|
* parameter of the model. If desired, the sun direction can also be
|
|
* specified manually.
|
|
*
|
|
* \renderings{
|
|
* \tinyrendering{2}{preetham_turb_2}
|
|
* \tinyrendering{3}{preetham_turb_3}
|
|
* \tinyrendering{4}{preetham_turb_4}
|
|
* \tinyrendering{5}{preetham_turb_5}
|
|
* \tinyrendering{6}{preetham_turb_6}
|
|
* \tinyrendering{7}{preetham_turb_7}
|
|
* \tinyrendering{8}{preetham_turb_8}
|
|
* \tinyrendering{9}{preetham_turb_9}
|
|
* \vspace{-3mm}
|
|
* \caption{Sky light for different turbidity values (fixed time \& location)}
|
|
* }
|
|
*
|
|
* \emph{Turbidity}, the other important parameter, specifies the amount of
|
|
* atmospheric extinction due to larger particles ($t_l$), as opposed to
|
|
* molecules ($t_m$). Lower values correspond to a clear sky, and higher values
|
|
* produce illumination resembling that of a hazy, overcast sky. Formally,
|
|
* the turbidity is defined as the ratio between the combined extinction
|
|
* cross-section and the cross-section only due to molecules, i.e.
|
|
* $T=\frac{t_m+t_l}{t_m}$. Values between 1 and 30 are possible, though
|
|
* the model will be most accurate for values between 2 and 6, to which
|
|
* it was fit using numerical optimization.
|
|
|
|
* The default coordinate system of the luminaire associates the up
|
|
* direction with the $+Y$ axis. The east direction is associated with $+X$
|
|
* and the north direction is equal to $+Z$. To change this coordinate
|
|
* system, rotations can be applied using the \code{toWorld} parameter.
|
|
*
|
|
* By default, the luminaire will not emit any light below the
|
|
* horizon, which means that these regions will be black when they
|
|
* are observed directly. By setting the \code{extend} parameter to
|
|
* \code{true}, the emitted radiance at the horizon will be extended to
|
|
* the entire bottom hemisphere. Note that this will significantly
|
|
* increase the amount of illumination present in the scene.
|
|
*
|
|
* For performance reasons, the implementation precomputes an environment
|
|
* map of the entire sky that is then forwarded to the \pluginref{envmap}
|
|
* plugin. The resolution of this environment map can affect the quality
|
|
* of the result. Due to the smoothness of the sky illumination,
|
|
* \code{resolution} values of around 256 (the default) are usually
|
|
* more than sufficient.
|
|
*
|
|
* Note that while the model encompasses sunrise and sunset configurations,
|
|
* it does not extend to the night sky, where illumination from stars, galaxies,
|
|
* and the moon dominate. The model also currently does not handle cloudy skies.
|
|
* The implementation in Mitsuba is based on code by Preetham et al. It was
|
|
* ported by Tom Kazimiers.
|
|
*/
|
|
class SkyLuminaire : public Luminaire {
|
|
public:
|
|
/**
|
|
* Creates a new Sky luminaire. The defaults values origate
|
|
* from the sample code of Preetham et al. and create an over
|
|
* head sun on a clear day.
|
|
*/
|
|
SkyLuminaire(const Properties &props)
|
|
: Luminaire(props) {
|
|
/* Transformation from the luminaire's local coordinates to
|
|
* world coordiantes */
|
|
m_luminaireToWorld =
|
|
props.getTransform("toWorld", Transform());
|
|
m_worldToLuminaire = m_luminaireToWorld.inverse();
|
|
|
|
m_scale = props.getFloat("scale", Float(1.0));
|
|
m_turbidity = props.getFloat("turbidity", Float(3.0));
|
|
if (m_turbidity < 1 || m_turbidity > 30)
|
|
Log(EError, "The turbidity parameter must be in the range [1,30]!");
|
|
|
|
m_extend = props.getBoolean("extend", false);
|
|
|
|
m_resolution = props.getInteger("resolution", 256);
|
|
|
|
/* configure position of sun */
|
|
if (props.hasProperty("sunDirection")) {
|
|
if (props.hasProperty("latitude") || props.hasProperty("longitude")
|
|
|| props.hasProperty("standardMeridian") || props.hasProperty("day")
|
|
|| props.hasProperty("time"))
|
|
Log(EError, "Both the 'sunDirection' parameter and time/location "
|
|
"information were provided -- only one of them can be specified at a time!");
|
|
|
|
configureSunPosition(
|
|
props.getVector("sunDirection"));
|
|
} else {
|
|
Float lat = props.getFloat("latitude", 35.6894f);
|
|
Float lon = props.getFloat("longitude", 139.6917f);
|
|
int stdMrd = props.getInteger("standardMeridian", 135);
|
|
|
|
int day = props.getInteger("day", 180);
|
|
if (day < 1 || day > 365)
|
|
Log(EError, "The day parameter must be in the range [1, 365]!");
|
|
|
|
Float time = props.getFloat("time", 15.00f);
|
|
if (time < 0 || time > 24)
|
|
Log(EError, "The time parameter must be in the range [0, 24]!");
|
|
|
|
configureSunPosition(lat, lon, stdMrd, day, time);
|
|
}
|
|
|
|
configure();
|
|
}
|
|
|
|
SkyLuminaire(Stream *stream, InstanceManager *manager)
|
|
: Luminaire(stream, manager) {
|
|
m_scale = stream->readFloat();
|
|
m_turbidity = stream->readFloat();
|
|
m_thetaS = stream->readFloat();
|
|
m_phiS = stream->readFloat();
|
|
m_extend = stream->readBool();
|
|
m_resolution = stream->readInt();
|
|
|
|
configure();
|
|
}
|
|
|
|
void serialize(Stream *stream, InstanceManager *manager) const {
|
|
Luminaire::serialize(stream, manager);
|
|
stream->writeFloat(m_scale);
|
|
stream->writeFloat(m_turbidity);
|
|
stream->writeFloat(m_thetaS);
|
|
stream->writeFloat(m_phiS);
|
|
stream->writeBool(m_extend);
|
|
stream->writeInt(m_resolution);
|
|
}
|
|
|
|
/**
|
|
* Precalculates some inernal varibles. It needs a valid sun position,
|
|
* hence the configureSunPos... method must have been called previously.
|
|
*/
|
|
void configure() {
|
|
Float theta2 = m_thetaS * m_thetaS;
|
|
Float theta3 = theta2 * m_thetaS;
|
|
Float turb2 = m_turbidity * m_turbidity;
|
|
|
|
/* calculate zenith chromaticity */
|
|
m_zenithX =
|
|
(+0.00165f*theta3 - 0.00374f*theta2 + 0.00208f*m_thetaS + 0.0f) * turb2 +
|
|
(-0.02902f*theta3 + 0.06377f*theta2 - 0.03202f*m_thetaS + 0.00394f) * m_turbidity +
|
|
(+0.11693f*theta3 - 0.21196f*theta2 + 0.06052f*m_thetaS + 0.25885f);
|
|
|
|
m_zenithY =
|
|
(+0.00275f*theta3 - 0.00610f*theta2 + 0.00316f*m_thetaS + 0.0f) * turb2 +
|
|
(-0.04214f*theta3 + 0.08970f*theta2 - 0.04153f*m_thetaS + 0.00515f) * m_turbidity +
|
|
(+0.15346f*theta3 - 0.26756f*theta2 + 0.06669f*m_thetaS + 0.26688f);
|
|
|
|
/* calculate zenith luminance */
|
|
Float chi = (4.0f/9.0f - m_turbidity / 120.0f) * (M_PI - 2 * m_thetaS);
|
|
|
|
m_zenithL = (4.0453f * m_turbidity - 4.9710f) * std::tan(chi)
|
|
- 0.2155f * m_turbidity + 2.4192f;
|
|
cout << toString() << endl;
|
|
|
|
/* Evaluate quadratic polynomials to find the Perez sky
|
|
* model coefficients for the x, y and luminance components */
|
|
m_perezL[0] = 0.17872f * m_turbidity - 1.46303f;
|
|
m_perezL[1] = -0.35540f * m_turbidity + 0.42749f;
|
|
m_perezL[2] = -0.02266f * m_turbidity + 5.32505f;
|
|
m_perezL[3] = 0.12064f * m_turbidity - 2.57705f;
|
|
m_perezL[4] = -0.06696f * m_turbidity + 0.37027f;
|
|
|
|
m_perezX[0] = -0.01925f * m_turbidity - 0.25922f;
|
|
m_perezX[1] = -0.06651f * m_turbidity + 0.00081f;
|
|
m_perezX[2] = -0.00041f * m_turbidity + 0.21247f;
|
|
m_perezX[3] = -0.06409f * m_turbidity - 0.89887f;
|
|
m_perezX[4] = -0.00325f * m_turbidity + 0.04517f;
|
|
|
|
m_perezY[0] = -0.01669f * m_turbidity - 0.26078f;
|
|
m_perezY[1] = -0.09495f * m_turbidity + 0.00921f;
|
|
m_perezY[2] = -0.00792f * m_turbidity + 0.21023f;
|
|
m_perezY[3] = -0.04405f * m_turbidity - 1.65369f;
|
|
m_perezY[4] = -0.01092f * m_turbidity + 0.05291f;
|
|
|
|
int thetaBins = m_resolution, phiBins = m_resolution*2;
|
|
|
|
ref<Bitmap> bitmap = new Bitmap(phiBins, thetaBins, 128);
|
|
bitmap->clear();
|
|
Point2 factor(M_PI / thetaBins, (2*M_PI) / phiBins);
|
|
for (int i=0; i<thetaBins; ++i) {
|
|
Float theta = (i+.5f)*factor.x;
|
|
for (int j=0; j<phiBins; ++j) {
|
|
Float phi = (j+.5f)*factor.y;
|
|
Spectrum s = getSkySpectralRadiance(theta, phi) * m_scale;
|
|
Float r, g, b;
|
|
s.toLinearRGB(r, g, b);
|
|
bitmap->getFloatData()[(j+i*phiBins)*4 + 0] = r;
|
|
bitmap->getFloatData()[(j+i*phiBins)*4 + 1] = g;
|
|
bitmap->getFloatData()[(j+i*phiBins)*4 + 2] = b;
|
|
bitmap->getFloatData()[(j+i*phiBins)*4 + 3] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector toSphere(Float theta, Float phi) const {
|
|
/* Spherical-to-cartesian coordinate mapping with
|
|
theta=0 => Y=1 */
|
|
Float cosTheta = std::cos(theta), sinTheta = std::sin(theta),
|
|
cosPhi = std::cos(phi), sinPhi = std::sin(phi);
|
|
return m_luminaireToWorld(Vector(
|
|
sinTheta * sinPhi, cosTheta, -sinTheta*cosPhi));
|
|
}
|
|
|
|
Point2 fromSphere(const Vector &d) const {
|
|
Float theta = std::acos(std::max((Float) -1.0f,
|
|
std::min((Float) 1.0f, d.y)));
|
|
Float phi = std::atan2(d.x,-d.z);
|
|
if (phi < 0)
|
|
phi += 2*M_PI;
|
|
return Point2(theta, phi);
|
|
}
|
|
|
|
/**
|
|
* Configures the position of the sun. This calculation is based on
|
|
* your position on the world and time of day.
|
|
* From IES Lighting Handbook pg 361.
|
|
*/
|
|
void configureSunPosition(Float lat, Float lon, int stdMrd,
|
|
int day, Float time) {
|
|
const Float solarTime = time
|
|
+ (0.170f * std::sin(4.0f * M_PI * (day - 80.0f) / 373.0f)
|
|
- 0.129f * std::sin(2.0f * M_PI * (day - 8.0f) / 355.0f))
|
|
+ (stdMrd - lon) / 15.0f;
|
|
|
|
const Float solarDeclination = (0.4093f * std::sin(2 * M_PI
|
|
* (day - 81.0f) / 368.0f));
|
|
|
|
lat = degToRad(lat);
|
|
const Float solarAltitude = std::asin(std::sin(lat)
|
|
* std::sin(solarDeclination) - std::cos(lat)
|
|
* std::cos(solarDeclination) * std::cos(M_PI * solarTime / 12.0f));
|
|
|
|
const Float opp = -std::cos(solarDeclination)
|
|
* std::sin(M_PI * solarTime / 12.0f);
|
|
const Float adj = -(std::cos(lat) * std::sin(solarDeclination)
|
|
+ std::sin(lat) * std::cos(solarDeclination)
|
|
* std::cos(M_PI * solarTime / 12.0f));
|
|
|
|
const Float solarAzimuth = std::atan2(opp, adj);
|
|
|
|
m_phiS = -solarAzimuth;
|
|
m_thetaS = M_PI / 2.0f - solarAltitude;
|
|
}
|
|
|
|
void configureSunPosition(const Vector& sunDir) {
|
|
Point2 sunPos = fromSphere(normalize(m_luminaireToWorld(sunDir)));
|
|
m_thetaS = sunPos.x;
|
|
m_phiS = sunPos.y;
|
|
}
|
|
|
|
void preprocess(const Scene *scene) {
|
|
/* Get the scene's bounding sphere and slightly enlarge it */
|
|
m_bsphere = scene->getBSphere();
|
|
m_bsphere.radius *= 1.01f;
|
|
}
|
|
|
|
Spectrum getPower() const {
|
|
/* TODO */
|
|
return m_average * (M_PI * 4 * M_PI
|
|
* m_bsphere.radius * m_bsphere.radius);
|
|
}
|
|
|
|
inline Spectrum Le(const Vector &direction) const {
|
|
/* Compute sky light radiance for direction */
|
|
Vector d = normalize(m_worldToLuminaire(direction));
|
|
const Point2 sphCoords = fromSphere(d);
|
|
return getSkySpectralRadiance(sphCoords.x, sphCoords.y) * m_scale;
|
|
}
|
|
|
|
inline Spectrum Le(const Ray &ray) const {
|
|
return Le(normalize(ray.d));
|
|
}
|
|
|
|
Spectrum Le(const LuminaireSamplingRecord &lRec) const {
|
|
return Le(-lRec.d);
|
|
}
|
|
|
|
inline void sample(const Point &p, LuminaireSamplingRecord &lRec,
|
|
const Point2 &sample) const {
|
|
lRec.d = sampleDirection(sample, lRec.pdf, lRec.value);
|
|
lRec.sRec.p = p - lRec.d * (2 * m_bsphere.radius);
|
|
}
|
|
|
|
void sample(const Intersection &its, LuminaireSamplingRecord &lRec,
|
|
const Point2 &sample) const {
|
|
SkyLuminaire::sample(its.p, lRec, sample);
|
|
}
|
|
|
|
inline Float pdf(const Point &p, const LuminaireSamplingRecord &lRec, bool delta) const {
|
|
#if defined(SAMPLE_UNIFORMLY)
|
|
return 1.0f / (4 * M_PI);
|
|
#endif
|
|
}
|
|
|
|
Float pdf(const Intersection &its, const LuminaireSamplingRecord &lRec, bool delta) const {
|
|
return SkyLuminaire::pdf(its.p, lRec, delta);
|
|
}
|
|
|
|
/**
|
|
* This is the tricky bit - we want to sample a ray that
|
|
* has uniform density over the set of all rays passing
|
|
* through the scene.
|
|
* For more detail, see "Using low-discrepancy sequences and
|
|
* the Crofton formula to compute surface areas of geometric models"
|
|
* by Li, X. and Wang, W. and Martin, R.R. and Bowyer, A.
|
|
* (Computer-Aided Design vol 35, #9, pp. 771--782)
|
|
*/
|
|
void sampleEmission(EmissionRecord &eRec,
|
|
const Point2 &sample1, const Point2 &sample2) const {
|
|
Assert(eRec.type == EmissionRecord::ENormal);
|
|
/* Chord model - generate the ray passing through two uniformly
|
|
distributed points on a sphere containing the scene */
|
|
Vector d = squareToSphere(sample1);
|
|
eRec.sRec.p = m_bsphere.center + d * m_bsphere.radius;
|
|
eRec.sRec.n = Normal(-d);
|
|
Point p2 = m_bsphere.center + squareToSphere(sample2) * m_bsphere.radius;
|
|
eRec.d = p2 - eRec.sRec.p;
|
|
Float length = eRec.d.length();
|
|
|
|
if (length == 0) {
|
|
eRec.value = Spectrum(0.0f);
|
|
eRec.pdfArea = eRec.pdfDir = 1.0f;
|
|
return;
|
|
}
|
|
|
|
eRec.d /= length;
|
|
eRec.pdfArea = 1.0f / (4 * M_PI * m_bsphere.radius * m_bsphere.radius);
|
|
eRec.pdfDir = INV_PI * dot(eRec.sRec.n, eRec.d);
|
|
eRec.value = Le(-eRec.d);
|
|
}
|
|
|
|
void sampleEmissionArea(EmissionRecord &eRec, const Point2 &sample) const {
|
|
if (eRec.type == EmissionRecord::ENormal) {
|
|
Vector d = squareToSphere(sample);
|
|
eRec.sRec.p = m_bsphere.center + d * m_bsphere.radius;
|
|
eRec.sRec.n = Normal(-d);
|
|
eRec.pdfArea = 1.0f / (4 * M_PI * m_bsphere.radius * m_bsphere.radius);
|
|
eRec.value = Spectrum(M_PI);
|
|
} else {
|
|
/* Preview mode, which is more suitable for VPL-based rendering: approximate
|
|
the infinitely far-away source with set of diffuse point sources */
|
|
const Float radius = m_bsphere.radius * 1.5f;
|
|
Vector d = squareToSphere(sample);
|
|
eRec.sRec.p = m_bsphere.center + d * radius;
|
|
eRec.sRec.n = Normal(-d);
|
|
eRec.pdfArea = 1.0f / (4 * M_PI * radius * radius);
|
|
eRec.value = Le(d) * M_PI;
|
|
}
|
|
}
|
|
|
|
Spectrum sampleEmissionDirection(EmissionRecord &eRec, const Point2 &sample) const {
|
|
Float radius = m_bsphere.radius;
|
|
if (eRec.type == EmissionRecord::EPreview)
|
|
radius *= 1.5f;
|
|
Point p2 = m_bsphere.center + squareToSphere(sample) * radius;
|
|
eRec.d = p2 - eRec.sRec.p;
|
|
Float length = eRec.d.length();
|
|
|
|
if (length == 0.0f) {
|
|
eRec.pdfDir = 1.0f;
|
|
return Spectrum(0.0f);
|
|
}
|
|
|
|
eRec.d /= length;
|
|
eRec.pdfDir = INV_PI * dot(eRec.sRec.n, eRec.d);
|
|
if (eRec.type == EmissionRecord::ENormal)
|
|
return Le(-eRec.d) * INV_PI;
|
|
else
|
|
return Spectrum(INV_PI);
|
|
}
|
|
|
|
Spectrum fDirection(const EmissionRecord &eRec) const {
|
|
if (eRec.type == EmissionRecord::ENormal)
|
|
return Le(-eRec.d) * INV_PI;
|
|
else
|
|
return Spectrum(INV_PI);
|
|
}
|
|
|
|
Spectrum fArea(const EmissionRecord &eRec) const {
|
|
Assert(eRec.type == EmissionRecord::ENormal);
|
|
return Spectrum(M_PI);
|
|
}
|
|
|
|
Spectrum f(const EmissionRecord &eRec) const {
|
|
if (eRec.type == EmissionRecord::ENormal)
|
|
return Le(-eRec.d) * INV_PI;
|
|
else
|
|
return Spectrum(INV_PI);
|
|
}
|
|
|
|
void pdfEmission(EmissionRecord &eRec, bool delta) const {
|
|
}
|
|
|
|
std::string toString() const {
|
|
std::ostringstream oss;
|
|
oss << "SkyLuminaire[" << endl
|
|
<< " turbidity = " << m_turbidity << "," << endl
|
|
<< " sunPos = [theta: " << m_thetaS << ", phi: "<< m_phiS << "]," << endl
|
|
<< " zenithL = " << m_zenithL << "," << endl
|
|
<< " scale = " << m_scale << endl
|
|
<< "]";
|
|
return oss.str();
|
|
}
|
|
|
|
bool isBackgroundLuminaire() const {
|
|
return true;
|
|
}
|
|
|
|
Vector sampleDirection(Point2 sample, Float &pdf, Spectrum &value) const {
|
|
#if defined(SAMPLE_UNIFORMLY)
|
|
pdf = 1.0f / (4*M_PI);
|
|
Vector d = squareToSphere(sample);
|
|
value = Le(-d);
|
|
return d;
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
/**
|
|
* Calculates the angle between two spherical cooridnates. All
|
|
* angles in radians, theta angles measured from up/zenith direction.
|
|
*/
|
|
inline Float getAngleBetween(const Float thetav, const Float phiv,
|
|
const Float theta, const Float phi) const {
|
|
const Float cospsi = std::sin(thetav) * std::sin(theta) * std::cos(phi - phiv)
|
|
+ std::cos(thetav) * std::cos(theta);
|
|
|
|
if (cospsi > 1.0f)
|
|
return 0.0f;
|
|
if (cospsi < -1.0f)
|
|
return M_PI;
|
|
return std::acos(cospsi);
|
|
}
|
|
|
|
/**
|
|
* Calculates the distribution of the sky radiance with two
|
|
* Perez functions:
|
|
*
|
|
* Perez(Theta, Gamma)
|
|
* d = ---------------------
|
|
* Perez(0, ThetaSun)
|
|
*
|
|
* From IES Lighting Handbook pg 361
|
|
*/
|
|
inline Float getDistribution(const Float *lam, const Float theta,
|
|
const Float gamma) const {
|
|
const Float cosGamma = std::cos(gamma);
|
|
const Float num = ((1 + lam[0] * std::exp(lam[1] / std::cos(theta)))
|
|
* (1 + lam[2] * std::exp(lam[3] * gamma)
|
|
+ lam[4] * cosGamma * cosGamma));
|
|
|
|
const Float cosTheta = std::cos(m_thetaS);
|
|
const Float den = ( (1 + lam[0] * std::exp(lam[1] /* / cos 0 */))
|
|
* (1 + lam[2] * std::exp(lam[3] * m_thetaS)
|
|
+ lam[4] * cosTheta * cosTheta));
|
|
|
|
return num / den;
|
|
}
|
|
|
|
/**
|
|
* Calculates the spectral radiance of the sky in the specified directiono.
|
|
*/
|
|
Spectrum getSkySpectralRadiance(Float theta, Float phi) const {
|
|
if (!m_extend && std::cos(theta) <= 0)
|
|
return Spectrum(0.0f);
|
|
/* Clip directions that are extremely close to grazing (for numerical
|
|
* stability) or entirely below the horizon. This effectively extends
|
|
* the horizon luminance value to the bottom hemisphere */
|
|
theta = std::min(theta, (M_PI * 0.5f) - 0.001f);
|
|
/* get angle between sun (zenith is 0, 0) and point (theta, phi) */
|
|
const Float gamma = getAngleBetween(theta, phi, m_thetaS, m_phiS);
|
|
/* Compute xyY values by calculating the distribution for the point
|
|
* point of interest and multiplying it with the the components
|
|
* zenith value. */
|
|
const Float x = m_zenithX * getDistribution(m_perezX, theta, gamma);
|
|
const Float y = m_zenithY * getDistribution(m_perezY, theta, gamma);
|
|
const Float Y = m_zenithL * getDistribution(m_perezL, theta, gamma);
|
|
|
|
/* Convert xyY to XYZ */
|
|
const Float yFrac = Y / y;
|
|
const Float X = yFrac * x;
|
|
/* It seems the following is necassary to stay always above zero */
|
|
const Float z = std::max((Float) 0.0f, 1.0f - x - y);
|
|
const Float Z = yFrac * z;
|
|
|
|
/* Create spectrum from XYZ values */
|
|
Spectrum dstSpect;
|
|
dstSpect.fromXYZ(X, Y, Z, Spectrum::EIlluminant);
|
|
/* The produced spectrum might contain out-of-gamut colors.
|
|
* The common solution is to clamp resulting values to zero. */
|
|
dstSpect.clampNegative();
|
|
return dstSpect;
|
|
}
|
|
|
|
MTS_DECLARE_CLASS()
|
|
protected:
|
|
Spectrum m_average;
|
|
BSphere m_bsphere;
|
|
int m_resolution;
|
|
Float m_scale;
|
|
/* The turbidity of the sky ranges normally from 1 to 30.
|
|
For clear skies values in range [2,6] are useful. */
|
|
Float m_turbidity;
|
|
/* Position of the sun in spherical coordinates */
|
|
Float m_thetaS, m_phiS;
|
|
/* Radiance at the zenith, in xyY */
|
|
Float m_zenithL, m_zenithX, m_zenithY;
|
|
/* The distribution coefficints are called A, B, C, D and E by
|
|
Preetham. They exist for x, y and Y (here called L). The
|
|
following attributes save the precalculated version of each */
|
|
Float m_perezL[5], m_perezX[5], m_perezY[5];
|
|
/* Extend to the bottom hemisphere? */
|
|
bool m_extend;
|
|
};
|
|
|
|
MTS_IMPLEMENT_CLASS_S(SkyLuminaire, false, Luminaire)
|
|
MTS_EXPORT_PLUGIN(SkyLuminaire, "Sky luminaire");
|
|
MTS_NAMESPACE_END
|
|
|