2011-07-06 23:52:02 +08:00
|
|
|
/*
|
|
|
|
This file is part of Mitsuba, a physically based rendering system.
|
|
|
|
|
|
|
|
Copyright (c) 2007-2011 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/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#if !defined(__MICROFACET_H)
|
|
|
|
#define __MICROFACET_H
|
|
|
|
|
2011-07-11 20:17:40 +08:00
|
|
|
#include <mitsuba/core/quad.h>
|
|
|
|
#include <mitsuba/core/timer.h>
|
|
|
|
#include <mitsuba/core/spline.h>
|
2011-07-06 23:52:02 +08:00
|
|
|
#include <boost/algorithm/string.hpp>
|
2011-07-11 20:17:40 +08:00
|
|
|
#include <boost/bind.hpp>
|
2011-07-06 23:52:02 +08:00
|
|
|
|
|
|
|
MTS_NAMESPACE_BEGIN
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements the microfacet distributions discussed in
|
|
|
|
* "Microfacet Models for Refraction through Rough Surfaces"
|
|
|
|
* by Bruce Walter, Stephen R. Marschner, Hongsong Li, and Kenneth E. Torrance
|
|
|
|
*/
|
|
|
|
class MicrofacetDistribution {
|
|
|
|
public:
|
|
|
|
/// Supported distribution types
|
|
|
|
enum EType {
|
|
|
|
/// Beckmann distribution derived from Gaussian random surfaces
|
2011-07-07 11:39:55 +08:00
|
|
|
EBeckmann = 0,
|
2011-07-06 23:52:02 +08:00
|
|
|
/// Long-tailed distribution proposed by Walter et al.
|
2011-07-08 03:57:35 +08:00
|
|
|
EGGX = 1,
|
|
|
|
/// Classical Phong distribution
|
|
|
|
EPhong = 2,
|
2011-07-07 11:39:55 +08:00
|
|
|
/// Anisotropic distribution by Ashikhmin and Shirley
|
|
|
|
EAshikhminShirley = 3
|
2011-07-06 23:52:02 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/// Create a microfacet distribution of the specified type
|
|
|
|
MicrofacetDistribution(EType type = EBeckmann)
|
|
|
|
: m_type(type) { }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief Create a microfacet distribution of the specified name
|
2011-07-07 11:39:55 +08:00
|
|
|
* (ggx/phong/beckmann/as)
|
2011-07-06 23:52:02 +08:00
|
|
|
*/
|
2011-07-11 20:17:40 +08:00
|
|
|
MicrofacetDistribution(const std::string &name) : m_type(EBeckmann) {
|
2011-07-07 09:07:32 +08:00
|
|
|
std::string distr = boost::to_lower_copy(name);
|
2011-07-06 23:52:02 +08:00
|
|
|
|
|
|
|
if (distr == "beckmann")
|
|
|
|
m_type = EBeckmann;
|
|
|
|
else if (distr == "phong")
|
|
|
|
m_type = EPhong;
|
|
|
|
else if (distr == "ggx")
|
|
|
|
m_type = EGGX;
|
2011-07-07 11:39:55 +08:00
|
|
|
else if (distr == "as")
|
|
|
|
m_type = EAshikhminShirley;
|
2011-07-06 23:52:02 +08:00
|
|
|
else
|
|
|
|
SLog(EError, "Specified an invalid distribution \"%s\", must be "
|
2011-07-07 11:39:55 +08:00
|
|
|
"\"beckmann\", \"phong\", \"ggx\", or \"as\"!", distr.c_str());
|
2011-07-06 23:52:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Return the distribution type
|
|
|
|
inline EType getType() const { return m_type; }
|
|
|
|
|
2011-07-11 20:17:40 +08:00
|
|
|
/// Is this an anisotropic microfacet distribution?
|
|
|
|
bool isAnisotropic() const {
|
|
|
|
return m_type == EAshikhminShirley;
|
|
|
|
}
|
|
|
|
|
2011-07-06 23:52:02 +08:00
|
|
|
/**
|
|
|
|
* \brief Convert the roughness values so that they behave similarly to the
|
|
|
|
* Beckmann distribution.
|
|
|
|
*
|
|
|
|
* Also clamps to the minimal roughness 1e-4 to avoid numerical issues
|
|
|
|
* (For lower roughness values, please switch to the smooth BSDF variants)
|
|
|
|
*/
|
|
|
|
Float transformRoughness(Float value) const {
|
2011-07-07 11:39:55 +08:00
|
|
|
if (m_type == EPhong || m_type == EAshikhminShirley)
|
2011-07-06 23:52:02 +08:00
|
|
|
value = 2 / (value * value) - 2;
|
|
|
|
return std::max(value, (Float) 1e-4f);
|
|
|
|
}
|
2011-07-11 20:17:40 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief Implements the microfacet distribution function D
|
|
|
|
*
|
|
|
|
* \param m The microsurface normal
|
|
|
|
* \param alpha The surface roughness
|
|
|
|
*/
|
|
|
|
inline Float eval(const Vector &m, Float alpha) const {
|
|
|
|
return eval(m, alpha, alpha);
|
|
|
|
}
|
2011-07-06 23:52:02 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief Implements the microfacet distribution function D
|
|
|
|
*
|
|
|
|
* \param m The microsurface normal
|
2011-07-11 20:17:40 +08:00
|
|
|
* \param alphaU The surface roughness in the tangent direction
|
|
|
|
* \param alphaV The surface roughness in the bitangent direction
|
2011-07-06 23:52:02 +08:00
|
|
|
*/
|
2011-07-07 11:39:55 +08:00
|
|
|
Float eval(const Vector &m, Float alphaU, Float alphaV) const {
|
2011-07-06 23:52:02 +08:00
|
|
|
if (Frame::cosTheta(m) <= 0)
|
|
|
|
return 0.0f;
|
|
|
|
|
|
|
|
Float result;
|
|
|
|
switch (m_type) {
|
|
|
|
case EBeckmann: {
|
|
|
|
/* Beckmann distribution function for Gaussian random surfaces */
|
2011-07-07 20:36:22 +08:00
|
|
|
const Float ex = Frame::tanTheta(m) / alphaU;
|
|
|
|
result = std::exp(-(ex*ex)) / (M_PI * alphaU*alphaU *
|
2011-07-06 23:52:02 +08:00
|
|
|
std::pow(Frame::cosTheta(m), (Float) 4.0f));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EGGX: {
|
|
|
|
/* Empirical GGX distribution function for rough surfaces */
|
|
|
|
const Float tanTheta = Frame::tanTheta(m),
|
|
|
|
cosTheta = Frame::cosTheta(m);
|
|
|
|
|
2011-07-07 20:36:22 +08:00
|
|
|
const Float root = alphaU / (cosTheta*cosTheta *
|
|
|
|
(alphaU*alphaU + tanTheta*tanTheta));
|
2011-07-06 23:52:02 +08:00
|
|
|
|
|
|
|
result = INV_PI * (root * root);
|
|
|
|
}
|
|
|
|
break;
|
2011-07-07 11:39:55 +08:00
|
|
|
|
2011-07-08 03:57:35 +08:00
|
|
|
case EPhong: {
|
|
|
|
/* Phong distribution function */
|
|
|
|
result = (alphaU + 2) * INV_TWOPI
|
|
|
|
* std::pow(Frame::cosTheta(m), alphaU);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2011-07-07 11:39:55 +08:00
|
|
|
case EAshikhminShirley: {
|
|
|
|
const Float cosTheta = Frame::cosTheta(m);
|
2011-07-07 20:36:22 +08:00
|
|
|
const Float ds = 1 - cosTheta * cosTheta;
|
|
|
|
if (ds < 0)
|
|
|
|
return 0.0f;
|
|
|
|
const Float exponent = (alphaU * m.x * m.x
|
|
|
|
+ alphaV * m.y * m.y) / ds;
|
|
|
|
result = std::sqrt((alphaU + 2) * (alphaV + 2))
|
2011-07-07 11:39:55 +08:00
|
|
|
* INV_TWOPI * std::pow(cosTheta, exponent);
|
|
|
|
}
|
|
|
|
break;
|
2011-07-07 20:36:22 +08:00
|
|
|
|
2011-07-06 23:52:02 +08:00
|
|
|
default:
|
|
|
|
SLog(EError, "Invalid distribution function!");
|
|
|
|
return 0.0f;
|
|
|
|
}
|
2011-07-07 20:36:22 +08:00
|
|
|
|
2011-07-06 23:52:02 +08:00
|
|
|
/* Prevent potential numerical issues in other stages of the model */
|
|
|
|
if (result < 1e-20f)
|
|
|
|
result = 0;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2011-07-07 11:39:55 +08:00
|
|
|
|
2011-07-07 20:36:22 +08:00
|
|
|
/**
|
|
|
|
* \brief Returns the density function associated with
|
2011-07-11 20:17:40 +08:00
|
|
|
* the \ref sample() function.
|
|
|
|
* \param m The microsurface normal
|
|
|
|
* \param alpha The surface roughness
|
|
|
|
*/
|
|
|
|
inline Float pdf(const Vector &m, Float alpha) const {
|
|
|
|
return pdf(m, alpha, alpha);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief Returns the density function associated with
|
|
|
|
* the \ref sample() function.
|
|
|
|
* \param m The microsurface normal
|
|
|
|
* \param alphaU The surface roughness in the tangent direction
|
|
|
|
* \param alphaV The surface roughness in the bitangent direction
|
2011-07-07 20:36:22 +08:00
|
|
|
*/
|
|
|
|
Float pdf(const Vector &m, Float alphaU, Float alphaV) const {
|
|
|
|
/* Usually, this is just D(m) * cos(theta_M) */
|
|
|
|
if (m_type != EAshikhminShirley)
|
|
|
|
return eval(m, alphaU, alphaV) * Frame::cosTheta(m);
|
|
|
|
|
|
|
|
/* For the Ashikhmin-Shirley model, the sampling density
|
2011-07-08 03:34:39 +08:00
|
|
|
does not include the cos(theta_M) factor, and the
|
|
|
|
normalization is slightly different than in eval(). */
|
2011-07-07 20:36:22 +08:00
|
|
|
const Float cosTheta = Frame::cosTheta(m);
|
|
|
|
const Float ds = 1 - cosTheta * cosTheta;
|
|
|
|
if (ds < 0)
|
|
|
|
return 0.0f;
|
|
|
|
const Float exponent = (alphaU * m.x * m.x
|
|
|
|
+ alphaV * m.y * m.y) / ds;
|
|
|
|
Float result = std::sqrt((alphaU + 1) * (alphaV + 1))
|
|
|
|
* INV_TWOPI * std::pow(cosTheta, exponent);
|
|
|
|
|
|
|
|
/* Prevent potential numerical issues in other stages of the model */
|
|
|
|
if (result < 1e-20f)
|
|
|
|
result = 0;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2011-07-07 11:39:55 +08:00
|
|
|
/// Helper routine: sample the first quadrant of the A&S distribution
|
|
|
|
void sampleFirstQuadrant(Float alphaU, Float alphaV, Float u1, Float u2,
|
|
|
|
Float &phi, Float &cosTheta) const {
|
|
|
|
if (alphaU == alphaV)
|
|
|
|
phi = M_PI * u1 * 0.5f;
|
|
|
|
else
|
2011-07-07 20:36:22 +08:00
|
|
|
phi = std::atan(
|
|
|
|
std::sqrt((alphaU + 1.0f) / (alphaV + 1.0f)) *
|
2011-07-07 11:39:55 +08:00
|
|
|
std::tan(M_PI * u1 * 0.5f));
|
|
|
|
const Float cosPhi = std::cos(phi), sinPhi = std::sin(phi);
|
|
|
|
cosTheta = std::pow(u2, 1.0f /
|
|
|
|
(alphaU * cosPhi * cosPhi + alphaV * sinPhi * sinPhi + 1.0f));
|
|
|
|
}
|
|
|
|
|
2011-07-06 23:52:02 +08:00
|
|
|
/**
|
2011-07-07 20:36:22 +08:00
|
|
|
* \brief Draw a sample from the microsurface normal distribution
|
2011-07-06 23:52:02 +08:00
|
|
|
*
|
|
|
|
* \param sample A uniformly distributed 2D sample
|
2011-07-11 20:17:40 +08:00
|
|
|
* \param alpha The surface roughness
|
|
|
|
*/
|
|
|
|
inline Normal sample(const Point2 &sample, Float alpha) const {
|
|
|
|
return MicrofacetDistribution::sample(sample, alpha, alpha);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief Draw a sample from the microsurface normal distribution
|
|
|
|
*
|
|
|
|
* \param sample A uniformly distributed 2D sample
|
|
|
|
* \param alphaU The surface roughness in the tangent direction
|
|
|
|
* \param alphaV The surface roughness in the bitangent direction
|
2011-07-06 23:52:02 +08:00
|
|
|
*/
|
2011-07-07 11:39:55 +08:00
|
|
|
Normal sample(const Point2 &sample, Float alphaU, Float alphaV) const {
|
2011-07-06 23:52:02 +08:00
|
|
|
/* The azimuthal component is always selected
|
|
|
|
uniformly regardless of the distribution */
|
2011-08-07 09:52:39 +08:00
|
|
|
Float cosThetaM = 0.0f, phiM = (2.0f * M_PI) * sample.y;
|
|
|
|
|
2011-07-06 23:52:02 +08:00
|
|
|
switch (m_type) {
|
2011-08-07 09:52:39 +08:00
|
|
|
case EBeckmann: {
|
|
|
|
Float tanThetaMSqr = -alphaU*alphaU * std::log(1.0f - sample.x);
|
|
|
|
cosThetaM = 1.0f / std::sqrt(1 + tanThetaMSqr);
|
|
|
|
}
|
2011-07-06 23:52:02 +08:00
|
|
|
break;
|
2011-08-07 09:52:39 +08:00
|
|
|
|
|
|
|
case EGGX: {
|
|
|
|
Float tanThetaMSqr = alphaU * alphaU * sample.x / (1.0f - sample.x);
|
|
|
|
cosThetaM = 1.0f / std::sqrt(1 + tanThetaMSqr);
|
|
|
|
}
|
2011-07-06 23:52:02 +08:00
|
|
|
break;
|
2011-07-07 20:36:22 +08:00
|
|
|
|
2011-08-07 09:52:39 +08:00
|
|
|
case EPhong: {
|
|
|
|
cosThetaM = std::pow(sample.x, 1 / (alphaU + 2));
|
|
|
|
}
|
2011-07-08 03:57:35 +08:00
|
|
|
break;
|
2011-08-07 09:52:39 +08:00
|
|
|
|
2011-07-07 11:39:55 +08:00
|
|
|
case EAshikhminShirley: {
|
|
|
|
/* Sampling method based on code from PBRT */
|
2011-07-07 20:36:22 +08:00
|
|
|
if (sample.x < 0.25f) {
|
2011-07-07 11:39:55 +08:00
|
|
|
sampleFirstQuadrant(alphaU, alphaV,
|
2011-08-07 09:52:39 +08:00
|
|
|
4 * sample.x, sample.y, phiM, cosThetaM);
|
2011-07-07 11:39:55 +08:00
|
|
|
} else if (sample.x < 0.5f) {
|
|
|
|
sampleFirstQuadrant(alphaU, alphaV,
|
2011-08-07 09:52:39 +08:00
|
|
|
4 * (0.5f - sample.x), sample.y, phiM, cosThetaM);
|
|
|
|
phiM = M_PI - phiM;
|
2011-07-07 11:39:55 +08:00
|
|
|
} else if (sample.x < 0.75f) {
|
|
|
|
sampleFirstQuadrant(alphaU, alphaV,
|
2011-08-07 09:52:39 +08:00
|
|
|
4 * (sample.x - 0.5f), sample.y, phiM, cosThetaM);
|
|
|
|
phiM += M_PI;
|
2011-07-07 11:39:55 +08:00
|
|
|
} else {
|
|
|
|
sampleFirstQuadrant(alphaU, alphaV,
|
2011-08-07 09:52:39 +08:00
|
|
|
4 * (1 - sample.x), sample.y, phiM, cosThetaM);
|
|
|
|
phiM = 2 * M_PI - phiM;
|
2011-07-07 11:39:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2011-07-06 23:52:02 +08:00
|
|
|
default:
|
|
|
|
SLog(EError, "Invalid distribution function!");
|
|
|
|
}
|
2011-08-07 09:52:39 +08:00
|
|
|
|
|
|
|
const Float sinThetaM = std::sqrt(
|
|
|
|
std::max((Float) 0, 1 - cosThetaM*cosThetaM));
|
|
|
|
return Vector(
|
|
|
|
sinThetaM * std::cos(phiM),
|
|
|
|
sinThetaM * std::sin(phiM),
|
|
|
|
cosThetaM
|
|
|
|
);
|
2011-07-06 23:52:02 +08:00
|
|
|
}
|
2011-07-11 20:17:40 +08:00
|
|
|
|
2011-08-07 09:52:39 +08:00
|
|
|
|
2011-07-11 20:17:40 +08:00
|
|
|
/**
|
2011-08-07 09:52:39 +08:00
|
|
|
* \brief Draw a sample from the microsurface normal distribution
|
|
|
|
* and return the associated probability density
|
2011-07-11 20:17:40 +08:00
|
|
|
*
|
2011-08-07 09:52:39 +08:00
|
|
|
* \param sample A uniformly distributed 2D sample
|
|
|
|
* \param alphaU The surface roughness in the tangent direction
|
|
|
|
* \param alphaV The surface roughness in the bitangent direction
|
|
|
|
* \param pdf The probability density wrt. solid angles
|
2011-07-11 20:17:40 +08:00
|
|
|
*/
|
2011-08-07 09:52:39 +08:00
|
|
|
Normal sample(const Point2 &sample, Float alphaU, Float alphaV, Float &pdf) const {
|
|
|
|
/* The azimuthal component is always selected
|
|
|
|
uniformly regardless of the distribution */
|
|
|
|
Float cosThetaM = 0.0f;
|
|
|
|
|
2011-07-11 20:17:40 +08:00
|
|
|
switch (m_type) {
|
2011-08-07 09:52:39 +08:00
|
|
|
case EBeckmann: {
|
|
|
|
Float tanThetaMSqr = -alphaU*alphaU * std::log(1.0f - sample.x);
|
|
|
|
cosThetaM = 1.0f / std::sqrt(1 + tanThetaMSqr);
|
|
|
|
Float cosThetaM2 = cosThetaM * cosThetaM,
|
|
|
|
cosThetaM3 = cosThetaM2 * cosThetaM;
|
|
|
|
pdf = (1.0f - sample.x) / (M_PI * alphaU*alphaU * cosThetaM3);
|
|
|
|
}
|
|
|
|
break;
|
2011-07-11 20:17:40 +08:00
|
|
|
|
2011-08-07 09:52:39 +08:00
|
|
|
case EGGX: {
|
|
|
|
Float alphaUSqr = alphaU * alphaU;
|
|
|
|
Float tanThetaMSqr = alphaUSqr * sample.x / (1.0f - sample.x);
|
|
|
|
cosThetaM = 1.0f / std::sqrt(1 + tanThetaMSqr);
|
2011-07-11 20:17:40 +08:00
|
|
|
|
2011-08-07 09:52:39 +08:00
|
|
|
Float cosThetaM2 = cosThetaM * cosThetaM,
|
|
|
|
cosThetaM3 = cosThetaM2 * cosThetaM,
|
|
|
|
temp = alphaUSqr + tanThetaMSqr;
|
|
|
|
|
|
|
|
pdf = INV_PI * alphaUSqr / (cosThetaM3 * temp * temp);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EPhong: {
|
|
|
|
Float exponent = 1 / (alphaU + 2);
|
|
|
|
cosThetaM = std::pow(sample.x, exponent);
|
|
|
|
pdf = (alphaU + 2) * INV_TWOPI * std::pow(sample.x, (alphaU+1) * exponent);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EAshikhminShirley: {
|
|
|
|
Float phiM;
|
|
|
|
|
|
|
|
/* Sampling method based on code from PBRT */
|
|
|
|
if (sample.x < 0.25f) {
|
|
|
|
sampleFirstQuadrant(alphaU, alphaV,
|
|
|
|
4 * sample.x, sample.y, phiM, cosThetaM);
|
|
|
|
} else if (sample.x < 0.5f) {
|
|
|
|
sampleFirstQuadrant(alphaU, alphaV,
|
|
|
|
4 * (0.5f - sample.x), sample.y, phiM, cosThetaM);
|
|
|
|
phiM = M_PI - phiM;
|
|
|
|
} else if (sample.x < 0.75f) {
|
|
|
|
sampleFirstQuadrant(alphaU, alphaV,
|
|
|
|
4 * (sample.x - 0.5f), sample.y, phiM, cosThetaM);
|
|
|
|
phiM += M_PI;
|
|
|
|
} else {
|
|
|
|
sampleFirstQuadrant(alphaU, alphaV,
|
|
|
|
4 * (1 - sample.x), sample.y, phiM, cosThetaM);
|
|
|
|
phiM = 2 * M_PI - phiM;
|
|
|
|
}
|
|
|
|
const Float sinThetaM = std::sqrt(
|
|
|
|
std::max((Float) 0, 1 - cosThetaM*cosThetaM)),
|
|
|
|
sinPhiM = std::sin(phiM),
|
|
|
|
cosPhiM = std::cos(phiM);
|
|
|
|
|
|
|
|
const Float exponent = alphaU * cosPhiM*cosPhiM
|
|
|
|
+ alphaV * sinPhiM*sinPhiM;
|
|
|
|
pdf = std::sqrt((alphaU + 1) * (alphaV + 1))
|
|
|
|
* INV_TWOPI * std::pow(cosThetaM, exponent);
|
2011-08-07 11:31:45 +08:00
|
|
|
|
|
|
|
/* Prevent potential numerical issues in other stages of the model */
|
|
|
|
if (pdf < 1e-20f)
|
|
|
|
pdf = 0;
|
2011-08-07 09:52:39 +08:00
|
|
|
|
|
|
|
return Vector(
|
|
|
|
sinThetaM * cosPhiM,
|
|
|
|
sinThetaM * sinPhiM,
|
|
|
|
cosThetaM
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
2011-07-11 20:17:40 +08:00
|
|
|
default:
|
|
|
|
SLog(EError, "Invalid distribution function!");
|
|
|
|
}
|
2011-08-07 09:52:39 +08:00
|
|
|
|
2011-08-07 11:31:45 +08:00
|
|
|
/* Prevent potential numerical issues in other stages of the model */
|
|
|
|
if (pdf < 1e-20f)
|
|
|
|
pdf = 0;
|
|
|
|
|
2011-08-07 09:52:39 +08:00
|
|
|
const Float sinThetaM = std::sqrt(
|
|
|
|
std::max((Float) 0, 1 - cosThetaM*cosThetaM));
|
|
|
|
Float phiM = (2.0f * M_PI) * sample.y;
|
|
|
|
return Vector(
|
|
|
|
sinThetaM * std::cos(phiM),
|
|
|
|
sinThetaM * std::sin(phiM),
|
|
|
|
cosThetaM
|
|
|
|
);
|
2011-07-11 20:17:40 +08:00
|
|
|
}
|
2011-08-07 09:52:39 +08:00
|
|
|
|
2011-07-06 23:52:02 +08:00
|
|
|
/**
|
|
|
|
* \brief Smith's shadow-masking function G1 for each
|
|
|
|
* of the supported microfacet distributions
|
|
|
|
*
|
|
|
|
* \param v An arbitrary direction
|
2011-07-07 09:07:32 +08:00
|
|
|
* \param m The microsurface normal
|
2011-07-06 23:52:02 +08:00
|
|
|
* \param alpha The surface roughness
|
|
|
|
*/
|
|
|
|
Float smithG1(const Vector &v, const Vector &m, Float alpha) const {
|
|
|
|
const Float tanTheta = std::abs(Frame::tanTheta(v));
|
2011-07-07 09:29:44 +08:00
|
|
|
|
2011-07-06 23:52:02 +08:00
|
|
|
/* perpendicular incidence -- no shadowing/masking */
|
|
|
|
if (tanTheta == 0.0f)
|
|
|
|
return 1.0f;
|
|
|
|
|
|
|
|
/* Can't see the back side from the front and vice versa */
|
|
|
|
if (dot(v, m) * Frame::cosTheta(v) <= 0)
|
|
|
|
return 0.0f;
|
|
|
|
|
|
|
|
switch (m_type) {
|
|
|
|
case EPhong:
|
|
|
|
case EBeckmann: {
|
2011-07-08 03:57:35 +08:00
|
|
|
Float a;
|
|
|
|
/* Approximation recommended by Bruce Walter: Use
|
|
|
|
the Beckmann shadowing-masking function with
|
|
|
|
specially chosen roughness value */
|
|
|
|
if (m_type != EBeckmann)
|
|
|
|
a = std::sqrt(0.5f * alpha + 1) / tanTheta;
|
|
|
|
else
|
|
|
|
a = 1.0f / (alpha * tanTheta);
|
|
|
|
|
2011-07-06 23:52:02 +08:00
|
|
|
if (a >= 1.6f)
|
|
|
|
return 1.0f;
|
|
|
|
|
2011-07-08 03:57:35 +08:00
|
|
|
/* Use a fast and accurate (<0.35% rel. error) rational
|
|
|
|
approximation to the shadowing-masking function */
|
|
|
|
const Float aSqr = a * a;
|
2011-07-06 23:52:02 +08:00
|
|
|
return (3.535f * a + 2.181f * aSqr)
|
|
|
|
/ (1.0f + 2.276f * a + 2.577f * aSqr);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EGGX: {
|
|
|
|
const Float root = alpha * tanTheta;
|
|
|
|
return 2.0f / (1.0f + std::sqrt(1.0f + root*root));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
SLog(EError, "Invalid distribution function!");
|
|
|
|
return 0.0f;
|
|
|
|
}
|
|
|
|
}
|
2011-07-07 09:07:32 +08:00
|
|
|
|
|
|
|
/**
|
2011-07-07 09:29:44 +08:00
|
|
|
* \brief Shadow-masking function for each of the supported
|
|
|
|
* microfacet distributions
|
2011-07-07 09:07:32 +08:00
|
|
|
*
|
|
|
|
* \param wi The incident direction
|
|
|
|
* \param wo The exitant direction
|
|
|
|
* \param m The microsurface normal
|
|
|
|
* \param alpha The surface roughness
|
|
|
|
*/
|
2011-07-11 20:17:40 +08:00
|
|
|
inline Float G(const Vector &wi, const Vector &wo, const Vector &m, Float alpha) const {
|
|
|
|
return G(wi, wo, m, alpha, alpha);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \brief Shadow-masking function for each of the supported
|
|
|
|
* microfacet distributions
|
|
|
|
*
|
|
|
|
* \param wi The incident direction
|
|
|
|
* \param wo The exitant direction
|
|
|
|
* \param m The microsurface normal
|
|
|
|
* \param alphaU The surface roughness in the tangent direction
|
|
|
|
* \param alphaV The surface roughness in the bitangent direction
|
|
|
|
*/
|
2011-07-07 11:39:55 +08:00
|
|
|
Float G(const Vector &wi, const Vector &wo, const Vector &m, Float alphaU, Float alphaV) const {
|
2011-07-08 05:06:43 +08:00
|
|
|
if (m_type != EAshikhminShirley) {
|
|
|
|
return smithG1(wi, m, alphaU)
|
|
|
|
* smithG1(wo, m, alphaU);
|
|
|
|
} else {
|
2011-07-08 06:36:02 +08:00
|
|
|
/* Can't see the back side from the front and vice versa */
|
|
|
|
if (dot(wi, m) * Frame::cosTheta(wi) <= 0 ||
|
|
|
|
dot(wo, m) * Frame::cosTheta(wo) <= 0)
|
|
|
|
return 0.0f;
|
2011-07-08 07:59:49 +08:00
|
|
|
|
2011-07-08 05:06:43 +08:00
|
|
|
/* Infinite groove shadowing/masking */
|
2011-07-08 07:59:49 +08:00
|
|
|
const Float nDotM = Frame::cosTheta(m),
|
|
|
|
nDotWo = Frame::cosTheta(wo),
|
|
|
|
nDotWi = Frame::cosTheta(wi),
|
2011-07-11 20:17:40 +08:00
|
|
|
woDotM = dot(wo, m),
|
|
|
|
wiDotM = dot(wi, m);
|
2011-07-08 07:59:49 +08:00
|
|
|
|
|
|
|
return std::min((Float) 1,
|
|
|
|
std::min(std::abs(2 * nDotM * nDotWo / woDotM),
|
2011-07-11 20:17:40 +08:00
|
|
|
std::abs(2 * nDotM * nDotWi / wiDotM)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-07-11 22:38:46 +08:00
|
|
|
* \brief Compute a spline representation for the overall Fresnel
|
|
|
|
* transmittance through a rough interface
|
2011-07-11 20:17:40 +08:00
|
|
|
*
|
2011-07-11 22:38:46 +08:00
|
|
|
* This function essentially computes the integral of
|
|
|
|
* 1 - \int_{S^2} f(w_i, w_o) * dw_o
|
2011-07-11 20:17:40 +08:00
|
|
|
* for incident directions 'wi' with a range of different inclinations
|
2011-07-11 22:38:46 +08:00
|
|
|
* (where f denotes a Cook-Torrance style reflectance model). It returns
|
|
|
|
* a cubic spline interpolation parameterized by the cosine of the angle
|
|
|
|
* between 'wi' and the (macro-) surface normal.
|
|
|
|
*
|
|
|
|
* \remark This only works for isotropic microfacet distributions
|
2011-07-11 20:17:40 +08:00
|
|
|
*/
|
2011-07-11 22:38:46 +08:00
|
|
|
CubicSpline *computeRoughTransmittance(Float extIOR, Float intIOR, Float alpha, size_t resolution) const {
|
2011-07-11 20:17:40 +08:00
|
|
|
if (isAnisotropic())
|
2011-07-11 22:38:46 +08:00
|
|
|
SLog(EError, "MicrofacetDistribution::computeRoughTransmission(): only "
|
2011-07-11 20:17:40 +08:00
|
|
|
"supports isotropic distributions!");
|
|
|
|
|
|
|
|
NDIntegrator integrator(1, 2, 5000, 0, 1e-5f);
|
|
|
|
CubicSpline *spline = new CubicSpline(resolution);
|
|
|
|
size_t nEvals, nEvalsTotal = 0;
|
|
|
|
ref<Timer> timer = new Timer();
|
|
|
|
|
|
|
|
Float stepSize = (1.0f-2*Epsilon)/(resolution-1);
|
|
|
|
for (size_t i=0; i<resolution; ++i) {
|
|
|
|
Float z = stepSize * i + Epsilon;
|
|
|
|
Vector wi(std::sqrt(std::max((Float) 0, 1-z*z)), 0, z);
|
|
|
|
Float min[2] = {0, 0}, max[2] = {1, 1},
|
|
|
|
integral = 0, error = 0;
|
|
|
|
|
|
|
|
integrator.integrateVectorized(
|
2011-07-11 22:38:46 +08:00
|
|
|
boost::bind(&MicrofacetDistribution::integrand1, this,
|
2011-07-11 20:17:40 +08:00
|
|
|
wi, extIOR, intIOR, alpha, _1, _2, _3),
|
|
|
|
min, max, &integral, &error, &nEvals
|
|
|
|
);
|
|
|
|
|
|
|
|
spline->append(z, 1-integral);
|
|
|
|
|
|
|
|
nEvalsTotal += nEvals;
|
2011-07-08 05:06:43 +08:00
|
|
|
}
|
2011-07-12 08:57:49 +08:00
|
|
|
SLog(EInfo, "Created a " SIZE_T_FMT "-node cubic spline approximation to the rough Fresnel "
|
2011-07-11 20:17:40 +08:00
|
|
|
"transmittance (integration took %i ms and " SIZE_T_FMT " function evaluations)",
|
|
|
|
resolution, timer->getMilliseconds(), nEvalsTotal);
|
|
|
|
spline->build();
|
|
|
|
return spline;
|
2011-07-07 09:07:32 +08:00
|
|
|
}
|
2011-07-07 11:39:55 +08:00
|
|
|
|
2011-07-06 23:52:02 +08:00
|
|
|
std::string toString() const {
|
2011-07-07 09:07:32 +08:00
|
|
|
switch (m_type) {
|
2011-07-06 23:52:02 +08:00
|
|
|
case EBeckmann: return "beckmann"; break;
|
|
|
|
case EPhong: return "phong"; break;
|
|
|
|
case EGGX: return "ggx"; break;
|
2011-07-07 11:39:55 +08:00
|
|
|
case EAshikhminShirley: return "as"; break;
|
2011-07-06 23:52:02 +08:00
|
|
|
default:
|
|
|
|
SLog(EError, "Invalid distribution function");
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
2011-07-11 20:17:40 +08:00
|
|
|
protected:
|
2011-07-11 22:38:46 +08:00
|
|
|
/// Integrand helper function called by \ref computeRoughTransmission
|
|
|
|
void integrand1(const Vector &wi, Float extIOR, Float intIOR, Float alpha,
|
2011-07-11 20:17:40 +08:00
|
|
|
size_t nPts, const Float *in, Float *out) const {
|
|
|
|
for (int i=0; i<(int) nPts; ++i) {
|
|
|
|
Normal m = sample(Point2(in[2*i], in[2*i+1]), alpha);
|
|
|
|
Vector wo = 2 * dot(wi, m) * Vector(m) - wi;
|
2011-07-11 22:38:46 +08:00
|
|
|
if (Frame::cosTheta(wo) <= 0) {
|
2011-07-11 20:17:40 +08:00
|
|
|
out[i] = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Calculate the specular reflection component */
|
|
|
|
out[i] = std::abs(fresnel(dot(wi, m), extIOR, intIOR)
|
|
|
|
* G(wi, wo, m, alpha) * dot(wi, m) /
|
|
|
|
(Frame::cosTheta(wi) * Frame::cosTheta(m)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
protected:
|
2011-07-07 09:07:32 +08:00
|
|
|
EType m_type;
|
2011-07-06 23:52:02 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
MTS_NAMESPACE_END
|
|
|
|
|
|
|
|
#endif /* __MICROFACET_H */
|