diff --git a/examples_tests b/examples_tests index 2b4db21239..829ea34183 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 2b4db2123918f380cc0a35f6889315a02f84ea73 +Subproject commit 829ea34183a0a62a3bd68ded4dd9e451b97126d4 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..de4cbcef8e 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,6 +84,7 @@ 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) @@ -155,13 +102,46 @@ struct SCookTorrance 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 > + static enable_if_t 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 > + static enable_if_t 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); + } + + 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; @@ -178,7 +158,7 @@ struct SCookTorrance overwriteDG(DG, ndf, gq, qq, _sample, interaction, cache, isInfinity); - if (isInfinity) + if (isInfinity) // after all calls setting DG, allows compiler to throw away calls to ndf.D if using overwriteDG return hlsl::promote(0.0); scalar_type clampedVdotH = cache.getVdotH(); @@ -187,57 +167,95 @@ 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, bool transmitted, + NBL_CONST_REF_ARG(fresnel::OrientedEtaRcps) rcpEta, + NBL_REF_ARG(anisocache_type) cache, NBL_REF_ARG(bool) valid) { + 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) { - 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) + cache = anisocache_type::createPartial(VdotH, LdotH, localH.z, transmitted, rcpEta); + + 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 > + enable_if_t 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, false, dummy, cache, 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) @@ -245,7 +263,7 @@ struct SCookTorrance 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 +273,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,50 +281,25 @@ struct SCookTorrance { assert(NdotV*VdotH >= scalar_type(0.0)); } - const scalar_type reflectance = _f(hlsl::abs(VdotH))[0]; + + spectral_type throughputWeights = interaction.getLuminosityContributionHint(); + const scalar_type reflectance = hlsl::dot(impl::__implicit_promote::__call(_f(hlsl::abs(VdotH))), throughputWeights); 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 + bool valid; + sample_type s = __generate_common(interaction, localH, NdotV, VdotH, transmitted, rcpEta, cache, 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); + 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) @@ -325,13 +318,14 @@ 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]; + spectral_type throughputWeights = interaction.getLuminosityContributionHint(); + const scalar_type reflectance = hlsl::dot(impl::__implicit_promote::__call(_f(hlsl::abs(cache.getVdotH()))), throughputWeights); return hlsl::mix(reflectance, scalar_type(1.0) - reflectance, cache.isTransmission()) * DG1.projectedLightMeasure; } else @@ -344,8 +338,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 +357,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 +372,13 @@ struct SCookTorrance spectral_type quo; NBL_IF_CONSTEXPR(IsBSDF) - quo = hlsl::promote(G2_over_G1); + { + spectral_type throughputWeights = interaction.getLuminosityContributionHint(); + spectral_type reflectance = impl::__implicit_promote::__call(_f(hlsl::abs(cache.getVdotH()))); + const scalar_type scaled_reflectance = hlsl::dot(reflectance, throughputWeights); + 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..c918f1c0fe 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,7 @@ struct Schlick { using scalar_type = typename vector_traits::scalar_type; using vector_type = T; + using eta_type = vector_type; static Schlick create(NBL_CONST_REF_ARG(T) F0) { @@ -357,11 +360,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 +377,7 @@ struct Conductor { using scalar_type = typename vector_traits::scalar_type; using vector_type = T; + using eta_type = vector_type; static Conductor create(NBL_CONST_REF_ARG(T) eta, NBL_CONST_REF_ARG(T) etak) { @@ -393,27 +397,48 @@ struct Conductor return retval; } + // TODO: will probably merge with __call at some point + static void __polarized(const T orientedEta, const T orientedEtak, const T cosTheta, NBL_REF_ARG(T) Rp, NBL_REF_ARG(T) Rs) + { + T cosTheta_2 = cosTheta * cosTheta; + // T sinTheta2 = hlsl::promote(1.0) - cosTheta_2; + const T eta = orientedEta; + const T eta2 = eta*eta; + const T etak = orientedEtak; + const T etak2 = etak*etak; + + const T etaLen2 = eta2 + etak2; + assert(hlsl::all(etaLen2 > hlsl::promote(hlsl::exp2(-numeric_limits::digits)))); + T t1 = etaLen2 * cosTheta_2; + const T etaCosTwice = eta * cosTheta * scalar_type(2.0); + + 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); + } + T operator()(const scalar_type clampedCosTheta) { - const scalar_type cosTheta2 = clampedCosTheta * clampedCosTheta; - //const float sinTheta2 = 1.0 - cosTheta2; + const scalar_type cosTheta_2 = clampedCosTheta * clampedCosTheta; + //const float sinTheta2 = 1.0 - cosTheta_2; assert(hlsl::all(etaLen2 > hlsl::promote(hlsl::exp2(-numeric_limits::digits)))); - const T etaCosTwice = eta * clampedCosTheta * 2.0f; + const T etaCosTwice = eta * clampedCosTheta * hlsl::promote(2.0); - const T rs_common = etaLen2 + (T)(cosTheta2); + const T rs_common = etaLen2 + hlsl::promote(cosTheta_2); const T rs2 = (rs_common - etaCosTwice) / (rs_common + etaCosTwice); - const T rp_common = etaLen2 * cosTheta2 + (T)(1.0); + const T rp_common = etaLen2 * cosTheta_2 + hlsl::promote(1.0); const T rp2 = (rp_common - etaCosTwice) / (rp_common + etaCosTwice); - 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 +453,7 @@ struct Dielectric { using scalar_type = typename vector_traits::scalar_type; using vector_type = T; + using eta_type = vector_type; static Dielectric create(NBL_CONST_REF_ARG(OrientedEtas) orientedEta) { @@ -437,6 +463,22 @@ struct Dielectric return retval; } + // TODO: will probably merge with __call at some point + static void __polarized(const T orientedEta, const T cosTheta, NBL_REF_ARG(T) Rp, NBL_REF_ARG(T) Rs) + { + T sinTheta2 = hlsl::promote(1.0) - cosTheta * cosTheta; + const T eta = orientedEta; + const T eta2 = eta * eta; + + T t0 = hlsl::sqrt(hlsl::max(eta2 - sinTheta2, hlsl::promote(0.0))); + T t2 = eta2 * cosTheta; + + T rp = (t0 - t2) / (t0 + t2); + Rp = rp * rp; + T rs = (cosTheta - t0) / (cosTheta + t0); + Rs = rs * rs; + } + static T __call(NBL_CONST_REF_ARG(T) orientedEta2, const scalar_type clampedCosTheta) { const scalar_type sinTheta2 = scalar_type(1.0) - clampedCosTheta * clampedCosTheta; @@ -471,6 +513,230 @@ 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); + } + + T __call(const scalar_type clampedCosTheta) + { + const vector_type wavelengths = vector_type(colorspace::scRGB::wavelength_R, colorspace::scRGB::wavelength_G, colorspace::scRGB::wavelength_B); + + 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, cosTheta_2, R23p, R23s); + else + Conductor::__polarized(eta23, etak23, 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 * Dinc) * thinFilmIor * 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); + } + + 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; + + static this_t create(scalar_type Dinc, vector_type ior1, vector_type ior2, vector_type ior3, vector_type iork3) + { + this_t retval; + retval.helper.Dinc = Dinc; + retval.helper.thinFilmIor = ior2; + retval.helper.eta12 = ior2/ior1; + retval.helper.eta23 = ior3/ior2; + retval.helper.etak23 = iork3/ior2; + return retval; + } + + T operator()(const scalar_type clampedCosTheta) + { + return helper.__call(clampedCosTheta); + } + + OrientedEtaRcps getOrientedEtaRcps() NBL_CONST_MEMBER_FUNC + { + OrientedEtaRcps rcpEta; + rcpEta.value = hlsl::promote(1.0) / helper.eta23; + rcpEta.value2 = rcpEta.value * rcpEta.value; + return rcpEta; + } + + impl::iridescent_helper helper; +}; + +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; + + static this_t create(scalar_type Dinc, vector_type ior1, vector_type ior2, vector_type ior3) + { + this_t retval; + retval.helper.Dinc = Dinc; + retval.helper.thinFilmIor = ior2; + retval.helper.eta12 = ior2/ior1; + retval.helper.eta23 = ior3/ior2; + retval.helper.etak23 = hlsl::promote(0.0); + return retval; + } + + T operator()(const scalar_type clampedCosTheta) + { + return helper.__call(clampedCosTheta); + } + + scalar_type getRefractionOrientedEta() NBL_CONST_MEMBER_FUNC { return helper.eta23[0]; } + OrientedEtaRcps getOrientedEtaRcps() NBL_CONST_MEMBER_FUNC + { + OrientedEtaRcps rcpEta; + rcpEta.value = hlsl::promote(1.0) / helper.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.helper.Dinc = helper.Dinc; + orientedFresnel.helper.thinFilmIor = helper.thinFilmIor; + orientedFresnel.helper.eta12 = hlsl::mix(helper.eta12, hlsl::promote(1.0)/helper.eta12, flip); + orientedFresnel.helper.eta23 = hlsl::mix(helper.eta23, hlsl::promote(1.0)/helper.eta23, flip); + orientedFresnel.helper.etak23 = hlsl::promote(0.0); + return orientedFresnel; + } + + impl::iridescent_helper helper; +}; + + // 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..70f58a0dc7 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,13 @@ 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; + 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..c2b9e959e1 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,13 @@ 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) + 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 +299,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 +323,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 +360,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..9e6da57344 --- /dev/null +++ b/include/nbl/builtin/hlsl/colorspace.hlsl @@ -0,0 +1,165 @@ +// 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 colorspace_base +{ + // default CIE RGB primaries wavelengths + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_R = 700.0f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_G = 546.1f; + NBL_CONSTEXPR_STATIC_INLINE float32_t wavelength_B = 435.8f; +}; + +struct scRGB : colorspace_base +{ + 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 : colorspace_base +{ + 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 : colorspace_base +{ + 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 : colorspace_base +{ + 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 : colorspace_base +{ + 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 : colorspace_base +{ + 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 : colorspace_base +{ + 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/math/angle_adding.hlsl b/include/nbl/builtin/hlsl/math/angle_adding.hlsl index 8a89840573..6918050542 100644 --- a/include/nbl/builtin/hlsl/math/angle_adding.hlsl +++ b/include/nbl/builtin/hlsl/math/angle_adding.hlsl @@ -21,53 +21,47 @@ 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) { this_t retval; - retval.runningSum = complex_t::create(cosA, T(0)); + retval.runningSum = complex_t::create(cosA, sinA); + // retval.runningSum.real(cosA); + // retval.runningSum.imag(sinA); + retval.wraparound = 0u; return retval; } - void addCosine(T cosA, T biasA) + void addCosine(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 reverse = 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.imag(ieee754::flipSign(d, reverse)); + + wraparound += hlsl::mix(0u, 1u, reverse); } void addCosine(T cosA) { - addCosine(cosA, T(0.0)); + addCosine(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; + uint16_t wraparound; }; } diff --git a/include/nbl/builtin/hlsl/math/functions.hlsl b/include/nbl/builtin/hlsl/math/functions.hlsl index 20442c467b..6eee1fae6e 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; } diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index fc75243f6b..83aef9f3dd 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 @@ -280,12 +281,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")