/* * 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 #include #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 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 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