mitsuba/src/bsdfs/microfacet.h

522 lines
15 KiB
C
Raw Normal View History

2011-07-06 23:52:02 +08:00
/*
This file is part of Mitsuba, a physically based rendering system.
Copyright (c) 2007-2012 by Wenzel Jakob and others.
2011-07-06 23:52:02 +08:00
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
#include <mitsuba/core/timer.h>
2011-07-06 23:52:02 +08:00
#include <boost/algorithm/string.hpp>
#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
EBeckmann = 0,
2011-07-06 23:52:02 +08:00
/// Long-tailed distribution proposed by Walter et al.
EGGX = 1,
/// Classical Phong distribution
EPhong = 2,
/// 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
* (ggx/phong/beckmann/as)
2011-07-06 23:52:02 +08:00
*/
MicrofacetDistribution(const std::string &name) : m_type(EBeckmann) {
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;
else if (distr == "as")
m_type = EAshikhminShirley;
else
2011-07-06 23:52:02 +08:00
SLog(EError, "Specified an invalid distribution \"%s\", must be "
"\"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; }
/// 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 exponent to to avoid numerical issues
2011-07-06 23:52:02 +08:00
* (For lower roughness values, please switch to the smooth BSDF variants)
*/
Float transformRoughness(Float value) const {
value = std::max(value, (Float) 1e-5f);
if (m_type == EPhong || m_type == EAshikhminShirley)
value = std::max(2 / (value * value) - 2, (Float) 0.1f);
return value;
2011-07-06 23:52:02 +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
* \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
*/
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;
2011-07-06 23:52:02 +08:00
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 = math::fastexp(-(ex*ex)) / (M_PI * alphaU*alphaU *
2011-07-06 23:52:02 +08:00
std::pow(Frame::cosTheta(m), (Float) 4.0f));
}
break;
2011-07-06 23:52:02 +08:00
case EGGX: {
/* Empirical GGX distribution function for rough surfaces */
const Float tanTheta = Frame::tanTheta(m),
cosTheta = Frame::cosTheta(m);
const Float root = alphaU / (cosTheta*cosTheta *
2011-07-07 20:36:22 +08:00
(alphaU*alphaU + tanTheta*tanTheta));
2011-07-06 23:52:02 +08:00
result = INV_PI * (root * root);
}
break;
case EPhong: {
/* Phong distribution function */
result = (alphaU + 2) * INV_TWOPI
* std::pow(Frame::cosTheta(m), alphaU);
}
break;
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
2011-07-07 20:36:22 +08:00
+ alphaV * m.y * m.y) / ds;
result = std::sqrt((alphaU + 2) * (alphaV + 2))
* 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;
2011-07-06 23:52:02 +08:00
return result;
}
2011-07-07 20:36:22 +08:00
/**
* \brief Returns the density function associated with
* 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
2011-07-07 20:36:22 +08:00
+ 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;
}
/// 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)) *
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
* \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
*/
Normal sample(const Point2 &sample, Float alphaU, Float alphaV) const {
/* The azimuthal component is always selected
2011-07-06 23:52:02 +08:00
uniformly regardless of the distribution */
Float cosThetaM = 0.0f, phiM = (2.0f * M_PI) * sample.y;
2011-07-06 23:52:02 +08:00
switch (m_type) {
case EBeckmann: {
Float tanThetaMSqr = -alphaU*alphaU * math::fastlog(1.0f - sample.x);
cosThetaM = 1.0f / std::sqrt(1 + tanThetaMSqr);
}
2011-07-06 23:52:02 +08:00
break;
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
case EPhong: {
cosThetaM = std::pow(sample.x, 1 / (alphaU + 2));
}
break;
case EAshikhminShirley: {
/* Sampling method based on code from PBRT */
2011-07-07 20:36:22 +08:00
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;
}
}
break;
default:
2011-07-06 23:52:02 +08:00
SLog(EError, "Invalid distribution function!");
}
const Float sinThetaM = std::sqrt(
std::max((Float) 0, 1 - cosThetaM*cosThetaM));
Float sinPhiM, cosPhiM;
math::sincos(phiM, &sinPhiM, &cosPhiM);
return Vector(
sinThetaM * cosPhiM,
sinThetaM * sinPhiM,
cosThetaM
);
2011-07-06 23:52:02 +08:00
}
/**
* \brief Draw a sample from the microsurface normal distribution
* and return the associated probability density
*
* \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
*/
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;
switch (m_type) {
case EBeckmann: {
Float tanThetaMSqr = -alphaU*alphaU * math::fastlog(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;
case EGGX: {
Float alphaUSqr = alphaU * alphaU;
Float tanThetaMSqr = alphaUSqr * sample.x / (1.0f - sample.x);
cosThetaM = 1.0f / std::sqrt(1 + tanThetaMSqr);
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;
return Vector(
sinThetaM * cosPhiM,
sinThetaM * sinPhiM,
cosThetaM
);
}
break;
2011-08-16 05:09:26 +08:00
default:
pdf = 0;
SLog(EError, "Invalid distribution function!");
}
2011-08-07 11:31:45 +08:00
/* Prevent potential numerical issues in other stages of the model */
if (pdf < 1e-20f)
pdf = 0;
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-06 23:52:02 +08:00
/**
* \brief Smith's shadowing-masking function G1 for each
2011-07-06 23:52:02 +08:00
* of the supported microfacet distributions
*
* \param v An arbitrary direction
* \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-06 23:52:02 +08:00
/* perpendicular incidence -- no shadowing/masking */
if (tanTheta == 0.0f)
return 1.0f;
2011-07-06 23:52:02 +08:00
/* Can't see the back side from the front and vice versa */
if (dot(v, m) * Frame::cosTheta(v) <= 0)
return 0.0f;
2011-07-06 23:52:02 +08:00
switch (m_type) {
case EPhong:
case EBeckmann: {
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;
/* Use a fast and accurate (<0.35% rel. error) rational
approximation to the shadowing-masking function */
const Float aSqr = a * a;
return (3.535f * a + 2.181f * aSqr)
2011-07-06 23:52:02 +08:00
/ (1.0f + 2.276f * a + 2.577f * aSqr);
}
break;
2011-07-06 23:52:02 +08:00
case EGGX: {
const Float root = alpha * tanTheta;
return 2.0f / (1.0f + std::sqrt(1.0f + root*root));
}
break;
2011-07-06 23:52:02 +08:00
default:
SLog(EError, "Invalid distribution function!");
return 0.0f;
}
}
/**
* \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 alpha The surface roughness
*/
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
*/
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 {
/* 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 05:06:43 +08:00
/* Infinite groove shadowing/masking */
const Float nDotM = Frame::cosTheta(m),
nDotWo = Frame::cosTheta(wo),
nDotWi = Frame::cosTheta(wi),
woDotM = dot(wo, m),
wiDotM = dot(wi, m);
return std::min((Float) 1,
std::min(std::abs(2 * nDotM * nDotWo / woDotM),
std::abs(2 * nDotM * nDotWi / wiDotM)));
}
}
2011-07-06 23:52:02 +08:00
std::string toString() const {
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;
case EAshikhminShirley: return "as"; break;
2011-07-06 23:52:02 +08:00
default:
SLog(EError, "Invalid distribution function");
return "";
}
}
protected:
EType m_type;
2011-07-06 23:52:02 +08:00
};
MTS_NAMESPACE_END
#endif /* __MICROFACET_H */