diff --git a/src/libbsdl/include/BSDL/MTX/bsdf_sheen_decl.h b/src/libbsdl/include/BSDL/MTX/bsdf_sheen_decl.h new file mode 100644 index 0000000000..7c08c17e34 --- /dev/null +++ b/src/libbsdl/include/BSDL/MTX/bsdf_sheen_decl.h @@ -0,0 +1,202 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + +#pragma once + +#include + +BSDL_ENTER_NAMESPACE + +namespace mtx { + +template struct SheenLobe : public Lobe { + using Base = Lobe; + + enum Mode { CONTY = 0, ZELTNER = 1 }; + + struct Data : public LayeredData { + Imath::V3f N; + Imath::C3f albedo; + float roughness; + int mode; + Stringhash label; + using lobe_type = SheenLobe; + }; + + template static typename LobeRegistry::Entry entry() + { + static_assert( + std::is_base_of::value); // Make no other assumptions + using R = LobeRegistry; + return { name(), + { R::param(&D::N), R::param(&D::albedo), + R::param(&D::roughness), R::param(&D::label, "label"), + R::param(&D::mode, "mode"), R::close() } }; + } + + template + BSDL_INLINE_METHOD SheenLobe(T*, const BsdfGlobals& globals, + const Data& data); + + static constexpr const char* name() { return "sheen_bsdf"; } + + BSDL_INLINE_METHOD Power albedo_impl() const { return Power(1 - Emiss, 1); } + BSDL_INLINE_METHOD Power filter_o(const Imath::V3f& wo) const + { + return Power(Emiss, 1); + } + + BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const; + +private: + BSDL_INLINE_METHOD bool use_zeltner() const + { + return sheen_mode == ZELTNER; + } + + Power tint; + float sheen_alpha; + float Emiss; + int sheen_mode; + bool is_backfacing; +}; + +// SHD_LEGACY tells the distribution to use the shadowing term from +// the original paper. Otherwise we use Dassault Systemes improvement. +template struct ContyKullaDist { + static constexpr float MIN_ROUGHNESS = 0.06f; + + BSDL_INLINE_METHOD ContyKullaDist(float rough) + : a(CLAMP(rough, MIN_ROUGHNESS, 1.0f)) + { + } + + BSDL_INLINE_METHOD float D(const Imath::V3f& Hr) const; + BSDL_INLINE_METHOD float get_lambda(float cosNO) const; + BSDL_INLINE_METHOD float G2(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD float roughness() const { return a; } + +private: + float a; +}; + +template struct SheenMicrofacet { + // describe how tabulation should be done + static constexpr int Nc = 16; + static constexpr int Nr = 16; + static constexpr int Nf = 1; + + static constexpr float get_cosine(int i) + { + return std::max(float(i) * (1.0f / (Nc - 1)), 1e-6f); + } + explicit BSDL_INLINE_METHOD SheenMicrofacet(float rough) : d(rough) {} + BSDL_INLINE_METHOD Sample eval(const Imath::V3f& wo, + const Imath::V3f& wi) const; + BSDL_INLINE_METHOD Sample sample(const Imath::V3f& wo, float randu, + float randv, float) const; + BSDL_INLINE_METHOD float roughness() const { return d.roughness(); } + +private: + Dist d; +}; + +template +struct ContyKullaSheenGen : public SheenMicrofacet> { + using SheenMicrofacet>::SheenMicrofacet; +}; + +struct ContyKullaSheen : public ContyKullaSheenGen { + using ContyKullaSheenGen::ContyKullaSheenGen; + explicit BSDL_INLINE_METHOD ContyKullaSheen(float, float rough, float) + : ContyKullaSheenGen(rough) + { + } + BSDL_INLINE_METHOD float albedo(float cosNO) const; + struct Energy { + float data[Nf * Nr * Nc]; + }; + static BSDL_INLINE_METHOD Energy& get_energy(); + + static constexpr const char* NS = "mtx"; + static const char* lut_header() { return "MTX/bsdf_contysheen_luts.h"; } + static const char* struct_name() { return "ContyKullaSheen"; } +}; + +struct ContyKullaSheenMTX : public ContyKullaSheenGen { + using ContyKullaSheenGen::ContyKullaSheenGen; + BSDL_INLINE_METHOD float albedo(float cosNO) const; +}; + +struct ZeltnerBurleySheen { +#if BAKE_BSDL_TABLES + // Use uniform sampling, more reliable + static constexpr bool LTC_SAMPLING = false; +#else + static constexpr bool LTC_SAMPLING = true; +#endif + // Skip albedo tables, use LTC coefficents. Disabling is useful for validation + static constexpr bool LTC_ALBEDO = true; + // This flattens the look a bit, so leaving it disabled for now + static constexpr bool FITTED_LTC = false; + + // LTC sampling is weak at low roughness, gains energy, so we clamp it. + static constexpr float MIN_ROUGHNESS = LTC_SAMPLING ? 0.02f : 0.0f; + + // describe how tabulation should be done + static constexpr int Nc = 16; + static constexpr int Nr = 16; + static constexpr int Nf = 1; + static constexpr int ltc_res = 32; + + explicit BSDL_INLINE_METHOD ZeltnerBurleySheen(float rough) + : roughness(CLAMP(rough, MIN_ROUGHNESS, 1.0f)) + { + } + // This constructor is just for baking albedo tables + explicit ZeltnerBurleySheen(float, float rough, float) : roughness(rough) {} + + static constexpr float get_cosine(int i) + { + return std::max(float(i) * (1.0f / (Nc - 1)), 1e-6f); + } + + BSDL_INLINE_METHOD Sample eval(Imath::V3f wo, Imath::V3f wi) const; + BSDL_INLINE_METHOD Sample eval(Imath::V3f wo, Imath::V3f wi, + Imath::V3f ltc) const; + BSDL_INLINE_METHOD Sample sample(Imath::V3f wo, float randu, float randv, + float randw) const; + BSDL_INLINE_METHOD float albedo(float cosNO) const; + + struct Energy { + float data[Nf * Nr * Nc]; + }; + struct Param { + Imath::V3f data[32][32]; + }; + + static BSDL_INLINE_METHOD Energy& get_energy(); + + typedef const Imath::V3f (*V32_array)[32]; + static BSDL_INLINE_METHOD V32_array param_ptr(); + + static constexpr const char* NS = "mtx"; + static const char* lut_header() { return "MTX/bsdf_zeltnersheen_luts.h"; } + static const char* struct_name() { return "ZeltnerBurleySheen"; } + + // Fetch the LTC coefficients by bilinearly interpolating entries in a 32x32 + // lookup table or using a fit. + BSDL_INLINE_METHOD Imath::V3f fetch_coeffs(float cosNO) const; + +private: + float roughness; +}; + +} // namespace mtx + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/MTX/bsdf_sheen_impl.h b/src/libbsdl/include/BSDL/MTX/bsdf_sheen_impl.h new file mode 100644 index 0000000000..b7b28be8bf --- /dev/null +++ b/src/libbsdl/include/BSDL/MTX/bsdf_sheen_impl.h @@ -0,0 +1,357 @@ +// Copyright Contributors to the Open Shading Language project. +// SPDX-License-Identifier: BSD-3-Clause +// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage + +#pragma once + +#include + +#include +#ifndef BAKE_BSDL_TABLES +# include +# include +#endif + +BSDL_ENTER_NAMESPACE + +namespace mtx { + +template +template +BSDL_INLINE_METHOD +SheenLobe::SheenLobe(T* lobe, const BsdfGlobals& globals, + const Data& data) + : Base(lobe, globals.visible_normal(data.N), globals.wo, + globals.regularize_roughness(CLAMP(data.roughness, 0.0f, 1.0f)), + globals.lambda_0, false) + , tint(globals.wave(data.albedo)) + , Emiss(1.0f) + , sheen_mode(data.mode) + , is_backfacing(globals.backfacing) +{ + Base::sample_filter = globals.get_sample_filter(Base::frame.Z, true); + sheen_alpha + = use_zeltner() + ? MAX(ZeltnerBurleySheen::MIN_ROUGHNESS, sqrtf(Base::roughness())) + : MAX(ContyKullaDist::MIN_ROUGHNESS, Base::roughness()); + + const float cosNO = CLAMP(Base::frame.Z.dot(globals.wo), 0.0f, 1.0f); + if (is_backfacing) + Emiss = 1; + else if (use_zeltner()) { + ZeltnerBurleySheen sheen(sheen_alpha); + Emiss = 1 - std::min(sheen.albedo(cosNO) * tint.max(), 1.0f); + } else { + ContyKullaSheenMTX sheen(sheen_alpha); + Emiss = 1 - std::min(sheen.albedo(cosNO) * tint.max(), 1.0f); + } +} + +template +BSDL_INLINE_METHOD Sample +SheenLobe::eval_impl(const Imath::V3f& wo, + const Imath::V3f& wi) const +{ + const float cosNO = wo.z; + const float cosNI = wi.z; + const bool is_reflection = cosNI > 0 && cosNO >= 0; + const bool do_self = is_reflection && !is_backfacing; + if (!do_self) + return {}; + + Sample s = use_zeltner() ? ZeltnerBurleySheen(sheen_alpha).eval(wo, wi) + : ContyKullaSheenMTX(sheen_alpha).eval(wo, wi); + s.weight *= tint; + s.roughness = Base::roughness(); + return s; +} + +template +BSDL_INLINE_METHOD Sample +SheenLobe::sample_impl(const Imath::V3f& wo, + const Imath::V3f& rnd) const +{ + if (is_backfacing) + return {}; + + Sample s + = use_zeltner() + ? ZeltnerBurleySheen(sheen_alpha).sample(wo, rnd.x, rnd.y, rnd.z) + : ContyKullaSheenMTX(sheen_alpha).sample(wo, rnd.x, rnd.y, rnd.z); + s.weight *= tint; + s.roughness = Base::roughness(); + return s; +} + +// Reference: +// Conty Estevez and Kulla, "Production Friendly Microfacet Sheen BRDF", 2017. +// https://github.com/aconty/aconty/blob/main/pdf/s2017_pbs_imageworks_sheen.pdf + +template +BSDL_INLINE_METHOD float +ContyKullaDist::D(const Imath::V3f& Hr) const +{ + // Eq. (2): D(m) = ((2 + 1/r) * sin(theta)^(1/r)) / (2*pi) + float cos_theta = CLAMP(Hr.z, 0.0f, 1.0f); + float sin_theta = sqrtf(1.0f - SQR(cos_theta)); + return BSDLConfig::Fast::powf(sin_theta, 1 / a) * (2 + 1 / a) * 0.5f + * ONEOVERPI; +} + +template +BSDL_INLINE_METHOD float +ContyKullaDist::get_lambda(float cosNO) const +{ + float rt = SQR(1 - a); + + // These params come from gnuplot fitting tool. Roughness = 1 and + // roughness = 0. We interpolate in between with (1 - roughness)^2 + // to get the best match. + // Eq. (3): fitted Lambda(theta) model. + const float a = LERP(rt, 21.5473f, 25.3245f); + const float b = LERP(rt, 3.82987f, 3.32435f); + const float c = LERP(rt, 0.19823f, 0.16801f); + const float d = LERP(rt, -1.97760f, -1.27393f); + const float e = LERP(rt, -4.32054f, -4.85967f); + // The curve is anti-symmetrical aroung 0.5 + const float x = cosNO > 0.5f ? 1 - cosNO : cosNO; + const float pivot = a / (1 + b * BSDLConfig::Fast::powf(0.5f, c)) + d * 0.5f + + e; + + float p = a / (1 + b * BSDLConfig::Fast::powf(x, c)) + d * x + e; + if (cosNO > 0.5f) // Mirror around 0.5f + p = 2 * pivot - p; + // That curve fits lambda in log scale, now exponentiate + return BSDLConfig::Fast::expf(p); +} + +template +BSDL_INLINE_METHOD float +ContyKullaDist::G2(const Imath::V3f& wo, const Imath::V3f& wi) const +{ + assert(wi.z > 0); + assert(wo.z > 0); + float cosNI = std::min(1.0f, wi.z); + float cosNO = std::min(1.0f, wo.z); + if constexpr (SHD_LEGACY) { + // Original shadowing term from the paper + float lambdaI = get_lambda(cosNI); + float lambdaO = get_lambda(cosNO); + // Eq. (4): lambda' light-side softening near the terminator. + // This is intentionally non-reciprocal. + lambdaI = BSDLConfig::Fast::powf(lambdaI, + 1.0f + 2 * SQR(SQR(SQR((1 - cosNI))))); + // Correlated masking-shadowing term: G2 = 1 / (1 + Lambda(wo) + Lambda(wi)). + return 1 / (1 + lambdaI + lambdaO); + } else + // The simpler shadowing/masking visibility term below is used. It also has less + // darkening at low roughness on grazing angles. + // https://dassaultsystemes-technology.github.io/EnterprisePBRShadingModel/spec-2022x.md.html#components/sheen + return (cosNI * cosNO) / (cosNI + cosNO - cosNI * cosNO); +} + +template +BSDL_INLINE_METHOD Sample +SheenMicrofacet::eval(const Imath::V3f& wo, const Imath::V3f& wi) const +{ + assert(wo.z >= 0); + assert(wi.z >= 0); + float cosNO = wo.z; + float cosNI = wi.z; + if (cosNI <= 1e-5f || cosNO <= 1e-5f) + return {}; + + const float D = d.D((wo + wi).normalized()); + if (D < 1e-6) + return {}; + const float G2 = d.G2(wo, wi); + // Eq. (1): microfacet BRDF form. For sheen, Fresnel F = 1. + return { wi, Power(D * G2 * 0.5f * PI / cosNO, 1), 0.5f * ONEOVERPI, 0 }; +} + +template +BSDL_INLINE_METHOD Sample +SheenMicrofacet::sample(const Imath::V3f& wo, float randu, float randv, + float randw) const +{ + Imath::V3f wi = sample_uniform_hemisphere(randu, randv); + return eval(wo, wi); +} + +BSDL_INLINE_METHOD float +ContyKullaSheen::albedo(float cosNO) const +{ + return 1 + - TabulatedEnergyCurve(roughness(), 0) + .Emiss_eval(cosNO); +} + +BSDL_INLINE_METHOD float +ContyKullaSheenMTX::albedo(float cosNO) const +{ + // Rational fit from the Material X project + // Ref: https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl + const Imath::V2f r + = Imath::V2f(13.67300f, 1.0f) + + Imath::V2f(-68.78018f, 61.57746f) * cosNO + + Imath::V2f(799.08825f, 442.78211f) * roughness() + + Imath::V2f(-905.00061f, 2597.49308f) * cosNO * roughness() + + Imath::V2f(60.28956f, 121.81241f) * cosNO * cosNO + + Imath::V2f(1086.96473f, 3045.55075f) * roughness() * roughness(); + return CLAMP(r.x / r.y, 0.0f, 1.0f); +} + +// Reference: +// Zeltner, Burley, Chiang, "Practical Multiple-Scattering Sheen Using +// Linearly Transformed Cosines", SIGGRAPH 2022. +// https://tizianzeltner.com/projects/Zeltner2022Practical/ + +BSDL_INLINE_METHOD Sample +ZeltnerBurleySheen::eval(Imath::V3f wo, Imath::V3f wi) const +{ + if (wo.z < 0 || wi.z <= 0) + return {}; + + return eval(wo, wi, fetch_coeffs(wo.z)); +} + +BSDL_INLINE_METHOD Sample +ZeltnerBurleySheen::eval(Imath::V3f wo, Imath::V3f wi, Imath::V3f ltc) const +{ + // Eq. (1): evaluate the sheen LTC with per-view coefficients + // (A, B, R) = (a_inv, b_inv, r_coeff). + const float a_inv = ltc.x; + const float b_inv = ltc.y; + const float r_coeff = ltc.z; + Imath::V3f wi_orig = { a_inv * wi.x + b_inv * wi.z, a_inv * wi.y, wi.z }; + + // The (inverse) transform matrix `M^{-1}` is given by: + // [[aInv 0 bInv] + // M^{-1} = [0 aInv 0 ] + // [0 0 1 ]] + // with `aInv = ltcCoeffs[0]`, `bInv = ltcCoeffs[1]` fetched from the + // table. The transformed direction `wiOriginal` is therefore: + // [[aInv * wi.x + bInv * wi.z] + // wiOriginal = M^{-1} * wi = [aInv * wi.y ] + // [wi.z ]] + // which is subsequently normalized. The determinant of the matrix is + // |M^{-1}| = aInv * aInv + // which is used to compute the Jacobian determinant of the complete + // mapping including the normalization. + // See the original paper [Heitz et al. 2016] for details about the LTC + // itself. + const float jacobian = pown<2>(a_inv / wi_orig.length2()); + + if (LTC_SAMPLING) { + float pdf = jacobian * std::max(wi_orig.z, 0.0f) * ONEOVERPI; + if (pdf > FLOAT_MIN) + return { wi, Power(r_coeff, 1), pdf, roughness }; + else + return {}; + } else { + float pdf = 0.5f * ONEOVERPI; + // Eq. (1): f_sheen = (2 * R / pi) * |M^{-1}| * max(0, z) / ||M^{-1} wi||^4. + // NOTE: sheen closure has no Fresnel/masking terms. + return { wi, + Power(2 * r_coeff * jacobian * std::max(wi_orig.z, 0.0f), 1), + pdf, roughness }; + } +} + +BSDL_INLINE_METHOD Sample +ZeltnerBurleySheen::sample(Imath::V3f wo, float randu, float randv, + float randw) const +{ + if (wo.z < 0) + return {}; + if (LTC_SAMPLING) { + // Sample the cosine base distribution, then transform by M. + const Imath::V3f ltc = fetch_coeffs(wo.z); + const float a_inv = ltc.x; + const float b_inv = ltc.y; + Imath::V3f wi_orig = sample_cos_hemisphere(randu, randv); + const Imath::V3f wi = { wi_orig.x - wi_orig.z * b_inv, wi_orig.y, + a_inv * wi_orig.z }; + + return eval(wo, wi.normalized(), ltc); + } else { + Imath::V3f wi = sample_uniform_hemisphere(randu, randv); + return eval(wo, wi); + } +} + +// The following functions and LTC coefficient tables are translated from: +// https://github.com/tizian/ltc-sheen +// and the Zeltner et al. 2022 sheen paper. + +// Fetch the LTC coefficients by bilinearly interpolating entries in a 32x32 +// lookup table. */ +BSDL_INLINE_METHOD Imath::V3f +ZeltnerBurleySheen::fetch_coeffs(float cosNO) const +{ + if (FITTED_LTC) { + // To avoid look-up tables, we use a fit of the LTC coefficients derived by Stephen Hill + // for the implementation in MaterialX: + // https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl + const float x = CLAMP(cosNO, 0.0f, 1.0f); + const float y = std::max(roughness, 1e-3f); + // Fitted approximation of the paper's tabulated LTC coefficients (A, B, R). + const float A = ((2.58126f * x + 0.813703f * y) * y) + / (1.0f + 0.310327f * x * x + 2.60994f * x * y); + const float B = sqrtf(1.0f - x) * (y - 1.0f) * y * y * y + / (0.0000254053f + 1.71228f * x - 1.71506f * x * y + + 1.34174f * y * y); + const float invs = (0.0379424f + y * (1.32227f + y)) + / (y * (0.0206607f + 1.58491f * y)); + const float m = y + * (-0.193854f + + y * (-1.14885 + y * (1.7932f - 0.95943f * y * y))) + / (0.046391f + y); + const float o = y * (0.000654023f + (-0.0207818f + 0.119681f * y) * y) + / (1.26264f + y * (-1.92021f + y)); + float q = (x - m) * invs; + const float inv_sqrt2pi = 0.39894228040143f; + float R = BSDLConfig::Fast::expf(-0.5f * q * q) * invs * inv_sqrt2pi + + o; + assert(isfinite(A)); + assert(isfinite(B)); + assert(isfinite(R)); + return { A, B, R }; + } else { + // Bilinearly interpolate (A, B, R) from the 32x32 LUT over + // (roughness, cos(theta_o)). + float row = CLAMP(roughness, 0.0f, ALMOSTONE) * (ltc_res - 1); + float col = CLAMP(cosNO, 0.0f, ALMOSTONE) * (ltc_res - 1); + float r = std::floor(row); + float c = std::floor(col); + float rf = row - r; + float cf = col - c; + int ri = (int)r; + int ci = (int)c; + + const V32_array param = param_ptr(); + // Bilinear interpolation + Imath::V3f coeffs; + const Imath::V3f v1 = param[ri][ci]; + const Imath::V3f v2 = param[ri][ci + 1]; + const Imath::V3f v3 = param[ri + 1][ci]; + const Imath::V3f v4 = param[ri + 1][ci + 1]; + coeffs = LERP(rf, LERP(cf, v1, v2), LERP(cf, v3, v4)); + return coeffs; + } +} + +BSDL_INLINE_METHOD float +ZeltnerBurleySheen::albedo(float cosNO) const +{ + if constexpr (LTC_ALBEDO) + return fetch_coeffs(cosNO).z; + else + return 1 + - TabulatedEnergyCurve(roughness, 0) + .Emiss_eval(cosNO); +} + +} // namespace mtx + +BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_param.h b/src/libbsdl/include/BSDL/MTX/bsdf_zeltnersheen_param.h similarity index 99% rename from src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_param.h rename to src/libbsdl/include/BSDL/MTX/bsdf_zeltnersheen_param.h index 4f09596ffd..863513d02e 100644 --- a/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_param.h +++ b/src/libbsdl/include/BSDL/MTX/bsdf_zeltnersheen_param.h @@ -5,14 +5,14 @@ #pragma once -#include +#include BSDL_ENTER_NAMESPACE -namespace spi { +namespace mtx { -BSDL_INLINE_METHOD SheenLTC::V32_array -SheenLTC::param_ptr() +BSDL_INLINE_METHOD ZeltnerBurleySheen::V32_array +ZeltnerBurleySheen::param_ptr() { static Param param = { { // clang-format off @@ -277,6 +277,6 @@ SheenLTC::param_ptr() return param.data; } -} // namespace spi +} // namespace mtx BSDL_LEAVE_NAMESPACE diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_decl.h index 1be197672b..964ac92786 100644 --- a/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_decl.h +++ b/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_decl.h @@ -5,66 +5,14 @@ #pragma once +#include #include BSDL_ENTER_NAMESPACE namespace spi { -struct CharlieDist { - static constexpr float MIN_ROUGHNESS = 0.06f; - - static BSDL_INLINE_METHOD float common_roughness(float alpha); - BSDL_INLINE_METHOD CharlieDist(float rough) - : a(CLAMP(rough, MIN_ROUGHNESS, 1.0f)) - { - } - - BSDL_INLINE_METHOD float D(const Imath::V3f& Hr) const; - BSDL_INLINE_METHOD float get_lambda(float cosNv) const; - BSDL_INLINE_METHOD float G2(const Imath::V3f& wo, - const Imath::V3f& wi) const; - BSDL_INLINE_METHOD float roughness() const { return a; } - -private: - float a; -}; - -template struct SheenMicrofacet { - // describe how tabulation should be done - static constexpr int Nc = 16; - static constexpr int Nr = 16; - static constexpr int Nf = 1; - - static constexpr float get_cosine(int i) - { - return std::max(float(i) * (1.0f / (Nc - 1)), 1e-6f); - } - explicit BSDL_INLINE_METHOD SheenMicrofacet(float rough) : d(rough) {} - BSDL_INLINE_METHOD Sample eval(const Imath::V3f& wo, - const Imath::V3f& wi) const; - BSDL_INLINE_METHOD Sample sample(const Imath::V3f& wo, float randu, - float randv, float) const; - BSDL_INLINE_METHOD float roughness() const { return d.roughness(); } - -private: - Dist d; -}; - -struct CharlieSheen : public SheenMicrofacet { - explicit BSDL_INLINE_METHOD CharlieSheen(float, float rough, float) - : SheenMicrofacet(rough) - { - } - struct Energy { - float data[Nf * Nr * Nc]; - }; - static BSDL_INLINE_METHOD Energy& get_energy(); - - static constexpr const char* NS = "spi"; - static const char* lut_header() { return "SPI/bsdf_backscatter_luts.h"; } - static const char* struct_name() { return "CharlieSheen"; } -}; +using mtx::ContyKullaSheen; template struct CharlieLobe : public Lobe { using Base = Lobe; @@ -91,7 +39,7 @@ template struct CharlieLobe : public Lobe { const Data& data); static const char* name() { return "sheen"; } - BSDL_INLINE_METHOD Power albedo_impl() const { return Power(1 - Eo, 1); } + BSDL_INLINE_METHOD Power albedo_impl() const { return Power(1 - Emiss, 1); } BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, const Imath::V3f& wi) const; @@ -99,10 +47,20 @@ template struct CharlieLobe : public Lobe { const Imath::V3f& sample) const; private: - CharlieSheen sheen; + BSDL_INLINE_METHOD float common_roughness(float alpha) + { + // Using the PDF we would have if we sampled the microfacet, one of + // the 1/2 comes from the cosine avg of 1/(4 cosMO). + // + // (2 + 1 / alpha) / 4 = 1 / (2 pi roughness^4) + // 1 / (pi (1 + 4 / alpha)) = roughness^4 + return sqrtf(sqrtf(ONEOVERPI / (1 + 0.5f / alpha))); + } + + ContyKullaSheen sheen; Power tint; - float Eo; - bool back; + float Emiss; + bool is_backfacing; }; } // namespace spi diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_impl.h index 6097aa675a..0ad20b6b73 100644 --- a/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_impl.h +++ b/src/libbsdl/include/BSDL/SPI/bsdf_backscatter_impl.h @@ -5,127 +5,36 @@ #pragma once +#include #include #include #include -#ifndef BAKE_BSDL_TABLES -# include -#endif BSDL_ENTER_NAMESPACE namespace spi { -BSDL_INLINE_METHOD float -CharlieDist::common_roughness(float alpha) -{ - // Using the PDF we would have if we sampled the microfacet, one of - // the 1/2 comes from the cosine avg of 1/(4 cosMO). - // - // (2 + 1 / alpha) / 4 = 1 / (2 pi roughness^4) - // 1 / (pi (1 + 4 / alpha)) = roughness^4 - return sqrtf(sqrtf(ONEOVERPI / (1 + 0.5f / alpha))); -} - -BSDL_INLINE_METHOD float -CharlieDist::D(const Imath::V3f& Hr) const -{ - float cos_theta = Hr.z; - float sin_theta = sqrtf(1.0f - SQR(cos_theta)); - return BSDLConfig::Fast::powf(sin_theta, 1 / a) * (2 + 1 / a) * 0.5f - * ONEOVERPI; -} - -BSDL_INLINE_METHOD float -CharlieDist::get_lambda(float cosNv) const -{ - float rt = SQR(1 - a); - - // These params come from gnuplot fitting tool. Roughness = 1 and - // roughness = 0. We interpolate in between with (1 - roughness)^2 - // to get the best match. - const float a = LERP(rt, 21.5473f, 25.3245f); - const float b = LERP(rt, 3.82987f, 3.32435f); - const float c = LERP(rt, 0.19823f, 0.16801f); - const float d = LERP(rt, -1.97760f, -1.27393f); - const float e = LERP(rt, -4.32054f, -4.85967f); - // The curve is anti-symmetrical aroung 0.5 - const float x = cosNv > 0.5f ? 1 - cosNv : cosNv; - const float pivot = a / (1 + b * BSDLConfig::Fast::powf(0.5f, c)) + d * 0.5f - + e; - - float p = a / (1 + b * BSDLConfig::Fast::powf(x, c)) + d * x + e; - if (cosNv > 0.5f) // Mirror around 0.5f - p = 2 * pivot - p; - // That curve fits lambda in log scale, now exponentiate - return BSDLConfig::Fast::expf(p); -} - -BSDL_INLINE_METHOD float -CharlieDist::G2(const Imath::V3f& wo, const Imath::V3f& wi) const -{ - assert(wi.z > 0); - assert(wo.z > 0); - float cosNI = std::min(1.0f, wi.z); - float cosNO = std::min(1.0f, wo.z); - float Li = get_lambda(cosNI); - float Lo = get_lambda(cosNO); - // This makes the BSDF non-reciprocal. Cheat to hide the terminator - Li = BSDLConfig::Fast::powf(Li, 1.0f + 2 * SQR(SQR(SQR((1 - cosNI))))); - return 1 / (1 + Li + Lo); -} - -template -BSDL_INLINE_METHOD Sample -SheenMicrofacet::eval(const Imath::V3f& wo, const Imath::V3f& wi) const -{ - assert(wo.z >= 0); - assert(wi.z >= 0); - float cosNO = wo.z; - float cosNI = wi.z; - if (cosNI <= 1e-5f || cosNO <= 1e-5f) - return {}; - - const float D = d.D((wo + wi).normalized()); - if (D < 1e-6) - return {}; - const float G2 = d.G2(wo, wi); - return { wi, Power(D * G2 * 0.5f * PI / cosNO, 1), 0.5f * ONEOVERPI, 0 }; -} - -template -BSDL_INLINE_METHOD Sample -SheenMicrofacet::sample(const Imath::V3f& wo, float randu, float randv, - float randw) const -{ - Imath::V3f wi = sample_uniform_hemisphere(randu, randv); - return eval(wo, wi); -} - template template BSDL_INLINE_METHOD CharlieLobe::CharlieLobe(T* lobe, const BsdfGlobals& globals, const CharlieLobe::Data& data) : Base(lobe, globals.visible_normal(data.N), - CharlieDist::common_roughness( - globals.regularize_roughness(data.roughness)), + common_roughness(globals.regularize_roughness(data.roughness)), globals.lambda_0, false) , sheen(0, CLAMP(globals.regularize_roughness(data.roughness), - CharlieDist::MIN_ROUGHNESS, 1.0f), + mtx::ContyKullaDist::MIN_ROUGHNESS, 1.0f), 0) , tint(globals.wave(data.tint)) - , back(data.doublesided ? false : globals.backfacing) + , is_backfacing(data.doublesided ? false : globals.backfacing) { Base::sample_filter = globals.get_sample_filter(Base::frame.Z, true); - TabulatedEnergyCurve curve(sheen.roughness(), 0); + const float cosNO = CLAMP(Base::frame.Z.dot(globals.wo), 0.0f, 1.0f); // Get energy compensation taking tint into account - Eo = back ? 1 - : 1 - - MIN((1 - curve.Emiss_eval(Base::frame.Z.dot(globals.wo))) - * tint.max(), - 1.0f); + Emiss = is_backfacing + ? 1 + : 1 - std::min(sheen.albedo(cosNO) * tint.max(), 1.0f); } template @@ -133,11 +42,11 @@ BSDL_INLINE_METHOD Sample CharlieLobe::eval_impl(const Imath::V3f& wo, const Imath::V3f& wi) const { - const float cosNO = wo.z; - const float cosNI = wi.z; - const bool isrefl = cosNI > 0 && cosNO >= 0; - const bool doself = isrefl && !back; - if (!doself) + const float cosNO = wo.z; + const float cosNI = wi.z; + const bool is_reflection = cosNI > 0 && cosNO >= 0; + const bool do_self = is_reflection && !is_backfacing; + if (!do_self) return {}; Sample s = sheen.eval(wo, wi); @@ -151,8 +60,8 @@ BSDL_INLINE_METHOD Sample CharlieLobe::sample_impl(const Imath::V3f& wo, const Imath::V3f& sample) const { - const bool doself = !back; - if (!doself) + const bool do_self = !is_backfacing; + if (!do_self) return {}; Sample ss = sheen.sample(wo, sample.x, sample.y, sample.z); diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_decl.h b/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_decl.h index 651e367dd8..427c890301 100644 --- a/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_decl.h +++ b/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_decl.h @@ -5,6 +5,7 @@ #pragma once +#include #include #include @@ -12,70 +13,7 @@ BSDL_ENTER_NAMESPACE namespace spi { -struct SheenLTC { - // describe how tabulation should be done - static constexpr int Nc = 16; - static constexpr int Nr = 16; - static constexpr int Nf = 1; - static constexpr int ltcRes = 32; - - explicit BSDL_INLINE_METHOD SheenLTC(float rough) - : roughness(CLAMP(rough, 0.0f, 1.0f)) - { - } - // This constructor is just for baking albedo tables - explicit SheenLTC(float, float rough, float) : roughness(rough) {} - - static constexpr const char* name() { return "sheen_ltc"; } - - static constexpr float get_cosine(int i) - { - return std::max(float(i) * (1.0f / (Nc - 1)), 1e-6f); - } - - BSDL_INLINE_METHOD Sample eval(Imath::V3f wo, Imath::V3f wi) const; - BSDL_INLINE_METHOD Sample sample(Imath::V3f wo, float randu, float randv, - float randw) const; - - struct Energy { - float data[Nf * Nr * Nc]; - }; - struct Param { - Imath::V3f data[32][32]; - }; - - static BSDL_INLINE_METHOD Energy& get_energy(); - - typedef const Imath::V3f (*V32_array)[32]; - static BSDL_INLINE_METHOD V32_array param_ptr(); - - static constexpr const char* NS = "spi"; - static const char* lut_header() { return "SPI/bsdf_sheenltc_luts.h"; } - static const char* struct_name() { return "SheenLTC"; } - - BSDL_INLINE_METHOD float calculate_phi(const Imath::V3f& v) const; - BSDL_INLINE_METHOD bool same_hemisphere(const Imath::V3f& wo, - const Imath::V3f& wi) const; - - BSDL_INLINE_METHOD float get_roughness() const { return roughness; } - - BSDL_INLINE_METHOD void albedo_range(float& min_albedo, - float& max_albedo) const; - BSDL_INLINE_METHOD void compute_scale(float& scale) const; - - // Fetch the LTC coefficients by bilinearly interpolating entries in a 32x32 - // lookup table. - BSDL_INLINE_METHOD Imath::V3f fetchCoeffs(const Imath::V3f& wo) const; - // Evaluate the LTC distribution in its local coordinate system. - BSDL_INLINE_METHOD float evalLTC(const Imath::V3f& wi, - const Imath::V3f& ltcCoeffs) const; - // Sample from the LTC distribution in its local coordinate system. - BSDL_INLINE_METHOD Imath::V3f sampleLTC(const Imath::V3f& ltcCoeffs, - float randu, float randv) const; - -private: - float roughness; -}; +using mtx::ZeltnerBurleySheen; template struct SheenLTCLobe : public Lobe { using Base = Lobe; @@ -103,10 +41,10 @@ template struct SheenLTCLobe : public Lobe { static const char* name() { return "sheen_ltc"; } - BSDL_INLINE_METHOD Power albedo_impl() const { return Power(1 - Eo, 1); } + BSDL_INLINE_METHOD Power albedo_impl() const { return Power(1 - Emiss, 1); } BSDL_INLINE_METHOD Power filter_o(const Imath::V3f& wo) const { - return Power(Eo, 1); + return Power(Emiss, 1); } BSDL_INLINE_METHOD Sample eval_impl(const Imath::V3f& wo, @@ -115,10 +53,10 @@ template struct SheenLTCLobe : public Lobe { const Imath::V3f& sample) const; private: - SheenLTC sheenLTC; + ZeltnerBurleySheen sheen; Power tint; - float Eo; - bool back; + float Emiss; + bool is_backfacing; }; } // namespace spi diff --git a/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_impl.h b/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_impl.h index 0f3ec5ab7c..dc13274431 100644 --- a/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_impl.h +++ b/src/libbsdl/include/BSDL/SPI/bsdf_sheenltc_impl.h @@ -5,206 +5,30 @@ #pragma once +#include #include -#include -#ifndef BAKE_BSDL_TABLES -# include -#endif BSDL_ENTER_NAMESPACE namespace spi { -BSDL_INLINE_METHOD Sample -SheenLTC::eval(Imath::V3f wo, Imath::V3f wi) const -{ - assert(wo.z >= 0); - assert(wi.z >= 0); - - // Rotate coordinate frame to align with incident direction wo. - float phiStd = calculate_phi(wo); - Imath::V3f wiStd = rotate(wi, { 0, 0, 1 }, -phiStd); - - // Evaluate the LTC distribution in aligned coordinates. - Imath::V3f ltcCoeffs = fetchCoeffs(wo); - float pdf = evalLTC(wiStd, ltcCoeffs); - float R = 1.333814f * ltcCoeffs.z; // reflectance - Power col = Power(R, 1); - - return { wi, col, pdf, roughness }; -} - -BSDL_INLINE_METHOD Sample -SheenLTC::sample(Imath::V3f wo, float randu, float randv, float randw) const -{ - // Sample from the LTC distribution in aligned coordinates. - Imath::V3f wiStd = sampleLTC(fetchCoeffs(wo), randu, randv); - - // Rotate coordinate frame based on incident direction wo. - float phiStd = calculate_phi(wo); - Imath::V3f wi = rotate(wiStd, { 0, 0, 1 }, +phiStd); - - if (!same_hemisphere(wo, wi)) - return {}; - - return eval(wo, wi); -} - -BSDL_INLINE_METHOD float -SheenLTC::calculate_phi(const Imath::V3f& v) const -{ - float p = BSDLConfig::Fast::atan2f(v.y, v.x); - if (p < 0) { - p += 2 * PI; - } - return p; -} - -BSDL_INLINE_METHOD bool -SheenLTC::same_hemisphere(const Imath::V3f& wo, const Imath::V3f& wi) const -{ - return wo.z * wi.z > 0; -} - -BSDL_INLINE_METHOD void -SheenLTC::albedo_range(float& min_albedo, float& max_albedo) const -{ - const V32_array param = param_ptr(); - min_albedo = max_albedo = 0; - for (int i = 0; i < ltcRes; ++i) - for (int j = 0; j < ltcRes; ++j) { - Imath::V3f v = param[i][j]; - float R = v.z; // reflectance - if (i == 0 && j == 0) { - min_albedo = max_albedo = R; - } else { - min_albedo = std::min(R, min_albedo); - max_albedo = std::max(R, max_albedo); - } - } -} - -BSDL_INLINE_METHOD void -SheenLTC::compute_scale(float& scale) const -{ - float min_albedo = 0, max_albedo = 0; - albedo_range(min_albedo, max_albedo); - - if (max_albedo == 0) { - scale = 0; - } else { - scale = 1.0f / max_albedo; - } -} - -// The following functions, and the data arrays ltcParamTableVolume and ltcParamTableApprox, -// are translated from the code repository https://github.com/tizian/ltc-sheen, and the -// paper "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines", by Tizian -// Zeltner, Brent Burley, and Matt Jen-Yuan Chiang. - -// Fetch the LTC coefficients by bilinearly interpolating entries in a 32x32 -// lookup table. */ -BSDL_INLINE_METHOD Imath::V3f -SheenLTC::fetchCoeffs(const Imath::V3f& wo) const -{ - // Compute table indices and interpolation factors. - float row = CLAMP(roughness, 0.0f, ALMOSTONE) * (ltcRes - 1); - float col = CLAMP(wo.z, 0.0f, ALMOSTONE) * (ltcRes - 1); - float r = std::floor(row); - float c = std::floor(col); - float rf = row - r; - float cf = col - c; - int ri = (int)r; - int ci = (int)c; - - const V32_array param = param_ptr(); - // Bilinear interpolation - Imath::V3f coeffs; - const Imath::V3f v1 = param[ri][ci]; - const Imath::V3f v2 = param[ri][ci + 1]; - const Imath::V3f v3 = param[ri + 1][ci]; - const Imath::V3f v4 = param[ri + 1][ci + 1]; - coeffs = LERP(rf, LERP(cf, v1, v2), LERP(cf, v3, v4)); - return coeffs; -} - -// Evaluate the LTC distribution in its local coordinate system. -BSDL_INLINE_METHOD float -SheenLTC::evalLTC(const Imath::V3f& wi, const Imath::V3f& ltcCoeffs) const -{ - // The (inverse) transform matrix `M^{-1}` is given by: - // [[aInv 0 bInv] - // M^{-1} = [0 aInv 0 ] - // [0 0 1 ]] - // with `aInv = ltcCoeffs[0]`, `bInv = ltcCoeffs[1]` fetched from the - // table. The transformed direction `wiOriginal` is therefore: - // [[aInv * wi.x + bInv * wi.z] - // wiOriginal = M^{-1} * wi = [aInv * wi.y ] - // [wi.z ]] - // which is subsequently normalized. The determinant of the matrix is - // |M^{-1}| = aInv * aInv - // which is used to compute the Jacobian determinant of the complete - // mapping including the normalization. - // See the original paper [Heitz et al. 2016] for details about the LTC - // itself. - float aInv = ltcCoeffs.x, bInv = ltcCoeffs.y; - Imath::V3f wiOriginal = { aInv * wi.x + bInv * wi.z, aInv * wi.y, wi.z }; - const float length = wiOriginal.length(); - wiOriginal *= 1.0f / length; - float det = aInv * aInv; - float jacobian = det / (length * length * length); - - return wiOriginal.z * ONEOVERPI * jacobian; -} - -// Sample from the LTC distribution in its local coordinate system. -BSDL_INLINE_METHOD Imath::V3f -SheenLTC::sampleLTC(const Imath::V3f& ltcCoeffs, float randu, float randv) const -{ - // The (inverse) transform matrix `M^{-1}` is given by: - // [[aInv 0 bInv] - // M^{-1} = [0 aInv 0 ] - // [0 0 1 ]] - // with `aInv = ltcCoeffs[0]`, `bInv = ltcCoeffs[1]` fetched from the - // table. The non-inverted matrix `M` is therefore: - // [[1/aInv 0 -bInv/aInv] - // M = [0 1/aInv 0 ] - // [0 0 1 ]] - // and the transformed direction wi is: - // [[wiOriginal.x/aInv - wiOriginal.z*bInv/aInv] - // wi = M * wiOriginal = [wiOriginal.y/aInv ] - // [wiOriginal.z ]] - // which is subsequently normalized. - // See the original paper [Heitz et al. 2016] for details about the LTC - // itself. - Imath::V3f wiOriginal = sample_uniform_hemisphere(randu, randv); - - float aInv = ltcCoeffs.x, bInv = ltcCoeffs.y; - Imath::V3f wi = { wiOriginal.x / aInv - wiOriginal.z * bInv / aInv, - wiOriginal.y / aInv, wiOriginal.z }; - return wi.normalized(); -} - template template BSDL_INLINE_METHOD SheenLTCLobe::SheenLTCLobe(T* lobe, const BsdfGlobals& globals, const Data& data) - : Base(lobe, globals.visible_normal(data.N), data.roughness, + : Base(lobe, globals.visible_normal(data.N), globals.wo, data.roughness, globals.lambda_0, false) - , sheenLTC(CLAMP(globals.regularize_roughness(data.roughness), 0.0f, 1.0f)) - , tint(globals.wave(data.tint)) - , back(data.doublesided ? false : globals.backfacing) + , sheen(CLAMP(globals.regularize_roughness(data.roughness), 0.0f, 1.0f)) + , tint(globals.wave(data.tint) * 1.333814f) // Legacy scaling + , is_backfacing(data.doublesided ? false : globals.backfacing) { Base::sample_filter = globals.get_sample_filter(Base::frame.Z, true); - TabulatedEnergyCurve curve(sheenLTC.get_roughness(), 0); + const float cosNO = CLAMP(Base::frame.Z.dot(globals.wo), 0.0f, 1.0f); // Get energy compensation taking tint into account - Eo = back ? 1 - : 1 - - std::min( - (1 - curve.Emiss_eval(Base::frame.Z.dot(globals.wo))) - * tint.max(), - 1.0f); + Emiss = is_backfacing + ? 1 + : 1 - std::min(sheen.albedo(cosNO) * tint.max(), 1.0f); } template @@ -212,14 +36,14 @@ BSDL_INLINE_METHOD Sample SheenLTCLobe::eval_impl(const Imath::V3f& wo, const Imath::V3f& wi) const { - const float cosNO = wo.z; - const float cosNI = wi.z; - const bool isrefl = cosNI > 0 && cosNO >= 0; - const bool doself = isrefl && !back; + const float cosNO = wo.z; + const float cosNI = wi.z; + const bool is_reflection = cosNI > 0 && cosNO >= 0; + const bool do_self = is_reflection && !is_backfacing; Sample s = {}; - if (doself) { - s = sheenLTC.eval(wo, wi); // Return a grayscale sheen. + if (do_self) { + s = sheen.eval(wo, wi); // Return a grayscale sheen. s.weight *= tint; } return s; @@ -230,12 +54,12 @@ BSDL_INLINE_METHOD Sample SheenLTCLobe::sample_impl(const Imath::V3f& wo, const Imath::V3f& sample) const { - const bool doself = !back; + const bool do_self = !is_backfacing; - if (!doself) + if (!do_self) return {}; - Sample s = sheenLTC.sample(wo, sample.x, sample.y, sample.z); + Sample s = sheen.sample(wo, sample.x, sample.y, sample.z); s.weight *= tint; return s; } diff --git a/src/libbsdl/include/BSDL/spectrum_decl.h b/src/libbsdl/include/BSDL/spectrum_decl.h index 2bfd4404b5..c07a6a36e8 100644 --- a/src/libbsdl/include/BSDL/spectrum_decl.h +++ b/src/libbsdl/include/BSDL/spectrum_decl.h @@ -276,7 +276,7 @@ struct Power { } BSDL_INLINE_METHOD Power scale_clamped(float maxv) const { - const float scale = 1 / std::max(maxv, max()); + const float scale = maxv / std::max(maxv, max()); return Power([&](int i) { return data[i] * scale; }, 1); } BSDL_INLINE_METHOD bool is_zero(float eps = 0) const diff --git a/src/libbsdl/include/BSDL/tools.h b/src/libbsdl/include/BSDL/tools.h index bbb049f0f2..1ada926446 100644 --- a/src/libbsdl/include/BSDL/tools.h +++ b/src/libbsdl/include/BSDL/tools.h @@ -42,17 +42,58 @@ AVG_RGB(const Imath::C3f& c) return (c.x + c.y + c.z) * (1.0f / 3); } +// Exponentiate to a compile time known positive integer constant. +// This expands into the minimum number of multiplications. +// +// Usage: pown(x) is x^n +// +template +constexpr F +pown(F x) +{ + if constexpr (E == 0) + return F(1); + else if constexpr (E == 1) + return x; + else { + constexpr unsigned half = E / 2; + constexpr unsigned remainder = E % 2; + const F rec = pown(x); + if constexpr (remainder == 0) + return rec * rec; + else + return rec * rec * x; + } +} + template BSDL_INLINE constexpr T SQR(T x) { - return x * x; + return pown<2>(x); +} + +// CUDA has a problem with std::max/min and constexpr, they take const +// references. This makes nvcc complain because a reference to constexpr is +// not defined in device code. So we define these taking values. +template +BSDL_INLINE T +MAX(T a, T b) +{ + return a > b ? a : b; +} + +template +BSDL_INLINE T +MIN(T a, T b) +{ + return a < b ? a : b; } BSDL_INLINE float CLAMP(float x, float a, float b) { - return std::min(std::max(x, a), b); + return MIN(MAX(x, a), b); } BSDL_INLINE Imath::C3f @@ -436,7 +477,7 @@ struct Frame { BSDL_INLINE_METHOD Frame(const Imath::V3f& Z, const Imath::V3f& _X) : X(_X), Z(Z) { - if (MAX_ABS_XYZ(X) < 1e-4f) { + if (MAX_ABS_XYZ(X) < 1e-4f || fabsf(Z.dot(X.normalized())) > 0.999f) { // X not provided, pick arbitrary auto XY = ortho_build(Z); X = std::get<0>(XY); diff --git a/src/libbsdl/src/genluts.cpp b/src/libbsdl/src/genluts.cpp index ddfedc9014..dc731a28e9 100644 --- a/src/libbsdl/src/genluts.cpp +++ b/src/libbsdl/src/genluts.cpp @@ -11,10 +11,9 @@ using BSDLConfig = bsdl::BSDLDefaultConfig; #include -#include +#include #include #include -#include #include #include #include @@ -31,9 +30,9 @@ using BSDLConfig = bsdl::BSDLDefaultConfig; E(spi::PlasticGGX) \ E(spi::DielectricFront) \ E(spi::DielectricBack) \ - E(spi::CharlieSheen) \ - E(spi::SheenLTC) \ E(spi::Thinlayer) \ + E(mtx::ContyKullaSheen) \ + E(mtx::ZeltnerBurleySheen) \ E(mtx::DielectricReflFront) \ E(mtx::DielectricBothFront) \ E(mtx::DielectricBothBack) diff --git a/src/testrender/shading.cpp b/src/testrender/shading.cpp index f7bba358e7..ebcb0f1cb9 100644 --- a/src/testrender/shading.cpp +++ b/src/testrender/shading.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -98,6 +99,7 @@ struct BSDLLobe : public BSDF { BSDL_WRAP(MxConductor, mtx::ConductorLobe, MX_CONDUCTOR_ID); BSDL_WRAP(MxDielectric, mtx::DielectricLobe, MX_DIELECTRIC_ID); BSDL_WRAP(MxGeneralizedSchlick, mtx::SchlickLobe, MX_GENERALIZED_SCHLICK_ID); +BSDL_WRAP(MxSheen, mtx::SheenLobe, MX_SHEEN_ID); // These two need a special wrapper, different methods @@ -263,14 +265,6 @@ register_closures(OSL::ShadingSystem* shadingsys) CLOSURE_FLOAT_PARAM(MxSubsurfaceParams, anisotropy), CLOSURE_STRING_KEYPARAM(MxSubsurfaceParams, label, "label"), CLOSURE_FINISH_PARAM(MxSubsurfaceParams) } }, - { "sheen_bsdf", - MX_SHEEN_ID, - { CLOSURE_VECTOR_PARAM(MxSheenParams, N), - CLOSURE_COLOR_PARAM(MxSheenParams, albedo), - CLOSURE_FLOAT_PARAM(MxSheenParams, roughness), - CLOSURE_STRING_KEYPARAM(MxSheenParams, label, "label"), - CLOSURE_INT_KEYPARAM(MxSheenParams, mode, "mode"), - CLOSURE_FINISH_PARAM(MxSheenParams) } }, { "uniform_edf", MX_UNIFORM_EDF_ID, { CLOSURE_COLOR_PARAM(MxUniformEdfParams, emittance), @@ -305,7 +299,7 @@ register_closures(OSL::ShadingSystem* shadingsys) // BSDFs coming from BSDL using bsdl_set = bsdl::TypeList; + MxGeneralizedSchlick, MxSheen>; // Register them bsdl_set::apply(BSDLtoOSL { shadingsys }); } @@ -1303,168 +1297,6 @@ struct MxBurleyDiffuse final : public BSDF, MxBurleyDiffuseParams { } }; -// Implementation of the "Charlie Sheen" model [Conty & Kulla, 2017] -// https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_sheen.pdf -// To simplify the implementation, the simpler shadowing/masking visibility term below is used: -// https://dassaultsystemes-technology.github.io/EnterprisePBRShadingModel/spec-2022x.md.html#components/sheen -struct CharlieSheen final : public BSDF, MxSheenParams { - OSL_HOSTDEVICE CharlieSheen(const MxSheenParams& params) - : BSDF(this), MxSheenParams(params) - { - } - - OSL_HOSTDEVICE Color3 get_albedo(const Vec3& wo) const - { - const float NdotV = clamp(N.dot(wo), 0.0f, 1.0f); - // Rational fit from the Material X project - // Ref: https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl - const Vec2 r = Vec2(13.67300f, 1.0f) - + Vec2(-68.78018f, 61.57746f) * NdotV - + Vec2(799.08825f, 442.78211f) * roughness - + Vec2(-905.00061f, 2597.49308f) * NdotV * roughness - + Vec2(60.28956f, 121.81241f) * NdotV * NdotV - + Vec2(1086.96473f, 3045.55075f) * roughness * roughness; - return clamp(albedo * (r.x / r.y), 0.0f, 1.0f); - } - - OSL_HOSTDEVICE Sample eval(const Vec3& wo, const Vec3& wi) const - { - const Vec3 L = wi, V = wo; - const Vec3 H = (L + V).normalize(); - float NdotV = clamp(dot(N, V), 0.0f, 1.0f); - float NdotL = clamp(dot(N, L), 0.0f, 1.0f); - float NdotH = clamp(dot(N, H), 0.0f, 1.0f); - float invRoughness = 1.0f / std::max(roughness, 0.005f); - - float D = (2.0f + invRoughness) - * powf(1.0f - NdotH * NdotH, invRoughness * 0.5f) - / float(2 * M_PI); - float pdf = float(0.5 * M_1_PI); - // NOTE: sheen closure has no fresnel/masking - return { wi, - Color3(float(2 * M_PI) * NdotL * albedo * D - / (4.0f * (NdotL + NdotV - NdotL * NdotV))), - pdf, 1.0f }; - } - - OSL_HOSTDEVICE Sample sample(const Vec3& wo, float rx, float ry, - float rz) const - { - Vec3 out_dir; - float pdf; - Sampling::sample_uniform_hemisphere(N, rx, ry, out_dir, pdf); - return eval(wo, out_dir); - } -}; - -// Implement the sheen model proposed in: -// "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines" -// Tizian Zeltner, Brent Burley, Matt Jen-Yuan Chiang - Siggraph 2022 -// https://tizianzeltner.com/projects/Zeltner2022Practical/ -struct ZeltnerBurleySheen final : public BSDF, MxSheenParams { - OSL_HOSTDEVICE ZeltnerBurleySheen(const MxSheenParams& params) - : BSDF(this), MxSheenParams(params) - { - } - -#define USE_LTC_SAMPLING 1 - - OSL_HOSTDEVICE Color3 get_albedo(const Vec3& wo) const - { - const float NdotV = clamp(N.dot(wo), 1e-5f, 1.0f); - return Color3(fetch_ltc(NdotV).z); - } - - OSL_HOSTDEVICE Sample eval(const Vec3& wo, const Vec3& wi) const - { - const Vec3 L = wi, V = wo; - const float NdotV = clamp(N.dot(V), 0.0f, 1.0f); - const Vec3 ltc = fetch_ltc(NdotV); - - const Vec3 localL = TangentFrame::from_normal_and_tangent(N, V).tolocal( - L); - - const float aInv = ltc.x, bInv = ltc.y, R = ltc.z; - Vec3 wiOriginal(aInv * localL.x + bInv * localL.z, aInv * localL.y, - localL.z); - const float len2 = dot(wiOriginal, wiOriginal); - - float det = aInv * aInv; - float jacobian = det / (len2 * len2); - -#if USE_LTC_SAMPLING == 1 - float pdf = jacobian * std::max(wiOriginal.z, 0.0f) * float(M_1_PI); - return { wi, Color3(R), pdf, 1.0f }; -#else - float pdf = float(0.5 * M_1_PI); - // NOTE: sheen closure has no fresnel/masking - return { wi, Color3(2 * R * jacobian * std::max(wiOriginal.z, 0.0f)), - pdf, 1.0f }; -#endif - } - - OSL_HOSTDEVICE Sample sample(const Vec3& wo, float rx, float ry, - float rz) const - { -#if USE_LTC_SAMPLING == 1 - const Vec3 V = wo; - const float NdotV = clamp(N.dot(V), 0.0f, 1.0f); - const Vec3 ltc = fetch_ltc(NdotV); - const float aInv = ltc.x, bInv = ltc.y, R = ltc.z; - Vec3 wi; - float pdf; - Sampling::sample_cosine_hemisphere(Vec3(0, 0, 1), rx, ry, wi, pdf); - - const Vec3 w = Vec3(wi.x - wi.z * bInv, wi.y, wi.z * aInv); - const float len2 = dot(w, w); - const float jacobian = len2 * len2 / (aInv * aInv); - const Vec3 wn = w / sqrtf(len2); - - const Vec3 L = TangentFrame::from_normal_and_tangent(N, V).toworld(wn); - - pdf = jacobian * std::max(wn.z, 0.0f) * float(M_1_PI); - - return { L, Color3(R), pdf, 1.0f }; -#else - // plain uniform-sampling for validation - Vec3 out_dir; - float pdf; - Sampling::sample_uniform_hemisphere(N, rx, ry, out_dir, pdf); - return eval(wo, out_dir); -#endif - } - -private: - OSL_HOSTDEVICE Vec3 fetch_ltc(float NdotV) const - { - // To avoid look-up tables, we use a fit of the LTC coefficients derived by Stephen Hill - // for the implementation in MaterialX: - // https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl - const float x = NdotV; - const float y = std::max(roughness, 1e-3f); - const float A = ((2.58126f * x + 0.813703f * y) * y) - / (1.0f + 0.310327f * x * x + 2.60994f * x * y); - const float B = sqrtf(1.0f - x) * (y - 1.0f) * y * y * y - / (0.0000254053f + 1.71228f * x - 1.71506f * x * y - + 1.34174f * y * y); - const float invs = (0.0379424f + y * (1.32227f + y)) - / (y * (0.0206607f + 1.58491f * y)); - const float m = y - * (-0.193854f - + y * (-1.14885 + y * (1.7932f - 0.95943f * y * y))) - / (0.046391f + y); - const float o = y * (0.000654023f + (-0.0207818f + 0.119681f * y) * y) - / (1.26264f + y * (-1.92021f + y)); - float q = (x - m) * invs; - const float inv_sqrt2pi = 0.39894228040143f; - float R = expf(-0.5f * q * q) * invs * inv_sqrt2pi + o; - assert(isfinite(A)); - assert(isfinite(B)); - assert(isfinite(R)); - return Vec3(A, B, R); - } -}; - OSL_HOSTDEVICE Color3 evaluate_layer_opacity(const ShaderGlobalsType& sg, float path_roughness, const ClosureColor* closure) @@ -1530,14 +1362,10 @@ evaluate_layer_opacity(const ShaderGlobalsType& sg, float path_roughness, weight *= w * (Color3(1) - d.filter_o(-sg.I).toRGB(0)); break; } - case MX_SHEEN_ID: { - const MxSheenParams& params = *comp->as(); - if (params.mode == 1) { - weight *= w * ZeltnerBurleySheen(params).get_albedo(-sg.I); - } else { - // otherwise, default to old sheen model - weight *= w * CharlieSheen(params).get_albedo(-sg.I); - } + case MxSheen::closureid(): { + const MxSheen::Data& params = *comp->as(); + MxSheen d(params, -sg.I, sg.backfacing, path_roughness); + weight *= w * (Color3(1) - d.filter_o(-sg.I).toRGB(0)); closure = nullptr; break; } @@ -1840,14 +1668,11 @@ process_bsdf_closure(const ShaderGlobalsType& sg, float path_roughness, params); break; } - case MX_SHEEN_ID: { - const MxSheenParams& params = *comp->as(); - if (params.mode == 1) - ok = result.bsdf.add_bsdf(cw, - params); - else - ok = result.bsdf.add_bsdf( - cw, params); // default to legacy closure + case MxSheen::closureid(): { + const MxSheen::Data& params = *comp->as(); + ok = result.bsdf.add_bsdf(cw, params, -sg.I, + sg.backfacing, + path_roughness); break; } case MX_LAYER_ID: { diff --git a/src/testrender/shading.h b/src/testrender/shading.h index 8e69828aa6..e1cb4d1417 100644 --- a/src/testrender/shading.h +++ b/src/testrender/shading.h @@ -246,6 +246,7 @@ struct MxMicrofacet; struct MxConductor; struct MxDielectric; struct MxGeneralizedSchlick; +struct MxSheen; struct Transparent; struct OrenNayar; @@ -265,9 +266,8 @@ using AbstractBSDF = bsdl::StaticVirtual< Diffuse<0>, Transparent, OrenNayar, Diffuse<1>, Phong, Ward, Reflection, Refraction, MicrofacetBeckmannRefl, MicrofacetBeckmannRefr, MicrofacetBeckmannBoth, MicrofacetGGXRefl, MicrofacetGGXRefr, - MicrofacetGGXBoth, MxConductor, MxDielectric, MxGeneralizedSchlick, - MxBurleyDiffuse, EnergyCompensatedOrenNayar, ZeltnerBurleySheen, - CharlieSheen, SpiThinLayer>; + MicrofacetGGXBoth, MxConductor, MxDielectric, MxGeneralizedSchlick, MxSheen, + MxBurleyDiffuse, EnergyCompensatedOrenNayar, SpiThinLayer>; // Then we just need to inherit from AbstractBSDF diff --git a/testsuite/render-mx-furnace-sheen/ref/out-alt-optix.exr b/testsuite/render-mx-furnace-sheen/ref/out-alt-optix.exr new file mode 100644 index 0000000000..8bf2caa064 Binary files /dev/null and b/testsuite/render-mx-furnace-sheen/ref/out-alt-optix.exr differ diff --git a/testsuite/render-mx-furnace-sheen/ref/out-optix-alt.exr b/testsuite/render-mx-furnace-sheen/ref/out-optix-alt.exr deleted file mode 100644 index 610f308c6e..0000000000 Binary files a/testsuite/render-mx-furnace-sheen/ref/out-optix-alt.exr and /dev/null differ diff --git a/testsuite/render-mx-furnace-sheen/ref/out.exr b/testsuite/render-mx-furnace-sheen/ref/out.exr index 9166ef285c..65e8ccfd77 100644 Binary files a/testsuite/render-mx-furnace-sheen/ref/out.exr and b/testsuite/render-mx-furnace-sheen/ref/out.exr differ diff --git a/testsuite/render-mx-furnace-sheen/run.py b/testsuite/render-mx-furnace-sheen/run.py index 3362f24eba..3e4d213a79 100755 --- a/testsuite/render-mx-furnace-sheen/run.py +++ b/testsuite/render-mx-furnace-sheen/run.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause # https://github.com/AcademySoftwareFoundation/OpenShadingLanguage -failthresh = 0.005 # allow a little more LSB noise between platforms +failthresh = 0.01 # allow a little more LSB noise between platforms +hardfail = 0.025 outputs = [ "out.exr" ] command = testrender("-r 768 128 -aa 16 scene.xml out.exr") diff --git a/testsuite/render-mx-furnace-sheen/scene.xml b/testsuite/render-mx-furnace-sheen/scene.xml index 29a9c60326..d4b6f8e147 100644 --- a/testsuite/render-mx-furnace-sheen/scene.xml +++ b/testsuite/render-mx-furnace-sheen/scene.xml @@ -1,4 +1,5 @@ +