diff --git a/examples_tests b/examples_tests index 2b4db21239..6d0a3fe499 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 2b4db2123918f380cc0a35f6889315a02f84ea73 +Subproject commit 6d0a3fe49997a1157e39483e0ec42e32f562e157 diff --git a/include/nbl/builtin/glsl/utils/morton.glsl b/include/nbl/builtin/glsl/utils/morton.glsl index de3be8b9c7..fd07a9cad8 100644 --- a/include/nbl/builtin/glsl/utils/morton.glsl +++ b/include/nbl/builtin/glsl/utils/morton.glsl @@ -22,6 +22,18 @@ uint nbl_glsl_morton_decode2d8bComponent(in uint x) return x; } +uint nbl_glsl_morton_decode2d32bComponent(in uint x) +{ + x &= 0x55555555u; + x = (x ^ (x >> 1u)) & 0x33333333u; + x = (x ^ (x >> 2u)) & 0x0f0f0f0fu; + x = (x ^ (x >> 4u)) & 0x00ff00ffu; + x = (x ^ (x >> 8u)) & 0x0000ffffu; + x = (x ^ (x >> 16u)); + return x; +} + + uvec2 nbl_glsl_morton_decode2d4b(in uint x) { return uvec2(nbl_glsl_morton_decode2d4bComponent(x), nbl_glsl_morton_decode2d4bComponent(x >> 1u)); @@ -32,4 +44,9 @@ uvec2 nbl_glsl_morton_decode2d8b(in uint x) return uvec2(nbl_glsl_morton_decode2d8bComponent(x), nbl_glsl_morton_decode2d8bComponent(x >> 1u)); } +uvec2 nbl_glsl_morton_decode2d32b(in uint x) +{ + return uvec2(nbl_glsl_morton_decode2d32bComponent(x), nbl_glsl_morton_decode2d32bComponent(x >> 1u)); +} + #endif \ No newline at end of file diff --git a/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl b/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl index fdce140f68..a185dc8d98 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl @@ -51,7 +51,7 @@ struct quant_query_helper template static quant_query_type __call(NBL_REF_ARG(N) ndf, NBL_CONST_REF_ARG(F) fresnel, NBL_CONST_REF_ARG(I) interaction, NBL_CONST_REF_ARG(C) cache) { - return ndf.template createQuantQuery(interaction, cache, fresnel.orientedEta.value[0]); + return ndf.template createQuantQuery(interaction, cache, fresnel.getRefractionOrientedEta()); } }; @@ -67,60 +67,6 @@ struct quant_query_helper return ndf.template createQuantQuery(interaction, cache, dummy); } }; - -template -struct checkValid; - -template -struct checkValid -{ - using scalar_type = typename F::scalar_type; - - template - static bool __call(NBL_CONST_REF_ARG(F) orientedFresnel, NBL_CONST_REF_ARG(Sample) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) - { - return _sample.getNdotL() > numeric_limits::min && interaction.getNdotV() > numeric_limits::min; - } -}; - -template -struct checkValid -{ - using scalar_type = typename F::scalar_type; - using vector_type = typename F::vector_type; // expect monochrome - - template - static bool __call(NBL_CONST_REF_ARG(F) orientedFresnel, NBL_CONST_REF_ARG(Sample) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) - { - fresnel::OrientedEtas orientedEta = fresnel::OrientedEtas::create(scalar_type(1.0), hlsl::promote(orientedFresnel.getRefractionOrientedEta())); - return cache.isValid(orientedEta); - } -}; - -template -struct getOrientedFresnel; - -template -struct getOrientedFresnel -{ - static F __call(NBL_CONST_REF_ARG(F) fresnel, typename F::scalar_type NdotV) - { - // expect conductor fresnel - return fresnel; - } -}; - -template -NBL_PARTIAL_REQ_TOP(fresnel::TwoSidedFresnel) -struct getOrientedFresnel) > -{ - using scalar_type = typename F::scalar_type; - - static F __call(NBL_CONST_REF_ARG(F) fresnel, scalar_type NdotV) - { - return fresnel.getReorientedFresnel(NdotV); - } -}; } // N (NDF), F (fresnel) @@ -138,30 +84,78 @@ struct SCookTorrance NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = ndf_type::SupportedPaths != ndf::MTT_REFLECT; NBL_HLSL_BXDF_ANISOTROPIC_COND_DECLS(IsAnisotropic); + // utility functions template, class MicrofacetCache=conditional_t NBL_FUNC_REQUIRES(!ndf::NDF_CanOverwriteDG && RequiredInteraction && RequiredMicrofacetCache) - static void overwriteDG(NBL_REF_ARG(scalar_type) DG, ndf_type ndf, NBL_CONST_REF_ARG(typename ndf_type::g2g1_query_type) query, NBL_CONST_REF_ARG(typename ndf_type::quant_query_type) quant_query, + static void __overwriteDG(NBL_REF_ARG(scalar_type) DG, ndf_type ndf, NBL_CONST_REF_ARG(typename ndf_type::g2g1_query_type) query, NBL_CONST_REF_ARG(typename ndf_type::quant_query_type) quant_query, NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache, NBL_REF_ARG(bool) isInfinity) { } template, class MicrofacetCache=conditional_t NBL_FUNC_REQUIRES(ndf::NDF_CanOverwriteDG && RequiredInteraction && RequiredMicrofacetCache) - static void overwriteDG(NBL_REF_ARG(scalar_type) DG, ndf_type ndf, NBL_CONST_REF_ARG(typename ndf_type::g2g1_query_type) query, NBL_CONST_REF_ARG(typename ndf_type::quant_query_type) quant_query, + static void __overwriteDG(NBL_REF_ARG(scalar_type) DG, ndf_type ndf, NBL_CONST_REF_ARG(typename ndf_type::g2g1_query_type) query, NBL_CONST_REF_ARG(typename ndf_type::quant_query_type) quant_query, NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache, NBL_REF_ARG(bool) isInfinity) { quant_type dg = ndf.template Dcorrelated(query, quant_query, _sample, interaction, cache, isInfinity); DG = dg.projectedLightMeasure; } + template) + static fresnel_type __getOrientedFresnel(NBL_CONST_REF_ARG(fresnel_type) fresnel, scalar_type NdotV) + { + // expect conductor fresnel + return fresnel; + } + template) + static fresnel_type __getOrientedFresnel(NBL_CONST_REF_ARG(fresnel_type) fresnel, scalar_type NdotV) + { + return fresnel.getReorientedFresnel(NdotV); + } + + template, + class MicrofacetCache=conditional_t, typename C=bool_constant NBL_FUNC_REQUIRES(C::value && !IsBSDF) + static bool __checkValid(NBL_CONST_REF_ARG(fresnel_type) orientedFresnel, NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) + { + return _sample.getNdotL() > numeric_limits::min && interaction.getNdotV() > numeric_limits::min; + } + template, + class MicrofacetCache=conditional_t, typename C=bool_constant NBL_FUNC_REQUIRES(C::value && IsBSDF) + static bool __checkValid(NBL_CONST_REF_ARG(fresnel_type) orientedFresnel, NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) + { + fresnel::OrientedEtas orientedEta = fresnel::OrientedEtas::create(scalar_type(1.0), hlsl::promote(orientedFresnel.getRefractionOrientedEta())); + return cache.isValid(orientedEta); + } + + template, + typename C=bool_constant NBL_FUNC_REQUIRES(C::value && !fresnel_type::ReturnsMonochrome) + static scalar_type __getScaledReflectance(NBL_CONST_REF_ARG(fresnel_type) orientedFresnel, NBL_CONST_REF_ARG(Interaction) interaction, scalar_type clampedVdotH) + { + spectral_type throughputWeights = interaction.getLuminosityContributionHint(); + return hlsl::dot(impl::__implicit_promote::__call(orientedFresnel(clampedVdotH)), throughputWeights); + } + template, + typename C=bool_constant NBL_FUNC_REQUIRES(C::value && fresnel_type::ReturnsMonochrome) + static scalar_type __getScaledReflectance(NBL_CONST_REF_ARG(fresnel_type) orientedFresnel, NBL_CONST_REF_ARG(Interaction) interaction, scalar_type clampedVdotH) + { + return orientedFresnel(clampedVdotH)[0]; + } + + bool __dotIsUnity(const vector3_type a, const vector3_type b, const scalar_type value) + { + const scalar_type ab = hlsl::dot(a, b); + return hlsl::max(ab, value / ab) <= scalar_type(value + 1e-3); + } + + // bxdf stuff template, class MicrofacetCache=conditional_t NBL_FUNC_REQUIRES(RequiredInteraction && RequiredMicrofacetCache) spectral_type eval(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) { - fresnel_type _f = impl::getOrientedFresnel::__call(fresnel, interaction.getNdotV()); - if (!impl::checkValid::template __call(_f, _sample, interaction, cache)) + fresnel_type _f = __getOrientedFresnel(fresnel, interaction.getNdotV()); + if (!__checkValid(_f, _sample, interaction, cache)) return hlsl::promote(0.0); using quant_query_type = typename ndf_type::quant_query_type; @@ -176,8 +170,10 @@ struct SCookTorrance if (!isInfinity) DG *= ndf.template correlated(gq, _sample, interaction, cache); - overwriteDG(DG, ndf, gq, qq, _sample, interaction, cache, isInfinity); + __overwriteDG(DG, ndf, gq, qq, _sample, interaction, cache, isInfinity); + // immediately return only after all calls setting DG + // allows compiler to throw away calls to ndf.D if using __overwriteDG, before that we only avoid computation for G2(correlated) if (isInfinity) return hlsl::promote(0.0); @@ -187,65 +183,99 @@ struct SCookTorrance NBL_IF_CONSTEXPR(IsBSDF) { - const scalar_type reflectance = _f(clampedVdotH)[0]; - return hlsl::promote(hlsl::mix(reflectance, scalar_type(1.0) - reflectance, cache.isTransmission())) * DG; + const spectral_type reflectance = impl::__implicit_promote::__call(_f(clampedVdotH)); + return hlsl::mix(reflectance, hlsl::promote(1.0) - reflectance, cache.isTransmission()) * DG; } else return impl::__implicit_promote::__call(_f(clampedVdotH)) * DG; } - template > - enable_if_t generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector2_type u, NBL_REF_ARG(anisocache_type) cache) + sample_type __generate_common(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector3_type localH, + const scalar_type NdotV, const scalar_type VdotH, const scalar_type LdotH, bool transmitted, + NBL_CONST_REF_ARG(fresnel::OrientedEtaRcps) rcpEta, + NBL_REF_ARG(bool) valid) { + // fail if samples have invalid paths + const scalar_type NdotL = hlsl::mix(scalar_type(2.0) * VdotH * localH.z - NdotV, + localH.z * (VdotH * rcpEta.value[0] + LdotH) - NdotV * rcpEta.value[0], transmitted); + // VNDF sampling guarantees that `VdotH` has same sign as `NdotV` + // and `transmitted` controls the sign of `LdotH` relative to `VdotH` by construction (reflect -> same sign, or refract -> opposite sign) + if (ComputeMicrofacetNormal::isTransmissionPath(NdotV, NdotL) != transmitted) { - const scalar_type NdotV = interaction.getNdotV(); - if (NdotV < numeric_limits::min) - return sample_type::createInvalid(); - assert(!hlsl::isnan(NdotV)); + valid = false; + return sample_type::createInvalid(); // should check if sample direction is invalid } - const vector3_type localV = interaction.getTangentSpaceV(); - const vector3_type localH = ndf.generateH(localV, u); - const scalar_type VdotH = hlsl::dot(localV, localH); - assert(VdotH >= scalar_type(0.0)); // VNDF sampling guarantees VdotH has same sign as NdotV (should be positive for BRDF) + ray_dir_info_type V = interaction.getV(); + const matrix3x3_type fromTangent = interaction.getFromTangentSpace(); + // tangent frame orthonormality + assert(__dotIsUnity(fromTangent[0],fromTangent[1],0.0)); + assert(__dotIsUnity(fromTangent[1],fromTangent[2],0.0)); + assert(__dotIsUnity(fromTangent[2],fromTangent[0],0.0)); + // NDF sampling produced a unit length direction + assert(__dotIsUnity(localH,localH,1.0)); + const vector3_type H = hlsl::mul(interaction.getFromTangentSpace(), localH); + Refract r = Refract::create(V.getDirection(), H); - const scalar_type NdotL = scalar_type(2.0) * VdotH * localH.z - localV.z; - ray_dir_info_type L; - if (NdotL > 0) // compiler's Common Subexpression Elimination pass should re-use 2*VdotH later + struct reflect_refract_wrapper // so we don't recalculate LdotH { - ray_dir_info_type V = interaction.getV(); - const vector3_type H = hlsl::mul(interaction.getFromTangentSpace(), localH); - struct reflect_wrapper // so we don't recalculate VdotH + vector3_type operator()(const bool doRefract, const scalar_type rcpOrientedEta) NBL_CONST_MEMBER_FUNC { - vector3_type operator()() NBL_CONST_MEMBER_FUNC - { - return r(VdotH); - } - bxdf::Reflect r; - scalar_type VdotH; - }; - reflect_wrapper rw; - rw.r = bxdf::Reflect::create(V.getDirection(), H); - rw.VdotH = VdotH; - L = V.template reflect(rw); + return rr(NdotTorR, rcpOrientedEta); + } + bxdf::ReflectRefract rr; + scalar_type NdotTorR; + }; + bxdf::ReflectRefract rr; // rr.getNdotTorR() and calls to mix as well as a good part of the computations should CSE with our computation of NdotL above + rr.refract = r; + reflect_refract_wrapper rrw; + rrw.rr = rr; + rrw.NdotTorR = LdotH; + ray_dir_info_type L = V.template reflectRefract(rrw, transmitted, rcpEta.value[0]); - cache = anisocache_type::createForReflection(localV, localH); + const vector3_type T = interaction.getT(); + const vector3_type B = interaction.getB(); - const vector3_type T = interaction.getT(); - const vector3_type B = interaction.getB(); + valid = true; + return sample_type::create(L, T, B, NdotL); + } + template NBL_FUNC_REQUIRES(C::value && !IsBSDF) + sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector2_type u, NBL_REF_ARG(anisocache_type) cache) + { + const scalar_type NdotV = interaction.getNdotV(); + if (NdotV < numeric_limits::min) + return sample_type::createInvalid(); + assert(!hlsl::isnan(NdotV)); - return sample_type::create(L, T, B, NdotL); + const vector3_type localV = interaction.getTangentSpaceV(); + const vector3_type localH = ndf.generateH(localV, u); + const scalar_type VdotH = hlsl::dot(localV, localH); + NBL_IF_CONSTEXPR(!ndf_type::GuaranteedVNDF) // VNDF sampling guarantees VdotH has same sign as NdotV (should be positive for BRDF) + { + // allow for rejection sampling, theoretically NdotV=0 or VdotH=0 is valid, but leads to 0 value contribution anyway + if (VdotH <= scalar_type(0.0)) + return sample_type::createInvalid(); + assert(!hlsl::isnan(NdotV*VdotH)); } - else // fail if samples have invalid paths - return sample_type::createInvalid(); // should check if sample direction is invalid + else + { + assert(VdotH >= scalar_type(0.0)); + } + + fresnel::OrientedEtaRcps dummy; + bool valid; + sample_type s = __generate_common(interaction, localH, NdotV, VdotH, VdotH, false, dummy, valid); + if (valid) + cache = anisocache_type::createForReflection(localV, localH); + return s; } - template > - enable_if_t generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector3_type u, NBL_REF_ARG(anisocache_type) cache) + template NBL_FUNC_REQUIRES(C::value && IsBSDF) + sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector3_type u, NBL_REF_ARG(anisocache_type) cache) { const vector3_type localV = interaction.getTangentSpaceV(); const scalar_type NdotV = localV.z; - fresnel_type _f = impl::getOrientedFresnel::__call(fresnel, NdotV); + fresnel_type _f = __getOrientedFresnel(fresnel, NdotV); fresnel::OrientedEtaRcps rcpEta = _f.getOrientedEtaRcps(); const vector3_type upperHemisphereV = ieee754::flipSignIfRHSNegative(localV, hlsl::promote(NdotV)); @@ -255,7 +285,7 @@ struct SCookTorrance NBL_IF_CONSTEXPR(!ndf_type::GuaranteedVNDF) { // allow for rejection sampling, theoretically NdotV=0 or VdotH=0 is valid, but leads to 0 value contribution anyway - if ((IsBSDF ? (NdotV*VdotH) : VdotH) <= scalar_type(0.0)) + if (NdotV*VdotH <= scalar_type(0.0)) return sample_type::createInvalid(); assert(!hlsl::isnan(NdotV*VdotH)); } @@ -263,53 +293,29 @@ struct SCookTorrance { assert(NdotV*VdotH >= scalar_type(0.0)); } - const scalar_type reflectance = _f(hlsl::abs(VdotH))[0]; + + const scalar_type reflectance = __getScaledReflectance(_f, interaction, hlsl::abs(VdotH)); scalar_type rcpChoiceProb; scalar_type z = u.z; bool transmitted = math::partitionRandVariable(reflectance, z, rcpChoiceProb); - const scalar_type LdotH = hlsl::mix(VdotH, ieee754::copySign(hlsl::sqrt(rcpEta.value2[0]*VdotH*VdotH + scalar_type(1.0) - rcpEta.value2[0]), -VdotH), transmitted); - // fail if samples have invalid paths - const scalar_type NdotL = hlsl::mix(scalar_type(2.0) * VdotH * localH.z - NdotV, - localH.z * (VdotH * rcpEta.value[0] + LdotH) - NdotV * rcpEta.value[0], transmitted); - // VNDF sampling guarantees that `VdotH` has same sign as `NdotV` - // and `transmitted` controls the sign of `LdotH` relative to `VdotH` by construction (reflect -> same sign, or refract -> opposite sign) - if (ComputeMicrofacetNormal::isTransmissionPath(NdotV, NdotL) != transmitted) - return sample_type::createInvalid(); // should check if sample direction is invalid - - cache = anisocache_type::createPartial(VdotH, LdotH, localH.z, transmitted, rcpEta); - assert(cache.isValid(fresnel::OrientedEtas::create(scalar_type(1.0), hlsl::promote(_f.getRefractionOrientedEta())))); - - ray_dir_info_type V = interaction.getV(); - const vector3_type H = hlsl::mul(interaction.getFromTangentSpace(), localH); - assert(hlsl::abs(hlsl::length(H) - scalar_type(1.0)) < scalar_type(1e-4)); - Refract r = Refract::create(V.getDirection(), H); - - struct reflect_refract_wrapper // so we don't recalculate LdotH + const scalar_type LdotH = hlsl::mix(VdotH, ieee754::copySign(hlsl::sqrt(rcpEta.value2[0]*VdotH*VdotH + scalar_type(1.0) - rcpEta.value2[0]), -VdotH), transmitted); + bool valid; + sample_type s = __generate_common(interaction, localH, NdotV, VdotH, LdotH, transmitted, rcpEta, valid); + if (valid) { - vector3_type operator()(const bool doRefract, const scalar_type rcpOrientedEta) NBL_CONST_MEMBER_FUNC - { - return rr(NdotTorR, rcpOrientedEta); - } - bxdf::ReflectRefract rr; - scalar_type NdotTorR; - }; - bxdf::ReflectRefract rr; // rr.getNdotTorR() and calls to mix as well as a good part of the computations should CSE with our computation of NdotL above - rr.refract = r; - reflect_refract_wrapper rrw; - rrw.rr = rr; - rrw.NdotTorR = LdotH; - ray_dir_info_type L = V.template reflectRefract(rrw, transmitted, rcpEta.value[0]); - - const vector3_type T = interaction.getT(); - const vector3_type B = interaction.getB(); - cache.fillTangents(T, B, H); - - return sample_type::create(L, T, B, NdotL); + cache = anisocache_type::createPartial(VdotH, LdotH, localH.z, transmitted, rcpEta); + assert(cache.isValid(fresnel::OrientedEtas::create(scalar_type(1.0), hlsl::promote(_f.getRefractionOrientedEta())))); + const vector3_type T = interaction.getT(); + const vector3_type B = interaction.getB(); + const vector3_type H = hlsl::mul(interaction.getFromTangentSpace(), localH); + cache.fillTangents(T, B, H); + } + return s; } - template > - enable_if_t generate(NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, const conditional_t u, NBL_REF_ARG(isocache_type) cache) + template NBL_FUNC_REQUIRES(C::value && !IsAnisotropic) + sample_type generate(NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, const conditional_t u, NBL_REF_ARG(isocache_type) cache) { anisocache_type aniso_cache; sample_type s = generate(anisotropic_interaction_type::create(interaction), u, aniso_cache); @@ -325,13 +331,13 @@ struct SCookTorrance dg1_query_type dq = ndf.template createDG1Query(interaction, cache); - fresnel_type _f = impl::getOrientedFresnel::__call(fresnel, interaction.getNdotV()); + fresnel_type _f = __getOrientedFresnel(fresnel, interaction.getNdotV()); quant_query_type qq = impl::quant_query_helper::template __call(ndf, _f, interaction, cache); quant_type DG1 = ndf.template DG1(dq, qq, _sample, interaction, isInfinity); NBL_IF_CONSTEXPR(IsBSDF) { - const scalar_type reflectance = _f(hlsl::abs(cache.getVdotH()))[0]; + const scalar_type reflectance = __getScaledReflectance(_f, interaction, hlsl::abs(cache.getVdotH())); return hlsl::mix(reflectance, scalar_type(1.0) - reflectance, cache.isTransmission()) * DG1.projectedLightMeasure; } else @@ -344,8 +350,8 @@ struct SCookTorrance NBL_FUNC_REQUIRES(RequiredInteraction && RequiredMicrofacetCache) scalar_type pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) { - fresnel_type _f = impl::getOrientedFresnel::__call(fresnel, interaction.getNdotV()); - if (!impl::checkValid::template __call(_f, _sample, interaction, cache)) + fresnel_type _f = __getOrientedFresnel(fresnel, interaction.getNdotV()); + if (!__checkValid(_f, _sample, interaction, cache)) return scalar_type(0.0); bool isInfinity; @@ -363,9 +369,9 @@ struct SCookTorrance bool isInfinity; scalar_type _pdf = __pdf(_sample, interaction, cache, isInfinity); - fresnel_type _f = impl::getOrientedFresnel::__call(fresnel, interaction.getNdotV()); + fresnel_type _f = __getOrientedFresnel(fresnel, interaction.getNdotV()); - const bool valid = impl::checkValid::template __call(_f, _sample, interaction, cache); + const bool valid = __checkValid(_f, _sample, interaction, cache); assert(valid); // expect the generated sample to always be valid, different checks for brdf and btdf scalar_type G2_over_G1 = scalar_type(1.0); @@ -378,7 +384,17 @@ struct SCookTorrance spectral_type quo; NBL_IF_CONSTEXPR(IsBSDF) - quo = hlsl::promote(G2_over_G1); + { + NBL_IF_CONSTEXPR(fresnel_type::ReturnsMonochrome) + quo = hlsl::promote(G2_over_G1); + else + { + const scalar_type scaled_reflectance = __getScaledReflectance(_f, interaction, hlsl::abs(cache.getVdotH())); + spectral_type reflectance = impl::__implicit_promote::__call(_f(hlsl::abs(cache.getVdotH()))); + quo = hlsl::mix(reflectance / scaled_reflectance, + (hlsl::promote(1.0) - reflectance) / (scalar_type(1.0) - scaled_reflectance), cache.isTransmission()) * G2_over_G1; + } + } else { const scalar_type VdotH = cache.getVdotH(); diff --git a/include/nbl/builtin/hlsl/bxdf/common.hlsl b/include/nbl/builtin/hlsl/bxdf/common.hlsl index 34d2e3d66c..ebad0a925c 100644 --- a/include/nbl/builtin/hlsl/bxdf/common.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/common.hlsl @@ -188,11 +188,13 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::ray_dir_info_type)) ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::spectral_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((iso.getV()), ::nbl::hlsl::is_same_v, typename T::ray_dir_info_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((iso.getN()), ::nbl::hlsl::is_same_v, typename T::vector3_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((iso.getNdotV(clampMode)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((iso.getNdotV2()), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((iso.getPathOrigin()), ::nbl::hlsl::is_same_v, PathOrigin)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((iso.getLuminosityContributionHint()), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::create(normV,normN)), ::nbl::hlsl::is_same_v, T)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(ray_dir_info::Basic, typename T::ray_dir_info_type)) ); @@ -202,21 +204,24 @@ NBL_CONCEPT_END( #undef iso #include -template) +template && concepts::FloatingPointLikeVectorial) struct SIsotropic { + using this_t = SIsotropic; using ray_dir_info_type = RayDirInfo; using scalar_type = typename RayDirInfo::scalar_type; using vector3_type = typename RayDirInfo::vector3_type; + using spectral_type = vector3_type; // WARNING: Changed since GLSL, now arguments need to be normalized! - static SIsotropic create(NBL_CONST_REF_ARG(RayDirInfo) normalizedV, const vector3_type normalizedN) + static this_t create(NBL_CONST_REF_ARG(RayDirInfo) normalizedV, const vector3_type normalizedN) { - SIsotropic retval; + this_t retval; retval.V = normalizedV; retval.N = normalizedN; retval.NdotV = nbl::hlsl::dot(retval.N, retval.V.getDirection()); retval.NdotV2 = retval.NdotV * retval.NdotV; + retval.luminosityContributionHint = hlsl::promote(1.0); return retval; } @@ -230,11 +235,14 @@ struct SIsotropic scalar_type getNdotV2() NBL_CONST_MEMBER_FUNC { return NdotV2; } PathOrigin getPathOrigin() NBL_CONST_MEMBER_FUNC { return PathOrigin::PO_SENSOR; } + spectral_type getLuminosityContributionHint() NBL_CONST_MEMBER_FUNC { return luminosityContributionHint; } RayDirInfo V; vector3_type N; scalar_type NdotV; scalar_type NdotV2; + + spectral_type luminosityContributionHint; }; #define NBL_CONCEPT_NAME Anisotropic @@ -278,6 +286,7 @@ struct SAnisotropic using scalar_type = typename ray_dir_info_type::scalar_type; using vector3_type = typename ray_dir_info_type::vector3_type; using matrix3x3_type = matrix; + using spectral_type = typename isotropic_interaction_type::spectral_type; // WARNING: Changed since GLSL, now arguments need to be normalized! static this_t create( @@ -319,6 +328,7 @@ struct SAnisotropic scalar_type getNdotV(BxDFClampMode _clamp = BxDFClampMode::BCM_NONE) NBL_CONST_MEMBER_FUNC { return isotropic.getNdotV(_clamp); } scalar_type getNdotV2() NBL_CONST_MEMBER_FUNC { return isotropic.getNdotV2(); } PathOrigin getPathOrigin() NBL_CONST_MEMBER_FUNC { return isotropic.getPathOrigin(); } + spectral_type getLuminosityContributionHint() NBL_CONST_MEMBER_FUNC { return isotropic.getLuminosityContributionHint(); } vector3_type getT() NBL_CONST_MEMBER_FUNC { return T; } vector3_type getB() NBL_CONST_MEMBER_FUNC { return B; } @@ -345,7 +355,7 @@ struct SAnisotropic #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (_sample, T) -#define NBL_CONCEPT_PARAM_1 (inter, surface_interactions::SIsotropic) +#define NBL_CONCEPT_PARAM_1 (inter, surface_interactions::SIsotropic) #define NBL_CONCEPT_PARAM_2 (rdirinfo, typename T::ray_dir_info_type) #define NBL_CONCEPT_PARAM_3 (pV, typename T::vector3_type) #define NBL_CONCEPT_PARAM_4 (frame, typename T::matrix3x3_type) @@ -376,7 +386,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::create(rdirinfo,pV)), ::nbl::hlsl::is_same_v, T)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::create(rdirinfo,pV,pV,pV)), ::nbl::hlsl::is_same_v, T)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::create(rdirinfo,pV,pV,pNdotL)), ::nbl::hlsl::is_same_v, T)) - // ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::template create >(pV,inter)), ::nbl::hlsl::is_same_v, T)) // NOTE: temporarily commented out due to dxc bug https://github.com/microsoft/DirectXShaderCompiler/issues/7154 + // ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::template create >(pV,inter)), ::nbl::hlsl::is_same_v, T)) // NOTE: temporarily commented out due to dxc bug https://github.com/microsoft/DirectXShaderCompiler/issues/7154 ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sample.getTangentSpaceL()), ::nbl::hlsl::is_same_v, typename T::vector3_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::createInvalid()), ::nbl::hlsl::is_same_v, T)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(ray_dir_info::Basic, typename T::ray_dir_info_type)) @@ -525,7 +535,7 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (cache, T) -#define NBL_CONCEPT_PARAM_1 (iso, surface_interactions::SIsotropic >) +#define NBL_CONCEPT_PARAM_1 (iso, surface_interactions::SIsotropic, typename T::vector3_type >) #define NBL_CONCEPT_PARAM_2 (pNdotV, typename T::scalar_type) #define NBL_CONCEPT_PARAM_3 (_sample, SLightSample >) #define NBL_CONCEPT_PARAM_4 (V, typename T::vector3_type) @@ -540,9 +550,9 @@ NBL_CONCEPT_BEGIN(6) NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::createForReflection(pNdotV,pNdotV,pNdotV,pNdotV)), ::nbl::hlsl::is_same_v, T)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::createForReflection(pNdotV,pNdotV,pNdotV)), ::nbl::hlsl::is_same_v, T)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::template createForReflection >,SLightSample > >(iso,_sample)), ::nbl::hlsl::is_same_v, T)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::template createForReflection, typename T::vector3_type >,SLightSample > >(iso,_sample)), ::nbl::hlsl::is_same_v, T)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::create(V,V,V,eta,V)), ::nbl::hlsl::is_same_v, T)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::template create >,SLightSample > >(iso,_sample,eta,V)), ::nbl::hlsl::is_same_v, T)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::template create, typename T::vector3_type >,SLightSample > >(iso,_sample,eta,V)), ::nbl::hlsl::is_same_v, T)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(ReadableIsotropicMicrofacetCache, T)) ); #undef eta @@ -698,7 +708,7 @@ struct SIsotropicMicrofacetCache #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (cache, T) -#define NBL_CONCEPT_PARAM_1 (aniso, surface_interactions::SAnisotropic > >) +#define NBL_CONCEPT_PARAM_1 (aniso, surface_interactions::SAnisotropic, typename T::vector3_type > >) #define NBL_CONCEPT_PARAM_2 (pNdotL, typename T::scalar_type) #define NBL_CONCEPT_PARAM_3 (_sample, SLightSample >) #define NBL_CONCEPT_PARAM_4 (V, typename T::vector3_type) @@ -724,9 +734,9 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::createForReflection(V,V)), ::nbl::hlsl::is_same_v, T)) // ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::create(V,V,b0,rcp_eta)), ::nbl::hlsl::is_same_v, T)) // TODO: refuses to compile when arg4 is rcp_eta for some reason, eta is fine ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::createForReflection(V,V,pNdotL)), ::nbl::hlsl::is_same_v, T)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::template createForReflection > >,SLightSample > >(aniso,_sample)), ::nbl::hlsl::is_same_v, T)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::template createForReflection, typename T::vector3_type > >,SLightSample > >(aniso,_sample)), ::nbl::hlsl::is_same_v, T)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::create(V,V,V,V,V,eta,V)), ::nbl::hlsl::is_same_v, T)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::template create > >,SLightSample > >(aniso,_sample,eta)), ::nbl::hlsl::is_same_v, T)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::template create, typename T::vector3_type > >,SLightSample > >(aniso,_sample,eta)), ::nbl::hlsl::is_same_v, T)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((T::createPartial(pNdotL,pNdotL,pNdotL,b0,rcp_eta)), ::nbl::hlsl::is_same_v, T)) ((NBL_CONCEPT_REQ_EXPR)(cache.fillTangents(V,V,V))) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(CreatableIsotropicMicrofacetCache, typename T::isocache_type)) diff --git a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl index 619d277d74..56ea88080c 100644 --- a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl @@ -10,6 +10,7 @@ #include "nbl/builtin/hlsl/numbers.hlsl" #include "nbl/builtin/hlsl/complex.hlsl" #include "nbl/builtin/hlsl/tgmath.hlsl" +#include "nbl/builtin/hlsl/colorspace.hlsl" #include "nbl/builtin/hlsl/vector_utils/vector_traits.hlsl" namespace nbl @@ -312,8 +313,9 @@ NBL_CONCEPT_BEGIN(2) NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) ((NBL_CONCEPT_REQ_TYPE)(T::vector_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::eta_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((fresnel(cosTheta)), ::nbl::hlsl::is_same_v, typename T::vector_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((fresnel.getOrientedEtaRcps()), ::nbl::hlsl::is_same_v, OrientedEtaRcps)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((fresnel.getOrientedEtaRcps()), ::nbl::hlsl::is_same_v, OrientedEtaRcps)) ); #undef cosTheta #undef fresnel @@ -341,6 +343,9 @@ struct Schlick { using scalar_type = typename vector_traits::scalar_type; using vector_type = T; + using eta_type = vector_type; + + NBL_CONSTEXPR_STATIC_INLINE bool ReturnsMonochrome = vector_traits::Dimension == 1; static Schlick create(NBL_CONST_REF_ARG(T) F0) { @@ -349,7 +354,7 @@ struct Schlick return retval; } - T operator()(const scalar_type clampedCosTheta) + T operator()(const scalar_type clampedCosTheta) NBL_CONST_MEMBER_FUNC { assert(clampedCosTheta >= scalar_type(0.0)); assert(hlsl::all(hlsl::promote(0.02) < F0 && F0 <= hlsl::promote(1.0))); @@ -357,11 +362,11 @@ struct Schlick return F0 + (1.0 - F0) * x*x*x*x*x; } - OrientedEtaRcps getOrientedEtaRcps() NBL_CONST_MEMBER_FUNC + OrientedEtaRcps getOrientedEtaRcps() NBL_CONST_MEMBER_FUNC { - const T sqrtF0 = hlsl::sqrt(F0); - OrientedEtaRcps rcpEta; - rcpEta.value = (hlsl::promote(1.0) - sqrtF0) / (hlsl::promote(1.0) + sqrtF0); + const eta_type sqrtF0 = hlsl::sqrt(F0); + OrientedEtaRcps rcpEta; + rcpEta.value = (hlsl::promote(1.0) - sqrtF0) / (hlsl::promote(1.0) + sqrtF0); rcpEta.value2 = rcpEta.value * rcpEta.value; return rcpEta; } @@ -374,6 +379,9 @@ struct Conductor { using scalar_type = typename vector_traits::scalar_type; using vector_type = T; + using eta_type = vector_type; + + NBL_CONSTEXPR_STATIC_INLINE bool ReturnsMonochrome = vector_traits::Dimension == 1; static Conductor create(NBL_CONST_REF_ARG(T) eta, NBL_CONST_REF_ARG(T) etak) { @@ -393,27 +401,33 @@ struct Conductor return retval; } - T operator()(const scalar_type clampedCosTheta) + static void __polarized(const T orientedEta, const T etaLen2, const T clampedCosTheta, NBL_REF_ARG(T) Rp, NBL_REF_ARG(T) Rs) { - const scalar_type cosTheta2 = clampedCosTheta * clampedCosTheta; - //const float sinTheta2 = 1.0 - cosTheta2; + const T cosTheta_2 = clampedCosTheta * clampedCosTheta; + const T eta = orientedEta; assert(hlsl::all(etaLen2 > hlsl::promote(hlsl::exp2(-numeric_limits::digits)))); - const T etaCosTwice = eta * clampedCosTheta * 2.0f; + T t1 = etaLen2 * cosTheta_2; + const T etaCosTwice = eta * clampedCosTheta * hlsl::promote(2.0); - const T rs_common = etaLen2 + (T)(cosTheta2); - const T rs2 = (rs_common - etaCosTwice) / (rs_common + etaCosTwice); + const T rs_common = etaLen2 + cosTheta_2; + Rs = (rs_common - etaCosTwice) / (rs_common + etaCosTwice); + const T rp_common = t1 + hlsl::promote(1.0); + Rp = (rp_common - etaCosTwice) / (rp_common + etaCosTwice); + } - const T rp_common = etaLen2 * cosTheta2 + (T)(1.0); - const T rp2 = (rp_common - etaCosTwice) / (rp_common + etaCosTwice); + T operator()(const scalar_type clampedCosTheta) NBL_CONST_MEMBER_FUNC + { + T rs2, rp2; + __polarized(eta, etaLen2, hlsl::promote(clampedCosTheta), rp2, rs2); - return (rs2 + rp2) * 0.5f; + return (rs2 + rp2) * hlsl::promote(0.5); } - OrientedEtaRcps getOrientedEtaRcps() NBL_CONST_MEMBER_FUNC + OrientedEtaRcps getOrientedEtaRcps() NBL_CONST_MEMBER_FUNC { - OrientedEtaRcps rcpEta; - rcpEta.value = hlsl::promote(1.0) / eta; + OrientedEtaRcps rcpEta; + rcpEta.value = hlsl::promote(1.0) / eta; rcpEta.value2 = rcpEta.value * rcpEta.value; return rcpEta; } @@ -428,6 +442,9 @@ struct Dielectric { using scalar_type = typename vector_traits::scalar_type; using vector_type = T; + using eta_type = vector_type; + + NBL_CONSTEXPR_STATIC_INLINE bool ReturnsMonochrome = vector_traits::Dimension == 1; static Dielectric create(NBL_CONST_REF_ARG(OrientedEtas) orientedEta) { @@ -437,21 +454,29 @@ struct Dielectric return retval; } - static T __call(NBL_CONST_REF_ARG(T) orientedEta2, const scalar_type clampedCosTheta) + static void __polarized(const T orientedEta2, const T cosTheta, NBL_REF_ARG(T) Rp, NBL_REF_ARG(T) Rs) { - const scalar_type sinTheta2 = scalar_type(1.0) - clampedCosTheta * clampedCosTheta; + const T sinTheta2 = hlsl::promote(1.0) - cosTheta * cosTheta; // the max() clamping can handle TIR when orientedEta2<1.0 - const T t0 = hlsl::sqrt(hlsl::max(orientedEta2 - sinTheta2, hlsl::promote(0.0))); - const T rs = (hlsl::promote(clampedCosTheta) - t0) / (hlsl::promote(clampedCosTheta) + t0); + T t0 = hlsl::sqrt(hlsl::max(orientedEta2 - sinTheta2, hlsl::promote(0.0))); + T t2 = orientedEta2 * cosTheta; + + T rp = (t0 - t2) / (t0 + t2); + Rp = rp * rp; + T rs = (cosTheta - t0) / (cosTheta + t0); + Rs = rs * rs; + } - const T t2 = orientedEta2 * clampedCosTheta; - const T rp = (t0 - t2) / (t0 + t2); + static T __call(NBL_CONST_REF_ARG(T) orientedEta2, const scalar_type clampedCosTheta) + { + T rs2, rp2; + __polarized(orientedEta2, hlsl::promote(clampedCosTheta), rp2, rs2); - return (rs * rs + rp * rp) * scalar_type(0.5); + return (rs2 + rp2) * hlsl::promote(0.5); } - T operator()(const scalar_type clampedCosTheta) + T operator()(const scalar_type clampedCosTheta) NBL_CONST_MEMBER_FUNC { return __call(orientedEta2, clampedCosTheta); } @@ -471,6 +496,240 @@ struct Dielectric T orientedEta2; }; +// adapted from https://belcour.github.io/blog/research/publication/2017/05/01/brdf-thin-film.html +template +struct Iridescent; + +namespace impl +{ +template) +struct iridescent_helper +{ + using scalar_type = typename vector_traits::scalar_type; + using vector_type = T; + + // returns reflectance R = (rp, rs), phi is the phase shift for each plane of polarization (p,s) + static void phase_shift(const vector_type orientedEta, const vector_type orientedEtak, const vector_type cosTheta, NBL_REF_ARG(vector_type) phiS, NBL_REF_ARG(vector_type) phiP) + { + vector_type cosTheta_2 = cosTheta * cosTheta; + vector_type sinTheta2 = hlsl::promote(1.0) - cosTheta_2; + const vector_type eta2 = orientedEta*orientedEta; + const vector_type etak2 = orientedEtak*orientedEtak; + + vector_type z = eta2 - etak2 - sinTheta2; + vector_type w = hlsl::sqrt(z * z + scalar_type(4.0) * eta2 * eta2 * etak2); + vector_type a2 = (z + w) * hlsl::promote(0.5); + vector_type b2 = (w - z) * hlsl::promote(0.5); + vector_type b = hlsl::sqrt(b2); + + const vector_type t0 = eta2 + etak2; + const vector_type t1 = t0 * cosTheta_2; + + phiS = hlsl::atan2(hlsl::promote(2.0) * b * cosTheta, a2 + b2 - cosTheta_2); + phiP = hlsl::atan2(hlsl::promote(2.0) * eta2 * cosTheta * (hlsl::promote(2.0) * orientedEtak * hlsl::sqrt(a2) - etak2 * b), t1 - a2 + b2); + } + + // Evaluation XYZ sensitivity curves in Fourier space + static vector_type evalSensitivity(vector_type opd, vector_type shift) + { + // Use Gaussian fits, given by 3 parameters: val, pos and var + vector_type phase = scalar_type(2.0) * numbers::pi * opd * scalar_type(1.0e-9); + vector_type phase2 = phase * phase; + vector_type val = vector_type(5.4856e-13, 4.4201e-13, 5.2481e-13); + vector_type pos = vector_type(1.6810e+06, 1.7953e+06, 2.2084e+06); + vector_type var = vector_type(4.3278e+09, 9.3046e+09, 6.6121e+09); + vector_type xyz = val * hlsl::sqrt(scalar_type(2.0) * numbers::pi * var) * hlsl::cos(pos * phase + shift) * hlsl::exp(-var * phase2); + xyz.x = xyz.x + scalar_type(9.7470e-14) * hlsl::sqrt(scalar_type(2.0) * numbers::pi * scalar_type(4.5282e+09)) * hlsl::cos(scalar_type(2.2399e+06) * phase[0] + shift[0]) * hlsl::exp(scalar_type(-4.5282e+09) * phase2[0]); + return xyz / scalar_type(1.0685e-7); + } + + template + static T __call(NBL_CONST_REF_ARG(Params) params, const scalar_type clampedCosTheta) + { + const vector_type wavelengths = vector_type(colorspace::scRGB::wavelength_R, colorspace::scRGB::wavelength_G, colorspace::scRGB::wavelength_B); + + const vector_type eta12 = params.getEta12(); + const vector_type eta23 = params.getEta23(); + const vector_type etak23 = params.getEtak23(); + const scalar_type cosTheta_1 = clampedCosTheta; + vector_type cosTheta_2; + + vector_type R12p, R23p, R12s, R23s; + const vector_type scale = scalar_type(1.0)/eta12; + const vector_type cosTheta2_2 = hlsl::promote(1.0) - hlsl::promote(1.0-cosTheta_1*cosTheta_1) * scale * scale; + + cosTheta_2 = hlsl::sqrt(hlsl::max(cosTheta2_2, hlsl::promote(0.0))); + Dielectric::__polarized(eta12, hlsl::promote(cosTheta_1), R12p, R12s); + + // Reflected part by the base + // if kappa==0, base material is dielectric + NBL_IF_CONSTEXPR(SupportsTransmission) + Dielectric::__polarized(eta23 * eta23, cosTheta_2, R23p, R23s); + else + { + vector_type etaLen2 = eta23 * eta23 + etak23 * etak23; + Conductor::__polarized(eta23, etaLen2, cosTheta_2, R23p, R23s); + } + + // Check for total internal reflection + R12s = hlsl::mix(R12s, hlsl::promote(1.0), cosTheta2_2 <= hlsl::promote(0.0)); + R12p = hlsl::mix(R12p, hlsl::promote(1.0), cosTheta2_2 <= hlsl::promote(0.0)); + + R23s = hlsl::mix(R23s, hlsl::promote(0.0), cosTheta2_2 <= hlsl::promote(0.0)); + R23p = hlsl::mix(R23p, hlsl::promote(0.0), cosTheta2_2 <= hlsl::promote(0.0)); + + // Compute the transmission coefficients + vector_type T121p = hlsl::promote(1.0) - R12p; + vector_type T121s = hlsl::promote(1.0) - R12s; + + // Optical Path Difference + const vector_type D = hlsl::promote(2.0 * params.getDinc()) * params.getThinFilmIor() * cosTheta_2; + const vector_type Dphi = hlsl::promote(2.0 * numbers::pi) * D / wavelengths; + + vector_type phi21p, phi21s, phi23p, phi23s, r123s, r123p, Rs; + vector_type I = hlsl::promote(0.0); + + // Evaluate the phase shift + phase_shift(eta12, hlsl::promote(0.0), hlsl::promote(cosTheta_1), phi21p, phi21s); + phase_shift(eta23, etak23, cosTheta_2, phi23p, phi23s); + phi21p = hlsl::promote(numbers::pi) - phi21p; + phi21s = hlsl::promote(numbers::pi) - phi21s; + + r123p = hlsl::sqrt(R12p*R23p); + r123s = hlsl::sqrt(R12s*R23s); + + vector_type C0, Cm, Sm; + const vector_type S0 = hlsl::promote(1.0); + + // Iridescence term using spectral antialiasing + // Reflectance term for m=0 (DC term amplitude) + Rs = (T121p*T121p*R23p) / (hlsl::promote(1.0) - R12p*R23p); + C0 = R12p + Rs; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121p; + NBL_UNROLL for (int m=1; m<=2; ++m) + { + Cm *= r123p; + Sm = hlsl::promote(2.0) * evalSensitivity(hlsl::promote(m)*D, hlsl::promote(m)*(phi23p+phi21p)); + I += Cm*Sm; + } + + // Reflectance term for m=0 (DC term amplitude) + Rs = (T121s*T121s*R23s) / (hlsl::promote(1.0) - R12s*R23s); + C0 = R12s + Rs; + I += C0 * S0; + + // Reflectance term for m>0 (pairs of diracs) + Cm = Rs - T121s; + NBL_UNROLL for (int m=1; m<=2; ++m) + { + Cm *= r123s; + Sm = hlsl::promote(2.0) * evalSensitivity(hlsl::promote(m)*D, hlsl::promote(m) *(phi23s+phi21s)); + I += Cm*Sm; + } + + return hlsl::max(colorspace::scRGB::FromXYZ(I), hlsl::promote(0.0)) * hlsl::promote(0.5); + } +}; + +template) +struct iridescent_base +{ + using scalar_type = typename vector_traits::scalar_type; + using vector_type = T; + + scalar_type getDinc() NBL_CONST_MEMBER_FUNC { return Dinc; } + vector_type getThinFilmIor() NBL_CONST_MEMBER_FUNC { return thinFilmIor; } + vector_type getEta12() NBL_CONST_MEMBER_FUNC { return eta12; } + vector_type getEta23() NBL_CONST_MEMBER_FUNC { return eta23; } + vector_type getEtak23() NBL_CONST_MEMBER_FUNC + { + NBL_IF_CONSTEXPR(SupportsTransmission) + return hlsl::promote(0.0); + else + return etak23; + } + + scalar_type Dinc; // thickness of thin film in nanometers, rec. 100-25000nm + vector_type thinFilmIor; + vector_type eta12; // outside (usually air 1.0) -> thin-film IOR + vector_type eta23; // thin-film -> base material IOR + vector_type etak23; // thin-film -> complex component, k==0 makes dielectric +}; +} + +template +NBL_PARTIAL_REQ_TOP(concepts::FloatingPointLikeVectorial) +struct Iridescent) > +{ + using this_t = Iridescent; + using scalar_type = typename vector_traits::scalar_type; + using vector_type = T; // assert dim==3? + using eta_type = vector_type; + using base_type = impl::iridescent_base; + + NBL_CONSTEXPR_STATIC_INLINE bool ReturnsMonochrome = vector_traits::Dimension == 1; + + T operator()(const scalar_type clampedCosTheta) NBL_CONST_MEMBER_FUNC + { + return impl::iridescent_helper::template __call(__base, clampedCosTheta); + } + + OrientedEtaRcps getOrientedEtaRcps() NBL_CONST_MEMBER_FUNC + { + OrientedEtaRcps rcpEta; + rcpEta.value = hlsl::promote(1.0) / __base.eta23; + rcpEta.value2 = rcpEta.value * rcpEta.value; + return rcpEta; + } + + base_type __base; +}; + +template +NBL_PARTIAL_REQ_TOP(concepts::FloatingPointLikeVectorial) +struct Iridescent) > +{ + using this_t = Iridescent; + using scalar_type = typename vector_traits::scalar_type; + using vector_type = T; // assert dim==3? + using eta_type = vector; + using base_type = impl::iridescent_base; + + NBL_CONSTEXPR_STATIC_INLINE bool ReturnsMonochrome = vector_traits::Dimension == 1; + + T operator()(const scalar_type clampedCosTheta) NBL_CONST_MEMBER_FUNC + { + return impl::iridescent_helper::template __call(__base, clampedCosTheta); + } + + scalar_type getRefractionOrientedEta() NBL_CONST_MEMBER_FUNC { return __base.eta23[0]; } + OrientedEtaRcps getOrientedEtaRcps() NBL_CONST_MEMBER_FUNC + { + OrientedEtaRcps rcpEta; + rcpEta.value = hlsl::promote(1.0) / __base.eta23[0]; + rcpEta.value2 = rcpEta.value * rcpEta.value; + return rcpEta; + } + + this_t getReorientedFresnel(const scalar_type NdotI) NBL_CONST_MEMBER_FUNC + { + const bool flip = NdotI < scalar_type(0.0); + this_t orientedFresnel; + orientedFresnel.__base.Dinc = __base.Dinc; + orientedFresnel.__base.thinFilmIor = __base.thinFilmIor; + orientedFresnel.__base.eta12 = hlsl::mix(__base.eta12, hlsl::promote(1.0)/__base.eta12, flip); + orientedFresnel.__base.eta23 = hlsl::mix(__base.eta23, hlsl::promote(1.0)/__base.eta23, flip); + orientedFresnel.__base.etak23 = hlsl::promote(0.0); + return orientedFresnel; + } + + base_type __base; +}; + + // gets the sum of all R, T R T, T R^3 T, T R^5 T, ... paths template || concepts::FloatingPointLikeVectorial) T thinDielectricInfiniteScatter(const T singleInterfaceReflectance) diff --git a/include/nbl/builtin/hlsl/bxdf/ndf.hlsl b/include/nbl/builtin/hlsl/bxdf/ndf.hlsl index da1dc5e630..f882374f99 100644 --- a/include/nbl/builtin/hlsl/bxdf/ndf.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/ndf.hlsl @@ -20,7 +20,7 @@ namespace ndf namespace dummy_impl { using sample_t = SLightSample >; -using interaction_t = surface_interactions::SAnisotropic > >; +using interaction_t = surface_interactions::SAnisotropic, vector > >; using cache_t = SAnisotropicMicrofacetCache >; } @@ -103,7 +103,6 @@ NBL_CONSTEXPR_STATIC_INLINE bool RequiredMicrofacetCache = IS_ANISO ? Anisotropi #define NBL_HLSL_NDF_CONSTEXPR_DECLS(ANISO,REFLECT_REFRACT) NBL_CONSTEXPR_STATIC_INLINE bool IsAnisotropic = ANISO;\ NBL_CONSTEXPR_STATIC_INLINE MicrofacetTransformTypes SupportedPaths = REFLECT_REFRACT;\ NBL_CONSTEXPR_STATIC_INLINE bool SupportsTransmission = REFLECT_REFRACT != MTT_REFLECT;\ -NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = SupportsTransmission ? BxDFClampMode::BCM_ABS : BxDFClampMode::BCM_NONE;\ NBL_HLSL_BXDF_ANISOTROPIC_COND_DECLS(IsAnisotropic);\ #define NBL_HLSL_NDF_TYPE_ALIASES(...) using this_t = BOOST_PP_REMOVE_PARENS(BOOST_PP_SEQ_ELEM(0, __VA_ARGS__));\ diff --git a/include/nbl/builtin/hlsl/bxdf/ndf/beckmann.hlsl b/include/nbl/builtin/hlsl/bxdf/ndf/beckmann.hlsl index f7d9f7c2f7..1406bc8d4f 100644 --- a/include/nbl/builtin/hlsl/bxdf/ndf/beckmann.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/ndf/beckmann.hlsl @@ -107,11 +107,12 @@ struct BeckmannCommon(numeric_limits::infinity); } - isInfinity = false; scalar_type NdotH2 = cache.getNdotH2(); scalar_type nom = exp2((NdotH2 - scalar_type(1.0)) / (log(2.0) * a2 * NdotH2)); scalar_type denom = a2 * NdotH2 * NdotH2; - return numbers::inv_pi * nom / denom; + scalar_type ndf = numbers::inv_pi * nom / denom; + isInfinity = hlsl::isinf(ndf); + return ndf; } scalar_type C2(scalar_type NdotX2) @@ -141,7 +142,9 @@ struct BeckmannCommon(-(cache.getTdotH2() / ax2 + cache.getBdotH2() / ay2) / NdotH2); scalar_type denom = a2 * NdotH2 * NdotH2; - return numbers::inv_pi * nom / denom; + scalar_type ndf = numbers::inv_pi * nom / denom; + isInfinity = hlsl::isinf(ndf); + return ndf; } scalar_type C2(scalar_type TdotX2, scalar_type BdotX2, scalar_type NdotX2) @@ -277,12 +280,7 @@ struct Beckmann quant_query_type quant_query; // only has members for refraction NBL_IF_CONSTEXPR(SupportsTransmission) { - quant_query.VdotHLdotH = cache.getVdotHLdotH(); - const scalar_type VdotH = cache.getVdotH(); - const scalar_type VdotH_etaLdotH = hlsl::mix(VdotH + orientedEta * cache.getLdotH(), - VdotH / orientedEta + cache.getLdotH(), - interaction.getPathOrigin() == PathOrigin::PO_SENSOR); - quant_query.neg_rcp2_refractionDenom = scalar_type(-1.0) / (VdotH_etaLdotH * VdotH_etaLdotH); + quant_query = quant_query_type::template create(interaction, cache, orientedEta); } return quant_query; } @@ -337,8 +335,15 @@ struct Beckmann quant_type DG1(NBL_CONST_REF_ARG(dg1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_REF_ARG(bool) isInfinity) { scalar_type D = query.getNdf(); - scalar_type dg1 = query.getNdf() / (scalar_type(1.0) + query.getLambdaV()); - isInfinity = D == bit_cast(numeric_limits::infinity); + isInfinity = hlsl::isinf(D); + if (isInfinity) + { + quant_type dmq; + dmq.microfacetMeasure = scalar_type(0.0); + dmq.projectedLightMeasure = scalar_type(0.0); + return dmq; + } + scalar_type dg1 = D / (scalar_type(1.0) + query.getLambdaV()); return createDualMeasureQuantity(dg1, interaction.getNdotV(BxDFClampMode::BCM_ABS), _sample.getNdotL(BxDFClampMode::BCM_ABS), quant_query); } diff --git a/include/nbl/builtin/hlsl/bxdf/ndf/ggx.hlsl b/include/nbl/builtin/hlsl/bxdf/ndf/ggx.hlsl index b27c892abe..0e2e9d1291 100644 --- a/include/nbl/builtin/hlsl/bxdf/ndf/ggx.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/ndf/ggx.hlsl @@ -84,8 +84,6 @@ struct GGXCommon) scalar_type D(NBL_CONST_REF_ARG(MicrofacetCache) cache, NBL_REF_ARG(bool) isInfinity) @@ -95,9 +93,10 @@ struct GGXCommon(numeric_limits::infinity); } - isInfinity = false; scalar_type denom = scalar_type(1.0) - one_minus_a2 * cache.getNdotH2(); - return a2 * numbers::inv_pi / (denom * denom); + scalar_type ndf = a2 * numbers::inv_pi / (denom * denom); + isInfinity = hlsl::isinf(ndf); + return ndf; } scalar_type devsh_part(scalar_type NdotX2) @@ -116,8 +115,6 @@ struct GGXCommon) scalar_type D(NBL_CONST_REF_ARG(MicrofacetCache) cache, NBL_REF_ARG(bool) isInfinity) { @@ -126,9 +123,10 @@ struct GGXCommon(numeric_limits::infinity); } - isInfinity = false; scalar_type denom = cache.getTdotH2() / ax2 + cache.getBdotH2() / ay2 + cache.getNdotH2(); - return numbers::inv_pi / (a2 * denom * denom); + scalar_type ndf = numbers::inv_pi / (a2 * denom * denom); + isInfinity = hlsl::isinf(ndf); + return ndf; } // TODO: potential idea for making GGX spin using covariance matrix of sorts: https://www.desmos.com/3d/weq2ginq9o @@ -220,12 +218,7 @@ struct GGX quant_query_type quant_query; // only has members for refraction NBL_IF_CONSTEXPR(SupportsTransmission) { - quant_query.VdotHLdotH = cache.getVdotHLdotH(); - const scalar_type VdotH = cache.getVdotH(); - const scalar_type VdotH_etaLdotH = hlsl::mix(VdotH + orientedEta * cache.getLdotH(), - VdotH / orientedEta + cache.getLdotH(), - interaction.getPathOrigin() == PathOrigin::PO_SENSOR); - quant_query.neg_rcp2_refractionDenom = scalar_type(-1.0) / (VdotH_etaLdotH * VdotH_etaLdotH); + quant_query = quant_query_type::template create(interaction, cache, orientedEta); } return quant_query; } @@ -235,7 +228,7 @@ struct GGX dg1_query_type dg1_query; bool dummy; dg1_query.ndf = __ndf_base.template D(cache, dummy); - scalar_type clampedNdotV = interaction.getNdotV(_clamp); + scalar_type clampedNdotV = interaction.getNdotV(BxDFClampMode::BCM_ABS); dg1_query.G1_over_2NdotV = G1_wo_numerator(clampedNdotV, __ndf_base.devsh_part(interaction.getNdotV2())); return dg1_query; } @@ -253,7 +246,7 @@ struct GGX dg1_query_type dg1_query; bool dummy; dg1_query.ndf = __ndf_base.template D(cache, dummy); - scalar_type clampedNdotV = interaction.getNdotV(_clamp); + scalar_type clampedNdotV = interaction.getNdotV(BxDFClampMode::BCM_ABS); dg1_query.G1_over_2NdotV = G1_wo_numerator(clampedNdotV, __ndf_base.devsh_part(interaction.getTdotV2(), interaction.getBdotV2(), interaction.getNdotV2())); return dg1_query; } @@ -282,10 +275,17 @@ struct GGX quant_type DG1(NBL_CONST_REF_ARG(dg1_query_type) query, NBL_CONST_REF_ARG(quant_query_type) quant_query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_REF_ARG(bool) isInfinity) { scalar_type D = query.getNdfwoNumerator(); - scalar_type dg1_over_2NdotV = query.getNdfwoNumerator() * query.getG1over2NdotV(); - isInfinity = D == bit_cast(numeric_limits::infinity); + isInfinity = hlsl::isinf(D); quant_type dmq; - dmq.microfacetMeasure = scalar_type(2.0) * interaction.getNdotV(_clamp) * dg1_over_2NdotV; + if (isInfinity) + { + dmq.microfacetMeasure = scalar_type(0.0); + dmq.projectedLightMeasure = scalar_type(0.0); + return dmq; + } + + scalar_type dg1_over_2NdotV = D * query.getG1over2NdotV(); + dmq.microfacetMeasure = scalar_type(2.0) * interaction.getNdotV(BxDFClampMode::BCM_ABS) * dg1_over_2NdotV; NBL_IF_CONSTEXPR(SupportsTransmission) { @@ -303,8 +303,8 @@ struct GGX template && RequiredInteraction && RequiredMicrofacetCache) scalar_type correlated_wo_numerator(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) { - scalar_type NdotV = interaction.getNdotV(_clamp); - scalar_type NdotL = _sample.getNdotL(_clamp); + scalar_type NdotV = interaction.getNdotV(BxDFClampMode::BCM_ABS); + scalar_type NdotL = _sample.getNdotL(BxDFClampMode::BCM_ABS); scalar_type devsh_v = query.getDevshV(); scalar_type devsh_l = query.getDevshL(); // without numerator, numerator is 2 * NdotV * NdotL, we factor out 4 * NdotV * NdotL, hence 0.5 @@ -327,7 +327,7 @@ struct GGX template && RequiredInteraction && RequiredMicrofacetCache) scalar_type correlated(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) { - return scalar_type(4.0) * interaction.getNdotV(_clamp) * _sample.getNdotL(_clamp) * correlated_wo_numerator(query, _sample, interaction, cache); + return scalar_type(4.0) * interaction.getNdotV(BxDFClampMode::BCM_ABS) * _sample.getNdotL(BxDFClampMode::BCM_ABS) * correlated_wo_numerator(query, _sample, interaction, cache); } template && RequiredInteraction && RequiredMicrofacetCache) @@ -364,8 +364,8 @@ struct GGX scalar_type G2_over_G1(NBL_CONST_REF_ARG(g2g1_query_type) query, NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache) { scalar_type G2_over_G1; - scalar_type NdotV = interaction.getNdotV(_clamp); - scalar_type NdotL = _sample.getNdotL(_clamp); + scalar_type NdotV = interaction.getNdotV(BxDFClampMode::BCM_ABS); + scalar_type NdotL = _sample.getNdotL(BxDFClampMode::BCM_ABS); scalar_type devsh_v = query.getDevshV(); scalar_type devsh_l = query.getDevshL(); if (cache.isTransmission()) diff --git a/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_to_light_transform.hlsl b/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_to_light_transform.hlsl index 58b8892ade..bfcb375ac8 100644 --- a/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_to_light_transform.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_to_light_transform.hlsl @@ -18,9 +18,9 @@ namespace ndf enum MicrofacetTransformTypes : uint16_t { - MTT_REFLECT = 0b01, - MTT_REFRACT = 0b10, - MTT_REFLECT_REFRACT = 0b11 + MTT_REFLECT = 0b01, + MTT_REFRACT = 0b10, + MTT_REFLECT_REFRACT = 0b11 }; namespace microfacet_transform_concepts @@ -43,25 +43,38 @@ NBL_CONCEPT_END( template struct DualMeasureQuantQuery { - using scalar_type = T; + using scalar_type = T; - // note in pbrt it's `abs(VdotH)*abs(LdotH)` - // we leverage the fact that under transmission the sign must always be negative and rest of the code already accounts for that - scalar_type getVdotHLdotH() NBL_CONST_MEMBER_FUNC { return VdotHLdotH; } - scalar_type getNeg_rcp2_refractionDenom() NBL_CONST_MEMBER_FUNC { return neg_rcp2_refractionDenom ; } + template + static DualMeasureQuantQuery create(NBL_CONST_REF_ARG(Interaction) interaction, NBL_CONST_REF_ARG(MicrofacetCache) cache, scalar_type orientedEta) + { + DualMeasureQuantQuery retval; + retval.VdotHLdotH = cache.getVdotHLdotH(); + const scalar_type VdotH = cache.getVdotH(); + const scalar_type VdotH_etaLdotH = hlsl::mix(VdotH + orientedEta * cache.getLdotH(), + VdotH / orientedEta + cache.getLdotH(), + interaction.getPathOrigin() == PathOrigin::PO_SENSOR); + retval.neg_rcp2_refractionDenom = scalar_type(-1.0) / (VdotH_etaLdotH * VdotH_etaLdotH); + return retval; + } - scalar_type VdotHLdotH; - scalar_type neg_rcp2_refractionDenom; + // note in pbrt it's `abs(VdotH)*abs(LdotH)` + // we leverage the fact that under transmission the sign must always be negative and rest of the code already accounts for that + scalar_type getVdotHLdotH() NBL_CONST_MEMBER_FUNC { return VdotHLdotH; } + scalar_type getNeg_rcp2_refractionDenom() NBL_CONST_MEMBER_FUNC { return neg_rcp2_refractionDenom ; } + + scalar_type VdotHLdotH; + scalar_type neg_rcp2_refractionDenom; }; template struct SDualMeasureQuant { - using value_type = T; - - T microfacetMeasure; - T projectedLightMeasure; + using value_type = T; + + T microfacetMeasure; + T projectedLightMeasure; }; namespace impl @@ -69,19 +82,19 @@ namespace impl template struct createDualMeasureQuantity_helper { - using scalar_type = typename vector_traits::scalar_type; + using scalar_type = typename vector_traits::scalar_type; - static SDualMeasureQuant __call(const T microfacetMeasure, scalar_type clampedNdotV, scalar_type clampedNdotL, scalar_type VdotHLdotH, scalar_type neg_rcp2_refractionDenom) - { - assert(clampedNdotV >= scalar_type(0.0) && clampedNdotL >= scalar_type(0.0)); - SDualMeasureQuant retval; - retval.microfacetMeasure = microfacetMeasure; - // do constexpr booleans first so optimizer picks up this and short circuits - const bool transmitted = reflect_refract==MTT_REFRACT || (reflect_refract!=MTT_REFLECT && VdotHLdotH < scalar_type(0.0)); - retval.projectedLightMeasure = microfacetMeasure * hlsl::mix(scalar_type(0.25),VdotHLdotH*neg_rcp2_refractionDenom,transmitted)/clampedNdotV; - // VdotHLdotH is negative under transmission, so thats denominator is negative - return retval; - } + static SDualMeasureQuant __call(const T microfacetMeasure, scalar_type clampedNdotV, scalar_type clampedNdotL, scalar_type VdotHLdotH, scalar_type neg_rcp2_refractionDenom) + { + assert(clampedNdotV >= scalar_type(0.0) && clampedNdotL >= scalar_type(0.0)); + SDualMeasureQuant retval; + retval.microfacetMeasure = microfacetMeasure; + // do constexpr booleans first so optimizer picks up this and short circuits + const bool transmitted = reflect_refract==MTT_REFRACT || (reflect_refract!=MTT_REFLECT && VdotHLdotH < scalar_type(0.0)); + retval.projectedLightMeasure = microfacetMeasure * hlsl::mix(scalar_type(0.25),VdotHLdotH*neg_rcp2_refractionDenom,transmitted)/clampedNdotV; + // VdotHLdotH is negative under transmission, so thats denominator is negative + return retval; + } }; } @@ -89,18 +102,18 @@ struct createDualMeasureQuantity_helper template SDualMeasureQuant createDualMeasureQuantity(const T specialMeasure, typename vector_traits::scalar_type clampedNdotV, typename vector_traits::scalar_type clampedNdotL) { - typename vector_traits::scalar_type dummy; - return impl::createDualMeasureQuantity_helper::__call(specialMeasure,clampedNdotV,clampedNdotL,dummy,dummy); + typename vector_traits::scalar_type dummy; + return impl::createDualMeasureQuantity_helper::__call(specialMeasure,clampedNdotV,clampedNdotL,dummy,dummy); } template SDualMeasureQuant createDualMeasureQuantity(const T specialMeasure, typename vector_traits::scalar_type clampedNdotV, typename vector_traits::scalar_type clampedNdotL, typename vector_traits::scalar_type VdotHLdotH, typename vector_traits::scalar_type neg_rcp2_refractionDenom) { - return impl::createDualMeasureQuantity_helper::__call(specialMeasure,clampedNdotV,clampedNdotL,VdotHLdotH,neg_rcp2_refractionDenom); + return impl::createDualMeasureQuantity_helper::__call(specialMeasure,clampedNdotV,clampedNdotL,VdotHLdotH,neg_rcp2_refractionDenom); } template SDualMeasureQuant createDualMeasureQuantity(const T specialMeasure, typename vector_traits::scalar_type clampedNdotV, typename vector_traits::scalar_type clampedNdotL, NBL_CONST_REF_ARG(Query) query) { - return impl::createDualMeasureQuantity_helper::__call(specialMeasure,clampedNdotV,clampedNdotL,query.getVdotHLdotH(),query.getNeg_rcp2_refractionDenom()); + return impl::createDualMeasureQuantity_helper::__call(specialMeasure,clampedNdotV,clampedNdotL,query.getVdotHLdotH(),query.getNeg_rcp2_refractionDenom()); } } diff --git a/include/nbl/builtin/hlsl/bxdf/reflection.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection.hlsl index dcc7d45998..c5d4b019c8 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection.hlsl @@ -9,6 +9,7 @@ #include "nbl/builtin/hlsl/bxdf/reflection/delta_distribution.hlsl" #include "nbl/builtin/hlsl/bxdf/reflection/beckmann.hlsl" #include "nbl/builtin/hlsl/bxdf/reflection/ggx.hlsl" +#include "nbl/builtin/hlsl/bxdf/reflection/iridescent.hlsl" namespace nbl { diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/iridescent.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/iridescent.hlsl new file mode 100644 index 0000000000..07762d1298 --- /dev/null +++ b/include/nbl/builtin/hlsl/bxdf/reflection/iridescent.hlsl @@ -0,0 +1,36 @@ +// Copyright (C) 2018-2025 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h +#ifndef _NBL_BUILTIN_HLSL_BXDF_REFLECTION_IRIDESCENT_INCLUDED_ +#define _NBL_BUILTIN_HLSL_BXDF_REFLECTION_IRIDESCENT_INCLUDED_ + +#include "nbl/builtin/hlsl/bxdf/reflection/ggx.hlsl" + +namespace nbl +{ +namespace hlsl +{ +namespace bxdf +{ +namespace reflection +{ + +template +using SIridescent = SCookTorrance, fresnel::Iridescent >; + +} + +template +struct traits > +{ + NBL_CONSTEXPR_STATIC_INLINE BxDFType type = BT_BRDF; + NBL_CONSTEXPR_STATIC_INLINE bool IsMicrofacet = true; + NBL_CONSTEXPR_STATIC_INLINE bool clampNdotV = true; + NBL_CONSTEXPR_STATIC_INLINE bool clampNdotL = true; +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/bxdf/transmission.hlsl b/include/nbl/builtin/hlsl/bxdf/transmission.hlsl index 5391705a8c..b5b6e101c1 100644 --- a/include/nbl/builtin/hlsl/bxdf/transmission.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/transmission.hlsl @@ -10,6 +10,7 @@ #include "nbl/builtin/hlsl/bxdf/transmission/delta_distribution.hlsl" #include "nbl/builtin/hlsl/bxdf/transmission/beckmann.hlsl" #include "nbl/builtin/hlsl/bxdf/transmission/ggx.hlsl" +#include "nbl/builtin/hlsl/bxdf/transmission/iridescent.hlsl" namespace nbl { diff --git a/include/nbl/builtin/hlsl/bxdf/transmission/iridescent.hlsl b/include/nbl/builtin/hlsl/bxdf/transmission/iridescent.hlsl new file mode 100644 index 0000000000..2e7aa0e56e --- /dev/null +++ b/include/nbl/builtin/hlsl/bxdf/transmission/iridescent.hlsl @@ -0,0 +1,36 @@ +// Copyright (C) 2018-2025 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h +#ifndef _NBL_BUILTIN_HLSL_BXDF_TRANSMISSION_IRIDESCENT_INCLUDED_ +#define _NBL_BUILTIN_HLSL_BXDF_TRANSMISSION_IRIDESCENT_INCLUDED_ + +#include "nbl/builtin/hlsl/bxdf/transmission/ggx.hlsl" + +namespace nbl +{ +namespace hlsl +{ +namespace bxdf +{ +namespace transmission +{ + +template +using SIridescent = SCookTorrance, fresnel::Iridescent >; + +} + +template +struct traits > +{ + NBL_CONSTEXPR_STATIC_INLINE BxDFType type = BT_BSDF; + NBL_CONSTEXPR_STATIC_INLINE bool IsMicrofacet = true; + NBL_CONSTEXPR_STATIC_INLINE bool clampNdotV = true; + NBL_CONSTEXPR_STATIC_INLINE bool clampNdotL = true; +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/bxdf/transmission/smooth_dielectric.hlsl b/include/nbl/builtin/hlsl/bxdf/transmission/smooth_dielectric.hlsl index bc95c03c91..17400adfe2 100644 --- a/include/nbl/builtin/hlsl/bxdf/transmission/smooth_dielectric.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/transmission/smooth_dielectric.hlsl @@ -121,7 +121,7 @@ struct SThinSmoothDielectric const spectral_type reflectance = fresnel::thinDielectricInfiniteScatter(fresnel(interaction.getNdotV(_clamp))); // we are only allowed one choice for the entire ray, so make the probability a weighted sum - const scalar_type reflectionProb = nbl::hlsl::dot(reflectance, luminosityContributionHint); + const scalar_type reflectionProb = nbl::hlsl::dot(reflectance, interaction.getLuminosityContributionHint()); scalar_type rcpChoiceProb; scalar_type z = u.z; @@ -161,7 +161,7 @@ struct SThinSmoothDielectric const spectral_type reflectance = fresnel::thinDielectricInfiniteScatter(fresnel(interaction.getNdotV(_clamp))); const spectral_type sampleValue = hlsl::mix(reflectance, hlsl::promote(1.0) - reflectance, transmitted); - const scalar_type sampleProb = nbl::hlsl::dot(sampleValue,luminosityContributionHint); + const scalar_type sampleProb = nbl::hlsl::dot(sampleValue,interaction.getLuminosityContributionHint()); const scalar_type _pdf = bit_cast(numeric_limits::infinity); return quotient_pdf_type::create(sampleValue / sampleProb, _pdf); @@ -172,7 +172,6 @@ struct SThinSmoothDielectric } fresnel::Dielectric fresnel; - spectral_type luminosityContributionHint; }; } diff --git a/include/nbl/builtin/hlsl/colorspace.hlsl b/include/nbl/builtin/hlsl/colorspace.hlsl new file mode 100644 index 0000000000..0c56f1e21f --- /dev/null +++ b/include/nbl/builtin/hlsl/colorspace.hlsl @@ -0,0 +1,169 @@ +// Copyright (C) 2018-2025 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _NBL_BUILTIN_HLSL_COLOR_SPACE_COLORSPACE_INCLUDED_ +#define _NBL_BUILTIN_HLSL_COLOR_SPACE_COLORSPACE_INCLUDED_ + +#include +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace colorspace +{ + +struct scRGB +{ + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_R = 611.4f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_G = 549.1f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_B = 464.2f; + + static float32_t3x3 FromXYZ() + { + return decode::XYZtoscRGB; + } + static float32_t3 FromXYZ(float32_t3 val) { return hlsl::mul(decode::XYZtoscRGB, val); } + + static float32_t3x3 ToXYZ() + { + return scRGBtoXYZ; + } + static float32_t3 ToXYZ(float32_t3 val) { return hlsl::mul(scRGBtoXYZ, val); } +}; + +struct sRGB : scRGB {}; +struct BT709 : scRGB {}; + +struct Display_P3 +{ + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_R = 614.9f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_G = 544.2f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_B = 464.2f; + + static float32_t3x3 FromXYZ() + { + return decode::XYZtoDisplay_P3; + } + static float32_t3 FromXYZ(float32_t3 val) { return hlsl::mul(decode::XYZtoDisplay_P3, val); } + + static float32_t3x3 ToXYZ() + { + return Display_P3toXYZ; + } + static float32_t3 ToXYZ(float32_t3 val) { return hlsl::mul(Display_P3toXYZ, val); } +}; + +struct DCI_P3 +{ + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_R = 614.9f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_G = 544.2f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_B = 464.2f; + + static float32_t3x3 FromXYZ() + { + return decode::XYZtoDCI_P3; + } + static float32_t3 FromXYZ(float32_t3 val) { return hlsl::mul(decode::XYZtoDCI_P3, val); } + + static float32_t3x3 ToXYZ() + { + return DCI_P3toXYZ; + } + static float32_t3 ToXYZ(float32_t3 val) { return hlsl::mul(DCI_P3toXYZ, val); } +}; + +struct BT2020 +{ + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_R = 630.0f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_G = 532.0f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_B = 467.0f; + + static float32_t3x3 FromXYZ() + { + return decode::XYZtoBT2020; + } + static float32_t3 FromXYZ(float32_t3 val) { return hlsl::mul(decode::XYZtoBT2020, val); } + + static float32_t3x3 ToXYZ() + { + return BT2020toXYZ; + } + static float32_t3 ToXYZ(float32_t3 val) { return hlsl::mul(BT2020toXYZ, val); } +}; + +struct HDR10_ST2084 : BT2020 {}; +struct DOLBYIVISION : BT2020 {}; +struct HDR10_HLG : BT2020 {}; + +struct AdobeRGB +{ + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_R = 611.4f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_G = 534.7f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_B = 464.2f; + + static float32_t3x3 FromXYZ() + { + return decode::XYZtoAdobeRGB; + } + static float32_t3 FromXYZ(float32_t3 val) { return hlsl::mul(decode::XYZtoAdobeRGB, val); } + + static float32_t3x3 ToXYZ() + { + return AdobeRGBtoXYZ; + } + static float32_t3 ToXYZ(float32_t3 val) { return hlsl::mul(AdobeRGBtoXYZ, val); } +}; + +struct ACES2065_1 +{ + // AP0 primaries (approximate) + // probably not appropriate to use since AP0 space is larger than CIE + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_R = 700.0f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_G = 520.0f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_B = 472.5f; + + static float32_t3x3 FromXYZ() + { + return decode::XYZtoACES2065_1; + } + static float32_t3 FromXYZ(float32_t3 val) { return hlsl::mul(decode::XYZtoACES2065_1, val); } + + static float32_t3x3 ToXYZ() + { + return ACES2065_1toXYZ; + } + static float32_t3 ToXYZ(float32_t3 val) { return hlsl::mul(ACES2065_1toXYZ, val); } +}; + +struct ACEScc +{ + // AP1 primaries (approximate) + // probably not appropriate to use since AP1 space is larger than CIE + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_R = 630.0f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_G = 533.0f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_B = 467.0f; + + static float32_t3x3 FromXYZ() + { + return decode::XYZtoACEScc; + } + static float32_t3 FromXYZ(float32_t3 val) { return hlsl::mul(decode::XYZtoACEScc, val); } + + static float32_t3x3 ToXYZ() + { + return ACEScctoXYZ; + } + static float32_t3 ToXYZ(float32_t3 val) { return hlsl::mul(ACEScctoXYZ, val); } +}; + +struct ACEScct : ACEScc {}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/complex.hlsl b/include/nbl/builtin/hlsl/complex.hlsl index f61b707865..da04c49b51 100644 --- a/include/nbl/builtin/hlsl/complex.hlsl +++ b/include/nbl/builtin/hlsl/complex.hlsl @@ -24,7 +24,7 @@ template struct complex_t : public std::complex { using base_t = std::complex; - complex_t(const Scalar real, const Scalar imag) : base_t(real, imag) {} + complex_t(const Scalar real = Scalar(), const Scalar imag = Scalar()) : base_t(real, imag) {} static complex_t create(const Scalar real, const Scalar imag) { complex_t retVal(real, imag); diff --git a/include/nbl/builtin/hlsl/limits.hlsl b/include/nbl/builtin/hlsl/limits.hlsl index ebc6f931e1..fa9edc3bde 100644 --- a/include/nbl/builtin/hlsl/limits.hlsl +++ b/include/nbl/builtin/hlsl/limits.hlsl @@ -146,7 +146,7 @@ struct num_base : type_identity // (TODO) think about what this means for HLSL // identifies floating-point types that can represent the special value "quiet not-a-number" (NaN) - NBL_CONSTEXPR_STATIC_INLINE bool has_quiet_NaN = !is_integer; + NBL_CONSTEXPR_STATIC_INLINE bool has_quiet_NaN = !is_integer; // identifies floating-point types that can represent the special value "signaling not-a-number" (NaN) NBL_CONSTEXPR_STATIC_INLINE bool has_signaling_NaN = !is_integer; // identifies the denormalization style used by the floating-point type diff --git a/include/nbl/builtin/hlsl/math/angle_adding.hlsl b/include/nbl/builtin/hlsl/math/angle_adding.hlsl index 8a89840573..e2064165f3 100644 --- a/include/nbl/builtin/hlsl/math/angle_adding.hlsl +++ b/include/nbl/builtin/hlsl/math/angle_adding.hlsl @@ -21,53 +21,51 @@ struct sincos_accumulator { using this_t = sincos_accumulator; - static this_t create() + static this_t create(T cosA) { - this_t retval; - retval.runningSum = complex_t::create(T(1.0), T(0.0)); - return retval; + return create(cosA, sqrt(T(1.0) - cosA * cosA)); } - static this_t create(T cosA) + static this_t create(T cosA, T sinA) { + assert(sinA >= T(0.0)); this_t retval; - retval.runningSum = complex_t::create(cosA, T(0)); + retval.runningSum = complex_t::create(cosA, sinA); + retval.wraparound = hlsl::mix(T(0.0), T(1.0), cosA == T(-1.0)); return retval; } - void addCosine(T cosA, T biasA) + // This utility expects that all input angles being added are [0,PI], i.e. runningSum.y and sinA are always positive + // We make use of sine and cosine angle sum formulas to accumulate a running sum of angles + // and any "overflow" (when sum goes over PI) is stored in wraparound + // This way, we can accumulate the sines and cosines of a set of angles, and only have to do one acos() call to retrieve the final sum + void addAngle(T cosA, T sinA) { - const T bias = biasA + runningSum.imag(); const T a = cosA; - const T b = runningSum.real(); - const bool reverse = abs(min(a, b)) > max(a, b); - const T c = a * b - sqrt((T(1.0) - a * a) * (T(1.0) - b * b)); + const T cosB = runningSum.real(); + const T sinB = runningSum.imag(); + const bool overflow = abs(min(a, cosB)) > max(a, cosB); + const T c = a * cosB - sinA * sinB; + const T d = sinA * cosB + a * sinB; - runningSum.real(ieee754::flipSign(c, reverse)); - runningSum.imag(hlsl::mix(bias, bias + numbers::pi, reverse)); + runningSum.real(ieee754::flipSign(c, overflow)); + runningSum.imag(ieee754::flipSign(d, overflow)); + + if (overflow) + wraparound++; } void addCosine(T cosA) { - addCosine(cosA, T(0.0)); + addAngle(cosA, sqrt(T(1.0) - cosA * cosA)); } T getSumofArccos() { - return acos(runningSum.real()) + runningSum.imag(); - } - - static T getArccosSumofABC_minus_PI(T cosA, T cosB, T cosC, T sinA, T sinB, T sinC) - { - const bool AltminusB = cosA < (-cosB); - const T cosSumAB = cosA * cosB - sinA * sinB; - const bool ABltminusC = cosSumAB < (-cosC); - const bool ABltC = cosSumAB < cosC; - // apply triple angle formula - const T absArccosSumABC = acos(clamp(cosSumAB * cosC - (cosA * sinB + sinA * cosB) * sinC, T(-1.0), T(1.0))); - return ((AltminusB ? ABltC : ABltminusC) ? (-absArccosSumABC) : absArccosSumABC) + ((AltminusB || ABltminusC) ? numbers::pi : (-numbers::pi)); + return acos(runningSum.real()) + wraparound * numbers::pi; } complex_t runningSum; + T wraparound; // counts in pi (half revolutions) }; } diff --git a/include/nbl/builtin/hlsl/math/functions.hlsl b/include/nbl/builtin/hlsl/math/functions.hlsl index 20442c467b..046c72c527 100644 --- a/include/nbl/builtin/hlsl/math/functions.hlsl +++ b/include/nbl/builtin/hlsl/math/functions.hlsl @@ -33,9 +33,9 @@ struct lp_norm getter; - scalar_type retval = abs(getter(v, 0)); - for (int i = 1; i < extent::value; i++) - retval = max(abs(getter(v, i)),retval); + scalar_type retval = abs(getter(v, 0)); + for (int i = 1; i < vector_traits::Dimension; i++) + retval = max(abs(getter(v, i)),retval); return retval; } }; @@ -49,9 +49,9 @@ struct lp_norm getter; - scalar_type retval = abs(getter(v, 0)); - for (int i = 1; i < extent::value; i++) - retval += abs(getter(v, i)); + scalar_type retval = abs(getter(v, 0)); + for (int i = 1; i < vector_traits::Dimension; i++) + retval += abs(getter(v, i)); return retval; } @@ -122,11 +122,7 @@ void frisvad(NBL_CONST_REF_ARG(T) normal, NBL_REF_ARG(T) tangent, NBL_REF_ARG(T) bool partitionRandVariable(float leftProb, NBL_REF_ARG(float) xi, NBL_REF_ARG(float) rcpChoiceProb) { -#ifdef __HLSL_VERSION - NBL_CONSTEXPR float NEXT_ULP_AFTER_UNITY = asfloat(0x3f800001u); -#else - NBL_CONSTEXPR float32_t NEXT_ULP_AFTER_UNITY = bit_cast(0x3f800001u); -#endif + const float32_t NEXT_ULP_AFTER_UNITY = bit_cast(0x3f800001u); const bool pickRight = xi >= leftProb * NEXT_ULP_AFTER_UNITY; // This is all 100% correct taking into account the above NEXT_ULP_AFTER_UNITY diff --git a/include/nbl/builtin/hlsl/math/morton.hlsl b/include/nbl/builtin/hlsl/math/morton.hlsl new file mode 100644 index 0000000000..4a6cb5dfd3 --- /dev/null +++ b/include/nbl/builtin/hlsl/math/morton.hlsl @@ -0,0 +1,68 @@ +// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h +#ifndef _NBL_BUILTIN_HLSL_MATH_MORTON_INCLUDED_ +#define _NBL_BUILTIN_HLSL_MATH_MORTON_INCLUDED_ + +#include "nbl/builtin/hlsl/cpp_compat.hlsl" + +namespace nbl +{ +namespace hlsl +{ +namespace math +{ + +namespace impl +{ + +template +struct MortonComponent; + +template +struct MortonComponent +{ + static T decode2d(T x) + { + x &= 0x55555555u; + x = (x ^ (x >> 1u)) & 0x33333333u; + x = (x ^ (x >> 2u)) & 0x0f0f0f0fu; + x = (x ^ (x >> 4u)) & 0x00ff00ffu; + return x; + } +}; + +template +struct MortonComponent +{ + static T decode2d(T x) + { + x &= 0x55555555u; + x = (x ^ (x >> 1u)) & 0x33333333u; + x = (x ^ (x >> 2u)) & 0x0f0f0f0fu; + x = (x ^ (x >> 4u)) & 0x00ff00ffu; + x = (x ^ (x >> 8u)) & 0x0000ffffu; + x = (x ^ (x >> 16u)); + return x; + } +}; + +} + +template +struct Morton +{ + using vector2_type = vector; + using component_type = impl::MortonComponent; + + static vector2_type decode2d(T x) + { + return vector2_type(component_type::decode2d(x), component_type::decode2d(x >> 1u)); + } +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl new file mode 100644 index 0000000000..42a923f650 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -0,0 +1,62 @@ +// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _NBL_BUILTIN_HLSL_SAMPLING_BILINEAR_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_BILINEAR_INCLUDED_ + +#include +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +template +struct Bilinear +{ + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; + + static Bilinear create(NBL_CONST_REF_ARG(vector4_type) bilinearCoeffs) + { + Bilinear retval; + retval.bilinearCoeffs = bilinearCoeffs; + return retval; + } + + vector2_type generate(NBL_REF_ARG(scalar_type) rcpPdf, NBL_CONST_REF_ARG(vector2_type) _u) + { + vector2_type u = _u; + const vector2_type twiceAreasUnderXCurve = vector2_type(bilinearCoeffs[0] + bilinearCoeffs[1], bilinearCoeffs[2] + bilinearCoeffs[3]); + Linear lineary = Linear::create(twiceAreasUnderXCurve); + u.y = lineary.generate(u.y); + + const vector2_type ySliceEndPoints = vector2_type(nbl::hlsl::mix(bilinearCoeffs[0], bilinearCoeffs[2], u.y), nbl::hlsl::mix(bilinearCoeffs[1], bilinearCoeffs[3], u.y)); + Linear linearx = Linear::create(ySliceEndPoints); + u.x = linearx.generate(u.x); + + rcpPdf = (twiceAreasUnderXCurve[0] + twiceAreasUnderXCurve[1]) / (4.0 * nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], u.x)); + + return u; + } + + scalar_type pdf(NBL_CONST_REF_ARG(vector2_type) u) + { + return 4.0 * nbl::hlsl::mix(nbl::hlsl::mix(bilinearCoeffs[0], bilinearCoeffs[1], u.x), nbl::hlsl::mix(bilinearCoeffs[2], bilinearCoeffs[3], u.x), u.y) / (bilinearCoeffs[0] + bilinearCoeffs[1] + bilinearCoeffs[2] + bilinearCoeffs[3]); + } + + vector4_type bilinearCoeffs; +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl new file mode 100644 index 0000000000..dcac2279be --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -0,0 +1,27 @@ +// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _NBL_BUILTIN_HLSL_BOX_MULLER_TRANSFORM_INCLUDED_ +#define _NBL_BUILTIN_HLSL_BOX_MULLER_TRANSFORM_INCLUDED_ + +#include "nbl/builtin/hlsl/math/functions.hlsl" +#include "nbl/builtin/hlsl/numbers.hlsl" + +namespace nbl +{ +namespace hlsl +{ + +template +vector boxMullerTransform(vector xi, T stddev) +{ + T sinPhi, cosPhi; + math::sincos(2.0 * numbers::pi * xi.y - numbers::pi, sinPhi, cosPhi); + return vector(cosPhi, sinPhi) * nbl::hlsl::sqrt(-2.0 * nbl::hlsl::log(xi.x)) * stddev; +} + +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl new file mode 100644 index 0000000000..12d445eefe --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -0,0 +1,45 @@ +// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _NBL_BUILTIN_HLSL_SAMPLING_LINEAR_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_LINEAR_INCLUDED_ + +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +template +struct Linear +{ + using scalar_type = T; + using vector2_type = vector; + + static Linear create(NBL_CONST_REF_ARG(vector2_type) linearCoeffs) + { + Linear retval; + retval.linearCoeffs = linearCoeffs; + return retval; + } + + scalar_type generate(scalar_type u) + { + const scalar_type rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); + const vector2_type squaredCoeffs = linearCoeffs * linearCoeffs; + return nbl::hlsl::abs(rcpDiff) < numeric_limits::max ? (linearCoeffs[0] - nbl::hlsl::sqrt(nbl::hlsl::mix(squaredCoeffs[0], squaredCoeffs[1], u))) * rcpDiff : u; + } + + vector2_type linearCoeffs; +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl new file mode 100644 index 0000000000..f2f29ed12b --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -0,0 +1,97 @@ +// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _NBL_BUILTIN_HLSL_SAMPLING_PROJECTED_SPHERICAL_TRIANGLE_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_PROJECTED_SPHERICAL_TRIANGLE_INCLUDED_ + +#include +#include +#include +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +template +struct ProjectedSphericalTriangle +{ + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; + + static ProjectedSphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) + { + ProjectedSphericalTriangle retval; + retval.tri = tri; + return retval; + } + + vector4_type computeBilinearPatch(NBL_CONST_REF_ARG(vector3_type) receiverNormal, bool isBSDF) + { + const scalar_type minimumProjSolidAngle = 0.0; + + matrix m = matrix(tri.vertex0, tri.vertex1, tri.vertex2); + const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(isBSDF, nbl::hlsl::mul(m, receiverNormal), (vector3_type)minimumProjSolidAngle); + + return bxdfPdfAtVertex.yyxz; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, scalar_type solidAngle, NBL_CONST_REF_ARG(vector3_type) cos_vertices, NBL_CONST_REF_ARG(vector3_type) sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, NBL_CONST_REF_ARG(vector3_type) receiverNormal, bool isBSDF, NBL_CONST_REF_ARG(vector2_type) _u) + { + vector2_type u; + // pre-warp according to proj solid angle approximation + vector4_type patch = computeBilinearPatch(receiverNormal, isBSDF); + Bilinear bilinear = Bilinear::create(patch); + u = bilinear.generate(rcpPdf, u); + + // now warp the points onto a spherical triangle + const vector3_type L = sphtri.generate(solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); + rcpPdf *= solidAngle; + + return L; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, NBL_CONST_REF_ARG(vector3_type) receiverNormal, bool isBSDF, NBL_CONST_REF_ARG(vector2_type) u) + { + scalar_type cos_a, cos_c, csc_b, csc_c; + vector3_type cos_vertices, sin_vertices; + const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + return generate(rcpPdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, receiverNormal, isBSDF, u); + } + + scalar_type pdf(scalar_type solidAngle, NBL_CONST_REF_ARG(vector3_type) cos_vertices, NBL_CONST_REF_ARG(vector3_type) sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, NBL_CONST_REF_ARG(vector3_type) receiverNormal, bool receiverWasBSDF, NBL_CONST_REF_ARG(vector3_type) L) + { + scalar_type pdf; + const vector2_type u = sphtri.generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); + + vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); + Bilinear bilinear = Bilinear::create(patch); + return pdf * bilinear.pdf(u); + } + + scalar_type pdf(NBL_CONST_REF_ARG(vector3_type) receiverNormal, bool receiverWasBSDF, NBL_CONST_REF_ARG(vector3_type) L) + { + scalar_type pdf; + const vector2_type u = sphtri.generateInverse(pdf, L); + + vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); + Bilinear bilinear = Bilinear::create(patch); + return pdf * bilinear.pdf(u); + } + + shapes::SphericalTriangle tri; + sampling::SphericalTriangle sphtri; +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl new file mode 100644 index 0000000000..663cd5e3d1 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -0,0 +1,86 @@ +// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _NBL_BUILTIN_HLSL_SAMPLING_SPHERICAL_RECTANGLE_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_SPHERICAL_RECTANGLE_INCLUDED_ + +#include +#include +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +template +struct SphericalRectangle +{ + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; + + static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect) + { + SphericalRectangle retval; + retval.rect = rect; + return retval; + } + + vector2_type generate(NBL_CONST_REF_ARG(vector2_type) rectangleExtents, NBL_CONST_REF_ARG(vector2_type) uv, NBL_REF_ARG(scalar_type) S) + { + const vector4_type denorm_n_z = vector4_type(-rect.r0.y, rect.r0.x + rectangleExtents.x, rect.r0.y + rectangleExtents.y, -rect.r0.x); + const vector4_type n_z = denorm_n_z / hlsl::sqrt((vector4_type)(rect.r0.z * rect.r0.z) + denorm_n_z * denorm_n_z); + const vector4_type cosGamma = vector4_type( + -n_z[0] * n_z[1], + -n_z[1] * n_z[2], + -n_z[2] * n_z[3], + -n_z[3] * n_z[0] + ); + + scalar_type p = math::getSumofArccosAB(cosGamma[0], cosGamma[1]); + scalar_type q = math::getSumofArccosAB(cosGamma[2], cosGamma[3]); + + const scalar_type k = 2 * numbers::pi - q; + const scalar_type b0 = n_z[0]; + const scalar_type b1 = n_z[2]; + S = p + q - 2 * numbers::pi; + + const scalar_type CLAMP_EPS = 1e-5f; + + // flip z axsis if rect.r0.z > 0 + const uint32_t zFlipMask = (bit_cast(rect.r0.z) ^ 0x80000000u) & 0x80000000u; + rect.r0.z = bit_cast(bit_cast(rect.r0.z) ^ zFlipMask); + vector3_type r1 = rect.r0 + vector3_type(rectangleExtents.x, rectangleExtents.y, 0); + + const scalar_type au = uv.x * S + k; + const scalar_type fu = (hlsl::cos(au) * b0 - b1) / hlsl::sin(au); + const scalar_type cu_2 = hlsl::max(fu * fu + b0 * b0, 1.f); // forces `cu` to be in [-1,1] + const scalar_type cu = bit_cast(bit_cast(1.0 / hlsl::sqrt(cu_2)) ^ (bit_cast(fu) & 0x80000000u)); + + scalar_type xu = -(cu * rect.r0.z) * 1.0 / hlsl::sqrt(1 - cu * cu); + xu = hlsl::clamp(xu, rect.r0.x, r1.x); // avoid Infs + const scalar_type d_2 = xu * xu + rect.r0.z * rect.r0.z; + const scalar_type d = hlsl::sqrt(d_2); + + const scalar_type h0 = rect.r0.y / hlsl::sqrt(d_2 + rect.r0.y * rect.r0.y); + const scalar_type h1 = r1.y / hlsl::sqrt(d_2 + r1.y * r1.y); + const scalar_type hv = h0 + uv.y * (h1 - h0), hv2 = hv * hv; + const scalar_type yv = (hv2 < 1 - CLAMP_EPS) ? (hv * d) / hlsl::sqrt(1 - hv2) : r1.y; + + return vector2_type((xu - rect.r0.x) / rectangleExtents.x, (yv - rect.r0.y) / rectangleExtents.y); + } + + shapes::SphericalRectangle rect; +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl new file mode 100644 index 0000000000..7828fc14ea --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -0,0 +1,132 @@ +// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _NBL_BUILTIN_HLSL_SAMPLING_SPHERICAL_TRIANGLE_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_SPHERICAL_TRIANGLE_INCLUDED_ + +#include +#include +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +template +struct SphericalTriangle +{ + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + + static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) + { + SphericalTriangle retval; + retval.tri = tri; + return retval; + } + + vector3_type slerp_delta(NBL_CONST_REF_ARG(vector3_type) start, NBL_CONST_REF_ARG(vector3_type) preScaledWaypoint, scalar_type cosAngleFromStart) + { + vector3_type planeNormal = nbl::hlsl::cross(start,preScaledWaypoint); + + cosAngleFromStart *= 0.5; + const scalar_type sinAngle = nbl::hlsl::sqrt(0.5 - cosAngleFromStart); + const scalar_type cosAngle = nbl::hlsl::sqrt(0.5 + cosAngleFromStart); + + planeNormal *= sinAngle; + const vector3_type precompPart = nbl::hlsl::cross(planeNormal, start) * 2.0; + + return precompPart * cosAngle + nbl::hlsl::cross(planeNormal, precompPart); + } + + // WARNING: can and will return NAN if one or three of the triangle edges are near zero length + vector3_type generate(scalar_type solidAngle, NBL_CONST_REF_ARG(vector3_type) cos_vertices, NBL_CONST_REF_ARG(vector3_type) sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, NBL_CONST_REF_ARG(vector2_type) u) + { + scalar_type negSinSubSolidAngle,negCosSubSolidAngle; + math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); + + const scalar_type p = negCosSubSolidAngle * sin_vertices[0] - negSinSubSolidAngle * cos_vertices[0]; + const scalar_type q = -negSinSubSolidAngle * sin_vertices[0] - negCosSubSolidAngle * cos_vertices[0]; + + // TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful + scalar_type u_ = q - cos_vertices[0]; + scalar_type v_ = p + sin_vertices[0] * cos_c; + + // the slerps could probably be optimized by sidestepping `normalize` calls and accumulating scaling factors + vector3_type C_s = tri.vertex0; + if (csc_b < numeric_limits::max) + { + const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cos_vertices[0] - v_) / ((v_ * p + u_ * q) * sin_vertices[0]); + if (nbl::hlsl::abs(cosAngleAlongAC) < 1.f) + C_s += slerp_delta(tri.vertex0, tri.vertex2 * csc_b, cosAngleAlongAC); + } + + vector3_type retval = tri.vertex1; + const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri.vertex1); + const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); + if (csc_b_s < numeric_limits::max) + { + const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); + if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) + retval += slerp_delta(tri.vertex1, C_s * csc_b_s, cosAngleAlongBC_s); + } + return retval; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, NBL_CONST_REF_ARG(vector2_type) u) + { + scalar_type cos_a, cos_c, csc_b, csc_c; + vector3_type cos_vertices, sin_vertices; + + rcpPdf = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + + return generate(rcpPdf, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); + } + + vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type solidAngle, NBL_CONST_REF_ARG(vector3_type) cos_vertices, NBL_CONST_REF_ARG(vector3_type) sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, NBL_CONST_REF_ARG(vector3_type) L) + { + pdf = 1.0 / solidAngle; + + const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertex1); + const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); + const scalar_type cos_b_ = nbl::hlsl::dot(L, tri.vertex0); + + const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; + const scalar_type sinB_ = nbl::hlsl::sqrt(1.0 - cosB_ * cosB_); + + const scalar_type cosC_ = sin_vertices[0] * sinB_* cos_c - cos_vertices[0] * cosB_; + const scalar_type sinC_ = nbl::hlsl::sqrt(1.0 - cosC_ * cosC_); + + const scalar_type subTriSolidAngleRatio = math::getArccosSumofABC_minus_PI(cos_vertices[0], cosB_, cosC_, sin_vertices[0], sinB_, sinC_) * pdf; + const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; + + const scalar_type cosBC_s = (cos_vertices[0] + cosB_ * cosC_) / (sinB_ * sinC_); + const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); + + return vector2_type(u,v); + } + + vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, NBL_CONST_REF_ARG(vector3_type) L) + { + scalar_type cos_a, cos_c, csc_b, csc_c; + vector3_type cos_vertices, sin_vertices; + + const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + + return generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); + } + + shapes::SphericalTriangle tri; +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/shapes/rectangle.hlsl b/include/nbl/builtin/hlsl/shapes/rectangle.hlsl new file mode 100644 index 0000000000..f1a1e37575 --- /dev/null +++ b/include/nbl/builtin/hlsl/shapes/rectangle.hlsl @@ -0,0 +1,62 @@ +// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _NBL_BUILTIN_HLSL_SHAPES_RECTANGLE_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SHAPES_RECTANGLE_INCLUDED_ + +#include +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace shapes +{ + +template +struct SphericalRectangle +{ + using scalar_type = Scalar; + using vector3_type = vector; + using vector4_type = vector; + using matrix3x3_type = matrix; + + static SphericalRectangle create(NBL_CONST_REF_ARG(vector3_type) observer, NBL_CONST_REF_ARG(vector3_type) rectangleOrigin, NBL_CONST_REF_ARG(matrix3x3_type) basis) + { + SphericalRectangle retval; + retval.r0 = nbl::hlsl::mul(basis, rectangleOrigin - observer); + return retval; + } + + static SphericalRectangle create(NBL_CONST_REF_ARG(vector3_type) observer, NBL_CONST_REF_ARG(vector3_type) rectangleOrigin, NBL_CONST_REF_ARG(vector3_type) T, NBL_CONST_REF_ARG(vector3_type) B, NBL_CONST_REF_ARG(vector3_type) N) + { + SphericalRectangle retval; + matrix3x3_type TBN = nbl::hlsl::transpose(matrix3x3_type(T, B, N)); + retval.r0 = nbl::hlsl::mul(TBN, rectangleOrigin - observer); + return retval; + } + + scalar_type solidAngleOfRectangle(NBL_CONST_REF_ARG(vector) rectangleExtents) + { + const vector4_type denorm_n_z = vector4_type(-r0.y, r0.x + rectangleExtents.x, r0.y + rectangleExtents.y, -r0.x); + const vector4_type n_z = denorm_n_z / nbl::hlsl::sqrt((vector4_type)(r0.z * r0.z) + denorm_n_z * denorm_n_z); + const vector4_type cosGamma = vector4_type( + -n_z[0] * n_z[1], + -n_z[1] * n_z[2], + -n_z[2] * n_z[3], + -n_z[3] * n_z[0] + ); + return math::getSumofArccosABCD(cosGamma[0], cosGamma[1], cosGamma[2], cosGamma[3]) - 2 * numbers::pi; + } + + vector3_type r0; +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/shapes/triangle.hlsl b/include/nbl/builtin/hlsl/shapes/triangle.hlsl index 4677b0e155..4c2d895c28 100644 --- a/include/nbl/builtin/hlsl/shapes/triangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/triangle.hlsl @@ -5,8 +5,10 @@ #ifndef _NBL_BUILTIN_HLSL_SHAPES_TRIANGLE_INCLUDED_ #define _NBL_BUILTIN_HLSL_SHAPES_TRIANGLE_INCLUDED_ -#include #include +#include +#include +#include #include namespace nbl @@ -16,6 +18,86 @@ namespace hlsl namespace shapes { +template +struct SphericalTriangle +{ + using scalar_type = T; + using vector3_type = vector; + + static SphericalTriangle create(NBL_CONST_REF_ARG(vector3_type) vertex0, NBL_CONST_REF_ARG(vector3_type) vertex1, NBL_CONST_REF_ARG(vector3_type) vertex2, NBL_CONST_REF_ARG(vector3_type) origin) + { + SphericalTriangle retval; + retval.vertex0 = nbl::hlsl::normalize(vertex0 - origin); + retval.vertex1 = nbl::hlsl::normalize(vertex1 - origin); + retval.vertex2 = nbl::hlsl::normalize(vertex2 - origin); + return retval; + } + + bool pyramidAngles(NBL_REF_ARG(vector3_type) cos_sides, NBL_REF_ARG(vector3_type) csc_sides) + { + cos_sides = vector3_type(hlsl::dot(vertex1, vertex2), hlsl::dot(vertex2, vertex0), hlsl::dot(vertex0, vertex1)); + csc_sides = (vector3_type)(1.f) - cos_sides * cos_sides; + csc_sides.x = hlsl::rsqrt(csc_sides.x); + csc_sides.y = hlsl::rsqrt(csc_sides.y); + csc_sides.z = hlsl::rsqrt(csc_sides.z); + + return hlsl::any >(csc_sides >= (vector3_type)(numeric_limits::max)); + } + + scalar_type solidAngleOfTriangle(NBL_REF_ARG(vector3_type) cos_vertices, NBL_REF_ARG(vector3_type) sin_vertices, NBL_REF_ARG(scalar_type) cos_a, NBL_REF_ARG(scalar_type) cos_c, NBL_REF_ARG(scalar_type) csc_b, NBL_REF_ARG(scalar_type) csc_c) + { + vector3_type cos_sides,csc_sides; + if (pyramidAngles(cos_sides, csc_sides)) + return 0.f; + + // these variables might eventually get optimized out + cos_a = cos_sides[0]; + cos_c = cos_sides[2]; + csc_b = csc_sides[1]; + csc_c = csc_sides[2]; + + // Both vertices and angles at the vertices are denoted by the same upper case letters A, B, and C. The angles A, B, C of the triangle are equal to the angles between the planes that intersect the surface of the sphere or, equivalently, the angles between the tangent vectors of the great circle arcs where they meet at the vertices. Angles are in radians. The angles of proper spherical triangles are (by convention) less than PI + cos_vertices = hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, (vector3_type)(-1.f), (vector3_type)1.f); // using Spherical Law of Cosines (TODO: do we need to clamp anymore? since the pyramid angles method introduction?) + sin_vertices = hlsl::sqrt((vector3_type)1.f - cos_vertices * cos_vertices); + + return math::getArccosSumofABC_minus_PI(cos_vertices[0], cos_vertices[1], cos_vertices[2], sin_vertices[0], sin_vertices[1], sin_vertices[2]); + } + + scalar_type solidAngleOfTriangle() + { + vector3_type dummy0,dummy1; + scalar_type dummy2,dummy3,dummy4,dummy5; + return solidAngleOfTriangle(dummy0,dummy1,dummy2,dummy3,dummy4,dummy5); + } + + scalar_type projectedSolidAngleOfTriangle(NBL_CONST_REF_ARG(vector3_type) receiverNormal, NBL_REF_ARG(vector3_type) cos_sides, NBL_REF_ARG(vector3_type) csc_sides, NBL_REF_ARG(vector3_type) cos_vertices) + { + if (pyramidAngles(cos_sides, csc_sides)) + return 0.f; + + vector3_type awayFromEdgePlane0 = hlsl::cross(vertex1, vertex2) * csc_sides[0]; + vector3_type awayFromEdgePlane1 = hlsl::cross(vertex2, vertex0) * csc_sides[1]; + vector3_type awayFromEdgePlane2 = hlsl::cross(vertex0, vertex1) * csc_sides[2]; + + // useless here but could be useful somewhere else + cos_vertices[0] = hlsl::dot(awayFromEdgePlane1, awayFromEdgePlane2); + cos_vertices[1] = hlsl::dot(awayFromEdgePlane2, awayFromEdgePlane0); + cos_vertices[2] = hlsl::dot(awayFromEdgePlane0, awayFromEdgePlane1); + // TODO: above dot products are in the wrong order, either work out which is which, or try all 6 permutations till it works + cos_vertices = hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, (vector3_type)(-1.f), (vector3_type)1.f); + + matrix awayFromEdgePlane = matrix(awayFromEdgePlane0, awayFromEdgePlane1, awayFromEdgePlane2); + const vector3_type externalProducts = hlsl::abs(hlsl::mul(/* transposed already */awayFromEdgePlane, receiverNormal)); + + const vector3_type pyramidAngles = acos(cos_sides); + return hlsl::dot(pyramidAngles, externalProducts) / (2.f * numbers::pi); + } + + vector3_type vertex0; + vector3_type vertex1; + vector3_type vertex2; +}; + namespace util { // Use this convetion e_i = v_{i+2}-v_{i+1}. vertex index is modulo by 3. diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index 1d6c224ce9..672f2a2c4f 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -238,6 +238,7 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/colorspace/EOTF.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/colorspace/OETF.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/colorspace/decodeCIEXYZ.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/colorspace/encodeCIEXYZ.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/colorspace.hlsl") #barycentrics LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/barycentric/utils.hlsl") #scanning append @@ -248,9 +249,16 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/shapes/ellipse.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/shapes/line.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/shapes/beziers.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/shapes/triangle.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/shapes/rectangle.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/shapes/aabb.hlsl") #sampling +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/linear.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/bilinear.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/concentric_mapping.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/box_muller_transform.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/spherical_triangle.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/projected_spherical_triangle.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/spherical_rectangle.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/cos_weighted_spheres.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/quotient_and_pdf.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/uniform_spheres.hlsl") @@ -281,12 +289,14 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/reflection/beckmann.hlsl LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/reflection/ggx.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/reflection/lambertian.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/reflection/oren_nayar.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/reflection/iridescent.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/reflection/delta_distribution.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/beckmann.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/ggx.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/lambertian.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/oren_nayar.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/smooth_dielectric.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/iridescent.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/delta_distribution.hlsl") #subgroup LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/subgroup/ballot.hlsl")