From ae39c11669fe82f059811cff78165b91e2fa9701 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 7 Apr 2026 17:06:43 +0700 Subject: [PATCH 01/32] draft version of microfacet normal mapping brdf --- .../bxdf/reflection/microfacet_normals.hlsl | 436 ++++++++++++++++++ 1 file changed, 436 insertions(+) create mode 100644 include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl new file mode 100644 index 0000000000..50c953f670 --- /dev/null +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -0,0 +1,436 @@ +// 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_MICROFACET_NORMALS_INCLUDED_ +#define _NBL_BUILTIN_HLSL_BXDF_REFLECTION_MICROFACET_NORMALS_INCLUDED_ + +#include "nbl/builtin/hlsl/bxdf/common.hlsl" +#include "nbl/builtin/hlsl/bxdf/config.hlsl" +#include "nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl" + +namespace nbl +{ +namespace hlsl +{ +namespace bxdf +{ +namespace reflection +{ + +namespace impl +{ +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 this_t create(NBL_CONST_REF_ARG(RayDirInfo) normalizedV, const vector3_type normalizedN) + { + 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; + } + + RayDirInfo getV() NBL_CONST_MEMBER_FUNC { return V; } + vector3_type getN() NBL_CONST_MEMBER_FUNC { return N; } + scalar_type getNdotV(BxDFClampMode _clamp = BxDFClampMode::BCM_NONE) NBL_CONST_MEMBER_FUNC + { + return bxdf::conditionalAbsOrMax(NdotV, _clamp); + } + scalar_type getNdotV(BxDFClampMode _clamp = BxDFClampMode::BCM_NONE) NBL_CONST_MEMBER_FUNC + { + return bxdf::conditionalAbsOrMax(NdotV, _clamp); + } + scalar_type getNdotV(BxDFClampMode _clamp = BxDFClampMode::BCM_NONE) NBL_CONST_MEMBER_FUNC + { + return bxdf::conditionalAbsOrMax(NdotV, _clamp); + } + 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; + vector3_type Np; + vector3_type Nt; + scalar_type NdotV; + scalar_type NdotV2; + + spectral_type luminosityContributionHint; +}; + +template) +struct SAnisotropic +{ + using this_t = SAnisotropic; + using isotropic_interaction_type = IsotropicInteraction; + using ray_dir_info_type = typename isotropic_interaction_type::ray_dir_info_type; + 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( + NBL_CONST_REF_ARG(isotropic_interaction_type) isotropic, + const vector3_type normalizedT, + const vector3_type normalizedB + ) + { + this_t retval; + retval.isotropic = isotropic; + + retval.T = normalizedT; + retval.B = normalizedB; + + retval.TdotV = nbl::hlsl::dot(retval.isotropic.getV().getDirection(), retval.T); + retval.BdotV = nbl::hlsl::dot(retval.isotropic.getV().getDirection(), retval.B); + + return retval; + } + static this_t create(NBL_CONST_REF_ARG(isotropic_interaction_type) isotropic, const vector3_type normalizedT) + { + return create(isotropic, normalizedT, cross(isotropic.getN(), normalizedT)); + } + static this_t create(NBL_CONST_REF_ARG(isotropic_interaction_type) isotropic) + { + vector3_type T, B; + math::frisvad(isotropic.getN(), T, B); + return create(isotropic, nbl::hlsl::normalize(T), nbl::hlsl::normalize(B)); + } + + static this_t create(NBL_CONST_REF_ARG(ray_dir_info_type) normalizedV, const vector3_type normalizedN) + { + isotropic_interaction_type isotropic = isotropic_interaction_type::create(normalizedV, normalizedN); + return create(isotropic); + } + + ray_dir_info_type getV() NBL_CONST_MEMBER_FUNC { return isotropic.getV(); } + vector3_type getN() NBL_CONST_MEMBER_FUNC { return isotropic.getN(); } + 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; } + scalar_type getTdotV() NBL_CONST_MEMBER_FUNC { return TdotV; } + scalar_type getTdotV2() NBL_CONST_MEMBER_FUNC { const scalar_type t = getTdotV(); return t*t; } + scalar_type getBdotV() NBL_CONST_MEMBER_FUNC { return BdotV; } + scalar_type getBdotV2() NBL_CONST_MEMBER_FUNC { const scalar_type t = getBdotV(); return t*t; } + + vector3_type getTangentSpaceV() NBL_CONST_MEMBER_FUNC { return vector3_type(TdotV, BdotV, isotropic.getNdotV()); } + matrix3x3_type getToTangentSpace() NBL_CONST_MEMBER_FUNC { return matrix3x3_type(T, B, isotropic.getN()); } + matrix3x3_type getFromTangentSpace() NBL_CONST_MEMBER_FUNC { return nbl::hlsl::transpose(matrix3x3_type(T, B, isotropic.getN())); } + + isotropic_interaction_type isotropic; + vector3_type T; + vector3_type B; + scalar_type TdotV; + scalar_type BdotV; +}; +} + +template) +struct SMicrofacetNormals +{ + using this_t = SMicrofacetNormals; + BXDF_CONFIG_TYPE_ALIASES(Config); + + using random_type = conditional_t; + struct Cache {}; // TODO: cache type? + using isocache_type = Cache; + using anisocache_type = Cache; + using bxdf_type = BRDF; + + NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = conditional_value::value; + + template // TODO: concept for accessor + anisotropic_interaction_type buildInteraction(NBL_CONST_REF_ARG(NormalsTexAccessor) normalMap, const vector2_type uv, const matrix object_to_world, const vector3_type V) NBL_CONST_MEMBER_FUNC + { + vector3_type localN; + normalMap.template get(localN, uv, 0); + localN = hlsl::normalize(hlsl::promote(2.0) * localN - hlsl::promote(1.0)); + + const vector3_type N = hlsl::mul(object_to_world, localN); + isotropic_interaction_type interaction = isotropic_interaction_type::create(V, N); + return anisotropic_interaction_type::create(interaction); + } + + // N -- geometric normal + // Np -- perturbed normal + // Nt -- tangent normal + static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) + { + const scalar_type sinThetaNp = hlsl::sqrt(1.0 - NdotNp * NdotNp); + return hlsl::min(scalar_type(1.0), + clampedNdotL * hlsl::max(scalar_type(0.0), NdotNp) + / (clampedNpdotL + clampedNtdotL * sinThetaNp) + ); + } + + static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type NpdotV, const scalar_type NtdotV) + { + const scalar_type sinThetaNp = hlsl::sqrt(1.0 - NdotNp * NdotNp); + return NpdotV / (NpdotV + NtdotV * sinThetaNp); + } + + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + { + return evalAndWeight(_sample, anisotropic_interaction_type::create(interaction)); + } + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getNp(); + const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); + const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); + + const vector3_type V = interaction.getV(); + if (interaction.getNdotNp() <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + { + typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, interaction.getN()); + typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); + return nested_brdf.evalAndWeight(_sample, interaction_N); + } + + spectral_type eval = hlsl::promote(0.0); + + const vector3_type L = _sample.getL().getDirection(); + const scalar_type NtdotL = hlsl::dot(Nt, L); + const scalar_type lambda_p = lambdaP(interaction.getNdotNp(), interaction.getNpdotV(), interaction.getNtdotV()); + const scalar_type shadowing = G1(_sample.getNdotL(BxDFClampMode::BCM_MAX), interaction.getNdotNp(), + hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, NtdotL)); + + typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); + typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); + + // i -> p -> o + { + sample_type sample_single_p = sample_type::create(_sample.getL(), Np); + + // TODO: what to do with cache? + value_weight_type eval_single_p = nested_brdf.evalAndWeight(sample_single_p, interaction_Np); + eval += eval_single_p.value() * lambda_p * shadowing; + } + + // i -> p -> t -> o + if (NtdotL > scalar_type(0.0)) + { + fresnel::Reflect reflectL = fresnel::Reflect::create(L, Nt); + ray_dir_info_type L_reflected; + L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); + sample_type sample_double_p = sample_type::create(L_reflected, Np); // Nt? + + const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), interaction.getNdotNp(), + hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, NtdotL)); + + value_weight_type eval_double_p = nested_brdf.evalAndWeight(sample_double_p, interaction_Np); + eval += eval_double_p.value() * (lambda_p * notShadowedNpMirror * shadowing); + } + + // i -> t -> p -> o + const scalar_type NtdotV = hlsl::dot(Nt, V); + if (NtdotV > scalar_type(0.0)) + { + fresnel::Reflect reflectV = fresnel::Reflect::create(V, Nt); + ray_dir_info_type V_reflected; + V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); + sample_type sample_double_t = sample_type::create(V_reflected, Np); // Nt? + + typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected.getDirection(), Np); + typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); + + value_weight_type eval_double_t = nested_brdf.evalAndWeight(sample_double_t, interaction_reflected); + eval += eval_double_t.value() * (scalar_type(1.0) - lambda_p) * shadowing; + } + + return value_weight_type::create(eval, forwardPdf(_sample, interaction)); + } + + sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const random_type u, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getNp(); + const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); + const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); + + const vector3_type V = interaction.getV(); + if (interaction.getNdotNp() <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + { + typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, interaction.getN()); + typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); + return nested_brdf.generate(_sample, interaction_N); + } + + typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); + typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); + + sample_type s; + if (u.x < lambdaP(interaction.getNdotNp(), interaction.getNpdotV(), interaction.getNtdotV())) { + // sample on Np + sample_type sample_p = sample_type::create(_sample.getL(), Np); + s = nested_brdf.generate(sample_p, interaction_Np); + + if (!s.isValid()) + return s; + + const scalar_type shadowed = G1(s.getNdotL(), interaction.getNdotNp(), + hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, hlsl::dot(Nt, L))); + + if (u.y > shadowed) + { + // if sample dir shadowed, reflect on Nt + fresnel::Reflect reflect_s = fresnel::Reflect::create(s.getL().getDirection(), Nt); + ray_dir_info_type s_reflected; + s_reflected.setDirection(hlsl::normalize(reflect_s())); + s = sample_type::create(s_reflected, interaction.getN()); // Np? + } + } + else + { + // do one reflection if we start at wt + fresnel::Reflect reflect_V = fresnel::Reflect::create(V, Nt); + ray_dir_info_type V_reflected; + V_reflected.setDirection(hlsl::normalize(reflect_V())); + sample_type reflV = sample_type::create(V_reflected, Np); // Np? + + // sample on wp + typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(reflV, Np); + typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); + s = nested_brdf.generate(reflV, interaction_Np); + if (!s.isValid()) + return s; + } + if (s.getNdotL() < scalar_type(0.0)) + return sample_type::createInvalid(); // for reflection + return s; + } + sample_type generate(NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, const random_type u, NBL_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + return generate(anisotropic_interaction_type::create(interaction), u, _cache); + } + + scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + { + return forwardPdf(_sample, anisotropic_interaction_type::create(interaction)); + } + scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getNp(); + const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); + const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); + + const vector3_type V = interaction.getV(); + if (interaction.getNdotNp() <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + { + typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, interaction.getN()); + typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); + return nested_brdf.forwardPdf(_sample, interaction_N); + } + + scalar_type pdf = scalar_type(0.0); + const scalar_type lambda_p = lambdaP(interaction.getNdotNp(), interaction.getNpdotV(), interaction.getNtdotV()); + const sample_type sample_p = sample_type::create(_sample.getL(), Np); + + if (lambda_p > scalar_type(0.0)) + { + typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); + typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); + + // TODO: cache? + pdf += lambda_p * nested_brdf.forwardPdf(sample_p, interaction_Np) * G1(sample_p.getNdotL(BxDFClampMode::BCM_MAX), interaction.getNdotNp(), + hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, NtdotL)); + + const vector3_type L = _sample.getL().getDirection(); + const scalar_type NtdotL = hlsl::dot(Nt, L); + if (NtdotL > numeric_limits::min) + { + fresnel::Reflect reflectL = fresnel::Reflect::create(L, Nt); + ray_dir_info_type L_reflected; + L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); + sample_type sample_reflected = sample_type::create(L_reflected, Np); + + pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction_Np) * + (scalar_type(1.0) - G1(sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), interaction.getNdotNp(), hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, NtdotL))); + } + } + + const scalar_type NtdotV = hlsl::dot(Nt, V); + if (lambda_p < scalar_type(1.0) && NtdotV > numeric_limits::min) + { + fresnel::Reflect reflectV = fresnel::Reflect::create(V, Nt); + ray_dir_info_type V_reflected; + V_reflected.setDirection(hlsl::normalize(reflectL(NtdotV))); + // sample_type sample_p = sample_type::create(L, Np); // Nt? + + typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected.getDirection(), Np); + typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); + + pdf += (scalar_type(1.0) - lambda_p) * nested_brdf.forwardPdf(sample_p, interaction_reflected); + } + + return pdf; + } + + quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + return quotientAndWeight(_sample, anisotropic_interaction_type::create(interaction), _cache); + } + quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getNp(); + const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); + const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); + + const vector3_type V = interaction.getV(); + if (interaction.getNdotNp() <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + { + typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, interaction.getN()); + typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); + return nested_brdf.quotientAndWeight(_sample, interaction_N); + } + + typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); + typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); + + spectral_type quo = hlsl::promote(1.0); + + sample_type neg_sample; + { + ray_dir_info_type L; + L.setDirection(-_sample.getL().getDirection()); + neg_sample = sample_type::create(L, Np); + } + + // TODO: cache? + quotient_weight_type qw = nested_brdf.quotientAndWeight(neg_sample, interaction); + quo *= qw.quotient(); + + quo *= G1(_sample.getNdotL(), interaction.getNdotNp(), hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, hlsl::dot(Nt, L))); + + return quotient_weight_type::create(quo, forwardPdf(_sample, interaction)); + } + + bxdf_type nested_brdf; +}; + +} +} +} +} + +#endif From 5ed75b680e1b547a36166d933629188a26d176da Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 8 Apr 2026 12:07:28 +0700 Subject: [PATCH 02/32] store shading (geometric) normal in bxdf wrapper, input interaction is perturbed normal + some bug fixes between generate and quotient --- .../bxdf/reflection/microfacet_normals.hlsl | 246 +++++------------- 1 file changed, 64 insertions(+), 182 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 50c953f670..de96071837 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -17,131 +17,7 @@ namespace bxdf namespace reflection { -namespace impl -{ -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 this_t create(NBL_CONST_REF_ARG(RayDirInfo) normalizedV, const vector3_type normalizedN) - { - 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; - } - - RayDirInfo getV() NBL_CONST_MEMBER_FUNC { return V; } - vector3_type getN() NBL_CONST_MEMBER_FUNC { return N; } - scalar_type getNdotV(BxDFClampMode _clamp = BxDFClampMode::BCM_NONE) NBL_CONST_MEMBER_FUNC - { - return bxdf::conditionalAbsOrMax(NdotV, _clamp); - } - scalar_type getNdotV(BxDFClampMode _clamp = BxDFClampMode::BCM_NONE) NBL_CONST_MEMBER_FUNC - { - return bxdf::conditionalAbsOrMax(NdotV, _clamp); - } - scalar_type getNdotV(BxDFClampMode _clamp = BxDFClampMode::BCM_NONE) NBL_CONST_MEMBER_FUNC - { - return bxdf::conditionalAbsOrMax(NdotV, _clamp); - } - 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; - vector3_type Np; - vector3_type Nt; - scalar_type NdotV; - scalar_type NdotV2; - - spectral_type luminosityContributionHint; -}; - -template) -struct SAnisotropic -{ - using this_t = SAnisotropic; - using isotropic_interaction_type = IsotropicInteraction; - using ray_dir_info_type = typename isotropic_interaction_type::ray_dir_info_type; - 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( - NBL_CONST_REF_ARG(isotropic_interaction_type) isotropic, - const vector3_type normalizedT, - const vector3_type normalizedB - ) - { - this_t retval; - retval.isotropic = isotropic; - - retval.T = normalizedT; - retval.B = normalizedB; - - retval.TdotV = nbl::hlsl::dot(retval.isotropic.getV().getDirection(), retval.T); - retval.BdotV = nbl::hlsl::dot(retval.isotropic.getV().getDirection(), retval.B); - - return retval; - } - static this_t create(NBL_CONST_REF_ARG(isotropic_interaction_type) isotropic, const vector3_type normalizedT) - { - return create(isotropic, normalizedT, cross(isotropic.getN(), normalizedT)); - } - static this_t create(NBL_CONST_REF_ARG(isotropic_interaction_type) isotropic) - { - vector3_type T, B; - math::frisvad(isotropic.getN(), T, B); - return create(isotropic, nbl::hlsl::normalize(T), nbl::hlsl::normalize(B)); - } - - static this_t create(NBL_CONST_REF_ARG(ray_dir_info_type) normalizedV, const vector3_type normalizedN) - { - isotropic_interaction_type isotropic = isotropic_interaction_type::create(normalizedV, normalizedN); - return create(isotropic); - } - - ray_dir_info_type getV() NBL_CONST_MEMBER_FUNC { return isotropic.getV(); } - vector3_type getN() NBL_CONST_MEMBER_FUNC { return isotropic.getN(); } - 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; } - scalar_type getTdotV() NBL_CONST_MEMBER_FUNC { return TdotV; } - scalar_type getTdotV2() NBL_CONST_MEMBER_FUNC { const scalar_type t = getTdotV(); return t*t; } - scalar_type getBdotV() NBL_CONST_MEMBER_FUNC { return BdotV; } - scalar_type getBdotV2() NBL_CONST_MEMBER_FUNC { const scalar_type t = getBdotV(); return t*t; } - - vector3_type getTangentSpaceV() NBL_CONST_MEMBER_FUNC { return vector3_type(TdotV, BdotV, isotropic.getNdotV()); } - matrix3x3_type getToTangentSpace() NBL_CONST_MEMBER_FUNC { return matrix3x3_type(T, B, isotropic.getN()); } - matrix3x3_type getFromTangentSpace() NBL_CONST_MEMBER_FUNC { return nbl::hlsl::transpose(matrix3x3_type(T, B, isotropic.getN())); } - - isotropic_interaction_type isotropic; - vector3_type T; - vector3_type B; - scalar_type TdotV; - scalar_type BdotV; -}; -} - +// based on Microfacet-based Normal Mapping for Robust Monte Carlo Path Tracing: https://jo.dreggn.org/home/2017_normalmap.pdf template) struct SMicrofacetNormals { @@ -156,6 +32,9 @@ struct SMicrofacetNormals NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = conditional_value::value; + // perturbed normal Np stored in interaction + // shading normal N (geometric normal in paper) stored in bxdf + // tangent normal Nt derived as needed template // TODO: concept for accessor anisotropic_interaction_type buildInteraction(NBL_CONST_REF_ARG(NormalsTexAccessor) normalMap, const vector2_type uv, const matrix object_to_world, const vector3_type V) NBL_CONST_MEMBER_FUNC { @@ -168,9 +47,6 @@ struct SMicrofacetNormals return anisotropic_interaction_type::create(interaction); } - // N -- geometric normal - // Np -- perturbed normal - // Nt -- tangent normal static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) { const scalar_type sinThetaNp = hlsl::sqrt(1.0 - NdotNp * NdotNp); @@ -192,26 +68,30 @@ struct SMicrofacetNormals } value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { - const vector3_type Np = interaction.getNp(); + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); - const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); const vector3_type V = interaction.getV(); - if (interaction.getNdotNp() <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { - typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, interaction.getN()); + typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); return nested_brdf.evalAndWeight(_sample, interaction_N); } + const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); + const scalar_type NpdotV = interaction.getNdotV(); + const scalar_type NtdotV = hlsl::dot(Nt, V); spectral_type eval = hlsl::promote(0.0); const vector3_type L = _sample.getL().getDirection(); + const scalar_type NpdotL = hlsl::dot(Np, L); const scalar_type NtdotL = hlsl::dot(Nt, L); - const scalar_type lambda_p = lambdaP(interaction.getNdotNp(), interaction.getNpdotV(), interaction.getNtdotV()); - const scalar_type shadowing = G1(_sample.getNdotL(BxDFClampMode::BCM_MAX), interaction.getNdotNp(), - hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, NtdotL)); + const scalar_type lambda_p = lambdaP(NdotNp, NpdotV, NtdotV); + const scalar_type shadowing = G1(_sample.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, + hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL)); typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); @@ -233,15 +113,14 @@ struct SMicrofacetNormals L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_double_p = sample_type::create(L_reflected, Np); // Nt? - const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), interaction.getNdotNp(), - hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, NtdotL)); + const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, + hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL)); value_weight_type eval_double_p = nested_brdf.evalAndWeight(sample_double_p, interaction_Np); eval += eval_double_p.value() * (lambda_p * notShadowedNpMirror * shadowing); } // i -> t -> p -> o - const scalar_type NtdotV = hlsl::dot(Nt, V); if (NtdotV > scalar_type(0.0)) { fresnel::Reflect reflectV = fresnel::Reflect::create(V, Nt); @@ -261,32 +140,35 @@ struct SMicrofacetNormals sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const random_type u, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { - const vector3_type Np = interaction.getNp(); + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); - const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); const vector3_type V = interaction.getV(); - if (interaction.getNdotNp() <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { - typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, interaction.getN()); + typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); return nested_brdf.generate(_sample, interaction_N); } - typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); - typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); + const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); + const scalar_type NpdotV = interaction.getNdotV(); + const scalar_type NtdotV = hlsl::dot(Nt, V); sample_type s; - if (u.x < lambdaP(interaction.getNdotNp(), interaction.getNpdotV(), interaction.getNtdotV())) { + if (u.x < lambdaP(NdotNp, NpdotV, NtdotV)) + { // sample on Np - sample_type sample_p = sample_type::create(_sample.getL(), Np); - s = nested_brdf.generate(sample_p, interaction_Np); + typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); + typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); + s = nested_brdf.generate(interaction_Np, u); // TODO: cache? if (!s.isValid()) return s; - const scalar_type shadowed = G1(s.getNdotL(), interaction.getNdotNp(), + const scalar_type shadowed = G1(s.getNdotL(), NdotNp, hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, hlsl::dot(Nt, L))); if (u.y > shadowed) @@ -295,21 +177,19 @@ struct SMicrofacetNormals fresnel::Reflect reflect_s = fresnel::Reflect::create(s.getL().getDirection(), Nt); ray_dir_info_type s_reflected; s_reflected.setDirection(hlsl::normalize(reflect_s())); - s = sample_type::create(s_reflected, interaction.getN()); // Np? + s = sample_type::create(s_reflected, Np); } } else { // do one reflection if we start at wt fresnel::Reflect reflect_V = fresnel::Reflect::create(V, Nt); - ray_dir_info_type V_reflected; - V_reflected.setDirection(hlsl::normalize(reflect_V())); - sample_type reflV = sample_type::create(V_reflected, Np); // Np? + const vector3_type V_negreflected = -hlsl::normalize(reflect_V()); // sample on wp - typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(reflV, Np); + typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_negreflected, Np); typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); - s = nested_brdf.generate(reflV, interaction_Np); + s = nested_brdf.generate(interaction_Np, u); // TODO: cache? if (!s.isValid()) return s; } @@ -328,21 +208,25 @@ struct SMicrofacetNormals } scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { - const vector3_type Np = interaction.getNp(); + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); - const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); const vector3_type V = interaction.getV(); - if (interaction.getNdotNp() <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { - typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, interaction.getN()); + typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); return nested_brdf.forwardPdf(_sample, interaction_N); } + const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); + const scalar_type NpdotV = interaction.getNdotV(); + const scalar_type NtdotV = hlsl::dot(Nt, V); + scalar_type pdf = scalar_type(0.0); - const scalar_type lambda_p = lambdaP(interaction.getNdotNp(), interaction.getNpdotV(), interaction.getNtdotV()); + const scalar_type lambda_p = lambdaP(NdotNp, NpdotV, NtdotV); const sample_type sample_p = sample_type::create(_sample.getL(), Np); if (lambda_p > scalar_type(0.0)) @@ -351,11 +235,12 @@ struct SMicrofacetNormals typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); // TODO: cache? - pdf += lambda_p * nested_brdf.forwardPdf(sample_p, interaction_Np) * G1(sample_p.getNdotL(BxDFClampMode::BCM_MAX), interaction.getNdotNp(), - hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, NtdotL)); - const vector3_type L = _sample.getL().getDirection(); + const scalar_type NpdotL = hlsl::dot(Np, L); const scalar_type NtdotL = hlsl::dot(Nt, L); + pdf += lambda_p * nested_brdf.forwardPdf(sample_p, interaction_Np) * G1(sample_p.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, + hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL)); + if (NtdotL > numeric_limits::min) { fresnel::Reflect reflectL = fresnel::Reflect::create(L, Nt); @@ -364,11 +249,10 @@ struct SMicrofacetNormals sample_type sample_reflected = sample_type::create(L_reflected, Np); pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction_Np) * - (scalar_type(1.0) - G1(sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), interaction.getNdotNp(), hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, NtdotL))); + (scalar_type(1.0) - G1(sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL))); } } - const scalar_type NtdotV = hlsl::dot(Nt, V); if (lambda_p < scalar_type(1.0) && NtdotV > numeric_limits::min) { fresnel::Reflect reflectV = fresnel::Reflect::create(V, Nt); @@ -391,41 +275,39 @@ struct SMicrofacetNormals } quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { - const vector3_type Np = interaction.getNp(); + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); - const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); const vector3_type V = interaction.getV(); - if (interaction.getNdotNp() <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { - typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, interaction.getN()); + typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); return nested_brdf.quotientAndWeight(_sample, interaction_N); } - typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); - typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); + const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); spectral_type quo = hlsl::promote(1.0); - sample_type neg_sample; - { - ray_dir_info_type L; - L.setDirection(-_sample.getL().getDirection()); - neg_sample = sample_type::create(L, Np); - } + // TODO: might need same interaction from branch in generate + typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); + typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); // TODO: cache? - quotient_weight_type qw = nested_brdf.quotientAndWeight(neg_sample, interaction); + quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction); quo *= qw.quotient(); - quo *= G1(_sample.getNdotL(), interaction.getNdotNp(), hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, hlsl::dot(Nt, L))); + const vector3_type L = _sample.getL().getDirection(); + quo *= G1(_sample.getNdotL(), NdotNp, hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, hlsl::dot(Nt, L))); return quotient_weight_type::create(quo, forwardPdf(_sample, interaction)); } bxdf_type nested_brdf; + vector3_type shadingNormal; }; } From 6d6421876527d62d311bdf150d7f325913fa882e Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 8 Apr 2026 15:18:10 +0700 Subject: [PATCH 03/32] fill in cache for generate and quotientAndWeight methods + some bug fixes --- .../bxdf/reflection/microfacet_normals.hlsl | 92 ++++++++++++------- 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index de96071837..0ddac7a011 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -21,13 +21,13 @@ namespace reflection template) struct SMicrofacetNormals { - using this_t = SMicrofacetNormals; + using this_t = SMicrofacetNormals; BXDF_CONFIG_TYPE_ALIASES(Config); + NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = false; // TODO: temp, should account for bsdfs at some point using random_type = conditional_t; - struct Cache {}; // TODO: cache type? - using isocache_type = Cache; - using anisocache_type = Cache; + using isocache_type = typename BRDF::isocache_type; + using anisocache_type = typename BRDF::anisocache_type; using bxdf_type = BRDF; NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = conditional_value::value; @@ -66,7 +66,7 @@ struct SMicrofacetNormals { return evalAndWeight(_sample, anisotropic_interaction_type::create(interaction)); } - value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { const vector3_type Np = interaction.getN(); const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); @@ -77,7 +77,8 @@ struct SMicrofacetNormals { typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); - return nested_brdf.evalAndWeight(_sample, interaction_N); + const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); + return nested_brdf.evalAndWeight(sample_N, interaction_N); } const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); @@ -89,7 +90,7 @@ struct SMicrofacetNormals const vector3_type L = _sample.getL().getDirection(); const scalar_type NpdotL = hlsl::dot(Np, L); const scalar_type NtdotL = hlsl::dot(Nt, L); - const scalar_type lambda_p = lambdaP(NdotNp, NpdotV, NtdotV); + const scalar_type lambda_p = lambdaP(NdotNp, NpdotV, NtdotV); const scalar_type shadowing = G1(_sample.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL)); @@ -106,7 +107,7 @@ struct SMicrofacetNormals } // i -> p -> t -> o - if (NtdotL > scalar_type(0.0)) + if (NtdotL > scalar_type(0.0)) { fresnel::Reflect reflectL = fresnel::Reflect::create(L, Nt); ray_dir_info_type L_reflected; @@ -117,8 +118,8 @@ struct SMicrofacetNormals hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL)); value_weight_type eval_double_p = nested_brdf.evalAndWeight(sample_double_p, interaction_Np); - eval += eval_double_p.value() * (lambda_p * notShadowedNpMirror * shadowing); - } + eval += eval_double_p.value() * (lambda_p * notShadowedNpMirror * shadowing); + } // i -> t -> p -> o if (NtdotV > scalar_type(0.0)) @@ -132,8 +133,8 @@ struct SMicrofacetNormals typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); value_weight_type eval_double_t = nested_brdf.evalAndWeight(sample_double_t, interaction_reflected); - eval += eval_double_t.value() * (scalar_type(1.0) - lambda_p) * shadowing; - } + eval += eval_double_t.value() * (scalar_type(1.0) - lambda_p) * shadowing; + } return value_weight_type::create(eval, forwardPdf(_sample, interaction)); } @@ -149,7 +150,7 @@ struct SMicrofacetNormals { typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); - return nested_brdf.generate(_sample, interaction_N); + return nested_brdf.generate(interaction_N, u, _cache); } const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); @@ -158,19 +159,19 @@ struct SMicrofacetNormals const scalar_type NtdotV = hlsl::dot(Nt, V); sample_type s; - if (u.x < lambdaP(NdotNp, NpdotV, NtdotV)) + if (u.x < lambdaP(NdotNp, NpdotV, NtdotV)) { - // sample on Np + // sample on Np typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); - s = nested_brdf.generate(interaction_Np, u); // TODO: cache? + s = nested_brdf.generate(interaction_Np, u, _cache); if (!s.isValid()) return s; const scalar_type shadowed = G1(s.getNdotL(), NdotNp, hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, hlsl::dot(Nt, L))); - + if (u.y > shadowed) { // if sample dir shadowed, reflect on Nt @@ -178,28 +179,32 @@ struct SMicrofacetNormals ray_dir_info_type s_reflected; s_reflected.setDirection(hlsl::normalize(reflect_s())); s = sample_type::create(s_reflected, Np); + cache = anisocache_type::createForReflection(interaction_Np.getTangentSpaceV(), s.getTangentSpaceL(), hlsl::dot(V, s_reflected.getDirection())); } - } + } else { - // do one reflection if we start at wt + // do one reflection if we start at wt fresnel::Reflect reflect_V = fresnel::Reflect::create(V, Nt); const vector3_type V_negreflected = -hlsl::normalize(reflect_V()); - // sample on wp + // sample on wp typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_negreflected, Np); typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); - s = nested_brdf.generate(interaction_Np, u); // TODO: cache? + s = nested_brdf.generate(interaction_Np, u, _cache); if (!s.isValid()) return s; - } + } if (s.getNdotL() < scalar_type(0.0)) return sample_type::createInvalid(); // for reflection return s; } sample_type generate(NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, const random_type u, NBL_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return generate(anisotropic_interaction_type::create(interaction), u, _cache); + anisocache_type aniso_cache; + sample_type s = generate(anisotropic_interaction_type::create(interaction), u, aniso_cache); + cache = aniso_cache.iso_cache; + return s; } scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC @@ -217,7 +222,8 @@ struct SMicrofacetNormals { typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); - return nested_brdf.forwardPdf(_sample, interaction_N); + const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); + return nested_brdf.forwardPdf(sample_N, interaction_N); } const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); @@ -229,7 +235,7 @@ struct SMicrofacetNormals const scalar_type lambda_p = lambdaP(NdotNp, NpdotV, NtdotV); const sample_type sample_p = sample_type::create(_sample.getL(), Np); - if (lambda_p > scalar_type(0.0)) + if (lambda_p > scalar_type(0.0)) { typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); @@ -241,7 +247,7 @@ struct SMicrofacetNormals pdf += lambda_p * nested_brdf.forwardPdf(sample_p, interaction_Np) * G1(sample_p.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL)); - if (NtdotL > numeric_limits::min) + if (NtdotL > numeric_limits::min) { fresnel::Reflect reflectL = fresnel::Reflect::create(L, Nt); ray_dir_info_type L_reflected; @@ -250,10 +256,10 @@ struct SMicrofacetNormals pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction_Np) * (scalar_type(1.0) - G1(sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL))); - } - } + } + } - if (lambda_p < scalar_type(1.0) && NtdotV > numeric_limits::min) + if (lambda_p < scalar_type(1.0) && NtdotV > numeric_limits::min) { fresnel::Reflect reflectV = fresnel::Reflect::create(V, Nt); ray_dir_info_type V_reflected; @@ -264,14 +270,17 @@ struct SMicrofacetNormals typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); pdf += (scalar_type(1.0) - lambda_p) * nested_brdf.forwardPdf(sample_p, interaction_reflected); - } + } - return pdf; + return pdf; } quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return quotientAndWeight(_sample, anisotropic_interaction_type::create(interaction), _cache); + anisocache_type aniso_cache; + quotient_weight_type quo_weight = quotientAndWeight(_sample, anisotropic_interaction_type::create(interaction), aniso_cache); + _cache = aniso_cache.iso_cache; + return quo_weight; } quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { @@ -284,7 +293,9 @@ struct SMicrofacetNormals { typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); - return nested_brdf.quotientAndWeight(_sample, interaction_N); + const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); + anisocache_type cache_N = anisocache_type::template createForReflection(interaction_N, sample_N); + return nested_brdf.quotientAndWeight(sample_N, interaction_N, cache_N); } const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); @@ -297,8 +308,8 @@ struct SMicrofacetNormals typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); // TODO: cache? - quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction); - quo *= qw.quotient(); + quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction, _cache); + quo *= qw.quotient(); const vector3_type L = _sample.getL().getDirection(); quo *= G1(_sample.getNdotL(), NdotNp, hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, hlsl::dot(Nt, L))); @@ -311,6 +322,17 @@ struct SMicrofacetNormals }; } + +template +struct traits > +{ + NBL_CONSTEXPR_STATIC_INLINE BxDFType type = BT_BRDF; + NBL_CONSTEXPR_STATIC_INLINE bool IsMicrofacet = false; + NBL_CONSTEXPR_STATIC_INLINE bool clampNdotV = false; + NBL_CONSTEXPR_STATIC_INLINE bool clampNdotL = true; + NBL_CONSTEXPR_STATIC_INLINE bool TractablePdf = true; +}; + } } } From 5c6a0c84a88aa4bd5ee98e870574fc2ee75b78fd Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 9 Apr 2026 11:28:04 +0700 Subject: [PATCH 04/32] various bug fixes so it runs + add to cmakelist sources --- include/nbl/builtin/hlsl/bxdf/reflection.hlsl | 1 + .../bxdf/reflection/microfacet_normals.hlsl | 69 ++++++++++--------- src/nbl/builtin/CMakeLists.txt | 1 + 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection.hlsl index c5d4b019c8..d0cb829039 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection.hlsl @@ -10,6 +10,7 @@ #include "nbl/builtin/hlsl/bxdf/reflection/beckmann.hlsl" #include "nbl/builtin/hlsl/bxdf/reflection/ggx.hlsl" #include "nbl/builtin/hlsl/bxdf/reflection/iridescent.hlsl" +#include "nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl" namespace nbl { diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 0ddac7a011..732fa9d51e 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -72,7 +72,7 @@ struct SMicrofacetNormals const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); - const vector3_type V = interaction.getV(); + const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); @@ -81,10 +81,10 @@ struct SMicrofacetNormals return nested_brdf.evalAndWeight(sample_N, interaction_N); } - const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); const scalar_type NpdotV = interaction.getNdotV(); - const scalar_type NtdotV = hlsl::dot(Nt, V); + const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); spectral_type eval = hlsl::promote(0.0); const vector3_type L = _sample.getL().getDirection(); @@ -92,7 +92,7 @@ struct SMicrofacetNormals const scalar_type NtdotL = hlsl::dot(Nt, L); const scalar_type lambda_p = lambdaP(NdotNp, NpdotV, NtdotV); const scalar_type shadowing = G1(_sample.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, - hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL)); + hlsl::max(scalar_type(0.0), NpdotL), hlsl::max(scalar_type(0.0), NtdotL)); typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); @@ -109,13 +109,13 @@ struct SMicrofacetNormals // i -> p -> t -> o if (NtdotL > scalar_type(0.0)) { - fresnel::Reflect reflectL = fresnel::Reflect::create(L, Nt); + Reflect reflectL = Reflect::create(L, Nt); ray_dir_info_type L_reflected; L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_double_p = sample_type::create(L_reflected, Np); // Nt? const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, - hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL)); + hlsl::max(scalar_type(0.0), NpdotL), hlsl::max(scalar_type(0.0), NtdotL)); value_weight_type eval_double_p = nested_brdf.evalAndWeight(sample_double_p, interaction_Np); eval += eval_double_p.value() * (lambda_p * notShadowedNpMirror * shadowing); @@ -124,12 +124,12 @@ struct SMicrofacetNormals // i -> t -> p -> o if (NtdotV > scalar_type(0.0)) { - fresnel::Reflect reflectV = fresnel::Reflect::create(V, Nt); + Reflect reflectV = Reflect::create(V.getDirection(), Nt); ray_dir_info_type V_reflected; V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); sample_type sample_double_t = sample_type::create(V_reflected, Np); // Nt? - typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected.getDirection(), Np); + typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected, Np); typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); value_weight_type eval_double_t = nested_brdf.evalAndWeight(sample_double_t, interaction_reflected); @@ -145,7 +145,7 @@ struct SMicrofacetNormals const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); - const vector3_type V = interaction.getV(); + const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); @@ -153,10 +153,10 @@ struct SMicrofacetNormals return nested_brdf.generate(interaction_N, u, _cache); } - const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); const scalar_type NpdotV = interaction.getNdotV(); - const scalar_type NtdotV = hlsl::dot(Nt, V); + const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); sample_type s; if (u.x < lambdaP(NdotNp, NpdotV, NtdotV)) @@ -169,29 +169,32 @@ struct SMicrofacetNormals if (!s.isValid()) return s; + const vector3_type L = s.getL().getDirection(); const scalar_type shadowed = G1(s.getNdotL(), NdotNp, - hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, hlsl::dot(Nt, L))); + hlsl::max(scalar_type(0.0), hlsl::dot(Np, L)), hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); if (u.y > shadowed) { // if sample dir shadowed, reflect on Nt - fresnel::Reflect reflect_s = fresnel::Reflect::create(s.getL().getDirection(), Nt); + Reflect reflect_s = Reflect::create(L, Nt); ray_dir_info_type s_reflected; s_reflected.setDirection(hlsl::normalize(reflect_s())); s = sample_type::create(s_reflected, Np); - cache = anisocache_type::createForReflection(interaction_Np.getTangentSpaceV(), s.getTangentSpaceL(), hlsl::dot(V, s_reflected.getDirection())); + // _cache = anisocache_type::createForReflection(interaction_Np.getTangentSpaceV(), s.getTangentSpaceL(), hlsl::dot(V, s_reflected.getDirection())); // TODO: remove } } else { // do one reflection if we start at wt - fresnel::Reflect reflect_V = fresnel::Reflect::create(V, Nt); + Reflect reflect_V = Reflect::create(V.getDirection(), Nt); const vector3_type V_negreflected = -hlsl::normalize(reflect_V()); // sample on wp - typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_negreflected, Np); - typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); - s = nested_brdf.generate(interaction_Np, u, _cache); + ray_dir_info_type Vnr; + Vnr.setDirection(V_negreflected); + typename bxdf_type::isotropic_interaction_type iso_negreflected = typename bxdf_type::isotropic_interaction_type::create(Vnr, Np); + typename bxdf_type::anisotropic_interaction_type interaction_negreflected = typename bxdf_type::anisotropic_interaction_type::create(iso_negreflected); + s = nested_brdf.generate(interaction_negreflected, u, _cache); if (!s.isValid()) return s; } @@ -203,7 +206,7 @@ struct SMicrofacetNormals { anisocache_type aniso_cache; sample_type s = generate(anisotropic_interaction_type::create(interaction), u, aniso_cache); - cache = aniso_cache.iso_cache; + // _cache = aniso_cache.iso_cache; // TODO: remove return s; } @@ -217,7 +220,7 @@ struct SMicrofacetNormals const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); - const vector3_type V = interaction.getV(); + const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); @@ -226,10 +229,10 @@ struct SMicrofacetNormals return nested_brdf.forwardPdf(sample_N, interaction_N); } - const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); const scalar_type NpdotV = interaction.getNdotV(); - const scalar_type NtdotV = hlsl::dot(Nt, V); + const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); scalar_type pdf = scalar_type(0.0); const scalar_type lambda_p = lambdaP(NdotNp, NpdotV, NtdotV); @@ -245,28 +248,28 @@ struct SMicrofacetNormals const scalar_type NpdotL = hlsl::dot(Np, L); const scalar_type NtdotL = hlsl::dot(Nt, L); pdf += lambda_p * nested_brdf.forwardPdf(sample_p, interaction_Np) * G1(sample_p.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, - hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL)); + hlsl::max(scalar_type(0.0), NpdotL), hlsl::max(scalar_type(0.0), NtdotL)); if (NtdotL > numeric_limits::min) { - fresnel::Reflect reflectL = fresnel::Reflect::create(L, Nt); + Reflect reflectL = Reflect::create(L, Nt); ray_dir_info_type L_reflected; L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_reflected = sample_type::create(L_reflected, Np); pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction_Np) * - (scalar_type(1.0) - G1(sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, hlsl::max(0.0, NpdotL), hlsl::max(0.0, NtdotL))); + (scalar_type(1.0) - G1(sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, hlsl::max(scalar_type(0.0), NpdotL), hlsl::max(scalar_type(0.0), NtdotL))); } } if (lambda_p < scalar_type(1.0) && NtdotV > numeric_limits::min) { - fresnel::Reflect reflectV = fresnel::Reflect::create(V, Nt); + Reflect reflectV = Reflect::create(V.getDirection(), Nt); ray_dir_info_type V_reflected; - V_reflected.setDirection(hlsl::normalize(reflectL(NtdotV))); + V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); // sample_type sample_p = sample_type::create(L, Np); // Nt? - typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected.getDirection(), Np); + typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected, Np); typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); pdf += (scalar_type(1.0) - lambda_p) * nested_brdf.forwardPdf(sample_p, interaction_reflected); @@ -279,7 +282,7 @@ struct SMicrofacetNormals { anisocache_type aniso_cache; quotient_weight_type quo_weight = quotientAndWeight(_sample, anisotropic_interaction_type::create(interaction), aniso_cache); - _cache = aniso_cache.iso_cache; + // _cache = aniso_cache.iso_cache; // TODO: remove return quo_weight; } quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC @@ -288,17 +291,17 @@ struct SMicrofacetNormals const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); - const vector3_type V = interaction.getV(); + const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); - anisocache_type cache_N = anisocache_type::template createForReflection(interaction_N, sample_N); + anisocache_type cache_N;// = anisocache_type::template createForReflection(interaction_N, sample_N); // TODO: remove return nested_brdf.quotientAndWeight(sample_N, interaction_N, cache_N); } - const vector3_type local_Nt = hlsl::normalize(vector3_type(-local_Np.xy, 0.0)); + const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); spectral_type quo = hlsl::promote(1.0); @@ -312,7 +315,7 @@ struct SMicrofacetNormals quo *= qw.quotient(); const vector3_type L = _sample.getL().getDirection(); - quo *= G1(_sample.getNdotL(), NdotNp, hlsl::max(0.0, hlsl::dot(Np, L)), hlsl::max(0.0, hlsl::dot(Nt, L))); + quo *= G1(_sample.getNdotL(), NdotNp, hlsl::max(scalar_type(0.0), hlsl::dot(Np, L)), hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); return quotient_weight_type::create(quo, forwardPdf(_sample, interaction)); } diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index 2057e56ba4..15bc4d19d6 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -315,6 +315,7 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/reflection/lambertian.hl 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/microfacet_normals.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") From f9171aae5e562b2055c0e78a29ae5f5cd61e7dc6 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 9 Apr 2026 15:33:32 +0700 Subject: [PATCH 05/32] fix avoid nans --- .../nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 732fa9d51e..9c32dd8408 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -49,7 +49,7 @@ struct SMicrofacetNormals static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) { - const scalar_type sinThetaNp = hlsl::sqrt(1.0 - NdotNp * NdotNp); + const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); return hlsl::min(scalar_type(1.0), clampedNdotL * hlsl::max(scalar_type(0.0), NdotNp) / (clampedNpdotL + clampedNtdotL * sinThetaNp) @@ -58,7 +58,7 @@ struct SMicrofacetNormals static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type NpdotV, const scalar_type NtdotV) { - const scalar_type sinThetaNp = hlsl::sqrt(1.0 - NdotNp * NdotNp); + const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); return NpdotV / (NpdotV + NtdotV * sinThetaNp); } From cfbd1e9e3aec3d3e0bfc99ad39375a7772e682d1 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 9 Apr 2026 17:05:24 +0700 Subject: [PATCH 06/32] fix confusion in usage of shading vs perturbed normals --- .../bxdf/reflection/microfacet_normals.hlsl | 62 ++++++++----------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 9c32dd8408..c2360bf1ee 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -56,10 +56,10 @@ struct SMicrofacetNormals ); } - static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type NpdotV, const scalar_type NtdotV) + static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) { const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); - return NpdotV / (NpdotV + NtdotV * sinThetaNp); + return clampedNpdotV / (clampedNpdotV + clampedNtdotV * sinThetaNp); } value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC @@ -88,21 +88,17 @@ struct SMicrofacetNormals spectral_type eval = hlsl::promote(0.0); const vector3_type L = _sample.getL().getDirection(); - const scalar_type NpdotL = hlsl::dot(Np, L); + const scalar_type NdotL = hlsl::dot(shadingNormal, L); + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); const scalar_type NtdotL = hlsl::dot(Nt, L); - const scalar_type lambda_p = lambdaP(NdotNp, NpdotV, NtdotV); - const scalar_type shadowing = G1(_sample.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, - hlsl::max(scalar_type(0.0), NpdotL), hlsl::max(scalar_type(0.0), NtdotL)); - - typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); - typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); + const scalar_type lambda_p = lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); + const scalar_type shadowing = G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, + NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); // i -> p -> o { - sample_type sample_single_p = sample_type::create(_sample.getL(), Np); - // TODO: what to do with cache? - value_weight_type eval_single_p = nested_brdf.evalAndWeight(sample_single_p, interaction_Np); + value_weight_type eval_single_p = nested_brdf.evalAndWeight(_sample, interaction); eval += eval_single_p.value() * lambda_p * shadowing; } @@ -114,10 +110,10 @@ struct SMicrofacetNormals L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_double_p = sample_type::create(L_reflected, Np); // Nt? - const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, - hlsl::max(scalar_type(0.0), NpdotL), hlsl::max(scalar_type(0.0), NtdotL)); + const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, + sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal))); - value_weight_type eval_double_p = nested_brdf.evalAndWeight(sample_double_p, interaction_Np); + value_weight_type eval_double_p = nested_brdf.evalAndWeight(sample_double_p, interaction); eval += eval_double_p.value() * (lambda_p * notShadowedNpMirror * shadowing); } @@ -136,7 +132,7 @@ struct SMicrofacetNormals eval += eval_double_t.value() * (scalar_type(1.0) - lambda_p) * shadowing; } - return value_weight_type::create(eval, forwardPdf(_sample, interaction)); + return value_weight_type::create(eval * NpdotL, forwardPdf(_sample, interaction)); } sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const random_type u, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC @@ -159,19 +155,17 @@ struct SMicrofacetNormals const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); sample_type s; - if (u.x < lambdaP(NdotNp, NpdotV, NtdotV)) + if (u.x < lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV))) { // sample on Np - typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); - typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); - s = nested_brdf.generate(interaction_Np, u, _cache); + s = nested_brdf.generate(interaction, u, _cache); if (!s.isValid()) return s; const vector3_type L = s.getL().getDirection(); - const scalar_type shadowed = G1(s.getNdotL(), NdotNp, - hlsl::max(scalar_type(0.0), hlsl::dot(Np, L)), hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); + const scalar_type shadowed = G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, + s.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); if (u.y > shadowed) { @@ -235,20 +229,17 @@ struct SMicrofacetNormals const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); scalar_type pdf = scalar_type(0.0); - const scalar_type lambda_p = lambdaP(NdotNp, NpdotV, NtdotV); - const sample_type sample_p = sample_type::create(_sample.getL(), Np); + const scalar_type lambda_p = lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); if (lambda_p > scalar_type(0.0)) { - typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); - typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); - // TODO: cache? const vector3_type L = _sample.getL().getDirection(); - const scalar_type NpdotL = hlsl::dot(Np, L); + const scalar_type NdotL = hlsl::dot(shadingNormal, L); + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); const scalar_type NtdotL = hlsl::dot(Nt, L); - pdf += lambda_p * nested_brdf.forwardPdf(sample_p, interaction_Np) * G1(sample_p.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, - hlsl::max(scalar_type(0.0), NpdotL), hlsl::max(scalar_type(0.0), NtdotL)); + pdf += lambda_p * nested_brdf.forwardPdf(_sample, interaction) * G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, + NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); if (NtdotL > numeric_limits::min) { @@ -257,8 +248,8 @@ struct SMicrofacetNormals L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_reflected = sample_type::create(L_reflected, Np); - pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction_Np) * - (scalar_type(1.0) - G1(sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), NdotNp, hlsl::max(scalar_type(0.0), NpdotL), hlsl::max(scalar_type(0.0), NtdotL))); + pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction) * + (scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), NtdotL))); } } @@ -272,7 +263,7 @@ struct SMicrofacetNormals typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected, Np); typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); - pdf += (scalar_type(1.0) - lambda_p) * nested_brdf.forwardPdf(sample_p, interaction_reflected); + pdf += (scalar_type(1.0) - lambda_p) * nested_brdf.forwardPdf(_sample, interaction_reflected); } return pdf; @@ -307,15 +298,14 @@ struct SMicrofacetNormals spectral_type quo = hlsl::promote(1.0); // TODO: might need same interaction from branch in generate - typename bxdf_type::isotropic_interaction_type iso_Np = typename bxdf_type::isotropic_interaction_type::create(V, Np); - typename bxdf_type::anisotropic_interaction_type interaction_Np = typename bxdf_type::anisotropic_interaction_type::create(iso_Np); // TODO: cache? + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction, _cache); quo *= qw.quotient(); const vector3_type L = _sample.getL().getDirection(); - quo *= G1(_sample.getNdotL(), NdotNp, hlsl::max(scalar_type(0.0), hlsl::dot(Np, L)), hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); + quo *= G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); return quotient_weight_type::create(quo, forwardPdf(_sample, interaction)); } From 8ef9c3bb86f95b5f5182bbc78bd6651ebf375151 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 10 Apr 2026 14:17:26 +0700 Subject: [PATCH 07/32] correct basis transforms for getting Nt --- .../bxdf/reflection/microfacet_normals.hlsl | 65 ++++++++++++------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index c2360bf1ee..5ca2fc1f44 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -28,6 +28,7 @@ struct SMicrofacetNormals using random_type = conditional_t; using isocache_type = typename BRDF::isocache_type; using anisocache_type = typename BRDF::anisocache_type; + using matrix3x3_type = matrix; using bxdf_type = BRDF; NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = conditional_value::value; @@ -62,6 +63,18 @@ struct SMicrofacetNormals return clampedNpdotV / (clampedNpdotV + clampedNtdotV * sinThetaNp); } + // static scalar_type lambdaP_alt(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV, bool t) + // { + // const scalar_type ap = clampedNpdotV / NdotNp; + // const scalar_type at = clampedNtdotV * hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)) / NdotNp; + // return t ? at / (ap + at) : ap / (ap + at); + // } + + static vector3_type __reflect(const vector3_type N, const vector3_type I) + { + return hlsl::normalize(I - scalar_type(2.0) * hlsl::dot(I, N) * N); + } + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { return evalAndWeight(_sample, anisotropic_interaction_type::create(interaction)); @@ -70,7 +83,7 @@ struct SMicrofacetNormals { const vector3_type Np = interaction.getN(); const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); - const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); + const vector3_type local_Np = hlsl::mul(shadingBasis, Np); const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) @@ -82,7 +95,7 @@ struct SMicrofacetNormals } const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); + const vector3_type Nt = hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); spectral_type eval = hlsl::promote(0.0); @@ -105,9 +118,10 @@ struct SMicrofacetNormals // i -> p -> t -> o if (NtdotL > scalar_type(0.0)) { - Reflect reflectL = Reflect::create(L, Nt); + // Reflect reflectL = Reflect::create(L, Nt); ray_dir_info_type L_reflected; - L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); + // L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); + L_reflected.setDirection(__reflect(Nt, L)); sample_type sample_double_p = sample_type::create(L_reflected, Np); // Nt? const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, @@ -120,26 +134,26 @@ struct SMicrofacetNormals // i -> t -> p -> o if (NtdotV > scalar_type(0.0)) { - Reflect reflectV = Reflect::create(V.getDirection(), Nt); + // Reflect reflectV = Reflect::create(V.getDirection(), Nt); ray_dir_info_type V_reflected; - V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); - sample_type sample_double_t = sample_type::create(V_reflected, Np); // Nt? + // V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); + V_reflected.setDirection(__reflect(Nt, V.getDirection())); typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected, Np); typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); - value_weight_type eval_double_t = nested_brdf.evalAndWeight(sample_double_t, interaction_reflected); + value_weight_type eval_double_t = nested_brdf.evalAndWeight(_sample, interaction_reflected); eval += eval_double_t.value() * (scalar_type(1.0) - lambda_p) * shadowing; } - return value_weight_type::create(eval * NpdotL, forwardPdf(_sample, interaction)); + return value_weight_type::create(eval, forwardPdf(_sample, interaction)); } sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const random_type u, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { const vector3_type Np = interaction.getN(); const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); - const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); + const vector3_type local_Np = hlsl::mul(shadingBasis, Np); const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) @@ -150,7 +164,7 @@ struct SMicrofacetNormals } const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); + const vector3_type Nt = hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); @@ -170,9 +184,10 @@ struct SMicrofacetNormals if (u.y > shadowed) { // if sample dir shadowed, reflect on Nt - Reflect reflect_s = Reflect::create(L, Nt); + // Reflect reflect_s = Reflect::create(L, Nt); ray_dir_info_type s_reflected; - s_reflected.setDirection(hlsl::normalize(reflect_s())); + // s_reflected.setDirection(hlsl::normalize(reflect_s())); + s_reflected.setDirection(hlsl::normalize(L + scalar_type(2.0) * hlsl::dot(-L, Nt) * Nt)); s = sample_type::create(s_reflected, Np); // _cache = anisocache_type::createForReflection(interaction_Np.getTangentSpaceV(), s.getTangentSpaceL(), hlsl::dot(V, s_reflected.getDirection())); // TODO: remove } @@ -180,8 +195,9 @@ struct SMicrofacetNormals else { // do one reflection if we start at wt - Reflect reflect_V = Reflect::create(V.getDirection(), Nt); - const vector3_type V_negreflected = -hlsl::normalize(reflect_V()); + // Reflect reflect_V = Reflect::create(V.getDirection(), Nt); + // const vector3_type V_negreflected = -hlsl::normalize(reflect_V()); + const vector3_type V_negreflected = hlsl::normalize(V.getDirection() + scalar_type(2.0) * hlsl::dot(-V.getDirection(), Nt) * Nt); // sample on wp ray_dir_info_type Vnr; @@ -212,7 +228,7 @@ struct SMicrofacetNormals { const vector3_type Np = interaction.getN(); const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); - const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); + const vector3_type local_Np = hlsl::mul(shadingBasis, Np); const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) @@ -224,7 +240,7 @@ struct SMicrofacetNormals } const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); + const vector3_type Nt = hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); @@ -243,9 +259,10 @@ struct SMicrofacetNormals if (NtdotL > numeric_limits::min) { - Reflect reflectL = Reflect::create(L, Nt); + // Reflect reflectL = Reflect::create(L, Nt); ray_dir_info_type L_reflected; - L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); + // L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); + L_reflected.setDirection(__reflect(Nt, L)); sample_type sample_reflected = sample_type::create(L_reflected, Np); pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction) * @@ -255,9 +272,10 @@ struct SMicrofacetNormals if (lambda_p < scalar_type(1.0) && NtdotV > numeric_limits::min) { - Reflect reflectV = Reflect::create(V.getDirection(), Nt); + // Reflect reflectV = Reflect::create(V.getDirection(), Nt); ray_dir_info_type V_reflected; - V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); + // V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); + V_reflected.setDirection(__reflect(Nt, V.getDirection())); // sample_type sample_p = sample_type::create(L, Np); // Nt? typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected, Np); @@ -280,7 +298,7 @@ struct SMicrofacetNormals { const vector3_type Np = interaction.getN(); const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); - const vector3_type local_Np = hlsl::mul(interaction.getToTangentSpace(), Np); + const vector3_type local_Np = hlsl::mul(shadingBasis, Np); const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) @@ -293,7 +311,7 @@ struct SMicrofacetNormals } const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(interaction.getFromTangentSpace(), local_Nt); + const vector3_type Nt = hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); spectral_type quo = hlsl::promote(1.0); @@ -312,6 +330,7 @@ struct SMicrofacetNormals bxdf_type nested_brdf; vector3_type shadingNormal; + matrix3x3_type shadingBasis; }; } From 428dfa7f67775a1e0c259a7e6d08431d5b12eccc Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 10 Apr 2026 15:10:00 +0700 Subject: [PATCH 08/32] fix wrong normals to G1 in eval --- .../nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 5ca2fc1f44..38d673317f 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -125,7 +125,7 @@ struct SMicrofacetNormals sample_type sample_double_p = sample_type::create(L_reflected, Np); // Nt? const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, - sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal))); + sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), Nt))); value_weight_type eval_double_p = nested_brdf.evalAndWeight(sample_double_p, interaction); eval += eval_double_p.value() * (lambda_p * notShadowedNpMirror * shadowing); From 4dfae88c5d0616aee4b579f8ed2ced2029e306b2 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 10 Apr 2026 16:54:35 +0700 Subject: [PATCH 09/32] use our own reflect because it works, custom cache type to pass condition from generate to quotient --- .../bxdf/reflection/microfacet_normals.hlsl | 92 ++++++++----------- 1 file changed, 37 insertions(+), 55 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 38d673317f..1d45c672aa 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -26,8 +26,14 @@ struct SMicrofacetNormals NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = false; // TODO: temp, should account for bsdfs at some point using random_type = conditional_t; - using isocache_type = typename BRDF::isocache_type; - using anisocache_type = typename BRDF::anisocache_type; + struct Cache + { + typename BRDF::isocache_type iso_cache; + typename BRDF::anisocache_type aniso_cache; + bool sampleIsShadowed; + }; + using isocache_type = Cache; + using anisocache_type = Cache; using matrix3x3_type = matrix; using bxdf_type = BRDF; @@ -63,18 +69,6 @@ struct SMicrofacetNormals return clampedNpdotV / (clampedNpdotV + clampedNtdotV * sinThetaNp); } - // static scalar_type lambdaP_alt(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV, bool t) - // { - // const scalar_type ap = clampedNpdotV / NdotNp; - // const scalar_type at = clampedNtdotV * hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)) / NdotNp; - // return t ? at / (ap + at) : ap / (ap + at); - // } - - static vector3_type __reflect(const vector3_type N, const vector3_type I) - { - return hlsl::normalize(I - scalar_type(2.0) * hlsl::dot(I, N) * N); - } - value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { return evalAndWeight(_sample, anisotropic_interaction_type::create(interaction)); @@ -110,7 +104,6 @@ struct SMicrofacetNormals // i -> p -> o { - // TODO: what to do with cache? value_weight_type eval_single_p = nested_brdf.evalAndWeight(_sample, interaction); eval += eval_single_p.value() * lambda_p * shadowing; } @@ -118,11 +111,10 @@ struct SMicrofacetNormals // i -> p -> t -> o if (NtdotL > scalar_type(0.0)) { - // Reflect reflectL = Reflect::create(L, Nt); + Reflect reflectL = Reflect::create(L, Nt); ray_dir_info_type L_reflected; - // L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); - L_reflected.setDirection(__reflect(Nt, L)); - sample_type sample_double_p = sample_type::create(L_reflected, Np); // Nt? + L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); + sample_type sample_double_p = sample_type::create(L_reflected, Np); const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), Nt))); @@ -134,10 +126,9 @@ struct SMicrofacetNormals // i -> t -> p -> o if (NtdotV > scalar_type(0.0)) { - // Reflect reflectV = Reflect::create(V.getDirection(), Nt); + Reflect reflectV = Reflect::create(V.getDirection(), Nt); ray_dir_info_type V_reflected; - // V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); - V_reflected.setDirection(__reflect(Nt, V.getDirection())); + V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected, Np); typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); @@ -149,18 +140,19 @@ struct SMicrofacetNormals return value_weight_type::create(eval, forwardPdf(_sample, interaction)); } - sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const random_type u, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC + sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const random_type u, NBL_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { const vector3_type Np = interaction.getN(); const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); const vector3_type local_Np = hlsl::mul(shadingBasis, Np); + _cache.sampleIsShadowed = false; const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); - return nested_brdf.generate(interaction_N, u, _cache); + return nested_brdf.generate(interaction_N, u, _cache.aniso_cache); } const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); @@ -172,7 +164,7 @@ struct SMicrofacetNormals if (u.x < lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV))) { // sample on Np - s = nested_brdf.generate(interaction, u, _cache); + s = nested_brdf.generate(interaction, u, _cache.aniso_cache); if (!s.isValid()) return s; @@ -184,27 +176,26 @@ struct SMicrofacetNormals if (u.y > shadowed) { // if sample dir shadowed, reflect on Nt - // Reflect reflect_s = Reflect::create(L, Nt); + Reflect reflect_s = Reflect::create(-L, Nt); ray_dir_info_type s_reflected; - // s_reflected.setDirection(hlsl::normalize(reflect_s())); - s_reflected.setDirection(hlsl::normalize(L + scalar_type(2.0) * hlsl::dot(-L, Nt) * Nt)); + s_reflected.setDirection(hlsl::normalize(reflect_s())); s = sample_type::create(s_reflected, Np); + _cache.sampleIsShadowed = true; // _cache = anisocache_type::createForReflection(interaction_Np.getTangentSpaceV(), s.getTangentSpaceL(), hlsl::dot(V, s_reflected.getDirection())); // TODO: remove } } else { // do one reflection if we start at wt - // Reflect reflect_V = Reflect::create(V.getDirection(), Nt); - // const vector3_type V_negreflected = -hlsl::normalize(reflect_V()); - const vector3_type V_negreflected = hlsl::normalize(V.getDirection() + scalar_type(2.0) * hlsl::dot(-V.getDirection(), Nt) * Nt); + Reflect reflect_V = Reflect::create(-V.getDirection(), Nt); + const vector3_type V_negreflected = hlsl::normalize(reflect_V()); // sample on wp ray_dir_info_type Vnr; Vnr.setDirection(V_negreflected); typename bxdf_type::isotropic_interaction_type iso_negreflected = typename bxdf_type::isotropic_interaction_type::create(Vnr, Np); typename bxdf_type::anisotropic_interaction_type interaction_negreflected = typename bxdf_type::anisotropic_interaction_type::create(iso_negreflected); - s = nested_brdf.generate(interaction_negreflected, u, _cache); + s = nested_brdf.generate(interaction_negreflected, u, _cache.aniso_cache); if (!s.isValid()) return s; } @@ -214,9 +205,8 @@ struct SMicrofacetNormals } sample_type generate(NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, const random_type u, NBL_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC { - anisocache_type aniso_cache; - sample_type s = generate(anisotropic_interaction_type::create(interaction), u, aniso_cache); - // _cache = aniso_cache.iso_cache; // TODO: remove + sample_type s = generate(anisotropic_interaction_type::create(interaction), u, _cache); + // _cache.iso_cache = _cache.aniso_cache.isotropic; // TODO: remove? return s; } @@ -249,7 +239,6 @@ struct SMicrofacetNormals if (lambda_p > scalar_type(0.0)) { - // TODO: cache? const vector3_type L = _sample.getL().getDirection(); const scalar_type NdotL = hlsl::dot(shadingNormal, L); const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); @@ -259,10 +248,9 @@ struct SMicrofacetNormals if (NtdotL > numeric_limits::min) { - // Reflect reflectL = Reflect::create(L, Nt); + Reflect reflectL = Reflect::create(L, Nt); ray_dir_info_type L_reflected; - // L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); - L_reflected.setDirection(__reflect(Nt, L)); + L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_reflected = sample_type::create(L_reflected, Np); pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction) * @@ -272,11 +260,9 @@ struct SMicrofacetNormals if (lambda_p < scalar_type(1.0) && NtdotV > numeric_limits::min) { - // Reflect reflectV = Reflect::create(V.getDirection(), Nt); + Reflect reflectV = Reflect::create(V.getDirection(), Nt); ray_dir_info_type V_reflected; - // V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); - V_reflected.setDirection(__reflect(Nt, V.getDirection())); - // sample_type sample_p = sample_type::create(L, Np); // Nt? + V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected, Np); typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); @@ -289,10 +275,7 @@ struct SMicrofacetNormals quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC { - anisocache_type aniso_cache; - quotient_weight_type quo_weight = quotientAndWeight(_sample, anisotropic_interaction_type::create(interaction), aniso_cache); - // _cache = aniso_cache.iso_cache; // TODO: remove - return quo_weight; + return quotientAndWeight(_sample, anisotropic_interaction_type::create(interaction), _cache); } quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { @@ -306,24 +289,23 @@ struct SMicrofacetNormals typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); - anisocache_type cache_N;// = anisocache_type::template createForReflection(interaction_N, sample_N); // TODO: remove + typename BRDF::anisocache_type cache_N;// = anisocache_type::template createForReflection(interaction_N, sample_N); // TODO: remove return nested_brdf.quotientAndWeight(sample_N, interaction_N, cache_N); } const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); const vector3_type Nt = hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); - spectral_type quo = hlsl::promote(1.0); - // TODO: might need same interaction from branch in generate - - // TODO: cache? const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); - quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction, _cache); + quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction, _cache.aniso_cache); quo *= qw.quotient(); - const vector3_type L = _sample.getL().getDirection(); - quo *= G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); + if (_cache.sampleIsShadowed) + { + const vector3_type L = _sample.getL().getDirection(); + quo *= G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); + } return quotient_weight_type::create(quo, forwardPdf(_sample, interaction)); } From 29d3f2ac5c698b3447506e58feb2a5c9db86d8a1 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 13 Apr 2026 11:32:02 +0700 Subject: [PATCH 10/32] separate cache creation by bxdf microfacet --- .../bxdf/reflection/microfacet_normals.hlsl | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 1d45c672aa..056316b24c 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -25,17 +25,17 @@ struct SMicrofacetNormals BXDF_CONFIG_TYPE_ALIASES(Config); NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = false; // TODO: temp, should account for bsdfs at some point + using bxdf_type = BRDF; using random_type = conditional_t; struct Cache { - typename BRDF::isocache_type iso_cache; - typename BRDF::anisocache_type aniso_cache; + typename bxdf_type::isocache_type iso_cache; + typename bxdf_type::anisocache_type aniso_cache; bool sampleIsShadowed; }; using isocache_type = Cache; using anisocache_type = Cache; using matrix3x3_type = matrix; - using bxdf_type = BRDF; NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = conditional_value::value; @@ -69,6 +69,18 @@ struct SMicrofacetNormals return clampedNpdotV / (clampedNpdotV + clampedNtdotV * sinThetaNp); } + template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && !traits::IsMicrofacet) + static typename bxdf_type::anisocache_type __createChildCache(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) + { + typename bxdf_type::anisocache_type cache; + return cache; + } + template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && traits::IsMicrofacet) + static typename bxdf_type::anisocache_type __createChildCache(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) + { + return typename bxdf_type::anisocache_type::template createForReflection(interaction, _sample); + } + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { return evalAndWeight(_sample, anisotropic_interaction_type::create(interaction)); @@ -181,7 +193,7 @@ struct SMicrofacetNormals s_reflected.setDirection(hlsl::normalize(reflect_s())); s = sample_type::create(s_reflected, Np); _cache.sampleIsShadowed = true; - // _cache = anisocache_type::createForReflection(interaction_Np.getTangentSpaceV(), s.getTangentSpaceL(), hlsl::dot(V, s_reflected.getDirection())); // TODO: remove + _cache.aniso_cache = __createChildCache(s, interaction); } } else @@ -289,7 +301,7 @@ struct SMicrofacetNormals typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); - typename BRDF::anisocache_type cache_N;// = anisocache_type::template createForReflection(interaction_N, sample_N); // TODO: remove + typename bxdf_type::anisocache_type cache_N = __createChildCache(sample_N, interaction_N); return nested_brdf.quotientAndWeight(sample_N, interaction_N, cache_N); } @@ -321,7 +333,7 @@ template struct traits > { NBL_CONSTEXPR_STATIC_INLINE BxDFType type = BT_BRDF; - NBL_CONSTEXPR_STATIC_INLINE bool IsMicrofacet = false; + NBL_CONSTEXPR_STATIC_INLINE bool IsMicrofacet = false; // should be microfacet? NBL_CONSTEXPR_STATIC_INLINE bool clampNdotV = false; NBL_CONSTEXPR_STATIC_INLINE bool clampNdotL = true; NBL_CONSTEXPR_STATIC_INLINE bool TractablePdf = true; From 84fcf79b34d240fd11d26ed1da43241afd229af6 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 13 Apr 2026 14:13:50 +0700 Subject: [PATCH 11/32] specialize call bxdf forwardPdf, some bug fixes --- include/nbl/builtin/hlsl/bxdf/common.hlsl | 2 +- include/nbl/builtin/hlsl/bxdf/fresnel.hlsl | 12 +++++------ .../bxdf/reflection/microfacet_normals.hlsl | 21 ++++++++++++++----- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/common.hlsl b/include/nbl/builtin/hlsl/bxdf/common.hlsl index 54e4a1b145..f94d0abbf9 100644 --- a/include/nbl/builtin/hlsl/bxdf/common.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/common.hlsl @@ -616,7 +616,7 @@ struct SIsotropicMicrofacetCache // not coming from the medium (reflected) OR // exiting at the macro scale AND ( (not L outside the cone of possible directions given IoR with constraint VdotH*LdotH<0.0) OR (microfacet not facing toward the macrosurface, i.e. non heightfield profile of microsurface) ) - const bool valid = ComputeMicrofacetNormal::isValidMicrofacet(transmitted, VdotL, retval.absNdotH, fresnel::OrientedEtas::create(1.0, computeMicrofacetNormal.orientedEta)); + const bool valid = ComputeMicrofacetNormal::isValidMicrofacet(transmitted, VdotL, retval.absNdotH, fresnel::OrientedEtas::create(1.0, hlsl::promote(computeMicrofacetNormal.orientedEta))); if (valid) { retval.VdotH = hlsl::dot(computeMicrofacetNormal.V,H); diff --git a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl index 44ba941bfe..4d65cb4a23 100644 --- a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl @@ -46,7 +46,7 @@ struct OrientedEtaRcps { using scalar_type = typename vector_traits::scalar_type; - static OrientedEtaRcps create(scalar_type NdotI, T eta) + static OrientedEtaRcps create(const scalar_type NdotI, const T eta) { OrientedEtaRcps retval; const bool backside = NdotI < scalar_type(0.0); @@ -73,7 +73,7 @@ struct OrientedEtas { using scalar_type = typename vector_traits::scalar_type; - static OrientedEtas create(scalar_type NdotI, T eta) + static OrientedEtas create(const scalar_type NdotI, const T eta) { OrientedEtas retval; const bool backside = NdotI < scalar_type(0.0); @@ -117,8 +117,8 @@ struct ComputeMicrofacetNormal ComputeMicrofacetNormal retval; retval.V = V; retval.L = L; - fresnel::OrientedEtas orientedEtas = fresnel::OrientedEtas::create(VdotH, eta); - retval.orientedEta = orientedEtas.value; + fresnel::OrientedEtas orientedEtas = fresnel::OrientedEtas::create(VdotH, hlsl::promote(eta)); + retval.orientedEta = orientedEtas.value[0]; return retval; } @@ -138,7 +138,7 @@ struct ComputeMicrofacetNormal // VdotH <= 1-orientedEta2 for orientedEta<1 -> VdotH<0 // VdotH <= 0 for orientedEta>1 // so for transmission VdotH<=0, H needs to be flipped to be consistent with oriented eta - vector_type unnormalized(const bool _refract) + vector_type unnormalized(const bool _refract) NBL_CONST_MEMBER_FUNC { assert(hlsl::dot(V, L) <= -hlsl::min(orientedEta, scalar_type(1.0) / orientedEta)); const scalar_type etaFactor = hlsl::mix(scalar_type(1.0), orientedEta, _refract); @@ -148,7 +148,7 @@ struct ComputeMicrofacetNormal } // returns normalized vector, but NaN when result is length 0 - vector_type normalized(const bool _refract) + vector_type normalized(const bool _refract) NBL_CONST_MEMBER_FUNC { const vector_type H = unnormalized(_refract); return hlsl::normalize(H); diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 056316b24c..25982c2c8d 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -78,7 +78,18 @@ struct SMicrofacetNormals template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && traits::IsMicrofacet) static typename bxdf_type::anisocache_type __createChildCache(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) { - return typename bxdf_type::anisocache_type::template createForReflection(interaction, _sample); + return bxdf_type::anisocache_type::template createForReflection(interaction, _sample); + } + + template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && !traits::IsMicrofacet) + scalar_type __childForwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + { + return nested_brdf.forwardPdf(_sample, interaction); + } + template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && traits::IsMicrofacet) + scalar_type __childForwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + { + return nested_brdf.forwardPdf(_sample, interaction, __createChildCache(_sample, interaction)); } value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC @@ -238,7 +249,7 @@ struct SMicrofacetNormals typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); - return nested_brdf.forwardPdf(sample_N, interaction_N); + return __childForwardPdf(sample_N, interaction_N); } const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); @@ -255,7 +266,7 @@ struct SMicrofacetNormals const scalar_type NdotL = hlsl::dot(shadingNormal, L); const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); const scalar_type NtdotL = hlsl::dot(Nt, L); - pdf += lambda_p * nested_brdf.forwardPdf(_sample, interaction) * G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, + pdf += lambda_p * __childForwardPdf(_sample, interaction) * G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); if (NtdotL > numeric_limits::min) @@ -265,7 +276,7 @@ struct SMicrofacetNormals L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_reflected = sample_type::create(L_reflected, Np); - pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction) * + pdf += lambda_p * __childForwardPdf(sample_reflected, interaction) * (scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), NtdotL))); } } @@ -279,7 +290,7 @@ struct SMicrofacetNormals typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected, Np); typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); - pdf += (scalar_type(1.0) - lambda_p) * nested_brdf.forwardPdf(_sample, interaction_reflected); + pdf += (scalar_type(1.0) - lambda_p) * __childForwardPdf(_sample, interaction_reflected); } return pdf; From b013d89791f691af42c8d5cbe3cf01ad404cd546 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 14 Apr 2026 16:47:18 +0700 Subject: [PATCH 12/32] minor bug fixes and redundant typename usage in bxdfs --- .../builtin/hlsl/bxdf/base/lambertian.hlsl | 8 ++-- .../bxdf/reflection/microfacet_normals.hlsl | 38 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl index 378035e67d..3f8426be61 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl @@ -76,12 +76,12 @@ struct SLambertianBase quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC { - sampling::quotient_and_pdf qp; + sampling::quotient_and_pdf qp; NBL_IF_CONSTEXPR (IsBSDF) - qp = sampling::ProjectedSphere::template quotientAndPdf(_sample.getNdotL(_clamp)); + qp = sampling::ProjectedSphere::template quotientAndPdf(_sample.getNdotL(_clamp)); else - qp = sampling::ProjectedHemisphere::template quotientAndPdf(_sample.getNdotL(_clamp)); - return quotient_weight_type::create(qp.quotient()[0], qp.pdf()); + qp = sampling::ProjectedHemisphere::template quotientAndPdf(_sample.getNdotL(_clamp)); + return quotient_weight_type::create(qp.quotient(), qp.pdf()); } quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 25982c2c8d..0a4c3ccc3c 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -43,13 +43,13 @@ struct SMicrofacetNormals // shading normal N (geometric normal in paper) stored in bxdf // tangent normal Nt derived as needed template // TODO: concept for accessor - anisotropic_interaction_type buildInteraction(NBL_CONST_REF_ARG(NormalsTexAccessor) normalMap, const vector2_type uv, const matrix object_to_world, const vector3_type V) NBL_CONST_MEMBER_FUNC + anisotropic_interaction_type buildInteraction(NBL_CONST_REF_ARG(NormalsTexAccessor) normalMap, const vector2_type uv, const matrix object_to_world, const ray_dir_info_type V) NBL_CONST_MEMBER_FUNC { vector3_type localN; - normalMap.template get(localN, uv, 0); - localN = hlsl::normalize(hlsl::promote(2.0) * localN - hlsl::promote(1.0)); + normalMap.get(localN, uv); + localN = hlsl::promote(2.0) * localN - hlsl::promote(1.0); - const vector3_type N = hlsl::mul(object_to_world, localN); + const vector3_type N = hlsl::normalize(hlsl::mul(object_to_world, localN)); isotropic_interaction_type interaction = isotropic_interaction_type::create(V, N); return anisotropic_interaction_type::create(interaction); } @@ -105,8 +105,8 @@ struct SMicrofacetNormals const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { - typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); - typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); return nested_brdf.evalAndWeight(sample_N, interaction_N); } @@ -153,8 +153,8 @@ struct SMicrofacetNormals ray_dir_info_type V_reflected; V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); - typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected, Np); - typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); + typename bxdf_type::isotropic_interaction_type iso_reflected = bxdf_type::isotropic_interaction_type::create(V_reflected, Np); + typename bxdf_type::anisotropic_interaction_type interaction_reflected = bxdf_type::anisotropic_interaction_type::create(iso_reflected); value_weight_type eval_double_t = nested_brdf.evalAndWeight(_sample, interaction_reflected); eval += eval_double_t.value() * (scalar_type(1.0) - lambda_p) * shadowing; @@ -173,8 +173,8 @@ struct SMicrofacetNormals const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { - typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); - typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); return nested_brdf.generate(interaction_N, u, _cache.aniso_cache); } @@ -216,13 +216,13 @@ struct SMicrofacetNormals // sample on wp ray_dir_info_type Vnr; Vnr.setDirection(V_negreflected); - typename bxdf_type::isotropic_interaction_type iso_negreflected = typename bxdf_type::isotropic_interaction_type::create(Vnr, Np); - typename bxdf_type::anisotropic_interaction_type interaction_negreflected = typename bxdf_type::anisotropic_interaction_type::create(iso_negreflected); + typename bxdf_type::isotropic_interaction_type iso_negreflected = bxdf_type::isotropic_interaction_type::create(Vnr, Np); + typename bxdf_type::anisotropic_interaction_type interaction_negreflected = bxdf_type::anisotropic_interaction_type::create(iso_negreflected); s = nested_brdf.generate(interaction_negreflected, u, _cache.aniso_cache); if (!s.isValid()) return s; } - if (s.getNdotL() < scalar_type(0.0)) + if (hlsl::dot(shadingNormal, s.getL().getDirection()) < scalar_type(0.0)) return sample_type::createInvalid(); // for reflection return s; } @@ -246,8 +246,8 @@ struct SMicrofacetNormals const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { - typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); - typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); return __childForwardPdf(sample_N, interaction_N); } @@ -287,8 +287,8 @@ struct SMicrofacetNormals ray_dir_info_type V_reflected; V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); - typename bxdf_type::isotropic_interaction_type iso_reflected = typename bxdf_type::isotropic_interaction_type::create(V_reflected, Np); - typename bxdf_type::anisotropic_interaction_type interaction_reflected = typename bxdf_type::anisotropic_interaction_type::create(iso_reflected); + typename bxdf_type::isotropic_interaction_type iso_reflected = bxdf_type::isotropic_interaction_type::create(V_reflected, Np); + typename bxdf_type::anisotropic_interaction_type interaction_reflected = bxdf_type::anisotropic_interaction_type::create(iso_reflected); pdf += (scalar_type(1.0) - lambda_p) * __childForwardPdf(_sample, interaction_reflected); } @@ -309,8 +309,8 @@ struct SMicrofacetNormals const ray_dir_info_type V = interaction.getV(); if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) { - typename bxdf_type::isotropic_interaction_type iso = typename bxdf_type::isotropic_interaction_type::create(V, shadingNormal); - typename bxdf_type::anisotropic_interaction_type interaction_N = typename bxdf_type::anisotropic_interaction_type::create(iso); + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); typename bxdf_type::anisocache_type cache_N = __createChildCache(sample_N, interaction_N); return nested_brdf.quotientAndWeight(sample_N, interaction_N, cache_N); From f632eb10f8511fee2b8edc30b5428716d1289cde Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 16 Apr 2026 11:23:00 +0700 Subject: [PATCH 13/32] change edge case to when perturbed normal is close to shading normal --- .../hlsl/bxdf/reflection/microfacet_normals.hlsl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 0a4c3ccc3c..7898384672 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -103,7 +103,7 @@ struct SMicrofacetNormals const vector3_type local_Np = hlsl::mul(shadingBasis, Np); const ray_dir_info_type V = interaction.getV(); - if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + if (NdotNp > scalar_type(1.0 - 1e-5)) { typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); @@ -116,7 +116,7 @@ struct SMicrofacetNormals const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); spectral_type eval = hlsl::promote(0.0); - + const vector3_type L = _sample.getL().getDirection(); const scalar_type NdotL = hlsl::dot(shadingNormal, L); const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); @@ -171,7 +171,7 @@ struct SMicrofacetNormals _cache.sampleIsShadowed = false; const ray_dir_info_type V = interaction.getV(); - if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + if (NdotNp > scalar_type(1.0 - 1e-5)) { typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); @@ -190,7 +190,7 @@ struct SMicrofacetNormals s = nested_brdf.generate(interaction, u, _cache.aniso_cache); if (!s.isValid()) - return s; + return sample_type::createInvalid(); const vector3_type L = s.getL().getDirection(); const scalar_type shadowed = G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, @@ -244,7 +244,7 @@ struct SMicrofacetNormals const vector3_type local_Np = hlsl::mul(shadingBasis, Np); const ray_dir_info_type V = interaction.getV(); - if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + if (NdotNp > scalar_type(1.0 - 1e-5)) { typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); @@ -307,7 +307,7 @@ struct SMicrofacetNormals const vector3_type local_Np = hlsl::mul(shadingBasis, Np); const ray_dir_info_type V = interaction.getV(); - if (NdotNp <= scalar_type(0.0) || (hlsl::abs(local_Np.x) < numeric_limits::min && hlsl::abs(local_Np.y) < numeric_limits::min)) + if (NdotNp > scalar_type(1.0 - 1e-5)) { typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); From a7067a44108c21814f9acc9197aa4591f6f1a9e0 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 16 Apr 2026 16:32:20 +0700 Subject: [PATCH 14/32] change build interaction for procedural normals --- .../nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 7898384672..851ef6091a 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -45,8 +45,9 @@ struct SMicrofacetNormals template // TODO: concept for accessor anisotropic_interaction_type buildInteraction(NBL_CONST_REF_ARG(NormalsTexAccessor) normalMap, const vector2_type uv, const matrix object_to_world, const ray_dir_info_type V) NBL_CONST_MEMBER_FUNC { + const matrix TBN = hlsl::transpose(object_to_world); vector3_type localN; - normalMap.get(localN, uv); + normalMap.get(localN, TBN[2], TBN[0], TBN[1]); localN = hlsl::promote(2.0) * localN - hlsl::promote(1.0); const vector3_type N = hlsl::normalize(hlsl::mul(object_to_world, localN)); From db2706a793ed8fe2d84786a02dd592e888042df3 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 20 Apr 2026 13:55:39 +0700 Subject: [PATCH 15/32] forward pdf of delta distributions should return inf --- .../builtin/hlsl/bxdf/reflection/delta_distribution.hlsl | 4 ++-- .../hlsl/bxdf/transmission/delta_distribution.hlsl | 4 ++-- .../builtin/hlsl/bxdf/transmission/smooth_dielectric.hlsl | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/delta_distribution.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/delta_distribution.hlsl index 1acdf211b5..6bbcb9d0c6 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/delta_distribution.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/delta_distribution.hlsl @@ -59,11 +59,11 @@ struct SDeltaDistribution scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return 0; + return bit_cast(numeric_limits::infinity); } scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return 0; + return bit_cast(numeric_limits::infinity); } quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC diff --git a/include/nbl/builtin/hlsl/bxdf/transmission/delta_distribution.hlsl b/include/nbl/builtin/hlsl/bxdf/transmission/delta_distribution.hlsl index c2ece15686..149bb4f4cb 100644 --- a/include/nbl/builtin/hlsl/bxdf/transmission/delta_distribution.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/transmission/delta_distribution.hlsl @@ -56,11 +56,11 @@ struct SDeltaDistribution scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return 0; + return bit_cast(numeric_limits::infinity); } scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return 0; + return bit_cast(numeric_limits::infinity); } quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC diff --git a/include/nbl/builtin/hlsl/bxdf/transmission/smooth_dielectric.hlsl b/include/nbl/builtin/hlsl/bxdf/transmission/smooth_dielectric.hlsl index 06266b1f45..b44823dd88 100644 --- a/include/nbl/builtin/hlsl/bxdf/transmission/smooth_dielectric.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/transmission/smooth_dielectric.hlsl @@ -64,11 +64,11 @@ struct SSmoothDielectric scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return 0; + return bit_cast(numeric_limits::infinity); } scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return 0; + return bit_cast(numeric_limits::infinity); } // smooth BxDFs are isotropic by definition @@ -153,11 +153,11 @@ struct SThinSmoothDielectric scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return 0; + return bit_cast(numeric_limits::infinity); } scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return 0; + return bit_cast(numeric_limits::infinity); } // smooth BxDFs are isotropic by definition From c1e224e8c5621c160066950288f8dfea774e230c Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 20 Apr 2026 13:56:59 +0700 Subject: [PATCH 16/32] minor bug fixes to changes from master in lambertian and cook torrance --- include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl | 5 +++-- include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) 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 8ed0d6ac92..475ac905e1 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl @@ -192,11 +192,12 @@ struct SCookTorrance NBL_FUNC_REQUIRES(RequiredInteraction && RequiredMicrofacetCache && !IsBSDF) value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(Interaction) interaction) NBL_CONST_MEMBER_FUNC { - const MicrofacetCache cache = MicrofacetCache::template createForReflection(interaction, _sample); + const MicrofacetCache cache = MicrofacetCache::template createForReflection(interaction, _sample); return evalAndWeight(_sample, interaction, cache); } - template, + class MicrofacetCache=conditional_t NBL_FUNC_REQUIRES(RequiredInteraction && RequiredMicrofacetCache && IsBSDF) value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(Interaction) interaction) NBL_CONST_MEMBER_FUNC { diff --git a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl index fd841efab3..6c270f1a36 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl @@ -72,7 +72,7 @@ struct SLambertianBase } scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return forwardPdf(_sample, interaction.isotropic); + return forwardPdf(_sample, interaction.isotropic, _cache); } quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC From a46ba65f6cdb5a6bad30ed3884c47544ef20d452 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 20 Apr 2026 16:35:58 +0700 Subject: [PATCH 17/32] add alternative microfacet shadowing methods, needs organizing --- .../bxdf/reflection/microfacet_normals.hlsl | 122 ++++++++++++------ 1 file changed, 79 insertions(+), 43 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 851ef6091a..c234b8f89c 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -48,6 +48,7 @@ struct SMicrofacetNormals const matrix TBN = hlsl::transpose(object_to_world); vector3_type localN; normalMap.get(localN, TBN[2], TBN[0], TBN[1]); + // normalMap.get(localN, uv); localN = hlsl::promote(2.0) * localN - hlsl::promote(1.0); const vector3_type N = hlsl::normalize(hlsl::mul(object_to_world, localN)); @@ -55,13 +56,34 @@ struct SMicrofacetNormals return anisotropic_interaction_type::create(interaction); } - static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) + // schussler (original) + // static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) + // { + // const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); + // return hlsl::min(scalar_type(1.0), + // clampedNdotL * hlsl::max(scalar_type(0.0), NdotNp) + // / (clampedNpdotL + clampedNtdotL * sinThetaNp) + // ); + // } + + // estevez + // static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp) + // { + // const scalar_type tan2_d = scalar_type(1.0) / (NdotNp * NdotNp) - scalar_type(1.0); + // const scalar_type bump_alpha2 = hlsl::clamp(scalar_type(0.125) * tan2_d, scalar_type(0.0), scalar_type(1.0)); + + // const scalar_type devsh_v = hlsl::sqrt(bump_alpha2 + (scalar_type(1.0) - bump_alpha2) * (clampedNdotL * clampedNdotL)); + // return scalar_type(2.0) * clampedNdotL / (devsh_v + clampedNdotL); + // } + + // yining + static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL) { - const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); - return hlsl::min(scalar_type(1.0), - clampedNdotL * hlsl::max(scalar_type(0.0), NdotNp) - / (clampedNpdotL + clampedNtdotL * sinThetaNp) + const scalar_type g = hlsl::min(scalar_type(1.0), + clampedNpdotL / (clampedNdotL * NdotNp) ); + const scalar_type g2 = g * g; + return -g2 * g + g2 + g; } static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) @@ -82,15 +104,19 @@ struct SMicrofacetNormals return bxdf_type::anisocache_type::template createForReflection(interaction, _sample); } - template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && !traits::IsMicrofacet) - scalar_type __childForwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC - { - return nested_brdf.forwardPdf(_sample, interaction); - } - template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && traits::IsMicrofacet) - scalar_type __childForwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + // schussler (original) + // vector3_type __computeNt(const vector3_type Np) + // { + // const vector3_type local_Np = hlsl::mul(shadingBasis, Np); + // const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); + // return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); + // } + + // yining + vector3_type __computeNt(const vector3_type Np) { - return nested_brdf.forwardPdf(_sample, interaction, __createChildCache(_sample, interaction)); + const vector3_type w = hlsl::normalize(hlsl::cross(shadingNormal, Np)); + return hlsl::normalize(hlsl::cross(Np, w)); } value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC @@ -101,7 +127,6 @@ struct SMicrofacetNormals { const vector3_type Np = interaction.getN(); const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); - const vector3_type local_Np = hlsl::mul(shadingBasis, Np); const ray_dir_info_type V = interaction.getV(); if (NdotNp > scalar_type(1.0 - 1e-5)) @@ -112,8 +137,7 @@ struct SMicrofacetNormals return nested_brdf.evalAndWeight(sample_N, interaction_N); } - const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); + const vector3_type Nt = __computeNt(Np); const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); spectral_type eval = hlsl::promote(0.0); @@ -123,8 +147,10 @@ struct SMicrofacetNormals const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); const scalar_type NtdotL = hlsl::dot(Nt, L); const scalar_type lambda_p = lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); - const scalar_type shadowing = G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, - NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); + // const scalar_type shadowing = G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, + // NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); + // const scalar_type shadowing = G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp); + const scalar_type shadowing = G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, NpdotL); // i -> p -> o { @@ -140,8 +166,12 @@ struct SMicrofacetNormals L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_double_p = sample_type::create(L_reflected, Np); - const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, - sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), Nt))); + // const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, + // sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), Nt))); + + // const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp); + + const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, sample_double_p.getNdotL(BxDFClampMode::BCM_MAX)); value_weight_type eval_double_p = nested_brdf.evalAndWeight(sample_double_p, interaction); eval += eval_double_p.value() * (lambda_p * notShadowedNpMirror * shadowing); @@ -161,14 +191,15 @@ struct SMicrofacetNormals eval += eval_double_t.value() * (scalar_type(1.0) - lambda_p) * shadowing; } - return value_weight_type::create(eval, forwardPdf(_sample, interaction)); + anisocache_type _cache; + _cache.aniso_cache = __createChildCache(_sample, interaction); + return value_weight_type::create(eval, forwardPdf(_sample, interaction, _cache)); } sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const random_type u, NBL_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { const vector3_type Np = interaction.getN(); const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); - const vector3_type local_Np = hlsl::mul(shadingBasis, Np); _cache.sampleIsShadowed = false; const ray_dir_info_type V = interaction.getV(); @@ -179,8 +210,7 @@ struct SMicrofacetNormals return nested_brdf.generate(interaction_N, u, _cache.aniso_cache); } - const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); + const vector3_type Nt = __computeNt(Np); const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); @@ -194,8 +224,10 @@ struct SMicrofacetNormals return sample_type::createInvalid(); const vector3_type L = s.getL().getDirection(); - const scalar_type shadowed = G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, - s.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); + // const scalar_type shadowed = G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, + // s.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); + // const scalar_type shadowed = G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp); + const scalar_type shadowed = G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, s.getNdotL(BxDFClampMode::BCM_MAX)); if (u.y > shadowed) { @@ -234,15 +266,14 @@ struct SMicrofacetNormals return s; } - scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC { - return forwardPdf(_sample, anisotropic_interaction_type::create(interaction)); + return forwardPdf(_sample, anisotropic_interaction_type::create(interaction), _cache); } - scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC { const vector3_type Np = interaction.getN(); const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); - const vector3_type local_Np = hlsl::mul(shadingBasis, Np); const ray_dir_info_type V = interaction.getV(); if (NdotNp > scalar_type(1.0 - 1e-5)) @@ -250,11 +281,10 @@ struct SMicrofacetNormals typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); - return __childForwardPdf(sample_N, interaction_N); + return nested_brdf.forwardPdf(sample_N, interaction_N, __createChildCache(sample_N, interaction_N)); } - const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); + const vector3_type Nt = __computeNt(Np); const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); @@ -267,8 +297,10 @@ struct SMicrofacetNormals const scalar_type NdotL = hlsl::dot(shadingNormal, L); const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); const scalar_type NtdotL = hlsl::dot(Nt, L); - pdf += lambda_p * __childForwardPdf(_sample, interaction) * G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, - NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); + // pdf += lambda_p * nested_brdf.forwardPdf(_sample, interaction, __createChildCache(_sample, interaction)) * G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, + // NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); + // pdf += lambda_p * __childForwardPdf(_sample, interaction) * G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp); + pdf += lambda_p * nested_brdf.forwardPdf(_sample, interaction, __createChildCache(_sample, interaction)) * G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, NpdotL); if (NtdotL > numeric_limits::min) { @@ -277,8 +309,12 @@ struct SMicrofacetNormals L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_reflected = sample_type::create(L_reflected, Np); - pdf += lambda_p * __childForwardPdf(sample_reflected, interaction) * - (scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), NtdotL))); + // pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction, __createChildCache(sample_reflected, interaction)) * + // (scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L_reflected.getDirection())), NdotNp, sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), NtdotL))); + // pdf += lambda_p * __childForwardPdf(sample_reflected, interaction) * + // (scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L_reflected.getDirection())), NdotNp)); + pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction, __createChildCache(sample_reflected, interaction)) * + (scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L_reflected.getDirection())), NdotNp, sample_reflected.getNdotL(BxDFClampMode::BCM_MAX))); } } @@ -291,7 +327,7 @@ struct SMicrofacetNormals typename bxdf_type::isotropic_interaction_type iso_reflected = bxdf_type::isotropic_interaction_type::create(V_reflected, Np); typename bxdf_type::anisotropic_interaction_type interaction_reflected = bxdf_type::anisotropic_interaction_type::create(iso_reflected); - pdf += (scalar_type(1.0) - lambda_p) * __childForwardPdf(_sample, interaction_reflected); + pdf += (scalar_type(1.0) - lambda_p) * nested_brdf.forwardPdf(_sample, interaction_reflected, __createChildCache(_sample, interaction_reflected)); } return pdf; @@ -305,7 +341,6 @@ struct SMicrofacetNormals { const vector3_type Np = interaction.getN(); const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); - const vector3_type local_Np = hlsl::mul(shadingBasis, Np); const ray_dir_info_type V = interaction.getV(); if (NdotNp > scalar_type(1.0 - 1e-5)) @@ -317,8 +352,7 @@ struct SMicrofacetNormals return nested_brdf.quotientAndWeight(sample_N, interaction_N, cache_N); } - const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); - const vector3_type Nt = hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); + const vector3_type Nt = __computeNt(Np); spectral_type quo = hlsl::promote(1.0); const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); @@ -328,10 +362,12 @@ struct SMicrofacetNormals if (_cache.sampleIsShadowed) { const vector3_type L = _sample.getL().getDirection(); - quo *= G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); + // quo *= G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); + // quo *= G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp); + quo *= G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, NpdotL); } - return quotient_weight_type::create(quo, forwardPdf(_sample, interaction)); + return quotient_weight_type::create(quo, forwardPdf(_sample, interaction, _cache)); } bxdf_type nested_brdf; From d9e2c861816bb9356675fe10a694c52bd52c4051 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 21 Apr 2026 10:22:12 +0700 Subject: [PATCH 18/32] made shadowing method for microfacet normals configurable --- .../bxdf/reflection/microfacet_normals.hlsl | 178 ++++++++++-------- 1 file changed, 99 insertions(+), 79 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index c234b8f89c..079ce528e6 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -17,11 +17,90 @@ namespace bxdf namespace reflection { +enum PerturbedNormalShadowing : uint16_t +{ + PNS_SCHUSSLER, + PNS_ESTEVEZ, + PNS_YINING +}; + +template +struct ShadowingMethod; + +template +struct ShadowingMethod +{ + using scalar_type = T; + using vector3_type = vector; + using matrix3x3_type = matrix; + + static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) + { + const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); + return hlsl::min(scalar_type(1.0), + clampedNdotL * hlsl::max(scalar_type(0.0), NdotNp) + / (clampedNpdotL + clampedNtdotL * sinThetaNp) + ); + } + + static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) + { + const vector3_type local_Np = hlsl::mul(shadingBasis, Np); + const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); + return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); + } +}; + +template +struct ShadowingMethod +{ + using scalar_type = T; + using vector3_type = vector; + using matrix3x3_type = matrix; + + static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) + { + const scalar_type tan2_d = scalar_type(1.0) / (NdotNp * NdotNp) - scalar_type(1.0); + const scalar_type bump_alpha2 = hlsl::clamp(scalar_type(0.125) * tan2_d, scalar_type(0.0), scalar_type(1.0)); + + const scalar_type devsh_v = hlsl::sqrt(bump_alpha2 + (scalar_type(1.0) - bump_alpha2) * (clampedNdotL * clampedNdotL)); + return scalar_type(2.0) * clampedNdotL / (devsh_v + clampedNdotL); + } + + static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) + { + return ShadowingMethod::computeNt(Np, shadingBasis); + } +}; + +template +struct ShadowingMethod +{ + using scalar_type = T; + using vector3_type = vector; + using matrix3x3_type = matrix; + + static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) + { + const scalar_type g = hlsl::min(scalar_type(1.0), + clampedNpdotL / (clampedNdotL * NdotNp) + ); + const scalar_type g2 = g * g; + return -g2 * g + g2 + g; + } + + static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) + { + const vector3_type w = hlsl::normalize(hlsl::cross(shadingBasis[2], Np)); + return hlsl::normalize(hlsl::cross(Np, w)); + } +}; + // based on Microfacet-based Normal Mapping for Robust Monte Carlo Path Tracing: https://jo.dreggn.org/home/2017_normalmap.pdf -template) +template) struct SMicrofacetNormals { - using this_t = SMicrofacetNormals; + using this_t = SMicrofacetNormals; BXDF_CONFIG_TYPE_ALIASES(Config); NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = false; // TODO: temp, should account for bsdfs at some point @@ -37,6 +116,8 @@ struct SMicrofacetNormals using anisocache_type = Cache; using matrix3x3_type = matrix; + using shadowing_method_type = ShadowingMethod; + NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = conditional_value::value; // perturbed normal Np stored in interaction @@ -56,36 +137,6 @@ struct SMicrofacetNormals return anisotropic_interaction_type::create(interaction); } - // schussler (original) - // static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) - // { - // const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); - // return hlsl::min(scalar_type(1.0), - // clampedNdotL * hlsl::max(scalar_type(0.0), NdotNp) - // / (clampedNpdotL + clampedNtdotL * sinThetaNp) - // ); - // } - - // estevez - // static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp) - // { - // const scalar_type tan2_d = scalar_type(1.0) / (NdotNp * NdotNp) - scalar_type(1.0); - // const scalar_type bump_alpha2 = hlsl::clamp(scalar_type(0.125) * tan2_d, scalar_type(0.0), scalar_type(1.0)); - - // const scalar_type devsh_v = hlsl::sqrt(bump_alpha2 + (scalar_type(1.0) - bump_alpha2) * (clampedNdotL * clampedNdotL)); - // return scalar_type(2.0) * clampedNdotL / (devsh_v + clampedNdotL); - // } - - // yining - static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL) - { - const scalar_type g = hlsl::min(scalar_type(1.0), - clampedNpdotL / (clampedNdotL * NdotNp) - ); - const scalar_type g2 = g * g; - return -g2 * g + g2 + g; - } - static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) { const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); @@ -104,21 +155,6 @@ struct SMicrofacetNormals return bxdf_type::anisocache_type::template createForReflection(interaction, _sample); } - // schussler (original) - // vector3_type __computeNt(const vector3_type Np) - // { - // const vector3_type local_Np = hlsl::mul(shadingBasis, Np); - // const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); - // return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); - // } - - // yining - vector3_type __computeNt(const vector3_type Np) - { - const vector3_type w = hlsl::normalize(hlsl::cross(shadingNormal, Np)); - return hlsl::normalize(hlsl::cross(Np, w)); - } - value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { return evalAndWeight(_sample, anisotropic_interaction_type::create(interaction)); @@ -137,7 +173,7 @@ struct SMicrofacetNormals return nested_brdf.evalAndWeight(sample_N, interaction_N); } - const vector3_type Nt = __computeNt(Np); + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); spectral_type eval = hlsl::promote(0.0); @@ -147,10 +183,8 @@ struct SMicrofacetNormals const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); const scalar_type NtdotL = hlsl::dot(Nt, L); const scalar_type lambda_p = lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); - // const scalar_type shadowing = G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, - // NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); - // const scalar_type shadowing = G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp); - const scalar_type shadowing = G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, NpdotL); + const scalar_type shadowing = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, + NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); // i -> p -> o { @@ -166,12 +200,8 @@ struct SMicrofacetNormals L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_double_p = sample_type::create(L_reflected, Np); - // const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, - // sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), Nt))); - - // const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp); - - const scalar_type notShadowedNpMirror = scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, sample_double_p.getNdotL(BxDFClampMode::BCM_MAX)); + const scalar_type notShadowedNpMirror = scalar_type(1.0) - shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, + sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), Nt))); value_weight_type eval_double_p = nested_brdf.evalAndWeight(sample_double_p, interaction); eval += eval_double_p.value() * (lambda_p * notShadowedNpMirror * shadowing); @@ -210,7 +240,7 @@ struct SMicrofacetNormals return nested_brdf.generate(interaction_N, u, _cache.aniso_cache); } - const vector3_type Nt = __computeNt(Np); + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); @@ -224,10 +254,8 @@ struct SMicrofacetNormals return sample_type::createInvalid(); const vector3_type L = s.getL().getDirection(); - // const scalar_type shadowed = G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, - // s.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); - // const scalar_type shadowed = G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp); - const scalar_type shadowed = G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, s.getNdotL(BxDFClampMode::BCM_MAX)); + const scalar_type shadowed = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, + s.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); if (u.y > shadowed) { @@ -284,7 +312,7 @@ struct SMicrofacetNormals return nested_brdf.forwardPdf(sample_N, interaction_N, __createChildCache(sample_N, interaction_N)); } - const vector3_type Nt = __computeNt(Np); + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); @@ -297,10 +325,8 @@ struct SMicrofacetNormals const scalar_type NdotL = hlsl::dot(shadingNormal, L); const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); const scalar_type NtdotL = hlsl::dot(Nt, L); - // pdf += lambda_p * nested_brdf.forwardPdf(_sample, interaction, __createChildCache(_sample, interaction)) * G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, - // NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); - // pdf += lambda_p * __childForwardPdf(_sample, interaction) * G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp); - pdf += lambda_p * nested_brdf.forwardPdf(_sample, interaction, __createChildCache(_sample, interaction)) * G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, NpdotL); + pdf += lambda_p * nested_brdf.forwardPdf(_sample, interaction, __createChildCache(_sample, interaction)) * shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, + NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); if (NtdotL > numeric_limits::min) { @@ -309,12 +335,8 @@ struct SMicrofacetNormals L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_reflected = sample_type::create(L_reflected, Np); - // pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction, __createChildCache(sample_reflected, interaction)) * - // (scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L_reflected.getDirection())), NdotNp, sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), NtdotL))); - // pdf += lambda_p * __childForwardPdf(sample_reflected, interaction) * - // (scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L_reflected.getDirection())), NdotNp)); pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction, __createChildCache(sample_reflected, interaction)) * - (scalar_type(1.0) - G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L_reflected.getDirection())), NdotNp, sample_reflected.getNdotL(BxDFClampMode::BCM_MAX))); + (scalar_type(1.0) - shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L_reflected.getDirection())), NdotNp, sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), NtdotL))); } } @@ -352,7 +374,7 @@ struct SMicrofacetNormals return nested_brdf.quotientAndWeight(sample_N, interaction_N, cache_N); } - const vector3_type Nt = __computeNt(Np); + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); spectral_type quo = hlsl::promote(1.0); const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); @@ -362,9 +384,7 @@ struct SMicrofacetNormals if (_cache.sampleIsShadowed) { const vector3_type L = _sample.getL().getDirection(); - // quo *= G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); - // quo *= G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp); - quo *= G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, NpdotL); + quo *= shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); } return quotient_weight_type::create(quo, forwardPdf(_sample, interaction, _cache)); @@ -377,8 +397,8 @@ struct SMicrofacetNormals } -template -struct traits > +template +struct traits > { NBL_CONSTEXPR_STATIC_INLINE BxDFType type = BT_BRDF; NBL_CONSTEXPR_STATIC_INLINE bool IsMicrofacet = false; // should be microfacet? From d267f6956cea4dc75cf2ab6506e293136ab505a2 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 22 Apr 2026 16:05:43 +0700 Subject: [PATCH 19/32] remember to pass luminosity contribution hint to interaction --- .../builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 079ce528e6..50882c23d9 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -134,6 +134,7 @@ struct SMicrofacetNormals const vector3_type N = hlsl::normalize(hlsl::mul(object_to_world, localN)); isotropic_interaction_type interaction = isotropic_interaction_type::create(V, N); + interaction.luminosityContributionHint = colorspace::scRGBtoXYZ[1]; return anisotropic_interaction_type::create(interaction); } @@ -168,6 +169,7 @@ struct SMicrofacetNormals if (NdotNp > scalar_type(1.0 - 1e-5)) { typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); return nested_brdf.evalAndWeight(sample_N, interaction_N); @@ -215,6 +217,7 @@ struct SMicrofacetNormals V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); typename bxdf_type::isotropic_interaction_type iso_reflected = bxdf_type::isotropic_interaction_type::create(V_reflected, Np); + iso_reflected.luminosityContributionHint = interaction.getLuminosityContributionHint(); typename bxdf_type::anisotropic_interaction_type interaction_reflected = bxdf_type::anisotropic_interaction_type::create(iso_reflected); value_weight_type eval_double_t = nested_brdf.evalAndWeight(_sample, interaction_reflected); @@ -236,6 +239,7 @@ struct SMicrofacetNormals if (NdotNp > scalar_type(1.0 - 1e-5)) { typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); return nested_brdf.generate(interaction_N, u, _cache.aniso_cache); } @@ -278,6 +282,7 @@ struct SMicrofacetNormals ray_dir_info_type Vnr; Vnr.setDirection(V_negreflected); typename bxdf_type::isotropic_interaction_type iso_negreflected = bxdf_type::isotropic_interaction_type::create(Vnr, Np); + iso_negreflected.luminosityContributionHint = interaction.getLuminosityContributionHint(); typename bxdf_type::anisotropic_interaction_type interaction_negreflected = bxdf_type::anisotropic_interaction_type::create(iso_negreflected); s = nested_brdf.generate(interaction_negreflected, u, _cache.aniso_cache); if (!s.isValid()) @@ -307,6 +312,7 @@ struct SMicrofacetNormals if (NdotNp > scalar_type(1.0 - 1e-5)) { typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); return nested_brdf.forwardPdf(sample_N, interaction_N, __createChildCache(sample_N, interaction_N)); @@ -347,6 +353,7 @@ struct SMicrofacetNormals V_reflected.setDirection(hlsl::normalize(reflectV(NtdotV))); typename bxdf_type::isotropic_interaction_type iso_reflected = bxdf_type::isotropic_interaction_type::create(V_reflected, Np); + iso_reflected.luminosityContributionHint = interaction.getLuminosityContributionHint(); typename bxdf_type::anisotropic_interaction_type interaction_reflected = bxdf_type::anisotropic_interaction_type::create(iso_reflected); pdf += (scalar_type(1.0) - lambda_p) * nested_brdf.forwardPdf(_sample, interaction_reflected, __createChildCache(_sample, interaction_reflected)); @@ -368,6 +375,7 @@ struct SMicrofacetNormals if (NdotNp > scalar_type(1.0 - 1e-5)) { typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); typename bxdf_type::anisocache_type cache_N = __createChildCache(sample_N, interaction_N); From ae0d6ce9a0442271be3ec400c480b894936fe061 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 23 Apr 2026 14:19:30 +0700 Subject: [PATCH 20/32] removed estevez variant, simplified Nt compute for yining --- .../bxdf/reflection/microfacet_normals.hlsl | 28 ++----------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 50882c23d9..0ebc102fac 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -20,7 +20,6 @@ namespace reflection enum PerturbedNormalShadowing : uint16_t { PNS_SCHUSSLER, - PNS_ESTEVEZ, PNS_YINING }; @@ -51,28 +50,6 @@ struct ShadowingMethod } }; -template -struct ShadowingMethod -{ - using scalar_type = T; - using vector3_type = vector; - using matrix3x3_type = matrix; - - static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) - { - const scalar_type tan2_d = scalar_type(1.0) / (NdotNp * NdotNp) - scalar_type(1.0); - const scalar_type bump_alpha2 = hlsl::clamp(scalar_type(0.125) * tan2_d, scalar_type(0.0), scalar_type(1.0)); - - const scalar_type devsh_v = hlsl::sqrt(bump_alpha2 + (scalar_type(1.0) - bump_alpha2) * (clampedNdotL * clampedNdotL)); - return scalar_type(2.0) * clampedNdotL / (devsh_v + clampedNdotL); - } - - static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) - { - return ShadowingMethod::computeNt(Np, shadingBasis); - } -}; - template struct ShadowingMethod { @@ -91,8 +68,9 @@ struct ShadowingMethod static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) { - const vector3_type w = hlsl::normalize(hlsl::cross(shadingBasis[2], Np)); - return hlsl::normalize(hlsl::cross(Np, w)); + const vector3_type local_Np = hlsl::mul(shadingBasis, Np); + const vector3_type local_Nt = hlsl::normalize(vector3_type(local_Np.xy * -local_Np.z, 1.0 - local_Np.z*local_Np.z)); + return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); } }; From 8e6387fc35098f4cc17a75348c05414459914a59 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 23 Apr 2026 15:29:59 +0700 Subject: [PATCH 21/32] separate lambda for yining variant --- .../bxdf/reflection/microfacet_normals.hlsl | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 0ebc102fac..ca1dbc688c 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -42,6 +42,12 @@ struct ShadowingMethod ); } + static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) + { + const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); + return clampedNpdotV / (clampedNpdotV + clampedNtdotV * sinThetaNp); + } + static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) { const vector3_type local_Np = hlsl::mul(shadingBasis, Np); @@ -66,6 +72,20 @@ struct ShadowingMethod return -g2 * g + g2 + g; } + // TODO: verify maths + // since Nt is now perpendicular to Np and not N + // total area of surface = 1 = hypotenuse of right triangle + // area of perturbed facet = cos(Np) = NdotNp + // area of tangent facet = cos(Nt) = sin(Np) + // projected area of Np onto V = area * NpdotV + static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) + { + const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); + const scalar_type ap = clampedNpdotV * NdotNp; + const scalar_type at = clampedNtdotV * sinThetaNp; + return ap / (ap + at); + } + static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) { const vector3_type local_Np = hlsl::mul(shadingBasis, Np); @@ -116,12 +136,6 @@ struct SMicrofacetNormals return anisotropic_interaction_type::create(interaction); } - static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) - { - const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); - return clampedNpdotV / (clampedNpdotV + clampedNtdotV * sinThetaNp); - } - template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && !traits::IsMicrofacet) static typename bxdf_type::anisocache_type __createChildCache(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) { @@ -162,7 +176,7 @@ struct SMicrofacetNormals const scalar_type NdotL = hlsl::dot(shadingNormal, L); const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); const scalar_type NtdotL = hlsl::dot(Nt, L); - const scalar_type lambda_p = lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); + const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); const scalar_type shadowing = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); @@ -227,7 +241,7 @@ struct SMicrofacetNormals const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); sample_type s; - if (u.x < lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV))) + if (u.x < shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV))) { // sample on Np s = nested_brdf.generate(interaction, u, _cache.aniso_cache); @@ -301,7 +315,7 @@ struct SMicrofacetNormals const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); scalar_type pdf = scalar_type(0.0); - const scalar_type lambda_p = lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); + const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); if (lambda_p > scalar_type(0.0)) { From 8c40f6d6d4089d2e1e7729a79f85c0f579eb6b45 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 23 Apr 2026 16:39:37 +0700 Subject: [PATCH 22/32] fix wrong shadowing term in eval --- .../nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index ca1dbc688c..e2cab5eef3 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -196,9 +196,11 @@ struct SMicrofacetNormals const scalar_type notShadowedNpMirror = scalar_type(1.0) - shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), Nt))); + const scalar_type shadowing_t = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), hlsl::dot(shadingNormal, Nt), + NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); value_weight_type eval_double_p = nested_brdf.evalAndWeight(sample_double_p, interaction); - eval += eval_double_p.value() * (lambda_p * notShadowedNpMirror * shadowing); + eval += eval_double_p.value() * (lambda_p * notShadowedNpMirror * shadowing_t); } // i -> t -> p -> o From 5b168fdc84c9e27322c1806ac7e7926c5e34532b Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 24 Apr 2026 15:15:16 +0700 Subject: [PATCH 23/32] add 1st order scattering version for schussler and yining, 2nd-order schussler only --- .../bxdf/reflection/microfacet_normals.hlsl | 297 ++++++++++++++++-- 1 file changed, 278 insertions(+), 19 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index e2cab5eef3..1d907bc5f1 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -26,6 +26,7 @@ enum PerturbedNormalShadowing : uint16_t template struct ShadowingMethod; +// based on Microfacet-based Normal Mapping for Robust Monte Carlo Path Tracing: https://jo.dreggn.org/home/2017_normalmap.pdf template struct ShadowingMethod { @@ -56,6 +57,7 @@ struct ShadowingMethod } }; +// based on Taming the Shadow Terminator: https://www.yiningkarlli.com/projects/shadowterminator/shadow_terminator_v1_1.pdf template struct ShadowingMethod { @@ -94,11 +96,14 @@ struct ShadowingMethod } }; -// based on Microfacet-based Normal Mapping for Robust Monte Carlo Path Tracing: https://jo.dreggn.org/home/2017_normalmap.pdf -template) -struct SMicrofacetNormals +template +struct SMicrofacetNormals; + +template +NBL_PARTIAL_REQ_TOP(config_concepts::BasicConfiguration) +struct SMicrofacetNormals) > { - using this_t = SMicrofacetNormals; + using this_t = SMicrofacetNormals; BXDF_CONFIG_TYPE_ALIASES(Config); NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = false; // TODO: temp, should account for bsdfs at some point @@ -114,7 +119,7 @@ struct SMicrofacetNormals using anisocache_type = Cache; using matrix3x3_type = matrix; - using shadowing_method_type = ShadowingMethod; + using shadowing_method_type = ShadowingMethod; NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = conditional_value::value; @@ -124,10 +129,10 @@ struct SMicrofacetNormals template // TODO: concept for accessor anisotropic_interaction_type buildInteraction(NBL_CONST_REF_ARG(NormalsTexAccessor) normalMap, const vector2_type uv, const matrix object_to_world, const ray_dir_info_type V) NBL_CONST_MEMBER_FUNC { - const matrix TBN = hlsl::transpose(object_to_world); + // const matrix TBN = hlsl::transpose(object_to_world); vector3_type localN; - normalMap.get(localN, TBN[2], TBN[0], TBN[1]); - // normalMap.get(localN, uv); + // normalMap.get(localN, TBN[2], TBN[0], TBN[1]); + normalMap.get(localN, uv); localN = hlsl::promote(2.0) * localN - hlsl::promote(1.0); const vector3_type N = hlsl::normalize(hlsl::mul(object_to_world, localN)); @@ -173,11 +178,12 @@ struct SMicrofacetNormals spectral_type eval = hlsl::promote(0.0); const vector3_type L = _sample.getL().getDirection(); + const scalar_type clampedNdotNp = hlsl::max(scalar_type(0.0), NdotNp); const scalar_type NdotL = hlsl::dot(shadingNormal, L); const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); const scalar_type NtdotL = hlsl::dot(Nt, L); const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); - const scalar_type shadowing = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, + const scalar_type shadowing = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), clampedNdotNp, NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); // i -> p -> o @@ -194,9 +200,9 @@ struct SMicrofacetNormals L_reflected.setDirection(hlsl::normalize(reflectL(NtdotL))); sample_type sample_double_p = sample_type::create(L_reflected, Np); - const scalar_type notShadowedNpMirror = scalar_type(1.0) - shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), NdotNp, + const scalar_type notShadowedNpMirror = scalar_type(1.0) - shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), shadingNormal)), clampedNdotNp, sample_double_p.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(L_reflected.getDirection(), Nt))); - const scalar_type shadowing_t = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), hlsl::dot(shadingNormal, Nt), + const scalar_type shadowing_t = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, Nt)), NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); value_weight_type eval_double_p = nested_brdf.evalAndWeight(sample_double_p, interaction); @@ -239,6 +245,7 @@ struct SMicrofacetNormals } const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); + const scalar_type clampedNdotNp = hlsl::max(scalar_type(0.0), NdotNp); const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); @@ -252,7 +259,7 @@ struct SMicrofacetNormals return sample_type::createInvalid(); const vector3_type L = s.getL().getDirection(); - const scalar_type shadowed = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, + const scalar_type shadowed = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), clampedNdotNp, s.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); if (u.y > shadowed) @@ -268,17 +275,18 @@ struct SMicrofacetNormals } else { - // do one reflection if we start at wt + // do one reflection if we start at Nt Reflect reflect_V = Reflect::create(-V.getDirection(), Nt); const vector3_type V_negreflected = hlsl::normalize(reflect_V()); - // sample on wp + // sample on Np ray_dir_info_type Vnr; Vnr.setDirection(V_negreflected); typename bxdf_type::isotropic_interaction_type iso_negreflected = bxdf_type::isotropic_interaction_type::create(Vnr, Np); iso_negreflected.luminosityContributionHint = interaction.getLuminosityContributionHint(); typename bxdf_type::anisotropic_interaction_type interaction_negreflected = bxdf_type::anisotropic_interaction_type::create(iso_negreflected); s = nested_brdf.generate(interaction_negreflected, u, _cache.aniso_cache); + _cache.sampleIsShadowed = true; if (!s.isValid()) return s; } @@ -313,6 +321,7 @@ struct SMicrofacetNormals } const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); + const scalar_type clampedNdotNp = hlsl::max(scalar_type(0.0), NdotNp); const scalar_type NpdotV = interaction.getNdotV(); const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); @@ -325,7 +334,7 @@ struct SMicrofacetNormals const scalar_type NdotL = hlsl::dot(shadingNormal, L); const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); const scalar_type NtdotL = hlsl::dot(Nt, L); - pdf += lambda_p * nested_brdf.forwardPdf(_sample, interaction, __createChildCache(_sample, interaction)) * shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, + pdf += lambda_p * nested_brdf.forwardPdf(_sample, interaction, __createChildCache(_sample, interaction)) * shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), clampedNdotNp, NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); if (NtdotL > numeric_limits::min) @@ -336,7 +345,7 @@ struct SMicrofacetNormals sample_type sample_reflected = sample_type::create(L_reflected, Np); pdf += lambda_p * nested_brdf.forwardPdf(sample_reflected, interaction, __createChildCache(sample_reflected, interaction)) * - (scalar_type(1.0) - shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L_reflected.getDirection())), NdotNp, sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), NtdotL))); + (scalar_type(1.0) - shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L_reflected.getDirection())), clampedNdotNp, sample_reflected.getNdotL(BxDFClampMode::BCM_MAX), hlsl::max(scalar_type(0.0), NtdotL))); } } @@ -386,7 +395,7 @@ struct SMicrofacetNormals if (_cache.sampleIsShadowed) { const vector3_type L = _sample.getL().getDirection(); - quo *= shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); + quo *= shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), hlsl::max(scalar_type(0.0), NdotNp), NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); } return quotient_weight_type::create(quo, forwardPdf(_sample, interaction, _cache)); @@ -397,10 +406,260 @@ struct SMicrofacetNormals matrix3x3_type shadingBasis; }; +template +NBL_PARTIAL_REQ_TOP(config_concepts::BasicConfiguration) +struct SMicrofacetNormals) > +{ + using this_t = SMicrofacetNormals; + BXDF_CONFIG_TYPE_ALIASES(Config); + + NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = false; // TODO: temp, should account for bsdfs at some point + using bxdf_type = BRDF; + using random_type = conditional_t; + struct Cache + { + typename bxdf_type::isocache_type iso_cache; + typename bxdf_type::anisocache_type aniso_cache; + bool sampleIsShadowed; + }; + using isocache_type = Cache; + using anisocache_type = Cache; + using matrix3x3_type = matrix; + + using shadowing_method_type = ShadowingMethod; + + NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = conditional_value::value; + + // perturbed normal Np stored in interaction + // shading normal N (geometric normal in paper) stored in bxdf + // tangent normal Nt derived as needed + template // TODO: concept for accessor + anisotropic_interaction_type buildInteraction(NBL_CONST_REF_ARG(NormalsTexAccessor) normalMap, const vector2_type uv, const matrix object_to_world, const ray_dir_info_type V) NBL_CONST_MEMBER_FUNC + { + // const matrix TBN = hlsl::transpose(object_to_world); + vector3_type localN; + // normalMap.get(localN, TBN[2], TBN[0], TBN[1]); + normalMap.get(localN, uv); + localN = hlsl::promote(2.0) * localN - hlsl::promote(1.0); + + const vector3_type N = hlsl::normalize(hlsl::mul(object_to_world, localN)); + isotropic_interaction_type interaction = isotropic_interaction_type::create(V, N); + interaction.luminosityContributionHint = colorspace::scRGBtoXYZ[1]; + return anisotropic_interaction_type::create(interaction); + } + + template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && !traits::IsMicrofacet) + static typename bxdf_type::anisocache_type __createChildCache(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) + { + typename bxdf_type::anisocache_type cache; + return cache; + } + template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && traits::IsMicrofacet) + static typename bxdf_type::anisocache_type __createChildCache(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) + { + return bxdf_type::anisocache_type::template createForReflection(interaction, _sample); + } + + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + { + return evalAndWeight(_sample, anisotropic_interaction_type::create(interaction)); + } + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); + + const ray_dir_info_type V = interaction.getV(); + if (NdotNp > scalar_type(1.0 - 1e-5)) + { + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); + const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); + return nested_brdf.evalAndWeight(sample_N, interaction_N); + } + + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); + const scalar_type NpdotV = interaction.getNdotV(); + const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); + spectral_type eval = hlsl::promote(0.0); + + const vector3_type L = _sample.getL().getDirection(); + const scalar_type NdotL = hlsl::dot(shadingNormal, L); + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); + const scalar_type NtdotL = hlsl::dot(Nt, L); + const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); + const scalar_type shadowing = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, + NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); + + // i -> p -> o + { + value_weight_type eval_p = nested_brdf.evalAndWeight(_sample, interaction); + eval += eval_p.value() * lambda_p * shadowing; + } + + // i -> t -> o + if (NtdotV > scalar_type(0.0)) + { + sample_type sample_t = sample_type::create(_sample.getL(), Nt); + + typename bxdf_type::isotropic_interaction_type iso_t = bxdf_type::isotropic_interaction_type::create(V, Nt); + iso_t.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_t = bxdf_type::anisotropic_interaction_type::create(iso_t); + + const scalar_type shadowing_t = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), hlsl::dot(shadingNormal, Nt), + NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); + + value_weight_type eval_t = nested_brdf.evalAndWeight(sample_t, interaction_t); + eval += eval_t.value() * (scalar_type(1.0) - lambda_p) * shadowing_t; + } + + anisocache_type _cache; + _cache.aniso_cache = __createChildCache(_sample, interaction); + return value_weight_type::create(eval, forwardPdf(_sample, interaction, _cache)); + } + + sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const random_type u, NBL_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); + _cache.sampleIsShadowed = false; + + const ray_dir_info_type V = interaction.getV(); + if (NdotNp > scalar_type(1.0 - 1e-5)) + { + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); + return nested_brdf.generate(interaction_N, u, _cache.aniso_cache); + } + + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); + const scalar_type NpdotV = interaction.getNdotV(); + const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); + + sample_type s; + if (u.x < shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV))) + { + // sample on Np + s = nested_brdf.generate(interaction, u, _cache.aniso_cache); + } + else + { + // sample on Nt + typename bxdf_type::isotropic_interaction_type iso_t = bxdf_type::isotropic_interaction_type::create(V, Nt); + iso_t.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_t = bxdf_type::anisotropic_interaction_type::create(iso_t); + s = nested_brdf.generate(interaction_t, u, _cache.aniso_cache); + _cache.sampleIsShadowed = true; + } + if (!s.isValid() || hlsl::dot(shadingNormal, s.getL().getDirection()) < scalar_type(0.0)) + return sample_type::createInvalid(); + return s; + } + sample_type generate(NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, const random_type u, NBL_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + sample_type s = generate(anisotropic_interaction_type::create(interaction), u, _cache); + // _cache.iso_cache = _cache.aniso_cache.isotropic; // TODO: remove? + return s; + } + + scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + return forwardPdf(_sample, anisotropic_interaction_type::create(interaction), _cache); + } + scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); + + const ray_dir_info_type V = interaction.getV(); + if (NdotNp > scalar_type(1.0 - 1e-5)) + { + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); + const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); + return nested_brdf.forwardPdf(sample_N, interaction_N, __createChildCache(sample_N, interaction_N)); + } + + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); + const scalar_type NpdotV = interaction.getNdotV(); + const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); + + scalar_type pdf = scalar_type(0.0); + const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); + + if (lambda_p > scalar_type(0.0)) + { + const vector3_type L = _sample.getL().getDirection(); + const scalar_type NdotL = hlsl::dot(shadingNormal, L); + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); + const scalar_type NtdotL = hlsl::dot(Nt, L); + pdf += lambda_p * nested_brdf.forwardPdf(_sample, interaction, __createChildCache(_sample, interaction)) + * shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); + } + + if (lambda_p < scalar_type(1.0) && NtdotV > numeric_limits::min) + { + sample_type sample_t = sample_type::create(_sample.getL(), Nt); + + typename bxdf_type::isotropic_interaction_type iso_t = bxdf_type::isotropic_interaction_type::create(V, Nt); + iso_t.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_t = bxdf_type::anisotropic_interaction_type::create(iso_t); + + pdf += (scalar_type(1.0) - lambda_p) * nested_brdf.forwardPdf(sample_t, interaction_t, __createChildCache(sample_t, interaction_t)); + } + + return pdf; + } + + quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + return quotientAndWeight(_sample, anisotropic_interaction_type::create(interaction), _cache); + } + quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); + + const ray_dir_info_type V = interaction.getV(); + if (NdotNp > scalar_type(1.0 - 1e-5)) + { + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); + const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); + typename bxdf_type::anisocache_type cache_N = __createChildCache(sample_N, interaction_N); + return nested_brdf.quotientAndWeight(sample_N, interaction_N, cache_N); + } + + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); + spectral_type quo = hlsl::promote(1.0); + + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); + quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction, _cache.aniso_cache); + quo *= qw.quotient(); + + if (_cache.sampleIsShadowed) + { + const vector3_type L = _sample.getL().getDirection(); + quo *= shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, Nt)), NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); + } + + return quotient_weight_type::create(quo, forwardPdf(_sample, interaction, _cache)); + } + + bxdf_type nested_brdf; + vector3_type shadingNormal; + matrix3x3_type shadingBasis; +}; + + } -template -struct traits > +template +struct traits > { NBL_CONSTEXPR_STATIC_INLINE BxDFType type = BT_BRDF; NBL_CONSTEXPR_STATIC_INLINE bool IsMicrofacet = false; // should be microfacet? From 1b742e58512cd339798f7b4e0525a82eefe89299 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 27 Apr 2026 11:26:38 +0700 Subject: [PATCH 24/32] some minor bug fixes --- .../hlsl/bxdf/reflection/microfacet_normals.hlsl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 1d907bc5f1..339414cf0c 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -129,10 +129,10 @@ struct SMicrofacetNormals // TODO: concept for accessor anisotropic_interaction_type buildInteraction(NBL_CONST_REF_ARG(NormalsTexAccessor) normalMap, const vector2_type uv, const matrix object_to_world, const ray_dir_info_type V) NBL_CONST_MEMBER_FUNC { - // const matrix TBN = hlsl::transpose(object_to_world); + const matrix TBN = hlsl::transpose(object_to_world); vector3_type localN; - // normalMap.get(localN, TBN[2], TBN[0], TBN[1]); - normalMap.get(localN, uv); + normalMap.get(localN, TBN[2], TBN[0], TBN[1]); + // normalMap.get(localN, uv); localN = hlsl::promote(2.0) * localN - hlsl::promote(1.0); const vector3_type N = hlsl::normalize(hlsl::mul(object_to_world, localN)); @@ -436,10 +436,10 @@ struct SMicrofacetNormals // TODO: concept for accessor anisotropic_interaction_type buildInteraction(NBL_CONST_REF_ARG(NormalsTexAccessor) normalMap, const vector2_type uv, const matrix object_to_world, const ray_dir_info_type V) NBL_CONST_MEMBER_FUNC { - // const matrix TBN = hlsl::transpose(object_to_world); + const matrix TBN = hlsl::transpose(object_to_world); vector3_type localN; - // normalMap.get(localN, TBN[2], TBN[0], TBN[1]); - normalMap.get(localN, uv); + normalMap.get(localN, TBN[2], TBN[0], TBN[1]); + // normalMap.get(localN, uv); localN = hlsl::promote(2.0) * localN - hlsl::promote(1.0); const vector3_type N = hlsl::normalize(hlsl::mul(object_to_world, localN)); @@ -596,7 +596,7 @@ struct SMicrofacetNormals Date: Mon, 27 Apr 2026 12:23:32 +0700 Subject: [PATCH 25/32] split out the shadowing methods into ndf file --- .../bxdf/ndf/microfacet_normal_shadowing.hlsl | 100 ++++++++++ .../bxdf/reflection/microfacet_normals.hlsl | 173 +++++++++--------- src/nbl/builtin/CMakeLists.txt | 4 +- 3 files changed, 190 insertions(+), 87 deletions(-) create mode 100644 include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl diff --git a/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl b/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl new file mode 100644 index 0000000000..94938cf745 --- /dev/null +++ b/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl @@ -0,0 +1,100 @@ +// Copyright (C) 2018-2026 - 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_NDF_MICROFACET_NORMAL_SHADOWING_INCLUDED_ +#define _NBL_BUILTIN_HLSL_BXDF_NDF_MICROFACET_NORMAL_SHADOWING_INCLUDED_ + +namespace nbl +{ +namespace hlsl +{ +namespace bxdf +{ +namespace ndf +{ + +enum PerturbedNormalShadowing : uint16_t +{ + PNS_SCHUSSLER, + PNS_YINING +}; + +template +struct ShadowingMethod; + +// based on Microfacet-based Normal Mapping for Robust Monte Carlo Path Tracing: https://jo.dreggn.org/home/2017_normalmap.pdf +template +struct ShadowingMethod +{ + using scalar_type = T; + using vector3_type = vector; + using matrix3x3_type = matrix; + + static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) + { + const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); + return hlsl::min(scalar_type(1.0), + clampedNdotL * hlsl::max(scalar_type(0.0), NdotNp) + / (clampedNpdotL + clampedNtdotL * sinThetaNp) + ); + } + + static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) + { + const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); + return clampedNpdotV / (clampedNpdotV + clampedNtdotV * sinThetaNp); + } + + static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) + { + const vector3_type local_Np = hlsl::mul(shadingBasis, Np); + const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); + return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); + } +}; + +// based on Taming the Shadow Terminator: https://www.yiningkarlli.com/projects/shadowterminator/shadow_terminator_v1_1.pdf +template +struct ShadowingMethod +{ + using scalar_type = T; + using vector3_type = vector; + using matrix3x3_type = matrix; + + static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) + { + const scalar_type g = hlsl::min(scalar_type(1.0), + clampedNpdotL / (clampedNdotL * NdotNp) + ); + const scalar_type g2 = g * g; + return -g2 * g + g2 + g; + } + + // TODO: verify maths + // since Nt is now perpendicular to Np and not N + // total area of surface = 1 = hypotenuse of right triangle + // area of perturbed facet = cos(Np) = NdotNp + // area of tangent facet = cos(Nt) = sin(Np) + // projected area of Np onto V = area * NpdotV + static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) + { + const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); + const scalar_type ap = clampedNpdotV * NdotNp; + const scalar_type at = clampedNtdotV * sinThetaNp; + return ap / (ap + at); + } + + static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) + { + const vector3_type local_Np = hlsl::mul(shadingBasis, Np); + const vector3_type local_Nt = hlsl::normalize(vector3_type(local_Np.xy * -local_Np.z, 1.0 - local_Np.z*local_Np.z)); + return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); + } +}; + +} +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 339414cf0c..eb86c21026 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -7,6 +7,7 @@ #include "nbl/builtin/hlsl/bxdf/common.hlsl" #include "nbl/builtin/hlsl/bxdf/config.hlsl" #include "nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl" +#include "nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl" namespace nbl { @@ -17,93 +18,93 @@ namespace bxdf namespace reflection { -enum PerturbedNormalShadowing : uint16_t -{ - PNS_SCHUSSLER, - PNS_YINING -}; - -template -struct ShadowingMethod; - -// based on Microfacet-based Normal Mapping for Robust Monte Carlo Path Tracing: https://jo.dreggn.org/home/2017_normalmap.pdf -template -struct ShadowingMethod -{ - using scalar_type = T; - using vector3_type = vector; - using matrix3x3_type = matrix; - - static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) - { - const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); - return hlsl::min(scalar_type(1.0), - clampedNdotL * hlsl::max(scalar_type(0.0), NdotNp) - / (clampedNpdotL + clampedNtdotL * sinThetaNp) - ); - } - - static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) - { - const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); - return clampedNpdotV / (clampedNpdotV + clampedNtdotV * sinThetaNp); - } - - static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) - { - const vector3_type local_Np = hlsl::mul(shadingBasis, Np); - const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); - return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); - } -}; - -// based on Taming the Shadow Terminator: https://www.yiningkarlli.com/projects/shadowterminator/shadow_terminator_v1_1.pdf -template -struct ShadowingMethod -{ - using scalar_type = T; - using vector3_type = vector; - using matrix3x3_type = matrix; - - static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) - { - const scalar_type g = hlsl::min(scalar_type(1.0), - clampedNpdotL / (clampedNdotL * NdotNp) - ); - const scalar_type g2 = g * g; - return -g2 * g + g2 + g; - } - - // TODO: verify maths - // since Nt is now perpendicular to Np and not N - // total area of surface = 1 = hypotenuse of right triangle - // area of perturbed facet = cos(Np) = NdotNp - // area of tangent facet = cos(Nt) = sin(Np) - // projected area of Np onto V = area * NpdotV - static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) - { - const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); - const scalar_type ap = clampedNpdotV * NdotNp; - const scalar_type at = clampedNtdotV * sinThetaNp; - return ap / (ap + at); - } - - static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) - { - const vector3_type local_Np = hlsl::mul(shadingBasis, Np); - const vector3_type local_Nt = hlsl::normalize(vector3_type(local_Np.xy * -local_Np.z, 1.0 - local_Np.z*local_Np.z)); - return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); - } -}; - -template +// enum ndf::PerturbedNormalShadowing : uint16_t +// { +// PNS_SCHUSSLER, +// PNS_YINING +// }; + +// template +// struct ShadowingMethod; + +// // based on Microfacet-based Normal Mapping for Robust Monte Carlo Path Tracing: https://jo.dreggn.org/home/2017_normalmap.pdf +// template +// struct ShadowingMethod +// { +// using scalar_type = T; +// using vector3_type = vector; +// using matrix3x3_type = matrix; + +// static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) +// { +// const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); +// return hlsl::min(scalar_type(1.0), +// clampedNdotL * hlsl::max(scalar_type(0.0), NdotNp) +// / (clampedNpdotL + clampedNtdotL * sinThetaNp) +// ); +// } + +// static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) +// { +// const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); +// return clampedNpdotV / (clampedNpdotV + clampedNtdotV * sinThetaNp); +// } + +// static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) +// { +// const vector3_type local_Np = hlsl::mul(shadingBasis, Np); +// const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); +// return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); +// } +// }; + +// // based on Taming the Shadow Terminator: https://www.yiningkarlli.com/projects/shadowterminator/shadow_terminator_v1_1.pdf +// template +// struct ShadowingMethod +// { +// using scalar_type = T; +// using vector3_type = vector; +// using matrix3x3_type = matrix; + +// static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) +// { +// const scalar_type g = hlsl::min(scalar_type(1.0), +// clampedNpdotL / (clampedNdotL * NdotNp) +// ); +// const scalar_type g2 = g * g; +// return -g2 * g + g2 + g; +// } + +// // TODO: verify maths +// // since Nt is now perpendicular to Np and not N +// // total area of surface = 1 = hypotenuse of right triangle +// // area of perturbed facet = cos(Np) = NdotNp +// // area of tangent facet = cos(Nt) = sin(Np) +// // projected area of Np onto V = area * NpdotV +// static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) +// { +// const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); +// const scalar_type ap = clampedNpdotV * NdotNp; +// const scalar_type at = clampedNtdotV * sinThetaNp; +// return ap / (ap + at); +// } + +// static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) +// { +// const vector3_type local_Np = hlsl::mul(shadingBasis, Np); +// const vector3_type local_Nt = hlsl::normalize(vector3_type(local_Np.xy * -local_Np.z, 1.0 - local_Np.z*local_Np.z)); +// return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); +// } +// }; + +template struct SMicrofacetNormals; template NBL_PARTIAL_REQ_TOP(config_concepts::BasicConfiguration) -struct SMicrofacetNormals) > +struct SMicrofacetNormals) > { - using this_t = SMicrofacetNormals; + using this_t = SMicrofacetNormals; BXDF_CONFIG_TYPE_ALIASES(Config); NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = false; // TODO: temp, should account for bsdfs at some point @@ -119,7 +120,7 @@ struct SMicrofacetNormals; - using shadowing_method_type = ShadowingMethod; + using shadowing_method_type = ndf::ShadowingMethod; NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = conditional_value::value; @@ -406,7 +407,7 @@ struct SMicrofacetNormals +template NBL_PARTIAL_REQ_TOP(config_concepts::BasicConfiguration) struct SMicrofacetNormals) > { @@ -426,7 +427,7 @@ struct SMicrofacetNormals; - using shadowing_method_type = ShadowingMethod; + using shadowing_method_type = ndf::ShadowingMethod; NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = conditional_value::value; @@ -658,7 +659,7 @@ struct SMicrofacetNormals +template struct traits > { NBL_CONSTEXPR_STATIC_INLINE BxDFType type = BT_BRDF; diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index 96fdb8325c..b2492c15fe 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -310,6 +310,7 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/bxdf_traits.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/ndf/beckmann.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/ndf/ggx.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/ndf/microfacet_to_light_transform.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/base/lambertian.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/base/oren_nayar.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/base/cook_torrance_base.hlsl") @@ -319,7 +320,7 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/reflection/lambertian.hl 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/microfacet_normals.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/reflection/microfacet_normals.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") @@ -327,6 +328,7 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/oren_nayar. 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") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/microfacet_normals.hlsl") #path tracing LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/path_tracing/concepts.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/path_tracing/unidirectional.hlsl") From b4a0f3f79098ba5653feac7c56d86793b0dcde08 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 27 Apr 2026 16:26:50 +0700 Subject: [PATCH 26/32] microfacet normal bsdf, 1st order only --- .../hlsl/bxdf/base/cook_torrance_base.hlsl | 2 +- .../bxdf/reflection/microfacet_normals.hlsl | 79 ----- .../nbl/builtin/hlsl/bxdf/transmission.hlsl | 1 + .../bxdf/transmission/microfacet_normals.hlsl | 293 ++++++++++++++++++ 4 files changed, 295 insertions(+), 80 deletions(-) create mode 100644 include/nbl/builtin/hlsl/bxdf/transmission/microfacet_normals.hlsl 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 475ac905e1..f91a41e82b 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl @@ -205,7 +205,7 @@ struct SCookTorrance using oriented_etas_t = fresnel::OrientedEtas; oriented_etas_t orientedEta = oriented_etas_t::create(interaction.getNdotV(), hlsl::promote(eta)); vector3_type dummyH; - MicrofacetCache cache = MicrofacetCache::template create(interaction, _sample, orientedEta, dummyH); + MicrofacetCache cache = MicrofacetCache::template create(interaction, _sample, orientedEta, dummyH); return evalAndWeight(_sample, interaction, cache); } diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index eb86c21026..0d0e5f9530 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -18,85 +18,6 @@ namespace bxdf namespace reflection { -// enum ndf::PerturbedNormalShadowing : uint16_t -// { -// PNS_SCHUSSLER, -// PNS_YINING -// }; - -// template -// struct ShadowingMethod; - -// // based on Microfacet-based Normal Mapping for Robust Monte Carlo Path Tracing: https://jo.dreggn.org/home/2017_normalmap.pdf -// template -// struct ShadowingMethod -// { -// using scalar_type = T; -// using vector3_type = vector; -// using matrix3x3_type = matrix; - -// static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) -// { -// const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); -// return hlsl::min(scalar_type(1.0), -// clampedNdotL * hlsl::max(scalar_type(0.0), NdotNp) -// / (clampedNpdotL + clampedNtdotL * sinThetaNp) -// ); -// } - -// static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) -// { -// const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); -// return clampedNpdotV / (clampedNpdotV + clampedNtdotV * sinThetaNp); -// } - -// static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) -// { -// const vector3_type local_Np = hlsl::mul(shadingBasis, Np); -// const vector3_type local_Nt = hlsl::normalize(-vector3_type(local_Np.xy, 0.0)); -// return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); -// } -// }; - -// // based on Taming the Shadow Terminator: https://www.yiningkarlli.com/projects/shadowterminator/shadow_terminator_v1_1.pdf -// template -// struct ShadowingMethod -// { -// using scalar_type = T; -// using vector3_type = vector; -// using matrix3x3_type = matrix; - -// static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) -// { -// const scalar_type g = hlsl::min(scalar_type(1.0), -// clampedNpdotL / (clampedNdotL * NdotNp) -// ); -// const scalar_type g2 = g * g; -// return -g2 * g + g2 + g; -// } - -// // TODO: verify maths -// // since Nt is now perpendicular to Np and not N -// // total area of surface = 1 = hypotenuse of right triangle -// // area of perturbed facet = cos(Np) = NdotNp -// // area of tangent facet = cos(Nt) = sin(Np) -// // projected area of Np onto V = area * NpdotV -// static scalar_type lambdaP(const scalar_type NdotNp, const scalar_type clampedNpdotV, const scalar_type clampedNtdotV) -// { -// const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); -// const scalar_type ap = clampedNpdotV * NdotNp; -// const scalar_type at = clampedNtdotV * sinThetaNp; -// return ap / (ap + at); -// } - -// static vector3_type computeNt(const vector3_type Np, const matrix3x3_type shadingBasis) -// { -// const vector3_type local_Np = hlsl::mul(shadingBasis, Np); -// const vector3_type local_Nt = hlsl::normalize(vector3_type(local_Np.xy * -local_Np.z, 1.0 - local_Np.z*local_Np.z)); -// return hlsl::mul(hlsl::transpose(shadingBasis), local_Nt); -// } -// }; - template struct SMicrofacetNormals; diff --git a/include/nbl/builtin/hlsl/bxdf/transmission.hlsl b/include/nbl/builtin/hlsl/bxdf/transmission.hlsl index b5b6e101c1..e9ad84f7c5 100644 --- a/include/nbl/builtin/hlsl/bxdf/transmission.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/transmission.hlsl @@ -11,6 +11,7 @@ #include "nbl/builtin/hlsl/bxdf/transmission/beckmann.hlsl" #include "nbl/builtin/hlsl/bxdf/transmission/ggx.hlsl" #include "nbl/builtin/hlsl/bxdf/transmission/iridescent.hlsl" +#include "nbl/builtin/hlsl/bxdf/transmission/microfacet_normals.hlsl" namespace nbl { diff --git a/include/nbl/builtin/hlsl/bxdf/transmission/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/transmission/microfacet_normals.hlsl new file mode 100644 index 0000000000..f7fcd123c9 --- /dev/null +++ b/include/nbl/builtin/hlsl/bxdf/transmission/microfacet_normals.hlsl @@ -0,0 +1,293 @@ +// 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_MICROFACET_NORMALS_INCLUDED_ +#define _NBL_BUILTIN_HLSL_BXDF_TRANSMISSION_MICROFACET_NORMALS_INCLUDED_ + +#include "nbl/builtin/hlsl/bxdf/common.hlsl" +#include "nbl/builtin/hlsl/bxdf/config.hlsl" +#include "nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl" +#include "nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl" + +namespace nbl +{ +namespace hlsl +{ +namespace bxdf +{ +namespace transmission +{ + +template +struct SMicrofacetNormals; + +template +NBL_PARTIAL_REQ_TOP(config_concepts::BasicConfiguration) +struct SMicrofacetNormals) > +{ + using this_t = SMicrofacetNormals; + BXDF_CONFIG_TYPE_ALIASES(Config); + + NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = true; // TODO: might combine with brdf version? + using bxdf_type = BRDF; + using random_type = conditional_t; + struct Cache + { + typename bxdf_type::isocache_type iso_cache; + typename bxdf_type::anisocache_type aniso_cache; + bool sampleIsShadowed; + }; + using isocache_type = Cache; + using anisocache_type = Cache; + using matrix3x3_type = matrix; + + using shadowing_method_type = ndf::ShadowingMethod; + + NBL_CONSTEXPR_STATIC_INLINE BxDFClampMode _clamp = conditional_value::value; + + // perturbed normal Np stored in interaction + // shading normal N (geometric normal in paper) stored in bxdf + // tangent normal Nt derived as needed + template // TODO: concept for accessor + anisotropic_interaction_type buildInteraction(NBL_CONST_REF_ARG(NormalsTexAccessor) normalMap, const vector2_type uv, const matrix object_to_world, const ray_dir_info_type V) NBL_CONST_MEMBER_FUNC + { + const matrix TBN = hlsl::transpose(object_to_world); + vector3_type localN; + normalMap.get(localN, TBN[2], TBN[0], TBN[1]); + // normalMap.get(localN, uv); + localN = hlsl::promote(2.0) * localN - hlsl::promote(1.0); + + const vector3_type N = hlsl::normalize(hlsl::mul(object_to_world, localN)); + isotropic_interaction_type interaction = isotropic_interaction_type::create(V, N); + interaction.luminosityContributionHint = colorspace::scRGBtoXYZ[1]; + return anisotropic_interaction_type::create(interaction); + } + + template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && !traits::IsMicrofacet) + typename bxdf_type::anisocache_type __createChildCache(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + { + typename bxdf_type::anisocache_type cache; + return cache; + } + template::IsMicrofacet> NBL_FUNC_REQUIRES(C::value && traits::IsMicrofacet) + typename bxdf_type::anisocache_type __createChildCache(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + { + const scalar_type eta = nested_bsdf.fresnel.getRefractionOrientedEta(); + using oriented_etas_t = fresnel::OrientedEtas; + oriented_etas_t orientedEta = oriented_etas_t::create(scalar_type(1.0), hlsl::promote(eta)); + return bxdf_type::anisocache_type::template create(interaction, _sample, orientedEta); + } + + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + { + return evalAndWeight(_sample, anisotropic_interaction_type::create(interaction)); + } + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); + + const ray_dir_info_type V = interaction.getV(); + if (NdotNp > scalar_type(1.0 - 1e-5)) + { + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); + const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); + return nested_bsdf.evalAndWeight(sample_N, interaction_N); + } + + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); + const scalar_type NpdotV = interaction.getNdotV(); + const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); + spectral_type eval = hlsl::promote(0.0); + + const vector3_type L = _sample.getL().getDirection(); + const scalar_type NdotL = hlsl::dot(shadingNormal, L); + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); + const scalar_type NtdotL = hlsl::dot(Nt, L); + const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::abs(NpdotV), hlsl::abs(NtdotV)); + const scalar_type shadowing = shadowing_method_type::G1(hlsl::abs(NdotL), NdotNp, + NpdotL, hlsl::abs(NtdotL)); + + // i -> p -> o + { + value_weight_type eval_p = nested_bsdf.evalAndWeight(_sample, interaction); + eval += eval_p.value() * lambda_p * shadowing; + } + + // i -> t -> o + if (NtdotV > scalar_type(0.0)) + { + sample_type sample_t = sample_type::create(_sample.getL(), Nt); + + typename bxdf_type::isotropic_interaction_type iso_t = bxdf_type::isotropic_interaction_type::create(V, Nt); + iso_t.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_t = bxdf_type::anisotropic_interaction_type::create(iso_t); + + const scalar_type shadowing_t = shadowing_method_type::G1(hlsl::abs(NdotL), hlsl::dot(shadingNormal, Nt), + NpdotL, hlsl::abs(NtdotL)); + + value_weight_type eval_t = nested_bsdf.evalAndWeight(sample_t, interaction_t); + eval += eval_t.value() * (scalar_type(1.0) - lambda_p) * shadowing_t; + } + + anisocache_type _cache; + _cache.aniso_cache = __createChildCache(_sample, interaction); + return value_weight_type::create(eval, forwardPdf(_sample, interaction, _cache)); + } + + sample_type generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const random_type u, NBL_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); + _cache.sampleIsShadowed = false; + + const ray_dir_info_type V = interaction.getV(); + if (NdotNp > scalar_type(1.0 - 1e-5)) + { + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); + return nested_bsdf.generate(interaction_N, u, _cache.aniso_cache); + } + + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); + const scalar_type NpdotV = interaction.getNdotV(); + const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); + + sample_type s; + if (u.x < shadowing_method_type::lambdaP(NdotNp, hlsl::abs(NpdotV), hlsl::abs(NtdotV))) + { + // sample on Np + s = nested_bsdf.generate(interaction, u, _cache.aniso_cache); + } + else + { + // sample on Nt + typename bxdf_type::isotropic_interaction_type iso_t = bxdf_type::isotropic_interaction_type::create(V, Nt); + iso_t.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_t = bxdf_type::anisotropic_interaction_type::create(iso_t); + s = nested_bsdf.generate(interaction_t, u, _cache.aniso_cache); + _cache.sampleIsShadowed = true; + } + if (!s.isValid()) + return sample_type::createInvalid(); + return s; + } + sample_type generate(NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, const random_type u, NBL_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + sample_type s = generate(anisotropic_interaction_type::create(interaction), u, _cache); + // _cache.iso_cache = _cache.aniso_cache.isotropic; // TODO: remove? + return s; + } + + scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + return forwardPdf(_sample, anisotropic_interaction_type::create(interaction), _cache); + } + scalar_type forwardPdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); + + const ray_dir_info_type V = interaction.getV(); + if (NdotNp > scalar_type(1.0 - 1e-5)) + { + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); + const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); + return nested_bsdf.forwardPdf(sample_N, interaction_N, __createChildCache(sample_N, interaction_N)); + } + + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); + const scalar_type NpdotV = interaction.getNdotV(); + const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); + + scalar_type pdf = scalar_type(0.0); + const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::abs(NpdotV), hlsl::abs(NtdotV)); + + if (lambda_p > scalar_type(0.0)) + { + const vector3_type L = _sample.getL().getDirection(); + const scalar_type NdotL = hlsl::dot(shadingNormal, L); + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); + const scalar_type NtdotL = hlsl::dot(Nt, L); + pdf += lambda_p * nested_bsdf.forwardPdf(_sample, interaction, _cache.aniso_cache) + * shadowing_method_type::G1(hlsl::abs(NdotL), NdotNp, NpdotL, hlsl::abs(NtdotL)); + } + + if (lambda_p < scalar_type(1.0) && NtdotV > numeric_limits::min) + { + sample_type sample_t = sample_type::create(_sample.getL(), Nt); + + typename bxdf_type::isotropic_interaction_type iso_t = bxdf_type::isotropic_interaction_type::create(V, Nt); + iso_t.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_t = bxdf_type::anisotropic_interaction_type::create(iso_t); + + pdf += (scalar_type(1.0) - lambda_p) * nested_bsdf.forwardPdf(sample_t, interaction_t, __createChildCache(sample_t, interaction_t)); + } + + return pdf; + } + + quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction, NBL_CONST_REF_ARG(isocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + return quotientAndWeight(_sample, anisotropic_interaction_type::create(interaction), _cache); + } + quotient_weight_type quotientAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, NBL_CONST_REF_ARG(anisocache_type) _cache) NBL_CONST_MEMBER_FUNC + { + const vector3_type Np = interaction.getN(); + const scalar_type NdotNp = hlsl::dot(shadingNormal, Np); + + const ray_dir_info_type V = interaction.getV(); + if (NdotNp > scalar_type(1.0 - 1e-5)) + { + typename bxdf_type::isotropic_interaction_type iso = bxdf_type::isotropic_interaction_type::create(V, shadingNormal); + iso.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_N = bxdf_type::anisotropic_interaction_type::create(iso); + const sample_type sample_N = sample_type::create(_sample.getL(), shadingNormal); + typename bxdf_type::anisocache_type cache_N = __createChildCache(sample_N, interaction_N); + return nested_bsdf.quotientAndWeight(sample_N, interaction_N, cache_N); + } + + const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); + spectral_type quo = hlsl::promote(1.0); + + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); + quotient_weight_type qw = nested_bsdf.quotientAndWeight(_sample, interaction, _cache.aniso_cache); + quo *= qw.quotient(); + + if (_cache.sampleIsShadowed) + { + const vector3_type L = _sample.getL().getDirection(); + quo *= shadowing_method_type::G1(hlsl::abs(hlsl::dot(shadingNormal, L)), hlsl::abs(hlsl::dot(shadingNormal, Nt)), NpdotL, hlsl::abs(hlsl::dot(Nt, L))); + } + + return quotient_weight_type::create(quo, forwardPdf(_sample, interaction, _cache)); + } + + bxdf_type nested_bsdf; + vector3_type shadingNormal; + matrix3x3_type shadingBasis; +}; + + +} + +template +struct traits > +{ + NBL_CONSTEXPR_STATIC_INLINE BxDFType type = BT_BSDF; + NBL_CONSTEXPR_STATIC_INLINE bool IsMicrofacet = false; // should be microfacet? + NBL_CONSTEXPR_STATIC_INLINE bool clampNdotV = false; + NBL_CONSTEXPR_STATIC_INLINE bool clampNdotL = true; + NBL_CONSTEXPR_STATIC_INLINE bool TractablePdf = true; +}; + +} +} +} + +#endif From 12b9c59868c5240b3721d6846203375eba42f43a Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 27 Apr 2026 16:45:40 +0700 Subject: [PATCH 27/32] latest example --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index a3b41ac8dc..8d5c9006ca 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit a3b41ac8dc2b37bb86dff574d783a90e0cdb3009 +Subproject commit 8d5c9006ca108675d75779f8579a81fd27c31456 From db2dc2633ce70cc2aeb09d0bec79c870fe9a3718 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 28 Apr 2026 16:31:01 +0700 Subject: [PATCH 28/32] fix quotient using wrong V when sample generated from tangent facet --- .../bxdf/reflection/microfacet_normals.hlsl | 51 ++++++++++++++--- .../bxdf/transmission/microfacet_normals.hlsl | 57 +++++++++++++------ 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 0d0e5f9530..1a49933d8e 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -28,13 +28,14 @@ struct SMicrofacetNormals; BXDF_CONFIG_TYPE_ALIASES(Config); - NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = false; // TODO: temp, should account for bsdfs at some point + NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = false; using bxdf_type = BRDF; using random_type = conditional_t; struct Cache { typename bxdf_type::isocache_type iso_cache; typename bxdf_type::anisocache_type aniso_cache; + bool sampleFromNt; bool sampleIsShadowed; }; using isocache_type = Cache; @@ -176,6 +177,7 @@ struct SMicrofacetNormals reflect_V = Reflect::create(-V.getDirection(), Nt); const vector3_type V_negreflected = hlsl::normalize(reflect_V()); + _cache.sampleFromNt = true; // sample on Np ray_dir_info_type Vnr; @@ -310,13 +313,30 @@ struct SMicrofacetNormals(1.0); - const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); - quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction, _cache.aniso_cache); - quo *= qw.quotient(); + if (_cache.sampleFromNt) + { + Reflect reflect_V = Reflect::create(-V.getDirection(), Nt); + const vector3_type V_negreflected = hlsl::normalize(reflect_V()); + + ray_dir_info_type Vnr; + Vnr.setDirection(V_negreflected); + typename bxdf_type::isotropic_interaction_type iso_negreflected = bxdf_type::isotropic_interaction_type::create(Vnr, Np); + iso_negreflected.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_negreflected = bxdf_type::anisotropic_interaction_type::create(iso_negreflected); + + quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction_negreflected, _cache.aniso_cache); + quo *= qw.quotient(); + } + else + { + quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction, _cache.aniso_cache); + quo *= qw.quotient(); + } if (_cache.sampleIsShadowed) { const vector3_type L = _sample.getL().getDirection(); + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); quo *= shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), hlsl::max(scalar_type(0.0), NdotNp), NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); } @@ -335,13 +355,14 @@ struct SMicrofacetNormals; BXDF_CONFIG_TYPE_ALIASES(Config); - NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = false; // TODO: temp, should account for bsdfs at some point + NBL_CONSTEXPR_STATIC_INLINE bool IsBSDF = false; // TODO: could probably merge with transmitted 1st order using bxdf_type = BRDF; using random_type = conditional_t; struct Cache { typename bxdf_type::isocache_type iso_cache; typename bxdf_type::anisocache_type aniso_cache; + bool sampleFromNt; bool sampleIsShadowed; }; using isocache_type = Cache; @@ -465,6 +486,7 @@ struct SMicrofacetNormals(1.0); - const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); - quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction, _cache.aniso_cache); - quo *= qw.quotient(); + if (_cache.sampleFromNt) + { + typename bxdf_type::isotropic_interaction_type iso_t = bxdf_type::isotropic_interaction_type::create(V, Nt); + iso_t.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_t = bxdf_type::anisotropic_interaction_type::create(iso_t); + + quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction_t, _cache.aniso_cache); + quo *= qw.quotient(); + } + else + { + quotient_weight_type qw = nested_brdf.quotientAndWeight(_sample, interaction, _cache.aniso_cache); + quo *= qw.quotient(); + } if (_cache.sampleIsShadowed) { const vector3_type L = _sample.getL().getDirection(); + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); quo *= shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, Nt)), NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); } diff --git a/include/nbl/builtin/hlsl/bxdf/transmission/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/transmission/microfacet_normals.hlsl index f7fcd123c9..4511ae864a 100644 --- a/include/nbl/builtin/hlsl/bxdf/transmission/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/transmission/microfacet_normals.hlsl @@ -35,6 +35,7 @@ struct SMicrofacetNormals(V.getDirection(), hlsl::promote(NdotV)); const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); - const scalar_type NpdotV = interaction.getNdotV(); - const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); + const scalar_type NpdotV = hlsl::dot(Np, upperHemisphereV); + const scalar_type NtdotV = hlsl::dot(Nt, upperHemisphereV); spectral_type eval = hlsl::promote(0.0); const vector3_type L = _sample.getL().getDirection(); const scalar_type NdotL = hlsl::dot(shadingNormal, L); const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); const scalar_type NtdotL = hlsl::dot(Nt, L); - const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::abs(NpdotV), hlsl::abs(NtdotV)); - const scalar_type shadowing = shadowing_method_type::G1(hlsl::abs(NdotL), NdotNp, - NpdotL, hlsl::abs(NtdotL)); + const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); + const scalar_type shadowing = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, + NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); // i -> p -> o { @@ -125,8 +128,8 @@ struct SMicrofacetNormals(V.getDirection(), hlsl::promote(NdotV)); const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); - const scalar_type NpdotV = interaction.getNdotV(); - const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); + const scalar_type NpdotV = hlsl::dot(Np, upperHemisphereV); + const scalar_type NtdotV = hlsl::dot(Nt, upperHemisphereV); sample_type s; - if (u.x < shadowing_method_type::lambdaP(NdotNp, hlsl::abs(NpdotV), hlsl::abs(NtdotV))) + if (u.x < shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV))) { // sample on Np s = nested_bsdf.generate(interaction, u, _cache.aniso_cache); + _cache.sampleFromNt = false; } else { @@ -169,6 +175,7 @@ struct SMicrofacetNormals(V.getDirection(), hlsl::promote(NdotV)); const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); - const scalar_type NpdotV = interaction.getNdotV(); - const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); + const scalar_type NpdotV = hlsl::dot(Np, upperHemisphereV); + const scalar_type NtdotV = hlsl::dot(Nt, upperHemisphereV); scalar_type pdf = scalar_type(0.0); - const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::abs(NpdotV), hlsl::abs(NtdotV)); + const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); if (lambda_p > scalar_type(0.0)) { @@ -215,7 +224,7 @@ struct SMicrofacetNormals numeric_limits::min) @@ -255,14 +264,26 @@ struct SMicrofacetNormals(1.0); - const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); - quotient_weight_type qw = nested_bsdf.quotientAndWeight(_sample, interaction, _cache.aniso_cache); - quo *= qw.quotient(); + if (_cache.sampleFromNt) + { + typename bxdf_type::isotropic_interaction_type iso_t = bxdf_type::isotropic_interaction_type::create(V, Nt); + iso_t.luminosityContributionHint = interaction.getLuminosityContributionHint(); + typename bxdf_type::anisotropic_interaction_type interaction_t = bxdf_type::anisotropic_interaction_type::create(iso_t); + + quotient_weight_type qw = nested_bsdf.quotientAndWeight(_sample, interaction_t, _cache.aniso_cache); + quo *= qw.quotient(); + } + else + { + quotient_weight_type qw = nested_bsdf.quotientAndWeight(_sample, interaction, _cache.aniso_cache); + quo *= qw.quotient(); + } if (_cache.sampleIsShadowed) { const vector3_type L = _sample.getL().getDirection(); - quo *= shadowing_method_type::G1(hlsl::abs(hlsl::dot(shadingNormal, L)), hlsl::abs(hlsl::dot(shadingNormal, Nt)), NpdotL, hlsl::abs(hlsl::dot(Nt, L))); + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); + quo *= shadowing_method_type::G1(hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, L)), hlsl::max(scalar_type(0.0), hlsl::dot(shadingNormal, Nt)), NpdotL, hlsl::max(scalar_type(0.0), hlsl::dot(Nt, L))); } return quotient_weight_type::create(quo, forwardPdf(_sample, interaction, _cache)); From 6e84d79a6bceab3405934c908c9edd549b132c15 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 29 Apr 2026 14:53:33 +0700 Subject: [PATCH 29/32] fix yining masking term --- .../builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl b/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl index 94938cf745..0a03036622 100644 --- a/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl @@ -64,7 +64,7 @@ struct ShadowingMethod static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) { const scalar_type g = hlsl::min(scalar_type(1.0), - clampedNpdotL / (clampedNdotL * NdotNp) + clampedNdotL / (clampedNpdotL * NdotNp) ); const scalar_type g2 = g * g; return -g2 * g + g2 + g; @@ -80,6 +80,8 @@ struct ShadowingMethod { const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); const scalar_type ap = clampedNpdotV * NdotNp; + if (ap < numeric_limits::min) + return scalar_type(0.0); const scalar_type at = clampedNtdotV * sinThetaNp; return ap / (ap + at); } From 965e9fc992ee4f87807976f58221dd33ab5767f2 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 29 Apr 2026 16:34:19 +0700 Subject: [PATCH 30/32] fix masking/shadowing conditions usage, especially for microfacet normals transmission --- .../bxdf/ndf/microfacet_normal_shadowing.hlsl | 6 ++-- .../bxdf/reflection/microfacet_normals.hlsl | 4 +-- .../bxdf/transmission/microfacet_normals.hlsl | 34 ++++++++----------- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl b/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl index 0a03036622..7b8860bf7d 100644 --- a/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/ndf/microfacet_normal_shadowing.hlsl @@ -30,7 +30,7 @@ struct ShadowingMethod using vector3_type = vector; using matrix3x3_type = matrix; - static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) + static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL, const bool isTangentFacet=false) { const scalar_type sinThetaNp = hlsl::sqrt(hlsl::max(1.0 - NdotNp * NdotNp, 0.0)); return hlsl::min(scalar_type(1.0), @@ -61,10 +61,10 @@ struct ShadowingMethod using vector3_type = vector; using matrix3x3_type = matrix; - static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL) + static scalar_type G1(const scalar_type clampedNdotL, const scalar_type NdotNp, const scalar_type clampedNpdotL, const scalar_type clampedNtdotL, const bool isTangentFacet=false) { const scalar_type g = hlsl::min(scalar_type(1.0), - clampedNdotL / (clampedNpdotL * NdotNp) + clampedNdotL / (hlsl::mix(clampedNpdotL, clampedNtdotL, isTangentFacet) * NdotNp) ); const scalar_type g2 = g * g; return -g2 * g + g2 + g; diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 1a49933d8e..e411f59bfb 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -451,7 +451,7 @@ struct SMicrofacetNormals(V.getDirection(), hlsl::promote(NdotV)); const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); - const scalar_type NpdotV = hlsl::dot(Np, upperHemisphereV); - const scalar_type NtdotV = hlsl::dot(Nt, upperHemisphereV); + const scalar_type NpdotV = interaction.getNdotV(); + const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); spectral_type eval = hlsl::promote(0.0); const vector3_type L = _sample.getL().getDirection(); const scalar_type NdotL = hlsl::dot(shadingNormal, L); - const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_ABS); const scalar_type NtdotL = hlsl::dot(Nt, L); - const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); - const scalar_type shadowing = shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, - NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); + const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::abs(NpdotV), hlsl::abs(NtdotV)); + const scalar_type shadowing = shadowing_method_type::G1(hlsl::abs(NdotL), NdotNp, + NpdotL, hlsl::abs(NtdotL)); // i -> p -> o { @@ -128,8 +126,8 @@ struct SMicrofacetNormals(V.getDirection(), hlsl::promote(NdotV)); const vector3_type Nt = shadowing_method_type::computeNt(Np, shadingBasis); - const scalar_type NpdotV = hlsl::dot(Np, upperHemisphereV); - const scalar_type NtdotV = hlsl::dot(Nt, upperHemisphereV); + const scalar_type NpdotV = interaction.getNdotV(); + const scalar_type NtdotV = hlsl::dot(Nt, V.getDirection()); scalar_type pdf = scalar_type(0.0); - const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::max(scalar_type(0.0), NpdotV), hlsl::max(scalar_type(0.0), NtdotV)); + const scalar_type lambda_p = shadowing_method_type::lambdaP(NdotNp, hlsl::abs(NpdotV), hlsl::abs(NtdotV)); if (lambda_p > scalar_type(0.0)) { const vector3_type L = _sample.getL().getDirection(); const scalar_type NdotL = hlsl::dot(shadingNormal, L); - const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_MAX); + const scalar_type NpdotL = _sample.getNdotL(BxDFClampMode::BCM_ABS); const scalar_type NtdotL = hlsl::dot(Nt, L); pdf += lambda_p * nested_bsdf.forwardPdf(_sample, interaction, _cache.aniso_cache) - * shadowing_method_type::G1(hlsl::max(scalar_type(0.0), NdotL), NdotNp, NpdotL, hlsl::max(scalar_type(0.0), NtdotL)); + * shadowing_method_type::G1(hlsl::abs(NdotL), NdotNp, NpdotL, hlsl::abs(NtdotL)); } if (lambda_p < scalar_type(1.0) && NtdotV > numeric_limits::min) @@ -282,8 +278,8 @@ struct SMicrofacetNormals Date: Thu, 30 Apr 2026 16:54:03 +0700 Subject: [PATCH 31/32] fix passing wrong cache to child bxdf in tangent facet case for quotient --- .../nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl | 4 ++-- .../builtin/hlsl/bxdf/transmission/microfacet_normals.hlsl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index e411f59bfb..011f0038b6 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -324,7 +324,7 @@ struct SMicrofacetNormals Date: Fri, 1 May 2026 11:04:42 +0700 Subject: [PATCH 32/32] mix child bxdf in microfacet normals quotient properly --- .../bxdf/reflection/microfacet_normals.hlsl | 32 ++++++++++++++++--- .../bxdf/transmission/microfacet_normals.hlsl | 16 ++++++++-- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl index 011f0038b6..e549c21e50 100644 --- a/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/reflection/microfacet_normals.hlsl @@ -76,6 +76,13 @@ struct SMicrofacetNormals(interaction, _sample); } + static spectral_type __calculateQuotient(const spectral_type quo, const spectral_type quo_other, const scalar_type choiceProb, const scalar_type pdf, const scalar_type pdf_other) + { + const scalar_type weight = pdf / (pdf + pdf_other); // balance heuristic + return (quo * weight / choiceProb) / (scalar_type(1.0) + pdf_other * (scalar_type(1.0) - choiceProb) / (pdf * choiceProb)) + + (quo_other * (scalar_type(1.0) - weight)) / (pdf_other * (scalar_type(1.0) - choiceProb) + (pdf * choiceProb)); + } + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { return evalAndWeight(_sample, anisotropic_interaction_type::create(interaction)); @@ -311,6 +318,9 @@ struct SMicrofacetNormals(1.0); if (_cache.sampleFromNt) @@ -325,12 +335,14 @@ struct SMicrofacetNormals(interaction, _sample); } + static spectral_type __calculateQuotient(const spectral_type quo, const spectral_type quo_other, const scalar_type choiceProb, const scalar_type pdf, const scalar_type pdf_other) + { + const scalar_type weight = pdf / (pdf + pdf_other); // balance heuristic + return (quo * weight / choiceProb) / (scalar_type(1.0) + pdf_other * (scalar_type(1.0) - choiceProb) / (pdf * choiceProb)) + + (quo_other * (scalar_type(1.0) - weight)) / (pdf_other * (scalar_type(1.0) - choiceProb) + (pdf * choiceProb)); + } + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { return evalAndWeight(_sample, anisotropic_interaction_type::create(interaction)); @@ -580,6 +599,9 @@ struct SMicrofacetNormals(1.0); if (_cache.sampleFromNt) @@ -589,12 +611,14 @@ struct SMicrofacetNormals(interaction, _sample, orientedEta); } + static spectral_type __calculateQuotient(const spectral_type quo, const spectral_type quo_other, const scalar_type choiceProb, const scalar_type pdf, const scalar_type pdf_other) + { + const scalar_type weight = pdf / (pdf + pdf_other); // balance heuristic + return (quo * weight / choiceProb) / (scalar_type(1.0) + pdf_other * (scalar_type(1.0) - choiceProb) / (pdf * choiceProb)) + + (quo_other * (scalar_type(1.0) - weight)) / (pdf_other * (scalar_type(1.0) - choiceProb) + (pdf * choiceProb)); + } + value_weight_type evalAndWeight(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { return evalAndWeight(_sample, anisotropic_interaction_type::create(interaction)); @@ -258,6 +265,9 @@ struct SMicrofacetNormals(1.0); if (_cache.sampleFromNt) @@ -267,12 +277,14 @@ struct SMicrofacetNormals