blender/intern/cycles/kernel/osl/bsdf_microfacet.cpp

534 lines
24 KiB
C++
Raw Normal View History

/*
* Adapted from Open Shading Language with this license:
*
* Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al.
* All Rights Reserved.
*
* Modifications Copyright 2011, Blender Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Sony Pictures Imageworks nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <OpenImageIO/fmath.h>
#include <OSL/genclosure.h>
#include "osl_closures.h"
#include "util_math.h"
using namespace OSL;
CCL_NAMESPACE_BEGIN
// TODO: fresnel_dielectric is only used for derivatives, could be optimized
// TODO: refactor these two classes so they share everything by the microfacet
// distribution terms
// microfacet model with GGX facet distribution
// see http://www.graphics.cornell.edu/~bjw/microfacetbsdf.pdf
template <int Refractive = 0>
class MicrofacetGGXClosure : public BSDFClosure {
public:
Vec3 m_N;
float m_ag; // width parameter (roughness)
float m_eta; // index of refraction (for fresnel term)
MicrofacetGGXClosure() : BSDFClosure(Labels::GLOSSY, Refractive ? Back : Front) { m_eta = 1.0f; }
void setup()
{
m_ag = clamp(m_ag, 1e-5f, 1.0f);
}
bool mergeable (const ClosurePrimitive *other) const {
const MicrofacetGGXClosure *comp = (const MicrofacetGGXClosure *)other;
return m_N == comp->m_N && m_ag == comp->m_ag &&
m_eta == comp->m_eta && BSDFClosure::mergeable(other);
}
size_t memsize () const { return sizeof(*this); }
const char *name () const {
return Refractive ? "microfacet_ggx_refraction" : "microfacet_ggx";
}
void print_on (std::ostream &out) const {
out << name() << " (";
out << "(" << m_N[0] << ", " << m_N[1] << ", " << m_N[2] << "), ";
out << m_ag << ", ";
out << m_eta;
out << ")";
}
float albedo (const Vec3 &omega_out) const
{
return 1.0f;
}
Color3 eval_reflect (const Vec3 &omega_out, const Vec3 &omega_in, float& pdf) const
{
if (Refractive == 1) return Color3 (0, 0, 0);
float cosNO = m_N.dot(omega_out);
float cosNI = m_N.dot(omega_in);
if (cosNI > 0 && cosNO > 0) {
// get half vector
Vec3 Hr = omega_in + omega_out;
Hr.normalize();
// eq. 20: (F*G*D)/(4*in*on)
// eq. 33: first we calculate D(m) with m=Hr:
float alpha2 = m_ag * m_ag;
float cosThetaM = m_N.dot(Hr);
float cosThetaM2 = cosThetaM * cosThetaM;
float tanThetaM2 = (1 - cosThetaM2) / cosThetaM2;
float cosThetaM4 = cosThetaM2 * cosThetaM2;
float D = alpha2 / ((float) M_PI * cosThetaM4 * (alpha2 + tanThetaM2) * (alpha2 + tanThetaM2));
// eq. 34: now calculate G1(i,m) and G1(o,m)
float G1o = 2 / (1 + sqrtf(1 + alpha2 * (1 - cosNO * cosNO) / (cosNO * cosNO)));
float G1i = 2 / (1 + sqrtf(1 + alpha2 * (1 - cosNI * cosNI) / (cosNI * cosNI)));
float G = G1o * G1i;
float out = (G * D) * 0.25f / cosNO;
// eq. 24
float pm = D * cosThetaM;
// convert into pdf of the sampled direction
// eq. 38 - but see also:
// eq. 17 in http://www.graphics.cornell.edu/~bjw/wardnotes.pdf
pdf = pm * 0.25f / Hr.dot(omega_out);
return Color3 (out, out, out);
}
return Color3 (0, 0, 0);
}
Color3 eval_transmit (const Vec3 &omega_out, const Vec3 &omega_in, float& pdf) const
{
if (Refractive == 0) return Color3 (0, 0, 0);
float cosNO = m_N.dot(omega_out);
float cosNI = m_N.dot(omega_in);
if (cosNO <= 0 || cosNI >= 0)
return Color3 (0, 0, 0); // vectors on same side -- not possible
// compute half-vector of the refraction (eq. 16)
Vec3 ht = -(m_eta * omega_in + omega_out);
Vec3 Ht = ht; Ht.normalize();
float cosHO = Ht.dot(omega_out);
float cosHI = Ht.dot(omega_in);
// eq. 33: first we calculate D(m) with m=Ht:
float alpha2 = m_ag * m_ag;
float cosThetaM = m_N.dot(Ht);
float cosThetaM2 = cosThetaM * cosThetaM;
float tanThetaM2 = (1 - cosThetaM2) / cosThetaM2;
float cosThetaM4 = cosThetaM2 * cosThetaM2;
float D = alpha2 / ((float) M_PI * cosThetaM4 * (alpha2 + tanThetaM2) * (alpha2 + tanThetaM2));
// eq. 34: now calculate G1(i,m) and G1(o,m)
float G1o = 2 / (1 + sqrtf(1 + alpha2 * (1 - cosNO * cosNO) / (cosNO * cosNO)));
float G1i = 2 / (1 + sqrtf(1 + alpha2 * (1 - cosNI * cosNI) / (cosNI * cosNI)));
float G = G1o * G1i;
// probability
float invHt2 = 1 / ht.dot(ht);
pdf = D * fabsf(cosThetaM) * (fabsf(cosHI) * (m_eta * m_eta)) * invHt2;
float out = (fabsf(cosHI * cosHO) * (m_eta * m_eta) * (G * D) * invHt2) / cosNO;
return Color3 (out, out, out);
}
ustring sample (const Vec3 &Ng,
const Vec3 &omega_out, const Vec3 &domega_out_dx, const Vec3 &domega_out_dy,
float randu, float randv,
Vec3 &omega_in, Vec3 &domega_in_dx, Vec3 &domega_in_dy,
float &pdf, Color3 &eval) const
{
float cosNO = m_N.dot(omega_out);
if (cosNO > 0) {
Vec3 X, Y, Z = m_N;
make_orthonormals(Z, X, Y);
// generate a random microfacet normal m
// eq. 35,36:
// we take advantage of cos(atan(x)) == 1/sqrt(1+x^2)
// and sin(atan(x)) == x/sqrt(1+x^2)
float alpha2 = m_ag * m_ag;
float tanThetaM2 = alpha2 * randu / (1 - randu);
float cosThetaM = 1 / sqrtf(1 + tanThetaM2);
float sinThetaM = cosThetaM * sqrtf(tanThetaM2);
float phiM = 2 * float(M_PI) * randv;
Vec3 m = (cosf(phiM) * sinThetaM) * X +
(sinf(phiM) * sinThetaM) * Y +
cosThetaM * Z;
if (Refractive == 0) {
float cosMO = m.dot(omega_out);
if (cosMO > 0) {
// eq. 39 - compute actual reflected direction
omega_in = 2 * cosMO * m - omega_out;
if (Ng.dot(omega_in) > 0) {
// microfacet normal is visible to this ray
// eq. 33
float cosThetaM2 = cosThetaM * cosThetaM;
float cosThetaM4 = cosThetaM2 * cosThetaM2;
float D = alpha2 / (float(M_PI) * cosThetaM4 * (alpha2 + tanThetaM2) * (alpha2 + tanThetaM2));
// eq. 24
float pm = D * cosThetaM;
// convert into pdf of the sampled direction
// eq. 38 - but see also:
// eq. 17 in http://www.graphics.cornell.edu/~bjw/wardnotes.pdf
pdf = pm * 0.25f / cosMO;
// eval BRDF*cosNI
float cosNI = m_N.dot(omega_in);
// eq. 34: now calculate G1(i,m) and G1(o,m)
float G1o = 2 / (1 + sqrtf(1 + alpha2 * (1 - cosNO * cosNO) / (cosNO * cosNO)));
float G1i = 2 / (1 + sqrtf(1 + alpha2 * (1 - cosNI * cosNI) / (cosNI * cosNI)));
float G = G1o * G1i;
// eq. 20: (F*G*D)/(4*in*on)
float out = (G * D) * 0.25f / cosNO;
eval.setValue(out, out, out);
domega_in_dx = (2 * m.dot(domega_out_dx)) * m - domega_out_dx;
domega_in_dy = (2 * m.dot(domega_out_dy)) * m - domega_out_dy;
/* disabled for now - gives texture filtering problems */
#if 0
// Since there is some blur to this reflection, make the
// derivatives a bit bigger. In theory this varies with the
// roughness but the exact relationship is complex and
// requires more ops than are practical.
domega_in_dx *= 10;
domega_in_dy *= 10;
#endif
}
}
} else {
// CAUTION: the i and o variables are inverted relative to the paper
// eq. 39 - compute actual refractive direction
Vec3 R, dRdx, dRdy;
Vec3 T, dTdx, dTdy;
bool inside;
fresnel_dielectric(m_eta, m, omega_out, domega_out_dx, domega_out_dy,
R, dRdx, dRdy,
T, dTdx, dTdy,
inside);
if (!inside) {
omega_in = T;
domega_in_dx = dTdx;
domega_in_dy = dTdy;
// eq. 33
float cosThetaM2 = cosThetaM * cosThetaM;
float cosThetaM4 = cosThetaM2 * cosThetaM2;
float D = alpha2 / (float(M_PI) * cosThetaM4 * (alpha2 + tanThetaM2) * (alpha2 + tanThetaM2));
// eq. 24
float pm = D * cosThetaM;
// eval BRDF*cosNI
float cosNI = m_N.dot(omega_in);
// eq. 34: now calculate G1(i,m) and G1(o,m)
float G1o = 2 / (1 + sqrtf(1 + alpha2 * (1 - cosNO * cosNO) / (cosNO * cosNO)));
float G1i = 2 / (1 + sqrtf(1 + alpha2 * (1 - cosNI * cosNI) / (cosNI * cosNI)));
float G = G1o * G1i;
// eq. 21
float cosHI = m.dot(omega_in);
float cosHO = m.dot(omega_out);
float Ht2 = m_eta * cosHI + cosHO;
Ht2 *= Ht2;
float out = (fabsf(cosHI * cosHO) * (m_eta * m_eta) * (G * D)) / (cosNO * Ht2);
// eq. 38 and eq. 17
pdf = pm * (m_eta * m_eta) * fabsf(cosHI) / Ht2;
eval.setValue(out, out, out);
/* disabled for now - gives texture filtering problems */
#if 0
// Since there is some blur to this refraction, make the
// derivatives a bit bigger. In theory this varies with the
// roughness but the exact relationship is complex and
// requires more ops than are practical.
domega_in_dx *= 10;
domega_in_dy *= 10;
#endif
}
}
}
return Refractive ? Labels::TRANSMIT : Labels::REFLECT;
}
};
// microfacet model with Beckmann facet distribution
// see http://www.graphics.cornell.edu/~bjw/microfacetbsdf.pdf
template <int Refractive = 0>
class MicrofacetBeckmannClosure : public BSDFClosure {
public:
Vec3 m_N;
float m_ab; // width parameter (roughness)
float m_eta; // index of refraction (for fresnel term)
MicrofacetBeckmannClosure() : BSDFClosure(Labels::GLOSSY, Refractive ? Back : Front) { }
void setup()
{
m_ab = clamp(m_ab, 1e-5f, 1.0f);
}
bool mergeable (const ClosurePrimitive *other) const {
const MicrofacetBeckmannClosure *comp = (const MicrofacetBeckmannClosure *)other;
return m_N == comp->m_N && m_ab == comp->m_ab &&
m_eta == comp->m_eta && BSDFClosure::mergeable(other);
}
size_t memsize () const { return sizeof(*this); }
const char * name () const {
return Refractive ? "microfacet_beckmann_refraction"
: "microfacet_beckmann";
}
void print_on (std::ostream &out) const
{
out << name() << " (";
out << "(" << m_N[0] << ", " << m_N[1] << ", " << m_N[2] << "), ";
out << m_ab << ", ";
out << m_eta;
out << ")";
}
float albedo (const Vec3 &omega_out) const
{
return 1.0f;
}
Color3 eval_reflect (const Vec3 &omega_out, const Vec3 &omega_in, float& pdf) const
{
if (Refractive == 1) return Color3 (0, 0, 0);
float cosNO = m_N.dot(omega_out);
float cosNI = m_N.dot(omega_in);
if (cosNO > 0 && cosNI > 0) {
// get half vector
Vec3 Hr = omega_in + omega_out;
Hr.normalize();
// eq. 20: (F*G*D)/(4*in*on)
// eq. 25: first we calculate D(m) with m=Hr:
float alpha2 = m_ab * m_ab;
float cosThetaM = m_N.dot(Hr);
float cosThetaM2 = cosThetaM * cosThetaM;
float tanThetaM2 = (1 - cosThetaM2) / cosThetaM2;
float cosThetaM4 = cosThetaM2 * cosThetaM2;
float D = expf(-tanThetaM2 / alpha2) / (float(M_PI) * alpha2 * cosThetaM4);
// eq. 26, 27: now calculate G1(i,m) and G1(o,m)
float ao = 1 / (m_ab * sqrtf((1 - cosNO * cosNO) / (cosNO * cosNO)));
float ai = 1 / (m_ab * sqrtf((1 - cosNI * cosNI) / (cosNI * cosNI)));
float G1o = ao < 1.6f ? (3.535f * ao + 2.181f * ao * ao) / (1 + 2.276f * ao + 2.577f * ao * ao) : 1.0f;
float G1i = ai < 1.6f ? (3.535f * ai + 2.181f * ai * ai) / (1 + 2.276f * ai + 2.577f * ai * ai) : 1.0f;
float G = G1o * G1i;
float out = (G * D) * 0.25f / cosNO;
// eq. 24
float pm = D * cosThetaM;
// convert into pdf of the sampled direction
// eq. 38 - but see also:
// eq. 17 in http://www.graphics.cornell.edu/~bjw/wardnotes.pdf
pdf = pm * 0.25f / Hr.dot(omega_out);
return Color3 (out, out, out);
}
return Color3 (0, 0, 0);
}
Color3 eval_transmit (const Vec3 &omega_out, const Vec3 &omega_in, float& pdf) const
{
if (Refractive == 0) return Color3 (0, 0, 0);
float cosNO = m_N.dot(omega_out);
float cosNI = m_N.dot(omega_in);
if (cosNO <= 0 || cosNI >= 0)
return Color3 (0, 0, 0);
// compute half-vector of the refraction (eq. 16)
Vec3 ht = -(m_eta * omega_in + omega_out);
Vec3 Ht = ht; Ht.normalize();
float cosHO = Ht.dot(omega_out);
float cosHI = Ht.dot(omega_in);
// eq. 33: first we calculate D(m) with m=Ht:
float alpha2 = m_ab * m_ab;
float cosThetaM = m_N.dot(Ht);
float cosThetaM2 = cosThetaM * cosThetaM;
float tanThetaM2 = (1 - cosThetaM2) / cosThetaM2;
float cosThetaM4 = cosThetaM2 * cosThetaM2;
float D = expf(-tanThetaM2 / alpha2) / (float(M_PI) * alpha2 * cosThetaM4);
// eq. 26, 27: now calculate G1(i,m) and G1(o,m)
float ao = 1 / (m_ab * sqrtf((1 - cosNO * cosNO) / (cosNO * cosNO)));
float ai = 1 / (m_ab * sqrtf((1 - cosNI * cosNI) / (cosNI * cosNI)));
float G1o = ao < 1.6f ? (3.535f * ao + 2.181f * ao * ao) / (1 + 2.276f * ao + 2.577f * ao * ao) : 1.0f;
float G1i = ai < 1.6f ? (3.535f * ai + 2.181f * ai * ai) / (1 + 2.276f * ai + 2.577f * ai * ai) : 1.0f;
float G = G1o * G1i;
// probability
float invHt2 = 1 / ht.dot(ht);
pdf = D * fabsf(cosThetaM) * (fabsf(cosHI) * (m_eta * m_eta)) * invHt2;
float out = (fabsf(cosHI * cosHO) * (m_eta * m_eta) * (G * D) * invHt2) / cosNO;
return Color3 (out, out, out);
}
ustring sample (const Vec3 &Ng,
const Vec3 &omega_out, const Vec3 &domega_out_dx, const Vec3 &domega_out_dy,
float randu, float randv,
Vec3 &omega_in, Vec3 &domega_in_dx, Vec3 &domega_in_dy,
float &pdf, Color3 &eval) const
{
float cosNO = m_N.dot(omega_out);
if (cosNO > 0) {
Vec3 X, Y, Z = m_N;
make_orthonormals(Z, X, Y);
// generate a random microfacet normal m
// eq. 35,36:
// we take advantage of cos(atan(x)) == 1/sqrt(1+x^2)
// and sin(atan(x)) == x/sqrt(1+x^2)
float alpha2 = m_ab * m_ab;
float tanThetaM = sqrtf(-alpha2 * logf(1 - randu));
float cosThetaM = 1 / sqrtf(1 + tanThetaM * tanThetaM);
float sinThetaM = cosThetaM * tanThetaM;
float phiM = 2 * float(M_PI) * randv;
Vec3 m = (cosf(phiM) * sinThetaM) * X +
(sinf(phiM) * sinThetaM) * Y +
cosThetaM * Z;
if (Refractive == 0) {
float cosMO = m.dot(omega_out);
if (cosMO > 0) {
// eq. 39 - compute actual reflected direction
omega_in = 2 * cosMO * m - omega_out;
if (Ng.dot(omega_in) > 0) {
// microfacet normal is visible to this ray
// eq. 25
float cosThetaM2 = cosThetaM * cosThetaM;
float tanThetaM2 = tanThetaM * tanThetaM;
float cosThetaM4 = cosThetaM2 * cosThetaM2;
float D = expf(-tanThetaM2 / alpha2) / (float(M_PI) * alpha2 * cosThetaM4);
// eq. 24
float pm = D * cosThetaM;
// convert into pdf of the sampled direction
// eq. 38 - but see also:
// eq. 17 in http://www.graphics.cornell.edu/~bjw/wardnotes.pdf
pdf = pm * 0.25f / cosMO;
// Eval BRDF*cosNI
float cosNI = m_N.dot(omega_in);
// eq. 26, 27: now calculate G1(i,m) and G1(o,m)
float ao = 1 / (m_ab * sqrtf((1 - cosNO * cosNO) / (cosNO * cosNO)));
float ai = 1 / (m_ab * sqrtf((1 - cosNI * cosNI) / (cosNI * cosNI)));
float G1o = ao < 1.6f ? (3.535f * ao + 2.181f * ao * ao) / (1 + 2.276f * ao + 2.577f * ao * ao) : 1.0f;
float G1i = ai < 1.6f ? (3.535f * ai + 2.181f * ai * ai) / (1 + 2.276f * ai + 2.577f * ai * ai) : 1.0f;
float G = G1o * G1i;
// eq. 20: (F*G*D)/(4*in*on)
float out = (G * D) * 0.25f / cosNO;
eval.setValue(out, out, out);
domega_in_dx = (2 * m.dot(domega_out_dx)) * m - domega_out_dx;
domega_in_dy = (2 * m.dot(domega_out_dy)) * m - domega_out_dy;
/* disabled for now - gives texture filtering problems */
#if 0
// Since there is some blur to this reflection, make the
// derivatives a bit bigger. In theory this varies with the
// roughness but the exact relationship is complex and
// requires more ops than are practical.
domega_in_dx *= 10;
domega_in_dy *= 10;
#endif
}
}
} else {
// CAUTION: the i and o variables are inverted relative to the paper
// eq. 39 - compute actual refractive direction
Vec3 R, dRdx, dRdy;
Vec3 T, dTdx, dTdy;
bool inside;
fresnel_dielectric(m_eta, m, omega_out, domega_out_dx, domega_out_dy,
R, dRdx, dRdy,
T, dTdx, dTdy,
inside);
if (!inside) {
omega_in = T;
domega_in_dx = dTdx;
domega_in_dy = dTdy;
// eq. 33
float cosThetaM2 = cosThetaM * cosThetaM;
float tanThetaM2 = tanThetaM * tanThetaM;
float cosThetaM4 = cosThetaM2 * cosThetaM2;
float D = expf(-tanThetaM2 / alpha2) / (float(M_PI) * alpha2 * cosThetaM4);
// eq. 24
float pm = D * cosThetaM;
// eval BRDF*cosNI
float cosNI = m_N.dot(omega_in);
// eq. 26, 27: now calculate G1(i,m) and G1(o,m)
float ao = 1 / (m_ab * sqrtf((1 - cosNO * cosNO) / (cosNO * cosNO)));
float ai = 1 / (m_ab * sqrtf((1 - cosNI * cosNI) / (cosNI * cosNI)));
float G1o = ao < 1.6f ? (3.535f * ao + 2.181f * ao * ao) / (1 + 2.276f * ao + 2.577f * ao * ao) : 1.0f;
float G1i = ai < 1.6f ? (3.535f * ai + 2.181f * ai * ai) / (1 + 2.276f * ai + 2.577f * ai * ai) : 1.0f;
float G = G1o * G1i;
// eq. 21
float cosHI = m.dot(omega_in);
float cosHO = m.dot(omega_out);
float Ht2 = m_eta * cosHI + cosHO;
Ht2 *= Ht2;
float out = (fabsf(cosHI * cosHO) * (m_eta * m_eta) * (G * D)) / (cosNO * Ht2);
// eq. 38 and eq. 17
pdf = pm * (m_eta * m_eta) * fabsf(cosHI) / Ht2;
eval.setValue(out, out, out);
/* disabled for now - gives texture filtering problems */
#if 0
// Since there is some blur to this refraction, make the
// derivatives a bit bigger. In theory this varies with the
// roughness but the exact relationship is complex and
// requires more ops than are practical.
domega_in_dx *= 10;
domega_in_dy *= 10;
#endif
}
}
}
return Refractive ? Labels::TRANSMIT : Labels::REFLECT;
}
};
ClosureParam bsdf_microfacet_ggx_params[] = {
CLOSURE_VECTOR_PARAM(MicrofacetGGXClosure<0>, m_N),
CLOSURE_FLOAT_PARAM (MicrofacetGGXClosure<0>, m_ag),
CLOSURE_STRING_KEYPARAM("label"),
CLOSURE_FINISH_PARAM(MicrofacetGGXClosure<0>) };
ClosureParam bsdf_microfacet_ggx_refraction_params[] = {
CLOSURE_VECTOR_PARAM(MicrofacetGGXClosure<1>, m_N),
CLOSURE_FLOAT_PARAM (MicrofacetGGXClosure<1>, m_ag),
CLOSURE_FLOAT_PARAM (MicrofacetGGXClosure<1>, m_eta),
CLOSURE_STRING_KEYPARAM("label"),
CLOSURE_FINISH_PARAM(MicrofacetGGXClosure<1>) };
ClosureParam bsdf_microfacet_beckmann_params[] = {
CLOSURE_VECTOR_PARAM(MicrofacetBeckmannClosure<0>, m_N),
CLOSURE_FLOAT_PARAM (MicrofacetBeckmannClosure<0>, m_ab),
CLOSURE_STRING_KEYPARAM("label"),
CLOSURE_FINISH_PARAM(MicrofacetBeckmannClosure<0>) };
ClosureParam bsdf_microfacet_beckmann_refraction_params[] = {
CLOSURE_VECTOR_PARAM(MicrofacetBeckmannClosure<1>, m_N),
CLOSURE_FLOAT_PARAM (MicrofacetBeckmannClosure<1>, m_ab),
CLOSURE_FLOAT_PARAM (MicrofacetBeckmannClosure<1>, m_eta),
CLOSURE_STRING_KEYPARAM("label"),
CLOSURE_FINISH_PARAM(MicrofacetBeckmannClosure<1>) };
CLOSURE_PREPARE(bsdf_microfacet_ggx_prepare, MicrofacetGGXClosure<0>)
CLOSURE_PREPARE(bsdf_microfacet_ggx_refraction_prepare, MicrofacetGGXClosure<1>)
CLOSURE_PREPARE(bsdf_microfacet_beckmann_prepare, MicrofacetBeckmannClosure<0>)
CLOSURE_PREPARE(bsdf_microfacet_beckmann_refraction_prepare, MicrofacetBeckmannClosure<1>)
CCL_NAMESPACE_END