mitsuba/src/bsdfs/microfacet.h

711 lines
22 KiB
C++

/*
This file is part of Mitsuba, a physically based rendering system.
Copyright (c) 2007-2014 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
#include <mitsuba/mitsuba.h>
#include <mitsuba/core/frame.h>
#include <mitsuba/core/properties.h>
#include <boost/algorithm/string.hpp>
MTS_NAMESPACE_BEGIN
/**
* \brief Implementation of the Beckman and GGX / Trowbridge-Reitz microfacet
* distributions and various useful sampling routines
*
* Based on the papers
*
* "Microfacet Models for Refraction through Rough Surfaces"
* by Bruce Walter, Stephen R. Marschner, Hongsong Li, and Kenneth E. Torrance
*
* and
*
* "Importance Sampling Microfacet-Based BSDFs using the Distribution of Visible Normals"
* by Eric Heitz and Eugene D'Eon
*
* The visible normal sampling code was provided by Eric Heitz and Eugene D'Eon.
*/
class MicrofacetDistribution {
public:
/// Supported distribution types
enum EType {
/// Beckmann distribution derived from Gaussian random surfaces
EBeckmann = 0,
/// GGX: Long-tailed distribution for very rough surfaces (aka. Trowbridge-Reitz distr.)
EGGX = 1,
/// Phong distribution (with the anisotropic extension by Ashikhmin and Shirley)
EPhong = 2
};
/**
* Create an isotropic microfacet distribution of the specified type
*
* \param type
* The desired type of microfacet distribution
* \param alpha
* The surface roughness
*/
inline MicrofacetDistribution(EType type, Float alpha, bool sampleVisible = true)
: m_type(type), m_alphaU(alpha), m_alphaV(alpha), m_sampleVisible(sampleVisible),
m_exponentU(0.0f), m_exponentV(0.0f) {
if (m_type == EPhong)
computePhongExponent();
}
/**
* Create an anisotropic microfacet distribution of the specified type
*
* \param type
* The desired type of microfacet distribution
* \param alphaU
* The surface roughness in the tangent direction
* \param alphaV
* The surface roughness in the bitangent direction
*/
inline MicrofacetDistribution(EType type, Float alphaU, Float alphaV, bool sampleVisible = true)
: m_type(type), m_alphaU(alphaU), m_alphaV(alphaV), m_sampleVisible(sampleVisible),
m_exponentU(0.0f), m_exponentV(0.0f) {
if (m_type == EPhong)
computePhongExponent();
}
/**
* \brief Create a microfacet distribution from a Property data
* structure
*/
MicrofacetDistribution(const Properties &props, EType type = EBeckmann,
Float alphaU = 0.1f, Float alphaV = 0.1f, bool sampleVisible = true)
: m_type(type), m_alphaU(alphaU), m_alphaV(alphaV), m_exponentU(0.0f),
m_exponentV(0.0f) {
if (props.hasProperty("distribution")) {
std::string distr = boost::to_lower_copy(props.getString("distribution"));
if (distr == "beckmann")
m_type = EBeckmann;
else if (distr == "ggx")
m_type = EGGX;
else if (distr == "phong" || distr == "as")
m_type = EPhong;
else
SLog(EError, "Specified an invalid distribution \"%s\", must be "
"\"beckmann\", \"ggx\", or \"phong\"/\"as\"!", distr.c_str());
}
if (props.hasProperty("alpha")) {
m_alphaU = m_alphaV = props.getFloat("alpha");
if (props.hasProperty("alphaU") || props.hasProperty("alphaV"))
SLog(EError, "Microfacet model: please specify either 'alpha' or 'alphaU'/'alphaV'.");
} else if (props.hasProperty("alphaU") || props.hasProperty("alphaV")) {
if (!props.hasProperty("alphaU") || !props.hasProperty("alphaV"))
SLog(EError, "Microfacet model: both 'alphaU' and 'alphaV' must be specified.");
if (props.hasProperty("alpha"))
SLog(EError, "Microfacet model: please specify either 'alpha' or 'alphaU'/'alphaV'.");
m_alphaU = props.getFloat("alphaU");
m_alphaV = props.getFloat("alphaV");
}
m_sampleVisible = props.getBoolean("sampleVisible", sampleVisible);
/* Visible normal sampling is not supported for the Phong / Ashikhmin-Shirley distribution */
if (m_type == EPhong) {
m_sampleVisible = false;
computePhongExponent();
}
}
/// Return the distribution type
inline EType getType() const { return m_type; }
/// Return the roughness (isotropic case)
inline Float getAlpha() const { return m_alphaU; }
/// Return the roughness along the tangent direction
inline Float getAlphaU() const { return m_alphaU; }
/// Return the roughness along the bitangent direction
inline Float getAlphaV() const { return m_alphaV; }
/// Return the Phong exponent (isotropic case)
inline Float getExponent() const { return m_exponentU; }
/// Return the Phong exponent along the tangent direction
inline Float getExponentU() const { return m_exponentU; }
/// Return the Phong exponent along the bitangent direction
inline Float getExponentV() const { return m_exponentV; }
/// Return whether or not only visible normals are sampled?
inline bool getSampleVisible() const { return m_sampleVisible; }
/// Is this an anisotropic microfacet distribution?
inline bool isAnisotropic() const { return m_alphaU != m_alphaV; }
/// Is this an anisotropic microfacet distribution?
inline bool isIsotropic() const { return m_alphaU == m_alphaV; }
/// Scale the roughness values by some constant
inline void scaleAlpha(Float value) {
m_alphaU *= value;
m_alphaV *= value;
if (m_type == EPhong)
computePhongExponent();
}
/**
* \brief Evaluate the microfacet distribution function
*
* \param m
* The microfacet normal
*/
inline Float eval(const Vector &m) const {
if (Frame::cosTheta(m) <= 0)
return 0.0f;
Float cosTheta2 = Frame::cosTheta2(m);
Float beckmannExponent = ((m.x*m.x) / (m_alphaU * m_alphaU)
+ (m.y*m.y) / (m_alphaV * m_alphaV)) / cosTheta2;
Float result;
switch (m_type) {
case EBeckmann: {
/* Beckmann distribution function for Gaussian random surfaces - [Walter 2005] evaluation */
result = math::fastexp(-beckmannExponent) /
(M_PI * m_alphaU * m_alphaV * cosTheta2 * cosTheta2);
}
break;
case EGGX: {
/* GGX / Trowbridge-Reitz distribution function for rough surfaces */
Float root = ((Float) 1 + beckmannExponent) * cosTheta2;
result = (Float) 1 / (M_PI * m_alphaU * m_alphaV * root * root);
}
break;
case EPhong: {
/* Isotropic case: Phong distribution. Anisotropic case: Ashikhmin-Shirley distribution */
Float exponent = interpolatePhongExponent(m);
result = std::sqrt((m_exponentU + 2) * (m_exponentV + 2))
* INV_TWOPI * std::pow(Frame::cosTheta(m), exponent);
}
break;
default:
SLog(EError, "Invalid distribution type!");
return -1;
}
/* Prevent potential numerical issues in other stages of the model */
if (result * Frame::cosTheta(m) < 1e-20f)
result = 0;
return result;
}
/**
* \brief Wrapper function which calls \ref sampleAll() or \ref sampleVisible()
* depending on the parameters of this class
*/
inline Normal sample(const Vector &wi, const Point2 &sample, Float &pdf) const {
Normal m;
if (m_sampleVisible) {
m = sampleVisible(wi, sample);
pdf = pdfVisible(wi, m);
} else {
m = sampleAll(sample, pdf);
}
return m;
}
/**
* \brief Wrapper function which calls \ref sampleAll() or \ref sampleVisible()
* depending on the parameters of this class
*/
inline Normal sample(const Vector &wi, const Point2 &sample) const {
Normal m;
if (m_sampleVisible) {
m = sampleVisible(wi, sample);
} else {
Float pdf;
m = sampleAll(sample, pdf);
}
return m;
}
/**
* \brief Wrapper function which calls \ref pdfAll() or \ref pdfVisible()
* depending on the parameters of this class
*/
inline Float pdf(const Vector &wi, const Vector &m) const {
if (m_sampleVisible)
return pdfVisible(wi, m);
else
return pdfAll(m);
}
/**
* \brief Draw a sample from the microfacet normal distribution
* (including *all* normals) and return the associated
* probability density
*
* \param sample
* A uniformly distributed 2D sample
* \param pdf
* The probability density wrt. solid angles
*/
inline Normal sampleAll(const Point2 &sample, Float &pdf) const {
/* The azimuthal component is always selected
uniformly regardless of the distribution */
Float cosThetaM = 0.0f;
Float sinPhiM, cosPhiM;
Float alphaSqr;
switch (m_type) {
case EBeckmann: {
/* Beckmann distribution function for Gaussian random surfaces */
if (isIsotropic()) {
/* Sample phi component (isotropic case) */
math::sincos((2.0f * M_PI) * sample.y, &sinPhiM, &cosPhiM);
alphaSqr = m_alphaU * m_alphaU;
} else {
/* Sample phi component (anisotropic case) */
Float phiM = std::atan(m_alphaV / m_alphaU *
std::tan(M_PI + 2*M_PI*sample.y)) + M_PI * std::floor(2*sample.y + 0.5f);
math::sincos(phiM, &sinPhiM, &cosPhiM);
Float cosSc = cosPhiM / m_alphaU, sinSc = sinPhiM / m_alphaV;
alphaSqr = 1.0f / (cosSc*cosSc + sinSc*sinSc);
}
/* Sample theta component */
Float tanThetaMSqr = alphaSqr * -math::fastlog(1.0f - sample.x);
cosThetaM = 1.0f / std::sqrt(1.0f + tanThetaMSqr);
/* Compute probability density of the sampled position */
pdf = (1.0f - sample.x) / (M_PI*m_alphaU*m_alphaV*cosThetaM*cosThetaM*cosThetaM);
}
break;
case EGGX: {
/* GGX / Trowbridge-Reitz distribution function for rough surfaces */
if (isIsotropic()) {
/* Sample phi component (isotropic case) */
math::sincos((2.0f * M_PI) * sample.y, &sinPhiM, &cosPhiM);
/* Sample theta component */
alphaSqr = m_alphaU*m_alphaU;
} else {
/* Sample phi component (anisotropic case) */
Float phiM = std::atan(m_alphaV / m_alphaU *
std::tan(M_PI + 2*M_PI*sample.y)) + M_PI * std::floor(2*sample.y + 0.5f);
math::sincos(phiM, &sinPhiM, &cosPhiM);
Float cosSc = cosPhiM / m_alphaU, sinSc = sinPhiM / m_alphaV;
alphaSqr = 1.0f / (cosSc*cosSc + sinSc*sinSc);
}
/* Sample theta component */
Float tanThetaMSqr = alphaSqr * sample.x / (1.0f - sample.x);
cosThetaM = 1.0f / std::sqrt(1.0f + tanThetaMSqr);
/* Compute probability density of the sampled position */
Float temp = 1+tanThetaMSqr/alphaSqr;
pdf = INV_PI / (m_alphaU*m_alphaV*cosThetaM*cosThetaM*cosThetaM*temp*temp);
}
break;
case EPhong: {
Float phiM;
Float exponent;
if (isIsotropic()) {
phiM = (2.0f * M_PI) * sample.y;
exponent = m_exponentU;
} else {
/* Sampling method based on code from PBRT */
if (sample.y < 0.25f) {
sampleFirstQuadrant(4 * sample.y, phiM, exponent);
} else if (sample.y < 0.5f) {
sampleFirstQuadrant(4 * (0.5f - sample.y), phiM, exponent);
phiM = M_PI - phiM;
} else if (sample.y < 0.75f) {
sampleFirstQuadrant(4 * (sample.y - 0.5f), phiM, exponent);
phiM += M_PI;
} else {
sampleFirstQuadrant(4 * (1 - sample.y), phiM, exponent);
phiM = 2 * M_PI - phiM;
}
}
math::sincos(phiM, &sinPhiM, &cosPhiM);
cosThetaM = std::pow(sample.x, 1.0f / (exponent + 2.0f));
pdf = std::sqrt((m_exponentU + 2.0f) * (m_exponentV + 2.0f))
* INV_TWOPI * std::pow(cosThetaM, exponent + 1.0f);
}
break;
default:
SLog(EError, "Invalid distribution type!");
pdf = -1;
return Vector(-1);
}
/* Prevent potential numerical issues in other stages of the model */
if (pdf < 1e-20f)
pdf = 0;
Float sinThetaM = std::sqrt(
std::max((Float) 0, 1 - cosThetaM*cosThetaM));
return Vector(
sinThetaM * cosPhiM,
sinThetaM * sinPhiM,
cosThetaM
);
}
/**
* \brief Returns the density function associated with
* the \ref sampleAll() function.
*
* \param m
* The microfacet normal
*/
inline Float pdfAll(const Vector &m) const {
/* PDF is just D(m) * cos(theta_M) */
return eval(m) * Frame::cosTheta(m);
}
/**
* \brief Draw a sample from the distribution of visible normals
* and return the associated probability density
*
* \param _wi
* A reference direction that defines the set of visible normals
* \param sample
* A uniformly distributed 2D sample
* \param pdf
* The probability density wrt. solid angles
*/
inline Normal sampleVisible(const Vector &_wi, const Point2 &sample) const {
/* Step 1: stretch wi */
Vector wi = normalize(Vector(
m_alphaU * _wi.x,
m_alphaV * _wi.y,
_wi.z
));
/* Get polar coordinates */
Float theta = 0, phi = 0;
if (wi.z < (Float) 0.99999) {
theta = std::acos(wi.z);
phi = std::atan2(wi.y, wi.x);
}
Float sinPhi, cosPhi;
math::sincos(phi, &sinPhi, &cosPhi);
/* Step 2: simulate P22_{wi}(slope.x, slope.y, 1, 1) */
Vector2 slope = sampleVisible11(theta, sample);
/* Step 3: rotate */
slope = Vector2(
cosPhi * slope.x - sinPhi * slope.y,
sinPhi * slope.x + cosPhi * slope.y);
/* Step 4: unstretch */
slope.x *= m_alphaU;
slope.y *= m_alphaV;
/* Step 5: compute normal */
Float normalization = (Float) 1 / std::sqrt(slope.x*slope.x
+ slope.y*slope.y + (Float) 1.0);
return Normal(
-slope.x * normalization,
-slope.y * normalization,
normalization
);
}
/// Implements the probability density of the function \ref sampleVisible()
Float pdfVisible(const Vector &wi, const Vector &m) const {
return smithG1(wi, m) * absDot(wi, m) * eval(m) / std::abs(Frame::cosTheta(wi));
}
/**
* \brief Smith's shadowing-masking function G1 for each
* of the supported microfacet distributions
*
* \param v
* An arbitrary direction
* \param m
* The microfacet normal
*/
Float smithG1(const Vector &v, const Vector &m) const {
/* Ensure consistent orientation (can't see the back
of the microfacet from the front and vice versa) */
if (dot(v, m) * Frame::cosTheta(v) <= 0)
return 0.0f;
/* Perpendicular incidence -- no shadowing/masking */
Float tanTheta = std::abs(Frame::tanTheta(v));
if (tanTheta == 0.0f)
return 1.0f;
Float alpha = projectRoughness(v);
switch (m_type) {
case EPhong:
case EBeckmann: {
Float a = 1.0f / (alpha * tanTheta);
if (a >= 1.6f)
return 1.0f;
/* Use a fast and accurate (<0.35% rel. error) rational
approximation to the shadowing-masking function */
Float aSqr = a*a;
return (3.535f * a + 2.181f * aSqr)
/ (1.0f + 2.276f * a + 2.577f * aSqr);
}
break;
case EGGX: {
Float root = alpha * tanTheta;
return 2.0f / (1.0f + std::sqrt(1.0f + root*root));
}
break;
default:
SLog(EError, "Invalid distribution type!");
return -1.0f;
}
}
/**
* \brief Separable shadow-masking function based on Smith's
* one-dimensional masking model
*/
inline Float G(const Vector &wi, const Vector &wo, const Vector &m) const {
return smithG1(wi, m) * smithG1(wo, m);
}
/// Return a string representation of the name of a distribution
inline static std::string distributionName(EType type) {
switch (type) {
case EBeckmann: return "beckmann"; break;
case EGGX: return "ggx"; break;
case EPhong: return "phong"; break;
default: return "invalid"; break;
}
}
/// Return a string representation of the contents of this instance
std::string toString() const {
return formatString("MicrofacetDistribution[type=\"%s\", alphaU=%f, alphaV=%f]",
distributionName(m_type).c_str(), m_alphaU, m_alphaV);
}
protected:
/// Compute the effective roughness projected on direction \c v
inline Float projectRoughness(const Vector &v) const {
Float invSinTheta2 = 1 / Frame::sinTheta2(v);
if (isIsotropic() || invSinTheta2 <= 0)
return m_alphaU;
Float cosPhi2 = v.x * v.x * invSinTheta2;
Float sinPhi2 = v.y * v.y * invSinTheta2;
return std::sqrt(cosPhi2 * m_alphaU * m_alphaU + sinPhi2 * m_alphaV * m_alphaV);
}
/// Compute the interpolated roughness for the Phong model
inline Float interpolatePhongExponent(const Vector &v) const {
Float invSinTheta2 = 1 / Frame::sinTheta2(v);
if (isIsotropic() || invSinTheta2 <= 0)
return m_exponentU;
Float cosPhi2 = v.x * v.x * invSinTheta2;
Float sinPhi2 = v.y * v.y * invSinTheta2;
return m_exponentU * cosPhi2 + m_exponentV * sinPhi2;
}
/**
* \brief Visible normal sampling code for the alpha=1 case
*
* Source: supplemental material of "Importance Sampling
* Microfacet-Based BSDFs using the Distribution of Visible Normals"
*/
Vector2 sampleVisible11(Float thetaI, Point2 sample) const {
const Float SQRT_PI_INV = 1 / std::sqrt(M_PI);
Vector2 slope;
switch (m_type) {
case EBeckmann: {
/* Special case (normal incidence) */
if (thetaI < 1e-4f) {
Float sinPhi, cosPhi;
Float r = std::sqrt(-math::fastlog(1.0f-sample.x));
math::sincos(2 * M_PI * sample.y, &sinPhi, &cosPhi);
return Vector2(r * cosPhi, r * sinPhi);
}
/* The original inversion routine from the paper contained
discontinuities, which causes issues for QMC integration
and techniques like Kelemen-style MLT. The following code
performs a numerical inversion with better behavior */
Float tanThetaI = std::tan(thetaI);
Float cotThetaI = 1 / tanThetaI;
/* Search interval -- everything is parameterized
in the erf() domain */
Float a = -1, c = math::erf(cotThetaI);
Float sample_x = std::max(sample.x, (Float) 1e-6f);
/* Start with a good initial guess */
//Float b = (1-sample_x) * a + sample_x * c;
/* We can do better (inverse of an approximation computed in Mathematica) */
Float fit = 1 + thetaI*(-0.876f + thetaI * (0.4265f - 0.0594f*thetaI));
Float b = c - (1+c) * std::pow(1-sample_x, fit);
/* Normalization factor for the CDF */
Float normalization = 1 / (1 + c + SQRT_PI_INV*
tanThetaI*std::exp(-cotThetaI*cotThetaI));
int it = 0;
while (++it < 10) {
/* Bisection criterion -- the oddly-looking
boolean expression are intentional to check
for NaNs at little additional cost */
if (!(b >= a && b <= c))
b = 0.5f * (a + c);
/* Evaluate the CDF and its derivative
(i.e. the density function) */
Float invErf = math::erfinv(b);
Float value = normalization*(1 + b + SQRT_PI_INV*
tanThetaI*std::exp(-invErf*invErf)) - sample_x;
Float derivative = normalization * (1
- invErf*tanThetaI);
if (std::abs(value) < 1e-5f)
break;
/* Update bisection intervals */
if (value > 0)
c = b;
else
a = b;
b -= value / derivative;
}
/* Now convert back into a slope value */
slope.x = math::erfinv(b);
/* Simulate Y component */
slope.y = math::erfinv(2.0f*std::max(sample.y, (Float) 1e-6f) - 1.0f);
};
break;
case EGGX: {
/* Special case (normal incidence) */
if (thetaI < 1e-4f) {
Float sinPhi, cosPhi;
Float r = math::safe_sqrt(sample.x / (1 - sample.x));
math::sincos(2 * M_PI * sample.y, &sinPhi, &cosPhi);
return Vector2(r * cosPhi, r * sinPhi);
}
/* Precomputations */
Float tanThetaI = std::tan(thetaI);
Float a = 1 / tanThetaI;
Float G1 = 2.0f / (1.0f + math::safe_sqrt(1.0f + 1.0f / (a*a)));
/* Simulate X component */
Float A = 2.0f * sample.x / G1 - 1.0f;
if (std::abs(A) == 1)
A -= math::signum(A)*Epsilon;
Float tmp = 1.0f / (A*A - 1.0f);
Float B = tanThetaI;
Float D = math::safe_sqrt(B*B*tmp*tmp - (A*A - B*B) * tmp);
Float slope_x_1 = B * tmp - D;
Float slope_x_2 = B * tmp + D;
slope.x = (A < 0.0f || slope_x_2 > 1.0f / tanThetaI) ? slope_x_1 : slope_x_2;
/* Simulate Y component */
Float S;
if (sample.y > 0.5f) {
S = 1.0f;
sample.y = 2.0f * (sample.y - 0.5f);
} else {
S = -1.0f;
sample.y = 2.0f * (0.5f - sample.y);
}
/* Improved fit */
Float z =
(sample.y * (sample.y * (sample.y * (-(Float) 0.365728915865723) + (Float) 0.790235037209296) -
(Float) 0.424965825137544) + (Float) 0.000152998850436920) /
(sample.y * (sample.y * (sample.y * (sample.y * (Float) 0.169507819808272 - (Float) 0.397203533833404) -
(Float) 0.232500544458471) + (Float) 1) - (Float) 0.539825872510702);
slope.y = S * z * std::sqrt(1.0f + slope.x*slope.x);
};
break;
default:
SLog(EError, "Invalid distribution type!");
return Vector2(-1);
};
return slope;
}
/// Helper routine: convert from Beckmann-style roughness values to Phong exponents (Walter et al.)
void computePhongExponent() {
m_exponentU = std::max(2.0f / (m_alphaU * m_alphaU) - 2.0f, (Float) 0.0f);
m_exponentV = std::max(2.0f / (m_alphaV * m_alphaV) - 2.0f, (Float) 0.0f);
}
/// Helper routine: sample the azimuthal part of the first quadrant of the A&S distribution
void sampleFirstQuadrant(Float u1, Float &phi, Float &exponent) const {
Float cosPhi, sinPhi;
phi = std::atan(
std::sqrt((m_exponentU + 2.0f) / (m_exponentV + 2.0f)) *
std::tan(M_PI * u1 * 0.5f));
math::sincos(phi, &sinPhi, &cosPhi);
/* Return the interpolated roughness */
exponent = m_exponentU * cosPhi * cosPhi + m_exponentV * sinPhi * sinPhi;
}
protected:
EType m_type;
Float m_alphaU, m_alphaV;
bool m_sampleVisible;
Float m_exponentU, m_exponentV;
};
MTS_NAMESPACE_END
#endif /* __MICROFACET_H */