ported all microfacet models to a new visible normal sampling implementation

metadata
Wenzel Jakob 2014-07-31 22:33:00 +02:00
parent bdc43fcbd8
commit d02d4ebedf
8 changed files with 1218 additions and 784 deletions

View File

@ -78,8 +78,8 @@ def texify(texfile):
else: else:
check_call(['pdflatex', texfile]) check_call(['pdflatex', texfile])
check_call(['bibtex', texfile.replace('.tex', '.aux')]) check_call(['bibtex', texfile.replace('.tex', '.aux')])
check_call(['pdflatex', texfile]) #check_call(['pdflatex', texfile])
check_call(['pdflatex', texfile]) #check_call(['pdflatex', texfile])
os.chdir(os.path.dirname(os.path.abspath(__file__))) os.chdir(os.path.dirname(os.path.abspath(__file__)))
with open('plugins_generated.tex', 'w') as f: with open('plugins_generated.tex', 'w') as f:

View File

@ -563,3 +563,24 @@
year = 2008, year = 2008,
pages = {183--190} pages = {183--190}
} }
@article{Trowbridge19975Average,
author = {T. S. Trowbridge and K. P. Reitz},
journal = {J. Opt. Soc. Am.},
number = {5},
pages = {531--536},
publisher = {OSA},
title = {Average irregularity representation of a rough surface for ray reflection},
volume = {65},
month = {May},
year = {1975}
}
@article{Heitz1014Importance,
author = {Heitz, Eric and D'Eon, Eugene},
title = {Importance Sampling Microfacet-Based BSDFs using the Distribution of Visible Normals},
journal = {Computer Graphics Forum},
publisher = {Blackwell Publishing},
year = {2014},
month = Jun
}

File diff suppressed because it is too large Load Diff

View File

@ -31,17 +31,20 @@ MTS_NAMESPACE_BEGIN
* \parameter{distribution}{\String}{ * \parameter{distribution}{\String}{
* Specifies the type of microfacet normal distribution * Specifies the type of microfacet normal distribution
* used to model the surface roughness. * used to model the surface roughness.
* \vspace{-1mm}
* \begin{enumerate}[(i)] * \begin{enumerate}[(i)]
* \item \code{beckmann}: Physically-based distribution derived from * \item \code{beckmann}: Physically-based distribution derived from
* Gaussian random surfaces. This is the default. * Gaussian random surfaces. This is the default.\vspace{-1.5mm}
* \item \code{ggx}: New distribution proposed by * \item \code{ggx}: The GGX \cite{Walter07Microfacet} distribution (also known as
* Walter et al. \cite{Walter07Microfacet}, which is meant to better handle * Trowbridge-Reitz \cite{Trowbridge19975Average} distribution)
* the long tails observed in measurements of ground surfaces. * was designed to better approximate the long tails observed in measurements
* Renderings with this distribution may converge slowly. * of ground surfaces, which are not modeled by the Beckmann distribution.
* \item \code{phong}: Classical $\cos^p\theta$ distribution. * \vspace{-1.5mm}
* Due to the underlying microfacet theory, * \item \code{phong}: Classical Phong distribution.
* the use of this distribution here leads to more realistic * In most cases, the \code{ggx} and \code{beckmann} distributions
* behavior than the separately available \pluginref{phong} plugin. * should be preferred, since they provide better importance sampling
* and accurate shadowing/masking computations.
* \vspace{-4mm}
* \end{enumerate} * \end{enumerate}
* } * }
* \parameter{alpha}{\Float\Or\Texture}{ * \parameter{alpha}{\Float\Or\Texture}{
@ -50,6 +53,11 @@ MTS_NAMESPACE_BEGIN
* \emph{root mean square} (RMS) slope of the microfacets. * \emph{root mean square} (RMS) slope of the microfacets.
* \default{0.1}. * \default{0.1}.
* } * }
* \parameter{sampleVisible}{\Boolean}{
* Enables an improved importance sampling technique. Refer to
* pages \pageref{plg:roughconductor} and \pageref{sec:visiblenormal-sampling}
* for details. \default{\code{true}}
* }
* \parameter{intIOR}{\Float\Or\String}{Interior index of refraction specified * \parameter{intIOR}{\Float\Or\String}{Interior index of refraction specified
* numerically or using a known material name. \default{\texttt{bk7} / 1.5046}} * numerically or using a known material name. \default{\texttt{bk7} / 1.5046}}
* \parameter{extIOR}{\Float\Or\String}{Exterior index of refraction specified * \parameter{extIOR}{\Float\Or\String}{Exterior index of refraction specified
@ -90,9 +98,10 @@ MTS_NAMESPACE_BEGIN
* loss for materials that reflect much of their energy near or below the critical * loss for materials that reflect much of their energy near or below the critical
* angle (i.e. diffuse or very rough materials). * angle (i.e. diffuse or very rough materials).
* *
* The implementation here is influenced by the paper * The implementation here is motivated by the paper
* ``Arbitrarily Layered Micro-Facet Surfaces'' by Weidlich and * ``Arbitrarily Layered Micro-Facet Surfaces'' by Weidlich and
* Wilkie \cite{Weidlich2007Arbitrarily}. * Wilkie \cite{Weidlich2007Arbitrarily}, though the implementation
* works differently.
*/ */
class RoughCoating : public BSDF { class RoughCoating : public BSDF {
public: public:
@ -127,25 +136,23 @@ public:
m_specularReflectance = new ConstantSpectrumTexture( m_specularReflectance = new ConstantSpectrumTexture(
props.getSpectrum("specularReflectance", Spectrum(1.0f))); props.getSpectrum("specularReflectance", Spectrum(1.0f)));
m_distribution = MicrofacetDistribution( MicrofacetDistribution distr(props);
props.getString("distribution", "beckmann") m_type = distr.getType();
); m_sampleVisible = distr.getSampleVisible();
if (m_distribution.isAnisotropic()) if (distr.isAnisotropic())
Log(EError, "The 'roughcoating' plugin currently does not support " Log(EError, "The 'roughplastic' plugin currently does not support "
"anisotropic microfacet distributions!"); "anisotropic microfacet distributions!");
m_alpha = new ConstantFloatTexture( m_alpha = new ConstantFloatTexture(distr.getAlpha());
props.getFloat("alpha", 0.1f));
m_specularSamplingWeight = 0.0f; m_specularSamplingWeight = 0.0f;
} }
RoughCoating(Stream *stream, InstanceManager *manager) RoughCoating(Stream *stream, InstanceManager *manager)
: BSDF(stream, manager) { : BSDF(stream, manager) {
m_distribution = MicrofacetDistribution( m_type = (MicrofacetDistribution::EType) stream->readUInt();
(MicrofacetDistribution::EType) stream->readUInt() m_sampleVisible = stream->readBool();
);
m_nested = static_cast<BSDF *>(manager->getInstance(stream)); m_nested = static_cast<BSDF *>(manager->getInstance(stream));
m_sigmaA = static_cast<Texture *>(manager->getInstance(stream)); m_sigmaA = static_cast<Texture *>(manager->getInstance(stream));
m_specularReflectance = static_cast<Texture *>(manager->getInstance(stream)); m_specularReflectance = static_cast<Texture *>(manager->getInstance(stream));
@ -160,7 +167,8 @@ public:
void serialize(Stream *stream, InstanceManager *manager) const { void serialize(Stream *stream, InstanceManager *manager) const {
BSDF::serialize(stream, manager); BSDF::serialize(stream, manager);
stream->writeUInt((uint32_t) m_distribution.getType()); stream->writeUInt((uint32_t) m_type);
stream->writeBool(m_sampleVisible);
manager->serialize(stream, m_nested.get()); manager->serialize(stream, m_nested.get());
manager->serialize(stream, m_sigmaA.get()); manager->serialize(stream, m_sigmaA.get());
manager->serialize(stream, m_specularReflectance.get()); manager->serialize(stream, m_specularReflectance.get());
@ -200,8 +208,7 @@ public:
if (!m_roughTransmittance.get()) { if (!m_roughTransmittance.get()) {
/* Load precomputed data used to compute the rough /* Load precomputed data used to compute the rough
transmittance through the dielectric interface */ transmittance through the dielectric interface */
m_roughTransmittance = new RoughTransmittance( m_roughTransmittance = new RoughTransmittance(m_type);
m_distribution.getType());
m_roughTransmittance->checkEta(m_eta); m_roughTransmittance->checkEta(m_eta);
m_roughTransmittance->checkAlpha(m_alpha->getMinimum().average()); m_roughTransmittance->checkAlpha(m_alpha->getMinimum().average());
@ -254,9 +261,13 @@ public:
&& (bRec.component == -1 || bRec.component == (int) m_components.size()-1) && (bRec.component == -1 || bRec.component == (int) m_components.size()-1)
&& measure == ESolidAngle; && measure == ESolidAngle;
/* Evaluate the roughness texture */ /* Construct the microfacet distribution matching the
Float alpha = m_alpha->eval(bRec.its).average(); roughness values at the current surface position. */
Float alphaT = m_distribution.transformRoughness(alpha); MicrofacetDistribution distr(
m_type,
m_alpha->eval(bRec.its).average(),
m_sampleVisible
);
Spectrum result(0.0f); Spectrum result(0.0f);
if (hasSpecular && Frame::cosTheta(bRec.wo) * Frame::cosTheta(bRec.wi) > 0) { if (hasSpecular && Frame::cosTheta(bRec.wo) * Frame::cosTheta(bRec.wi) > 0) {
@ -264,14 +275,14 @@ public:
const Vector H = normalize(bRec.wo+bRec.wi) const Vector H = normalize(bRec.wo+bRec.wi)
* math::signum(Frame::cosTheta(bRec.wo)); * math::signum(Frame::cosTheta(bRec.wo));
/* Evaluate the microsurface normal distribution */ /* Evaluate the microfacet normal distribution */
const Float D = m_distribution.eval(H, alphaT); const Float D = distr.eval(H);
/* Fresnel term */ /* Fresnel term */
const Float F = fresnelDielectricExt(absDot(bRec.wi, H), m_eta); const Float F = fresnelDielectricExt(absDot(bRec.wi, H), m_eta);
/* Smith's shadow-masking function */ /* Smith's shadow-masking function */
const Float G = m_distribution.G(bRec.wi, bRec.wo, H, alphaT); const Float G = distr.G(bRec.wi, bRec.wo, H);
/* Calculate the specular reflection component */ /* Calculate the specular reflection component */
Float value = F * D * G / Float value = F * D * G /
@ -286,8 +297,8 @@ public:
bRecInt.wo = refractTo(EInterior, bRec.wo); bRecInt.wo = refractTo(EInterior, bRec.wo);
Spectrum nestedResult = m_nested->eval(bRecInt, measure) * Spectrum nestedResult = m_nested->eval(bRecInt, measure) *
m_roughTransmittance->eval(std::abs(Frame::cosTheta(bRec.wi)), alpha) * m_roughTransmittance->eval(std::abs(Frame::cosTheta(bRec.wi)), distr.getAlpha()) *
m_roughTransmittance->eval(std::abs(Frame::cosTheta(bRec.wo)), alpha); m_roughTransmittance->eval(std::abs(Frame::cosTheta(bRec.wo)), distr.getAlpha());
Spectrum sigmaA = m_sigmaA->eval(bRec.its) * m_thickness; Spectrum sigmaA = m_sigmaA->eval(bRec.its) * m_thickness;
if (!sigmaA.isZero()) if (!sigmaA.isZero())
@ -319,15 +330,19 @@ public:
const Vector H = normalize(bRec.wo+bRec.wi) const Vector H = normalize(bRec.wo+bRec.wi)
* math::signum(Frame::cosTheta(bRec.wo)); * math::signum(Frame::cosTheta(bRec.wo));
/* Evaluate the roughness texture */ /* Construct the microfacet distribution matching the
Float alpha = m_alpha->eval(bRec.its).average(); roughness values at the current surface position. */
Float alphaT = m_distribution.transformRoughness(alpha); MicrofacetDistribution distr(
m_type,
m_alpha->eval(bRec.its).average(),
m_sampleVisible
);
Float probNested, probSpecular; Float probNested, probSpecular;
if (hasSpecular && hasNested) { if (hasSpecular && hasNested) {
/* Find the probability of sampling the specular component */ /* Find the probability of sampling the specular component */
probSpecular = 1-m_roughTransmittance->eval( probSpecular = 1-m_roughTransmittance->eval(
std::abs(Frame::cosTheta(bRec.wi)), alpha); std::abs(Frame::cosTheta(bRec.wi)), distr.getAlpha());
/* Reallocate samples */ /* Reallocate samples */
probSpecular = (probSpecular*m_specularSamplingWeight) / probSpecular = (probSpecular*m_specularSamplingWeight) /
@ -344,8 +359,8 @@ public:
/* Jacobian of the half-direction mapping */ /* Jacobian of the half-direction mapping */
const Float dwh_dwo = 1.0f / (4.0f * absDot(bRec.wo, H)); const Float dwh_dwo = 1.0f / (4.0f * absDot(bRec.wo, H));
/* Evaluate the microsurface normal distribution */ /* Evaluate the microfacet model sampling density function */
const Float prob = m_distribution.pdf(H, alphaT); const Float prob = distr.pdf(bRec.wi, H);
result = prob * dwh_dwo * probSpecular; result = prob * dwh_dwo * probSpecular;
} }
@ -377,14 +392,19 @@ public:
bool choseSpecular = hasSpecular; bool choseSpecular = hasSpecular;
Point2 sample(_sample); Point2 sample(_sample);
/* Evaluate the roughness texture */ /* Construct the microfacet distribution matching the
Float alpha = m_alpha->eval(bRec.its).average(); roughness values at the current surface position. */
Float alphaT = m_distribution.transformRoughness(alpha); MicrofacetDistribution distr(
m_type,
m_alpha->eval(bRec.its).average(),
m_sampleVisible
);
Float probSpecular; Float probSpecular;
if (hasSpecular && hasNested) { if (hasSpecular && hasNested) {
/* Find the probability of sampling the diffuse component */ /* Find the probability of sampling the diffuse component */
probSpecular = 1 - m_roughTransmittance->eval(std::abs(Frame::cosTheta(bRec.wi)), alpha); probSpecular = 1 - m_roughTransmittance->eval(
std::abs(Frame::cosTheta(bRec.wi)), distr.getAlpha());
/* Reallocate samples */ /* Reallocate samples */
probSpecular = (probSpecular*m_specularSamplingWeight) / probSpecular = (probSpecular*m_specularSamplingWeight) /
@ -400,8 +420,8 @@ public:
} }
if (choseSpecular) { if (choseSpecular) {
/* Perfect specular reflection based on the microsurface normal */ /* Perfect specular reflection based on the microfacet normal */
Normal m = m_distribution.sample(sample, alphaT); Normal m = distr.sample(bRec.wi, sample);
bRec.wo = reflect(bRec.wi, m); bRec.wo = reflect(bRec.wi, m);
bRec.sampledComponent = (int) m_components.size() - 1; bRec.sampledComponent = (int) m_components.size() - 1;
bRec.sampledType = EGlossyReflection; bRec.sampledType = EGlossyReflection;
@ -464,7 +484,8 @@ public:
std::ostringstream oss; std::ostringstream oss;
oss << "RoughCoating[" << endl oss << "RoughCoating[" << endl
<< " id = \"" << getID() << "\"," << endl << " id = \"" << getID() << "\"," << endl
<< " distribution = " << m_distribution.toString() << "," << endl << " distribution = " << MicrofacetDistribution::distributionName(m_type) << "," << endl
<< " sampleVisible = " << m_sampleVisible << "," << endl
<< " alpha = " << indent(m_alpha->toString()) << "," << endl << " alpha = " << indent(m_alpha->toString()) << "," << endl
<< " sigmaA = " << indent(m_sigmaA->toString()) << "," << endl << " sigmaA = " << indent(m_sigmaA->toString()) << "," << endl
<< " specularReflectance = " << indent(m_specularReflectance->toString()) << "," << endl << " specularReflectance = " << indent(m_specularReflectance->toString()) << "," << endl
@ -480,7 +501,7 @@ public:
MTS_DECLARE_CLASS() MTS_DECLARE_CLASS()
private: private:
MicrofacetDistribution m_distribution; MicrofacetDistribution::EType m_type;
ref<RoughTransmittance> m_roughTransmittance; ref<RoughTransmittance> m_roughTransmittance;
ref<Texture> m_sigmaA; ref<Texture> m_sigmaA;
ref<Texture> m_alpha; ref<Texture> m_alpha;
@ -489,6 +510,7 @@ private:
Float m_eta, m_invEta; Float m_eta, m_invEta;
Float m_specularSamplingWeight; Float m_specularSamplingWeight;
Float m_thickness; Float m_thickness;
bool m_sampleVisible;
}; };
/** /**

View File

@ -31,32 +31,30 @@ MTS_NAMESPACE_BEGIN
* \parameter{distribution}{\String}{ * \parameter{distribution}{\String}{
* Specifies the type of microfacet normal distribution * Specifies the type of microfacet normal distribution
* used to model the surface roughness. * used to model the surface roughness.
* \vspace{-1mm}
* \begin{enumerate}[(i)] * \begin{enumerate}[(i)]
* \item \code{beckmann}: Physically-based distribution derived from * \item \code{beckmann}: Physically-based distribution derived from
* Gaussian random surfaces. This is the default.\vspace{-1mm} * Gaussian random surfaces. This is the default.\vspace{-1.5mm}
* \item \code{ggx}: New distribution proposed by * \item \code{ggx}: The GGX \cite{Walter07Microfacet} distribution (also known as
* Walter et al. \cite{Walter07Microfacet}, which is meant to better handle * Trowbridge-Reitz \cite{Trowbridge19975Average} distribution)
* the long tails observed in measurements of ground surfaces. * was designed to better approximate the long tails observed in measurements
* Renderings with this distribution may converge slowly.\vspace{-1mm} * of ground surfaces, which are not modeled by the Beckmann distribution.
* \item \code{phong}: Classical $\cos^p\theta$ distribution. * \vspace{-1.5mm}
* Due to the underlying microfacet theory, * \item \code{phong}: Anisotropic Phong distribution by
* the use of this distribution here leads to more realistic * Ashikhmin and Shirley \cite{Ashikhmin2005Anisotropic}.
* behavior than the separately available \pluginref{phong} plugin.\vspace{-1mm} * In most cases, the \code{ggx} and \code{beckmann} distributions
* \item \code{as}: Anisotropic Phong-style microfacet distribution proposed by * should be preferred, since they provide better importance sampling
* Ashikhmin and Shirley \cite{Ashikhmin2005Anisotropic}.\vspace{-3mm} * and accurate shadowing/masking computations.
* \vspace{-4mm}
* \end{enumerate} * \end{enumerate}
* } * }
* \parameter{alpha}{\Float\Or\Texture}{ * \parameter{alpha, alphaU, alphaV}{\Float\Or\Texture}{
* Specifies the roughness of the unresolved surface micro-geometry. * Specifies the roughness of the unresolved surface micro-geometry
* When the Beckmann distribution is used, this parameter is equal to the * along the tangent and bitangent directions. When the Beckmann
* \emph{root mean square} (RMS) slope of the microfacets. This * distribution is used, this parameter is equal to the
* parameter is only valid when \texttt{distribution=beckmann/phong/ggx}. * \emph{root mean square} (RMS) slope of the microfacets.
* \default{0.1}. * \code{alpha} is a convenience parameter to initialize both
* } * \code{alphaU} and \code{alphaV} to the same value. \default{0.1}.
* \parameter{alphaU, alphaV}{\Float\Or\Texture}{
* Specifies the anisotropic roughness values along the tangent and
* bitangent directions. These parameter are only valid when
* \texttt{distribution=as}. \default{0.1}.
* } * }
* \parameter{material}{\String}{Name of a material preset, see * \parameter{material}{\String}{Name of a material preset, see
* \tblref{conductor-iors}.\!\default{\texttt{Cu} / copper}} * \tblref{conductor-iors}.\!\default{\texttt{Cu} / copper}}
@ -66,30 +64,36 @@ MTS_NAMESPACE_BEGIN
* Real-valued index of refraction of the surrounding dielectric, * Real-valued index of refraction of the surrounding dielectric,
* or a material name of a dielectric \default{\code{air}} * or a material name of a dielectric \default{\code{air}}
* } * }
* \parameter{sampleVisible}{\Boolean}{
* Enables a sampling technique proposed by Heitz and D'Eon~\cite{Heitz1014Importance},
* which focuses computation on the visible parts of the microfacet normal
* distribution, considerably reducing variance in some cases.
* \default{\code{true}, i.e. use visible normal sampling}
* }
* \parameter{specular\showbreak Reflectance}{\Spectrum\Or\Texture}{Optional * \parameter{specular\showbreak Reflectance}{\Spectrum\Or\Texture}{Optional
* factor that can be used to modulate the specular reflection component. Note * factor that can be used to modulate the specular reflection component. Note
* that for physical realism, this parameter should never be touched. \default{1.0}} * that for physical realism, this parameter should never be touched. \default{1.0}}
* } * }
* \vspace{4mm} * \vspace{3mm}
* This plugin implements a realistic microfacet scattering model for rendering * This plugin implements a realistic microfacet scattering model for rendering
* rough conducting materials, such as metals. It can be interpreted as a fancy * rough conducting materials, such as metals. It can be interpreted as a fancy
* version of the Cook-Torrance model and should be preferred over * version of the Cook-Torrance model and should be preferred over
* heuristic models like \pluginref{phong} and \pluginref{ward} when possible. * heuristic models like \pluginref{phong} and \pluginref{ward} if possible.
* \renderings{ * \renderings{
* \rendering{Rough copper (Beckmann, $\alpha=0.1$)} * \rendering{Rough copper (Beckmann, $\alpha=0.1$)}
* {bsdf_roughconductor_copper.jpg} * {bsdf_roughconductor_copper.jpg}
* \rendering{Vertically brushed aluminium (Ashikhmin-Shirley, * \rendering{Vertically brushed aluminium (Anisotropic Phong,
* $\alpha_u=0.05,\ \alpha_v=0.3$), see * $\alpha_u=0.05,\ \alpha_v=0.3$), see
* \lstref{roughconductor-aluminium}} * \lstref{roughconductor-aluminium}}
* {bsdf_roughconductor_anisotropic_aluminium.jpg} * {bsdf_roughconductor_anisotropic_aluminium.jpg}
* } * }
* *
* Microfacet theory describes rough * Microfacet theory describes rough surfaces as an arrangement of unresolved
* surfaces as an arrangement of unresolved and ideally specular facets, whose * and ideally specular facets, whose normal directions are given by a
* normal directions are given by a specially chosen \emph{microfacet distribution}. * specially chosen \emph{microfacet distribution}. By accounting for shadowing
* By accounting for shadowing and masking effects between these facets, it is * and masking effects between these facets, it is possible to reproduce the
* possible to reproduce the important off-specular reflections peaks observed * important off-specular reflections peaks observed in real-world measurements
* in real-world measurements of such materials. * of such materials.
* *
* This plugin is essentially the ``roughened'' equivalent of the (smooth) plugin * This plugin is essentially the ``roughened'' equivalent of the (smooth) plugin
* \pluginref{conductor}. For very low values of $\alpha$, the two will * \pluginref{conductor}. For very low values of $\alpha$, the two will
@ -98,7 +102,7 @@ MTS_NAMESPACE_BEGIN
* *
* The implementation is based on the paper ``Microfacet Models * The implementation is based on the paper ``Microfacet Models
* for Refraction through Rough Surfaces'' by Walter et al. * for Refraction through Rough Surfaces'' by Walter et al.
* \cite{Walter07Microfacet}. It supports several different types of microfacet * \cite{Walter07Microfacet}. It supports three different types of microfacet
* distributions and has a texturable roughness parameter. * distributions and has a texturable roughness parameter.
* To facilitate the tedious task of specifying spectrally-varying index of * To facilitate the tedious task of specifying spectrally-varying index of
* refraction information, this plugin can access a set of measured materials * refraction information, this plugin can access a set of measured materials
@ -109,41 +113,48 @@ MTS_NAMESPACE_BEGIN
* 100% reflecting mirror. * 100% reflecting mirror.
* *
* When no parameters are given, the plugin activates the default settings, * When no parameters are given, the plugin activates the default settings,
* which describe copper with a light amount of roughness modeled using a * which describe copper with a medium amount of roughness modeled using a
* Beckmann distribution. * Beckmann distribution.
* *
* To get an intuition about the effect of the surface roughness * To get an intuition about the effect of the surface roughness parameter
* parameter $\alpha$, consider the following approximate classification: * $\alpha$, consider the following approximate classification: a value of
* a value of $\alpha=0.001-0.01$ corresponds to a material * $\alpha=0.001-0.01$ corresponds to a material with slight imperfections
* with slight imperfections on an * on an otherwise smooth surface finish, $\alpha=0.1$ is relatively rough,
* otherwise smooth surface finish, $\alpha=0.1$ is relatively rough,
* and $\alpha=0.3-0.7$ is \emph{extremely} rough (e.g. an etched or ground * and $\alpha=0.3-0.7$ is \emph{extremely} rough (e.g. an etched or ground
* finish). Values significantly above that are probably not too realistic. * finish). Values significantly above that are probably not too realistic.
* \vspace{4mm} * \vspace{4mm}
* \begin{xml}[caption={A material definition for brushed aluminium}, label=lst:roughconductor-aluminium] * \begin{xml}[caption={A material definition for brushed aluminium}, label=lst:roughconductor-aluminium]
* <bsdf type="roughconductor"> * <bsdf type="roughconductor">
* <string name="material" value="Al"/> * <string name="material" value="Al"/>
* <string name="distribution" value="as"/> * <string name="distribution" value="phong"/>
* <float name="alphaU" value="0.05"/> * <float name="alphaU" value="0.05"/>
* <float name="alphaV" value="0.3"/> * <float name="alphaV" value="0.3"/>
* </bsdf> * </bsdf>
* \end{xml} * \end{xml}
* *
* \subsubsection*{Technical details} * \subsubsection*{Technical details}
* When rendering with the Ashikhmin-Shirley or Phong microfacet * All microfacet distributions allow the specification of two distinct
* distributions, a conversion is used to turn the specified * roughness values along the tangent and bitangent directions. This can be
* $\alpha$ roughness value into the exponents of these distributions. * used to provide a material with a ``brushed'' appearance. The alignment
* This is done in a way, such that the different * of the anisotropy will follow the UV parameterization of the underlying
* distributions all produce a similar appearance for the same value of * mesh. This means that such an anisotropic material cannot be applied to
* $\alpha$. * triangle meshes that are missing texture coordinates.
* *
* The Ashikhmin-Shirley microfacet distribution allows the specification * \label{sec:visiblenormal-sampling}
* of two distinct roughness values along the tangent and bitangent * Since Mitsuba 0.5.1, this plugin uses a new importance sampling technique
* directions. This can be used to provide a material with a ``brushed'' * contributed by Eric Heitz and Eugene D'Eon, which restricts the sampling
* appearance. The alignment of the anisotropy will follow the UV * domain to the set of visible (unmasked) microfacet normals. The previous
* parameterization of the underlying mesh in this case. This also means that * approach of sampling all normals is still available and can be enabled
* such an anisotropic material cannot be applied to triangle meshes that * by setting \code{sampleVisible} to \code{false}.
* are missing texture coordinates. * Note that this new method is only available for the \code{beckmann} and
* \code{ggx} microfacet distributions. When the \code{phong} distribution
* is selected, the parameter has no effect.
*
* When rendering with the Phong microfacet distribution, a conversion is
* used to turn the specified Beckmann-equivalent $\alpha$ roughness value
* into the exponent parameter of this distribution. This is done in a way,
* such that the same value $\alpha$ will produce a similar appearance across
* different microfacet distributions.
* *
* When using this plugin, you should ideally compile Mitsuba with support for * When using this plugin, you should ideally compile Mitsuba with support for
* spectral rendering to get the most accurate results. While it also works * spectral rendering to get the most accurate results. While it also works
@ -178,26 +189,21 @@ public:
m_eta = props.getSpectrum("eta", intEta) / extEta; m_eta = props.getSpectrum("eta", intEta) / extEta;
m_k = props.getSpectrum("k", intK) / extEta; m_k = props.getSpectrum("k", intK) / extEta;
m_distribution = MicrofacetDistribution( MicrofacetDistribution distr(props);
props.getString("distribution", "beckmann") m_type = distr.getType();
); m_sampleVisible = distr.getSampleVisible();
Float alpha = props.getFloat("alpha", 0.1f), m_alphaU = new ConstantFloatTexture(distr.getAlphaU());
alphaU = props.getFloat("alphaU", alpha), if (distr.getAlphaU() == distr.getAlphaV())
alphaV = props.getFloat("alphaV", alpha);
m_alphaU = new ConstantFloatTexture(alphaU);
if (alphaU == alphaV)
m_alphaV = m_alphaU; m_alphaV = m_alphaU;
else else
m_alphaV = new ConstantFloatTexture(alphaV); m_alphaV = new ConstantFloatTexture(distr.getAlphaV());
} }
RoughConductor(Stream *stream, InstanceManager *manager) RoughConductor(Stream *stream, InstanceManager *manager)
: BSDF(stream, manager) { : BSDF(stream, manager) {
m_distribution = MicrofacetDistribution( m_type = (MicrofacetDistribution::EType) stream->readUInt();
(MicrofacetDistribution::EType) stream->readUInt() m_sampleVisible = stream->readBool();
);
m_alphaU = static_cast<Texture *>(manager->getInstance(stream)); m_alphaU = static_cast<Texture *>(manager->getInstance(stream));
m_alphaV = static_cast<Texture *>(manager->getInstance(stream)); m_alphaV = static_cast<Texture *>(manager->getInstance(stream));
m_specularReflectance = static_cast<Texture *>(manager->getInstance(stream)); m_specularReflectance = static_cast<Texture *>(manager->getInstance(stream));
@ -207,17 +213,23 @@ public:
configure(); configure();
} }
void serialize(Stream *stream, InstanceManager *manager) const {
BSDF::serialize(stream, manager);
stream->writeUInt((uint32_t) m_type);
stream->writeBool(m_sampleVisible);
manager->serialize(stream, m_alphaU.get());
manager->serialize(stream, m_alphaV.get());
manager->serialize(stream, m_specularReflectance.get());
m_eta.serialize(stream);
m_k.serialize(stream);
}
void configure() { void configure() {
unsigned int extraFlags = 0; unsigned int extraFlags = 0;
if (m_alphaU != m_alphaV) { if (m_alphaU != m_alphaV)
extraFlags |= EAnisotropic; extraFlags |= EAnisotropic;
if (m_distribution.getType() !=
MicrofacetDistribution::EAshikhminShirley)
Log(EError, "Different roughness values along the tangent and "
"bitangent directions are only supported when using the "
"anisotropic Ashikhmin-Shirley microfacet distribution "
"(named \"as\")");
}
if (!m_alphaU->isConstant() || !m_alphaV->isConstant() || if (!m_alphaU->isConstant() || !m_alphaV->isConstant() ||
!m_specularReflectance->isConstant()) !m_specularReflectance->isConstant())
extraFlags |= ESpatiallyVarying; extraFlags |= ESpatiallyVarying;
@ -254,27 +266,31 @@ public:
/* Calculate the reflection half-vector */ /* Calculate the reflection half-vector */
Vector H = normalize(bRec.wo+bRec.wi); Vector H = normalize(bRec.wo+bRec.wi);
/* Evaluate the roughness */ /* Construct the microfacet distribution matching the
Float alphaU = m_distribution.transformRoughness( roughness values at the current surface position. */
m_alphaU->eval(bRec.its).average()), MicrofacetDistribution distr(
alphaV = m_distribution.transformRoughness( m_type,
m_alphaV->eval(bRec.its).average()); m_alphaU->eval(bRec.its).average(),
m_alphaV->eval(bRec.its).average(),
m_sampleVisible
);
/* Evaluate the microsurface normal distribution */ /* Evaluate the microfacet normal distribution */
const Float D = m_distribution.eval(H, alphaU, alphaV); const Float D = distr.eval(H);
if (D == 0) if (D == 0)
return Spectrum(0.0f); return Spectrum(0.0f);
/* Fresnel factor */ /* Fresnel factor */
const Spectrum F = fresnelConductorExact(dot(bRec.wi, H), m_eta, m_k); const Spectrum F = fresnelConductorExact(dot(bRec.wi, H), m_eta, m_k) *
m_specularReflectance->eval(bRec.its);
/* Smith's shadow-masking function */ /* Smith's shadow-masking function */
const Float G = m_distribution.G(bRec.wi, bRec.wo, H, alphaU, alphaV); const Float G = distr.G(bRec.wi, bRec.wo, H);
/* Calculate the total amount of reflection */ /* Calculate the total amount of reflection */
Float value = D * G / (4.0f * Frame::cosTheta(bRec.wi)); Float model = D * G / (4.0f * Frame::cosTheta(bRec.wi));
return m_specularReflectance->eval(bRec.its) * F * value; return F * model;
} }
Float pdf(const BSDFSamplingRecord &bRec, EMeasure measure) const { Float pdf(const BSDFSamplingRecord &bRec, EMeasure measure) const {
@ -288,14 +304,20 @@ public:
/* Calculate the reflection half-vector */ /* Calculate the reflection half-vector */
Vector H = normalize(bRec.wo+bRec.wi); Vector H = normalize(bRec.wo+bRec.wi);
/* Evaluate the roughness */ /* Construct the microfacet distribution matching the
Float alphaU = m_distribution.transformRoughness( roughness values at the current surface position. */
m_alphaU->eval(bRec.its).average()), MicrofacetDistribution distr(
alphaV = m_distribution.transformRoughness( m_type,
m_alphaV->eval(bRec.its).average()); m_alphaU->eval(bRec.its).average(),
m_alphaV->eval(bRec.its).average(),
m_sampleVisible
);
return m_distribution.pdf(H, alphaU, alphaV) if (m_sampleVisible)
/ (4 * absDot(bRec.wo, H)); return distr.eval(H) * distr.smithG1(bRec.wi, H)
/ (4.0f * Frame::cosTheta(bRec.wi));
else
return distr.pdf(bRec.wi, H) / (4 * absDot(bRec.wo, H));
} }
Spectrum sample(BSDFSamplingRecord &bRec, const Point2 &sample) const { Spectrum sample(BSDFSamplingRecord &bRec, const Point2 &sample) const {
@ -304,21 +326,23 @@ public:
!(bRec.typeMask & EGlossyReflection))) !(bRec.typeMask & EGlossyReflection)))
return Spectrum(0.0f); return Spectrum(0.0f);
/* Evaluate the roughness */ /* Construct the microfacet distribution matching the
Float alphaU = m_distribution.transformRoughness( roughness values at the current surface position. */
m_alphaU->eval(bRec.its).average()), MicrofacetDistribution distr(
alphaV = m_distribution.transformRoughness( m_type,
m_alphaV->eval(bRec.its).average()); m_alphaU->eval(bRec.its).average(),
m_alphaV->eval(bRec.its).average(),
m_sampleVisible
);
/* Sample M, the microsurface normal */ /* Sample M, the microfacet normal */
Float microfacetPDF; Float pdf;
const Normal m = m_distribution.sample(sample, Normal m = distr.sample(bRec.wi, sample, pdf);
alphaU, alphaV, microfacetPDF);
if (microfacetPDF == 0) if (pdf == 0)
return Spectrum(0.0f); return Spectrum(0.0f);
/* Perfect specular reflection based on the microsurface normal */ /* Perfect specular reflection based on the microfacet normal */
bRec.wo = reflect(bRec.wi, m); bRec.wo = reflect(bRec.wi, m);
bRec.eta = 1.0f; bRec.eta = 1.0f;
bRec.sampledComponent = 0; bRec.sampledComponent = 0;
@ -328,18 +352,18 @@ public:
if (Frame::cosTheta(bRec.wo) <= 0) if (Frame::cosTheta(bRec.wo) <= 0)
return Spectrum(0.0f); return Spectrum(0.0f);
const Spectrum F = fresnelConductorExact(dot(bRec.wi, m), Spectrum F = fresnelConductorExact(dot(bRec.wi, m),
m_eta, m_k); m_eta, m_k) * m_specularReflectance->eval(bRec.its);
Float numerator = m_distribution.eval(m, alphaU, alphaV) Float weight;
* m_distribution.G(bRec.wi, bRec.wo, m, alphaU, alphaV) if (m_sampleVisible) {
* dot(bRec.wi, m); weight = distr.smithG1(bRec.wo, m);
} else {
weight = distr.eval(m) * distr.G(bRec.wi, bRec.wo, m)
* dot(bRec.wi, m) / (pdf * Frame::cosTheta(bRec.wi));
}
Float denominator = microfacetPDF return F * weight;
* Frame::cosTheta(bRec.wi);
return m_specularReflectance->eval(bRec.its) * F
* (numerator / denominator);
} }
Spectrum sample(BSDFSamplingRecord &bRec, Float &pdf, const Point2 &sample) const { Spectrum sample(BSDFSamplingRecord &bRec, Float &pdf, const Point2 &sample) const {
@ -348,20 +372,22 @@ public:
!(bRec.typeMask & EGlossyReflection))) !(bRec.typeMask & EGlossyReflection)))
return Spectrum(0.0f); return Spectrum(0.0f);
/* Evaluate the roughness */ /* Construct the microfacet distribution matching the
Float alphaU = m_distribution.transformRoughness( roughness values at the current surface position. */
m_alphaU->eval(bRec.its).average()), MicrofacetDistribution distr(
alphaV = m_distribution.transformRoughness( m_type,
m_alphaV->eval(bRec.its).average()); m_alphaU->eval(bRec.its).average(),
m_alphaV->eval(bRec.its).average(),
m_sampleVisible
);
/* Sample M, the microsurface normal */ /* Sample M, the microfacet normal */
const Normal m = m_distribution.sample(sample, Normal m = distr.sample(bRec.wi, sample, pdf);
alphaU, alphaV, pdf);
if (pdf == 0) if (pdf == 0)
return Spectrum(0.0f); return Spectrum(0.0f);
/* Perfect specular reflection based on the microsurface normal */ /* Perfect specular reflection based on the microfacet normal */
bRec.wo = reflect(bRec.wi, m); bRec.wo = reflect(bRec.wi, m);
bRec.eta = 1.0f; bRec.eta = 1.0f;
bRec.sampledComponent = 0; bRec.sampledComponent = 0;
@ -371,23 +397,23 @@ public:
if (Frame::cosTheta(bRec.wo) <= 0) if (Frame::cosTheta(bRec.wo) <= 0)
return Spectrum(0.0f); return Spectrum(0.0f);
const Spectrum F = fresnelConductorExact(dot(bRec.wi, m), Spectrum F = fresnelConductorExact(dot(bRec.wi, m),
m_eta, m_k); m_eta, m_k) * m_specularReflectance->eval(bRec.its);
Float numerator = m_distribution.eval(m, alphaU, alphaV) Float weight;
* m_distribution.G(bRec.wi, bRec.wo, m, alphaU, alphaV) if (m_sampleVisible) {
* dot(bRec.wi, m); weight = distr.smithG1(bRec.wo, m);
} else {
Float denominator = pdf * Frame::cosTheta(bRec.wi); weight = distr.eval(m) * distr.G(bRec.wi, bRec.wo, m)
* dot(bRec.wi, m) / (pdf * Frame::cosTheta(bRec.wi));
}
/* Jacobian of the half-direction mapping */ /* Jacobian of the half-direction mapping */
pdf /= 4.0f * dot(bRec.wo, m); pdf /= 4.0f * dot(bRec.wo, m);
return m_specularReflectance->eval(bRec.its) * F return F * weight;
* (numerator / denominator);
} }
void addChild(const std::string &name, ConfigurableObject *child) { void addChild(const std::string &name, ConfigurableObject *child) {
if (child->getClass()->derivesFrom(MTS_CLASS(Texture))) { if (child->getClass()->derivesFrom(MTS_CLASS(Texture))) {
if (name == "alpha") if (name == "alpha")
@ -405,17 +431,6 @@ public:
} }
} }
void serialize(Stream *stream, InstanceManager *manager) const {
BSDF::serialize(stream, manager);
stream->writeUInt((uint32_t) m_distribution.getType());
manager->serialize(stream, m_alphaU.get());
manager->serialize(stream, m_alphaV.get());
manager->serialize(stream, m_specularReflectance.get());
m_eta.serialize(stream);
m_k.serialize(stream);
}
Float getRoughness(const Intersection &its, int component) const { Float getRoughness(const Intersection &its, int component) const {
return 0.5f * (m_alphaU->eval(its).average() return 0.5f * (m_alphaU->eval(its).average()
+ m_alphaV->eval(its).average()); + m_alphaV->eval(its).average());
@ -425,7 +440,8 @@ public:
std::ostringstream oss; std::ostringstream oss;
oss << "RoughConductor[" << endl oss << "RoughConductor[" << endl
<< " id = \"" << getID() << "\"," << endl << " id = \"" << getID() << "\"," << endl
<< " distribution = " << m_distribution.toString() << "," << endl << " distribution = " << MicrofacetDistribution::distributionName(m_type) << "," << endl
<< " sampleVisible = " << m_sampleVisible << "," << endl
<< " alphaU = " << indent(m_alphaU->toString()) << "," << endl << " alphaU = " << indent(m_alphaU->toString()) << "," << endl
<< " alphaV = " << indent(m_alphaV->toString()) << "," << endl << " alphaV = " << indent(m_alphaV->toString()) << "," << endl
<< " specularReflectance = " << indent(m_specularReflectance->toString()) << "," << endl << " specularReflectance = " << indent(m_specularReflectance->toString()) << "," << endl
@ -439,9 +455,10 @@ public:
MTS_DECLARE_CLASS() MTS_DECLARE_CLASS()
private: private:
MicrofacetDistribution m_distribution; MicrofacetDistribution::EType m_type;
ref<Texture> m_specularReflectance; ref<Texture> m_specularReflectance;
ref<Texture> m_alphaU, m_alphaV; ref<Texture> m_alphaU, m_alphaV;
bool m_sampleVisible;
Spectrum m_eta, m_k; Spectrum m_eta, m_k;
}; };

View File

@ -24,12 +24,6 @@
MTS_NAMESPACE_BEGIN MTS_NAMESPACE_BEGIN
/* Suggestion by Bruce Walter: sample the model using a slightly
wider density function. This in practice limits the importance
weights to values <= 4.
*/
#define ENLARGE_LOBE_TRICK 1
/*!\plugin{roughdielectric}{Rough dielectric material} /*!\plugin{roughdielectric}{Rough dielectric material}
* \order{5} * \order{5}
* \icon{bsdf_roughdielectric} * \icon{bsdf_roughdielectric}
@ -37,42 +31,44 @@ MTS_NAMESPACE_BEGIN
* \parameter{distribution}{\String}{ * \parameter{distribution}{\String}{
* Specifies the type of microfacet normal distribution * Specifies the type of microfacet normal distribution
* used to model the surface roughness. * used to model the surface roughness.
* \vspace{-1mm}
* \begin{enumerate}[(i)] * \begin{enumerate}[(i)]
* \item \code{beckmann}: Physically-based distribution derived from * \item \code{beckmann}: Physically-based distribution derived from
* Gaussian random surfaces. This is the default. * Gaussian random surfaces. This is the default.\vspace{-1.5mm}
* \item \code{ggx}: New distribution proposed by * \item \code{ggx}: The GGX \cite{Walter07Microfacet} distribution (also known as
* Walter et al. \cite{Walter07Microfacet}, which is meant to better handle * Trowbridge-Reitz \cite{Trowbridge19975Average} distribution)
* the long tails observed in measurements of ground surfaces. * was designed to better approximate the long tails observed in measurements
* Renderings with this distribution may converge slowly. * of ground surfaces, which are not modeled by the Beckmann distribution.
* \item \code{phong}: Classical $\cos^p\theta$ distribution. * \vspace{-1.5mm}
* Due to the underlying microfacet theory, * \item \code{phong}: Anisotropic Phong distribution by
* the use of this distribution here leads to more realistic * Ashikhmin and Shirley \cite{Ashikhmin2005Anisotropic}.
* behavior than the separately available \pluginref{phong} plugin. * In most cases, the \code{ggx} and \code{beckmann} distributions
* \item \code{as}: Anisotropic Phong-style microfacet distribution proposed by * should be preferred, since they provide better importance sampling
* Ashikhmin and Shirley \cite{Ashikhmin2005Anisotropic}.\vspace{-3mm} * and accurate shadowing/masking computations.
* \vspace{-4mm}
* \end{enumerate} * \end{enumerate}
* } * }
* \parameter{alpha}{\Float\Or\Texture}{ * \parameter{alpha, alphaU, alphaV}{\Float\Or\Texture}{
* Specifies the roughness of the unresolved surface micro-geometry. * Specifies the roughness of the unresolved surface micro-geometry
* When the Beckmann distribution is used, this parameter is equal to the * along the tangent and bitangent directions. When the Beckmann
* \emph{root mean square} (RMS) slope of the microfacets. This * distribution is used, this parameter is equal to the
* parameter is only valid when \texttt{distribution=beckmann/phong/ggx}. * \emph{root mean square} (RMS) slope of the microfacets.
* \default{0.1}. * \code{alpha} is a convenience parameter to initialize both
* } * \code{alphaU} and \code{alphaV} to the same value. \default{0.1}.
* \parameter{alphaU, alphaV}{\Float\Or\Texture}{
* Specifies the anisotropic roughness values along the tangent and
* bitangent directions. These parameter are only valid when
* \texttt{distribution=as}. \default{0.1}.
* } * }
* \parameter{intIOR}{\Float\Or\String}{Interior index of refraction specified * \parameter{intIOR}{\Float\Or\String}{Interior index of refraction specified
* numerically or using a known material name. \default{\texttt{bk7} / 1.5046}} * numerically or using a known material name. \default{\texttt{bk7} / 1.5046}}
* \parameter{extIOR}{\Float\Or\String}{Exterior index of refraction specified * \parameter{extIOR}{\Float\Or\String}{Exterior index of refraction specified
* numerically or using a known material name. \default{\texttt{air} / 1.000277}} * numerically or using a known material name. \default{\texttt{air} / 1.000277}}
* \parameter{specular\showbreak Reflectance}{\Spectrum\Or\Texture}{Optional * \parameter{sampleVisible}{\Boolean}{
* factor that can be used to modulate the specular reflection component. Note * Enables a sampling technique proposed by Heitz and D'Eon~\cite{Heitz1014Importance},
* that for physical realism, this parameter should never be touched. \default{1.0}} * which focuses computation on the visible parts of the microfacet normal
* \parameter{specular\showbreak Transmittance}{\Spectrum\Or\Texture}{Optional * distribution, considerably reducing variance in some cases.
* factor that can be used to modulate the specular transmission component. Note * \default{\code{true}, i.e. use visible normal sampling}
* }
* \parameter{specular\showbreak Reflectance,\newline
* specular\showbreak Transmittance}{\Spectrum\Or\Texture}{Optional
* factor that can be used to modulate the specular reflection/transmission component. Note
* that for physical realism, this parameter should never be touched. \default{1.0}} * that for physical realism, this parameter should never be touched. \default{1.0}}
* }\vspace{4mm} * }\vspace{4mm}
* *
@ -98,7 +94,7 @@ MTS_NAMESPACE_BEGIN
* *
* The implementation is based on the paper ``Microfacet Models * The implementation is based on the paper ``Microfacet Models
* for Refraction through Rough Surfaces'' by Walter et al. * for Refraction through Rough Surfaces'' by Walter et al.
* \cite{Walter07Microfacet}. It supports several different types of microfacet * \cite{Walter07Microfacet}. It supports three different types of microfacet
* distributions and has a texturable roughness parameter. Exterior and * distributions and has a texturable roughness parameter. Exterior and
* interior IOR values can be specified independently, where ``exterior'' * interior IOR values can be specified independently, where ``exterior''
* refers to the side that contains the surface normal. Similar to the * refers to the side that contains the surface normal. Similar to the
@ -109,13 +105,12 @@ MTS_NAMESPACE_BEGIN
* glass BK7/air interface with a light amount of roughness modeled using a * glass BK7/air interface with a light amount of roughness modeled using a
* Beckmann distribution. * Beckmann distribution.
* *
* To get an intuition about the effect of the surface roughness * To get an intuition about the effect of the surface roughness parameter
* parameter $\alpha$, consider the following approximate classification: * $\alpha$, consider the following approximate classification: a value of
* a value of $\alpha=0.001-0.01$ corresponds to a material * $\alpha=0.001-0.01$ corresponds to a material with slight imperfections
* with slight imperfections on an * on an otherwise smooth surface finish, $\alpha=0.1$ is relatively rough,
* otherwise smooth surface finish, $\alpha=0.1$ is relatively rough,
* and $\alpha=0.3-0.7$ is \emph{extremely} rough (e.g. an etched or ground * and $\alpha=0.3-0.7$ is \emph{extremely} rough (e.g. an etched or ground
* finish). * finish). Values significantly above that are probably not too realistic.
* *
* Please note that when using this plugin, it is crucial that the scene contains * Please note that when using this plugin, it is crucial that the scene contains
* meaningful and mutually compatible index of refraction changes---see * meaningful and mutually compatible index of refraction changes---see
@ -127,20 +122,33 @@ MTS_NAMESPACE_BEGIN
* converge slowly. * converge slowly.
* *
* \subsubsection*{Technical details} * \subsubsection*{Technical details}
* When rendering with the Ashikhmin-Shirley or Phong microfacet * All microfacet distributions allow the specification of two distinct
* distributions, a conversion is used to turn the specified * roughness values along the tangent and bitangent directions. This can be
* $\alpha$ roughness value into the exponents of these distributions. * used to provide a material with a ``brushed'' appearance. The alignment
* This is done in a way, such that the different * of the anisotropy will follow the UV parameterization of the underlying
* distributions all produce a similar appearance for the same value of * mesh. This means that such an anisotropic material cannot be applied to
* $\alpha$. * triangle meshes that are missing texture coordinates.
* *
* The Ashikhmin-Shirley microfacet distribution allows the specification * Since Mitsuba 0.5.1, this plugin uses a new importance sampling technique
* of two distinct roughness values along the tangent and bitangent * contributed by Eric Heitz and Eugene D'Eon, which restricts the sampling
* directions. This can be used to provide a material with a ``brushed'' * domain to the set of visible (unmasked) microfacet normals. The previous
* appearance. The alignment of the anisotropy will follow the UV * approach of sampling all normals is still available and can be enabled
* parameterization of the underlying mesh in this case. This also means that * by setting \code{sampleVisible} to \code{false}.
* such an anisotropic material cannot be applied to triangle meshes that * Note that this new method is only available for the \code{beckmann} and
* are missing texture coordinates.\newpage * \code{ggx} microfacet distributions. When the \code{phong} distribution
* is selected, the parameter has no effect.
*
* When rendering with the Phong microfacet distribution, a conversion is
* used to turn the specified Beckmann-equivalent $\alpha$ roughness value
* into the exponent parameter of this distribution. This is done in a way,
* such that the same value $\alpha$ will produce a similar appearance across
* different microfacet distributions.
*
* When rendering with the Phong microfacet distribution, a conversion is
* used to turn the specified Beckmann-equivalent $\alpha$ roughness value
* into the exponents of the distribution. This is done in a way, such that
* the different distributions all produce a similar appearance for the
* same value of $\alpha$.
* *
* \renderings{ * \renderings{
* \rendering{Ground glass (GGX, $\alpha$=0.304, * \rendering{Ground glass (GGX, $\alpha$=0.304,
@ -191,26 +199,21 @@ public:
m_eta = intIOR / extIOR; m_eta = intIOR / extIOR;
m_invEta = 1 / m_eta; m_invEta = 1 / m_eta;
m_distribution = MicrofacetDistribution( MicrofacetDistribution distr(props);
props.getString("distribution", "beckmann") m_type = distr.getType();
); m_sampleVisible = distr.getSampleVisible();
Float alpha = props.getFloat("alpha", 0.1f), m_alphaU = new ConstantFloatTexture(distr.getAlphaU());
alphaU = props.getFloat("alphaU", alpha), if (distr.getAlphaU() == distr.getAlphaV())
alphaV = props.getFloat("alphaV", alpha);
m_alphaU = new ConstantFloatTexture(alphaU);
if (alphaU == alphaV)
m_alphaV = m_alphaU; m_alphaV = m_alphaU;
else else
m_alphaV = new ConstantFloatTexture(alphaV); m_alphaV = new ConstantFloatTexture(distr.getAlphaV());
} }
RoughDielectric(Stream *stream, InstanceManager *manager) RoughDielectric(Stream *stream, InstanceManager *manager)
: BSDF(stream, manager) { : BSDF(stream, manager) {
m_distribution = MicrofacetDistribution( m_type = (MicrofacetDistribution::EType) stream->readUInt();
(MicrofacetDistribution::EType) stream->readUInt() m_sampleVisible = stream->readBool();
);
m_alphaU = static_cast<Texture *>(manager->getInstance(stream)); m_alphaU = static_cast<Texture *>(manager->getInstance(stream));
m_alphaV = static_cast<Texture *>(manager->getInstance(stream)); m_alphaV = static_cast<Texture *>(manager->getInstance(stream));
m_specularReflectance = static_cast<Texture *>(manager->getInstance(stream)); m_specularReflectance = static_cast<Texture *>(manager->getInstance(stream));
@ -221,17 +224,22 @@ public:
configure(); configure();
} }
void serialize(Stream *stream, InstanceManager *manager) const {
BSDF::serialize(stream, manager);
stream->writeUInt((uint32_t) m_type);
stream->writeBool(m_sampleVisible);
manager->serialize(stream, m_alphaU.get());
manager->serialize(stream, m_alphaV.get());
manager->serialize(stream, m_specularReflectance.get());
manager->serialize(stream, m_specularTransmittance.get());
stream->writeFloat(m_eta);
}
void configure() { void configure() {
unsigned int extraFlags = 0; unsigned int extraFlags = 0;
if (m_alphaU != m_alphaV) { if (m_alphaU != m_alphaV)
extraFlags |= EAnisotropic; extraFlags |= EAnisotropic;
if (m_distribution.getType() !=
MicrofacetDistribution::EAshikhminShirley)
Log(EError, "Different roughness values along the tangent and "
"bitangent directions are only supported when using the "
"anisotropic Ashikhmin-Shirley microfacet distribution "
"(named \"as\")");
}
if (!m_alphaU->isConstant() || !m_alphaV->isConstant()) if (!m_alphaU->isConstant() || !m_alphaV->isConstant())
extraFlags |= ESpatiallyVarying; extraFlags |= ESpatiallyVarying;
@ -293,14 +301,17 @@ public:
same hemisphere as the macrosurface normal */ same hemisphere as the macrosurface normal */
H *= math::signum(Frame::cosTheta(H)); H *= math::signum(Frame::cosTheta(H));
/* Evaluate the roughness */ /* Construct the microfacet distribution matching the
Float alphaU = m_distribution.transformRoughness( roughness values at the current surface position. */
m_alphaU->eval(bRec.its).average()), MicrofacetDistribution distr(
alphaV = m_distribution.transformRoughness( m_type,
m_alphaV->eval(bRec.its).average()); m_alphaU->eval(bRec.its).average(),
m_alphaV->eval(bRec.its).average(),
m_sampleVisible
);
/* Evaluate the microsurface normal distribution */ /* Evaluate the microfacet normal distribution */
const Float D = m_distribution.eval(H, alphaU, alphaV); const Float D = distr.eval(H);
if (D == 0) if (D == 0)
return Spectrum(0.0f); return Spectrum(0.0f);
@ -308,7 +319,7 @@ public:
const Float F = fresnelDielectricExt(dot(bRec.wi, H), m_eta); const Float F = fresnelDielectricExt(dot(bRec.wi, H), m_eta);
/* Smith's shadow-masking function */ /* Smith's shadow-masking function */
const Float G = m_distribution.G(bRec.wi, bRec.wo, H, alphaU, alphaV); const Float G = distr.G(bRec.wi, bRec.wo, H);
if (reflect) { if (reflect) {
/* Calculate the total amount of reflection */ /* Calculate the total amount of reflection */
@ -383,21 +394,24 @@ public:
same hemisphere as the macrosurface normal */ same hemisphere as the macrosurface normal */
H *= math::signum(Frame::cosTheta(H)); H *= math::signum(Frame::cosTheta(H));
/* Evaluate the roughness */ /* Construct the microfacet distribution matching the
Float alphaU = m_alphaU->eval(bRec.its).average(), roughness values at the current surface position. */
alphaV = m_alphaV->eval(bRec.its).average(); MicrofacetDistribution sampleDistr(
m_type,
m_alphaU->eval(bRec.its).average(),
m_alphaV->eval(bRec.its).average(),
m_sampleVisible
);
#if ENLARGE_LOBE_TRICK == 1 /* Trick by Walter et al.: slightly scale the roughness values to
Float factor = (1.2f - 0.2f * std::sqrt( reduce importance sampling weights. Not needed for the
Heitz and D'Eon sampling technique. */
if (!m_sampleVisible)
sampleDistr.scaleAlpha(1.2f - 0.2f * std::sqrt(
std::abs(Frame::cosTheta(bRec.wi)))); std::abs(Frame::cosTheta(bRec.wi))));
alphaU *= factor; alphaV *= factor;
#endif
alphaU = m_distribution.transformRoughness(alphaU); /* Evaluate the microfacet model sampling density function */
alphaV = m_distribution.transformRoughness(alphaV); Float prob = sampleDistr.pdf(math::signum(Frame::cosTheta(bRec.wi)) * bRec.wi, H);
/* Evaluate the microsurface normal sampling density */
Float prob = m_distribution.pdf(H, alphaU, alphaV);
if (hasTransmission && hasReflection) { if (hasTransmission && hasReflection) {
Float F = fresnelDielectricExt(dot(bRec.wi, H), m_eta); Float F = fresnelDielectricExt(dot(bRec.wi, H), m_eta);
@ -419,44 +433,42 @@ public:
if (!hasReflection && !hasTransmission) if (!hasReflection && !hasTransmission)
return Spectrum(0.0f); return Spectrum(0.0f);
/* Evaluate the roughness */ /* Construct the microfacet distribution matching the
Float alphaU = m_alphaU->eval(bRec.its).average(), roughness values at the current surface position. */
alphaV = m_alphaV->eval(bRec.its).average(), MicrofacetDistribution distr(
sampleAlphaU = alphaU, m_type,
sampleAlphaV = alphaV; m_alphaU->eval(bRec.its).average(),
m_alphaV->eval(bRec.its).average(),
m_sampleVisible
);
#if ENLARGE_LOBE_TRICK == 1 /* Trick by Walter et al.: slightly scale the roughness values to
Float factor = (1.2f - 0.2f * std::sqrt( reduce importance sampling weights. Not needed for the
Heitz and D'Eon sampling technique. */
MicrofacetDistribution sampleDistr(distr);
if (!m_sampleVisible)
sampleDistr.scaleAlpha(1.2f - 0.2f * std::sqrt(
std::abs(Frame::cosTheta(bRec.wi)))); std::abs(Frame::cosTheta(bRec.wi))));
sampleAlphaU *= factor; sampleAlphaV *= factor;
#endif
alphaU = m_distribution.transformRoughness(alphaU); /* Sample M, the microfacet normal */
alphaV = m_distribution.transformRoughness(alphaV);
sampleAlphaU = m_distribution.transformRoughness(sampleAlphaU);
sampleAlphaV = m_distribution.transformRoughness(sampleAlphaV);
/* Sample M, the microsurface normal */
Float microfacetPDF; Float microfacetPDF;
const Normal m = m_distribution.sample(sample, const Normal m = sampleDistr.sample(math::signum(Frame::cosTheta(bRec.wi)) * bRec.wi, sample, microfacetPDF);
sampleAlphaU, sampleAlphaV, microfacetPDF);
if (microfacetPDF == 0) if (microfacetPDF == 0)
return Spectrum(0.0f); return Spectrum(0.0f);
Float cosThetaT, numerator = 1.0f; Float cosThetaT;
Float F = fresnelDielectricExt(dot(bRec.wi, m), cosThetaT, m_eta); Float F = fresnelDielectricExt(dot(bRec.wi, m), cosThetaT, m_eta);
Spectrum weight(1.0f);
if (hasReflection && hasTransmission) { if (hasReflection && hasTransmission) {
if (bRec.sampler->next1D() > F) if (bRec.sampler->next1D() > F)
sampleReflection = false; sampleReflection = false;
} else { } else {
numerator = hasReflection ? F : (1-F); weight = Spectrum(hasReflection ? F : (1-F));
} }
Spectrum result;
if (sampleReflection) { if (sampleReflection) {
/* Perfect specular reflection based on the microsurface normal */ /* Perfect specular reflection based on the microfacet normal */
bRec.wo = reflect(bRec.wi, m); bRec.wo = reflect(bRec.wi, m);
bRec.eta = 1.0f; bRec.eta = 1.0f;
bRec.sampledComponent = 0; bRec.sampledComponent = 0;
@ -466,12 +478,12 @@ public:
if (Frame::cosTheta(bRec.wi) * Frame::cosTheta(bRec.wo) <= 0) if (Frame::cosTheta(bRec.wi) * Frame::cosTheta(bRec.wo) <= 0)
return Spectrum(0.0f); return Spectrum(0.0f);
result = m_specularReflectance->eval(bRec.its); weight *= m_specularReflectance->eval(bRec.its);
} else { } else {
if (cosThetaT == 0) if (cosThetaT == 0)
return Spectrum(0.0f); return Spectrum(0.0f);
/* Perfect specular transmission based on the microsurface normal */ /* Perfect specular transmission based on the microfacet normal */
bRec.wo = refract(bRec.wi, m, m_eta, cosThetaT); bRec.wo = refract(bRec.wi, m, m_eta, cosThetaT);
bRec.eta = cosThetaT < 0 ? m_eta : m_invEta; bRec.eta = cosThetaT < 0 ? m_eta : m_invEta;
bRec.sampledComponent = 1; bRec.sampledComponent = 1;
@ -486,17 +498,16 @@ public:
Float factor = (bRec.mode == ERadiance) Float factor = (bRec.mode == ERadiance)
? (cosThetaT < 0 ? m_invEta : m_eta) : 1.0f; ? (cosThetaT < 0 ? m_invEta : m_eta) : 1.0f;
result = m_specularTransmittance->eval(bRec.its) * (factor * factor); weight *= m_specularTransmittance->eval(bRec.its) * (factor * factor);
} }
numerator *= m_distribution.eval(m, alphaU, alphaV) if (m_sampleVisible)
* m_distribution.G(bRec.wi, bRec.wo, m, alphaU, alphaV) weight *= distr.smithG1(bRec.wo, m);
* dot(bRec.wi, m); else
weight *= std::abs(distr.eval(m) * distr.G(bRec.wi, bRec.wo, m)
* dot(bRec.wi, m) / (microfacetPDF * Frame::cosTheta(bRec.wi)));
Float denominator = microfacetPDF return weight;
* Frame::cosTheta(bRec.wi);
return result * std::abs(numerator / denominator);
} }
Spectrum sample(BSDFSamplingRecord &bRec, Float &pdf, const Point2 &_sample) const { Spectrum sample(BSDFSamplingRecord &bRec, Float &pdf, const Point2 &_sample) const {
@ -511,35 +522,33 @@ public:
if (!hasReflection && !hasTransmission) if (!hasReflection && !hasTransmission)
return Spectrum(0.0f); return Spectrum(0.0f);
/* Evaluate the roughness */ /* Construct the microfacet distribution matching the
Float alphaU = m_alphaU->eval(bRec.its).average(), roughness values at the current surface position. */
alphaV = m_alphaV->eval(bRec.its).average(), MicrofacetDistribution distr(
sampleAlphaU = alphaU, m_type,
sampleAlphaV = alphaV; m_alphaU->eval(bRec.its).average(),
m_alphaV->eval(bRec.its).average(),
m_sampleVisible
);
#if ENLARGE_LOBE_TRICK == 1 /* Trick by Walter et al.: slightly scale the roughness values to
Float factor = (1.2f - 0.2f * std::sqrt( reduce importance sampling weights. Not needed for the
Heitz and D'Eon sampling technique. */
MicrofacetDistribution sampleDistr(distr);
if (!m_sampleVisible)
sampleDistr.scaleAlpha(1.2f - 0.2f * std::sqrt(
std::abs(Frame::cosTheta(bRec.wi)))); std::abs(Frame::cosTheta(bRec.wi))));
sampleAlphaU *= factor; sampleAlphaV *= factor;
#endif
alphaU = m_distribution.transformRoughness(alphaU); /* Sample M, the microfacet normal */
alphaV = m_distribution.transformRoughness(alphaV);
sampleAlphaU = m_distribution.transformRoughness(sampleAlphaU);
sampleAlphaV = m_distribution.transformRoughness(sampleAlphaV);
/* Sample M, the microsurface normal */
Float microfacetPDF; Float microfacetPDF;
const Normal m = m_distribution.sample(sample, const Normal m = sampleDistr.sample(math::signum(Frame::cosTheta(bRec.wi)) * bRec.wi, sample, microfacetPDF);
sampleAlphaU, sampleAlphaV, microfacetPDF);
if (microfacetPDF == 0) if (microfacetPDF == 0)
return Spectrum(0.0f); return Spectrum(0.0f);
pdf = microfacetPDF; pdf = microfacetPDF;
Float cosThetaT, numerator = 1.0f; Float cosThetaT;
Float F = fresnelDielectricExt(dot(bRec.wi, m), cosThetaT, m_eta); Float F = fresnelDielectricExt(dot(bRec.wi, m), cosThetaT, m_eta);
Spectrum weight(1.0f);
if (hasReflection && hasTransmission) { if (hasReflection && hasTransmission) {
if (bRec.sampler->next1D() > F) { if (bRec.sampler->next1D() > F) {
@ -549,14 +558,12 @@ public:
pdf *= F; pdf *= F;
} }
} else { } else {
numerator = hasReflection ? F : (1-F); weight *= hasReflection ? F : (1-F);
} }
Spectrum result;
Float dwh_dwo; Float dwh_dwo;
if (sampleReflection) { if (sampleReflection) {
/* Perfect specular reflection based on the microsurface normal */ /* Perfect specular reflection based on the microfacet normal */
bRec.wo = reflect(bRec.wi, m); bRec.wo = reflect(bRec.wi, m);
bRec.eta = 1.0f; bRec.eta = 1.0f;
bRec.sampledComponent = 0; bRec.sampledComponent = 0;
@ -566,7 +573,7 @@ public:
if (Frame::cosTheta(bRec.wi) * Frame::cosTheta(bRec.wo) <= 0) if (Frame::cosTheta(bRec.wi) * Frame::cosTheta(bRec.wo) <= 0)
return Spectrum(0.0f); return Spectrum(0.0f);
result = m_specularReflectance->eval(bRec.its); weight *= m_specularReflectance->eval(bRec.its);
/* Jacobian of the half-direction mapping */ /* Jacobian of the half-direction mapping */
dwh_dwo = 1.0f / (4.0f * dot(bRec.wo, m)); dwh_dwo = 1.0f / (4.0f * dot(bRec.wo, m));
@ -574,7 +581,7 @@ public:
if (cosThetaT == 0) if (cosThetaT == 0)
return Spectrum(0.0f); return Spectrum(0.0f);
/* Perfect specular transmission based on the microsurface normal */ /* Perfect specular transmission based on the microfacet normal */
bRec.wo = refract(bRec.wi, m, m_eta, cosThetaT); bRec.wo = refract(bRec.wi, m, m_eta, cosThetaT);
bRec.eta = cosThetaT < 0 ? m_eta : m_invEta; bRec.eta = cosThetaT < 0 ? m_eta : m_invEta;
bRec.sampledComponent = 1; bRec.sampledComponent = 1;
@ -589,22 +596,22 @@ public:
Float factor = (bRec.mode == ERadiance) Float factor = (bRec.mode == ERadiance)
? (cosThetaT < 0 ? m_invEta : m_eta) : 1.0f; ? (cosThetaT < 0 ? m_invEta : m_eta) : 1.0f;
result = m_specularTransmittance->eval(bRec.its) * (factor * factor); weight *= m_specularTransmittance->eval(bRec.its) * (factor * factor);
/* Jacobian of the half-direction mapping */ /* Jacobian of the half-direction mapping */
Float sqrtDenom = dot(bRec.wi, m) + bRec.eta * dot(bRec.wo, m); Float sqrtDenom = dot(bRec.wi, m) + bRec.eta * dot(bRec.wo, m);
dwh_dwo = (bRec.eta*bRec.eta * dot(bRec.wo, m)) / (sqrtDenom*sqrtDenom); dwh_dwo = (bRec.eta*bRec.eta * dot(bRec.wo, m)) / (sqrtDenom*sqrtDenom);
} }
numerator *= m_distribution.eval(m, alphaU, alphaV) if (m_sampleVisible)
* m_distribution.G(bRec.wi, bRec.wo, m, alphaU, alphaV) weight *= distr.smithG1(bRec.wo, m);
* dot(bRec.wi, m); else
weight *= std::abs(distr.eval(m) * distr.G(bRec.wi, bRec.wo, m)
Float denominator = microfacetPDF * Frame::cosTheta(bRec.wi); * dot(bRec.wi, m) / (microfacetPDF * Frame::cosTheta(bRec.wi)));
pdf *= std::abs(dwh_dwo); pdf *= std::abs(dwh_dwo);
return result * std::abs(numerator / denominator); return weight;
} }
void addChild(const std::string &name, ConfigurableObject *child) { void addChild(const std::string &name, ConfigurableObject *child) {
@ -626,17 +633,6 @@ public:
} }
} }
void serialize(Stream *stream, InstanceManager *manager) const {
BSDF::serialize(stream, manager);
stream->writeUInt((uint32_t) m_distribution.getType());
manager->serialize(stream, m_alphaU.get());
manager->serialize(stream, m_alphaV.get());
manager->serialize(stream, m_specularReflectance.get());
manager->serialize(stream, m_specularTransmittance.get());
stream->writeFloat(m_eta);
}
Float getEta() const { Float getEta() const {
return m_eta; return m_eta;
} }
@ -650,7 +646,8 @@ public:
std::ostringstream oss; std::ostringstream oss;
oss << "RoughDielectric[" << endl oss << "RoughDielectric[" << endl
<< " id = \"" << getID() << "\"," << endl << " id = \"" << getID() << "\"," << endl
<< " distribution = " << m_distribution.toString() << "," << endl << " distribution = " << MicrofacetDistribution::distributionName(m_type) << "," << endl
<< " sampleVisible = " << m_sampleVisible << "," << endl
<< " eta = " << m_eta << "," << endl << " eta = " << m_eta << "," << endl
<< " alphaU = " << indent(m_alphaU->toString()) << "," << endl << " alphaU = " << indent(m_alphaU->toString()) << "," << endl
<< " alphaV = " << indent(m_alphaV->toString()) << "," << endl << " alphaV = " << indent(m_alphaV->toString()) << "," << endl
@ -664,11 +661,12 @@ public:
MTS_DECLARE_CLASS() MTS_DECLARE_CLASS()
private: private:
MicrofacetDistribution m_distribution; MicrofacetDistribution::EType m_type;
ref<Texture> m_specularTransmittance; ref<Texture> m_specularTransmittance;
ref<Texture> m_specularReflectance; ref<Texture> m_specularReflectance;
ref<Texture> m_alphaU, m_alphaV; ref<Texture> m_alphaU, m_alphaV;
Float m_eta, m_invEta; Float m_eta, m_invEta;
bool m_sampleVisible;
}; };
/* Fake glass shader -- it is really hopeless to visualize /* Fake glass shader -- it is really hopeless to visualize

View File

@ -32,18 +32,20 @@ MTS_NAMESPACE_BEGIN
* \parameter{distribution}{\String}{ * \parameter{distribution}{\String}{
* Specifies the type of microfacet normal distribution * Specifies the type of microfacet normal distribution
* used to model the surface roughness. * used to model the surface roughness.
* \vspace{-1mm}
* \begin{enumerate}[(i)] * \begin{enumerate}[(i)]
* \item \code{beckmann}: Physically-based distribution derived from * \item \code{beckmann}: Physically-based distribution derived from
* Gaussian random surfaces. This is the default. * Gaussian random surfaces. This is the default.\vspace{-1.5mm}
* \item \code{ggx}: New distribution proposed by * \item \code{ggx}: The GGX \cite{Walter07Microfacet} distribution (also known as
* Walter et al. \cite{Walter07Microfacet}, which is meant to better handle * Trowbridge-Reitz \cite{Trowbridge19975Average} distribution)
* the long tails observed in measurements of ground surfaces. * was designed to better approximate the long tails observed in measurements
* Renderings with this distribution may converge slowly. * of ground surfaces, which are not modeled by the Beckmann distribution.
* \item \code{phong}: Classical $\cos^p\theta$ distribution. * \vspace{-1.5mm}
* Due to the underlying microfacet theory, * \item \code{phong}: Classical Phong distribution.
* the use of this distribution here leads to more realistic * In most cases, the \code{ggx} and \code{beckmann} distributions
* behavior than the separately available \pluginref{phong} plugin. * should be preferred, since they provide better importance sampling
* \vspace{-3mm} * and accurate shadowing/masking computations.
* \vspace{-4mm}
* \end{enumerate} * \end{enumerate}
* } * }
* \parameter{alpha}{\Float\Or\Texture}{ * \parameter{alpha}{\Float\Or\Texture}{
@ -52,10 +54,16 @@ MTS_NAMESPACE_BEGIN
* \emph{root mean square} (RMS) slope of the microfacets. * \emph{root mean square} (RMS) slope of the microfacets.
* \default{0.1}. * \default{0.1}.
* } * }
*
* \parameter{intIOR}{\Float\Or\String}{Interior index of refraction specified * \parameter{intIOR}{\Float\Or\String}{Interior index of refraction specified
* numerically or using a known material name. \default{\texttt{polypropylene} / 1.49}} * numerically or using a known material name. \default{\texttt{polypropylene} / 1.49}}
* \parameter{extIOR}{\Float\Or\String}{Exterior index of refraction specified * \parameter{extIOR}{\Float\Or\String}{Exterior index of refraction specified
* numerically or using a known material name. \default{\texttt{air} / 1.000277}} * numerically or using a known material name. \default{\texttt{air} / 1.000277}}
* \parameter{sampleVisible}{\Boolean}{
* Enables an improved importance sampling technique. Refer to
* pages \pageref{plg:roughconductor} and \pageref{sec:visiblenormal-sampling}
* for details. \default{\code{true}}
* }
* \parameter{specular\showbreak Reflectance}{\Spectrum\Or\Texture}{Optional * \parameter{specular\showbreak Reflectance}{\Spectrum\Or\Texture}{Optional
* factor that can be used to modulate the specular reflection component. Note * factor that can be used to modulate the specular reflection component. Note
* that for physical realism, this parameter should never be touched. \default{1.0}} * that for physical realism, this parameter should never be touched. \default{1.0}}
@ -75,12 +83,12 @@ MTS_NAMESPACE_BEGIN
* preferred over heuristic models like \pluginref{phong} and \pluginref{ward} * preferred over heuristic models like \pluginref{phong} and \pluginref{ward}
* when possible. * when possible.
* *
* Microfacet theory describes rough surfaces as an arrangement of unresolved and * Microfacet theory describes rough surfaces as an arrangement of
* ideally specular facets, whose normal directions are given by a specially * unresolved and ideally specular facets, whose normal directions are given by
* chosen \emph{microfacet distribution}. * a specially chosen \emph{microfacet distribution}. By accounting for shadowing
* By accounting for shadowing and masking effects between these facets, it is * and masking effects between these facets, it is possible to reproduce the important
* possible to reproduce the important off-specular reflections peaks observed * off-specular reflections peaks observed in real-world measurements of such
* in real-world measurements of such materials. * materials.
* *
* \renderings{ * \renderings{
* \rendering{Beckmann, $\alpha=0.1$}{bsdf_roughplastic_beckmann} * \rendering{Beckmann, $\alpha=0.1$}{bsdf_roughplastic_beckmann}
@ -112,12 +120,10 @@ MTS_NAMESPACE_BEGIN
* of this parameter given in the \pluginref{plastic} plugin section * of this parameter given in the \pluginref{plastic} plugin section
* on \pluginpage{plastic}. * on \pluginpage{plastic}.
* *
* * To get an intuition about the effect of the surface roughness parameter
* To get an intuition about the effect of the surface roughness * $\alpha$, consider the following approximate classification: a value of
* parameter $\alpha$, consider the following approximate classification: * $\alpha=0.001-0.01$ corresponds to a material with slight imperfections
* a value of $\alpha=0.001-0.01$ corresponds to a material * on an otherwise smooth surface finish, $\alpha=0.1$ is relatively rough,
* with slight imperfections on an
* otherwise smooth surface finish, $\alpha=0.1$ is relatively rough,
* and $\alpha=0.3-0.7$ is \emph{extremely} rough (e.g. an etched or ground * and $\alpha=0.3-0.7$ is \emph{extremely} rough (e.g. an etched or ground
* finish). Values significantly above that are probably not too realistic. * finish). Values significantly above that are probably not too realistic.
* *
@ -133,7 +139,6 @@ MTS_NAMESPACE_BEGIN
* \pluginref{roughplastic} and support for nonlinear color shifts. * \pluginref{roughplastic} and support for nonlinear color shifts.
* } * }
* } * }
* \newpage
* \renderings{ * \renderings{
* \rendering{Wood material with smooth horizontal stripes}{bsdf_roughplastic_roughtex1} * \rendering{Wood material with smooth horizontal stripes}{bsdf_roughplastic_roughtex1}
* \rendering{A material with imperfections at a much smaller scale than what * \rendering{A material with imperfections at a much smaller scale than what
@ -181,11 +186,11 @@ MTS_NAMESPACE_BEGIN
* values of this function over a large range of parameter values. At runtime, * values of this function over a large range of parameter values. At runtime,
* the relevant parts are extracted using tricubic interpolation. * the relevant parts are extracted using tricubic interpolation.
* *
* When rendering with the Phong microfacet distributions, a conversion * When rendering with the Phong microfacet distribution, a conversion is
* is used to turn the specified $\alpha$ roughness value into the Phong * used to turn the specified Beckmann-equivalent $\alpha$ roughness value
* exponent. This is done in a way, such that the different distributions * into the exponent parameter of this distribution. This is done in a way,
* all produce a similar appearance for the same value of $\alpha$. * such that the same value $\alpha$ will produce a similar appearance across
* * different microfacet distributions.
*/ */
class RoughPlastic : public BSDF { class RoughPlastic : public BSDF {
public: public:
@ -207,27 +212,25 @@ public:
m_eta = intIOR / extIOR; m_eta = intIOR / extIOR;
m_distribution = MicrofacetDistribution( m_nonlinear = props.getBoolean("nonlinear", false);
props.getString("distribution", "beckmann")
);
if (m_distribution.isAnisotropic()) MicrofacetDistribution distr(props);
m_type = distr.getType();
m_sampleVisible = distr.getSampleVisible();
if (distr.isAnisotropic())
Log(EError, "The 'roughplastic' plugin currently does not support " Log(EError, "The 'roughplastic' plugin currently does not support "
"anisotropic microfacet distributions!"); "anisotropic microfacet distributions!");
m_nonlinear = props.getBoolean("nonlinear", false); m_alpha = new ConstantFloatTexture(distr.getAlpha());
m_alpha = new ConstantFloatTexture(
props.getFloat("alpha", 0.1f));
m_specularSamplingWeight = 0.0f; m_specularSamplingWeight = 0.0f;
} }
RoughPlastic(Stream *stream, InstanceManager *manager) RoughPlastic(Stream *stream, InstanceManager *manager)
: BSDF(stream, manager) { : BSDF(stream, manager) {
m_distribution = MicrofacetDistribution( m_type = (MicrofacetDistribution::EType) stream->readUInt();
(MicrofacetDistribution::EType) stream->readUInt() m_sampleVisible = stream->readBool();
);
m_specularReflectance = static_cast<Texture *>(manager->getInstance(stream)); m_specularReflectance = static_cast<Texture *>(manager->getInstance(stream));
m_diffuseReflectance = static_cast<Texture *>(manager->getInstance(stream)); m_diffuseReflectance = static_cast<Texture *>(manager->getInstance(stream));
m_alpha = static_cast<Texture *>(manager->getInstance(stream)); m_alpha = static_cast<Texture *>(manager->getInstance(stream));
@ -240,7 +243,8 @@ public:
void serialize(Stream *stream, InstanceManager *manager) const { void serialize(Stream *stream, InstanceManager *manager) const {
BSDF::serialize(stream, manager); BSDF::serialize(stream, manager);
stream->writeUInt((uint32_t) m_distribution.getType()); stream->writeUInt((uint32_t) m_type);
stream->writeBool(m_sampleVisible);
manager->serialize(stream, m_specularReflectance.get()); manager->serialize(stream, m_specularReflectance.get());
manager->serialize(stream, m_diffuseReflectance.get()); manager->serialize(stream, m_diffuseReflectance.get());
manager->serialize(stream, m_alpha.get()); manager->serialize(stream, m_alpha.get());
@ -277,8 +281,7 @@ public:
if (!m_externalRoughTransmittance.get()) { if (!m_externalRoughTransmittance.get()) {
/* Load precomputed data used to compute the rough /* Load precomputed data used to compute the rough
transmittance through the dielectric interface */ transmittance through the dielectric interface */
m_externalRoughTransmittance = new RoughTransmittance( m_externalRoughTransmittance = new RoughTransmittance(m_type);
m_distribution.getType());
m_externalRoughTransmittance->checkEta(m_eta); m_externalRoughTransmittance->checkEta(m_eta);
m_externalRoughTransmittance->checkAlpha(m_alpha->getMinimum().average()); m_externalRoughTransmittance->checkAlpha(m_alpha->getMinimum().average());
@ -332,23 +335,27 @@ public:
(!hasSpecular && !hasDiffuse)) (!hasSpecular && !hasDiffuse))
return Spectrum(0.0f); return Spectrum(0.0f);
/* Evaluate the roughness texture */ /* Construct the microfacet distribution matching the
Float alpha = m_alpha->eval(bRec.its).average(); roughness values at the current surface position. */
Float alphaT = m_distribution.transformRoughness(alpha); MicrofacetDistribution distr(
m_type,
m_alpha->eval(bRec.its).average(),
m_sampleVisible
);
Spectrum result(0.0f); Spectrum result(0.0f);
if (hasSpecular) { if (hasSpecular) {
/* Calculate the reflection half-vector */ /* Calculate the reflection half-vector */
const Vector H = normalize(bRec.wo+bRec.wi); const Vector H = normalize(bRec.wo+bRec.wi);
/* Evaluate the microsurface normal distribution */ /* Evaluate the microfacet normal distribution */
const Float D = m_distribution.eval(H, alphaT); const Float D = distr.eval(H);
/* Fresnel term */ /* Fresnel term */
const Float F = fresnelDielectricExt(dot(bRec.wi, H), m_eta); const Float F = fresnelDielectricExt(dot(bRec.wi, H), m_eta);
/* Smith's shadow-masking function */ /* Smith's shadow-masking function */
const Float G = m_distribution.G(bRec.wi, bRec.wo, H, alphaT); const Float G = distr.G(bRec.wi, bRec.wo, H);
/* Calculate the specular reflection component */ /* Calculate the specular reflection component */
Float value = F * D * G / Float value = F * D * G /
@ -359,9 +366,9 @@ public:
if (hasDiffuse) { if (hasDiffuse) {
Spectrum diff = m_diffuseReflectance->eval(bRec.its); Spectrum diff = m_diffuseReflectance->eval(bRec.its);
Float T12 = m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wi), alpha); Float T12 = m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wi), distr.getAlpha());
Float T21 = m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wo), alpha); Float T21 = m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wo), distr.getAlpha());
Float Fdr = 1-m_internalRoughTransmittance->evalDiffuse(alpha); Float Fdr = 1-m_internalRoughTransmittance->evalDiffuse(distr.getAlpha());
if (m_nonlinear) if (m_nonlinear)
diff /= Spectrum(1.0f) - diff * Fdr; diff /= Spectrum(1.0f) - diff * Fdr;
@ -386,9 +393,13 @@ public:
(!hasSpecular && !hasDiffuse)) (!hasSpecular && !hasDiffuse))
return 0.0f; return 0.0f;
/* Evaluate the roughness texture */ /* Construct the microfacet distribution matching the
Float alpha = m_alpha->eval(bRec.its).average(); roughness values at the current surface position. */
Float alphaT = m_distribution.transformRoughness(alpha); MicrofacetDistribution distr(
m_type,
m_alpha->eval(bRec.its).average(),
m_sampleVisible
);
/* Calculate the reflection half-vector */ /* Calculate the reflection half-vector */
const Vector H = normalize(bRec.wo+bRec.wi); const Vector H = normalize(bRec.wo+bRec.wi);
@ -396,7 +407,7 @@ public:
Float probDiffuse, probSpecular; Float probDiffuse, probSpecular;
if (hasSpecular && hasDiffuse) { if (hasSpecular && hasDiffuse) {
/* Find the probability of sampling the specular component */ /* Find the probability of sampling the specular component */
probSpecular = 1-m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wi), alpha); probSpecular = 1-m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wi), distr.getAlpha());
/* Reallocate samples */ /* Reallocate samples */
probSpecular = (probSpecular*m_specularSamplingWeight) / probSpecular = (probSpecular*m_specularSamplingWeight) /
@ -413,8 +424,8 @@ public:
/* Jacobian of the half-direction mapping */ /* Jacobian of the half-direction mapping */
const Float dwh_dwo = 1.0f / (4.0f * dot(bRec.wo, H)); const Float dwh_dwo = 1.0f / (4.0f * dot(bRec.wo, H));
/* Evaluate the microsurface normal distribution */ /* Evaluate the microfacet model sampling density function */
const Float prob = m_distribution.pdf(H, alphaT); const Float prob = distr.pdf(bRec.wi, H);
result = prob * dwh_dwo * probSpecular; result = prob * dwh_dwo * probSpecular;
} }
@ -437,14 +448,18 @@ public:
bool choseSpecular = hasSpecular; bool choseSpecular = hasSpecular;
Point2 sample(_sample); Point2 sample(_sample);
/* Evaluate the roughness texture */ /* Construct the microfacet distribution matching the
Float alpha = m_alpha->eval(bRec.its).average(); roughness values at the current surface position. */
Float alphaT = m_distribution.transformRoughness(alpha); MicrofacetDistribution distr(
m_type,
m_alpha->eval(bRec.its).average(),
m_sampleVisible
);
Float probSpecular; Float probSpecular;
if (hasSpecular && hasDiffuse) { if (hasSpecular && hasDiffuse) {
/* Find the probability of sampling the specular component */ /* Find the probability of sampling the specular component */
probSpecular = 1 - m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wi), alpha); probSpecular = 1 - m_externalRoughTransmittance->eval(Frame::cosTheta(bRec.wi), distr.getAlpha());
/* Reallocate samples */ /* Reallocate samples */
probSpecular = (probSpecular*m_specularSamplingWeight) / probSpecular = (probSpecular*m_specularSamplingWeight) /
@ -460,8 +475,8 @@ public:
} }
if (choseSpecular) { if (choseSpecular) {
/* Perfect specular reflection based on the microsurface normal */ /* Perfect specular reflection based on the microfacet normal */
Normal m = m_distribution.sample(sample, alphaT); Normal m = distr.sample(bRec.wi, sample);
bRec.wo = reflect(bRec.wi, m); bRec.wo = reflect(bRec.wi, m);
bRec.sampledComponent = 0; bRec.sampledComponent = 0;
bRec.sampledType = EGlossyReflection; bRec.sampledType = EGlossyReflection;
@ -518,7 +533,8 @@ public:
std::ostringstream oss; std::ostringstream oss;
oss << "RoughPlastic[" << endl oss << "RoughPlastic[" << endl
<< " id = \"" << getID() << "\"," << endl << " id = \"" << getID() << "\"," << endl
<< " distribution = " << m_distribution.toString() << "," << endl << " distribution = " << MicrofacetDistribution::distributionName(m_type) << "," << endl
<< " sampleVisible = " << m_sampleVisible << "," << endl
<< " alpha = " << indent(m_alpha->toString()) << "," << endl << " alpha = " << indent(m_alpha->toString()) << "," << endl
<< " specularReflectance = " << indent(m_specularReflectance->toString()) << "," << endl << " specularReflectance = " << indent(m_specularReflectance->toString()) << "," << endl
<< " diffuseReflectance = " << indent(m_diffuseReflectance->toString()) << "," << endl << " diffuseReflectance = " << indent(m_diffuseReflectance->toString()) << "," << endl
@ -534,7 +550,7 @@ public:
MTS_DECLARE_CLASS() MTS_DECLARE_CLASS()
private: private:
MicrofacetDistribution m_distribution; MicrofacetDistribution::EType m_type;
ref<RoughTransmittance> m_externalRoughTransmittance; ref<RoughTransmittance> m_externalRoughTransmittance;
ref<RoughTransmittance> m_internalRoughTransmittance; ref<RoughTransmittance> m_internalRoughTransmittance;
ref<Texture> m_diffuseReflectance; ref<Texture> m_diffuseReflectance;
@ -543,6 +559,7 @@ private:
Float m_eta, m_invEta2; Float m_eta, m_invEta2;
Float m_specularSamplingWeight; Float m_specularSamplingWeight;
bool m_nonlinear; bool m_nonlinear;
bool m_sampleVisible;
}; };
/** /**

View File

@ -0,0 +1,176 @@
/*
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/>.
*/
#include <mitsuba/core/plugin.h>
#include <mitsuba/core/statistics.h>
#include <mitsuba/core/chisquare.h>
#include <mitsuba/core/fresolver.h>
#include <mitsuba/render/testcase.h>
#include <boost/bind.hpp>
#include "../bsdfs/microfacet.h"
/* Statistical significance level of the test. Set to
1/4 percent by default -- we want there to be strong
evidence of an implementaiton error before failing
a test case */
#define SIGNIFICANCE_LEVEL 0.0025f
/* Relative bound on what is still accepted as roundoff
error -- be quite tolerant */
#if defined(SINGLE_PRECISION)
#define ERROR_REQ 1e-2f
#else
#define ERROR_REQ 1e-5
#endif
MTS_NAMESPACE_BEGIN
class TestChiSquare : public TestCase {
public:
MTS_BEGIN_TESTCASE()
MTS_DECLARE_TEST(test01_Microfacet)
MTS_DECLARE_TEST(test02_MicrofacetVisible)
MTS_END_TESTCASE()
class MicrofacetAdapter {
public:
MicrofacetAdapter(Sampler *sampler, const MicrofacetDistribution &distr, const Vector &wi = Vector(0.0f)) : m_sampler(sampler), m_distr(distr), m_wi(wi) { }
boost::tuple<Vector, Float, EMeasure> generateSample() {
Float pdf;
if (m_wi.lengthSquared() == 0) {
Normal m = m_distr.sampleAll(m_sampler->next2D(), pdf);
Float pdf_ref = m_distr.pdfAll(m);
SAssert(std::isfinite(pdf) && pdf > 0);
SAssert(std::isfinite(pdf_ref) && pdf_ref > 0);
SAssert(std::isfinite(m.x) && std::isfinite(m.y) && std::isfinite(m.z));
SAssert(std::abs(m.length() - 1) < 1e-4f);
SAssert(std::abs((pdf-pdf_ref)/pdf_ref) < 1e-4f);
return boost::make_tuple(m, 1.0f, ESolidAngle);
} else {
Normal m = m_distr.sampleVisible(m_wi, m_sampler->next2D());
SAssert(std::isfinite(m.x) && std::isfinite(m.y) && std::isfinite(m.z));
SAssert(std::abs(m.length() - 1) < 1e-4f);
return boost::make_tuple(m, 1.0f, ESolidAngle);
}
}
Float pdf(const Vector &d, EMeasure measure) const {
if (measure != ESolidAngle)
return 0.0f;
Float pdf = m_wi.lengthSquared() == 0 ? m_distr.pdfAll(d)
: m_distr.pdfVisible(m_wi, d);
SAssert(std::isfinite(pdf) && pdf >= 0);
return pdf;
}
private:
ref<Sampler> m_sampler;
MicrofacetDistribution m_distr;
Vector m_wi;
};
void test01_Microfacet() {
int thetaBins = 20;
std::vector<MicrofacetDistribution> distrs;
distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EBeckmann, 0.5f));
distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EBeckmann, 0.5f, 0.3f));
distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EGGX, 0.5f));
distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EGGX, 0.5f, 0.3f));
distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EPhong, 0.5f));
distrs.push_back(MicrofacetDistribution(MicrofacetDistribution::EPhong, 0.5f, 0.3f));
ref<Sampler> sampler = static_cast<Sampler *> (PluginManager::getInstance()->
createObject(MTS_CLASS(Sampler), Properties("independent")));
ref<ChiSquare> chiSqr = new ChiSquare(thetaBins, 2*thetaBins, (int) distrs.size());
chiSqr->setLogLevel(EDebug);
for (size_t i=0; i<distrs.size(); ++i) {
Log(EInfo, "Testing %s", distrs[i].toString().c_str());
// Initialize the tables used by the chi-square test
MicrofacetAdapter adapter(sampler, distrs[i]);
chiSqr->fill(
boost::bind(&MicrofacetAdapter::generateSample, &adapter),
boost::bind(&MicrofacetAdapter::pdf, &adapter, _1, _2)
);
// (the following assumes that the distribution has 1 parameter, e.g. exponent value)
ChiSquare::ETestResult result = chiSqr->runTest(SIGNIFICANCE_LEVEL);
if (result == ChiSquare::EReject) {
std::string filename = formatString("failure_%i.m", (int) i);
chiSqr->dumpTables(filename);
failAndContinue(formatString("Uh oh, the chi-square test indicates a potential "
"issue. Dumped the contingency tables to '%s' for user analysis",
filename.c_str()));
} else {
//chiSqr->dumpTables(formatString("success_%i.m", (int) i));
succeed();
}
}
}
void test02_MicrofacetVisible() {
int thetaBins = 10;
std::vector<std::pair<MicrofacetDistribution, Vector> > distrs;
ref<Sampler> sampler = static_cast<Sampler *> (PluginManager::getInstance()->
createObject(MTS_CLASS(Sampler), Properties("independent")));
for (int i=0; i<10; ++i) {
Vector wi = warp::squareToUniformHemisphere(sampler->next2D());
distrs.push_back(std::make_pair(MicrofacetDistribution(MicrofacetDistribution::EBeckmann, 0.3f), wi));
distrs.push_back(std::make_pair(MicrofacetDistribution(MicrofacetDistribution::EBeckmann, 0.5f, 0.3f), wi));
distrs.push_back(std::make_pair(MicrofacetDistribution(MicrofacetDistribution::EGGX, 0.1f), wi));
distrs.push_back(std::make_pair(MicrofacetDistribution(MicrofacetDistribution::EGGX, 0.2f, 0.3f), wi));
}
ref<ChiSquare> chiSqr = new ChiSquare(thetaBins, 2*thetaBins, (int) distrs.size());
chiSqr->setLogLevel(EDebug);
for (size_t i=0; i<distrs.size(); ++i) {
Log(EInfo, "Testing %s (wi=%s)", distrs[i].first.toString().c_str(), distrs[i].second.toString().c_str());
// Initialize the tables used by the chi-square test
MicrofacetAdapter adapter(sampler, distrs[i].first, distrs[i].second);
chiSqr->fill(
boost::bind(&MicrofacetAdapter::generateSample, &adapter),
boost::bind(&MicrofacetAdapter::pdf, &adapter, _1, _2)
);
// (the following assumes that the distribution has 1 parameter, e.g. exponent value)
ChiSquare::ETestResult result = chiSqr->runTest(SIGNIFICANCE_LEVEL);
if (result == ChiSquare::EReject) {
std::string filename = formatString("failure_%i.m", (int) i);
chiSqr->dumpTables(filename);
failAndContinue(formatString("Uh oh, the chi-square test indicates a potential "
"issue. Dumped the contingency tables to '%s' for user analysis",
filename.c_str()));
} else {
//chiSqr->dumpTables(formatString("success_%i.m", (int) i));
succeed();
}
}
}
};
MTS_EXPORT_TESTCASE(TestChiSquare, "Chi-square test for microfacet sampling")
MTS_NAMESPACE_END