From f71610b381b86b59c541e81e3dc5a17f5292176e Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Wed, 18 Feb 2026 23:08:19 +0300 Subject: [PATCH 01/33] Created concepts for samplers, added quotient_and_pdf variants to satisfy the concepts --- examples_tests | 2 +- .../nbl/builtin/hlsl/sampling/bilinear.hlsl | 7 + .../hlsl/sampling/box_muller_transform.hlsl | 48 ++-- .../nbl/builtin/hlsl/sampling/concepts.hlsl | 170 +++++++++++++++ .../hlsl/sampling/cos_weighted_spheres.hlsl | 142 ++++++------ include/nbl/builtin/hlsl/sampling/linear.hlsl | 74 ++++--- .../projected_spherical_triangle.hlsl | 161 +++++++------- .../hlsl/sampling/quotient_and_pdf.hlsl | 136 +++++++++--- .../hlsl/sampling/spherical_rectangle.hlsl | 150 +++++++------ .../hlsl/sampling/spherical_triangle.hlsl | 206 +++++++++--------- .../hlsl/sampling/uniform_spheres.hlsl | 114 +++++----- 11 files changed, 760 insertions(+), 450 deletions(-) create mode 100644 include/nbl/builtin/hlsl/sampling/concepts.hlsl diff --git a/examples_tests b/examples_tests index 655aa991e9..ebf25f4ea0 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 655aa991e96c8e1466d3c61c16f0d12fa36e86df +Subproject commit ebf25f4ea033960b89f9e5192b031cfa7b2a3b52 diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index a74869990f..65f3b33f10 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -24,6 +24,13 @@ struct Bilinear using vector3_type = vector; using vector4_type = vector; + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; + static Bilinear create(const vector4_type bilinearCoeffs) { Bilinear retval; diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index 9474642f4c..9385233a11 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -7,32 +7,38 @@ #include "nbl/builtin/hlsl/math/functions.hlsl" #include "nbl/builtin/hlsl/numbers.hlsl" +#include "nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl" namespace nbl { -namespace hlsl -{ -namespace sampling -{ - -template) -struct BoxMullerTransform -{ - using scalar_type = T; - using vector2_type = vector; - - vector2_type operator()(const vector2_type xi) + namespace hlsl { - scalar_type sinPhi, cosPhi; - math::sincos(2.0 * numbers::pi * xi.y - numbers::pi, sinPhi, cosPhi); - return vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(-2.0 * nbl::hlsl::log(xi.x)) * stddev; + namespace sampling + { + + template ) struct BoxMullerTransform + { + using scalar_type = T; + using vector2_type = vector; + + // BackwardDensitySampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + + vector2_type operator()(const vector2_type xi) + { + scalar_type sinPhi, cosPhi; + math::sincos(2.0 * numbers::pi * xi.y - numbers::pi, sinPhi, cosPhi); + return vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(-2.0 * nbl::hlsl::log(xi.x)) * stddev; + } + + T stddev; + }; + + } } - - T stddev; -}; - -} -} } #endif diff --git a/include/nbl/builtin/hlsl/sampling/concepts.hlsl b/include/nbl/builtin/hlsl/sampling/concepts.hlsl new file mode 100644 index 0000000000..9a56173c72 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/concepts.hlsl @@ -0,0 +1,170 @@ +#ifndef _NBL_BUILTIN_HLSL_SAMPLING_CONCEPTS_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_CONCEPTS_INCLUDED_ + +#include + +namespace nbl +{ + namespace hlsl + { + namespace sampling + { + namespace concepts + { + + // ============================================================================ + // BasicSampler + // + // The simplest sampler: maps domain -> codomain. + // + // Required types: + // domain_type - the input space (e.g. float for 1D, float2 for 2D) + // codomain_type - the output space (e.g. float3 for directions) + // + // Required methods: + // codomain_type generate(domain_type u) + // ============================================================================ + +#define NBL_CONCEPT_NAME BasicSampler +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (sampler, T) +#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) + NBL_CONCEPT_BEGIN(2) +#define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 + NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::codomain_type)) + ); +#undef u +#undef sampler +#include + + // ============================================================================ + // TractableSampler + // + // A sampler whose density can be computed analytically in the forward + // (sampling) direction. The generate method returns the sample bundled + // with its density to avoid redundant computation. + // + // Required types: + // domain_type - the input space + // codomain_type - the output space + // density_type - the density type (typically scalar) + // sample_type - bundled return of generate, should be one of: + // codomain_and_rcpPdf (preferred) + // codomain_and_pdf + // + // Required methods: + // sample_type generate(domain_type u) - sample + density + // density_type forwardPdf(domain_type u) - density only + // ============================================================================ + +#define NBL_CONCEPT_NAME TractableSampler +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (sampler, T) +#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) + NBL_CONCEPT_BEGIN(2) +#define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 + NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::density_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type)) + ); +#undef u +#undef sampler +#include + + // ============================================================================ + // BackwardDensitySampler + // + // Extends TractableSampler with the ability to evaluate the PDF given + // a codomain value (i.e. without knowing the original domain input). + // + // Required methods (in addition to TractableSampler): + // density_type backwardPdf(codomain_type v) + // ============================================================================ + +#define NBL_CONCEPT_NAME BackwardDensitySampler +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (sampler, T) +#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) +#define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) + NBL_CONCEPT_BEGIN(3) +#define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 + NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::density_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type)) + ); +#undef v +#undef u +#undef sampler +#include + + // ============================================================================ + // BijectiveSampler + // + // The mapping domain <-> codomain is bijective (1:1), so it can be + // inverted. Extends BackwardDensitySampler with invertGenerate. + // + // Because the mapping is bijective, the Jacobian of the inverse is + // the reciprocal of the Jacobian of the forward mapping: + // backwardPdf(v) == 1.0 / forwardPdf(invertGenerate(v).value) + // + // Required types (in addition to BackwardDensitySampler): + // inverse_sample_type - bundled return of invertGenerate, should be + // one of: + // domain_and_rcpPdf (preferred) + // domain_and_pdf + // + // Required methods (in addition to BackwardDensitySampler): + // inverse_sample_type invertGenerate(codomain_type v) + // ============================================================================ + +#define NBL_CONCEPT_NAME BijectiveSampler +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (sampler, T) +#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) +#define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) + NBL_CONCEPT_BEGIN(3) +#define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 + NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::density_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::inverse_sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.invertGenerate(v)), ::nbl::hlsl::is_same_v, typename T::inverse_sample_type)) + ); +#undef v +#undef u +#undef sampler +#include + + } // namespace concepts + } // namespace sampling + } // namespace hlsl +} // namespace nbl + +#endif // _NBL_BUILTIN_HLSL_SAMPLING_CONCEPTS_INCLUDED_ diff --git a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl index ddbb961300..3885651740 100644 --- a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl @@ -11,80 +11,94 @@ namespace nbl { -namespace hlsl -{ -namespace sampling -{ - -template) -struct ProjectedHemisphere -{ - using vector_t2 = vector; - using vector_t3 = vector; - - static vector_t3 generate(const vector_t2 _sample) + namespace hlsl { - vector_t2 p = concentricMapping(_sample * T(0.99999) + T(0.000005)); - T z = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - p.x * p.x - p.y * p.y)); - return vector_t3(p.x, p.y, z); - } + namespace sampling + { - static T pdf(const T L_z) - { - return L_z * numbers::inv_pi; - } + template ) struct ProjectedHemisphere + { + using vector_t2 = vector; + using vector_t3 = vector; - template > - static sampling::quotient_and_pdf quotient_and_pdf(const T L) - { - return sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); - } + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t2; + using codomain_type = vector_t3; + using density_type = T; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; - template > - static sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) - { - return sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); - } -}; + static vector_t3 generate(const vector_t2 _sample) + { + vector_t2 p = concentricMapping(_sample * T(0.99999) + T(0.000005)); + T z = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - p.x * p.x - p.y * p.y)); + return vector_t3(p.x, p.y, z); + } -template) -struct ProjectedSphere -{ - using vector_t2 = vector; - using vector_t3 = vector; - using hemisphere_t = ProjectedHemisphere; + static T pdf(const T L_z) + { + return L_z * numbers::inv_pi; + } - static vector_t3 generate(NBL_REF_ARG(vector_t3) _sample) - { - vector_t3 retval = hemisphere_t::generate(_sample.xy); - const bool chooseLower = _sample.z > T(0.5); - retval.z = chooseLower ? (-retval.z) : retval.z; - if (chooseLower) - _sample.z -= T(0.5); - _sample.z *= T(2.0); - return retval; - } + template > + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const T L) + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); + } - static T pdf(T L_z) - { - return T(0.5) * hemisphere_t::pdf(L_z); - } + template > + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); + } + }; - template > - static sampling::quotient_and_pdf quotient_and_pdf(T L) - { - return sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); - } + template ) struct ProjectedSphere + { + using vector_t2 = vector; + using vector_t3 = vector; + using hemisphere_t = ProjectedHemisphere; - template > - static sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) - { - return sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); - } -}; + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t3; + using codomain_type = vector_t3; + using density_type = T; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; -} -} + static vector_t3 generate(NBL_REF_ARG(vector_t3) _sample) + { + vector_t3 retval = hemisphere_t::generate(_sample.xy); + const bool chooseLower = _sample.z > T(0.5); + retval.z = chooseLower ? (-retval.z) : retval.z; + if (chooseLower) + _sample.z -= T(0.5); + _sample.z *= T(2.0); + return retval; + } + + static T pdf(T L_z) + { + return T(0.5) * hemisphere_t::pdf(L_z); + } + + template > + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(T L) + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); + } + + template > + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); + } + }; + + } + } } #endif diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index 6c3cf1fad9..f8ebea7e2a 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -7,44 +7,52 @@ #include #include +#include namespace nbl { -namespace hlsl -{ -namespace sampling -{ - -template -struct Linear -{ - using scalar_type = T; - using vector2_type = vector; - - static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end) - { - Linear retval; - retval.linearCoeffStart = linearCoeffs[0]; - retval.rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); - vector2_type squaredCoeffs = linearCoeffs * linearCoeffs; - retval.squaredCoeffStart = squaredCoeffs[0]; - retval.squaredCoeffDiff = squaredCoeffs[1] - squaredCoeffs[0]; - return retval; - } - - scalar_type generate(const scalar_type u) + namespace hlsl { - return hlsl::mix(u, (linearCoeffStart - hlsl::sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, hlsl::abs(rcpDiff) < numeric_limits::max); + namespace sampling + { + + template + struct Linear + { + using scalar_type = T; + using vector2_type = vector; + + // BijectiveSampler concept types + using domain_type = scalar_type; + using codomain_type = scalar_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; + + static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end) + { + Linear retval; + retval.linearCoeffStart = linearCoeffs[0]; + retval.rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); + vector2_type squaredCoeffs = linearCoeffs * linearCoeffs; + retval.squaredCoeffStart = squaredCoeffs[0]; + retval.squaredCoeffDiff = squaredCoeffs[1] - squaredCoeffs[0]; + return retval; + } + + scalar_type generate(const scalar_type u) + { + return hlsl::mix(u, (linearCoeffStart - hlsl::sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, hlsl::abs(rcpDiff) < numeric_limits::max); + } + + scalar_type linearCoeffStart; + scalar_type rcpDiff; + scalar_type squaredCoeffStart; + scalar_type squaredCoeffDiff; + }; + + } } - - scalar_type linearCoeffStart; - scalar_type rcpDiff; - scalar_type squaredCoeffStart; - scalar_type squaredCoeffDiff; -}; - -} -} } #endif diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index e60fe28423..aa0e57e277 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -10,88 +10,95 @@ #include #include #include +#include namespace nbl { -namespace hlsl -{ -namespace sampling -{ - -template -struct ProjectedSphericalTriangle -{ - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - using vector4_type = vector; - - static ProjectedSphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) - { - ProjectedSphericalTriangle retval; - retval.tri = tri; - return retval; - } - - vector4_type computeBilinearPatch(const vector3_type receiverNormal, bool isBSDF) - { - const scalar_type minimumProjSolidAngle = 0.0; - - matrix m = matrix(tri.vertex0, tri.vertex1, tri.vertex2); - const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(isBSDF, nbl::hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); - - return bxdfPdfAtVertex.yyxz; - } - - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool isBSDF, const vector2_type _u) - { - vector2_type u; - // pre-warp according to proj solid angle approximation - vector4_type patch = computeBilinearPatch(receiverNormal, isBSDF); - Bilinear bilinear = Bilinear::create(patch); - u = bilinear.generate(rcpPdf, _u); - - // now warp the points onto a spherical triangle - const vector3_type L = sphtri.generate(solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); - rcpPdf *= solidAngle; - - return L; - } - - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector3_type receiverNormal, bool isBSDF, const vector2_type u) - { - scalar_type cos_a, cos_c, csc_b, csc_c; - vector3_type cos_vertices, sin_vertices; - const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); - return generate(rcpPdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, receiverNormal, isBSDF, u); - } - - scalar_type pdf(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) + namespace hlsl { - scalar_type pdf; - const vector2_type u = sphtri.generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); - - vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); - Bilinear bilinear = Bilinear::create(patch); - return pdf * bilinear.pdf(u); + namespace sampling + { + + template + struct ProjectedSphericalTriangle + { + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; + + // BackwardDensitySampler concept types + using domain_type = vector2_type; + using codomain_type = vector3_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + + static ProjectedSphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) + { + ProjectedSphericalTriangle retval; + retval.tri = tri; + return retval; + } + + vector4_type computeBilinearPatch(const vector3_type receiverNormal, bool isBSDF) + { + const scalar_type minimumProjSolidAngle = 0.0; + + matrix m = matrix(tri.vertex0, tri.vertex1, tri.vertex2); + const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(isBSDF, nbl::hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); + + return bxdfPdfAtVertex.yyxz; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool isBSDF, const vector2_type _u) + { + vector2_type u; + // pre-warp according to proj solid angle approximation + vector4_type patch = computeBilinearPatch(receiverNormal, isBSDF); + Bilinear bilinear = Bilinear::create(patch); + u = bilinear.generate(rcpPdf, _u); + + // now warp the points onto a spherical triangle + const vector3_type L = sphtri.generate(solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); + rcpPdf *= solidAngle; + + return L; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector3_type receiverNormal, bool isBSDF, const vector2_type u) + { + scalar_type cos_a, cos_c, csc_b, csc_c; + vector3_type cos_vertices, sin_vertices; + const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + return generate(rcpPdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, receiverNormal, isBSDF, u); + } + + scalar_type pdf(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) + { + scalar_type pdf; + const vector2_type u = sphtri.generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); + + vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); + Bilinear bilinear = Bilinear::create(patch); + return pdf * bilinear.pdf(u); + } + + scalar_type pdf(const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) + { + scalar_type pdf; + const vector2_type u = sphtri.generateInverse(pdf, L); + + vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); + Bilinear bilinear = Bilinear::create(patch); + return pdf * bilinear.pdf(u); + } + + shapes::SphericalTriangle tri; + sampling::SphericalTriangle sphtri; + }; + + } } - - scalar_type pdf(const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) - { - scalar_type pdf; - const vector2_type u = sphtri.generateInverse(pdf, L); - - vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); - Bilinear bilinear = Bilinear::create(patch); - return pdf * bilinear.pdf(u); - } - - shapes::SphericalTriangle tri; - sampling::SphericalTriangle sphtri; -}; - -} -} } #endif diff --git a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl index 26a62ea617..91e4c128b3 100644 --- a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl +++ b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl @@ -10,45 +10,115 @@ namespace nbl { -namespace hlsl -{ -namespace sampling -{ + namespace hlsl + { + namespace sampling + { + // Returned by TractableSampler::generate — codomain sample bundled with its rcpPdf + template + struct codomain_and_rcpPdf + { + using this_t = codomain_and_rcpPdf; -// finally fixed the semantic F-up, value/pdf = quotient not remainder -template && concepts::FloatingPointLikeScalar

) -struct quotient_and_pdf -{ - using this_t = quotient_and_pdf; - using scalar_q = typename vector_traits::scalar_type; + static this_t create(const V _value, const P _rcpPdf) + { + this_t retval; + retval.value = _value; + retval.rcpPdf = _rcpPdf; + return retval; + } - static this_t create(const Q _quotient, const P _pdf) - { - this_t retval; - retval.quotient = _quotient; - retval.pdf = _pdf; - return retval; - } + V value; + P rcpPdf; + }; - static this_t create(const scalar_q _quotient, const P _pdf) - { - this_t retval; - retval.quotient = hlsl::promote(_quotient); - retval.pdf = _pdf; - return retval; - } + // Returned by TractableSampler::generate — codomain sample bundled with its pdf + template + struct codomain_and_pdf + { + using this_t = codomain_and_pdf; - Q value() - { - return quotient*pdf; - } + static this_t create(const V _value, const P _pdf) + { + this_t retval; + retval.value = _value; + retval.pdf = _pdf; + return retval; + } - Q quotient; - P pdf; -}; + V value; + P pdf; + }; -} -} + // Returned by BijectiveSampler::invertGenerate — domain value bundled with its rcpPdf + template + struct domain_and_rcpPdf + { + using this_t = domain_and_rcpPdf; + + static this_t create(const V _value, const P _rcpPdf) + { + this_t retval; + retval.value = _value; + retval.rcpPdf = _rcpPdf; + return retval; + } + + V value; + P rcpPdf; + }; + + // Returned by BijectiveSampler::invertGenerate — domain value bundled with its pdf + template + struct domain_and_pdf + { + using this_t = domain_and_pdf; + + static this_t create(const V _value, const P _pdf) + { + this_t retval; + retval.value = _value; + retval.pdf = _pdf; + return retval; + } + + V value; + P pdf; + }; + + // finally fixed the semantic F-up, value/pdf = quotient not remainder + template &&concepts::FloatingPointLikeScalar

) struct quotient_and_pdf + { + using this_t = quotient_and_pdf; + using scalar_q = typename vector_traits::scalar_type; + + static this_t create(const Q _quotient, const P _pdf) + { + this_t retval; + retval.quotient = _quotient; + retval.pdf = _pdf; + return retval; + } + + static this_t create(const scalar_q _quotient, const P _pdf) + { + this_t retval; + retval.quotient = hlsl::promote(_quotient); + retval.pdf = _pdf; + return retval; + } + + Q value() + { + return quotient * pdf; + } + + Q quotient; + P pdf; + }; + + } + } } #endif diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index f9e3d2f7ae..845e243cbe 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -8,83 +8,89 @@ #include #include #include -#include +#include +#include namespace nbl { -namespace hlsl -{ -namespace sampling -{ - -template -struct SphericalRectangle -{ - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - using vector4_type = vector; - - static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect) - { - SphericalRectangle retval; - retval.rect = rect; - return retval; - } - - vector2_type generate(const vector2_type rectangleExtents, const vector2_type uv, NBL_REF_ARG(scalar_type) S) + namespace hlsl { - const vector4_type denorm_n_z = vector4_type(-rect.r0.y, rect.r0.x + rectangleExtents.x, rect.r0.y + rectangleExtents.y, -rect.r0.x); - const vector4_type n_z = denorm_n_z / hlsl::sqrt(hlsl::promote(rect.r0.z * rect.r0.z) + denorm_n_z * denorm_n_z); - const vector4_type cosGamma = vector4_type( - -n_z[0] * n_z[1], - -n_z[1] * n_z[2], - -n_z[2] * n_z[3], - -n_z[3] * n_z[0] - ); - - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[0]); - angle_adder.addCosine(cosGamma[1]); - scalar_type p = angle_adder.getSumofArccos(); - angle_adder = math::sincos_accumulator::create(cosGamma[2]); - angle_adder.addCosine(cosGamma[3]); - scalar_type q = angle_adder.getSumofArccos(); - - const scalar_type k = scalar_type(2.0) * numbers::pi - q; - const scalar_type b0 = n_z[0]; - const scalar_type b1 = n_z[2]; - S = p + q - scalar_type(2.0) * numbers::pi; - - const scalar_type CLAMP_EPS = 1e-5; - - // flip z axis if rect.r0.z > 0 - rect.r0.z = ieee754::flipSignIfRHSNegative(rect.r0.z, -rect.r0.z); - vector3_type r1 = rect.r0 + vector3_type(rectangleExtents.x, rectangleExtents.y, 0); - - const scalar_type au = uv.x * S + k; - const scalar_type fu = (hlsl::cos(au) * b0 - b1) / hlsl::sin(au); - const scalar_type cu_2 = hlsl::max(fu * fu + b0 * b0, 1.f); // forces `cu` to be in [-1,1] - const scalar_type cu = ieee754::flipSignIfRHSNegative(scalar_type(1.0) / hlsl::sqrt(cu_2), fu); - - scalar_type xu = -(cu * rect.r0.z) / hlsl::sqrt(scalar_type(1.0) - cu * cu); - xu = hlsl::clamp(xu, rect.r0.x, r1.x); // avoid Infs - const scalar_type d_2 = xu * xu + rect.r0.z * rect.r0.z; - const scalar_type d = hlsl::sqrt(d_2); - - const scalar_type h0 = rect.r0.y / hlsl::sqrt(d_2 + rect.r0.y * rect.r0.y); - const scalar_type h1 = r1.y / hlsl::sqrt(d_2 + r1.y * r1.y); - const scalar_type hv = h0 + uv.y * (h1 - h0); - const scalar_type hv2 = hv * hv; - const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - CLAMP_EPS); - - return vector2_type((xu - rect.r0.x) / rectangleExtents.x, (yv - rect.r0.y) / rectangleExtents.y); + namespace sampling + { + + template + struct SphericalRectangle + { + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; + + // BackwardDensitySampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + + static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect) + { + SphericalRectangle retval; + retval.rect = rect; + return retval; + } + + vector2_type generate(const vector2_type rectangleExtents, const vector2_type uv, NBL_REF_ARG(scalar_type) S) + { + const vector4_type denorm_n_z = vector4_type(-rect.r0.y, rect.r0.x + rectangleExtents.x, rect.r0.y + rectangleExtents.y, -rect.r0.x); + const vector4_type n_z = denorm_n_z / hlsl::sqrt(hlsl::promote(rect.r0.z * rect.r0.z) + denorm_n_z * denorm_n_z); + const vector4_type cosGamma = vector4_type( + -n_z[0] * n_z[1], + -n_z[1] * n_z[2], + -n_z[2] * n_z[3], + -n_z[3] * n_z[0]); + + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[0]); + angle_adder.addCosine(cosGamma[1]); + scalar_type p = angle_adder.getSumofArccos(); + angle_adder = math::sincos_accumulator::create(cosGamma[2]); + angle_adder.addCosine(cosGamma[3]); + scalar_type q = angle_adder.getSumofArccos(); + + const scalar_type k = scalar_type(2.0) * numbers::pi - q; + const scalar_type b0 = n_z[0]; + const scalar_type b1 = n_z[2]; + S = p + q - scalar_type(2.0) * numbers::pi; + + const scalar_type CLAMP_EPS = 1e-5; + + // flip z axis if rect.r0.z > 0 + rect.r0.z = ieee754::flipSignIfRHSNegative(rect.r0.z, -rect.r0.z); + vector3_type r1 = rect.r0 + vector3_type(rectangleExtents.x, rectangleExtents.y, 0); + + const scalar_type au = uv.x * S + k; + const scalar_type fu = (hlsl::cos(au) * b0 - b1) / hlsl::sin(au); + const scalar_type cu_2 = hlsl::max(fu * fu + b0 * b0, 1.f); // forces `cu` to be in [-1,1] + const scalar_type cu = ieee754::flipSignIfRHSNegative(scalar_type(1.0) / hlsl::sqrt(cu_2), fu); + + scalar_type xu = -(cu * rect.r0.z) / hlsl::sqrt(scalar_type(1.0) - cu * cu); + xu = hlsl::clamp(xu, rect.r0.x, r1.x); // avoid Infs + const scalar_type d_2 = xu * xu + rect.r0.z * rect.r0.z; + const scalar_type d = hlsl::sqrt(d_2); + + const scalar_type h0 = rect.r0.y / hlsl::sqrt(d_2 + rect.r0.y * rect.r0.y); + const scalar_type h1 = r1.y / hlsl::sqrt(d_2 + r1.y * r1.y); + const scalar_type hv = h0 + uv.y * (h1 - h0); + const scalar_type hv2 = hv * hv; + const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - CLAMP_EPS); + + return vector2_type((xu - rect.r0.x) / rectangleExtents.x, (yv - rect.r0.y) / rectangleExtents.y); + } + + shapes::SphericalRectangle rect; + }; + + } } - - shapes::SphericalRectangle rect; -}; - -} -} } #endif diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 5770403cd2..f7a5dcae50 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -10,113 +10,121 @@ #include #include #include +#include namespace nbl { -namespace hlsl -{ -namespace sampling -{ - -template -struct SphericalTriangle -{ - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - - static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) + namespace hlsl { - SphericalTriangle retval; - retval.tri = tri; - return retval; - } - - // WARNING: can and will return NAN if one or three of the triangle edges are near zero length - vector3_type generate(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector2_type u) - { - scalar_type negSinSubSolidAngle,negCosSubSolidAngle; - math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); - - const scalar_type p = negCosSubSolidAngle * sin_vertices[0] - negSinSubSolidAngle * cos_vertices[0]; - const scalar_type q = -negSinSubSolidAngle * sin_vertices[0] - negCosSubSolidAngle * cos_vertices[0]; - - // TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful - scalar_type u_ = q - cos_vertices[0]; - scalar_type v_ = p + sin_vertices[0] * cos_c; - - // the slerps could probably be optimized by sidestepping `normalize` calls and accumulating scaling factors - vector3_type C_s = tri.vertex0; - if (csc_b < numeric_limits::max) + namespace sampling { - const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cos_vertices[0] - v_) / ((v_ * p + u_ * q) * sin_vertices[0]); - if (nbl::hlsl::abs(cosAngleAlongAC) < 1.f) - C_s += math::quaternion::slerp_delta(tri.vertex0, tri.vertex2 * csc_b, cosAngleAlongAC); - } - - vector3_type retval = tri.vertex1; - const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri.vertex1); - const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); - if (csc_b_s < numeric_limits::max) - { - const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); - if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) - retval += math::quaternion::slerp_delta(tri.vertex1, C_s * csc_b_s, cosAngleAlongBC_s); - } - return retval; - } - - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type u) - { - scalar_type cos_a, cos_c, csc_b, csc_c; - vector3_type cos_vertices, sin_vertices; - - rcpPdf = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); - - return generate(rcpPdf, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); - } - - vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type L) - { - pdf = 1.0 / solidAngle; - - const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertex1); - const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); - const scalar_type cos_b_ = nbl::hlsl::dot(L, tri.vertex0); - - const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; - const scalar_type sinB_ = nbl::hlsl::sqrt(1.0 - cosB_ * cosB_); - const scalar_type cosC_ = sin_vertices[0] * sinB_* cos_c - cos_vertices[0] * cosB_; - const scalar_type sinC_ = nbl::hlsl::sqrt(1.0 - cosC_ * cosC_); - - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cos_vertices[0], sin_vertices[0]); - angle_adder.addAngle(cosB_, sinB_); - angle_adder.addAngle(cosC_, sinC_); - const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi) * pdf; - const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; - - const scalar_type cosBC_s = (cos_vertices[0] + cosB_ * cosC_) / (sinB_ * sinC_); - const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); - - return vector2_type(u,v); - } + template + struct SphericalTriangle + { + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector3_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; + + static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) + { + SphericalTriangle retval; + retval.tri = tri; + return retval; + } + + // WARNING: can and will return NAN if one or three of the triangle edges are near zero length + vector3_type generate(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector2_type u) + { + scalar_type negSinSubSolidAngle, negCosSubSolidAngle; + math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); + + const scalar_type p = negCosSubSolidAngle * sin_vertices[0] - negSinSubSolidAngle * cos_vertices[0]; + const scalar_type q = -negSinSubSolidAngle * sin_vertices[0] - negCosSubSolidAngle * cos_vertices[0]; + + // TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful + scalar_type u_ = q - cos_vertices[0]; + scalar_type v_ = p + sin_vertices[0] * cos_c; + + // the slerps could probably be optimized by sidestepping `normalize` calls and accumulating scaling factors + vector3_type C_s = tri.vertex0; + if (csc_b < numeric_limits::max) + { + const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cos_vertices[0] - v_) / ((v_ * p + u_ * q) * sin_vertices[0]); + if (nbl::hlsl::abs(cosAngleAlongAC) < 1.f) + C_s += math::quaternion::slerp_delta(tri.vertex0, tri.vertex2 * csc_b, cosAngleAlongAC); + } + + vector3_type retval = tri.vertex1; + const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri.vertex1); + const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); + if (csc_b_s < numeric_limits::max) + { + const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); + if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) + retval += math::quaternion::slerp_delta(tri.vertex1, C_s * csc_b_s, cosAngleAlongBC_s); + } + return retval; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type u) + { + scalar_type cos_a, cos_c, csc_b, csc_c; + vector3_type cos_vertices, sin_vertices; + + rcpPdf = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + + return generate(rcpPdf, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); + } + + vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type L) + { + pdf = 1.0 / solidAngle; + + const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertex1); + const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); + const scalar_type cos_b_ = nbl::hlsl::dot(L, tri.vertex0); + + const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; + const scalar_type sinB_ = nbl::hlsl::sqrt(1.0 - cosB_ * cosB_); + + const scalar_type cosC_ = sin_vertices[0] * sinB_ * cos_c - cos_vertices[0] * cosB_; + const scalar_type sinC_ = nbl::hlsl::sqrt(1.0 - cosC_ * cosC_); + + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cos_vertices[0], sin_vertices[0]); + angle_adder.addAngle(cosB_, sinB_); + angle_adder.addAngle(cosC_, sinC_); + const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi)*pdf; + const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; + + const scalar_type cosBC_s = (cos_vertices[0] + cosB_ * cosC_) / (sinB_ * sinC_); + const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); + + return vector2_type(u, v); + } + + vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, const vector3_type L) + { + scalar_type cos_a, cos_c, csc_b, csc_c; + vector3_type cos_vertices, sin_vertices; + + const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + + return generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); + } + + shapes::SphericalTriangle tri; + }; - vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, const vector3_type L) - { - scalar_type cos_a, cos_c, csc_b, csc_c; - vector3_type cos_vertices, sin_vertices; - - const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); - - return generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); + } } - - shapes::SphericalTriangle tri; -}; - -} -} } #endif diff --git a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl index 5fc3bc7a0b..785cc04c93 100644 --- a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl @@ -12,65 +12,79 @@ namespace nbl { -namespace hlsl -{ -namespace sampling -{ + namespace hlsl + { + namespace sampling + { -template) -struct UniformHemisphere -{ - using vector_t2 = vector; - using vector_t3 = vector; + template ) struct UniformHemisphere + { + using vector_t2 = vector; + using vector_t3 = vector; - static vector_t3 generate(const vector_t2 _sample) - { - T z = _sample.x; - T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); - T phi = T(2.0) * numbers::pi * _sample.y; - return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); - } + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t2; + using codomain_type = vector_t3; + using density_type = T; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; - static T pdf() - { - return T(1.0) / (T(2.0) * numbers::pi); - } + static vector_t3 generate(const vector_t2 _sample) + { + T z = _sample.x; + T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); + T phi = T(2.0) * numbers::pi * _sample.y; + return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); + } - template > - static quotient_and_pdf quotient_and_pdf() - { - return quotient_and_pdf::create(hlsl::promote(1.0), pdf()); - } -}; + static T pdf() + { + return T(1.0) / (T(2.0) * numbers::pi); + } -template) -struct UniformSphere -{ - using vector_t2 = vector; - using vector_t3 = vector; + template > + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf()); + } + }; - static vector_t3 generate(const vector_t2 _sample) - { - T z = T(1.0) - T(2.0) * _sample.x; - T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); - T phi = T(2.0) * numbers::pi * _sample.y; - return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); - } + template ) struct UniformSphere + { + using vector_t2 = vector; + using vector_t3 = vector; - static T pdf() - { - return T(1.0) / (T(4.0) * numbers::pi); - } + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t2; + using codomain_type = vector_t3; + using density_type = T; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; - template > - static quotient_and_pdf quotient_and_pdf() - { - return quotient_and_pdf::create(hlsl::promote(1.0), pdf()); - } -}; -} + static vector_t3 generate(const vector_t2 _sample) + { + T z = T(1.0) - T(2.0) * _sample.x; + T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); + T phi = T(2.0) * numbers::pi * _sample.y; + return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); + } -} + static T pdf() + { + return T(1.0) / (T(4.0) * numbers::pi); + } + + template > + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf()); + } + }; + } + + } } #endif From 0e0b0ad70fb403169309cb43510ae49eeaa49b3c Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Wed, 25 Feb 2026 07:38:14 +0300 Subject: [PATCH 02/33] revert bad formatting for hlsl sampling headers --- include/nbl/builtin/hlsl/sampling/basic.hlsl | 32 +-- .../nbl/builtin/hlsl/sampling/bilinear.hlsl | 78 +++--- .../hlsl/sampling/box_muller_transform.hlsl | 59 ++--- .../hlsl/sampling/concentric_mapping.hlsl | 55 +++-- .../nbl/builtin/hlsl/sampling/concepts.hlsl | 185 +++++++-------- .../hlsl/sampling/cos_weighted_spheres.hlsl | 180 +++++++------- include/nbl/builtin/hlsl/sampling/linear.hlsl | 86 +++---- .../projected_spherical_triangle.hlsl | 172 +++++++------- .../hlsl/sampling/quotient_and_pdf.hlsl | 221 ++++++++--------- .../hlsl/sampling/spherical_rectangle.hlsl | 158 ++++++------ .../hlsl/sampling/spherical_triangle.hlsl | 224 +++++++++--------- .../hlsl/sampling/uniform_spheres.hlsl | 128 +++++----- 12 files changed, 781 insertions(+), 797 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/basic.hlsl b/include/nbl/builtin/hlsl/sampling/basic.hlsl index 9c575a22ce..c405275e55 100644 --- a/include/nbl/builtin/hlsl/sampling/basic.hlsl +++ b/include/nbl/builtin/hlsl/sampling/basic.hlsl @@ -18,29 +18,29 @@ namespace sampling template) struct PartitionRandVariable { - using floating_point_type = T; - using uint_type = unsigned_integer_of_size_t; + using floating_point_type = T; + using uint_type = unsigned_integer_of_size_t; - bool operator()(NBL_REF_ARG(floating_point_type) xi, NBL_REF_ARG(floating_point_type) rcpChoiceProb) - { - const floating_point_type NextULPAfterUnity = bit_cast(bit_cast(floating_point_type(1.0)) + uint_type(1u)); - const bool pickRight = xi >= leftProb * NextULPAfterUnity; + bool operator()(NBL_REF_ARG(floating_point_type) xi, NBL_REF_ARG(floating_point_type) rcpChoiceProb) + { + const floating_point_type NextULPAfterUnity = bit_cast(bit_cast(floating_point_type(1.0)) + uint_type(1u)); + const bool pickRight = xi >= leftProb * NextULPAfterUnity; - // This is all 100% correct taking into account the above NextULPAfterUnity - xi -= pickRight ? leftProb : floating_point_type(0.0); + // This is all 100% correct taking into account the above NextULPAfterUnity + xi -= pickRight ? leftProb : floating_point_type(0.0); - rcpChoiceProb = floating_point_type(1.0) / (pickRight ? (floating_point_type(1.0) - leftProb) : leftProb); - xi *= rcpChoiceProb; + rcpChoiceProb = floating_point_type(1.0) / (pickRight ? (floating_point_type(1.0) - leftProb) : leftProb); + xi *= rcpChoiceProb; - return pickRight; - } + return pickRight; + } - floating_point_type leftProb; + floating_point_type leftProb; }; -} -} -} +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index 65f3b33f10..2b6282eb8d 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -19,54 +19,54 @@ namespace sampling template struct Bilinear { - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - using vector4_type = vector; + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; - // BijectiveSampler concept types - using domain_type = vector2_type; - using codomain_type = vector2_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; - static Bilinear create(const vector4_type bilinearCoeffs) - { - Bilinear retval; - retval.bilinearCoeffs = bilinearCoeffs; - retval.twiceAreasUnderXCurve = vector2_type(bilinearCoeffs[0] + bilinearCoeffs[1], bilinearCoeffs[2] + bilinearCoeffs[3]); - return retval; - } + static Bilinear create(const vector4_type bilinearCoeffs) + { + Bilinear retval; + retval.bilinearCoeffs = bilinearCoeffs; + retval.twiceAreasUnderXCurve = vector2_type(bilinearCoeffs[0] + bilinearCoeffs[1], bilinearCoeffs[2] + bilinearCoeffs[3]); + return retval; + } - vector2_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type _u) - { - vector2_type u; - Linear lineary = Linear::create(twiceAreasUnderXCurve); - u.y = lineary.generate(_u.y); + vector2_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type _u) + { + vector2_type u; + Linear lineary = Linear::create(twiceAreasUnderXCurve); + u.y = lineary.generate(_u.y); - const vector2_type ySliceEndPoints = vector2_type(nbl::hlsl::mix(bilinearCoeffs[0], bilinearCoeffs[2], u.y), nbl::hlsl::mix(bilinearCoeffs[1], bilinearCoeffs[3], u.y)); - Linear linearx = Linear::create(ySliceEndPoints); - u.x = linearx.generate(_u.x); + const vector2_type ySliceEndPoints = vector2_type(nbl::hlsl::mix(bilinearCoeffs[0], bilinearCoeffs[2], u.y), nbl::hlsl::mix(bilinearCoeffs[1], bilinearCoeffs[3], u.y)); + Linear linearx = Linear::create(ySliceEndPoints); + u.x = linearx.generate(_u.x); - rcpPdf = (twiceAreasUnderXCurve[0] + twiceAreasUnderXCurve[1]) / (4.0 * nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], u.x)); + rcpPdf = (twiceAreasUnderXCurve[0] + twiceAreasUnderXCurve[1]) / (4.0 * nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], u.x)); - return u; - } + return u; + } - scalar_type pdf(const vector2_type u) - { - return 4.0 * nbl::hlsl::mix(nbl::hlsl::mix(bilinearCoeffs[0], bilinearCoeffs[1], u.x), nbl::hlsl::mix(bilinearCoeffs[2], bilinearCoeffs[3], u.x), u.y) / (bilinearCoeffs[0] + bilinearCoeffs[1] + bilinearCoeffs[2] + bilinearCoeffs[3]); - } + scalar_type pdf(const vector2_type u) + { + return 4.0 * nbl::hlsl::mix(nbl::hlsl::mix(bilinearCoeffs[0], bilinearCoeffs[1], u.x), nbl::hlsl::mix(bilinearCoeffs[2], bilinearCoeffs[3], u.x), u.y) / (bilinearCoeffs[0] + bilinearCoeffs[1] + bilinearCoeffs[2] + bilinearCoeffs[3]); + } - // unit square: x0y0 x1y0 - // x0y1 x1y1 - vector4_type bilinearCoeffs; // (x0y0, x0y1, x1y0, x1y1) - vector2_type twiceAreasUnderXCurve; + // unit square: x0y0 x1y0 + // x0y1 x1y1 + vector4_type bilinearCoeffs; // (x0y0, x0y1, x1y0, x1y1) + vector2_type twiceAreasUnderXCurve; }; -} -} -} +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index 9385233a11..2b74381c66 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -11,34 +11,35 @@ namespace nbl { - namespace hlsl - { - namespace sampling - { - - template ) struct BoxMullerTransform - { - using scalar_type = T; - using vector2_type = vector; - - // BackwardDensitySampler concept types - using domain_type = vector2_type; - using codomain_type = vector2_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - - vector2_type operator()(const vector2_type xi) - { - scalar_type sinPhi, cosPhi; - math::sincos(2.0 * numbers::pi * xi.y - numbers::pi, sinPhi, cosPhi); - return vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(-2.0 * nbl::hlsl::log(xi.x)) * stddev; - } - - T stddev; - }; - - } - } -} +namespace hlsl +{ +namespace sampling +{ + +template) +struct BoxMullerTransform +{ + using scalar_type = T; + using vector2_type = vector; + + // BackwardDensitySampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + + vector2_type operator()(const vector2_type xi) + { + scalar_type sinPhi, cosPhi; + math::sincos(2.0 * numbers::pi * xi.y - numbers::pi, sinPhi, cosPhi); + return vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(-2.0 * nbl::hlsl::log(xi.x)) * stddev; + } + + T stddev; +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl index 841fc9ff2d..7b6e215515 100644 --- a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl @@ -17,34 +17,37 @@ namespace sampling { template -vector concentricMapping(const vector _u) +vector concentricMapping(const vector _u) { - //map [0;1]^2 to [-1;1]^2 - vector u = 2.0f * _u - hlsl::promote >(1.0); - - vector p; - if (hlsl::all >(glsl::equal(u, hlsl::promote >(0.0)))) - p = hlsl::promote >(0.0); - else - { - T r; - T theta; - if (abs(u.x) > abs(u.y)) { - r = u.x; - theta = 0.25 * numbers::pi * (u.y / u.x); - } else { - r = u.y; - theta = 0.5 * numbers::pi - 0.25 * numbers::pi * (u.x / u.y); - } - - p = r * vector(cos(theta), sin(theta)); - } - - return p; + //map [0;1]^2 to [-1;1]^2 + vector u = 2.0f * _u - hlsl::promote>(1.0); + + vector p; + if (hlsl::all>(glsl::equal(u, hlsl::promote>(0.0)))) + p = hlsl::promote>(0.0); + else + { + T r; + T theta; + if (abs(u.x) > abs(u.y)) + { + r = u.x; + theta = 0.25 * numbers::pi * (u.y / u.x); + } + else + { + r = u.y; + theta = 0.5 * numbers::pi - 0.25 * numbers::pi * (u.x / u.y); + } + + p = r * vector(cos(theta), sin(theta)); + } + + return p; } -} -} -} +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/concepts.hlsl b/include/nbl/builtin/hlsl/sampling/concepts.hlsl index 9a56173c72..ba9454ae05 100644 --- a/include/nbl/builtin/hlsl/sampling/concepts.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concepts.hlsl @@ -5,92 +5,83 @@ namespace nbl { - namespace hlsl - { - namespace sampling - { - namespace concepts - { +namespace hlsl +{ +namespace sampling +{ +namespace concepts +{ - // ============================================================================ - // BasicSampler - // - // The simplest sampler: maps domain -> codomain. - // - // Required types: - // domain_type - the input space (e.g. float for 1D, float2 for 2D) - // codomain_type - the output space (e.g. float3 for directions) - // - // Required methods: - // codomain_type generate(domain_type u) - // ============================================================================ +// ============================================================================ +// BasicSampler +// +// The simplest sampler: maps domain -> codomain. +// +// Required types: +// domain_type - the input space (e.g. float for 1D, float2 for 2D) +// codomain_type - the output space (e.g. float3 for directions) +// +// Required methods: +// codomain_type generate(domain_type u) +// ============================================================================ #define NBL_CONCEPT_NAME BasicSampler #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (sampler, T) #define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) - NBL_CONCEPT_BEGIN(2) +NBL_CONCEPT_BEGIN(2) #define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 - NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::codomain_type)) - ); +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type))((NBL_CONCEPT_REQ_TYPE)(T::codomain_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::codomain_type))); #undef u #undef sampler #include - // ============================================================================ - // TractableSampler - // - // A sampler whose density can be computed analytically in the forward - // (sampling) direction. The generate method returns the sample bundled - // with its density to avoid redundant computation. - // - // Required types: - // domain_type - the input space - // codomain_type - the output space - // density_type - the density type (typically scalar) - // sample_type - bundled return of generate, should be one of: - // codomain_and_rcpPdf (preferred) - // codomain_and_pdf - // - // Required methods: - // sample_type generate(domain_type u) - sample + density - // density_type forwardPdf(domain_type u) - density only - // ============================================================================ +// ============================================================================ +// TractableSampler +// +// A sampler whose density can be computed analytically in the forward +// (sampling) direction. The generate method returns the sample bundled +// with its density to avoid redundant computation. +// +// Required types: +// domain_type - the input space +// codomain_type - the output space +// density_type - the density type (typically scalar) +// sample_type - bundled return of generate, should be one of: +// codomain_and_rcpPdf (preferred) +// codomain_and_pdf +// +// Required methods: +// sample_type generate(domain_type u) - sample + density +// density_type forwardPdf(domain_type u) - density only +// ============================================================================ #define NBL_CONCEPT_NAME TractableSampler #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (sampler, T) #define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) - NBL_CONCEPT_BEGIN(2) +NBL_CONCEPT_BEGIN(2) #define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 - NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::density_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type)) - ); +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type))((NBL_CONCEPT_REQ_TYPE)(T::codomain_type))((NBL_CONCEPT_REQ_TYPE)(T::density_type))((NBL_CONCEPT_REQ_TYPE)(T::sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type))); #undef u #undef sampler #include - // ============================================================================ - // BackwardDensitySampler - // - // Extends TractableSampler with the ability to evaluate the PDF given - // a codomain value (i.e. without knowing the original domain input). - // - // Required methods (in addition to TractableSampler): - // density_type backwardPdf(codomain_type v) - // ============================================================================ +// ============================================================================ +// BackwardDensitySampler +// +// Extends TractableSampler with the ability to evaluate the PDF given +// a codomain value (i.e. without knowing the original domain input). +// +// Required methods (in addition to TractableSampler): +// density_type backwardPdf(codomain_type v) +// ============================================================================ #define NBL_CONCEPT_NAME BackwardDensitySampler #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) @@ -98,43 +89,36 @@ namespace nbl #define NBL_CONCEPT_PARAM_0 (sampler, T) #define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) #define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) - NBL_CONCEPT_BEGIN(3) +NBL_CONCEPT_BEGIN(3) #define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 - NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::density_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type)) - ); +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type))((NBL_CONCEPT_REQ_TYPE)(T::codomain_type))((NBL_CONCEPT_REQ_TYPE)(T::density_type))((NBL_CONCEPT_REQ_TYPE)(T::sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type))); #undef v #undef u #undef sampler #include - // ============================================================================ - // BijectiveSampler - // - // The mapping domain <-> codomain is bijective (1:1), so it can be - // inverted. Extends BackwardDensitySampler with invertGenerate. - // - // Because the mapping is bijective, the Jacobian of the inverse is - // the reciprocal of the Jacobian of the forward mapping: - // backwardPdf(v) == 1.0 / forwardPdf(invertGenerate(v).value) - // - // Required types (in addition to BackwardDensitySampler): - // inverse_sample_type - bundled return of invertGenerate, should be - // one of: - // domain_and_rcpPdf (preferred) - // domain_and_pdf - // - // Required methods (in addition to BackwardDensitySampler): - // inverse_sample_type invertGenerate(codomain_type v) - // ============================================================================ +// ============================================================================ +// BijectiveSampler +// +// The mapping domain <-> codomain is bijective (1:1), so it can be +// inverted. Extends BackwardDensitySampler with invertGenerate. +// +// Because the mapping is bijective, the Jacobian of the inverse is +// the reciprocal of the Jacobian of the forward mapping: +// backwardPdf(v) == 1.0 / forwardPdf(invertGenerate(v).value) +// +// Required types (in addition to BackwardDensitySampler): +// inverse_sample_type - bundled return of invertGenerate, should be +// one of: +// domain_and_rcpPdf (preferred) +// domain_and_pdf +// +// Required methods (in addition to BackwardDensitySampler): +// inverse_sample_type invertGenerate(codomain_type v) +// ============================================================================ #define NBL_CONCEPT_NAME BijectiveSampler #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) @@ -142,29 +126,20 @@ namespace nbl #define NBL_CONCEPT_PARAM_0 (sampler, T) #define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) #define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) - NBL_CONCEPT_BEGIN(3) +NBL_CONCEPT_BEGIN(3) #define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 - NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::density_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::inverse_sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.invertGenerate(v)), ::nbl::hlsl::is_same_v, typename T::inverse_sample_type)) - ); +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type))((NBL_CONCEPT_REQ_TYPE)(T::codomain_type))((NBL_CONCEPT_REQ_TYPE)(T::density_type))((NBL_CONCEPT_REQ_TYPE)(T::sample_type))((NBL_CONCEPT_REQ_TYPE)(T::inverse_sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.invertGenerate(v)), ::nbl::hlsl::is_same_v, typename T::inverse_sample_type))); #undef v #undef u #undef sampler #include - } // namespace concepts - } // namespace sampling - } // namespace hlsl +} // namespace concepts +} // namespace sampling +} // namespace hlsl } // namespace nbl #endif // _NBL_BUILTIN_HLSL_SAMPLING_CONCEPTS_INCLUDED_ diff --git a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl index 3885651740..618abf3981 100644 --- a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl @@ -11,94 +11,96 @@ namespace nbl { - namespace hlsl - { - namespace sampling - { - - template ) struct ProjectedHemisphere - { - using vector_t2 = vector; - using vector_t3 = vector; - - // BijectiveSampler concept types - using scalar_type = T; - using domain_type = vector_t2; - using codomain_type = vector_t3; - using density_type = T; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; - - static vector_t3 generate(const vector_t2 _sample) - { - vector_t2 p = concentricMapping(_sample * T(0.99999) + T(0.000005)); - T z = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - p.x * p.x - p.y * p.y)); - return vector_t3(p.x, p.y, z); - } - - static T pdf(const T L_z) - { - return L_z * numbers::inv_pi; - } - - template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const T L) - { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); - } - - template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) - { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); - } - }; - - template ) struct ProjectedSphere - { - using vector_t2 = vector; - using vector_t3 = vector; - using hemisphere_t = ProjectedHemisphere; - - // BijectiveSampler concept types - using scalar_type = T; - using domain_type = vector_t3; - using codomain_type = vector_t3; - using density_type = T; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; - - static vector_t3 generate(NBL_REF_ARG(vector_t3) _sample) - { - vector_t3 retval = hemisphere_t::generate(_sample.xy); - const bool chooseLower = _sample.z > T(0.5); - retval.z = chooseLower ? (-retval.z) : retval.z; - if (chooseLower) - _sample.z -= T(0.5); - _sample.z *= T(2.0); - return retval; - } - - static T pdf(T L_z) - { - return T(0.5) * hemisphere_t::pdf(L_z); - } - - template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(T L) - { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); - } - - template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) - { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); - } - }; - - } - } -} +namespace hlsl +{ +namespace sampling +{ + +template) +struct ProjectedHemisphere +{ + using vector_t2 = vector; + using vector_t3 = vector; + + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t2; + using codomain_type = vector_t3; + using density_type = T; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; + + static vector_t3 generate(const vector_t2 _sample) + { + vector_t2 p = concentricMapping(_sample * T(0.99999) + T(0.000005)); + T z = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - p.x * p.x - p.y * p.y)); + return vector_t3(p.x, p.y, z); + } + + static T pdf(const T L_z) + { + return L_z * numbers::inv_pi; + } + + template> + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const T L) + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); + } + + template> + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); + } +}; + +template) +struct ProjectedSphere +{ + using vector_t2 = vector; + using vector_t3 = vector; + using hemisphere_t = ProjectedHemisphere; + + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t3; + using codomain_type = vector_t3; + using density_type = T; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; + + static vector_t3 generate(NBL_REF_ARG(vector_t3) _sample) + { + vector_t3 retval = hemisphere_t::generate(_sample.xy); + const bool chooseLower = _sample.z > T(0.5); + retval.z = chooseLower ? (-retval.z) : retval.z; + if (chooseLower) + _sample.z -= T(0.5); + _sample.z *= T(2.0); + return retval; + } + + static T pdf(T L_z) + { + return T(0.5) * hemisphere_t::pdf(L_z); + } + + template> + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(T L) + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); + } + + template> + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); + } +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index f8ebea7e2a..dc59e6902f 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -11,48 +11,48 @@ namespace nbl { - namespace hlsl - { - namespace sampling - { - - template - struct Linear - { - using scalar_type = T; - using vector2_type = vector; - - // BijectiveSampler concept types - using domain_type = scalar_type; - using codomain_type = scalar_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; - - static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end) - { - Linear retval; - retval.linearCoeffStart = linearCoeffs[0]; - retval.rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); - vector2_type squaredCoeffs = linearCoeffs * linearCoeffs; - retval.squaredCoeffStart = squaredCoeffs[0]; - retval.squaredCoeffDiff = squaredCoeffs[1] - squaredCoeffs[0]; - return retval; - } - - scalar_type generate(const scalar_type u) - { - return hlsl::mix(u, (linearCoeffStart - hlsl::sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, hlsl::abs(rcpDiff) < numeric_limits::max); - } - - scalar_type linearCoeffStart; - scalar_type rcpDiff; - scalar_type squaredCoeffStart; - scalar_type squaredCoeffDiff; - }; - - } - } -} +namespace hlsl +{ +namespace sampling +{ + +template +struct Linear +{ + using scalar_type = T; + using vector2_type = vector; + + // BijectiveSampler concept types + using domain_type = scalar_type; + using codomain_type = scalar_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; + + static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end) + { + Linear retval; + retval.linearCoeffStart = linearCoeffs[0]; + retval.rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); + vector2_type squaredCoeffs = linearCoeffs * linearCoeffs; + retval.squaredCoeffStart = squaredCoeffs[0]; + retval.squaredCoeffDiff = squaredCoeffs[1] - squaredCoeffs[0]; + return retval; + } + + scalar_type generate(const scalar_type u) + { + return hlsl::mix(u, (linearCoeffStart - hlsl::sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, hlsl::abs(rcpDiff) < numeric_limits::max); + } + + scalar_type linearCoeffStart; + scalar_type rcpDiff; + scalar_type squaredCoeffStart; + scalar_type squaredCoeffDiff; +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index aa0e57e277..c8744931d1 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -14,91 +14,91 @@ namespace nbl { - namespace hlsl - { - namespace sampling - { - - template - struct ProjectedSphericalTriangle - { - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - using vector4_type = vector; - - // BackwardDensitySampler concept types - using domain_type = vector2_type; - using codomain_type = vector3_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - - static ProjectedSphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) - { - ProjectedSphericalTriangle retval; - retval.tri = tri; - return retval; - } - - vector4_type computeBilinearPatch(const vector3_type receiverNormal, bool isBSDF) - { - const scalar_type minimumProjSolidAngle = 0.0; - - matrix m = matrix(tri.vertex0, tri.vertex1, tri.vertex2); - const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(isBSDF, nbl::hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); - - return bxdfPdfAtVertex.yyxz; - } - - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool isBSDF, const vector2_type _u) - { - vector2_type u; - // pre-warp according to proj solid angle approximation - vector4_type patch = computeBilinearPatch(receiverNormal, isBSDF); - Bilinear bilinear = Bilinear::create(patch); - u = bilinear.generate(rcpPdf, _u); - - // now warp the points onto a spherical triangle - const vector3_type L = sphtri.generate(solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); - rcpPdf *= solidAngle; - - return L; - } - - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector3_type receiverNormal, bool isBSDF, const vector2_type u) - { - scalar_type cos_a, cos_c, csc_b, csc_c; - vector3_type cos_vertices, sin_vertices; - const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); - return generate(rcpPdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, receiverNormal, isBSDF, u); - } - - scalar_type pdf(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) - { - scalar_type pdf; - const vector2_type u = sphtri.generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); - - vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); - Bilinear bilinear = Bilinear::create(patch); - return pdf * bilinear.pdf(u); - } - - scalar_type pdf(const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) - { - scalar_type pdf; - const vector2_type u = sphtri.generateInverse(pdf, L); - - vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); - Bilinear bilinear = Bilinear::create(patch); - return pdf * bilinear.pdf(u); - } - - shapes::SphericalTriangle tri; - sampling::SphericalTriangle sphtri; - }; - - } - } -} +namespace hlsl +{ +namespace sampling +{ + +template +struct ProjectedSphericalTriangle +{ + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; + + // BackwardDensitySampler concept types + using domain_type = vector2_type; + using codomain_type = vector3_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + + static ProjectedSphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) + { + ProjectedSphericalTriangle retval; + retval.tri = tri; + return retval; + } + + vector4_type computeBilinearPatch(const vector3_type receiverNormal, bool isBSDF) + { + const scalar_type minimumProjSolidAngle = 0.0; + + matrix m = matrix(tri.vertex0, tri.vertex1, tri.vertex2); + const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(isBSDF, nbl::hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); + + return bxdfPdfAtVertex.yyxz; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool isBSDF, const vector2_type _u) + { + vector2_type u; + // pre-warp according to proj solid angle approximation + vector4_type patch = computeBilinearPatch(receiverNormal, isBSDF); + Bilinear bilinear = Bilinear::create(patch); + u = bilinear.generate(rcpPdf, _u); + + // now warp the points onto a spherical triangle + const vector3_type L = sphtri.generate(solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); + rcpPdf *= solidAngle; + + return L; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector3_type receiverNormal, bool isBSDF, const vector2_type u) + { + scalar_type cos_a, cos_c, csc_b, csc_c; + vector3_type cos_vertices, sin_vertices; + const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + return generate(rcpPdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, receiverNormal, isBSDF, u); + } + + scalar_type pdf(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) + { + scalar_type pdf; + const vector2_type u = sphtri.generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); + + vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); + Bilinear bilinear = Bilinear::create(patch); + return pdf * bilinear.pdf(u); + } + + scalar_type pdf(const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) + { + scalar_type pdf; + const vector2_type u = sphtri.generateInverse(pdf, L); + + vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); + Bilinear bilinear = Bilinear::create(patch); + return pdf * bilinear.pdf(u); + } + + shapes::SphericalTriangle tri; + sampling::SphericalTriangle sphtri; +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl index 91e4c128b3..c94aa7e805 100644 --- a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl +++ b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl @@ -10,115 +10,116 @@ namespace nbl { - namespace hlsl - { - namespace sampling - { - // Returned by TractableSampler::generate — codomain sample bundled with its rcpPdf - template - struct codomain_and_rcpPdf - { - using this_t = codomain_and_rcpPdf; - - static this_t create(const V _value, const P _rcpPdf) - { - this_t retval; - retval.value = _value; - retval.rcpPdf = _rcpPdf; - return retval; - } - - V value; - P rcpPdf; - }; - - // Returned by TractableSampler::generate — codomain sample bundled with its pdf - template - struct codomain_and_pdf - { - using this_t = codomain_and_pdf; - - static this_t create(const V _value, const P _pdf) - { - this_t retval; - retval.value = _value; - retval.pdf = _pdf; - return retval; - } - - V value; - P pdf; - }; - - // Returned by BijectiveSampler::invertGenerate — domain value bundled with its rcpPdf - template - struct domain_and_rcpPdf - { - using this_t = domain_and_rcpPdf; - - static this_t create(const V _value, const P _rcpPdf) - { - this_t retval; - retval.value = _value; - retval.rcpPdf = _rcpPdf; - return retval; - } - - V value; - P rcpPdf; - }; - - // Returned by BijectiveSampler::invertGenerate — domain value bundled with its pdf - template - struct domain_and_pdf - { - using this_t = domain_and_pdf; - - static this_t create(const V _value, const P _pdf) - { - this_t retval; - retval.value = _value; - retval.pdf = _pdf; - return retval; - } - - V value; - P pdf; - }; - - // finally fixed the semantic F-up, value/pdf = quotient not remainder - template &&concepts::FloatingPointLikeScalar

) struct quotient_and_pdf - { - using this_t = quotient_and_pdf; - using scalar_q = typename vector_traits::scalar_type; - - static this_t create(const Q _quotient, const P _pdf) - { - this_t retval; - retval.quotient = _quotient; - retval.pdf = _pdf; - return retval; - } - - static this_t create(const scalar_q _quotient, const P _pdf) - { - this_t retval; - retval.quotient = hlsl::promote(_quotient); - retval.pdf = _pdf; - return retval; - } - - Q value() - { - return quotient * pdf; - } - - Q quotient; - P pdf; - }; - - } - } -} +namespace hlsl +{ +namespace sampling +{ +// Returned by TractableSampler::generate — codomain sample bundled with its rcpPdf +template +struct codomain_and_rcpPdf +{ + using this_t = codomain_and_rcpPdf; + + static this_t create(const V _value, const P _rcpPdf) + { + this_t retval; + retval.value = _value; + retval.rcpPdf = _rcpPdf; + return retval; + } + + V value; + P rcpPdf; +}; + +// Returned by TractableSampler::generate — codomain sample bundled with its pdf +template +struct codomain_and_pdf +{ + using this_t = codomain_and_pdf; + + static this_t create(const V _value, const P _pdf) + { + this_t retval; + retval.value = _value; + retval.pdf = _pdf; + return retval; + } + + V value; + P pdf; +}; + +// Returned by BijectiveSampler::invertGenerate — domain value bundled with its rcpPdf +template +struct domain_and_rcpPdf +{ + using this_t = domain_and_rcpPdf; + + static this_t create(const V _value, const P _rcpPdf) + { + this_t retval; + retval.value = _value; + retval.rcpPdf = _rcpPdf; + return retval; + } + + V value; + P rcpPdf; +}; + +// Returned by BijectiveSampler::invertGenerate — domain value bundled with its pdf +template +struct domain_and_pdf +{ + using this_t = domain_and_pdf; + + static this_t create(const V _value, const P _pdf) + { + this_t retval; + retval.value = _value; + retval.pdf = _pdf; + return retval; + } + + V value; + P pdf; +}; + +// finally fixed the semantic F-up, value/pdf = quotient not remainder +template&& concepts::FloatingPointLikeScalar

) +struct quotient_and_pdf +{ + using this_t = quotient_and_pdf; + using scalar_q = typename vector_traits::scalar_type; + + static this_t create(const Q _quotient, const P _pdf) + { + this_t retval; + retval.quotient = _quotient; + retval.pdf = _pdf; + return retval; + } + + static this_t create(const scalar_q _quotient, const P _pdf) + { + this_t retval; + retval.quotient = hlsl::promote(_quotient); + retval.pdf = _pdf; + return retval; + } + + Q value() + { + return quotient * pdf; + } + + Q quotient; + P pdf; +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index 845e243cbe..272740a0f7 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -13,84 +13,84 @@ namespace nbl { - namespace hlsl - { - namespace sampling - { - - template - struct SphericalRectangle - { - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - using vector4_type = vector; - - // BackwardDensitySampler concept types - using domain_type = vector2_type; - using codomain_type = vector2_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - - static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect) - { - SphericalRectangle retval; - retval.rect = rect; - return retval; - } - - vector2_type generate(const vector2_type rectangleExtents, const vector2_type uv, NBL_REF_ARG(scalar_type) S) - { - const vector4_type denorm_n_z = vector4_type(-rect.r0.y, rect.r0.x + rectangleExtents.x, rect.r0.y + rectangleExtents.y, -rect.r0.x); - const vector4_type n_z = denorm_n_z / hlsl::sqrt(hlsl::promote(rect.r0.z * rect.r0.z) + denorm_n_z * denorm_n_z); - const vector4_type cosGamma = vector4_type( - -n_z[0] * n_z[1], - -n_z[1] * n_z[2], - -n_z[2] * n_z[3], - -n_z[3] * n_z[0]); - - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[0]); - angle_adder.addCosine(cosGamma[1]); - scalar_type p = angle_adder.getSumofArccos(); - angle_adder = math::sincos_accumulator::create(cosGamma[2]); - angle_adder.addCosine(cosGamma[3]); - scalar_type q = angle_adder.getSumofArccos(); - - const scalar_type k = scalar_type(2.0) * numbers::pi - q; - const scalar_type b0 = n_z[0]; - const scalar_type b1 = n_z[2]; - S = p + q - scalar_type(2.0) * numbers::pi; - - const scalar_type CLAMP_EPS = 1e-5; - - // flip z axis if rect.r0.z > 0 - rect.r0.z = ieee754::flipSignIfRHSNegative(rect.r0.z, -rect.r0.z); - vector3_type r1 = rect.r0 + vector3_type(rectangleExtents.x, rectangleExtents.y, 0); - - const scalar_type au = uv.x * S + k; - const scalar_type fu = (hlsl::cos(au) * b0 - b1) / hlsl::sin(au); - const scalar_type cu_2 = hlsl::max(fu * fu + b0 * b0, 1.f); // forces `cu` to be in [-1,1] - const scalar_type cu = ieee754::flipSignIfRHSNegative(scalar_type(1.0) / hlsl::sqrt(cu_2), fu); - - scalar_type xu = -(cu * rect.r0.z) / hlsl::sqrt(scalar_type(1.0) - cu * cu); - xu = hlsl::clamp(xu, rect.r0.x, r1.x); // avoid Infs - const scalar_type d_2 = xu * xu + rect.r0.z * rect.r0.z; - const scalar_type d = hlsl::sqrt(d_2); - - const scalar_type h0 = rect.r0.y / hlsl::sqrt(d_2 + rect.r0.y * rect.r0.y); - const scalar_type h1 = r1.y / hlsl::sqrt(d_2 + r1.y * r1.y); - const scalar_type hv = h0 + uv.y * (h1 - h0); - const scalar_type hv2 = hv * hv; - const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - CLAMP_EPS); - - return vector2_type((xu - rect.r0.x) / rectangleExtents.x, (yv - rect.r0.y) / rectangleExtents.y); - } - - shapes::SphericalRectangle rect; - }; - - } - } -} +namespace hlsl +{ +namespace sampling +{ + +template +struct SphericalRectangle +{ + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; + + // BackwardDensitySampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + + static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect) + { + SphericalRectangle retval; + retval.rect = rect; + return retval; + } + + vector2_type generate(const vector2_type rectangleExtents, const vector2_type uv, NBL_REF_ARG(scalar_type) S) + { + const vector4_type denorm_n_z = vector4_type(-rect.r0.y, rect.r0.x + rectangleExtents.x, rect.r0.y + rectangleExtents.y, -rect.r0.x); + const vector4_type n_z = denorm_n_z / hlsl::sqrt(hlsl::promote(rect.r0.z * rect.r0.z) + denorm_n_z * denorm_n_z); + const vector4_type cosGamma = vector4_type( + -n_z[0] * n_z[1], + -n_z[1] * n_z[2], + -n_z[2] * n_z[3], + -n_z[3] * n_z[0]); + + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[0]); + angle_adder.addCosine(cosGamma[1]); + scalar_type p = angle_adder.getSumofArccos(); + angle_adder = math::sincos_accumulator::create(cosGamma[2]); + angle_adder.addCosine(cosGamma[3]); + scalar_type q = angle_adder.getSumofArccos(); + + const scalar_type k = scalar_type(2.0) * numbers::pi - q; + const scalar_type b0 = n_z[0]; + const scalar_type b1 = n_z[2]; + S = p + q - scalar_type(2.0) * numbers::pi; + + const scalar_type CLAMP_EPS = 1e-5; + + // flip z axis if rect.r0.z > 0 + rect.r0.z = ieee754::flipSignIfRHSNegative(rect.r0.z, -rect.r0.z); + vector3_type r1 = rect.r0 + vector3_type(rectangleExtents.x, rectangleExtents.y, 0); + + const scalar_type au = uv.x * S + k; + const scalar_type fu = (hlsl::cos(au) * b0 - b1) / hlsl::sin(au); + const scalar_type cu_2 = hlsl::max(fu * fu + b0 * b0, 1.f); // forces `cu` to be in [-1,1] + const scalar_type cu = ieee754::flipSignIfRHSNegative(scalar_type(1.0) / hlsl::sqrt(cu_2), fu); + + scalar_type xu = -(cu * rect.r0.z) / hlsl::sqrt(scalar_type(1.0) - cu * cu); + xu = hlsl::clamp(xu, rect.r0.x, r1.x); // avoid Infs + const scalar_type d_2 = xu * xu + rect.r0.z * rect.r0.z; + const scalar_type d = hlsl::sqrt(d_2); + + const scalar_type h0 = rect.r0.y / hlsl::sqrt(d_2 + rect.r0.y * rect.r0.y); + const scalar_type h1 = r1.y / hlsl::sqrt(d_2 + r1.y * r1.y); + const scalar_type hv = h0 + uv.y * (h1 - h0); + const scalar_type hv2 = hv * hv; + const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - CLAMP_EPS); + + return vector2_type((xu - rect.r0.x) / rectangleExtents.x, (yv - rect.r0.y) / rectangleExtents.y); + } + + shapes::SphericalRectangle rect; +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index f7a5dcae50..0a6e85ea71 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -14,117 +14,117 @@ namespace nbl { - namespace hlsl - { - namespace sampling - { - - template - struct SphericalTriangle - { - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - - // BijectiveSampler concept types - using domain_type = vector2_type; - using codomain_type = vector3_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; - - static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) - { - SphericalTriangle retval; - retval.tri = tri; - return retval; - } - - // WARNING: can and will return NAN if one or three of the triangle edges are near zero length - vector3_type generate(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector2_type u) - { - scalar_type negSinSubSolidAngle, negCosSubSolidAngle; - math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); - - const scalar_type p = negCosSubSolidAngle * sin_vertices[0] - negSinSubSolidAngle * cos_vertices[0]; - const scalar_type q = -negSinSubSolidAngle * sin_vertices[0] - negCosSubSolidAngle * cos_vertices[0]; - - // TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful - scalar_type u_ = q - cos_vertices[0]; - scalar_type v_ = p + sin_vertices[0] * cos_c; - - // the slerps could probably be optimized by sidestepping `normalize` calls and accumulating scaling factors - vector3_type C_s = tri.vertex0; - if (csc_b < numeric_limits::max) - { - const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cos_vertices[0] - v_) / ((v_ * p + u_ * q) * sin_vertices[0]); - if (nbl::hlsl::abs(cosAngleAlongAC) < 1.f) - C_s += math::quaternion::slerp_delta(tri.vertex0, tri.vertex2 * csc_b, cosAngleAlongAC); - } - - vector3_type retval = tri.vertex1; - const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri.vertex1); - const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); - if (csc_b_s < numeric_limits::max) - { - const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); - if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) - retval += math::quaternion::slerp_delta(tri.vertex1, C_s * csc_b_s, cosAngleAlongBC_s); - } - return retval; - } - - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type u) - { - scalar_type cos_a, cos_c, csc_b, csc_c; - vector3_type cos_vertices, sin_vertices; - - rcpPdf = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); - - return generate(rcpPdf, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); - } - - vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type L) - { - pdf = 1.0 / solidAngle; - - const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertex1); - const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); - const scalar_type cos_b_ = nbl::hlsl::dot(L, tri.vertex0); - - const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; - const scalar_type sinB_ = nbl::hlsl::sqrt(1.0 - cosB_ * cosB_); - - const scalar_type cosC_ = sin_vertices[0] * sinB_ * cos_c - cos_vertices[0] * cosB_; - const scalar_type sinC_ = nbl::hlsl::sqrt(1.0 - cosC_ * cosC_); - - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cos_vertices[0], sin_vertices[0]); - angle_adder.addAngle(cosB_, sinB_); - angle_adder.addAngle(cosC_, sinC_); - const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi)*pdf; - const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; - - const scalar_type cosBC_s = (cos_vertices[0] + cosB_ * cosC_) / (sinB_ * sinC_); - const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); - - return vector2_type(u, v); - } - - vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, const vector3_type L) - { - scalar_type cos_a, cos_c, csc_b, csc_c; - vector3_type cos_vertices, sin_vertices; - - const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); - - return generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); - } - - shapes::SphericalTriangle tri; - }; - - } - } -} +namespace hlsl +{ +namespace sampling +{ + +template +struct SphericalTriangle +{ + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector3_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; + + static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) + { + SphericalTriangle retval; + retval.tri = tri; + return retval; + } + + // WARNING: can and will return NAN if one or three of the triangle edges are near zero length + vector3_type generate(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector2_type u) + { + scalar_type negSinSubSolidAngle, negCosSubSolidAngle; + math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); + + const scalar_type p = negCosSubSolidAngle * sin_vertices[0] - negSinSubSolidAngle * cos_vertices[0]; + const scalar_type q = -negSinSubSolidAngle * sin_vertices[0] - negCosSubSolidAngle * cos_vertices[0]; + + // TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful + scalar_type u_ = q - cos_vertices[0]; + scalar_type v_ = p + sin_vertices[0] * cos_c; + + // the slerps could probably be optimized by sidestepping `normalize` calls and accumulating scaling factors + vector3_type C_s = tri.vertex0; + if (csc_b < numeric_limits::max) + { + const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cos_vertices[0] - v_) / ((v_ * p + u_ * q) * sin_vertices[0]); + if (nbl::hlsl::abs(cosAngleAlongAC) < 1.f) + C_s += math::quaternion::slerp_delta(tri.vertex0, tri.vertex2 * csc_b, cosAngleAlongAC); + } + + vector3_type retval = tri.vertex1; + const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri.vertex1); + const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); + if (csc_b_s < numeric_limits::max) + { + const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); + if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) + retval += math::quaternion::slerp_delta(tri.vertex1, C_s * csc_b_s, cosAngleAlongBC_s); + } + return retval; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type u) + { + scalar_type cos_a, cos_c, csc_b, csc_c; + vector3_type cos_vertices, sin_vertices; + + rcpPdf = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + + return generate(rcpPdf, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); + } + + vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type L) + { + pdf = 1.0 / solidAngle; + + const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertex1); + const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); + const scalar_type cos_b_ = nbl::hlsl::dot(L, tri.vertex0); + + const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; + const scalar_type sinB_ = nbl::hlsl::sqrt(1.0 - cosB_ * cosB_); + + const scalar_type cosC_ = sin_vertices[0] * sinB_ * cos_c - cos_vertices[0] * cosB_; + const scalar_type sinC_ = nbl::hlsl::sqrt(1.0 - cosC_ * cosC_); + + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cos_vertices[0], sin_vertices[0]); + angle_adder.addAngle(cosB_, sinB_); + angle_adder.addAngle(cosC_, sinC_); + const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi)*pdf; + const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; + + const scalar_type cosBC_s = (cos_vertices[0] + cosB_ * cosC_) / (sinB_ * sinC_); + const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); + + return vector2_type(u, v); + } + + vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, const vector3_type L) + { + scalar_type cos_a, cos_c, csc_b, csc_c; + vector3_type cos_vertices, sin_vertices; + + const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + + return generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); + } + + shapes::SphericalTriangle tri; +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl index 785cc04c93..3ad0b4e748 100644 --- a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl @@ -12,79 +12,81 @@ namespace nbl { - namespace hlsl - { - namespace sampling - { +namespace hlsl +{ +namespace sampling +{ - template ) struct UniformHemisphere - { - using vector_t2 = vector; - using vector_t3 = vector; +template) +struct UniformHemisphere +{ + using vector_t2 = vector; + using vector_t3 = vector; - // BijectiveSampler concept types - using scalar_type = T; - using domain_type = vector_t2; - using codomain_type = vector_t3; - using density_type = T; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t2; + using codomain_type = vector_t3; + using density_type = T; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; - static vector_t3 generate(const vector_t2 _sample) - { - T z = _sample.x; - T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); - T phi = T(2.0) * numbers::pi * _sample.y; - return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); - } + static vector_t3 generate(const vector_t2 _sample) + { + T z = _sample.x; + T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); + T phi = T(2.0) * numbers::pi * _sample.y; + return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); + } - static T pdf() - { - return T(1.0) / (T(2.0) * numbers::pi); - } + static T pdf() + { + return T(1.0) / (T(2.0) * numbers::pi); + } - template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() - { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf()); - } - }; + template> + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf()); + } +}; - template ) struct UniformSphere - { - using vector_t2 = vector; - using vector_t3 = vector; +template) +struct UniformSphere +{ + using vector_t2 = vector; + using vector_t3 = vector; - // BijectiveSampler concept types - using scalar_type = T; - using domain_type = vector_t2; - using codomain_type = vector_t3; - using density_type = T; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t2; + using codomain_type = vector_t3; + using density_type = T; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; - static vector_t3 generate(const vector_t2 _sample) - { - T z = T(1.0) - T(2.0) * _sample.x; - T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); - T phi = T(2.0) * numbers::pi * _sample.y; - return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); - } + static vector_t3 generate(const vector_t2 _sample) + { + T z = T(1.0) - T(2.0) * _sample.x; + T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); + T phi = T(2.0) * numbers::pi * _sample.y; + return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); + } - static T pdf() - { - return T(1.0) / (T(4.0) * numbers::pi); - } + static T pdf() + { + return T(1.0) / (T(4.0) * numbers::pi); + } - template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() - { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf()); - } - }; - } + template> + static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() + { + return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf()); + } +}; +} // namespace sampling - } -} +} // namespace hlsl +} // namespace nbl #endif From 3574d837ee19b7cc5d3151fa0f9374c22ee9906b Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Wed, 25 Feb 2026 09:37:52 +0300 Subject: [PATCH 03/33] Separate warp sample types from quotient_and_pdf, add sample density concepts - Move codomain_and_*Pdf and domain_and_*Pdf structs into their own warp_and_pdf.hlsl header - Keeping quotient_and_pdf.hlsl focused on importance sampling quotients for BxDFs - Add SampleWithPDF, SampleWithRcpPDF, and SampleWithDensity concepts to validate sample types - Used concept composition (NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT) to build ResamplableSampler on TractableSampler and BijectiveSampler on ResamplableSampler --- examples_tests | 2 +- .../hlsl/sampling/box_muller_transform.hlsl | 4 +- .../hlsl/sampling/concentric_mapping.hlsl | 6 +- .../nbl/builtin/hlsl/sampling/concepts.hlsl | 150 +++++++++++++----- .../hlsl/sampling/cos_weighted_spheres.hlsl | 9 +- include/nbl/builtin/hlsl/sampling/linear.hlsl | 2 +- .../projected_spherical_triangle.hlsl | 4 +- .../hlsl/sampling/quotient_and_pdf.hlsl | 131 ++++----------- .../hlsl/sampling/spherical_rectangle.hlsl | 4 +- .../hlsl/sampling/spherical_triangle.hlsl | 2 +- .../hlsl/sampling/uniform_spheres.hlsl | 5 +- .../builtin/hlsl/sampling/warp_and_pdf.hlsl | 91 +++++++++++ src/nbl/builtin/CMakeLists.txt | 2 + 13 files changed, 255 insertions(+), 157 deletions(-) create mode 100644 include/nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl diff --git a/examples_tests b/examples_tests index ebf25f4ea0..18fe5eb6a3 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit ebf25f4ea033960b89f9e5192b031cfa7b2a3b52 +Subproject commit 18fe5eb6a39d7a09f8e928ca040d06d430e205bb diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index 2b74381c66..4dd774c8ba 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -7,7 +7,7 @@ #include "nbl/builtin/hlsl/math/functions.hlsl" #include "nbl/builtin/hlsl/numbers.hlsl" -#include "nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl" +#include "nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl" namespace nbl { @@ -22,7 +22,7 @@ struct BoxMullerTransform using scalar_type = T; using vector2_type = vector; - // BackwardDensitySampler concept types + // ResamplableSampler concept types using domain_type = vector2_type; using codomain_type = vector2_type; using density_type = scalar_type; diff --git a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl index 7b6e215515..4d80e14861 100644 --- a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl @@ -20,11 +20,11 @@ template vector concentricMapping(const vector _u) { //map [0;1]^2 to [-1;1]^2 - vector u = 2.0f * _u - hlsl::promote>(1.0); + vector u = 2.0f * _u - hlsl::promote >(1.0); vector p; - if (hlsl::all>(glsl::equal(u, hlsl::promote>(0.0)))) - p = hlsl::promote>(0.0); + if (hlsl::all >(glsl::equal(u, hlsl::promote >(0.0)))) + p = hlsl::promote >(0.0); else { T r; diff --git a/include/nbl/builtin/hlsl/sampling/concepts.hlsl b/include/nbl/builtin/hlsl/sampling/concepts.hlsl index ba9454ae05..7ad680d34b 100644 --- a/include/nbl/builtin/hlsl/sampling/concepts.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concepts.hlsl @@ -12,6 +12,67 @@ namespace sampling namespace concepts { +// ============================================================================ +// SampleWithPDF +// +// Checks that a sample type bundles a value with its PDF. +// +// Required members/methods: +// value - the sampled value (member or method) +// pdf - the probability density +// +// Satisfied by: codomain_and_pdf, domain_and_pdf, quotient_and_pdf +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME SampleWithPDF +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (s, T) +NBL_CONCEPT_BEGIN(1) +#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_EXPR)(s.pdf)) + ((NBL_CONCEPT_REQ_EXPR)(s.value))); +#undef s +#include +// clang-format on + +// ============================================================================ +// SampleWithRcpPDF +// +// Checks that a sample type bundles a value with its reciprocal PDF. +// +// Required members/methods: +// value - the sampled value (member or method) +// rcpPdf - the reciprocal probability density +// +// Satisfied by: codomain_and_rcpPdf, domain_and_rcpPdf +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME SampleWithRcpPDF +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (s, T) +NBL_CONCEPT_BEGIN(1) +#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_EXPR)(s.rcpPdf)) + ((NBL_CONCEPT_REQ_EXPR)(s.value))); +#undef s +#include +// clang-format on + +// ============================================================================ +// SampleWithDensity +// +// A sample type that bundles a value with either its PDF or reciprocal PDF. +// This is the disjunction of SampleWithPDF and SampleWithRcpPDF. +// ============================================================================ +template +NBL_BOOL_CONCEPT SampleWithDensity = SampleWithPDF || SampleWithRcpPDF; + // ============================================================================ // BasicSampler // @@ -25,56 +86,67 @@ namespace concepts // codomain_type generate(domain_type u) // ============================================================================ +// clang-format off #define NBL_CONCEPT_NAME BasicSampler #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) -#define NBL_CONCEPT_PARAM_0 (sampler, T) +#define NBL_CONCEPT_PARAM_0 (_sampler, T) #define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) NBL_CONCEPT_BEGIN(2) -#define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE)(T::domain_type))((NBL_CONCEPT_REQ_TYPE)(T::codomain_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::codomain_type))); + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::codomain_type))); #undef u -#undef sampler +#undef _sampler #include +// clang-format on // ============================================================================ // TractableSampler // -// A sampler whose density can be computed analytically in the forward +// A _sampler whose density can be computed analytically in the forward // (sampling) direction. The generate method returns the sample bundled // with its density to avoid redundant computation. // // Required types: // domain_type - the input space // codomain_type - the output space -// density_type - the density type (typically scalar) -// sample_type - bundled return of generate, should be one of: -// codomain_and_rcpPdf (preferred) -// codomain_and_pdf +// density_type - the density type +// sample_type - bundled return of generate, must satisfy +// SampleWithDensity (i.e. SampleWithPDF or SampleWithRcpPDF) // // Required methods: // sample_type generate(domain_type u) - sample + density // density_type forwardPdf(domain_type u) - density only // ============================================================================ +// clang-format off #define NBL_CONCEPT_NAME TractableSampler #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) -#define NBL_CONCEPT_PARAM_0 (sampler, T) +#define NBL_CONCEPT_PARAM_0 (_sampler, T) #define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) NBL_CONCEPT_BEGIN(2) -#define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE)(T::domain_type))((NBL_CONCEPT_REQ_TYPE)(T::codomain_type))((NBL_CONCEPT_REQ_TYPE)(T::density_type))((NBL_CONCEPT_REQ_TYPE)(T::sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type))); + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::density_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(SampleWithDensity, typename T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type))); #undef u -#undef sampler +#undef _sampler #include +// clang-format on // ============================================================================ -// BackwardDensitySampler +// ResamplableSampler // // Extends TractableSampler with the ability to evaluate the PDF given // a codomain value (i.e. without knowing the original domain input). @@ -83,59 +155,61 @@ NBL_CONCEPT_END( // density_type backwardPdf(codomain_type v) // ============================================================================ -#define NBL_CONCEPT_NAME BackwardDensitySampler +// clang-format off +#define NBL_CONCEPT_NAME ResamplableSampler #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) -#define NBL_CONCEPT_PARAM_0 (sampler, T) -#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) -#define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) -NBL_CONCEPT_BEGIN(3) -#define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 -#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define NBL_CONCEPT_PARAM_0 (_sampler, T) +#define NBL_CONCEPT_PARAM_1 (v, typename T::codomain_type) +NBL_CONCEPT_BEGIN(2) +#define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE)(T::domain_type))((NBL_CONCEPT_REQ_TYPE)(T::codomain_type))((NBL_CONCEPT_REQ_TYPE)(T::density_type))((NBL_CONCEPT_REQ_TYPE)(T::sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type))); + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(TractableSampler, T)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type))); #undef v -#undef u -#undef sampler +#undef _sampler #include +// clang-format on // ============================================================================ // BijectiveSampler // // The mapping domain <-> codomain is bijective (1:1), so it can be -// inverted. Extends BackwardDensitySampler with invertGenerate. +// inverted. Extends ResamplableSampler with invertGenerate. // // Because the mapping is bijective, the Jacobian of the inverse is // the reciprocal of the Jacobian of the forward mapping: // backwardPdf(v) == 1.0 / forwardPdf(invertGenerate(v).value) // -// Required types (in addition to BackwardDensitySampler): +// Required types (in addition to ResamplableSampler): // inverse_sample_type - bundled return of invertGenerate, should be // one of: // domain_and_rcpPdf (preferred) // domain_and_pdf // -// Required methods (in addition to BackwardDensitySampler): +// Required methods (in addition to ResamplableSampler): // inverse_sample_type invertGenerate(codomain_type v) // ============================================================================ +// clang-format off #define NBL_CONCEPT_NAME BijectiveSampler #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) -#define NBL_CONCEPT_PARAM_0 (sampler, T) -#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) -#define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) -NBL_CONCEPT_BEGIN(3) -#define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 -#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define NBL_CONCEPT_PARAM_0 (_sampler, T) +#define NBL_CONCEPT_PARAM_1 (v, typename T::codomain_type) +NBL_CONCEPT_BEGIN(2) +#define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE)(T::domain_type))((NBL_CONCEPT_REQ_TYPE)(T::codomain_type))((NBL_CONCEPT_REQ_TYPE)(T::density_type))((NBL_CONCEPT_REQ_TYPE)(T::sample_type))((NBL_CONCEPT_REQ_TYPE)(T::inverse_sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type))((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.invertGenerate(v)), ::nbl::hlsl::is_same_v, typename T::inverse_sample_type))); + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(ResamplableSampler, T)) + ((NBL_CONCEPT_REQ_TYPE)(T::inverse_sample_type)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(SampleWithDensity, typename T::inverse_sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.invertGenerate(v)), ::nbl::hlsl::is_same_v, typename T::inverse_sample_type))); #undef v -#undef u -#undef sampler +#undef _sampler #include +// clang-format on } // namespace concepts } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl index 618abf3981..c65a688eb3 100644 --- a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl @@ -8,6 +8,7 @@ #include "nbl/builtin/hlsl/concepts.hlsl" #include "nbl/builtin/hlsl/sampling/concentric_mapping.hlsl" #include "nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl" +#include "nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl" namespace nbl { @@ -42,13 +43,13 @@ struct ProjectedHemisphere return L_z * numbers::inv_pi; } - template> + template > static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const T L) { return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); } - template> + template > static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) { return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); @@ -86,13 +87,13 @@ struct ProjectedSphere return T(0.5) * hemisphere_t::pdf(L_z); } - template> + template > static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(T L) { return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); } - template> + template > static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) { return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index dc59e6902f..16f583bbbf 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -7,7 +7,7 @@ #include #include -#include +#include namespace nbl { diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index c8744931d1..eeb48ea388 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -10,7 +10,7 @@ #include #include #include -#include +#include namespace nbl { @@ -27,7 +27,7 @@ struct ProjectedSphericalTriangle using vector3_type = vector; using vector4_type = vector; - // BackwardDensitySampler concept types + // ResamplableSampler concept types using domain_type = vector2_type; using codomain_type = vector3_type; using density_type = scalar_type; diff --git a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl index c94aa7e805..26a62ea617 100644 --- a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl +++ b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl @@ -14,112 +14,41 @@ namespace hlsl { namespace sampling { -// Returned by TractableSampler::generate — codomain sample bundled with its rcpPdf -template -struct codomain_and_rcpPdf -{ - using this_t = codomain_and_rcpPdf; - - static this_t create(const V _value, const P _rcpPdf) - { - this_t retval; - retval.value = _value; - retval.rcpPdf = _rcpPdf; - return retval; - } - - V value; - P rcpPdf; -}; - -// Returned by TractableSampler::generate — codomain sample bundled with its pdf -template -struct codomain_and_pdf -{ - using this_t = codomain_and_pdf; - - static this_t create(const V _value, const P _pdf) - { - this_t retval; - retval.value = _value; - retval.pdf = _pdf; - return retval; - } - - V value; - P pdf; -}; - -// Returned by BijectiveSampler::invertGenerate — domain value bundled with its rcpPdf -template -struct domain_and_rcpPdf -{ - using this_t = domain_and_rcpPdf; - - static this_t create(const V _value, const P _rcpPdf) - { - this_t retval; - retval.value = _value; - retval.rcpPdf = _rcpPdf; - return retval; - } - - V value; - P rcpPdf; -}; - -// Returned by BijectiveSampler::invertGenerate — domain value bundled with its pdf -template -struct domain_and_pdf -{ - using this_t = domain_and_pdf; - - static this_t create(const V _value, const P _pdf) - { - this_t retval; - retval.value = _value; - retval.pdf = _pdf; - return retval; - } - - V value; - P pdf; -}; // finally fixed the semantic F-up, value/pdf = quotient not remainder -template&& concepts::FloatingPointLikeScalar

) +template && concepts::FloatingPointLikeScalar

) struct quotient_and_pdf { - using this_t = quotient_and_pdf; - using scalar_q = typename vector_traits::scalar_type; - - static this_t create(const Q _quotient, const P _pdf) - { - this_t retval; - retval.quotient = _quotient; - retval.pdf = _pdf; - return retval; - } - - static this_t create(const scalar_q _quotient, const P _pdf) - { - this_t retval; - retval.quotient = hlsl::promote(_quotient); - retval.pdf = _pdf; - return retval; - } - - Q value() - { - return quotient * pdf; - } - - Q quotient; - P pdf; + using this_t = quotient_and_pdf; + using scalar_q = typename vector_traits::scalar_type; + + static this_t create(const Q _quotient, const P _pdf) + { + this_t retval; + retval.quotient = _quotient; + retval.pdf = _pdf; + return retval; + } + + static this_t create(const scalar_q _quotient, const P _pdf) + { + this_t retval; + retval.quotient = hlsl::promote(_quotient); + retval.pdf = _pdf; + return retval; + } + + Q value() + { + return quotient*pdf; + } + + Q quotient; + P pdf; }; -} // namespace sampling -} // namespace hlsl -} // namespace nbl +} +} +} #endif diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index 272740a0f7..8f90be6b3a 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -9,7 +9,7 @@ #include #include #include -#include +#include namespace nbl { @@ -26,7 +26,7 @@ struct SphericalRectangle using vector3_type = vector; using vector4_type = vector; - // BackwardDensitySampler concept types + // ResamplableSampler concept types using domain_type = vector2_type; using codomain_type = vector2_type; using density_type = scalar_type; diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 0a6e85ea71..5d9d32ad21 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -10,7 +10,7 @@ #include #include #include -#include +#include namespace nbl { diff --git a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl index 3ad0b4e748..c92d732b43 100644 --- a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl @@ -9,6 +9,7 @@ #include "nbl/builtin/hlsl/numbers.hlsl" #include "nbl/builtin/hlsl/tgmath.hlsl" #include "nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl" +#include "nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl" namespace nbl { @@ -44,7 +45,7 @@ struct UniformHemisphere return T(1.0) / (T(2.0) * numbers::pi); } - template> + template > static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() { return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf()); @@ -78,7 +79,7 @@ struct UniformSphere return T(1.0) / (T(4.0) * numbers::pi); } - template> + template > static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() { return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf()); diff --git a/include/nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl b/include/nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl new file mode 100644 index 0000000000..529cbd5e82 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl @@ -0,0 +1,91 @@ +// 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_SAMPLING_WARP_AND_PDF_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_WARP_AND_PDF_INCLUDED_ + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +// Returned by TractableSampler::generate, codomain sample bundled with its rcpPdf +template +struct codomain_and_rcpPdf +{ + using this_t = codomain_and_rcpPdf; + + static this_t create(const V _value, const P _rcpPdf) + { + this_t retval; + retval.value = _value; + retval.rcpPdf = _rcpPdf; + return retval; + } + + V value; + P rcpPdf; +}; + +// Returned by TractableSampler::generate, codomain sample bundled with its pdf +template +struct codomain_and_pdf +{ + using this_t = codomain_and_pdf; + + static this_t create(const V _value, const P _pdf) + { + this_t retval; + retval.value = _value; + retval.pdf = _pdf; + return retval; + } + + V value; + P pdf; +}; + +// Returned by BijectiveSampler::invertGenerate, domain value bundled with its rcpPdf +template +struct domain_and_rcpPdf +{ + using this_t = domain_and_rcpPdf; + + static this_t create(const V _value, const P _rcpPdf) + { + this_t retval; + retval.value = _value; + retval.rcpPdf = _rcpPdf; + return retval; + } + + V value; + P rcpPdf; +}; + +// Returned by BijectiveSampler::invertGenerate, domain value bundled with its pdf +template +struct domain_and_pdf +{ + using this_t = domain_and_pdf; + + static this_t create(const V _value, const P _pdf) + { + this_t retval; + retval.value = _value; + retval.pdf = _pdf; + return retval; + } + + V value; + P pdf; +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl + +#endif diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index d228be8ea4..258ed858a6 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -280,6 +280,8 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/projected_spherical_ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/spherical_rectangle.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/cos_weighted_spheres.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/quotient_and_pdf.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/warp_and_pdf.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/concepts.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/uniform_spheres.hlsl") # LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/ndarray_addressing.hlsl") From f1493eda9386c57282b5debe22a81a5e2674b013 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 20 Jan 2026 15:56:23 +0700 Subject: [PATCH 04/33] changes to linear, bilinear, box muller for pdf and backward pdf --- .../nbl/builtin/hlsl/sampling/bilinear.hlsl | 77 +++++----- .../hlsl/sampling/box_muller_transform.hlsl | 33 ++-- include/nbl/builtin/hlsl/sampling/linear.hlsl | 62 ++++---- .../projected_spherical_triangle.hlsl | 144 +++++++++--------- 4 files changed, 159 insertions(+), 157 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index 2b6282eb8d..7006e63852 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -19,50 +19,53 @@ namespace sampling template struct Bilinear { - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - using vector4_type = vector; + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; - // BijectiveSampler concept types - using domain_type = vector2_type; - using codomain_type = vector2_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; - static Bilinear create(const vector4_type bilinearCoeffs) - { - Bilinear retval; - retval.bilinearCoeffs = bilinearCoeffs; - retval.twiceAreasUnderXCurve = vector2_type(bilinearCoeffs[0] + bilinearCoeffs[1], bilinearCoeffs[2] + bilinearCoeffs[3]); - return retval; - } + static Bilinear create(const vector4_type bilinearCoeffs) + { + Bilinear retval; + retval.bilinearCoeffs = bilinearCoeffs; + retval.bilinearCoeffDiffs = vector2_type(bilinearCoeffs[2]-bilinearCoeffs[0], bilinearCoeffs[3]-bilinearCoeffs[1]); + vector2_type twiceAreasUnderXCurve = vector2_type(bilinearCoeffs[0] + bilinearCoeffs[1], bilinearCoeffs[2] + bilinearCoeffs[3]); + retval.twiceAreasUnderXCurveSumOverFour = scalar_type(4.0) / (twiceAreasUnderXCurve[0] + twiceAreasUnderXCurve[1]); + retval.lineary = Linear::create(twiceAreasUnderXCurve); + return retval; + } - vector2_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type _u) - { - vector2_type u; - Linear lineary = Linear::create(twiceAreasUnderXCurve); - u.y = lineary.generate(_u.y); + vector2_type generate(const vector2_type _u) + { + vector2_type u; + u.y = lineary.generate(_u.y); - const vector2_type ySliceEndPoints = vector2_type(nbl::hlsl::mix(bilinearCoeffs[0], bilinearCoeffs[2], u.y), nbl::hlsl::mix(bilinearCoeffs[1], bilinearCoeffs[3], u.y)); - Linear linearx = Linear::create(ySliceEndPoints); - u.x = linearx.generate(_u.x); + const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + u.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + u.y * bilinearCoeffDiffs[1]); + Linear linearx = Linear::create(ySliceEndPoints); + u.x = linearx.generate(_u.x); - rcpPdf = (twiceAreasUnderXCurve[0] + twiceAreasUnderXCurve[1]) / (4.0 * nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], u.x)); + return u; + } - return u; - } + scalar_type backwardPdf(const vector2_type u) + { + const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + u.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + u.y * bilinearCoeffDiffs[1]); + return nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], u.x) * fourOverTwiceAreasUnderXCurveSum; + } - scalar_type pdf(const vector2_type u) - { - return 4.0 * nbl::hlsl::mix(nbl::hlsl::mix(bilinearCoeffs[0], bilinearCoeffs[1], u.x), nbl::hlsl::mix(bilinearCoeffs[2], bilinearCoeffs[3], u.x), u.y) / (bilinearCoeffs[0] + bilinearCoeffs[1] + bilinearCoeffs[2] + bilinearCoeffs[3]); - } - - // unit square: x0y0 x1y0 - // x0y1 x1y1 - vector4_type bilinearCoeffs; // (x0y0, x0y1, x1y0, x1y1) - vector2_type twiceAreasUnderXCurve; + // unit square: x0y0 x1y0 + // x0y1 x1y1 + vector4_type bilinearCoeffs; // (x0y0, x0y1, x1y0, x1y1) + vector2_type bilinearCoeffDiffs; + vector2_type fourOverTwiceAreasUnderXCurveSum; + Linear lineary; }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index 4dd774c8ba..9f76f06576 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -19,23 +19,22 @@ namespace sampling template) struct BoxMullerTransform { - using scalar_type = T; - using vector2_type = vector; - - // ResamplableSampler concept types - using domain_type = vector2_type; - using codomain_type = vector2_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - - vector2_type operator()(const vector2_type xi) - { - scalar_type sinPhi, cosPhi; - math::sincos(2.0 * numbers::pi * xi.y - numbers::pi, sinPhi, cosPhi); - return vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(-2.0 * nbl::hlsl::log(xi.x)) * stddev; - } - - T stddev; + using scalar_type = T; + using vector2_type = vector; + + // ResamplableSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + + vector2_type backwardPdf(const vector2_type outPos) + { + const vector2_type outPos2 = outPos * outPos; + return vector2_type(nbl::hlsl::exp(scalar_type(-0.5) * (outPos2.x + outPos2.y)), numbers::pi * scalar_type(0.5) * hlsl::atan2(outPos.y, outPos.x)); + } + + T stddev; }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index 16f583bbbf..1c12aeea29 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -19,36 +19,38 @@ namespace sampling template struct Linear { - using scalar_type = T; - using vector2_type = vector; - - // BijectiveSampler concept types - using domain_type = scalar_type; - using codomain_type = scalar_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; - - static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end) - { - Linear retval; - retval.linearCoeffStart = linearCoeffs[0]; - retval.rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); - vector2_type squaredCoeffs = linearCoeffs * linearCoeffs; - retval.squaredCoeffStart = squaredCoeffs[0]; - retval.squaredCoeffDiff = squaredCoeffs[1] - squaredCoeffs[0]; - return retval; - } - - scalar_type generate(const scalar_type u) - { - return hlsl::mix(u, (linearCoeffStart - hlsl::sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, hlsl::abs(rcpDiff) < numeric_limits::max); - } - - scalar_type linearCoeffStart; - scalar_type rcpDiff; - scalar_type squaredCoeffStart; - scalar_type squaredCoeffDiff; + using scalar_type = T; + using vector2_type = vector; + + // BijectiveSampler concept types + using domain_type = scalar_type; + using codomain_type = scalar_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; + + static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end), assumed to be at x=0 and x=1 + { + Linear retval; + scalar_type rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); + retval.linearCoeffStartOverDiff = linearCoeffs[0] * rcpDiff; + vector2_type squaredCoeffs = linearCoeffs * linearCoeffs; + scalar_type squaredRcpDiff = rcpDiff * rcpDiff; + retval.squaredCoeffStartOverDiff = squaredCoeffs[0] * squaredRcpDiff; + retval.squaredCoeffDiffOverDiff = (squaredCoeffs[1] - squaredCoeffs[0]) * squaredRcpDiff; + return retval; + } + + scalar_type generate(const scalar_type u) + { + return hlsl::mix(u, (linearCoeffStartOverDiff - hlsl::sqrt(squaredCoeffStartOverDiff + u * squaredCoeffDiffOverDiff)), hlsl::abs(linearCoeffStartOverDiff) < numeric_limits::max); + } + + // TODO: add forwardPdf and backwardPdf methods, forward computes from u and backwards from the result of generate + + scalar_type linearCoeffStartOverDiff; + scalar_type squaredCoeffStartOverDiff; + scalar_type squaredCoeffDiffOverDiff; }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index eeb48ea388..63926c9df4 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -22,79 +22,77 @@ namespace sampling template struct ProjectedSphericalTriangle { - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - using vector4_type = vector; - - // ResamplableSampler concept types - using domain_type = vector2_type; - using codomain_type = vector3_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - - static ProjectedSphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) - { - ProjectedSphericalTriangle retval; - retval.tri = tri; - return retval; - } - - vector4_type computeBilinearPatch(const vector3_type receiverNormal, bool isBSDF) - { - const scalar_type minimumProjSolidAngle = 0.0; - - matrix m = matrix(tri.vertex0, tri.vertex1, tri.vertex2); - const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(isBSDF, nbl::hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); - - return bxdfPdfAtVertex.yyxz; - } - - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool isBSDF, const vector2_type _u) - { - vector2_type u; - // pre-warp according to proj solid angle approximation - vector4_type patch = computeBilinearPatch(receiverNormal, isBSDF); - Bilinear bilinear = Bilinear::create(patch); - u = bilinear.generate(rcpPdf, _u); - - // now warp the points onto a spherical triangle - const vector3_type L = sphtri.generate(solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); - rcpPdf *= solidAngle; - - return L; - } - - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector3_type receiverNormal, bool isBSDF, const vector2_type u) - { - scalar_type cos_a, cos_c, csc_b, csc_c; - vector3_type cos_vertices, sin_vertices; - const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); - return generate(rcpPdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, receiverNormal, isBSDF, u); - } - - scalar_type pdf(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) - { - scalar_type pdf; - const vector2_type u = sphtri.generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); - - vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); - Bilinear bilinear = Bilinear::create(patch); - return pdf * bilinear.pdf(u); - } - - scalar_type pdf(const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) - { - scalar_type pdf; - const vector2_type u = sphtri.generateInverse(pdf, L); - - vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); - Bilinear bilinear = Bilinear::create(patch); - return pdf * bilinear.pdf(u); - } - - shapes::SphericalTriangle tri; - sampling::SphericalTriangle sphtri; + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; + + // ResamplableSampler concept types + using domain_type = vector2_type; + using codomain_type = vector3_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + + static ProjectedSphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) + { + ProjectedSphericalTriangle retval; + retval.tri = tri; + return retval; + } + + vector4_type computeBilinearPatch(const vector3_type receiverNormal, bool isBSDF) + { + const scalar_type minimumProjSolidAngle = 0.0; + + matrix m = matrix(tri.vertex0, tri.vertex1, tri.vertex2); + const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(isBSDF, nbl::hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); + + return bxdfPdfAtVertex.yyxz; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool isBSDF, const vector2_type _u) + { + vector2_type u; + // pre-warp according to proj solid angle approximation + vector4_type patch = computeBilinearPatch(receiverNormal, isBSDF); + Bilinear bilinear = Bilinear::create(patch); + u = bilinear.generate(_u); + + // now warp the points onto a spherical triangle + const vector3_type L = sphtri.generate(solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); + rcpPdf = solidAngle / bilinear.backwardPdf(u); + + return L; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector3_type receiverNormal, bool isBSDF, const vector2_type u) + { + scalar_type cos_a, cos_c, csc_b, csc_c; + vector3_type cos_vertices, sin_vertices; + const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + return generate(rcpPdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, receiverNormal, isBSDF, u); + } + + scalar_type pdf(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) + { + scalar_type pdf; + const vector2_type u = sphtri.generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); + vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); + Bilinear bilinear = Bilinear::create(patch); + return pdf * bilinear.backwardPdf(u); + } + + scalar_type pdf(const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) + { + scalar_type pdf; + const vector2_type u = sphtri.generateInverse(pdf, L); + vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); + Bilinear bilinear = Bilinear::create(patch); + return pdf * bilinear.backwardPdf(u); + } + + shapes::SphericalTriangle tri; + sampling::SphericalTriangle sphtri; }; } // namespace sampling From 5933fe0478e1eb1b2729a0a814139d22645a4cbd Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 21 Jan 2026 15:08:09 +0700 Subject: [PATCH 05/33] changes to solid angle method name, simplified a lot of code in spherical triangle --- .../projected_spherical_triangle.hlsl | 7 +- .../hlsl/sampling/spherical_triangle.hlsl | 204 +++++++++--------- .../hlsl/shapes/spherical_rectangle.hlsl | 4 +- .../hlsl/shapes/spherical_triangle.hlsl | 58 ++--- 4 files changed, 133 insertions(+), 140 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index 63926c9df4..0952ed423a 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -67,9 +67,12 @@ struct ProjectedSphericalTriangle vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector3_type receiverNormal, bool isBSDF, const vector2_type u) { - scalar_type cos_a, cos_c, csc_b, csc_c; + const scalar_type cos_a = tri.cos_sides[0]; + const scalar_type cos_c = tri.cos_sides[2]; + const scalar_type csc_b = tri.csc_sides[1]; + const scalar_type csc_c = tri.csc_sides[2]; vector3_type cos_vertices, sin_vertices; - const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + const scalar_type solidAngle = tri.solidAngle(cos_vertices, sin_vertices); return generate(rcpPdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, receiverNormal, isBSDF, u); } diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 5d9d32ad21..430c8ccd0d 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -22,105 +22,111 @@ namespace sampling template struct SphericalTriangle { - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - - // BijectiveSampler concept types - using domain_type = vector2_type; - using codomain_type = vector3_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; - - static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) - { - SphericalTriangle retval; - retval.tri = tri; - return retval; - } - - // WARNING: can and will return NAN if one or three of the triangle edges are near zero length - vector3_type generate(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector2_type u) - { - scalar_type negSinSubSolidAngle, negCosSubSolidAngle; - math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); - - const scalar_type p = negCosSubSolidAngle * sin_vertices[0] - negSinSubSolidAngle * cos_vertices[0]; - const scalar_type q = -negSinSubSolidAngle * sin_vertices[0] - negCosSubSolidAngle * cos_vertices[0]; - - // TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful - scalar_type u_ = q - cos_vertices[0]; - scalar_type v_ = p + sin_vertices[0] * cos_c; - - // the slerps could probably be optimized by sidestepping `normalize` calls and accumulating scaling factors - vector3_type C_s = tri.vertex0; - if (csc_b < numeric_limits::max) - { - const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cos_vertices[0] - v_) / ((v_ * p + u_ * q) * sin_vertices[0]); - if (nbl::hlsl::abs(cosAngleAlongAC) < 1.f) - C_s += math::quaternion::slerp_delta(tri.vertex0, tri.vertex2 * csc_b, cosAngleAlongAC); - } - - vector3_type retval = tri.vertex1; - const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri.vertex1); - const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); - if (csc_b_s < numeric_limits::max) - { - const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); - if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) - retval += math::quaternion::slerp_delta(tri.vertex1, C_s * csc_b_s, cosAngleAlongBC_s); - } - return retval; - } - - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type u) - { - scalar_type cos_a, cos_c, csc_b, csc_c; - vector3_type cos_vertices, sin_vertices; - - rcpPdf = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); - - return generate(rcpPdf, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); - } - - vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type L) - { - pdf = 1.0 / solidAngle; - - const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertex1); - const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); - const scalar_type cos_b_ = nbl::hlsl::dot(L, tri.vertex0); - - const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; - const scalar_type sinB_ = nbl::hlsl::sqrt(1.0 - cosB_ * cosB_); - - const scalar_type cosC_ = sin_vertices[0] * sinB_ * cos_c - cos_vertices[0] * cosB_; - const scalar_type sinC_ = nbl::hlsl::sqrt(1.0 - cosC_ * cosC_); - - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cos_vertices[0], sin_vertices[0]); - angle_adder.addAngle(cosB_, sinB_); - angle_adder.addAngle(cosC_, sinC_); - const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi)*pdf; - const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; - - const scalar_type cosBC_s = (cos_vertices[0] + cosB_ * cosC_) / (sinB_ * sinC_); - const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); - - return vector2_type(u, v); - } - - vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, const vector3_type L) - { - scalar_type cos_a, cos_c, csc_b, csc_c; - vector3_type cos_vertices, sin_vertices; - - const scalar_type solidAngle = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); - - return generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); - } - - shapes::SphericalTriangle tri; + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector3_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + using inverse_sample_type = domain_and_rcpPdf; + + static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) + { + SphericalTriangle retval; + retval.tri = tri; + return retval; + } + + // WARNING: can and will return NAN if one or three of the triangle edges are near zero length + vector3_type generate(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector2_type u) + { + scalar_type negSinSubSolidAngle,negCosSubSolidAngle; + math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); + + const scalar_type p = negCosSubSolidAngle * sin_vertices[0] - negSinSubSolidAngle * cos_vertices[0]; + const scalar_type q = -negSinSubSolidAngle * sin_vertices[0] - negCosSubSolidAngle * cos_vertices[0]; + + // TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful + scalar_type u_ = q - cos_vertices[0]; + scalar_type v_ = p + sin_vertices[0] * cos_c; + + // the slerps could probably be optimized by sidestepping `normalize` calls and accumulating scaling factors + vector3_type C_s = tri.vertices[0]; + if (csc_b < numeric_limits::max) + { + const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cos_vertices[0] - v_) / ((v_ * p + u_ * q) * sin_vertices[0]); + if (nbl::hlsl::abs(cosAngleAlongAC) < 1.f) + C_s += math::quaternion::slerp_delta(tri.vertices[0], tri.vertices[2] * csc_b, cosAngleAlongAC); + } + + vector3_type retval = tri.vertices[1]; + const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri.vertices[1]); + const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); + if (csc_b_s < numeric_limits::max) + { + const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); + if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) + retval += math::quaternion::slerp_delta(tri.vertices[1], C_s * csc_b_s, cosAngleAlongBC_s); + } + return retval; + } + + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type u) + { + const scalar_type cos_a = tri.cos_sides[0]; + const scalar_type cos_c = tri.cos_sides[2]; + const scalar_type csc_b = tri.csc_sides[1]; + const scalar_type csc_c = tri.csc_sides[2]; + vector3_type cos_vertices, sin_vertices; + + rcpPdf = tri.solidAngle(cos_vertices, sin_vertices); + + return generate(rcpPdf, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); + } + + vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type L) + { + pdf = 1.0 / solidAngle; + + const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertices[1]); + const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); + const scalar_type cos_b_ = nbl::hlsl::dot(L, tri.vertices[0]); + + const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; + const scalar_type sinB_ = nbl::hlsl::sqrt(1.0 - cosB_ * cosB_); + + const scalar_type cosC_ = sin_vertices[0] * sinB_* cos_c - cos_vertices[0] * cosB_; + const scalar_type sinC_ = nbl::hlsl::sqrt(1.0 - cosC_ * cosC_); + + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cos_vertices[0], sin_vertices[0]); + angle_adder.addAngle(cosB_, sinB_); + angle_adder.addAngle(cosC_, sinC_); + const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi) * pdf; + const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; + + const scalar_type cosBC_s = (cos_vertices[0] + cosB_ * cosC_) / (sinB_ * sinC_); + const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); + + return vector2_type(u,v); + } + + vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, const vector3_type L) + { + const scalar_type cos_a = tri.cos_sides[0]; + const scalar_type cos_c = tri.cos_sides[2]; + const scalar_type csc_b = tri.csc_sides[1]; + const scalar_type csc_c = tri.csc_sides[2]; + vector3_type cos_vertices, sin_vertices; + + const scalar_type solidAngle = tri.solidAngle(cos_vertices, sin_vertices); + + return generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); + } + + shapes::SphericalTriangle tri; }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl index 11442bef7c..587e221996 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl @@ -22,7 +22,6 @@ struct SphericalRectangle { using scalar_type = Scalar; using vector3_type = vector; - using vector4_type = vector; using matrix3x3_type = matrix; static SphericalRectangle create(const vector3_type observer, const vector3_type rectangleOrigin, const matrix3x3_type basis) @@ -40,8 +39,9 @@ struct SphericalRectangle return retval; } - scalar_type solidAngleOfRectangle(const vector rectangleExtents) + scalar_type solidAngle(const vector rectangleExtents) { + using vector4_type = vector; const vector4_type denorm_n_z = vector4_type(-r0.y, r0.x + rectangleExtents.x, r0.y + rectangleExtents.y, -r0.x); const vector4_type n_z = denorm_n_z / nbl::hlsl::sqrt((vector4_type)(r0.z * r0.z) + denorm_n_z * denorm_n_z); const vector4_type cosGamma = vector4_type( diff --git a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl index f574b106ce..028d3e3653 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl @@ -25,36 +25,29 @@ struct SphericalTriangle using scalar_type = T; using vector3_type = vector; - static SphericalTriangle create(const vector3_type vertex0, const vector3_type vertex1, const vector3_type vertex2, const vector3_type origin) + static SphericalTriangle create(const vector3_type vertices[3], const vector3_type origin) { SphericalTriangle retval; - retval.vertex0 = nbl::hlsl::normalize(vertex0 - origin); - retval.vertex1 = nbl::hlsl::normalize(vertex1 - origin); - retval.vertex2 = nbl::hlsl::normalize(vertex2 - origin); - retval.cos_sides = vector3_type(hlsl::dot(retval.vertex1, retval.vertex2), hlsl::dot(retval.vertex2, retval.vertex0), hlsl::dot(retval.vertex0, retval.vertex1)); - const vector3_type csc_sides2 = hlsl::promote(1.0) - retval.cos_sides * retval.cos_sides; - retval.csc_sides.x = hlsl::rsqrt(csc_sides2.x); - retval.csc_sides.y = hlsl::rsqrt(csc_sides2.y); - retval.csc_sides.z = hlsl::rsqrt(csc_sides2.z); + retval.vertices[0] = nbl::hlsl::normalize(vertices[0] - origin); + retval.vertices[1] = nbl::hlsl::normalize(vertices[1] - origin); + retval.vertices[2] = nbl::hlsl::normalize(vertices[2] - origin); + retval.cos_sides = vector3_type(hlsl::dot(retval.vertices[1], retval.vertices[2]), hlsl::dot(retval.vertices[2], retval.vertices[0]), hlsl::dot(retval.vertices[0], retval.vertices[1])); + const vector3_type sin_sides2 = hlsl::promote(1.0) - retval.cos_sides * retval.cos_sides; + retval.csc_sides = hlsl::rsqrt(sin_sides2); return retval; } + // checks if any angles are small enough to disregard bool pyramidAngles() { - return hlsl::any >(csc_sides >= (vector3_type)(numeric_limits::max)); + return hlsl::any >(csc_sides >= hlsl::promote(numeric_limits::max)); } - scalar_type solidAngleOfTriangle(NBL_REF_ARG(vector3_type) cos_vertices, NBL_REF_ARG(vector3_type) sin_vertices, NBL_REF_ARG(scalar_type) cos_a, NBL_REF_ARG(scalar_type) cos_c, NBL_REF_ARG(scalar_type) csc_b, NBL_REF_ARG(scalar_type) csc_c) + scalar_type solidAngle(NBL_REF_ARG(vector3_type) cos_vertices, NBL_REF_ARG(vector3_type) sin_vertices) { if (pyramidAngles()) return 0.f; - // these variables might eventually get optimized out - cos_a = cos_sides[0]; - cos_c = cos_sides[2]; - csc_b = csc_sides[1]; - csc_c = csc_sides[2]; - // Both vertices and angles at the vertices are denoted by the same upper case letters A, B, and C. The angles A, B, C of the triangle are equal to the angles between the planes that intersect the surface of the sphere or, equivalently, the angles between the tangent vectors of the great circle arcs where they meet at the vertices. Angles are in radians. The angles of proper spherical triangles are (by convention) less than PI cos_vertices = hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); // using Spherical Law of Cosines (TODO: do we need to clamp anymore? since the pyramid angles method introduction?) sin_vertices = hlsl::sqrt(hlsl::promote(1.0) - cos_vertices * cos_vertices); @@ -65,39 +58,30 @@ struct SphericalTriangle return angle_adder.getSumofArccos() - numbers::pi; } - scalar_type solidAngleOfTriangle() + scalar_type solidAngle() { vector3_type dummy0,dummy1; - scalar_type dummy2,dummy3,dummy4,dummy5; - return solidAngleOfTriangle(dummy0,dummy1,dummy2,dummy3,dummy4,dummy5); + return solidAngle(dummy0,dummy1); } - scalar_type projectedSolidAngleOfTriangle(const vector3_type receiverNormal, NBL_REF_ARG(vector3_type) cos_sides, NBL_REF_ARG(vector3_type) csc_sides, NBL_REF_ARG(vector3_type) cos_vertices) + scalar_type projectedSolidAngle(const vector3_type receiverNormal, NBL_REF_ARG(vector3_type) cos_sides, NBL_REF_ARG(vector3_type) csc_sides, NBL_REF_ARG(vector3_type) cos_vertices) { if (pyramidAngles()) return 0.f; - vector3_type awayFromEdgePlane0 = hlsl::cross(vertex1, vertex2) * csc_sides[0]; - vector3_type awayFromEdgePlane1 = hlsl::cross(vertex2, vertex0) * csc_sides[1]; - vector3_type awayFromEdgePlane2 = hlsl::cross(vertex0, vertex1) * csc_sides[2]; - - // useless here but could be useful somewhere else - cos_vertices[0] = hlsl::dot(awayFromEdgePlane1, awayFromEdgePlane2); - cos_vertices[1] = hlsl::dot(awayFromEdgePlane2, awayFromEdgePlane0); - cos_vertices[2] = hlsl::dot(awayFromEdgePlane0, awayFromEdgePlane1); - // TODO: above dot products are in the wrong order, either work out which is which, or try all 6 permutations till it works - cos_vertices = hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); + cos_vertices = hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); - matrix awayFromEdgePlane = matrix(awayFromEdgePlane0, awayFromEdgePlane1, awayFromEdgePlane2); + matrix awayFromEdgePlane; + awayFromEdgePlane[0] = hlsl::cross(vertices[1], vertices[2]) * csc_sides[0]; + awayFromEdgePlane[1] = hlsl::cross(vertices[2], vertices[0]) * csc_sides[1]; + awayFromEdgePlane[2] = hlsl::cross(vertices[0], vertices[1]) * csc_sides[2]; const vector3_type externalProducts = hlsl::abs(hlsl::mul(/* transposed already */awayFromEdgePlane, receiverNormal)); - const vector3_type pyramidAngles = acos(cos_sides); - return hlsl::dot(pyramidAngles, externalProducts) / (2.f * numbers::pi); + const vector3_type pyramidAngles = hlsl::acos(cos_sides); + return hlsl::dot(pyramidAngles, externalProducts) / (2.f * numbers::pi); } - vector3_type vertex0; - vector3_type vertex1; - vector3_type vertex2; + vector3_type vertices[3]; vector3_type cos_sides; vector3_type csc_sides; }; From 3ac7b834adc0ef1e91312c0a2b88bfb28da4263e Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 22 Jan 2026 11:35:14 +0700 Subject: [PATCH 06/33] removed redundant/unused variables from spherical triangle sample --- .../hlsl/sampling/spherical_triangle.hlsl | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 430c8ccd0d..191f187649 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -37,27 +37,30 @@ struct SphericalTriangle { SphericalTriangle retval; retval.tri = tri; + vector3_type cos_vertices, sin_vertices; + retval.solidAngle = tri.solidAngle(cos_vertices, sin_vertices); + retval.cosA = cos_vertices[0]; + retval.sinA = sin_vertices[0]; return retval; } - // WARNING: can and will return NAN if one or three of the triangle edges are near zero length - vector3_type generate(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector2_type u) + vector3_type generate(scalar_type cos_c, scalar_type csc_b, const vector2_type u) { scalar_type negSinSubSolidAngle,negCosSubSolidAngle; math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); - const scalar_type p = negCosSubSolidAngle * sin_vertices[0] - negSinSubSolidAngle * cos_vertices[0]; - const scalar_type q = -negSinSubSolidAngle * sin_vertices[0] - negCosSubSolidAngle * cos_vertices[0]; + const scalar_type p = negCosSubSolidAngle * sinA - negSinSubSolidAngle * cosA; + const scalar_type q = -negSinSubSolidAngle * sinA - negCosSubSolidAngle * cosA; // TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful - scalar_type u_ = q - cos_vertices[0]; - scalar_type v_ = p + sin_vertices[0] * cos_c; + scalar_type u_ = q - cosA; + scalar_type v_ = p + sinA * cos_c; // the slerps could probably be optimized by sidestepping `normalize` calls and accumulating scaling factors vector3_type C_s = tri.vertices[0]; if (csc_b < numeric_limits::max) { - const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cos_vertices[0] - v_) / ((v_ * p + u_ * q) * sin_vertices[0]); + const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cosA - v_) / ((v_ * p + u_ * q) * sinA); if (nbl::hlsl::abs(cosAngleAlongAC) < 1.f) C_s += math::quaternion::slerp_delta(tri.vertices[0], tri.vertices[2] * csc_b, cosAngleAlongAC); } @@ -76,18 +79,15 @@ struct SphericalTriangle vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type u) { - const scalar_type cos_a = tri.cos_sides[0]; const scalar_type cos_c = tri.cos_sides[2]; const scalar_type csc_b = tri.csc_sides[1]; - const scalar_type csc_c = tri.csc_sides[2]; - vector3_type cos_vertices, sin_vertices; - rcpPdf = tri.solidAngle(cos_vertices, sin_vertices); + rcpPdf = solidAngle; - return generate(rcpPdf, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); + return generate(cos_c, csc_b, u); } - vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type L) + vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type cos_c, scalar_type csc_c, const vector3_type L) { pdf = 1.0 / solidAngle; @@ -98,16 +98,16 @@ struct SphericalTriangle const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; const scalar_type sinB_ = nbl::hlsl::sqrt(1.0 - cosB_ * cosB_); - const scalar_type cosC_ = sin_vertices[0] * sinB_* cos_c - cos_vertices[0] * cosB_; + const scalar_type cosC_ = sinA * sinB_* cos_c - cosA * cosB_; const scalar_type sinC_ = nbl::hlsl::sqrt(1.0 - cosC_ * cosC_); - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cos_vertices[0], sin_vertices[0]); + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosA, sinA); angle_adder.addAngle(cosB_, sinB_); angle_adder.addAngle(cosC_, sinC_); const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi) * pdf; const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; - const scalar_type cosBC_s = (cos_vertices[0] + cosB_ * cosC_) / (sinB_ * sinC_); + const scalar_type cosBC_s = (cosA + cosB_ * cosC_) / (sinB_ * sinC_); const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); return vector2_type(u,v); @@ -115,18 +115,16 @@ struct SphericalTriangle vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, const vector3_type L) { - const scalar_type cos_a = tri.cos_sides[0]; const scalar_type cos_c = tri.cos_sides[2]; - const scalar_type csc_b = tri.csc_sides[1]; const scalar_type csc_c = tri.csc_sides[2]; - vector3_type cos_vertices, sin_vertices; - - const scalar_type solidAngle = tri.solidAngle(cos_vertices, sin_vertices); - return generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); + return generateInverse(pdf, cos_c, csc_c, L); } shapes::SphericalTriangle tri; + scalar_type solidAngle; + scalar_type cosA; + scalar_type sinA; }; } // namespace sampling From 4ed1cbc937ae538771288bb8c92349f662edf1ce Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 22 Jan 2026 12:24:57 +0700 Subject: [PATCH 07/33] spherical rectangle stores origin, extent, basis and takes observer instead --- .../hlsl/sampling/spherical_rectangle.hlsl | 134 +++++++++--------- .../hlsl/shapes/spherical_rectangle.hlsl | 25 ++-- 2 files changed, 81 insertions(+), 78 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index 8f90be6b3a..a157ff0d8c 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -21,72 +21,74 @@ namespace sampling template struct SphericalRectangle { - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - using vector4_type = vector; - - // ResamplableSampler concept types - using domain_type = vector2_type; - using codomain_type = vector2_type; - using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - - static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect) - { - SphericalRectangle retval; - retval.rect = rect; - return retval; - } - - vector2_type generate(const vector2_type rectangleExtents, const vector2_type uv, NBL_REF_ARG(scalar_type) S) - { - const vector4_type denorm_n_z = vector4_type(-rect.r0.y, rect.r0.x + rectangleExtents.x, rect.r0.y + rectangleExtents.y, -rect.r0.x); - const vector4_type n_z = denorm_n_z / hlsl::sqrt(hlsl::promote(rect.r0.z * rect.r0.z) + denorm_n_z * denorm_n_z); - const vector4_type cosGamma = vector4_type( - -n_z[0] * n_z[1], - -n_z[1] * n_z[2], - -n_z[2] * n_z[3], - -n_z[3] * n_z[0]); - - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[0]); - angle_adder.addCosine(cosGamma[1]); - scalar_type p = angle_adder.getSumofArccos(); - angle_adder = math::sincos_accumulator::create(cosGamma[2]); - angle_adder.addCosine(cosGamma[3]); - scalar_type q = angle_adder.getSumofArccos(); - - const scalar_type k = scalar_type(2.0) * numbers::pi - q; - const scalar_type b0 = n_z[0]; - const scalar_type b1 = n_z[2]; - S = p + q - scalar_type(2.0) * numbers::pi; - - const scalar_type CLAMP_EPS = 1e-5; - - // flip z axis if rect.r0.z > 0 - rect.r0.z = ieee754::flipSignIfRHSNegative(rect.r0.z, -rect.r0.z); - vector3_type r1 = rect.r0 + vector3_type(rectangleExtents.x, rectangleExtents.y, 0); - - const scalar_type au = uv.x * S + k; - const scalar_type fu = (hlsl::cos(au) * b0 - b1) / hlsl::sin(au); - const scalar_type cu_2 = hlsl::max(fu * fu + b0 * b0, 1.f); // forces `cu` to be in [-1,1] - const scalar_type cu = ieee754::flipSignIfRHSNegative(scalar_type(1.0) / hlsl::sqrt(cu_2), fu); - - scalar_type xu = -(cu * rect.r0.z) / hlsl::sqrt(scalar_type(1.0) - cu * cu); - xu = hlsl::clamp(xu, rect.r0.x, r1.x); // avoid Infs - const scalar_type d_2 = xu * xu + rect.r0.z * rect.r0.z; - const scalar_type d = hlsl::sqrt(d_2); - - const scalar_type h0 = rect.r0.y / hlsl::sqrt(d_2 + rect.r0.y * rect.r0.y); - const scalar_type h1 = r1.y / hlsl::sqrt(d_2 + r1.y * r1.y); - const scalar_type hv = h0 + uv.y * (h1 - h0); - const scalar_type hv2 = hv * hv; - const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - CLAMP_EPS); - - return vector2_type((xu - rect.r0.x) / rectangleExtents.x, (yv - rect.r0.y) / rectangleExtents.y); - } - - shapes::SphericalRectangle rect; + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; + + // ResamplableSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using sample_type = codomain_and_rcpPdf; + + static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect) + { + SphericalRectangle retval; + retval.rect = rect; + return retval; + } + + vector2_type generate(const vector3_type observer, const vector2_type uv, NBL_REF_ARG(scalar_type) S) + { + vector3_type r0 = hlsl::mul(rect.basis, rect.origin - observer); + const vector4_type denorm_n_z = vector4_type(-r0.y, r0.x + rect.extents.x, r0.y + rect.extents.y, -r0.x); + const vector4_type n_z = denorm_n_z / hlsl::sqrt(hlsl::promote(r0.z * r0.z) + denorm_n_z * denorm_n_z); + const vector4_type cosGamma = vector4_type( + -n_z[0] * n_z[1], + -n_z[1] * n_z[2], + -n_z[2] * n_z[3], + -n_z[3] * n_z[0] + ); + + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[0]); + angle_adder.addCosine(cosGamma[1]); + scalar_type p = angle_adder.getSumofArccos(); + angle_adder = math::sincos_accumulator::create(cosGamma[2]); + angle_adder.addCosine(cosGamma[3]); + scalar_type q = angle_adder.getSumofArccos(); + + const scalar_type k = scalar_type(2.0) * numbers::pi - q; + const scalar_type b0 = n_z[0]; + const scalar_type b1 = n_z[2]; + S = p + q - scalar_type(2.0) * numbers::pi; + + const scalar_type CLAMP_EPS = 1e-5; + + // flip z axis if r0.z > 0 + r0.z = ieee754::flipSignIfRHSNegative(r0.z, -r0.z); + vector3_type r1 = r0 + vector3_type(rect.extents.x, rect.extents.y, 0); + + const scalar_type au = uv.x * S + k; + const scalar_type fu = (hlsl::cos(au) * b0 - b1) / hlsl::sin(au); + const scalar_type cu_2 = hlsl::max(fu * fu + b0 * b0, 1.f); // forces `cu` to be in [-1,1] + const scalar_type cu = ieee754::flipSignIfRHSNegative(scalar_type(1.0) / hlsl::sqrt(cu_2), fu); + + scalar_type xu = -(cu * r0.z) / hlsl::sqrt(scalar_type(1.0) - cu * cu); + xu = hlsl::clamp(xu, r0.x, r1.x); // avoid Infs + const scalar_type d_2 = xu * xu + r0.z * r0.z; + const scalar_type d = hlsl::sqrt(d_2); + + const scalar_type h0 = r0.y / hlsl::sqrt(d_2 + r0.y * r0.y); + const scalar_type h1 = r1.y / hlsl::sqrt(d_2 + r1.y * r1.y); + const scalar_type hv = h0 + uv.y * (h1 - h0); + const scalar_type hv2 = hv * hv; + const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - CLAMP_EPS); + + return vector2_type((xu - r0.x) / rect.extents.x, (yv - r0.y) / rect.extents.y); + } + + shapes::SphericalRectangle rect; }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl index 587e221996..60c2729f21 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl @@ -21,28 +21,27 @@ template struct SphericalRectangle { using scalar_type = Scalar; + using vector2_type = vector; using vector3_type = vector; using matrix3x3_type = matrix; - static SphericalRectangle create(const vector3_type observer, const vector3_type rectangleOrigin, const matrix3x3_type basis) + static SphericalRectangle create(const vector3_type rectangleOrigin, const vector3_type right, const vector3_type up) { SphericalRectangle retval; - retval.r0 = nbl::hlsl::mul(basis, rectangleOrigin - observer); + retval.origin = rectangleOrigin; + retval.extents = vector2_type(hlsl::length(right), hlsl::length(up)); + retval.basis[0] = right / retval.extents[0]; + retval.basis[1] = up / retval.extents[1]; + retval.basis[2] = hlsl::normalize(hlsl::cross(retval.basis[0], retval.basis[1])); return retval; } - static SphericalRectangle create(const vector3_type observer, const vector3_type rectangleOrigin, const vector3_type T, vector3_type B, const vector3_type N) + scalar_type solidAngle(const vector3_type observer) { - SphericalRectangle retval; - matrix3x3_type TBN = nbl::hlsl::transpose(matrix3x3_type(T, B, N)); - retval.r0 = nbl::hlsl::mul(TBN, rectangleOrigin - observer); - return retval; - } + const vector3_type r0 = hlsl::mul(basis, origin - observer); - scalar_type solidAngle(const vector rectangleExtents) - { using vector4_type = vector; - const vector4_type denorm_n_z = vector4_type(-r0.y, r0.x + rectangleExtents.x, r0.y + rectangleExtents.y, -r0.x); + const vector4_type denorm_n_z = vector4_type(-r0.y, r0.x + extents.x, r0.y + extents.y, -r0.x); const vector4_type n_z = denorm_n_z / nbl::hlsl::sqrt((vector4_type)(r0.z * r0.z) + denorm_n_z * denorm_n_z); const vector4_type cosGamma = vector4_type( -n_z[0] * n_z[1], @@ -57,7 +56,9 @@ struct SphericalRectangle return angle_adder.getSumofArccos() - scalar_type(2.0) * numbers::pi; } - vector3_type r0; + vector3_type origin; + vector2_type extents; + matrix3x3_type basis; }; } From 65ef4b3687c143d4183aaefd3cd62fbeacc36232 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 22 Jan 2026 14:28:42 +0700 Subject: [PATCH 08/33] added compressed spherical rectangle, comments for info of implementation --- .../hlsl/shapes/spherical_rectangle.hlsl | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl index 60c2729f21..5e23774640 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl @@ -17,6 +17,40 @@ namespace hlsl namespace shapes { +// What are we likely to do with a Spherical Rectangle? +// 1) Initialize it multiple times from different observers +// 2) Sample it repeatedly + +// How are we likely to get a spherical rect? +// 1) from OBB matrix (with a model space z-axis scale thats irrelevant - but should be forced to 1.f to not mess with distance) +// 2) in a compressed form + +// So, to bring multiple world-space observers into Spherical Rectangle's own space, we need the basis matrix. +// The matrix should be a matrix where the last column is the translation, a 3x3 matrix with a pre-transform translation (worldSpace rectangle origin to be subtracted). + +// You can compute it from an OBB matrix (as given by/to imguizmo to position a [0,1]^2 rectangle mesh where Z+ is the front face. + +// Now, can apply translation: +// 1) post-rotation so a it automatically gets added during a affine pseudo-mul of a 3x4, so pseudo_mul(basis,observer) +// 2) pre-rotation so you keep a worldspace rectangle origin and subtract it before, e.g. mul(basis,worldSpaceOrigin-observer) - this one is possibly better due to next point + +// So we need to store: +// 1) first two COLUMNS of the original OBB matrix (rows of 3x3 basis matrix with the scale still in there), thats kinda your right and up vectors +// 2) pre-rotation translation / the world-space translation of the rectangle +// Theoretically you could get away with not storing one of the up vector components but its not always the same component you can reconstruct (plane orthogonal to up isn't always the XY plane). +// Could compress up vector as a rotation of the default vector orthogonal to right as given by the frisvad-basis function around the right vector plus a scale +// but that becomes a very expensive decompression step involving a quaternion with uniform scale. + +template +struct CompressedSphericalRectangle +{ + using vector3_type = vector; + + vector3_type origin; + vector3_type right; + vector3_type up; +}; + template struct SphericalRectangle { @@ -36,6 +70,11 @@ struct SphericalRectangle return retval; } + static SphericalRectangle create(NBL_CONST_REF_ARG(CompressedSphericalRectangle) compressed) + { + return create(compressed.origin, compressed.right, compressed.up); + } + scalar_type solidAngle(const vector3_type observer) { const vector3_type r0 = hlsl::mul(basis, origin - observer); From 7fc828192e7e124a40fe3306ab01a2092d649592 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 25 Feb 2026 11:36:36 +0700 Subject: [PATCH 09/33] minor fixes to spherical rectangle stuff --- .../hlsl/sampling/spherical_rectangle.hlsl | 2 +- .../hlsl/shapes/spherical_rectangle.hlsl | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index a157ff0d8c..04534c919d 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -66,7 +66,7 @@ struct SphericalRectangle const scalar_type CLAMP_EPS = 1e-5; // flip z axis if r0.z > 0 - r0.z = ieee754::flipSignIfRHSNegative(r0.z, -r0.z); + r0.z = -hlsl::abs(r0.z); vector3_type r1 = r0 + vector3_type(rect.extents.x, rect.extents.y, 0); const scalar_type au = uv.x * S + k; diff --git a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl index 5e23774640..3890d1a2db 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl @@ -30,6 +30,31 @@ namespace shapes // You can compute it from an OBB matrix (as given by/to imguizmo to position a [0,1]^2 rectangle mesh where Z+ is the front face. +/* +matrix check = mul(modelSpace,tranpose(modelSpace)); +// orthogonality (don't need to check the other 3 lower half numbers, cause MM^T is symmetric) +assert(check[0][1]==0.f); +assert(check[0][2]==0.f); +assert(check[1][2]==0.f); +// the scales are squared +const vector2_type scalesSq = vector2_type(check[0][0],check[1][1]); +const vector2_type scalesRcp = rsqrt(scalesSq); +// only rotation, scale needs to be thrown away +basis = tranpose(modelSpace); +// right now `mul(basis,fromObserver)` will apply extent scales on the dot product +// need to remove that +basis[0] *= scalesRcp[0]; +basis[1] *= scalesRcp[1]; +// but also back it up so we know the size of the original rectangle +extents = promote(vector2_type>(1.f)/scalesRcp; +if (dontAssertZScaleIsOne) + basis[2] *= rsqrt(check[2][2]); +else +{ + assert(check[2][2]==1.f); +} +*/ + // Now, can apply translation: // 1) post-rotation so a it automatically gets added during a affine pseudo-mul of a 3x4, so pseudo_mul(basis,observer) // 2) pre-rotation so you keep a worldspace rectangle origin and subtract it before, e.g. mul(basis,worldSpaceOrigin-observer) - this one is possibly better due to next point From 855dac402e5c09d7c96b07e44e88c5d366526e70 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 2 Mar 2026 15:47:56 +0700 Subject: [PATCH 10/33] spherical rectangle constructor for same rectangle and observer --- .../hlsl/sampling/spherical_rectangle.hlsl | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index 04534c919d..22e3b02397 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -32,25 +32,34 @@ struct SphericalRectangle using density_type = scalar_type; using sample_type = codomain_and_rcpPdf; - static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect) - { - SphericalRectangle retval; - retval.rect = rect; - return retval; - } + NBL_CONSTEXPR_STATIC_INLINE scalar_type ClampEps = 1e-5; - vector2_type generate(const vector3_type observer, const vector2_type uv, NBL_REF_ARG(scalar_type) S) + static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect, const vector3_type observer) { - vector3_type r0 = hlsl::mul(rect.basis, rect.origin - observer); - const vector4_type denorm_n_z = vector4_type(-r0.y, r0.x + rect.extents.x, r0.y + rect.extents.y, -r0.x); - const vector4_type n_z = denorm_n_z / hlsl::sqrt(hlsl::promote(r0.z * r0.z) + denorm_n_z * denorm_n_z); - const vector4_type cosGamma = vector4_type( + SphericalRectangle retval; + + retval.r0 = hlsl::mul(rect.basis, rect.origin - observer); + const vector4_type denorm_n_z = vector4_type(-retval.r0.y, retval.r0.x + rect.extents.x, retval.r0.y + rect.extents.y, -retval.r0.x); + const vector4_type n_z = denorm_n_z / hlsl::sqrt(hlsl::promote(retval.r0.z * retval.r0.z) + denorm_n_z * denorm_n_z); + retval.cosGamma = vector4_type( -n_z[0] * n_z[1], -n_z[1] * n_z[2], -n_z[2] * n_z[3], -n_z[3] * n_z[0] ); + // flip z axis if r0.z > 0 + retval.r0 = -hlsl::abs(retval.r0.z); + retval.r1 = retval.r0 + vector3_type(rect.extents.x, rect.extents.y, 0); + retval.extents = rect.extents; + + retval.b0 = n_z[0]; + retval.b1 = n_z[2]; + return retval; + } + + vector2_type generate(const vector2_type uv, NBL_REF_ARG(scalar_type) S) + { math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[0]); angle_adder.addCosine(cosGamma[1]); scalar_type p = angle_adder.getSumofArccos(); @@ -59,16 +68,8 @@ struct SphericalRectangle scalar_type q = angle_adder.getSumofArccos(); const scalar_type k = scalar_type(2.0) * numbers::pi - q; - const scalar_type b0 = n_z[0]; - const scalar_type b1 = n_z[2]; S = p + q - scalar_type(2.0) * numbers::pi; - const scalar_type CLAMP_EPS = 1e-5; - - // flip z axis if r0.z > 0 - r0.z = -hlsl::abs(r0.z); - vector3_type r1 = r0 + vector3_type(rect.extents.x, rect.extents.y, 0); - const scalar_type au = uv.x * S + k; const scalar_type fu = (hlsl::cos(au) * b0 - b1) / hlsl::sin(au); const scalar_type cu_2 = hlsl::max(fu * fu + b0 * b0, 1.f); // forces `cu` to be in [-1,1] @@ -83,12 +84,17 @@ struct SphericalRectangle const scalar_type h1 = r1.y / hlsl::sqrt(d_2 + r1.y * r1.y); const scalar_type hv = h0 + uv.y * (h1 - h0); const scalar_type hv2 = hv * hv; - const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - CLAMP_EPS); + const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - ClampEps); - return vector2_type((xu - r0.x) / rect.extents.x, (yv - r0.y) / rect.extents.y); + return vector2_type((xu - r0.x) / extents.x, (yv - r0.y) / extents.y); } - shapes::SphericalRectangle rect; + vector4_type cosGamma; + scalar_type b0; + scalar_type b1; + vector3_type r0; + vector3_type r1; + vector2_type extents; }; } // namespace sampling From 468031f6e4cc7cc5623e4824e892d53b2a749d45 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 2 Mar 2026 16:07:07 +0700 Subject: [PATCH 11/33] spherical rectangle create only from compressed, minor fix for spherical tri --- .../builtin/hlsl/shapes/spherical_rectangle.hlsl | 16 ++++++---------- .../builtin/hlsl/shapes/spherical_triangle.hlsl | 10 ++++++++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl index 3890d1a2db..9743049a60 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl @@ -84,22 +84,18 @@ struct SphericalRectangle using vector3_type = vector; using matrix3x3_type = matrix; - static SphericalRectangle create(const vector3_type rectangleOrigin, const vector3_type right, const vector3_type up) + static SphericalRectangle create(NBL_CONST_REF_ARG(CompressedSphericalRectangle) compressed) { SphericalRectangle retval; - retval.origin = rectangleOrigin; - retval.extents = vector2_type(hlsl::length(right), hlsl::length(up)); - retval.basis[0] = right / retval.extents[0]; - retval.basis[1] = up / retval.extents[1]; + retval.origin = compressed.origin; + retval.extents = vector2_type(hlsl::length(compressed.right), hlsl::length(compressed.up)); + retval.basis[0] = compressed.right / retval.extents[0]; + retval.basis[1] = compressed.up / retval.extents[1]; + assert(hlsl::dot(retval.basis[0], retval.basis[1]) > scalar_type(0.0)); retval.basis[2] = hlsl::normalize(hlsl::cross(retval.basis[0], retval.basis[1])); return retval; } - static SphericalRectangle create(NBL_CONST_REF_ARG(CompressedSphericalRectangle) compressed) - { - return create(compressed.origin, compressed.right, compressed.up); - } - scalar_type solidAngle(const vector3_type observer) { const vector3_type r0 = hlsl::mul(basis, origin - observer); diff --git a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl index 028d3e3653..118f022640 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl @@ -43,13 +43,19 @@ struct SphericalTriangle return hlsl::any >(csc_sides >= hlsl::promote(numeric_limits::max)); } + vector3_type __getCosVertices() + { + // using Spherical Law of Cosines (TODO: do we need to clamp anymore? since the pyramid angles method introduction?) + return hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); + } + scalar_type solidAngle(NBL_REF_ARG(vector3_type) cos_vertices, NBL_REF_ARG(vector3_type) sin_vertices) { if (pyramidAngles()) return 0.f; // Both vertices and angles at the vertices are denoted by the same upper case letters A, B, and C. The angles A, B, C of the triangle are equal to the angles between the planes that intersect the surface of the sphere or, equivalently, the angles between the tangent vectors of the great circle arcs where they meet at the vertices. Angles are in radians. The angles of proper spherical triangles are (by convention) less than PI - cos_vertices = hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); // using Spherical Law of Cosines (TODO: do we need to clamp anymore? since the pyramid angles method introduction?) + cos_vertices = __getCosVertices(); sin_vertices = hlsl::sqrt(hlsl::promote(1.0) - cos_vertices * cos_vertices); math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cos_vertices[0], sin_vertices[0]); @@ -69,7 +75,7 @@ struct SphericalTriangle if (pyramidAngles()) return 0.f; - cos_vertices = hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); + cos_vertices = __getCosVertices(); matrix awayFromEdgePlane; awayFromEdgePlane[0] = hlsl::cross(vertices[1], vertices[2]) * csc_sides[0]; From 0f143a0c8f7de607dfec24983359d7e00e781893 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 2 Mar 2026 16:40:57 +0700 Subject: [PATCH 12/33] reduced duplicate methods to only ones matching (close to) concept in projected/spherical triangle --- .../projected_spherical_triangle.hlsl | 56 +++++-------------- .../hlsl/sampling/spherical_triangle.hlsl | 25 +++------ 2 files changed, 21 insertions(+), 60 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index 0952ed423a..87a3fa4044 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -33,69 +33,39 @@ struct ProjectedSphericalTriangle using density_type = scalar_type; using sample_type = codomain_and_rcpPdf; - static ProjectedSphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) - { - ProjectedSphericalTriangle retval; - retval.tri = tri; - return retval; - } - - vector4_type computeBilinearPatch(const vector3_type receiverNormal, bool isBSDF) + Bilinear bilinear computeBilinearPatch() { const scalar_type minimumProjSolidAngle = 0.0; - matrix m = matrix(tri.vertex0, tri.vertex1, tri.vertex2); - const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(isBSDF, nbl::hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); + matrix m = matrix(sphtri.tri.vertices[0], sphtri.tri.vertices[1], sphtri.tri.vertices[2]); + const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(receiverWasBSDF, hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); - return bxdfPdfAtVertex.yyxz; + return Bilinear::create(bxdfPdfAtVertex.yyxz); } - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool isBSDF, const vector2_type _u) + vector3_type generate(const vector2_type u) { vector2_type u; // pre-warp according to proj solid angle approximation - vector4_type patch = computeBilinearPatch(receiverNormal, isBSDF); - Bilinear bilinear = Bilinear::create(patch); + Bilinear bilinear = computeBilinearPatch(); u = bilinear.generate(_u); // now warp the points onto a spherical triangle - const vector3_type L = sphtri.generate(solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); - rcpPdf = solidAngle / bilinear.backwardPdf(u); - + const vector3_type L = sphtri.generate(u); return L; } - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector3_type receiverNormal, bool isBSDF, const vector2_type u) - { - const scalar_type cos_a = tri.cos_sides[0]; - const scalar_type cos_c = tri.cos_sides[2]; - const scalar_type csc_b = tri.csc_sides[1]; - const scalar_type csc_c = tri.csc_sides[2]; - vector3_type cos_vertices, sin_vertices; - const scalar_type solidAngle = tri.solidAngle(cos_vertices, sin_vertices); - return generate(rcpPdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, receiverNormal, isBSDF, u); - } - - scalar_type pdf(scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) - { - scalar_type pdf; - const vector2_type u = sphtri.generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); - vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); - Bilinear bilinear = Bilinear::create(patch); - return pdf * bilinear.backwardPdf(u); - } - - scalar_type pdf(const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) + scalar_type backwardPdf(const vector3_type L) { - scalar_type pdf; - const vector2_type u = sphtri.generateInverse(pdf, L); - vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); - Bilinear bilinear = Bilinear::create(patch); + const scalar_type pdf = sphtri.backwardPdf(L); + const vector2_type u = sphtri.generateInverse(L); + Bilinear bilinear = computeBilinearPatch(); return pdf * bilinear.backwardPdf(u); } - shapes::SphericalTriangle tri; sampling::SphericalTriangle sphtri; + vector3_type receiverNormal; + bool receiverWasBSDF; }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 191f187649..82b171545b 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -44,8 +44,11 @@ struct SphericalTriangle return retval; } - vector3_type generate(scalar_type cos_c, scalar_type csc_b, const vector2_type u) + vector3_type generate(const vector2_type u) { + const scalar_type cos_c = tri.cos_sides[2]; + const scalar_type csc_b = tri.csc_sides[1]; + scalar_type negSinSubSolidAngle,negCosSubSolidAngle; math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); @@ -77,19 +80,10 @@ struct SphericalTriangle return retval; } - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type u) + vector2_type generateInverse(const vector3_type L) { const scalar_type cos_c = tri.cos_sides[2]; - const scalar_type csc_b = tri.csc_sides[1]; - - rcpPdf = solidAngle; - - return generate(cos_c, csc_b, u); - } - - vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type cos_c, scalar_type csc_c, const vector3_type L) - { - pdf = 1.0 / solidAngle; + const scalar_type csc_c = tri.csc_sides[2]; const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertices[1]); const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); @@ -113,12 +107,9 @@ struct SphericalTriangle return vector2_type(u,v); } - vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, const vector3_type L) + scalar_type backwardPdf(const vector3_type L) { - const scalar_type cos_c = tri.cos_sides[2]; - const scalar_type csc_c = tri.csc_sides[2]; - - return generateInverse(pdf, cos_c, csc_c, L); + return scalar_type(1.0) / solidAngle; } shapes::SphericalTriangle tri; From fb0e8a5d1686e8f4943016fde8677e52babe15e4 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 2 Mar 2026 16:58:28 +0700 Subject: [PATCH 13/33] spherical rect generate don't divide by extents, let user do that --- include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index 22e3b02397..26f3ca667b 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -51,7 +51,6 @@ struct SphericalRectangle // flip z axis if r0.z > 0 retval.r0 = -hlsl::abs(retval.r0.z); retval.r1 = retval.r0 + vector3_type(rect.extents.x, rect.extents.y, 0); - retval.extents = rect.extents; retval.b0 = n_z[0]; retval.b1 = n_z[2]; @@ -86,7 +85,7 @@ struct SphericalRectangle const scalar_type hv2 = hv * hv; const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - ClampEps); - return vector2_type((xu - r0.x) / extents.x, (yv - r0.y) / extents.y); + return vector2_type((xu - r0.x), (yv - r0.y)); } vector4_type cosGamma; @@ -94,7 +93,6 @@ struct SphericalRectangle scalar_type b1; vector3_type r0; vector3_type r1; - vector2_type extents; }; } // namespace sampling From ab5ee786b001e4b8af6fd236dcfc965d0d9af557 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 3 Mar 2026 11:15:53 +0700 Subject: [PATCH 14/33] store only needed members from tri --- .../hlsl/sampling/spherical_triangle.hlsl | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 82b171545b..c1ed6be599 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -36,19 +36,19 @@ struct SphericalTriangle static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) { SphericalTriangle retval; - retval.tri = tri; vector3_type cos_vertices, sin_vertices; retval.solidAngle = tri.solidAngle(cos_vertices, sin_vertices); retval.cosA = cos_vertices[0]; retval.sinA = sin_vertices[0]; + retval.tri_vertices = tri.vertices; + retval.triCosC = tri.cos_sides[2]; + retval.triCscB = tri.csc_sides[1]; + retval.triCscC = tri.csc_sides[2]; return retval; } vector3_type generate(const vector2_type u) { - const scalar_type cos_c = tri.cos_sides[2]; - const scalar_type csc_b = tri.csc_sides[1]; - scalar_type negSinSubSolidAngle,negCosSubSolidAngle; math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); @@ -57,42 +57,39 @@ struct SphericalTriangle // TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful scalar_type u_ = q - cosA; - scalar_type v_ = p + sinA * cos_c; + scalar_type v_ = p + sinA * triCosC; // the slerps could probably be optimized by sidestepping `normalize` calls and accumulating scaling factors - vector3_type C_s = tri.vertices[0]; - if (csc_b < numeric_limits::max) + vector3_type C_s = tri_vertices[0]; + if (triCscB < numeric_limits::max) { const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cosA - v_) / ((v_ * p + u_ * q) * sinA); if (nbl::hlsl::abs(cosAngleAlongAC) < 1.f) - C_s += math::quaternion::slerp_delta(tri.vertices[0], tri.vertices[2] * csc_b, cosAngleAlongAC); + C_s += math::quaternion::slerp_delta(tri_vertices[0], tri_vertices[2] * triCscB, cosAngleAlongAC); } - vector3_type retval = tri.vertices[1]; - const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri.vertices[1]); + vector3_type retval = tri_vertices[1]; + const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri_vertices[1]); const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); if (csc_b_s < numeric_limits::max) { const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) - retval += math::quaternion::slerp_delta(tri.vertices[1], C_s * csc_b_s, cosAngleAlongBC_s); + retval += math::quaternion::slerp_delta(tri_vertices[1], C_s * csc_b_s, cosAngleAlongBC_s); } return retval; } vector2_type generateInverse(const vector3_type L) { - const scalar_type cos_c = tri.cos_sides[2]; - const scalar_type csc_c = tri.csc_sides[2]; - - const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertices[1]); + const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri_vertices[1]); const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); - const scalar_type cos_b_ = nbl::hlsl::dot(L, tri.vertices[0]); + const scalar_type cos_b_ = nbl::hlsl::dot(L, tri_vertices[0]); - const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; + const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * triCosC) * csc_a_ * triCscC; const scalar_type sinB_ = nbl::hlsl::sqrt(1.0 - cosB_ * cosB_); - const scalar_type cosC_ = sinA * sinB_* cos_c - cosA * cosB_; + const scalar_type cosC_ = sinA * sinB_* triCosC - cosA * cosB_; const scalar_type sinC_ = nbl::hlsl::sqrt(1.0 - cosC_ * cosC_); math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosA, sinA); @@ -102,7 +99,7 @@ struct SphericalTriangle const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; const scalar_type cosBC_s = (cosA + cosB_ * cosC_) / (sinB_ * sinC_); - const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); + const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : triCosC)); return vector2_type(u,v); } @@ -112,10 +109,14 @@ struct SphericalTriangle return scalar_type(1.0) / solidAngle; } - shapes::SphericalTriangle tri; scalar_type solidAngle; scalar_type cosA; scalar_type sinA; + + vector3_type tri_vertices[3]; + scalar_type triCosC; + scalar_type triCscB; + scalar_type triCscC; }; } // namespace sampling From 17c85ba927a9f16fe89d232135c535eabeb87d4b Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 3 Mar 2026 15:07:06 +0700 Subject: [PATCH 15/33] forward/backward pdfs for spherical triangle/rectangle, projected sph tri to match concepts --- .../projected_spherical_triangle.hlsl | 9 ++++- .../hlsl/sampling/spherical_rectangle.hlsl | 34 ++++++++++++++----- .../hlsl/sampling/spherical_triangle.hlsl | 5 +++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index 87a3fa4044..5fba1df2d7 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -33,7 +33,7 @@ struct ProjectedSphericalTriangle using density_type = scalar_type; using sample_type = codomain_and_rcpPdf; - Bilinear bilinear computeBilinearPatch() + Bilinear computeBilinearPatch() { const scalar_type minimumProjSolidAngle = 0.0; @@ -55,6 +55,13 @@ struct ProjectedSphericalTriangle return L; } + scalar_type forwardPdf(const vector2_type u) + { + const scalar_type pdf = sphtri.forwardPdf(u); + Bilinear bilinear = computeBilinearPatch(); + return pdf * bilinear.backwardPdf(u); + } + scalar_type backwardPdf(const vector3_type L) { const scalar_type pdf = sphtri.backwardPdf(L); diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index 26f3ca667b..c80406a8f8 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -48,6 +48,16 @@ struct SphericalRectangle -n_z[3] * n_z[0] ); + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[0]); + angle_adder.addCosine(cosGamma[1]); + scalar_type p = angle_adder.getSumofArccos(); + angle_adder = math::sincos_accumulator::create(cosGamma[2]); + angle_adder.addCosine(cosGamma[3]); + scalar_type q = angle_adder.getSumofArccos(); + + const scalar_type k = scalar_type(2.0) * numbers::pi - q; + retval.solidAngle = p + q - scalar_type(2.0) * numbers::pi; + // flip z axis if r0.z > 0 retval.r0 = -hlsl::abs(retval.r0.z); retval.r1 = retval.r0 + vector3_type(rect.extents.x, rect.extents.y, 0); @@ -57,19 +67,14 @@ struct SphericalRectangle return retval; } - vector2_type generate(const vector2_type uv, NBL_REF_ARG(scalar_type) S) + vector2_type generate(const vector2_type u) { - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[0]); - angle_adder.addCosine(cosGamma[1]); - scalar_type p = angle_adder.getSumofArccos(); - angle_adder = math::sincos_accumulator::create(cosGamma[2]); + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[2]); angle_adder.addCosine(cosGamma[3]); scalar_type q = angle_adder.getSumofArccos(); - const scalar_type k = scalar_type(2.0) * numbers::pi - q; - S = p + q - scalar_type(2.0) * numbers::pi; - const scalar_type au = uv.x * S + k; + const scalar_type au = u.x * solidAngle + k; const scalar_type fu = (hlsl::cos(au) * b0 - b1) / hlsl::sin(au); const scalar_type cu_2 = hlsl::max(fu * fu + b0 * b0, 1.f); // forces `cu` to be in [-1,1] const scalar_type cu = ieee754::flipSignIfRHSNegative(scalar_type(1.0) / hlsl::sqrt(cu_2), fu); @@ -81,13 +86,24 @@ struct SphericalRectangle const scalar_type h0 = r0.y / hlsl::sqrt(d_2 + r0.y * r0.y); const scalar_type h1 = r1.y / hlsl::sqrt(d_2 + r1.y * r1.y); - const scalar_type hv = h0 + uv.y * (h1 - h0); + const scalar_type hv = h0 + u.y * (h1 - h0); const scalar_type hv2 = hv * hv; const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - ClampEps); return vector2_type((xu - r0.x), (yv - r0.y)); } + scalar_type forwardPdf(const vector2_type u) + { + return scalar_type(1.0) / solidAngle; + } + + scalar_type backwardPdf(const vector2_type L) + { + return scalar_type(1.0) / solidAngle; + } + + scalar_type solidAngle; vector4_type cosGamma; scalar_type b0; scalar_type b1; diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index c1ed6be599..83cde18a96 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -104,6 +104,11 @@ struct SphericalTriangle return vector2_type(u,v); } + scalar_type forwardPdf(const vector2_type u) + { + return scalar_type(1.0) / solidAngle; + } + scalar_type backwardPdf(const vector3_type L) { return scalar_type(1.0) / solidAngle; From d95cfa72cedb0dd6e0dbf9e0c83e601c36a21285 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 3 Mar 2026 16:41:00 +0700 Subject: [PATCH 16/33] copied over fixed linear sampling because merge fucked up, added forward/backward pdfs + inverse generate --- include/nbl/builtin/hlsl/sampling/linear.hlsl | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index 1c12aeea29..78c57a53bb 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -32,25 +32,44 @@ struct Linear static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end), assumed to be at x=0 and x=1 { Linear retval; - scalar_type rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); - retval.linearCoeffStartOverDiff = linearCoeffs[0] * rcpDiff; + retval.linearCoeffStart = linearCoeffs[0]; + retval.linearCoeffDiff = linearCoeffs[1] - linearCoeffs[0]; + retval.rcpCoeffSum = scalar_type(1.0) / (linearCoeffs[0] + linearCoeffs[1]); + retval.rcpDiff = -scalar_type(1.0) / retval.linearCoeffDiff; vector2_type squaredCoeffs = linearCoeffs * linearCoeffs; - scalar_type squaredRcpDiff = rcpDiff * rcpDiff; - retval.squaredCoeffStartOverDiff = squaredCoeffs[0] * squaredRcpDiff; - retval.squaredCoeffDiffOverDiff = (squaredCoeffs[1] - squaredCoeffs[0]) * squaredRcpDiff; + retval.squaredCoeffStart = squaredCoeffs[0]; + retval.squaredCoeffDiff = squaredCoeffs[1] - squaredCoeffs[0]; return retval; } scalar_type generate(const scalar_type u) { - return hlsl::mix(u, (linearCoeffStartOverDiff - hlsl::sqrt(squaredCoeffStartOverDiff + u * squaredCoeffDiffOverDiff)), hlsl::abs(linearCoeffStartOverDiff) < numeric_limits::max); + return hlsl::mix(u, (linearCoeffStart - hlsl::sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, hlsl::abs(rcpDiff) < numeric_limits::max); } - // TODO: add forwardPdf and backwardPdf methods, forward computes from u and backwards from the result of generate + scalar_type generateInverse(const scalar_type x) + { + return x * (scalar_type(2.0) * linearCoeffStart + linearCoeffDiff * x) * rcpCoeffSum; + } + + scalar_type forwardPdf(const scalar_type u) + { + return backwardPdf(generate(u)); + } + + scalar_type backwardPdf(const scalar_type x) + { + if (x < scalar_type(0.0) || x > scalar_type(1.0)) + return scalar_type(0.0); + return scalar_type(2.0) * (linearCoeffStart + x * linearCoeffDiff) * rcpCoeffSum; + } - scalar_type linearCoeffStartOverDiff; - scalar_type squaredCoeffStartOverDiff; - scalar_type squaredCoeffDiffOverDiff; + scalar_type linearCoeffStart; + scalar_type linearCoeffDiff; + scalar_type rcpCoeffSum; + scalar_type rcpDiff; + scalar_type squaredCoeffStart; + scalar_type squaredCoeffDiff; }; } // namespace sampling From 0bb7b39f90a203cc2abb51bb1ebf8d690d95c712 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 4 Mar 2026 11:12:05 +0700 Subject: [PATCH 17/33] add forward pdf, generate inverse to bilinear --- .../nbl/builtin/hlsl/sampling/bilinear.hlsl | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index 7006e63852..af84e49544 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -42,22 +42,38 @@ struct Bilinear return retval; } - vector2_type generate(const vector2_type _u) + vector2_type generate(const vector2_type u) { - vector2_type u; - u.y = lineary.generate(_u.y); + vector2_type p; + p.y = lineary.generate(u.y); + + const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); + Linear linearx = Linear::create(ySliceEndPoints); + p.x = linearx.generate(u.x); + + return p; + } - const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + u.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + u.y * bilinearCoeffDiffs[1]); + vector2_type generateInverse(const vector2_type p) + { + vector2_type u; + const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); Linear linearx = Linear::create(ySliceEndPoints); - u.x = linearx.generate(_u.x); + u.x = linearx.generateInverse(p.x); + u.y = lineary.generateInverse(p.y); return u; } - scalar_type backwardPdf(const vector2_type u) + scalar_type forwardPdf(const vector2_type u) + { + return backwardPdf(generate(u)); + } + + scalar_type backwardPdf(const vector2_type p) { - const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + u.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + u.y * bilinearCoeffDiffs[1]); - return nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], u.x) * fourOverTwiceAreasUnderXCurveSum; + const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); + return nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], p.x) * fourOverTwiceAreasUnderXCurveSum; } // unit square: x0y0 x1y0 From 89f6d5f043c8080202b0bcf9cf7fa0f953e24512 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 4 Mar 2026 14:22:33 +0700 Subject: [PATCH 18/33] uniform hemi/sphere samplign make static methods private, added methods to match concept --- .../hlsl/sampling/uniform_spheres.hlsl | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl index c92d732b43..6f3200f4d9 100644 --- a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl @@ -32,7 +32,7 @@ struct UniformHemisphere using sample_type = codomain_and_rcpPdf; using inverse_sample_type = domain_and_rcpPdf; - static vector_t3 generate(const vector_t2 _sample) + static vector_t3 __generate(const vector_t2 _sample) { T z = _sample.x; T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); @@ -40,11 +40,39 @@ struct UniformHemisphere return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); } - static T pdf() + vector_t3 generate(const vector_t2 _sample) + { + return __generate(_sample); + } + + static vector_t2 __generateInverse(const vector_t3 _sample) + { + T phi = hlsl::atan2(_sample.y, _sample.x); + const T twopi = T(2.0) * numbers::pi; + phi += hlsl::mix(T(0.0), twopi, phi < T(0.0)); + return vector_t2(_sample.z, phi / twopi); + } + + vector_t2 generateInverse(const vector_t3 _sample) + { + return __generateInverse(_sample); + } + + static scalar_type __pdf() { return T(1.0) / (T(2.0) * numbers::pi); } + scalar_type forwardPdf(const vector_t2 _sample) + { + return __pdf(); + } + + scalar_type backwardPdf(const vector_t3 _sample) + { + return __pdf(); + } + template > static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() { @@ -66,7 +94,7 @@ struct UniformSphere using sample_type = codomain_and_rcpPdf; using inverse_sample_type = domain_and_rcpPdf; - static vector_t3 generate(const vector_t2 _sample) + static vector_t3 __generate(const vector_t2 _sample) { T z = T(1.0) - T(2.0) * _sample.x; T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); @@ -74,11 +102,39 @@ struct UniformSphere return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); } - static T pdf() + vector_t3 generate(const vector_t2 _sample) + { + return __generate(_sample); + } + + static vector_t2 __generateInverse(const vector_t3 _sample) + { + T phi = hlsl::atan2(_sample.y, _sample.x); + const T twopi = T(2.0) * numbers::pi; + phi += hlsl::mix(T(0.0), twopi, phi < T(0.0)); + return vector_t2((T(1.0) - _sample.z) * T(0.5), phi / twopi); + } + + vector_t2 generateInverse(const vector_t3 _sample) + { + return __generateInverse(_sample); + } + + static T __pdf() { return T(1.0) / (T(4.0) * numbers::pi); } + scalar_type forwardPdf(const vector_t2 _sample) + { + return __pdf(); + } + + scalar_type backwardPdf(const vector_t3 _sample) + { + return __pdf(); + } + template > static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() { From e07ebc1247cebf5e3f5b85393b794949a3f8b4d8 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 4 Mar 2026 15:43:14 +0700 Subject: [PATCH 19/33] cosine hemi/sphere sampling make static methods private, added methods to match concept (inverse is sketchy), invert concentric mapping --- .../hlsl/sampling/concentric_mapping.hlsl | 37 +++++++++++ .../hlsl/sampling/cos_weighted_spheres.hlsl | 64 +++++++++++++++++-- 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl index 4d80e14861..342b754c5a 100644 --- a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl @@ -46,6 +46,43 @@ vector concentricMapping(const vector _u) return p; } +template +vector invertConcentricMapping(const vector p) +{ + T theta = hlsl::atan2(p.y, p.x); // -pi -> pi + T r = hlsl::sqrt(p.x * p.x + p.y * p.y); + const T PiOver4 = T(0.25) * numbers::pi; + + vector u; + // TODO: should reduce branching somehow? + if (hlsl::abs(theta) < PiOver4 || hlsl::abs(theta) > 3 * PiOver4) + { + r = ieee754::copySign(r, p.x); + u.x = r; + if (p.x < 0) { + if (p.y < 0) { + u.y = (numbers::pi + theta) * r / PiOver4; + } else { + u.y = (theta - numbers::pi) * r / PiOver4; + } + } else { + u.y = (theta * r) / PiOver4; + } + } + else + { + r = ieee754::copySign(r, p.y); + u.y = r; + if (p.y < 0) { + u.x = -(T(0.5) * numbers::pi + theta) * r / PiOver4; + } else { + u.x = (T(0.5) * numbers::pi - theta) * r / PiOver4; + } + } + + return (u + hlsl::promote >(1.0)) * T(0.5); +} + } // namespace sampling } // namespace hlsl } // namespace nbl diff --git a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl index c65a688eb3..ed6c574284 100644 --- a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl @@ -31,18 +31,43 @@ struct ProjectedHemisphere using sample_type = codomain_and_rcpPdf; using inverse_sample_type = domain_and_rcpPdf; - static vector_t3 generate(const vector_t2 _sample) + static vector_t3 __generate(const vector_t2 _sample) { vector_t2 p = concentricMapping(_sample * T(0.99999) + T(0.000005)); T z = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - p.x * p.x - p.y * p.y)); return vector_t3(p.x, p.y, z); } - static T pdf(const T L_z) + vector_t3 generate(const vector_t2 _sample) + { + return __generate(_sample); + } + + static vector_t2 __generateInverse(const vector_t3 L) + { + return invertConcentricMapping(L.xy); + } + + vector_t2 generateInverse(const vector_t3 L) + { + return __generateInverse(L); + } + + static T __pdf(const T L_z) { return L_z * numbers::inv_pi; } + scalar_type forwardPdf(const vector_t2 _sample) + { + return __pdf(__generate(_sample).z); + } + + scalar_type backwardPdf(const vector_t3 L) + { + return __pdf(L.z); + } + template > static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const T L) { @@ -71,9 +96,9 @@ struct ProjectedSphere using sample_type = codomain_and_rcpPdf; using inverse_sample_type = domain_and_rcpPdf; - static vector_t3 generate(NBL_REF_ARG(vector_t3) _sample) + static vector_t3 __generate(NBL_REF_ARG(vector_t3) _sample) { - vector_t3 retval = hemisphere_t::generate(_sample.xy); + vector_t3 retval = hemisphere_t::__generate(_sample.xy); const bool chooseLower = _sample.z > T(0.5); retval.z = chooseLower ? (-retval.z) : retval.z; if (chooseLower) @@ -82,9 +107,36 @@ struct ProjectedSphere return retval; } - static T pdf(T L_z) + vector_t3 generate(NBL_REF_ARG(vector_t3) _sample) + { + return __generate(_sample); + } + + static vector_t3 __generateInverse(const vector_t3 L) + { + // TODO: incomplete information to get z component, we only know mapping of (u.z > 0.5 <-> L +ve) and (u.z < 0.5 <-> L -ve) + // so set to 0 or 1 for now + return vector_t3(hemisphere_t::__generateInverse(L.xy), hlsl::mix(T(0.0), T(1.0), L.z > T(0.0))); + } + + vector_t3 generateInverse(const vector_t3 L) + { + return __generateInverse(L); + } + + static T __pdf(T L_z) + { + return T(0.5) * hemisphere_t::__pdf(L_z); + } + + scalar_type forwardPdf(const vector_t2 _sample) + { + return __pdf(__generate(_sample).z); + } + + scalar_type backwardPdf(const vector_t3 L) { - return T(0.5) * hemisphere_t::pdf(L_z); + return __pdf(L.z); } template > From c4e63b3b4a7f898b0895703429528914187169a6 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 4 Mar 2026 16:50:49 +0700 Subject: [PATCH 20/33] box muller transform add forward pdf, generate wasn't merged from pt branch --- .../builtin/hlsl/sampling/box_muller_transform.hlsl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index 9f76f06576..01d6143de5 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -28,6 +28,18 @@ struct BoxMullerTransform using density_type = scalar_type; using sample_type = codomain_and_rcpPdf; + vector2_type generate(const vector2_type u) + { + scalar_type sinPhi, cosPhi; + math::sincos(2.0 * numbers::pi * xi.y - numbers::pi, sinPhi, cosPhi); + return vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(-2.0 * nbl::hlsl::log(xi.x)) * stddev; + } + + vector2_type forwardPdf(const vector2_type u) + { + return backwardPdf(generate(u)); + } + vector2_type backwardPdf(const vector2_type outPos) { const vector2_type outPos2 = outPos * outPos; From c064b29b141acd077d27306ed7ea1f8b4be613b7 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Thu, 5 Mar 2026 17:40:29 +0300 Subject: [PATCH 21/33] `approx_compare` uses abs then relative comparison, better sampler concepts, prevent NaN in spherical rectangle, few refactors --- examples_tests | 2 +- .../builtin/hlsl/bxdf/base/lambertian.hlsl | 6 +- .../hlsl/sampling/box_muller_transform.hlsl | 2 +- .../nbl/builtin/hlsl/sampling/concepts.hlsl | 100 ++++++++++++++---- .../hlsl/sampling/cos_weighted_spheres.hlsl | 18 ++-- include/nbl/builtin/hlsl/sampling/linear.hlsl | 4 +- .../projected_spherical_triangle.hlsl | 2 +- .../hlsl/sampling/quotient_and_pdf.hlsl | 17 +-- .../hlsl/sampling/spherical_rectangle.hlsl | 2 +- .../hlsl/sampling/spherical_triangle.hlsl | 20 +++- .../hlsl/sampling/uniform_spheres.hlsl | 10 +- .../{warp_and_pdf.hlsl => value_and_pdf.hlsl} | 74 +++++-------- .../builtin/hlsl/testing/approx_compare.hlsl | 82 ++++++++++++++ src/nbl/builtin/CMakeLists.txt | 3 +- 14 files changed, 239 insertions(+), 103 deletions(-) rename include/nbl/builtin/hlsl/sampling/{warp_and_pdf.hlsl => value_and_pdf.hlsl} (53%) create mode 100644 include/nbl/builtin/hlsl/testing/approx_compare.hlsl diff --git a/examples_tests b/examples_tests index 18fe5eb6a3..d1fa476e8a 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 18fe5eb6a39d7a09f8e928ca040d06d430e205bb +Subproject commit d1fa476e8ac195f2755d42936a827797e159a47d diff --git a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl index a107921026..cd64543c3f 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl @@ -76,10 +76,10 @@ struct SLambertianBase { sampling::quotient_and_pdf qp; NBL_IF_CONSTEXPR (IsBSDF) - qp = sampling::ProjectedSphere::template quotient_and_pdf(_sample.getNdotL(_clamp)); + qp = sampling::ProjectedSphere::template quotientAndPdf(_sample.getNdotL(_clamp)); else - qp = sampling::ProjectedHemisphere::template quotient_and_pdf(_sample.getNdotL(_clamp)); - return quotient_pdf_type::create(qp.quotient[0], qp.pdf); + qp = sampling::ProjectedHemisphere::template quotientAndPdf(_sample.getNdotL(_clamp)); + return quotient_pdf_type::create(qp.quotient()[0], qp.pdf()); } quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index 4dd774c8ba..aaf6336f61 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -7,7 +7,7 @@ #include "nbl/builtin/hlsl/math/functions.hlsl" #include "nbl/builtin/hlsl/numbers.hlsl" -#include "nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl" +#include "nbl/builtin/hlsl/sampling/value_and_pdf.hlsl" namespace nbl { diff --git a/include/nbl/builtin/hlsl/sampling/concepts.hlsl b/include/nbl/builtin/hlsl/sampling/concepts.hlsl index 7ad680d34b..a408c0beae 100644 --- a/include/nbl/builtin/hlsl/sampling/concepts.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concepts.hlsl @@ -17,9 +17,9 @@ namespace concepts // // Checks that a sample type bundles a value with its PDF. // -// Required members/methods: -// value - the sampled value (member or method) -// pdf - the probability density +// Required methods: +// value() - the sampled value +// pdf() - the probability density // // Satisfied by: codomain_and_pdf, domain_and_pdf, quotient_and_pdf // ============================================================================ @@ -32,8 +32,8 @@ namespace concepts NBL_CONCEPT_BEGIN(1) #define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR)(s.pdf)) - ((NBL_CONCEPT_REQ_EXPR)(s.value))); + ((NBL_CONCEPT_REQ_EXPR)(s.pdf())) + ((NBL_CONCEPT_REQ_EXPR)(s.value()))); #undef s #include // clang-format on @@ -43,9 +43,9 @@ NBL_CONCEPT_END( // // Checks that a sample type bundles a value with its reciprocal PDF. // -// Required members/methods: -// value - the sampled value (member or method) -// rcpPdf - the reciprocal probability density +// Required methods: +// value() - the sampled value +// rcpPdf() - the reciprocal probability density // // Satisfied by: codomain_and_rcpPdf, domain_and_rcpPdf // ============================================================================ @@ -58,8 +58,8 @@ NBL_CONCEPT_END( NBL_CONCEPT_BEGIN(1) #define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR)(s.rcpPdf)) - ((NBL_CONCEPT_REQ_EXPR)(s.value))); + ((NBL_CONCEPT_REQ_EXPR)(s.rcpPdf())) + ((NBL_CONCEPT_REQ_EXPR)(s.value()))); #undef s #include // clang-format on @@ -148,26 +148,80 @@ NBL_CONCEPT_END( // ============================================================================ // ResamplableSampler // +// Extends BasicSampler with forward and backward importance weights, enabling +// use in Multiple Importance Sampling (MIS) and Resampled Importance +// Sampling (RIS). +// +// Note: resampling does not require tractability - the weights need not be +// normalized probability densities, so this concept is satisfied by +// intractable samplers as well. +// +// Required types (in addition to BasicSampler): +// weight_type - the type of the importance weight +// +// Required methods (in addition to BasicSampler): +// weight_type forwardWeight(domain_type u) - forward weight for MIS +// weight_type backwardWeight(codomain_type v) - backward weight for RIS +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME ResamplableSampler +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (_sampler, T) +#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) +#define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) +NBL_CONCEPT_BEGIN(3) +#define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(BasicSampler, T)) + ((NBL_CONCEPT_REQ_TYPE)(T::weight_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(u)), ::nbl::hlsl::is_same_v, typename T::weight_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardWeight(v)), ::nbl::hlsl::is_same_v, typename T::weight_type))); +#undef v +#undef u +#undef _sampler +#include +// clang-format on + +// ============================================================================ +// InvertibleSampler +// // Extends TractableSampler with the ability to evaluate the PDF given // a codomain value (i.e. without knowing the original domain input). +// The reverse mapping could be implemented via bisection search and is +// not necessarily bijective - input/output pairs need not match. +// +// Also exposes forward and backward importance weights for use in MIS and RIS. +// For an invertible sampler these are just the forward and backward PDFs, +// but the names signal the intended use at call sites. // // Required methods (in addition to TractableSampler): // density_type backwardPdf(codomain_type v) +// density_type forwardWeight(domain_type u) - weight for MIS +// density_type backwardWeight(codomain_type v) - weight for RIS // ============================================================================ // clang-format off -#define NBL_CONCEPT_NAME ResamplableSampler +#define NBL_CONCEPT_NAME InvertibleSampler #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (_sampler, T) -#define NBL_CONCEPT_PARAM_1 (v, typename T::codomain_type) -NBL_CONCEPT_BEGIN(2) +#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) +#define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) +NBL_CONCEPT_BEGIN(3) #define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(TractableSampler, T)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type))); + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(u)), ::nbl::hlsl::is_same_v, typename T::density_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardWeight(v)), ::nbl::hlsl::is_same_v, typename T::density_type))); #undef v +#undef u #undef _sampler #include // clang-format on @@ -176,19 +230,21 @@ NBL_CONCEPT_END( // BijectiveSampler // // The mapping domain <-> codomain is bijective (1:1), so it can be -// inverted. Extends ResamplableSampler with invertGenerate. +// inverted. Extends InvertibleSampler with invertGenerate. // -// Because the mapping is bijective, the Jacobian of the inverse is -// the reciprocal of the Jacobian of the forward mapping: -// backwardPdf(v) == 1.0 / forwardPdf(invertGenerate(v).value) +// Because the mapping is bijective, the absolute value of the determinant +// of the Jacobian matrix of the inverse equals the reciprocal of the +// absolute value of the determinant of the Jacobian matrix of the forward +// mapping (the Jacobian is an NxM matrix, not a scalar): +// backwardPdf(v) == 1.0 / forwardPdf(invertGenerate(v).value()) // -// Required types (in addition to ResamplableSampler): +// Required types (in addition to InvertibleSampler): // inverse_sample_type - bundled return of invertGenerate, should be // one of: // domain_and_rcpPdf (preferred) // domain_and_pdf // -// Required methods (in addition to ResamplableSampler): +// Required methods (in addition to InvertibleSampler): // inverse_sample_type invertGenerate(codomain_type v) // ============================================================================ @@ -202,7 +258,7 @@ NBL_CONCEPT_BEGIN(2) #define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(ResamplableSampler, T)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(InvertibleSampler, T)) ((NBL_CONCEPT_REQ_TYPE)(T::inverse_sample_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(SampleWithDensity, typename T::inverse_sample_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.invertGenerate(v)), ::nbl::hlsl::is_same_v, typename T::inverse_sample_type))); diff --git a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl index c65a688eb3..92e2cf3bd0 100644 --- a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl @@ -8,7 +8,7 @@ #include "nbl/builtin/hlsl/concepts.hlsl" #include "nbl/builtin/hlsl/sampling/concentric_mapping.hlsl" #include "nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl" -#include "nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl" +#include "nbl/builtin/hlsl/sampling/value_and_pdf.hlsl" namespace nbl { @@ -44,15 +44,15 @@ struct ProjectedHemisphere } template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const T L) + static quotient_and_pdf quotientAndPdf(const T L) { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); + return quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); } template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) + static quotient_and_pdf quotientAndPdf(const vector_t3 L) { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); + return quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); } }; @@ -88,15 +88,15 @@ struct ProjectedSphere } template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(T L) + static quotient_and_pdf quotientAndPdf(T L) { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); + return quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); } template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) + static quotient_and_pdf quotientAndPdf(const vector_t3 L) { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); + return quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); } }; diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index 16f583bbbf..e4d32d801c 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -7,7 +7,7 @@ #include #include -#include +#include namespace nbl { @@ -42,7 +42,7 @@ struct Linear scalar_type generate(const scalar_type u) { - return hlsl::mix(u, (linearCoeffStart - hlsl::sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, hlsl::abs(rcpDiff) < numeric_limits::max); + return hlsl::mix(u, (linearCoeffStart - sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, abs(rcpDiff) < numeric_limits::max); } scalar_type linearCoeffStart; diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index eeb48ea388..27096bf7a9 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -10,7 +10,7 @@ #include #include #include -#include +#include namespace nbl { diff --git a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl index 26a62ea617..056f5a91c7 100644 --- a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl +++ b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl @@ -25,26 +25,29 @@ struct quotient_and_pdf static this_t create(const Q _quotient, const P _pdf) { this_t retval; - retval.quotient = _quotient; - retval.pdf = _pdf; + retval._quotient = _quotient; + retval._pdf = _pdf; return retval; } static this_t create(const scalar_q _quotient, const P _pdf) { this_t retval; - retval.quotient = hlsl::promote(_quotient); - retval.pdf = _pdf; + retval._quotient = hlsl::promote(_quotient); + retval._pdf = _pdf; return retval; } + Q quotient() { return _quotient; } + P pdf() { return _pdf; } + Q value() { - return quotient*pdf; + return _quotient * _pdf; } - Q quotient; - P pdf; + Q _quotient; + P _pdf; }; } diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index 8f90be6b3a..8fdbf9fd0c 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -9,7 +9,7 @@ #include #include #include -#include +#include namespace nbl { diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 5d9d32ad21..263a2e9fd2 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -10,7 +10,7 @@ #include #include #include -#include +#include namespace nbl { @@ -67,7 +67,7 @@ struct SphericalTriangle const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); if (csc_b_s < numeric_limits::max) { - const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); + const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) retval += math::quaternion::slerp_delta(tri.vertex1, C_s * csc_b_s, cosAngleAlongBC_s); } @@ -86,10 +86,14 @@ struct SphericalTriangle vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type solidAngle, const vector3_type cos_vertices, const vector3_type sin_vertices, scalar_type cos_a, scalar_type cos_c, scalar_type csc_b, scalar_type csc_c, const vector3_type L) { + using uint_type = unsigned_integer_of_size_t; + pdf = 1.0 / solidAngle; const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertex1); - const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); + const scalar_type sin_a = nbl::hlsl::sqrt(nbl::hlsl::max(scalar_type(0.0), scalar_type(1.0) - cosAngleAlongBC_s * cosAngleAlongBC_s)); + const scalar_type csc_a_ = (sin_a > scalar_type(1e-7)) ? scalar_type(1.0) / sin_a : scalar_type(1e8); + const scalar_type cos_b_ = nbl::hlsl::dot(L, tri.vertex0); const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; @@ -104,8 +108,14 @@ struct SphericalTriangle const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi)*pdf; const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; - const scalar_type cosBC_s = (cos_vertices[0] + cosB_ * cosC_) / (sinB_ * sinC_); - const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); + const scalar_type sinBsinC = sinB_ * sinC_; + + // 1 ULP below 1.0, ensures (1.0 - cosBC_s) is strictly positive in float + const scalar_type one_below_one = bit_cast(bit_cast(scalar_type(1)) - uint_type(1)); + const scalar_type cosBC_s_raw = (cos_vertices[0] + cosB_ * cosC_) / sinBsinC; + const scalar_type cosBC_s = sinBsinC > scalar_type(1e-7) ? cosBC_s_raw : cos_c; + const scalar_type v_denom = scalar_type(1.0) - (cosBC_s < one_below_one ? cosBC_s : cos_c); + const scalar_type v = (scalar_type(1.0) - cosAngleAlongBC_s) / v_denom; return vector2_type(u, v); } diff --git a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl index c92d732b43..d92c8e846e 100644 --- a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl @@ -9,7 +9,7 @@ #include "nbl/builtin/hlsl/numbers.hlsl" #include "nbl/builtin/hlsl/tgmath.hlsl" #include "nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl" -#include "nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl" +#include "nbl/builtin/hlsl/sampling/value_and_pdf.hlsl" namespace nbl { @@ -46,9 +46,9 @@ struct UniformHemisphere } template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() + static quotient_and_pdf quotientAndPdf() { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf()); + return quotient_and_pdf::create(hlsl::promote(1.0), pdf()); } }; @@ -80,9 +80,9 @@ struct UniformSphere } template > - static ::nbl::hlsl::sampling::quotient_and_pdf quotient_and_pdf() + static quotient_and_pdf quotientAndPdf() { - return ::nbl::hlsl::sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf()); + return quotient_and_pdf::create(hlsl::promote(1.0), pdf()); } }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl b/include/nbl/builtin/hlsl/sampling/value_and_pdf.hlsl similarity index 53% rename from include/nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl rename to include/nbl/builtin/hlsl/sampling/value_and_pdf.hlsl index 529cbd5e82..a037c0e3d8 100644 --- a/include/nbl/builtin/hlsl/sampling/warp_and_pdf.hlsl +++ b/include/nbl/builtin/hlsl/sampling/value_and_pdf.hlsl @@ -2,8 +2,8 @@ // This file is part of the "Nabla Engine". // For conditions of distribution and use, see copyright notice in nabla.h -#ifndef _NBL_BUILTIN_HLSL_SAMPLING_WARP_AND_PDF_INCLUDED_ -#define _NBL_BUILTIN_HLSL_SAMPLING_WARP_AND_PDF_INCLUDED_ +#ifndef _NBL_BUILTIN_HLSL_SAMPLING_VALUE_AND_PDF_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_VALUE_AND_PDF_INCLUDED_ namespace nbl { @@ -12,77 +12,61 @@ namespace hlsl namespace sampling { -// Returned by TractableSampler::generate, codomain sample bundled with its rcpPdf template -struct codomain_and_rcpPdf +struct value_and_rcpPdf { - using this_t = codomain_and_rcpPdf; + using this_t = value_and_rcpPdf; static this_t create(const V _value, const P _rcpPdf) { this_t retval; - retval.value = _value; - retval.rcpPdf = _rcpPdf; + retval._value = _value; + retval._rcpPdf = _rcpPdf; return retval; } - V value; - P rcpPdf; + V value() { return _value; } + P rcpPdf() { return _rcpPdf; } + + V _value; + P _rcpPdf; }; -// Returned by TractableSampler::generate, codomain sample bundled with its pdf template -struct codomain_and_pdf +struct value_and_pdf { - using this_t = codomain_and_pdf; + using this_t = value_and_pdf; static this_t create(const V _value, const P _pdf) { this_t retval; - retval.value = _value; - retval.pdf = _pdf; + retval._value = _value; + retval._pdf = _pdf; return retval; } - V value; - P pdf; + V value() { return _value; } + P pdf() { return _pdf; } + + V _value; + P _pdf; }; -// Returned by BijectiveSampler::invertGenerate, domain value bundled with its rcpPdf +// Returned by TractableSampler::generate, codomain sample bundled with its rcpPdf template -struct domain_and_rcpPdf -{ - using this_t = domain_and_rcpPdf; +using codomain_and_rcpPdf = value_and_rcpPdf; - static this_t create(const V _value, const P _rcpPdf) - { - this_t retval; - retval.value = _value; - retval.rcpPdf = _rcpPdf; - return retval; - } +// Returned by TractableSampler::generate, codomain sample bundled with its pdf +template +using codomain_and_pdf = value_and_pdf; - V value; - P rcpPdf; -}; +// Returned by BijectiveSampler::invertGenerate, domain value bundled with its rcpPdf +template +using domain_and_rcpPdf = value_and_rcpPdf; // Returned by BijectiveSampler::invertGenerate, domain value bundled with its pdf template -struct domain_and_pdf -{ - using this_t = domain_and_pdf; - - static this_t create(const V _value, const P _pdf) - { - this_t retval; - retval.value = _value; - retval.pdf = _pdf; - return retval; - } - - V value; - P pdf; -}; +using domain_and_pdf = value_and_pdf; } // namespace sampling } // namespace hlsl diff --git a/include/nbl/builtin/hlsl/testing/approx_compare.hlsl b/include/nbl/builtin/hlsl/testing/approx_compare.hlsl new file mode 100644 index 0000000000..945e9a48cb --- /dev/null +++ b/include/nbl/builtin/hlsl/testing/approx_compare.hlsl @@ -0,0 +1,82 @@ +#ifndef _NBL_BUILTIN_HLSL_TESTING_APPROX_COMPARE_INCLUDED_ +#define _NBL_BUILTIN_HLSL_TESTING_APPROX_COMPARE_INCLUDED_ + +#include + +namespace nbl +{ +namespace hlsl +{ +namespace testing +{ +namespace impl +{ + +template +struct AbsoluteAndRelativeApproxCompareHelper; + +template +NBL_PARTIAL_REQ_TOP(concepts::FloatingPointLikeScalar) +struct AbsoluteAndRelativeApproxCompareHelper) > +{ + static bool __call(NBL_CONST_REF_ARG(FloatingPoint) lhs, NBL_CONST_REF_ARG(FloatingPoint) rhs, const float64_t maxAbsoluteDifference, const float64_t maxRelativeDifference) + { + // Absolute check first: catches small-magnitude values where relative comparison breaks down + if (hlsl::abs(float64_t(lhs) - float64_t(rhs)) <= maxAbsoluteDifference) + return true; + + // Fall back to relative comparison for larger values + return RelativeApproxCompareHelper::__call(lhs, rhs, maxRelativeDifference); + } +}; + +template +NBL_PARTIAL_REQ_TOP(concepts::FloatingPointLikeVectorial) +struct AbsoluteAndRelativeApproxCompareHelper) > +{ + static bool __call(NBL_CONST_REF_ARG(FloatingPointVector) lhs, NBL_CONST_REF_ARG(FloatingPointVector) rhs, const float64_t maxAbsoluteDifference, const float64_t maxRelativeDifference) + { + using traits = nbl::hlsl::vector_traits; + for (uint32_t i = 0; i < traits::Dimension; ++i) + { + if (!AbsoluteAndRelativeApproxCompareHelper::__call(lhs[i], rhs[i], maxAbsoluteDifference, maxRelativeDifference)) + return false; + } + + return true; + } +}; + +template +NBL_PARTIAL_REQ_TOP(concepts::Matricial && concepts::FloatingPointLikeScalar::scalar_type>) +struct AbsoluteAndRelativeApproxCompareHelper && concepts::FloatingPointLikeScalar::scalar_type>) > +{ + static bool __call(NBL_CONST_REF_ARG(FloatingPointMatrix) lhs, NBL_CONST_REF_ARG(FloatingPointMatrix) rhs, const float64_t maxAbsoluteDifference, const float64_t maxRelativeDifference) + { + using traits = nbl::hlsl::matrix_traits; + for (uint32_t i = 0; i < traits::RowCount; ++i) + { + if (!AbsoluteAndRelativeApproxCompareHelper::__call(lhs[i], rhs[i], maxAbsoluteDifference, maxRelativeDifference)) + return false; + } + + return true; + } +}; + +} + +// Composite comparator that builds on top of relativeApproxCompare. +// Checks absolute difference first (handles small-magnitude values where +// relative comparison breaks down), then falls back to relative comparison. +template +bool approxCompare(NBL_CONST_REF_ARG(T) lhs, NBL_CONST_REF_ARG(T) rhs, const float64_t maxAbsoluteDifference, const float64_t maxRelativeDifference) +{ + return impl::AbsoluteAndRelativeApproxCompareHelper::__call(lhs, rhs, maxAbsoluteDifference, maxRelativeDifference); +} + +} +} +} + +#endif diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index 258ed858a6..aaec420d50 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -280,7 +280,7 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/projected_spherical_ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/spherical_rectangle.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/cos_weighted_spheres.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/quotient_and_pdf.hlsl") -LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/warp_and_pdf.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/value_and_pdf.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/concepts.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/uniform_spheres.hlsl") # @@ -388,6 +388,7 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/rwmc/ResolveParameters.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/morton.hlsl") #testing LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/relative_approx_compare.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/approx_compare.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/orientation_compare.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/vector_length_compare.hlsl") From d2114f815580ea6ebb3f5fb0e4ed6be5dc91ab9f Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Sat, 7 Mar 2026 11:31:17 +0300 Subject: [PATCH 22/33] fixes after merge --- .../hlsl/path_tracing/unidirectional.hlsl | 18 +++++++++--------- .../hlsl/sampling/box_muller_transform.hlsl | 7 +++++++ .../hlsl/sampling/spherical_rectangle.hlsl | 6 +++--- .../hlsl/sampling/spherical_triangle.hlsl | 18 ++++++++++++++---- .../hlsl/shapes/spherical_triangle.hlsl | 6 +++--- 5 files changed, 36 insertions(+), 19 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 43e4cb124e..98a81738cb 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -106,15 +106,15 @@ struct Unidirectional // So we need to weigh the Delta lobes as if the MIS weight is always 1, but other areas regularly. // Meaning that eval's pdf should equal quotient's pdf , this way even the diffuse contributions coming from within a specular lobe get a MIS weight near 0 for NEE. // This stops a discrepancy in MIS weights and NEE mistakenly trying to add non-delta lobe contributions with a MIS weight > 0 and creating energy from thin air. - if (neeContrib.pdf > scalar_type(0.0)) + if (neeContrib.pdf() > scalar_type(0.0)) { // TODO: we'll need an `eval_and_mis_weight` and `quotient_and_mis_weight` const scalar_type bsdf_pdf = materialSystem.pdf(matID, nee_sample, interaction); - neeContrib.quotient *= materialSystem.eval(matID, nee_sample, interaction) * rcpChoiceProb; - if (neeContrib.pdf < bit_cast(numeric_limits::infinity)) + neeContrib._quotient *= materialSystem.eval(matID, nee_sample, interaction) * rcpChoiceProb; + if (neeContrib.pdf() < bit_cast(numeric_limits::infinity)) { - const scalar_type otherGenOverLightAndChoice = bsdf_pdf * rcpChoiceProb / neeContrib.pdf; - neeContrib.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic + const scalar_type otherGenOverLightAndChoice = bsdf_pdf * rcpChoiceProb / neeContrib.pdf(); + neeContrib._quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic } const vector3_type origin = intersectP; @@ -124,8 +124,8 @@ struct Unidirectional nee_ray.template setInteraction(interaction); nee_ray.setT(t); tolerance_method_type::template adjust(nee_ray, intersectData.getGeometricNormal(), depth); - if (getLuma(neeContrib.quotient) > lumaContributionThreshold) - ray.addPayloadContribution(neeContrib.quotient * intersector_type::traceShadowRay(scene, nee_ray, ret.getLightObjectID())); + if (getLuma(neeContrib.quotient()) > lumaContributionThreshold) + ray.addPayloadContribution(neeContrib.quotient() * intersector_type::traceShadowRay(scene, nee_ray, ret.getLightObjectID())); } } @@ -142,8 +142,8 @@ struct Unidirectional // the value of the bsdf divided by the probability of the sample being generated quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, bsdf_sample, interaction, _cache); - throughput *= bsdf_quotient_pdf.quotient; - bxdfPdf = bsdf_quotient_pdf.pdf; + throughput *= bsdf_quotient_pdf.quotient(); + bxdfPdf = bsdf_quotient_pdf.pdf(); bxdfSample = bsdf_sample.getL().getDirection(); } diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index af530d16a4..b9f305de29 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -28,6 +28,13 @@ struct BoxMullerTransform using density_type = scalar_type; using sample_type = codomain_and_rcpPdf; + vector2_type operator()(const vector2_type xi) + { + scalar_type sinPhi, cosPhi; + math::sincos(2.0 * numbers::pi * xi.y - numbers::pi, sinPhi, cosPhi); + return vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(-2.0 * nbl::hlsl::log(xi.x)) * stddev; + } + vector2_type backwardPdf(const vector2_type outPos) { const vector2_type outPos2 = outPos * outPos; diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index 36be863764..e47a05b615 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -73,9 +73,9 @@ struct SphericalRectangle const scalar_type cu_2 = hlsl::max(fu * fu + b0 * b0, 1.f); // forces `cu` to be in [-1,1] const scalar_type cu = ieee754::flipSignIfRHSNegative(scalar_type(1.0) / hlsl::sqrt(cu_2), fu); - scalar_type xu = -(cu * rect.r0.z) / hlsl::sqrt(scalar_type(1.0) - cu * cu); - xu = hlsl::clamp(xu, rect.r0.x, r1.x); // avoid Infs - const scalar_type d_2 = xu * xu + rect.r0.z * rect.r0.z; + scalar_type xu = -(cu * r0.z) / hlsl::sqrt(scalar_type(1.0) - cu * cu); + xu = hlsl::clamp(xu, r0.x, r1.x); // avoid Infs + const scalar_type d_2 = xu * xu + r0.z * r0.z; const scalar_type d = hlsl::sqrt(d_2); const scalar_type h0 = r0.y / hlsl::sqrt(d_2 + r0.y * r0.y); diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 88637c0176..6858c5a74c 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -69,7 +69,7 @@ struct SphericalTriangle const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); if (csc_b_s < numeric_limits::max) { - const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); + const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) retval += math::quaternion::slerp_delta(tri.vertices[1], C_s * csc_b_s, cosAngleAlongBC_s); } @@ -88,10 +88,14 @@ struct SphericalTriangle vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type cos_c, scalar_type csc_c, const vector3_type L) { + using uint_type = unsigned_integer_of_size_t; + pdf = 1.0 / solidAngle; const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertices[1]); - const scalar_type csc_a_ = nbl::hlsl::rsqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); + const scalar_type sin_a = nbl::hlsl::sqrt(nbl::hlsl::max(scalar_type(0.0), scalar_type(1.0) - cosAngleAlongBC_s * cosAngleAlongBC_s)); + const scalar_type csc_a_ = (sin_a > scalar_type(1e-7)) ? scalar_type(1.0) / sin_a : scalar_type(1e8); + const scalar_type cos_b_ = nbl::hlsl::dot(L, tri.vertices[0]); const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; @@ -106,8 +110,14 @@ struct SphericalTriangle const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi)*pdf; const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; - const scalar_type cosBC_s = (cosA + cosB_ * cosC_) / (sinB_ * sinC_); - const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); + const scalar_type sinBsinC = sinB_ * sinC_; + + // 1 ULP below 1.0, ensures (1.0 - cosBC_s) is strictly positive in float + const scalar_type one_below_one = bit_cast(bit_cast(scalar_type(1)) - uint_type(1)); + const scalar_type cosBC_s_raw = (cosA + cosB_ * cosC_) / sinBsinC; + const scalar_type cosBC_s = sinBsinC > scalar_type(1e-7) ? cosBC_s_raw : cos_c; + const scalar_type v_denom = scalar_type(1.0) - (cosBC_s < one_below_one ? cosBC_s : cos_c); + const scalar_type v = (scalar_type(1.0) - cosAngleAlongBC_s) / v_denom; return vector2_type(u, v); } diff --git a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl index 028d3e3653..b8106f2244 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl @@ -38,12 +38,12 @@ struct SphericalTriangle } // checks if any angles are small enough to disregard - bool pyramidAngles() + bool pyramidAngles() NBL_CONST_MEMBER_FUNC { return hlsl::any >(csc_sides >= hlsl::promote(numeric_limits::max)); } - scalar_type solidAngle(NBL_REF_ARG(vector3_type) cos_vertices, NBL_REF_ARG(vector3_type) sin_vertices) + scalar_type solidAngle(NBL_REF_ARG(vector3_type) cos_vertices, NBL_REF_ARG(vector3_type) sin_vertices) NBL_CONST_MEMBER_FUNC { if (pyramidAngles()) return 0.f; @@ -58,7 +58,7 @@ struct SphericalTriangle return angle_adder.getSumofArccos() - numbers::pi; } - scalar_type solidAngle() + scalar_type solidAngle() NBL_CONST_MEMBER_FUNC { vector3_type dummy0,dummy1; return solidAngle(dummy0,dummy1); From 743575fac25d34e2f5e38232a767cf312658ae53 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Sat, 7 Mar 2026 12:49:02 +0300 Subject: [PATCH 23/33] update `examples_tests` --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 67408835bc..4a45531e4f 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 67408835bc90ed12f5f371cafd2d192b70ea8bd6 +Subproject commit 4a45531e4f1837f20035535e90e6229cebd93966 From 4d266ec785618a42041b7348bc71ac31803cbd74 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Fri, 13 Mar 2026 03:43:35 +0300 Subject: [PATCH 24/33] All samplers now conform to concepts --- examples_tests | 2 +- .../builtin/hlsl/bxdf/base/lambertian.hlsl | 6 +- .../builtin/hlsl/bxdf/base/oren_nayar.hlsl | 6 +- include/nbl/builtin/hlsl/math/functions.hlsl | 4 +- .../hlsl/path_tracing/gaussian_filter.hlsl | 3 +- .../nbl/builtin/hlsl/sampling/bilinear.hlsl | 46 ++++-- .../hlsl/sampling/box_muller_transform.hlsl | 51 ++++-- .../hlsl/sampling/concentric_mapping.hlsl | 153 ++++++++++++------ .../nbl/builtin/hlsl/sampling/concepts.hlsl | 112 +++++++------ .../hlsl/sampling/cos_weighted_spheres.hlsl | 107 ++++++++---- include/nbl/builtin/hlsl/sampling/linear.hlsl | 45 ++++-- .../projected_spherical_triangle.hlsl | 52 +++--- .../hlsl/sampling/spherical_rectangle.hlsl | 42 +++-- .../hlsl/sampling/spherical_triangle.hlsl | 59 +++++-- .../hlsl/sampling/uniform_spheres.hlsl | 73 ++++++--- 15 files changed, 521 insertions(+), 240 deletions(-) diff --git a/examples_tests b/examples_tests index 4a45531e4f..793182654b 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 4a45531e4f1837f20035535e90e6229cebd93966 +Subproject commit 793182654b252e5bd7ab9fedb5bb12e2135303d3 diff --git a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl index cd64543c3f..3f7b85875a 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl @@ -37,16 +37,18 @@ struct SLambertianBase template > enable_if_t generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector2_type u) NBL_CONST_MEMBER_FUNC { + typename sampling::ProjectedHemisphere::cache_type cache; ray_dir_info_type L; - L.setDirection(sampling::ProjectedHemisphere::generate(u)); + L.setDirection(sampling::ProjectedHemisphere::generate(u, cache)); return sample_type::createFromTangentSpace(L, interaction.getFromTangentSpace()); } template > enable_if_t generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector3_type u) NBL_CONST_MEMBER_FUNC { + typename sampling::ProjectedSphere::cache_type cache; vector3_type _u = u; ray_dir_info_type L; - L.setDirection(sampling::ProjectedSphere::generate(_u)); + L.setDirection(sampling::ProjectedSphere::generate(_u, cache)); return sample_type::createFromTangentSpace(L, interaction.getFromTangentSpace()); } template > diff --git a/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl b/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl index d104842608..ab06e8d43a 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl @@ -72,16 +72,18 @@ struct SOrenNayarBase template > enable_if_t generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector2_type u) NBL_CONST_MEMBER_FUNC { + typename sampling::ProjectedHemisphere::cache_type cache; ray_dir_info_type L; - L.setDirection(sampling::ProjectedHemisphere::generate(u)); + L.setDirection(sampling::ProjectedHemisphere::generate(u, cache)); return sample_type::createFromTangentSpace(L, interaction.getFromTangentSpace()); } template > enable_if_t generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector3_type u) NBL_CONST_MEMBER_FUNC { + typename sampling::ProjectedSphere::cache_type cache; vector3_type _u = u; ray_dir_info_type L; - L.setDirection(sampling::ProjectedSphere::generate(_u)); + L.setDirection(sampling::ProjectedSphere::generate(_u, cache)); return sample_type::createFromTangentSpace(L, interaction.getFromTangentSpace()); } template > diff --git a/include/nbl/builtin/hlsl/math/functions.hlsl b/include/nbl/builtin/hlsl/math/functions.hlsl index f7db44b9fb..7930bb73aa 100644 --- a/include/nbl/builtin/hlsl/math/functions.hlsl +++ b/include/nbl/builtin/hlsl/math/functions.hlsl @@ -136,7 +136,7 @@ struct conditionalAbsOrMax_helper; const T condAbs = bit_cast(bit_cast(x) & (cond ? (numeric_limits::max >> 1) : numeric_limits::max)); - return max(condAbs, limit); + return nbl::hlsl::max(condAbs, limit); } }; @@ -156,7 +156,7 @@ struct conditionalAbsOrMax_helper(condAbsAsUint); - return max(condAbs, limit); + return nbl::hlsl::max(condAbs, limit); } }; } diff --git a/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl b/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl index 6e27749405..9667275f4e 100644 --- a/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl @@ -31,7 +31,8 @@ struct GaussianFilter vector2_type remappedRand = randVec; remappedRand.x *= 1.0 - truncation; remappedRand.x += truncation; - return boxMuller(remappedRand); + typename nbl::hlsl::sampling::BoxMullerTransform::cache_type cache; + return boxMuller.generate(remappedRand, cache); } scalar_type truncation; diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index af84e49544..74b93c831d 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -28,8 +28,12 @@ struct Bilinear using domain_type = vector2_type; using codomain_type = vector2_type; using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + }; static Bilinear create(const vector4_type bilinearCoeffs) { @@ -37,50 +41,66 @@ struct Bilinear retval.bilinearCoeffs = bilinearCoeffs; retval.bilinearCoeffDiffs = vector2_type(bilinearCoeffs[2]-bilinearCoeffs[0], bilinearCoeffs[3]-bilinearCoeffs[1]); vector2_type twiceAreasUnderXCurve = vector2_type(bilinearCoeffs[0] + bilinearCoeffs[1], bilinearCoeffs[2] + bilinearCoeffs[3]); - retval.twiceAreasUnderXCurveSumOverFour = scalar_type(4.0) / (twiceAreasUnderXCurve[0] + twiceAreasUnderXCurve[1]); + retval.fourOverTwiceAreasUnderXCurveSum = scalar_type(4.0) / (twiceAreasUnderXCurve[0] + twiceAreasUnderXCurve[1]); retval.lineary = Linear::create(twiceAreasUnderXCurve); return retval; } - vector2_type generate(const vector2_type u) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { + typename Linear::cache_type linearCache; + vector2_type p; - p.y = lineary.generate(u.y); + p.y = lineary.generate(u.y, linearCache); const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); Linear linearx = Linear::create(ySliceEndPoints); - p.x = linearx.generate(u.x); + p.x = linearx.generate(u.x, linearCache); + cache.pdf = backwardPdf(p); return p; } - vector2_type generateInverse(const vector2_type p) + domain_type generateInverse(const codomain_type p, NBL_REF_ARG(cache_type) cache) { + typename Linear::cache_type linearCache; + vector2_type u; const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); Linear linearx = Linear::create(ySliceEndPoints); - u.x = linearx.generateInverse(p.x); - u.y = lineary.generateInverse(p.y); + u.x = linearx.generateInverse(p.x, linearCache); + u.y = lineary.generateInverse(p.y, linearCache); + cache.pdf = backwardPdf(p); return u; } - scalar_type forwardPdf(const vector2_type u) + density_type forwardPdf(const cache_type cache) + { + return cache.pdf; + } + + weight_type forwardWeight(const cache_type cache) { - return backwardPdf(generate(u)); + return forwardPdf(cache); } - scalar_type backwardPdf(const vector2_type p) + density_type backwardPdf(const codomain_type p) { const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); return nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], p.x) * fourOverTwiceAreasUnderXCurveSum; } + weight_type backwardWeight(const codomain_type p) + { + return backwardPdf(p); + } + // unit square: x0y0 x1y0 // x0y1 x1y1 vector4_type bilinearCoeffs; // (x0y0, x0y1, x1y0, x1y1) vector2_type bilinearCoeffDiffs; - vector2_type fourOverTwiceAreasUnderXCurveSum; + scalar_type fourOverTwiceAreasUnderXCurveSum; Linear lineary; }; diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index 3bf1a5d5a1..1ee96a3f75 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -22,28 +22,61 @@ struct BoxMullerTransform using scalar_type = T; using vector2_type = vector; - // ResamplableSampler concept types + // InvertibleSampler concept types using domain_type = vector2_type; using codomain_type = vector2_type; using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; + using weight_type = density_type; - vector2_type generate(const vector2_type u) + struct cache_type + { + density_type pdf; + }; + + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { scalar_type sinPhi, cosPhi; - math::sincos(2.0 * numbers::pi * xi.y - numbers::pi, sinPhi, cosPhi); - return vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(-2.0 * nbl::hlsl::log(xi.x)) * stddev; + math::sincos(scalar_type(2.0) * numbers::pi * u.y - numbers::pi, sinPhi, cosPhi); + const codomain_type outPos = vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(scalar_type(-2.0) * nbl::hlsl::log(u.x)) * stddev; + cache.pdf = backwardPdf(outPos); + return outPos; + } + + density_type forwardPdf(const cache_type cache) + { + return cache.pdf; + } + + vector2_type separateForwardPdf(const cache_type cache, const codomain_type outPos) + { + return separateBackwardPdf(outPos); + } + + weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); } - vector2_type forwardPdf(const vector2_type u) + density_type backwardPdf(const codomain_type outPos) { - return backwardPdf(generate(u)); + const vector2_type marginals = separateBackwardPdf(outPos); + return marginals.x * marginals.y; } - vector2_type backwardPdf(const vector2_type outPos) + vector2_type separateBackwardPdf(const codomain_type outPos) { + const scalar_type stddev2 = stddev * stddev; + const scalar_type normalization = scalar_type(1.0) / (stddev * nbl::hlsl::sqrt(scalar_type(2.0) * numbers::pi)); const vector2_type outPos2 = outPos * outPos; - return vector2_type(nbl::hlsl::exp(scalar_type(-0.5) * (outPos2.x + outPos2.y)), numbers::pi * scalar_type(0.5) * hlsl::atan2(outPos.y, outPos.x)); + return vector2_type( + normalization * nbl::hlsl::exp(scalar_type(-0.5) * outPos2.x / stddev2), + normalization * nbl::hlsl::exp(scalar_type(-0.5) * outPos2.y / stddev2) + ); + } + + weight_type backwardWeight(const codomain_type outPos) + { + return backwardPdf(outPos); } T stddev; diff --git a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl index 342b754c5a..abede02ed6 100644 --- a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl @@ -17,71 +17,120 @@ namespace sampling { template -vector concentricMapping(const vector _u) +struct ConcentricMapping { - //map [0;1]^2 to [-1;1]^2 - vector u = 2.0f * _u - hlsl::promote >(1.0); + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; - vector p; - if (hlsl::all >(glsl::equal(u, hlsl::promote >(0.0)))) - p = hlsl::promote >(0.0); - else + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + // TODO: should we cache `r`? + }; + + static codomain_type generate(const domain_type _u, NBL_REF_ARG(cache_type) cache) + { + cache.pdf = numbers::inv_pi; + //map [0;1]^2 to [-1;1]^2 + domain_type u = 2.0f * _u - hlsl::promote >(1.0); + + vector p; + if (hlsl::all >(glsl::equal(u, hlsl::promote >(0.0)))) + p = hlsl::promote >(0.0); + else + { + T r; + T theta; + if (hlsl::abs(u.x) > hlsl::abs(u.y)) + { + r = u.x; + theta = 0.25 * numbers::pi * (u.y / u.x); + } + else + { + r = u.y; + theta = 0.5 * numbers::pi - 0.25 * numbers::pi * (u.x / u.y); + } + + p = r * vector(hlsl::cos(theta), hlsl::sin(theta)); + } + + return p; + } + + // Overload for BasicSampler + static codomain_type generate(domain_type _u) + { + cache_type dummy; + return generate(_u, dummy); + } + + static domain_type generateInverse(const codomain_type p, NBL_REF_ARG(cache_type) cache) { - T r; - T theta; - if (abs(u.x) > abs(u.y)) + T theta = hlsl::atan2(p.y, p.x); // -pi -> pi + T r = hlsl::sqrt(p.x * p.x + p.y * p.y); + const T PiOver4 = T(0.25) * numbers::pi; + + vector u; + // TODO: should reduce branching somehow? + if (hlsl::abs(theta) < PiOver4 || hlsl::abs(theta) > 3 * PiOver4) { - r = u.x; - theta = 0.25 * numbers::pi * (u.y / u.x); + r = ieee754::copySign(r, p.x); + u.x = r; + if (p.x < 0) + { + if (p.y < 0) + { + u.y = (numbers::pi + theta) * r / PiOver4; + } + else + { + u.y = (theta - numbers::pi)*r / PiOver4; + } + } + else + { + u.y = (theta * r) / PiOver4; + } } else { - r = u.y; - theta = 0.5 * numbers::pi - 0.25 * numbers::pi * (u.x / u.y); + r = ieee754::copySign(r, p.y); + u.y = r; + if (p.y < 0) + { + u.x = -(T(0.5) * numbers::pi + theta) * r / PiOver4; + } + else + { + u.x = (T(0.5) * numbers::pi - theta) * r / PiOver4; + } } - p = r * vector(cos(theta), sin(theta)); + return (u + hlsl::promote >(1.0)) * T(0.5); } - return p; -} + static domain_type generateInverse(const codomain_type p) + { + cache_type dummy; + return generateInverse(p, dummy); + } -template -vector invertConcentricMapping(const vector p) -{ - T theta = hlsl::atan2(p.y, p.x); // -pi -> pi - T r = hlsl::sqrt(p.x * p.x + p.y * p.y); - const T PiOver4 = T(0.25) * numbers::pi; + // The PDF of Shirley mapping is constant (1/PI on the unit disk) + static density_type forwardPdf(cache_type cache) { return numbers::inv_pi; } + static density_type backwardPdf(codomain_type v) { return numbers::inv_pi; } - vector u; - // TODO: should reduce branching somehow? - if (hlsl::abs(theta) < PiOver4 || hlsl::abs(theta) > 3 * PiOver4) - { - r = ieee754::copySign(r, p.x); - u.x = r; - if (p.x < 0) { - if (p.y < 0) { - u.y = (numbers::pi + theta) * r / PiOver4; - } else { - u.y = (theta - numbers::pi) * r / PiOver4; - } - } else { - u.y = (theta * r) / PiOver4; - } - } - else - { - r = ieee754::copySign(r, p.y); - u.y = r; - if (p.y < 0) { - u.x = -(T(0.5) * numbers::pi + theta) * r / PiOver4; - } else { - u.x = (T(0.5) * numbers::pi - theta) * r / PiOver4; - } - } - - return (u + hlsl::promote >(1.0)) * T(0.5); -} + static weight_type forwardWeight(cache_type cache) { return forwardPdf(cache); } + static weight_type backwardWeight(codomain_type v) { return backwardPdf(v); } +}; } // namespace sampling } // namespace hlsl diff --git a/include/nbl/builtin/hlsl/sampling/concepts.hlsl b/include/nbl/builtin/hlsl/sampling/concepts.hlsl index a408c0beae..537d87bcd8 100644 --- a/include/nbl/builtin/hlsl/sampling/concepts.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concepts.hlsl @@ -107,20 +107,26 @@ NBL_CONCEPT_END( // ============================================================================ // TractableSampler // -// A _sampler whose density can be computed analytically in the forward -// (sampling) direction. The generate method returns the sample bundled -// with its density to avoid redundant computation. +// A sampler whose density can be computed analytically in the forward +// (sampling) direction. generate returns a codomain_type value and writes +// intermediates to a cache_type out-param for later pdf evaluation. +// +// The cache_type out-param stores intermediates computed during generate +// (e.g. DG1 in Cook-Torrance, or simply the pdf for simple samplers) for +// reuse by forwardPdf without redundant recomputation. +// +// For constant-pdf samplers, forwardPdf(cache) == __pdf() (cache ignored). +// For variable-pdf samplers (e.g. Linear), forwardPdf(cache) returns the +// pre-computed pdf rather than re-evaluating __pdf(x) from the sample value. +// For complex samplers (e.g. Cook-Torrance), cache carries DG1/Fresnel and +// forwardPdf computes the pdf from those stored intermediates. // // Required types: -// domain_type - the input space -// codomain_type - the output space -// density_type - the density type -// sample_type - bundled return of generate, must satisfy -// SampleWithDensity (i.e. SampleWithPDF or SampleWithRcpPDF) +// domain_type, codomain_type, density_type, cache_type // // Required methods: -// sample_type generate(domain_type u) - sample + density -// density_type forwardPdf(domain_type u) - density only +// codomain_type generate(domain_type u, out cache_type cache) +// density_type forwardPdf(cache_type cache) // ============================================================================ // clang-format off @@ -129,17 +135,19 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (_sampler, T) #define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) -NBL_CONCEPT_BEGIN(2) +#define NBL_CONCEPT_PARAM_2 (cache, typename T::cache_type) +NBL_CONCEPT_BEGIN(3) #define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) ((NBL_CONCEPT_REQ_TYPE)(T::density_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) - ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(SampleWithDensity, typename T::sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.forwardPdf(u)), ::nbl::hlsl::is_same_v, typename T::density_type))); + ((NBL_CONCEPT_REQ_TYPE)(T::cache_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.generate(u, cache)), ::nbl::hlsl::is_same_v, typename T::codomain_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.forwardPdf(cache)), ::nbl::hlsl::is_same_v, typename T::density_type))); +#undef cache #undef u #undef _sampler #include @@ -148,20 +156,26 @@ NBL_CONCEPT_END( // ============================================================================ // ResamplableSampler // -// Extends BasicSampler with forward and backward importance weights, enabling -// use in Multiple Importance Sampling (MIS) and Resampled Importance -// Sampling (RIS). +// A sampler with forward and backward importance weights, enabling use in +// Multiple Importance Sampling (MIS) and Resampled Importance Sampling (RIS). // // Note: resampling does not require tractability - the weights need not be // normalized probability densities, so this concept is satisfied by // intractable samplers as well. // -// Required types (in addition to BasicSampler): -// weight_type - the type of the importance weight +// Unlike TractableSampler, generate returns bare codomain_type (not sample_type) +// and writes a cache_type out-param for later reuse by forwardWeight. // -// Required methods (in addition to BasicSampler): -// weight_type forwardWeight(domain_type u) - forward weight for MIS -// weight_type backwardWeight(codomain_type v) - backward weight for RIS +// Required types: +// domain_type - the input space +// codomain_type - the output space +// cache_type - stores intermediates from generate for forward weight reuse +// weight_type - the type of the importance weight +// +// Required methods: +// codomain_type generate(domain_type u, out cache_type cache) +// weight_type forwardWeight(cache_type cache) - forward weight for MIS +// weight_type backwardWeight(codomain_type v) - backward weight for RIS // ============================================================================ // clang-format off @@ -171,15 +185,21 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_PARAM_0 (_sampler, T) #define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) #define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) -NBL_CONCEPT_BEGIN(3) +#define NBL_CONCEPT_PARAM_3 (cache, typename T::cache_type) +NBL_CONCEPT_BEGIN(4) #define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(BasicSampler, T)) + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::cache_type)) ((NBL_CONCEPT_REQ_TYPE)(T::weight_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(u)), ::nbl::hlsl::is_same_v, typename T::weight_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.generate(u, cache)), ::nbl::hlsl::is_same_v, typename T::codomain_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(cache)), ::nbl::hlsl::is_same_v, typename T::weight_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardWeight(v)), ::nbl::hlsl::is_same_v, typename T::weight_type))); +#undef cache #undef v #undef u #undef _sampler @@ -198,10 +218,13 @@ NBL_CONCEPT_END( // For an invertible sampler these are just the forward and backward PDFs, // but the names signal the intended use at call sites. // +// Required types (in addition to TractableSampler): +// weight_type - the type of the importance weight +// // Required methods (in addition to TractableSampler): -// density_type backwardPdf(codomain_type v) -// density_type forwardWeight(domain_type u) - weight for MIS -// density_type backwardWeight(codomain_type v) - weight for RIS +// density_type backwardPdf(codomain_type v) - evaluate pdf at codomain value v +// weight_type forwardWeight(cache_type cache) - weight for MIS, reuses generate cache +// weight_type backwardWeight(codomain_type v) - weight for RIS, evaluated at v // ============================================================================ // clang-format off @@ -211,15 +234,19 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_PARAM_0 (_sampler, T) #define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) #define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) -NBL_CONCEPT_BEGIN(3) +#define NBL_CONCEPT_PARAM_3 (cache, typename T::cache_type) +NBL_CONCEPT_BEGIN(4) #define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(TractableSampler, T)) + ((NBL_CONCEPT_REQ_TYPE)(T::weight_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(u)), ::nbl::hlsl::is_same_v, typename T::density_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardWeight(v)), ::nbl::hlsl::is_same_v, typename T::density_type))); + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(cache)), ::nbl::hlsl::is_same_v, typename T::weight_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardWeight(v)), ::nbl::hlsl::is_same_v, typename T::weight_type))); +#undef cache #undef v #undef u #undef _sampler @@ -230,22 +257,16 @@ NBL_CONCEPT_END( // BijectiveSampler // // The mapping domain <-> codomain is bijective (1:1), so it can be -// inverted. Extends InvertibleSampler with invertGenerate. +// inverted. Extends InvertibleSampler with generateInverse. // // Because the mapping is bijective, the absolute value of the determinant // of the Jacobian matrix of the inverse equals the reciprocal of the // absolute value of the determinant of the Jacobian matrix of the forward // mapping (the Jacobian is an NxM matrix, not a scalar): -// backwardPdf(v) == 1.0 / forwardPdf(invertGenerate(v).value()) -// -// Required types (in addition to InvertibleSampler): -// inverse_sample_type - bundled return of invertGenerate, should be -// one of: -// domain_and_rcpPdf (preferred) -// domain_and_pdf +// backwardPdf(v) == 1.0 / forwardPdf(cache) (where v == generate(u, cache).value()) // // Required methods (in addition to InvertibleSampler): -// inverse_sample_type invertGenerate(codomain_type v) +// domain_type generateInverse(codomain_type v, out cache_type cache) // ============================================================================ // clang-format off @@ -254,14 +275,15 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (_sampler, T) #define NBL_CONCEPT_PARAM_1 (v, typename T::codomain_type) -NBL_CONCEPT_BEGIN(2) +#define NBL_CONCEPT_PARAM_2 (cache, typename T::cache_type) +NBL_CONCEPT_BEGIN(3) #define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(InvertibleSampler, T)) - ((NBL_CONCEPT_REQ_TYPE)(T::inverse_sample_type)) - ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(SampleWithDensity, typename T::inverse_sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.invertGenerate(v)), ::nbl::hlsl::is_same_v, typename T::inverse_sample_type))); + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.generateInverse(v, cache)), ::nbl::hlsl::is_same_v, typename T::domain_type))); +#undef cache #undef v #undef _sampler #include diff --git a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl index f937cf783a..85dd962397 100644 --- a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl @@ -8,7 +8,6 @@ #include "nbl/builtin/hlsl/concepts.hlsl" #include "nbl/builtin/hlsl/sampling/concentric_mapping.hlsl" #include "nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl" -#include "nbl/builtin/hlsl/sampling/value_and_pdf.hlsl" namespace nbl { @@ -28,56 +27,78 @@ struct ProjectedHemisphere using domain_type = vector_t2; using codomain_type = vector_t3; using density_type = T; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; + using weight_type = density_type; - static vector_t3 __generate(const vector_t2 _sample) + struct cache_type { - vector_t2 p = concentricMapping(_sample * T(0.99999) + T(0.000005)); + density_type pdf; + }; + + static codomain_type __generate(const domain_type _sample) + { + vector_t2 p = ConcentricMapping::generate(_sample * T(0.99999) + T(0.000005)); T z = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - p.x * p.x - p.y * p.y)); return vector_t3(p.x, p.y, z); } - vector_t3 generate(const vector_t2 _sample) + static codomain_type generate(const domain_type _sample, NBL_REF_ARG(cache_type) cache) { - return __generate(_sample); + const codomain_type L = __generate(_sample); + cache.pdf = __pdf(L.z); + return L; } - static vector_t2 __generateInverse(const vector_t3 L) + static domain_type __generateInverse(const codomain_type L) { - return invertConcentricMapping(L.xy); + return ConcentricMapping::generateInverse(L.xy); } - vector_t2 generateInverse(const vector_t3 L) + static domain_type generateInverse(const codomain_type L, NBL_REF_ARG(cache_type) cache) { + cache.pdf = __pdf(L.z); return __generateInverse(L); } static T __pdf(const T L_z) { - return L_z * numbers::inv_pi; + return L_z * numbers::inv_pi; + } + + static scalar_type pdf(const T L_z) + { + return __pdf(L_z); } - scalar_type forwardPdf(const vector_t2 _sample) + static density_type forwardPdf(const cache_type cache) { - return __pdf(__generate(_sample).z); + return cache.pdf; } - scalar_type backwardPdf(const vector_t3 L) + static weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + static density_type backwardPdf(const codomain_type L) { return __pdf(L.z); } + static weight_type backwardWeight(const codomain_type L) + { + return backwardPdf(L); + } + template > static quotient_and_pdf quotientAndPdf(const T L) { - return quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L)); } template > static quotient_and_pdf quotientAndPdf(const vector_t3 L) { - return quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L.z)); } }; @@ -93,10 +114,14 @@ struct ProjectedSphere using domain_type = vector_t3; using codomain_type = vector_t3; using density_type = T; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; + using weight_type = density_type; - static vector_t3 __generate(NBL_REF_ARG(vector_t3) _sample) + struct cache_type + { + density_type pdf; + }; + + static codomain_type __generate(NBL_REF_ARG(domain_type) _sample) { vector_t3 retval = hemisphere_t::__generate(_sample.xy); const bool chooseLower = _sample.z > T(0.5); @@ -107,48 +132,66 @@ struct ProjectedSphere return retval; } - vector_t3 generate(NBL_REF_ARG(vector_t3) _sample) + static codomain_type generate(NBL_REF_ARG(domain_type) _sample, NBL_REF_ARG(cache_type) cache) { - return __generate(_sample); + const codomain_type L = __generate(_sample); + cache.pdf = __pdf(L.z); + return L; } - static vector_t3 __generateInverse(const vector_t3 L) + static domain_type __generateInverse(const codomain_type L) { - // TODO: incomplete information to get z component, we only know mapping of (u.z > 0.5 <-> L +ve) and (u.z < 0.5 <-> L -ve) - // so set to 0 or 1 for now - return vector_t3(hemisphere_t::__generateInverse(L.xy), hlsl::mix(T(0.0), T(1.0), L.z > T(0.0))); + // NOTE: incomplete information to recover exact z component; we only know which hemisphere L came from, + // so we return a canonical value (0.0 for upper, 1.0 for lower) that round-trips correctly through __generate + return vector_t3(hemisphere_t::__generateInverse(L), hlsl::mix(T(1.0), T(0.0), L.z > T(0.0))); } - vector_t3 generateInverse(const vector_t3 L) + static domain_type generateInverse(const codomain_type L, NBL_REF_ARG(cache_type) cache) { + cache.pdf = __pdf(L.z); return __generateInverse(L); } static T __pdf(T L_z) { - return T(0.5) * hemisphere_t::__pdf(L_z); + return T(0.5) * hemisphere_t::__pdf(hlsl::abs(L_z)); + } + + static scalar_type pdf(T L_z) + { + return __pdf(L_z); } - scalar_type forwardPdf(const vector_t2 _sample) + static density_type forwardPdf(const cache_type cache) { - return __pdf(__generate(_sample).z); + return cache.pdf; } - scalar_type backwardPdf(const vector_t3 L) + static weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + static density_type backwardPdf(const codomain_type L) { return __pdf(L.z); } + static weight_type backwardWeight(const codomain_type L) + { + return backwardPdf(L); + } + template > static quotient_and_pdf quotientAndPdf(T L) { - return quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L)); } template > static quotient_and_pdf quotientAndPdf(const vector_t3 L) { - return quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L.z)); } }; diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index 0413876c02..1de23eec5d 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -7,7 +7,6 @@ #include #include -#include namespace nbl { @@ -26,8 +25,12 @@ struct Linear using domain_type = scalar_type; using codomain_type = scalar_type; using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + }; static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end), assumed to be at x=0 and x=1 { @@ -42,26 +45,44 @@ struct Linear return retval; } - scalar_type generate(const scalar_type u) + density_type __pdf(const codomain_type x) { - return hlsl::mix(u, (linearCoeffStart - hlsl::sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, hlsl::abs(rcpDiff) < numeric_limits::max); + if (x < scalar_type(0.0) || x > scalar_type(1.0)) + return scalar_type(0.0); + return scalar_type(2.0) * (linearCoeffStart + x * linearCoeffDiff) * rcpCoeffSum; } - scalar_type generateInverse(const scalar_type x) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { + const codomain_type x = hlsl::mix(u, (linearCoeffStart - sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, abs(rcpDiff) < hlsl::numeric_limits::max); + cache.pdf = __pdf(x); + return x; + } + + domain_type generateInverse(const codomain_type x, NBL_REF_ARG(cache_type) cache) + { + cache.pdf = __pdf(x); return x * (scalar_type(2.0) * linearCoeffStart + linearCoeffDiff * x) * rcpCoeffSum; } - scalar_type forwardPdf(const scalar_type u) + density_type forwardPdf(const cache_type cache) { - return backwardPdf(generate(u)); + return cache.pdf; } - scalar_type backwardPdf(const scalar_type x) + weight_type forwardWeight(const cache_type cache) { - if (x < scalar_type(0.0) || x > scalar_type(1.0)) - return scalar_type(0.0); - return scalar_type(2.0) * (linearCoeffStart + x * linearCoeffDiff) * rcpCoeffSum; + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type x) + { + return __pdf(x); + } + + weight_type backwardWeight(const codomain_type x) + { + return backwardPdf(x); } scalar_type linearCoeffStart; diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index 577a4895c3..2639bc7610 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -10,7 +10,6 @@ #include #include #include -#include namespace nbl { @@ -27,49 +26,66 @@ struct ProjectedSphericalTriangle using vector3_type = vector; using vector4_type = vector; - // ResamplableSampler concept types + // InvertibleSampler concept types using domain_type = vector2_type; using codomain_type = vector3_type; using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; + using weight_type = density_type; + struct cache_type + { + density_type pdf; + }; + + // NOTE: produces a degenerate (all-zero) bilinear patch when the receiver normal faces away + // from all three triangle vertices, resulting in NaN PDFs (0 * inf). Callers must ensure + // at least one vertex has positive projection onto the receiver normal. Bilinear computeBilinearPatch() { const scalar_type minimumProjSolidAngle = 0.0; - matrix m = matrix(sphtri.tri.vertices[0], sphtri.tri.vertices[1], sphtri.tri.vertices[2]); + matrix m = matrix(sphtri.tri_vertices[0], sphtri.tri_vertices[1], sphtri.tri_vertices[2]); const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(receiverWasBSDF, hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); return Bilinear::create(bxdfPdfAtVertex.yyxz); } - vector3_type generate(const vector2_type u) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { - vector2_type u; - // pre-warp according to proj solid angle approximation Bilinear bilinear = computeBilinearPatch(); - u = bilinear.generate(_u); - - // now warp the points onto a spherical triangle - const vector3_type L = sphtri.generate(u); + typename Bilinear::cache_type bilinearCache; + const vector2_type warped = bilinear.generate(u, bilinearCache); + typename SphericalTriangle::cache_type sphtriCache; + const vector3_type L = sphtri.generate(warped, sphtriCache); + // combined weight: sphtri pdf (1/solidAngle) * bilinear pdf at u + cache.pdf = sphtri.forwardPdf(sphtriCache) * bilinear.forwardPdf(bilinearCache); return L; } - scalar_type forwardPdf(const vector2_type u) + density_type forwardPdf(const cache_type cache) { - const scalar_type pdf = sphtri.forwardPdf(u); - Bilinear bilinear = computeBilinearPatch(); - return pdf * bilinear.backwardPdf(u); + return cache.pdf; } - scalar_type backwardPdf(const vector3_type L) + weight_type forwardWeight(const cache_type cache) { - const scalar_type pdf = sphtri.backwardPdf(L); - const vector2_type u = sphtri.generateInverse(L); + return forwardPdf(cache); + } + + density_type backwardPdf(const vector3_type L) + { + const density_type pdf = sphtri.backwardPdf(L); + typename SphericalTriangle::cache_type dummyCache; + const vector2_type u = sphtri.generateInverse(L, dummyCache); Bilinear bilinear = computeBilinearPatch(); return pdf * bilinear.backwardPdf(u); } + weight_type backwardWeight(const vector3_type L) + { + return backwardPdf(L); + } + sampling::SphericalTriangle sphtri; vector3_type receiverNormal; bool receiverWasBSDF; diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index 40aca59c9d..afa805150b 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -9,7 +9,6 @@ #include #include #include -#include namespace nbl { @@ -26,18 +25,23 @@ struct SphericalRectangle using vector3_type = vector; using vector4_type = vector; - // ResamplableSampler concept types + // InvertibleSampler concept types using domain_type = vector2_type; using codomain_type = vector2_type; using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + }; NBL_CONSTEXPR_STATIC_INLINE scalar_type ClampEps = 1e-5; static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect, const vector3_type observer) { SphericalRectangle retval; - + retval.r0 = hlsl::mul(rect.basis, rect.origin - observer); const vector4_type denorm_n_z = vector4_type(-retval.r0.y, retval.r0.x + rect.extents.x, retval.r0.y + rect.extents.y, -retval.r0.x); const vector4_type n_z = denorm_n_z / hlsl::sqrt(hlsl::promote(retval.r0.z * retval.r0.z) + denorm_n_z * denorm_n_z); @@ -48,18 +52,18 @@ struct SphericalRectangle -n_z[3] * n_z[0] ); - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[0]); - angle_adder.addCosine(cosGamma[1]); + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(retval.cosGamma[0]); + angle_adder.addCosine(retval.cosGamma[1]); scalar_type p = angle_adder.getSumofArccos(); - angle_adder = math::sincos_accumulator::create(cosGamma[2]); - angle_adder.addCosine(cosGamma[3]); + angle_adder = math::sincos_accumulator::create(retval.cosGamma[2]); + angle_adder.addCosine(retval.cosGamma[3]); scalar_type q = angle_adder.getSumofArccos(); const scalar_type k = scalar_type(2.0) * numbers::pi - q; retval.solidAngle = p + q - scalar_type(2.0) * numbers::pi; // flip z axis if r0.z > 0 - retval.r0 = -hlsl::abs(retval.r0.z); + retval.r0 = hlsl::promote(-hlsl::abs(retval.r0.z)); retval.r1 = retval.r0 + vector3_type(rect.extents.x, rect.extents.y, 0); retval.b0 = n_z[0]; @@ -67,7 +71,7 @@ struct SphericalRectangle return retval; } - vector2_type generate(const vector2_type u) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[2]); angle_adder.addCosine(cosGamma[3]); @@ -90,19 +94,31 @@ struct SphericalRectangle const scalar_type hv2 = hv * hv; const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - ClampEps); + cache.pdf = scalar_type(1.0) / solidAngle; + return vector2_type((xu - r0.x), (yv - r0.y)); } - scalar_type forwardPdf(const vector2_type u) + density_type forwardPdf(const cache_type cache) { - return scalar_type(1.0) / solidAngle; + return cache.pdf; } - scalar_type backwardPdf(const vector2_type L) + weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type L) { return scalar_type(1.0) / solidAngle; } + weight_type backwardWeight(const codomain_type L) + { + return backwardPdf(L); + } + scalar_type solidAngle; vector4_type cosGamma; scalar_type b0; diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index de4d185818..84c8dc207e 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -10,7 +10,6 @@ #include #include #include -#include namespace nbl { @@ -25,29 +24,37 @@ struct SphericalTriangle using scalar_type = T; using vector2_type = vector; using vector3_type = vector; + using uint_type = unsigned_integer_of_size_t; // BijectiveSampler concept types using domain_type = vector2_type; using codomain_type = vector3_type; using density_type = scalar_type; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + }; static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) { SphericalTriangle retval; vector3_type cos_vertices, sin_vertices; - retval.solidAngle = tri.solidAngle(cos_vertices, sin_vertices); + shapes::SphericalTriangle tri_mut = tri; + retval.solidAngle = tri_mut.solidAngle(cos_vertices, sin_vertices); retval.cosA = cos_vertices[0]; retval.sinA = sin_vertices[0]; - retval.tri_vertices = tri.vertices; + retval.tri_vertices[0] = tri.vertices[0]; + retval.tri_vertices[1] = tri.vertices[1]; + retval.tri_vertices[2] = tri.vertices[2]; retval.triCosC = tri.cos_sides[2]; retval.triCscB = tri.csc_sides[1]; retval.triCscC = tri.csc_sides[2]; return retval; } - vector3_type generate(const vector2_type u) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { scalar_type negSinSubSolidAngle, negCosSubSolidAngle; math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); @@ -73,14 +80,17 @@ struct SphericalTriangle const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); if (csc_b_s < numeric_limits::max) { - const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); - if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) + const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(scalar_type(1.0) + cosBC_s * u.y - u.y, scalar_type(-1.0), scalar_type(1.0)); + if (nbl::hlsl::abs(cosAngleAlongBC_s) < scalar_type(1.0)) retval += math::quaternion::slerp_delta(tri_vertices[1], C_s * csc_b_s, cosAngleAlongBC_s); } + + cache.pdf = scalar_type(1.0) / solidAngle; + return retval; } - vector2_type generateInverse(const vector3_type L) + domain_type _generateInverse(const codomain_type L) { const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri_vertices[1]); const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); @@ -95,31 +105,46 @@ struct SphericalTriangle math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosA, sinA); angle_adder.addAngle(cosB_, sinB_); angle_adder.addAngle(cosC_, sinC_); - const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi)*pdf; + const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi) * (scalar_type(1.0) / solidAngle); const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; - const scalar_type sinBsinC = sinB_ * sinC_; + const scalar_type sinBC_s_product = sinB_ * sinC_; // 1 ULP below 1.0, ensures (1.0 - cosBC_s) is strictly positive in float const scalar_type one_below_one = bit_cast(bit_cast(scalar_type(1)) - uint_type(1)); - const scalar_type cosBC_s_raw = (cosA + cosB_ * cosC_) / sinBsinC; - const scalar_type cosBC_s = sinBsinC > scalar_type(1e-7) ? cosBC_s_raw : cos_c; - const scalar_type v_denom = scalar_type(1.0) - (cosBC_s < one_below_one ? cosBC_s : cos_c); + const scalar_type cosBC_s = sinBC_s_product > numeric_limits::min ? (cosA + cosB_ * cosC_) / sinBC_s_product : triCosC; + const scalar_type v_denom = scalar_type(1.0) - (cosBC_s < one_below_one ? cosBC_s : triCosC); const scalar_type v = (scalar_type(1.0) - cosAngleAlongBC_s) / v_denom; return vector2_type(u, v); } - scalar_type forwardPdf(const vector2_type u) + domain_type generateInverse(const codomain_type L, NBL_REF_ARG(cache_type) cache) { - return scalar_type(1.0) / solidAngle; + cache.pdf = scalar_type(1.0) / solidAngle; + return _generateInverse(L); + } + + density_type forwardPdf(const cache_type cache) + { + return cache.pdf; } - scalar_type backwardPdf(const vector3_type L) + weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type L) { return scalar_type(1.0) / solidAngle; } + weight_type backwardWeight(const codomain_type L) + { + return backwardPdf(L); + } + scalar_type solidAngle; scalar_type cosA; scalar_type sinA; diff --git a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl index 08ef412e4d..605d7e9370 100644 --- a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl @@ -9,7 +9,6 @@ #include "nbl/builtin/hlsl/numbers.hlsl" #include "nbl/builtin/hlsl/tgmath.hlsl" #include "nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl" -#include "nbl/builtin/hlsl/sampling/value_and_pdf.hlsl" namespace nbl { @@ -29,10 +28,14 @@ struct UniformHemisphere using domain_type = vector_t2; using codomain_type = vector_t3; using density_type = T; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; + using weight_type = density_type; - static vector_t3 __generate(const vector_t2 _sample) + struct cache_type + { + density_type pdf; + }; + + static codomain_type __generate(const domain_type _sample) { T z = _sample.x; T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); @@ -40,12 +43,13 @@ struct UniformHemisphere return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); } - vector_t3 generate(const vector_t2 _sample) + static codomain_type generate(const domain_type _sample, NBL_REF_ARG(cache_type) cache) { + cache.pdf = __pdf(); return __generate(_sample); } - static vector_t2 __generateInverse(const vector_t3 _sample) + static domain_type __generateInverse(const codomain_type _sample) { T phi = hlsl::atan2(_sample.y, _sample.x); const T twopi = T(2.0) * numbers::pi; @@ -53,8 +57,9 @@ struct UniformHemisphere return vector_t2(_sample.z, phi / twopi); } - vector_t2 generateInverse(const vector_t3 _sample) + static domain_type generateInverse(const codomain_type _sample, NBL_REF_ARG(cache_type) cache) { + cache.pdf = __pdf(); return __generateInverse(_sample); } @@ -63,20 +68,30 @@ struct UniformHemisphere return T(1.0) / (T(2.0) * numbers::pi); } - scalar_type forwardPdf(const vector_t2 _sample) + static density_type forwardPdf(const cache_type cache) { - return __pdf(); + return cache.pdf; } - scalar_type backwardPdf(const vector_t3 _sample) + static weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + static density_type backwardPdf(const vector_t3 _sample) { return __pdf(); } + static weight_type backwardWeight(const codomain_type sample) + { + return backwardPdf(sample); + } + template > static quotient_and_pdf quotientAndPdf() { - return quotient_and_pdf::create(hlsl::promote(1.0), pdf()); + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf()); } }; @@ -91,10 +106,14 @@ struct UniformSphere using domain_type = vector_t2; using codomain_type = vector_t3; using density_type = T; - using sample_type = codomain_and_rcpPdf; - using inverse_sample_type = domain_and_rcpPdf; + using weight_type = density_type; - static vector_t3 __generate(const vector_t2 _sample) + struct cache_type + { + density_type pdf; + }; + + static codomain_type __generate(const domain_type _sample) { T z = T(1.0) - T(2.0) * _sample.x; T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); @@ -102,12 +121,13 @@ struct UniformSphere return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); } - vector_t3 generate(const vector_t2 _sample) + static codomain_type generate(const domain_type _sample, NBL_REF_ARG(cache_type) cache) { + cache.pdf = __pdf(); return __generate(_sample); } - static vector_t2 __generateInverse(const vector_t3 _sample) + static domain_type __generateInverse(const codomain_type _sample) { T phi = hlsl::atan2(_sample.y, _sample.x); const T twopi = T(2.0) * numbers::pi; @@ -115,8 +135,9 @@ struct UniformSphere return vector_t2((T(1.0) - _sample.z) * T(0.5), phi / twopi); } - vector_t2 generateInverse(const vector_t3 _sample) + static domain_type generateInverse(const codomain_type _sample, NBL_REF_ARG(cache_type) cache) { + cache.pdf = __pdf(); return __generateInverse(_sample); } @@ -125,20 +146,30 @@ struct UniformSphere return T(1.0) / (T(4.0) * numbers::pi); } - scalar_type forwardPdf(const vector_t2 _sample) + static density_type forwardPdf(const cache_type cache) { - return __pdf(); + return cache.pdf; } - scalar_type backwardPdf(const vector_t3 _sample) + static weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + static density_type backwardPdf(const vector_t3 _sample) { return __pdf(); } + static weight_type backwardWeight(const codomain_type sample) + { + return backwardPdf(sample); + } + template > static quotient_and_pdf quotientAndPdf() { - return quotient_and_pdf::create(hlsl::promote(1.0), pdf()); + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf()); } }; } // namespace sampling From 86fa3f6ef3bfb93d031229598dd654bd4bbaeba7 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Tue, 17 Mar 2026 03:01:20 +0300 Subject: [PATCH 25/33] Added alias table and cumulative prbability builders and samplers --- examples_tests | 2 +- .../builtin/hlsl/sampling/alias_table.hlsl | 114 ++++++++++++++++++ .../hlsl/sampling/cumulative_probability.hlsl | 104 ++++++++++++++++ .../nbl/core/sampling/alias_table_builder.h | 92 ++++++++++++++ .../sampling/cumulative_probability_builder.h | 53 ++++++++ src/nbl/builtin/CMakeLists.txt | 2 + 6 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 include/nbl/builtin/hlsl/sampling/alias_table.hlsl create mode 100644 include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl create mode 100644 include/nbl/core/sampling/alias_table_builder.h create mode 100644 include/nbl/core/sampling/cumulative_probability_builder.h diff --git a/examples_tests b/examples_tests index 793182654b..d9f49070f0 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 793182654b252e5bd7ab9fedb5bb12e2135303d3 +Subproject commit d9f49070f0795d7c584b518d35e4091d089f6084 diff --git a/include/nbl/builtin/hlsl/sampling/alias_table.hlsl b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl new file mode 100644 index 0000000000..1c97f9ff08 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl @@ -0,0 +1,114 @@ +// 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_SAMPLING_ALIAS_TABLE_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_ALIAS_TABLE_INCLUDED_ + +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +// Alias Method (Vose/Walker) discrete sampler. +// +// Samples a discrete index in [0, N) with probability proportional to +// precomputed weights in O(1) time per sample, using a prebuilt alias table. +// +// Template parameters are ReadOnly accessors, each with: +// value_type get(uint32_t i) const; +// +// - ProbabilityAccessor: returns scalar_type threshold in [0, 1] for bin i +// - AliasIndexAccessor: returns uint32_t redirect index for bin i +// - PdfAccessor: returns scalar_type weight[i] / totalWeight +// +// Satisfies TractableSampler (not InvertibleSampler: the mapping is discrete). +// The cache stores the sampled index so forwardPdf can look up the PDF. +template +struct AliasTable +{ + using scalar_type = T; + + using domain_type = scalar_type; + using codomain_type = uint32_t; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type + { + codomain_type sampledIndex; + }; + + static AliasTable create(NBL_CONST_REF_ARG(ProbabilityAccessor) _probAccessor, NBL_CONST_REF_ARG(AliasIndexAccessor) _aliasAccessor, NBL_CONST_REF_ARG(PdfAccessor) _pdfAccessor, uint32_t _size) + { + AliasTable retval; + retval.probAccessor = _probAccessor; + retval.aliasAccessor = _aliasAccessor; + retval.pdfAccessor = _pdfAccessor; + // Precompute tableSize as float minus 1 ULP so that u=1.0 maps to bin N-1 + const scalar_type exact = scalar_type(_size); + retval.tableSizeMinusUlp = nbl::hlsl::bit_cast(nbl::hlsl::bit_cast(exact) - 1u); + return retval; + } + + // BasicSampler interface + codomain_type generate(const domain_type u) + { + const scalar_type scaled = u * tableSizeMinusUlp; + const uint32_t bin = uint32_t(scaled); + const scalar_type remainder = scaled - scalar_type(bin); + + // Use if-statement to avoid select: aliasIndex is a dependent read + codomain_type result; + if (remainder < probAccessor.get(bin)) + result = bin; + else + result = aliasAccessor.get(bin); + + return result; + } + + // TractableSampler interface + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + { + const codomain_type result = generate(u); + cache.sampledIndex = result; + return result; + } + + density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) + { + return pdfAccessor.get(cache.sampledIndex); + } + + weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) + { + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type v) + { + return pdfAccessor.get(v); + } + + weight_type backwardWeight(const codomain_type v) + { + return backwardPdf(v); + } + + ProbabilityAccessor probAccessor; + AliasIndexAccessor aliasAccessor; + PdfAccessor pdfAccessor; + scalar_type tableSizeMinusUlp; +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl + +#endif diff --git a/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl new file mode 100644 index 0000000000..a8cb01b7a1 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl @@ -0,0 +1,104 @@ +// 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_SAMPLING_CUMULATIVE_PROBABILITY_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_CUMULATIVE_PROBABILITY_INCLUDED_ + +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +// Discrete sampler using cumulative probability lookup via upper_bound. +// +// Samples a discrete index in [0, N) with probability proportional to +// precomputed weights in O(log N) time per sample. +// +// The cumulative probability array stores N-1 entries (the last bucket +// is always 1.0 and need not be stored). Entry i holds the sum of +// probabilities for indices [0, i]. +// +// Template parameters are ReadOnly accessors: +// - CumulativeProbabilityAccessor: returns scalar_type cumProb for index i, +// must have `value_type` typedef and `operator[](uint32_t)` for upper_bound +// - PdfAccessor: returns scalar_type weight[i] / totalWeight +// +// Satisfies TractableSampler and ResamplableSampler (not InvertibleSampler: +// the mapping is discrete). +template +struct CumulativeProbabilitySampler +{ + using scalar_type = T; + + using domain_type = scalar_type; + using codomain_type = uint32_t; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type + { + codomain_type sampledIndex; + }; + + static CumulativeProbabilitySampler create(NBL_CONST_REF_ARG(CumulativeProbabilityAccessor) _cumProbAccessor, NBL_CONST_REF_ARG(PdfAccessor) _pdfAccessor, uint32_t _size) + { + CumulativeProbabilitySampler retval; + retval.cumProbAccessor = _cumProbAccessor; + retval.pdfAccessor = _pdfAccessor; + retval.size = _size; + return retval; + } + + // BasicSampler interface + codomain_type generate(const domain_type u) + { + // upper_bound on N-1 stored entries; if u >= all stored values, returns N-1 (the last bucket) + const uint32_t storedCount = size - 1u; + // upper_bound returns first index where cumProb > u + return hlsl::upper_bound(cumProbAccessor, 0u, storedCount, u); + } + + // TractableSampler interface + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + { + const codomain_type result = generate(u); + cache.sampledIndex = result; + return result; + } + + density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) + { + return pdfAccessor.get(cache.sampledIndex); + } + + weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) + { + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type v) + { + return pdfAccessor.get(v); + } + + weight_type backwardWeight(const codomain_type v) + { + return backwardPdf(v); + } + + CumulativeProbabilityAccessor cumProbAccessor; + PdfAccessor pdfAccessor; + uint32_t size; +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl + +#endif diff --git a/include/nbl/core/sampling/alias_table_builder.h b/include/nbl/core/sampling/alias_table_builder.h new file mode 100644 index 0000000000..a0a10f25d8 --- /dev/null +++ b/include/nbl/core/sampling/alias_table_builder.h @@ -0,0 +1,92 @@ +// 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_CORE_SAMPLING_ALIAS_TABLE_BUILDER_H_INCLUDED_ +#define _NBL_CORE_SAMPLING_ALIAS_TABLE_BUILDER_H_INCLUDED_ + +#include + +namespace nbl +{ +namespace core +{ +namespace sampling +{ + +// Builds the alias table from an array of non-negative weights. +// All output arrays must be pre-allocated to N entries. +// +// Parameters: +// weights - input weights (non-negative, at least one must be > 0) +// N - number of entries +// outProbability - [out] alias table probability threshold per bin, in [0, 1] +// outAlias - [out] alias redirect index per bin +// outPdf - [out] normalized PDF per entry: weight[i] / sum(weights) +// workspace - scratch buffer of N uint32_t entries +template +struct AliasTableBuilder +{ + static void build(const T* weights, uint32_t N, T* outProbability, uint32_t* outAlias, T* outPdf, uint32_t* workspace) + { + T totalWeight = T(0); + for (uint32_t i = 0; i < N; i++) + totalWeight += weights[i]; + + const T rcpTotalWeight = T(1) / totalWeight; + + // Compute PDFs, scaled probabilities, and partition into small/large in one pass + uint32_t smallEnd = 0; + uint32_t largeBegin = N; + for (uint32_t i = 0; i < N; i++) + { + outPdf[i] = weights[i] * rcpTotalWeight; + outProbability[i] = outPdf[i] * T(N); + + if (outProbability[i] < T(1)) + workspace[smallEnd++] = i; + else + workspace[--largeBegin] = i; + } + + // Pair small and large entries + while (smallEnd > 0 && largeBegin < N) + { + const uint32_t s = workspace[--smallEnd]; + const uint32_t l = workspace[largeBegin]; + + outAlias[s] = l; + // outProbability[s] already holds the correct probability for bin s + + outProbability[l] -= (T(1) - outProbability[s]); + + if (outProbability[l] < T(1)) + { + // l became small: pop from large, push to small + largeBegin++; + workspace[smallEnd++] = l; + } + // else l stays in large (don't pop, reuse next iteration) + } + + // Remaining entries (floating point rounding artifacts) + while (smallEnd > 0) + { + const uint32_t s = workspace[--smallEnd]; + outProbability[s] = T(1); + outAlias[s] = s; + } + while (largeBegin < N) + { + const uint32_t l = workspace[largeBegin++]; + outProbability[l] = T(1); + outAlias[l] = l; + } + } +}; + +} // namespace sampling +} // namespace core +} // namespace nbl + +#endif diff --git a/include/nbl/core/sampling/cumulative_probability_builder.h b/include/nbl/core/sampling/cumulative_probability_builder.h new file mode 100644 index 0000000000..fe2191bb7a --- /dev/null +++ b/include/nbl/core/sampling/cumulative_probability_builder.h @@ -0,0 +1,53 @@ +// 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_CORE_SAMPLING_CUMULATIVE_PROBABILITY_BUILDER_H_INCLUDED_ +#define _NBL_CORE_SAMPLING_CUMULATIVE_PROBABILITY_BUILDER_H_INCLUDED_ + +#include + +namespace nbl +{ +namespace core +{ +namespace sampling +{ + +// Builds the CDF and PDF arrays from an array of non-negative weights. +// +// Parameters: +// weights - input weights (non-negative, at least one must be > 0) +// N - number of entries +// outCumProb - [out] cumulative probability array, N-1 entries +// (last bucket implicitly 1.0) +// outPdf - [out] normalized PDF per entry: weight[i] / sum(weights), N entries +template +struct CumulativeProbabilityBuilder +{ + static void build(const T* weights, uint32_t N, T* outCumProb, T* outPdf) + { + T totalWeight = T(0); + for (uint32_t i = 0; i < N; i++) + totalWeight += weights[i]; + + const T rcpTotalWeight = T(1) / totalWeight; + + for (uint32_t i = 0; i < N; i++) + outPdf[i] = weights[i] * rcpTotalWeight; + + // N-1 stored entries (last bucket is implicitly 1.0) + T cumulative = T(0); + for (uint32_t i = 0; i < N - 1; i++) + { + cumulative += outPdf[i]; + outCumProb[i] = cumulative; + } + } +}; + +} // namespace sampling +} // namespace core +} // namespace nbl + +#endif diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index da8a6c9fa1..839c5e3778 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -283,6 +283,8 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/quotient_and_pdf.hls LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/value_and_pdf.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/concepts.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/uniform_spheres.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/alias_table.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/cumulative_probability.hlsl") # LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/ndarray_addressing.hlsl") # From 254404b5cfa69581820833c5886dee5835a0bb42 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Tue, 17 Mar 2026 16:56:42 +0300 Subject: [PATCH 26/33] update examples_tests --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 69fe7598e2..8f3c1c9de2 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 69fe7598e24227334f26b55d2847af834f00209c +Subproject commit 8f3c1c9de246cedbec4fdfb87a8b620af54e37e4 From 1fb987a265198477a6d870d56da672a37d99d66e Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Wed, 18 Mar 2026 22:07:34 +0300 Subject: [PATCH 27/33] addressing comments in concepts.hlsl --- examples_tests | 2 +- include/nbl/builtin/hlsl/bxdf/concepts.hlsl | 81 +++++++++++++++ .../nbl/builtin/hlsl/sampling/bilinear.hlsl | 9 +- .../hlsl/sampling/concentric_mapping.hlsl | 8 +- .../nbl/builtin/hlsl/sampling/concepts.hlsl | 99 +++---------------- .../hlsl/sampling/cos_weighted_spheres.hlsl | 6 +- include/nbl/builtin/hlsl/sampling/linear.hlsl | 3 +- .../projected_spherical_triangle.hlsl | 3 +- .../hlsl/sampling/spherical_triangle.hlsl | 3 +- .../hlsl/sampling/uniform_spheres.hlsl | 36 +++---- 10 files changed, 125 insertions(+), 125 deletions(-) create mode 100644 include/nbl/builtin/hlsl/bxdf/concepts.hlsl diff --git a/examples_tests b/examples_tests index 8f3c1c9de2..24baa877d2 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 8f3c1c9de246cedbec4fdfb87a8b620af54e37e4 +Subproject commit 24baa877d25cf8eaf0461ba0bce371a4bad57537 diff --git a/include/nbl/builtin/hlsl/bxdf/concepts.hlsl b/include/nbl/builtin/hlsl/bxdf/concepts.hlsl new file mode 100644 index 0000000000..f28fe96755 --- /dev/null +++ b/include/nbl/builtin/hlsl/bxdf/concepts.hlsl @@ -0,0 +1,81 @@ +#ifndef _NBL_BUILTIN_HLSL_BXDF_CONCEPTS_INCLUDED_ +#define _NBL_BUILTIN_HLSL_BXDF_CONCEPTS_INCLUDED_ + +#include + +namespace nbl +{ +namespace hlsl +{ +namespace bxdf +{ +namespace concepts +{ + +// ============================================================================ +// SampleWithPDF +// +// Checks that a sample type bundles a value with its PDF. +// +// Required methods: +// value() - the sampled value +// pdf() - the probability density +// +// Satisfied by: codomain_and_pdf, domain_and_pdf, quotient_and_pdf +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME SampleWithPDF +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (s, T) +NBL_CONCEPT_BEGIN(1) +#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_EXPR)(s.pdf())) + ((NBL_CONCEPT_REQ_EXPR)(s.value()))); +#undef s +#include +// clang-format on + +// ============================================================================ +// SampleWithRcpPDF +// +// Checks that a sample type bundles a value with its reciprocal PDF. +// +// Required methods: +// value() - the sampled value +// rcpPdf() - the reciprocal probability density +// +// Satisfied by: codomain_and_rcpPdf, domain_and_rcpPdf +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME SampleWithRcpPDF +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (s, T) +NBL_CONCEPT_BEGIN(1) +#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_EXPR)(s.rcpPdf())) + ((NBL_CONCEPT_REQ_EXPR)(s.value()))); +#undef s +#include +// clang-format on + +// ============================================================================ +// SampleWithDensity +// +// A sample type that bundles a value with either its PDF or reciprocal PDF. +// This is the disjunction of SampleWithPDF and SampleWithRcpPDF. +// ============================================================================ +template +NBL_BOOL_CONCEPT SampleWithDensity = SampleWithPDF || SampleWithRcpPDF; + +} // namespace concepts +} // namespace bxdf +} // namespace hlsl +} // namespace nbl + +#endif // _NBL_BUILTIN_HLSL_BXDF_CONCEPTS_INCLUDED_ diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index 74b93c831d..b676269714 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -61,17 +61,14 @@ struct Bilinear return p; } - domain_type generateInverse(const codomain_type p, NBL_REF_ARG(cache_type) cache) + domain_type generateInverse(const codomain_type p) { - typename Linear::cache_type linearCache; - vector2_type u; const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); Linear linearx = Linear::create(ySliceEndPoints); - u.x = linearx.generateInverse(p.x, linearCache); - u.y = lineary.generateInverse(p.y, linearCache); + u.x = linearx.generateInverse(p.x); + u.y = lineary.generateInverse(p.y); - cache.pdf = backwardPdf(p); return u; } diff --git a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl index abede02ed6..b94c4fc561 100644 --- a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl @@ -73,7 +73,7 @@ struct ConcentricMapping return generate(_u, dummy); } - static domain_type generateInverse(const codomain_type p, NBL_REF_ARG(cache_type) cache) + static domain_type generateInverse(const codomain_type p) { T theta = hlsl::atan2(p.y, p.x); // -pi -> pi T r = hlsl::sqrt(p.x * p.x + p.y * p.y); @@ -118,12 +118,6 @@ struct ConcentricMapping return (u + hlsl::promote >(1.0)) * T(0.5); } - static domain_type generateInverse(const codomain_type p) - { - cache_type dummy; - return generateInverse(p, dummy); - } - // The PDF of Shirley mapping is constant (1/PI on the unit disk) static density_type forwardPdf(cache_type cache) { return numbers::inv_pi; } static density_type backwardPdf(codomain_type v) { return numbers::inv_pi; } diff --git a/include/nbl/builtin/hlsl/sampling/concepts.hlsl b/include/nbl/builtin/hlsl/sampling/concepts.hlsl index 537d87bcd8..4bd129f732 100644 --- a/include/nbl/builtin/hlsl/sampling/concepts.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concepts.hlsl @@ -12,67 +12,6 @@ namespace sampling namespace concepts { -// ============================================================================ -// SampleWithPDF -// -// Checks that a sample type bundles a value with its PDF. -// -// Required methods: -// value() - the sampled value -// pdf() - the probability density -// -// Satisfied by: codomain_and_pdf, domain_and_pdf, quotient_and_pdf -// ============================================================================ - -// clang-format off -#define NBL_CONCEPT_NAME SampleWithPDF -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) -#define NBL_CONCEPT_TPLT_PRM_NAMES (T) -#define NBL_CONCEPT_PARAM_0 (s, T) -NBL_CONCEPT_BEGIN(1) -#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR)(s.pdf())) - ((NBL_CONCEPT_REQ_EXPR)(s.value()))); -#undef s -#include -// clang-format on - -// ============================================================================ -// SampleWithRcpPDF -// -// Checks that a sample type bundles a value with its reciprocal PDF. -// -// Required methods: -// value() - the sampled value -// rcpPdf() - the reciprocal probability density -// -// Satisfied by: codomain_and_rcpPdf, domain_and_rcpPdf -// ============================================================================ - -// clang-format off -#define NBL_CONCEPT_NAME SampleWithRcpPDF -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) -#define NBL_CONCEPT_TPLT_PRM_NAMES (T) -#define NBL_CONCEPT_PARAM_0 (s, T) -NBL_CONCEPT_BEGIN(1) -#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR)(s.rcpPdf())) - ((NBL_CONCEPT_REQ_EXPR)(s.value()))); -#undef s -#include -// clang-format on - -// ============================================================================ -// SampleWithDensity -// -// A sample type that bundles a value with either its PDF or reciprocal PDF. -// This is the disjunction of SampleWithPDF and SampleWithRcpPDF. -// ============================================================================ -template -NBL_BOOL_CONCEPT SampleWithDensity = SampleWithPDF || SampleWithRcpPDF; - // ============================================================================ // BasicSampler // @@ -111,15 +50,15 @@ NBL_CONCEPT_END( // (sampling) direction. generate returns a codomain_type value and writes // intermediates to a cache_type out-param for later pdf evaluation. // -// The cache_type out-param stores intermediates computed during generate -// (e.g. DG1 in Cook-Torrance, or simply the pdf for simple samplers) for -// reuse by forwardPdf without redundant recomputation. +// The cache_type out-param stores the input domain_type and/or values +// derived from it during generate, for reuse by forwardPdf without +// redundant recomputation. If there is no common computation between +// generate and backwardPdf, the cache simply stores the codomain value. // // For constant-pdf samplers, forwardPdf(cache) == __pdf() (cache ignored). -// For variable-pdf samplers (e.g. Linear), forwardPdf(cache) returns the -// pre-computed pdf rather than re-evaluating __pdf(x) from the sample value. -// For complex samplers (e.g. Cook-Torrance), cache carries DG1/Fresnel and -// forwardPdf computes the pdf from those stored intermediates. +// For complex samplers (e.g. Cook-Torrance), cache carries intermediate +// values derived from the domain input (e.g. DG1/Fresnel) and forwardPdf +// computes the pdf from those stored intermediates. // // Required types: // domain_type, codomain_type, density_type, cache_type @@ -163,9 +102,6 @@ NBL_CONCEPT_END( // normalized probability densities, so this concept is satisfied by // intractable samplers as well. // -// Unlike TractableSampler, generate returns bare codomain_type (not sample_type) -// and writes a cache_type out-param for later reuse by forwardWeight. -// // Required types: // domain_type - the input space // codomain_type - the output space @@ -211,12 +147,12 @@ NBL_CONCEPT_END( // // Extends TractableSampler with the ability to evaluate the PDF given // a codomain value (i.e. without knowing the original domain input). -// The reverse mapping could be implemented via bisection search and is -// not necessarily bijective - input/output pairs need not match. +// The mapping need not be injective — multiple domain elements may map +// to the same codomain output, and backwardPdf accounts for this +// correctly (e.g. by summing contributions). // -// Also exposes forward and backward importance weights for use in MIS and RIS. -// For an invertible sampler these are just the forward and backward PDFs, -// but the names signal the intended use at call sites. +// Also exposes forward and backward importance weights for use in MIS +// and RIS. // // Required types (in addition to TractableSampler): // weight_type - the type of the importance weight @@ -263,10 +199,10 @@ NBL_CONCEPT_END( // of the Jacobian matrix of the inverse equals the reciprocal of the // absolute value of the determinant of the Jacobian matrix of the forward // mapping (the Jacobian is an NxM matrix, not a scalar): -// backwardPdf(v) == 1.0 / forwardPdf(cache) (where v == generate(u, cache).value()) +// backwardPdf(v) == 1.0 / forwardPdf(cache) (where v == generate(u, cache)) // // Required methods (in addition to InvertibleSampler): -// domain_type generateInverse(codomain_type v, out cache_type cache) +// domain_type generateInverse(codomain_type v) // ============================================================================ // clang-format off @@ -275,15 +211,12 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (_sampler, T) #define NBL_CONCEPT_PARAM_1 (v, typename T::codomain_type) -#define NBL_CONCEPT_PARAM_2 (cache, typename T::cache_type) -NBL_CONCEPT_BEGIN(3) +NBL_CONCEPT_BEGIN(2) #define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 -#define cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(InvertibleSampler, T)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.generateInverse(v, cache)), ::nbl::hlsl::is_same_v, typename T::domain_type))); -#undef cache + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.generateInverse(v)), ::nbl::hlsl::is_same_v, typename T::domain_type))); #undef v #undef _sampler #include diff --git a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl index 85dd962397..06fd7552d3 100644 --- a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl @@ -53,9 +53,8 @@ struct ProjectedHemisphere return ConcentricMapping::generateInverse(L.xy); } - static domain_type generateInverse(const codomain_type L, NBL_REF_ARG(cache_type) cache) + static domain_type generateInverse(const codomain_type L) { - cache.pdf = __pdf(L.z); return __generateInverse(L); } @@ -146,9 +145,8 @@ struct ProjectedSphere return vector_t3(hemisphere_t::__generateInverse(L), hlsl::mix(T(1.0), T(0.0), L.z > T(0.0))); } - static domain_type generateInverse(const codomain_type L, NBL_REF_ARG(cache_type) cache) + static domain_type generateInverse(const codomain_type L) { - cache.pdf = __pdf(L.z); return __generateInverse(L); } diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index 1de23eec5d..2d3097f490 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -59,9 +59,8 @@ struct Linear return x; } - domain_type generateInverse(const codomain_type x, NBL_REF_ARG(cache_type) cache) + domain_type generateInverse(const codomain_type x) { - cache.pdf = __pdf(x); return x * (scalar_type(2.0) * linearCoeffStart + linearCoeffDiff * x) * rcpCoeffSum; } diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index 2639bc7610..2866d3e618 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -75,8 +75,7 @@ struct ProjectedSphericalTriangle density_type backwardPdf(const vector3_type L) { const density_type pdf = sphtri.backwardPdf(L); - typename SphericalTriangle::cache_type dummyCache; - const vector2_type u = sphtri.generateInverse(L, dummyCache); + const vector2_type u = sphtri.generateInverse(L); Bilinear bilinear = computeBilinearPatch(); return pdf * bilinear.backwardPdf(u); } diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 84c8dc207e..0e922882bb 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -119,9 +119,8 @@ struct SphericalTriangle return vector2_type(u, v); } - domain_type generateInverse(const codomain_type L, NBL_REF_ARG(cache_type) cache) + domain_type generateInverse(const codomain_type L) { - cache.pdf = scalar_type(1.0) / solidAngle; return _generateInverse(L); } diff --git a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl index 605d7e9370..06f9baca29 100644 --- a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl @@ -30,10 +30,7 @@ struct UniformHemisphere using density_type = T; using weight_type = density_type; - struct cache_type - { - density_type pdf; - }; + struct cache_type {}; static codomain_type __generate(const domain_type _sample) { @@ -43,9 +40,13 @@ struct UniformHemisphere return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); } + static codomain_type generate(const domain_type _sample) + { + return __generate(_sample); + } + static codomain_type generate(const domain_type _sample, NBL_REF_ARG(cache_type) cache) { - cache.pdf = __pdf(); return __generate(_sample); } @@ -57,9 +58,8 @@ struct UniformHemisphere return vector_t2(_sample.z, phi / twopi); } - static domain_type generateInverse(const codomain_type _sample, NBL_REF_ARG(cache_type) cache) + static domain_type generateInverse(const codomain_type _sample) { - cache.pdf = __pdf(); return __generateInverse(_sample); } @@ -70,12 +70,12 @@ struct UniformHemisphere static density_type forwardPdf(const cache_type cache) { - return cache.pdf; + return __pdf(); } static weight_type forwardWeight(const cache_type cache) { - return forwardPdf(cache); + return __pdf(); } static density_type backwardPdf(const vector_t3 _sample) @@ -108,10 +108,7 @@ struct UniformSphere using density_type = T; using weight_type = density_type; - struct cache_type - { - density_type pdf; - }; + struct cache_type {}; static codomain_type __generate(const domain_type _sample) { @@ -121,9 +118,13 @@ struct UniformSphere return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); } + static codomain_type generate(const domain_type _sample) + { + return __generate(_sample); + } + static codomain_type generate(const domain_type _sample, NBL_REF_ARG(cache_type) cache) { - cache.pdf = __pdf(); return __generate(_sample); } @@ -135,9 +136,8 @@ struct UniformSphere return vector_t2((T(1.0) - _sample.z) * T(0.5), phi / twopi); } - static domain_type generateInverse(const codomain_type _sample, NBL_REF_ARG(cache_type) cache) + static domain_type generateInverse(const codomain_type _sample) { - cache.pdf = __pdf(); return __generateInverse(_sample); } @@ -148,12 +148,12 @@ struct UniformSphere static density_type forwardPdf(const cache_type cache) { - return cache.pdf; + return __pdf(); } static weight_type forwardWeight(const cache_type cache) { - return forwardPdf(cache); + return __pdf(); } static density_type backwardPdf(const vector_t3 _sample) From fc7e1747af7ef1afff5dc150fbc74d6b7333d930 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Thu, 19 Mar 2026 19:59:10 +0300 Subject: [PATCH 28/33] address comments in concepts.hlsl --- examples_tests | 2 +- include/nbl/builtin/hlsl/bxdf/concepts.hlsl | 81 ------------------- .../builtin/hlsl/sampling/alias_table.hlsl | 2 +- .../hlsl/sampling/box_muller_transform.hlsl | 2 +- .../nbl/builtin/hlsl/sampling/concepts.hlsl | 64 +++++++++++++-- .../hlsl/sampling/cumulative_probability.hlsl | 2 +- .../projected_spherical_triangle.hlsl | 2 +- .../hlsl/sampling/spherical_rectangle.hlsl | 2 +- 8 files changed, 62 insertions(+), 95 deletions(-) delete mode 100644 include/nbl/builtin/hlsl/bxdf/concepts.hlsl diff --git a/examples_tests b/examples_tests index 24baa877d2..54acb0c658 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 24baa877d25cf8eaf0461ba0bce371a4bad57537 +Subproject commit 54acb0c658c48b6b802147eb01e53109900150c4 diff --git a/include/nbl/builtin/hlsl/bxdf/concepts.hlsl b/include/nbl/builtin/hlsl/bxdf/concepts.hlsl deleted file mode 100644 index f28fe96755..0000000000 --- a/include/nbl/builtin/hlsl/bxdf/concepts.hlsl +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef _NBL_BUILTIN_HLSL_BXDF_CONCEPTS_INCLUDED_ -#define _NBL_BUILTIN_HLSL_BXDF_CONCEPTS_INCLUDED_ - -#include - -namespace nbl -{ -namespace hlsl -{ -namespace bxdf -{ -namespace concepts -{ - -// ============================================================================ -// SampleWithPDF -// -// Checks that a sample type bundles a value with its PDF. -// -// Required methods: -// value() - the sampled value -// pdf() - the probability density -// -// Satisfied by: codomain_and_pdf, domain_and_pdf, quotient_and_pdf -// ============================================================================ - -// clang-format off -#define NBL_CONCEPT_NAME SampleWithPDF -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) -#define NBL_CONCEPT_TPLT_PRM_NAMES (T) -#define NBL_CONCEPT_PARAM_0 (s, T) -NBL_CONCEPT_BEGIN(1) -#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR)(s.pdf())) - ((NBL_CONCEPT_REQ_EXPR)(s.value()))); -#undef s -#include -// clang-format on - -// ============================================================================ -// SampleWithRcpPDF -// -// Checks that a sample type bundles a value with its reciprocal PDF. -// -// Required methods: -// value() - the sampled value -// rcpPdf() - the reciprocal probability density -// -// Satisfied by: codomain_and_rcpPdf, domain_and_rcpPdf -// ============================================================================ - -// clang-format off -#define NBL_CONCEPT_NAME SampleWithRcpPDF -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) -#define NBL_CONCEPT_TPLT_PRM_NAMES (T) -#define NBL_CONCEPT_PARAM_0 (s, T) -NBL_CONCEPT_BEGIN(1) -#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR)(s.rcpPdf())) - ((NBL_CONCEPT_REQ_EXPR)(s.value()))); -#undef s -#include -// clang-format on - -// ============================================================================ -// SampleWithDensity -// -// A sample type that bundles a value with either its PDF or reciprocal PDF. -// This is the disjunction of SampleWithPDF and SampleWithRcpPDF. -// ============================================================================ -template -NBL_BOOL_CONCEPT SampleWithDensity = SampleWithPDF || SampleWithRcpPDF; - -} // namespace concepts -} // namespace bxdf -} // namespace hlsl -} // namespace nbl - -#endif // _NBL_BUILTIN_HLSL_BXDF_CONCEPTS_INCLUDED_ diff --git a/include/nbl/builtin/hlsl/sampling/alias_table.hlsl b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl index 1c97f9ff08..66424f5d66 100644 --- a/include/nbl/builtin/hlsl/sampling/alias_table.hlsl +++ b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl @@ -27,7 +27,7 @@ namespace sampling // - AliasIndexAccessor: returns uint32_t redirect index for bin i // - PdfAccessor: returns scalar_type weight[i] / totalWeight // -// Satisfies TractableSampler (not InvertibleSampler: the mapping is discrete). +// Satisfies TractableSampler (not BackwardTractableSampler: the mapping is discrete). // The cache stores the sampled index so forwardPdf can look up the PDF. template struct AliasTable diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index 1ee96a3f75..ed088c3041 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -22,7 +22,7 @@ struct BoxMullerTransform using scalar_type = T; using vector2_type = vector; - // InvertibleSampler concept types + // BackwardTractableSampler concept types using domain_type = vector2_type; using codomain_type = vector2_type; using density_type = scalar_type; diff --git a/include/nbl/builtin/hlsl/sampling/concepts.hlsl b/include/nbl/builtin/hlsl/sampling/concepts.hlsl index 4bd129f732..1645867362 100644 --- a/include/nbl/builtin/hlsl/sampling/concepts.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concepts.hlsl @@ -12,6 +12,55 @@ namespace sampling namespace concepts { +// ============================================================================ +// SampleWithPDF +// +// Checks that a sample type bundles a value with its PDF. +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME SampleWithPDF +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (s, T) +NBL_CONCEPT_BEGIN(1) +#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_EXPR)(s.pdf())) + ((NBL_CONCEPT_REQ_EXPR)(s.value()))); +#undef s +#include +// clang-format on + +// ============================================================================ +// SampleWithRcpPDF +// +// Checks that a sample type bundles a value with its reciprocal PDF. +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME SampleWithRcpPDF +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (s, T) +NBL_CONCEPT_BEGIN(1) +#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_EXPR)(s.rcpPdf())) + ((NBL_CONCEPT_REQ_EXPR)(s.value()))); +#undef s +#include +// clang-format on + +// ============================================================================ +// SampleWithDensity +// +// A sample type that bundles a value with either its PDF or reciprocal PDF. +// This is the disjunction of SampleWithPDF and SampleWithRcpPDF. +// ============================================================================ +template +NBL_BOOL_CONCEPT SampleWithDensity = SampleWithPDF || SampleWithRcpPDF; + // ============================================================================ // BasicSampler // @@ -56,9 +105,8 @@ NBL_CONCEPT_END( // generate and backwardPdf, the cache simply stores the codomain value. // // For constant-pdf samplers, forwardPdf(cache) == __pdf() (cache ignored). -// For complex samplers (e.g. Cook-Torrance), cache carries intermediate -// values derived from the domain input (e.g. DG1/Fresnel) and forwardPdf -// computes the pdf from those stored intermediates. +// For complex samplers, cache carries intermediate values derived from +// the domain input and forwardPdf computes the pdf from those. // // Required types: // domain_type, codomain_type, density_type, cache_type @@ -143,7 +191,7 @@ NBL_CONCEPT_END( // clang-format on // ============================================================================ -// InvertibleSampler +// BackwardTractableSampler // // Extends TractableSampler with the ability to evaluate the PDF given // a codomain value (i.e. without knowing the original domain input). @@ -164,7 +212,7 @@ NBL_CONCEPT_END( // ============================================================================ // clang-format off -#define NBL_CONCEPT_NAME InvertibleSampler +#define NBL_CONCEPT_NAME BackwardTractableSampler #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (_sampler, T) @@ -193,7 +241,7 @@ NBL_CONCEPT_END( // BijectiveSampler // // The mapping domain <-> codomain is bijective (1:1), so it can be -// inverted. Extends InvertibleSampler with generateInverse. +// inverted. Extends BackwardTractableSampler with generateInverse. // // Because the mapping is bijective, the absolute value of the determinant // of the Jacobian matrix of the inverse equals the reciprocal of the @@ -201,7 +249,7 @@ NBL_CONCEPT_END( // mapping (the Jacobian is an NxM matrix, not a scalar): // backwardPdf(v) == 1.0 / forwardPdf(cache) (where v == generate(u, cache)) // -// Required methods (in addition to InvertibleSampler): +// Required methods (in addition to BackwardTractableSampler): // domain_type generateInverse(codomain_type v) // ============================================================================ @@ -215,7 +263,7 @@ NBL_CONCEPT_BEGIN(2) #define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(InvertibleSampler, T)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(BackwardTractableSampler, T)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.generateInverse(v)), ::nbl::hlsl::is_same_v, typename T::domain_type))); #undef v #undef _sampler diff --git a/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl index a8cb01b7a1..dc1da5f23c 100644 --- a/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl @@ -29,7 +29,7 @@ namespace sampling // must have `value_type` typedef and `operator[](uint32_t)` for upper_bound // - PdfAccessor: returns scalar_type weight[i] / totalWeight // -// Satisfies TractableSampler and ResamplableSampler (not InvertibleSampler: +// Satisfies TractableSampler and ResamplableSampler (not BackwardTractableSampler: // the mapping is discrete). template struct CumulativeProbabilitySampler diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index 2866d3e618..ae47e75d98 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -26,7 +26,7 @@ struct ProjectedSphericalTriangle using vector3_type = vector; using vector4_type = vector; - // InvertibleSampler concept types + // BackwardTractableSampler concept types using domain_type = vector2_type; using codomain_type = vector3_type; using density_type = scalar_type; diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index afa805150b..d4993610ee 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -25,7 +25,7 @@ struct SphericalRectangle using vector3_type = vector; using vector4_type = vector; - // InvertibleSampler concept types + // BackwardTractableSampler concept types using domain_type = vector2_type; using codomain_type = vector2_type; using density_type = scalar_type; From edc3c3ed47d09ccf78eb3379fe7b7905c54495a4 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Tue, 24 Mar 2026 02:41:02 +0300 Subject: [PATCH 29/33] addressed more comments --- examples_tests | 2 +- include/nbl/builtin/hlsl/algorithm.hlsl | 15 ++-- .../builtin/hlsl/bxdf/base/lambertian.hlsl | 12 +-- .../builtin/hlsl/bxdf/base/oren_nayar.hlsl | 4 +- include/nbl/builtin/hlsl/ieee754.hlsl | 35 ++++++++ .../hlsl/path_tracing/gaussian_filter.hlsl | 2 +- .../builtin/hlsl/sampling/alias_table.hlsl | 53 ++++++++---- .../hlsl}/sampling/alias_table_builder.h | 8 +- .../nbl/builtin/hlsl/sampling/bilinear.hlsl | 2 +- .../hlsl/sampling/box_muller_transform.hlsl | 26 ++++-- .../hlsl/sampling/concentric_mapping.hlsl | 43 +++------- .../hlsl/sampling/cos_weighted_spheres.hlsl | 86 ++++--------------- .../hlsl/sampling/cumulative_probability.hlsl | 78 +++++++++++++---- .../sampling/cumulative_probability_builder.h | 33 +++++++ include/nbl/builtin/hlsl/sampling/linear.hlsl | 17 ++-- .../projected_spherical_triangle.hlsl | 43 ++++++---- .../hlsl/sampling/spherical_rectangle.hlsl | 11 +-- .../hlsl/sampling/spherical_triangle.hlsl | 36 +++----- .../hlsl/sampling/uniform_spheres.hlsl | 60 +++++-------- .../hlsl/shapes/spherical_triangle.hlsl | 64 +++++++------- .../sampling/cumulative_probability_builder.h | 53 ------------ 21 files changed, 332 insertions(+), 351 deletions(-) rename include/nbl/{core => builtin/hlsl}/sampling/alias_table_builder.h (93%) create mode 100644 include/nbl/builtin/hlsl/sampling/cumulative_probability_builder.h delete mode 100644 include/nbl/core/sampling/cumulative_probability_builder.h diff --git a/examples_tests b/examples_tests index 54acb0c658..60e0a3506c 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 54acb0c658c48b6b802147eb01e53109900150c4 +Subproject commit 60e0a3506c59182c434326c4451bb5dcd786f4ab diff --git a/include/nbl/builtin/hlsl/algorithm.hlsl b/include/nbl/builtin/hlsl/algorithm.hlsl index 66442a11a1..ab715ca0da 100644 --- a/include/nbl/builtin/hlsl/algorithm.hlsl +++ b/include/nbl/builtin/hlsl/algorithm.hlsl @@ -163,21 +163,22 @@ struct lower_to_upper_comparator_transform_t template -uint32_t lower_bound(NBL_REF_ARG(Accessor) accessor, const uint32_t begin, const uint32_t end, const typename Accessor::value_type value, const Comparator comp) +uint32_t lower_bound(NBL_REF_ARG(Accessor) accessor, const uint32_t begin, const uint32_t end, const typename Accessor::value_type value, NBL_REF_ARG(Comparator) comp) { impl::bound_t implementation = impl::bound_t::setup(begin,end,value,comp); - return implementation(accessor); + const uint32_t retval = implementation(accessor); + comp = implementation.comp; + return retval; } template -uint32_t upper_bound(NBL_REF_ARG(Accessor) accessor, const uint32_t begin, const uint32_t end, const typename Accessor::value_type value, const Comparator comp) +uint32_t upper_bound(NBL_REF_ARG(Accessor) accessor, const uint32_t begin, const uint32_t end, const typename Accessor::value_type value, NBL_REF_ARG(Comparator) comp) { - //using TransformedComparator = impl::lower_to_upper_comparator_transform_t; - //TransformedComparator transformedComparator; - impl::lower_to_upper_comparator_transform_t transformedComparator; transformedComparator.comp = comp; - return lower_bound >(accessor,begin,end,value,transformedComparator); + const uint32_t retval = lower_bound >(accessor,begin,end,value,transformedComparator); + comp = transformedComparator.comp; + return retval; } diff --git a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl index 3f7b85875a..3b15cd95fe 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl @@ -65,9 +65,9 @@ struct SLambertianBase scalar_type pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { NBL_IF_CONSTEXPR (IsBSDF) - return sampling::ProjectedSphere::pdf(_sample.getNdotL(_clamp)); + return sampling::ProjectedSphere::backwardPdf(vector(0, 0, _sample.getNdotL(_clamp))); else - return sampling::ProjectedHemisphere::pdf(_sample.getNdotL(_clamp)); + return sampling::ProjectedHemisphere::backwardPdf(vector(0, 0, _sample.getNdotL(_clamp))); } scalar_type pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { @@ -76,12 +76,12 @@ struct SLambertianBase quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { - sampling::quotient_and_pdf qp; + scalar_type p; NBL_IF_CONSTEXPR (IsBSDF) - qp = sampling::ProjectedSphere::template quotientAndPdf(_sample.getNdotL(_clamp)); + p = sampling::ProjectedSphere::backwardPdf(vector(0, 0, _sample.getNdotL(_clamp))); else - qp = sampling::ProjectedHemisphere::template quotientAndPdf(_sample.getNdotL(_clamp)); - return quotient_pdf_type::create(qp.quotient()[0], qp.pdf()); + p = sampling::ProjectedHemisphere::backwardPdf(vector(0, 0, _sample.getNdotL(_clamp))); + return quotient_pdf_type::create(scalar_type(1.0), p); } quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { diff --git a/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl b/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl index ab06e8d43a..6412dc0fca 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl @@ -100,9 +100,9 @@ struct SOrenNayarBase scalar_type pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { if (IsBSDF) - return sampling::ProjectedSphere::pdf(_sample.getNdotL(_clamp)); + return sampling::ProjectedSphere::backwardPdf(vector(0, 0, _sample.getNdotL(_clamp))); else - return sampling::ProjectedHemisphere::pdf(_sample.getNdotL(_clamp)); + return sampling::ProjectedHemisphere::backwardPdf(vector(0, 0, _sample.getNdotL(_clamp))); } scalar_type pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { diff --git a/include/nbl/builtin/hlsl/ieee754.hlsl b/include/nbl/builtin/hlsl/ieee754.hlsl index 0663d89c0b..27857ae319 100644 --- a/include/nbl/builtin/hlsl/ieee754.hlsl +++ b/include/nbl/builtin/hlsl/ieee754.hlsl @@ -291,6 +291,41 @@ NBL_CONSTEXPR_FUNC bool isZero(T val) return !(ieee754::impl::bitCastToUintType(val) & exponentAndMantissaMask); } +// Returns the largest representable value less than `val`. +// For positive values this decrements the bit representation; for negative values it increments. +// Caller must guarantee val is finite and non-zero. +template ) +NBL_CONSTEXPR_FUNC T nextDown(T val) +{ + using AsUint = typename unsigned_integer_of_size::type; + using traits_t = traits; + + const AsUint bits = ieee754::impl::bitCastToUintType(val); + + // positive: decrement; negative: increment + AsUint result; + if (CanBeNeg) + { + const bool isNegative = (bits & traits_t::signMask) != AsUint(0); + result = isNegative ? (bits + AsUint(1)) : (bits - AsUint(1)); + } + else + result = bits - AsUint(1); + return impl::castBackToFloatType(result); +} + +// Returns the representable value nearest to `val` in the direction of zero. +// For positive values this decrements the bit representation; for negative values it decrements (moving toward zero). +// Caller must guarantee val is finite and non-zero. +template ) +NBL_CONSTEXPR_FUNC T nextTowardZero(T val) +{ + using AsUint = typename unsigned_integer_of_size::type; + + const AsUint bits = ieee754::impl::bitCastToUintType(val); + return impl::castBackToFloatType(bits - AsUint(1)); +} + } } } diff --git a/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl b/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl index 9667275f4e..8500474552 100644 --- a/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl @@ -22,7 +22,7 @@ struct GaussianFilter { this_t retval; retval.truncation = hlsl::exp(-0.5 * gaussianFilterCutoff * gaussianFilterCutoff); - retval.boxMuller.stddev = stddev; + retval.boxMuller = sampling::BoxMullerTransform::create(stddev); return retval; } diff --git a/include/nbl/builtin/hlsl/sampling/alias_table.hlsl b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl index 66424f5d66..21acdd956f 100644 --- a/include/nbl/builtin/hlsl/sampling/alias_table.hlsl +++ b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl @@ -7,6 +7,7 @@ #include #include +#include namespace nbl { @@ -20,31 +21,36 @@ namespace sampling // Samples a discrete index in [0, N) with probability proportional to // precomputed weights in O(1) time per sample, using a prebuilt alias table. // -// Template parameters are ReadOnly accessors, each with: -// value_type get(uint32_t i) const; +// Accessor template parameters must satisfy GenericReadAccessor: +// accessor.template get(index, outVal) // void, writes to outVal // -// - ProbabilityAccessor: returns scalar_type threshold in [0, 1] for bin i -// - AliasIndexAccessor: returns uint32_t redirect index for bin i -// - PdfAccessor: returns scalar_type weight[i] / totalWeight +// - ProbabilityAccessor: reads scalar_type threshold in [0, 1] for bin i +// - AliasIndexAccessor: reads uint32_t redirect index for bin i +// - PdfAccessor: reads scalar_type weight[i] / totalWeight // // Satisfies TractableSampler (not BackwardTractableSampler: the mapping is discrete). -// The cache stores the sampled index so forwardPdf can look up the PDF. -template +// The cache stores the PDF value looked up during generate, avoiding redundant +// storage of the codomain (sampled index) which is already the return value. +template && + concepts::accessors::GenericReadAccessor && + concepts::accessors::GenericReadAccessor) struct AliasTable { using scalar_type = T; - using domain_type = scalar_type; - using codomain_type = uint32_t; + using domain_type = Domain; + using codomain_type = Codomain; using density_type = scalar_type; using weight_type = density_type; struct cache_type { - codomain_type sampledIndex; + density_type pdf; }; - static AliasTable create(NBL_CONST_REF_ARG(ProbabilityAccessor) _probAccessor, NBL_CONST_REF_ARG(AliasIndexAccessor) _aliasAccessor, NBL_CONST_REF_ARG(PdfAccessor) _pdfAccessor, uint32_t _size) + static AliasTable create(NBL_CONST_REF_ARG(ProbabilityAccessor) _probAccessor, NBL_CONST_REF_ARG(AliasIndexAccessor) _aliasAccessor, NBL_CONST_REF_ARG(PdfAccessor) _pdfAccessor, codomain_type _size) { AliasTable retval; retval.probAccessor = _probAccessor; @@ -60,15 +66,24 @@ struct AliasTable codomain_type generate(const domain_type u) { const scalar_type scaled = u * tableSizeMinusUlp; - const uint32_t bin = uint32_t(scaled); + const codomain_type bin = codomain_type(scaled); const scalar_type remainder = scaled - scalar_type(bin); + scalar_type prob; + probAccessor.template get(bin, prob); + // Use if-statement to avoid select: aliasIndex is a dependent read codomain_type result; - if (remainder < probAccessor.get(bin)) + if (remainder < prob) + { result = bin; + } else - result = aliasAccessor.get(bin); + { + codomain_type alias; + aliasAccessor.template get(bin, alias); + result = alias; + } return result; } @@ -77,23 +92,25 @@ struct AliasTable codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { const codomain_type result = generate(u); - cache.sampledIndex = result; + pdfAccessor.template get(result, cache.pdf); return result; } density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) { - return pdfAccessor.get(cache.sampledIndex); + return cache.pdf; } weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) { - return forwardPdf(cache); + return cache.pdf; } density_type backwardPdf(const codomain_type v) { - return pdfAccessor.get(v); + scalar_type pdf; + pdfAccessor.template get(v, pdf); + return pdf; } weight_type backwardWeight(const codomain_type v) diff --git a/include/nbl/core/sampling/alias_table_builder.h b/include/nbl/builtin/hlsl/sampling/alias_table_builder.h similarity index 93% rename from include/nbl/core/sampling/alias_table_builder.h rename to include/nbl/builtin/hlsl/sampling/alias_table_builder.h index a0a10f25d8..88c49c0893 100644 --- a/include/nbl/core/sampling/alias_table_builder.h +++ b/include/nbl/builtin/hlsl/sampling/alias_table_builder.h @@ -2,14 +2,14 @@ // This file is part of the "Nabla Engine". // For conditions of distribution and use, see copyright notice in nabla.h -#ifndef _NBL_CORE_SAMPLING_ALIAS_TABLE_BUILDER_H_INCLUDED_ -#define _NBL_CORE_SAMPLING_ALIAS_TABLE_BUILDER_H_INCLUDED_ +#ifndef _NBL_BUILTIN_HLSL_SAMPLING_ALIAS_TABLE_BUILDER_H_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_ALIAS_TABLE_BUILDER_H_INCLUDED_ #include namespace nbl { -namespace core +namespace hlsl { namespace sampling { @@ -86,7 +86,7 @@ struct AliasTableBuilder }; } // namespace sampling -} // namespace core +} // namespace hlsl } // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index b676269714..3a5d085ac3 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -57,7 +57,7 @@ struct Bilinear Linear linearx = Linear::create(ySliceEndPoints); p.x = linearx.generate(u.x, linearCache); - cache.pdf = backwardPdf(p); + cache.pdf = nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], p.x) * fourOverTwiceAreasUnderXCurveSum; return p; } diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index ed088c3041..c5568980d0 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -30,21 +30,29 @@ struct BoxMullerTransform struct cache_type { - density_type pdf; + scalar_type u_x; }; + static BoxMullerTransform create(const scalar_type _stddev) + { + BoxMullerTransform retval; + retval.stddev = _stddev; + retval.halfRcpStddev2 = scalar_type(0.5) / (_stddev * _stddev); + return retval; + } + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { scalar_type sinPhi, cosPhi; math::sincos(scalar_type(2.0) * numbers::pi * u.y - numbers::pi, sinPhi, cosPhi); const codomain_type outPos = vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(scalar_type(-2.0) * nbl::hlsl::log(u.x)) * stddev; - cache.pdf = backwardPdf(outPos); + cache.u_x = u.x; return outPos; } density_type forwardPdf(const cache_type cache) { - return cache.pdf; + return halfRcpStddev2 * numbers::inv_pi * cache.u_x; } vector2_type separateForwardPdf(const cache_type cache, const codomain_type outPos) @@ -59,18 +67,17 @@ struct BoxMullerTransform density_type backwardPdf(const codomain_type outPos) { - const vector2_type marginals = separateBackwardPdf(outPos); - return marginals.x * marginals.y; + const scalar_type normalization = halfRcpStddev2 * numbers::inv_pi; + return normalization * nbl::hlsl::exp(-halfRcpStddev2 * nbl::hlsl::dot(outPos, outPos)); } vector2_type separateBackwardPdf(const codomain_type outPos) { - const scalar_type stddev2 = stddev * stddev; - const scalar_type normalization = scalar_type(1.0) / (stddev * nbl::hlsl::sqrt(scalar_type(2.0) * numbers::pi)); + const scalar_type normalization = nbl::hlsl::sqrt(halfRcpStddev2 * numbers::inv_pi); const vector2_type outPos2 = outPos * outPos; return vector2_type( - normalization * nbl::hlsl::exp(scalar_type(-0.5) * outPos2.x / stddev2), - normalization * nbl::hlsl::exp(scalar_type(-0.5) * outPos2.y / stddev2) + normalization * nbl::hlsl::exp(-halfRcpStddev2 * outPos2.x), + normalization * nbl::hlsl::exp(-halfRcpStddev2 * outPos2.y) ); } @@ -80,6 +87,7 @@ struct BoxMullerTransform } T stddev; + T halfRcpStddev2; }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl index b94c4fc561..c91c61573f 100644 --- a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl @@ -32,13 +32,11 @@ struct ConcentricMapping struct cache_type { - density_type pdf; // TODO: should we cache `r`? }; static codomain_type generate(const domain_type _u, NBL_REF_ARG(cache_type) cache) { - cache.pdf = numbers::inv_pi; //map [0;1]^2 to [-1;1]^2 domain_type u = 2.0f * _u - hlsl::promote >(1.0); @@ -75,44 +73,27 @@ struct ConcentricMapping static domain_type generateInverse(const codomain_type p) { - T theta = hlsl::atan2(p.y, p.x); // -pi -> pi - T r = hlsl::sqrt(p.x * p.x + p.y * p.y); + const T theta = hlsl::atan2(p.y, p.x); // -pi -> pi + const T r = hlsl::sqrt(p.x * p.x + p.y * p.y); const T PiOver4 = T(0.25) * numbers::pi; + const T rDivPi4 = r / PiOver4; vector u; // TODO: should reduce branching somehow? - if (hlsl::abs(theta) < PiOver4 || hlsl::abs(theta) > 3 * PiOver4) + if (hlsl::abs(theta) < PiOver4 || hlsl::abs(theta) > T(3) * PiOver4) { - r = ieee754::copySign(r, p.x); - u.x = r; - if (p.x < 0) - { - if (p.y < 0) - { - u.y = (numbers::pi + theta) * r / PiOver4; - } - else - { - u.y = (theta - numbers::pi)*r / PiOver4; - } - } + const T A = ieee754::copySign(rDivPi4, p.x); + u.x = ieee754::copySign(r, p.x); + if (p.x < T(0)) + u.y = theta * A + ieee754::copySign(numbers::pi, -p.y) * A; else - { - u.y = (theta * r) / PiOver4; - } + u.y = theta * A; } else { - r = ieee754::copySign(r, p.y); - u.y = r; - if (p.y < 0) - { - u.x = -(T(0.5) * numbers::pi + theta) * r / PiOver4; - } - else - { - u.x = (T(0.5) * numbers::pi - theta) * r / PiOver4; - } + const T A = ieee754::copySign(rDivPi4, p.y); + u.y = ieee754::copySign(r, p.y); + u.x = ieee754::copySign(T(0.5) * numbers::pi, p.y) * A - theta * A; } return (u + hlsl::promote >(1.0)) * T(0.5); diff --git a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl index 06fd7552d3..7da6d5c4c7 100644 --- a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl @@ -7,7 +7,6 @@ #include "nbl/builtin/hlsl/concepts.hlsl" #include "nbl/builtin/hlsl/sampling/concentric_mapping.hlsl" -#include "nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl" namespace nbl { @@ -31,7 +30,7 @@ struct ProjectedHemisphere struct cache_type { - density_type pdf; + scalar_type L_z; }; static codomain_type __generate(const domain_type _sample) @@ -44,33 +43,18 @@ struct ProjectedHemisphere static codomain_type generate(const domain_type _sample, NBL_REF_ARG(cache_type) cache) { const codomain_type L = __generate(_sample); - cache.pdf = __pdf(L.z); + cache.L_z = L.z; return L; } - static domain_type __generateInverse(const codomain_type L) - { - return ConcentricMapping::generateInverse(L.xy); - } - static domain_type generateInverse(const codomain_type L) { - return __generateInverse(L); - } - - static T __pdf(const T L_z) - { - return L_z * numbers::inv_pi; - } - - static scalar_type pdf(const T L_z) - { - return __pdf(L_z); + return ConcentricMapping::generateInverse(L.xy); } static density_type forwardPdf(const cache_type cache) { - return cache.pdf; + return cache.L_z * numbers::inv_pi; } static weight_type forwardWeight(const cache_type cache) @@ -80,25 +64,15 @@ struct ProjectedHemisphere static density_type backwardPdf(const codomain_type L) { - return __pdf(L.z); + cache_type c; + c.L_z = L.z; + return forwardPdf(c); } static weight_type backwardWeight(const codomain_type L) { return backwardPdf(L); } - - template > - static quotient_and_pdf quotientAndPdf(const T L) - { - return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L)); - } - - template > - static quotient_and_pdf quotientAndPdf(const vector_t3 L) - { - return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L.z)); - } }; template) @@ -115,10 +89,7 @@ struct ProjectedSphere using density_type = T; using weight_type = density_type; - struct cache_type - { - density_type pdf; - }; + using cache_type = typename hemisphere_t::cache_type; static codomain_type __generate(NBL_REF_ARG(domain_type) _sample) { @@ -134,35 +105,22 @@ struct ProjectedSphere static codomain_type generate(NBL_REF_ARG(domain_type) _sample, NBL_REF_ARG(cache_type) cache) { const codomain_type L = __generate(_sample); - cache.pdf = __pdf(L.z); + cache.L_z = L.z; return L; } - static domain_type __generateInverse(const codomain_type L) + static domain_type generateInverse(const codomain_type L) { // NOTE: incomplete information to recover exact z component; we only know which hemisphere L came from, // so we return a canonical value (0.0 for upper, 1.0 for lower) that round-trips correctly through __generate - return vector_t3(hemisphere_t::__generateInverse(L), hlsl::mix(T(1.0), T(0.0), L.z > T(0.0))); - } - - static domain_type generateInverse(const codomain_type L) - { - return __generateInverse(L); - } - - static T __pdf(T L_z) - { - return T(0.5) * hemisphere_t::__pdf(hlsl::abs(L_z)); - } - - static scalar_type pdf(T L_z) - { - return __pdf(L_z); + return vector_t3(hemisphere_t::generateInverse(L), hlsl::mix(T(1.0), T(0.0), L.z > T(0.0))); } static density_type forwardPdf(const cache_type cache) { - return cache.pdf; + cache_type hc; + hc.L_z = hlsl::abs(cache.L_z); + return T(0.5) * hemisphere_t::forwardPdf(hc); } static weight_type forwardWeight(const cache_type cache) @@ -172,25 +130,15 @@ struct ProjectedSphere static density_type backwardPdf(const codomain_type L) { - return __pdf(L.z); + cache_type c; + c.L_z = L.z; + return forwardPdf(c); } static weight_type backwardWeight(const codomain_type L) { return backwardPdf(L); } - - template > - static quotient_and_pdf quotientAndPdf(T L) - { - return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L)); - } - - template > - static quotient_and_pdf quotientAndPdf(const vector_t3 L) - { - return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L.z)); - } }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl index dc1da5f23c..85b0c4c73b 100644 --- a/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl @@ -7,6 +7,7 @@ #include #include +#include namespace nbl { @@ -15,6 +16,28 @@ namespace hlsl namespace sampling { +namespace concepts +{ + +// clang-format off +#define NBL_CONCEPT_NAME CumulativeProbabilityAccessor +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T)(Scalar) +#define NBL_CONCEPT_PARAM_0 (accessor, T) +#define NBL_CONCEPT_PARAM_1 (index, uint32_t) +NBL_CONCEPT_BEGIN(2) +#define accessor NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define index NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::value_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((accessor[index]), ::nbl::hlsl::is_same_v, Scalar))); +#undef index +#undef accessor +#include +// clang-format on + +} // namespace concepts + // Discrete sampler using cumulative probability lookup via upper_bound. // // Samples a discrete index in [0, N) with probability proportional to @@ -24,14 +47,9 @@ namespace sampling // is always 1.0 and need not be stored). Entry i holds the sum of // probabilities for indices [0, i]. // -// Template parameters are ReadOnly accessors: -// - CumulativeProbabilityAccessor: returns scalar_type cumProb for index i, -// must have `value_type` typedef and `operator[](uint32_t)` for upper_bound -// - PdfAccessor: returns scalar_type weight[i] / totalWeight -// // Satisfies TractableSampler and ResamplableSampler (not BackwardTractableSampler: // the mapping is discrete). -template +template) struct CumulativeProbabilitySampler { using scalar_type = T; @@ -43,23 +61,40 @@ struct CumulativeProbabilitySampler struct cache_type { - codomain_type sampledIndex; + density_type oneBefore; + density_type upperBound; + }; + + // Stateful comparator that tracks the CDF values seen during binary search. + // upper_bound uses lower_to_upper_comparator_transform_t which calls !comp(rhs, lhs), + // so our operator() receives (value=u, rhs=cumProb[testPoint]). + struct CdfComparator + { + bool operator()(const density_type value, const density_type rhs) + { + const bool retval = value < rhs; + if (retval) + upperBound = rhs; + else + oneBefore = rhs; + return retval; + } + + density_type oneBefore; + density_type upperBound; }; - static CumulativeProbabilitySampler create(NBL_CONST_REF_ARG(CumulativeProbabilityAccessor) _cumProbAccessor, NBL_CONST_REF_ARG(PdfAccessor) _pdfAccessor, uint32_t _size) + static CumulativeProbabilitySampler create(NBL_CONST_REF_ARG(CumProbAccessor) _cumProbAccessor, uint32_t _size) { CumulativeProbabilitySampler retval; retval.cumProbAccessor = _cumProbAccessor; - retval.pdfAccessor = _pdfAccessor; - retval.size = _size; + retval.storedCount = _size - 1u; return retval; } // BasicSampler interface codomain_type generate(const domain_type u) { - // upper_bound on N-1 stored entries; if u >= all stored values, returns N-1 (the last bucket) - const uint32_t storedCount = size - 1u; // upper_bound returns first index where cumProb > u return hlsl::upper_bound(cumProbAccessor, 0u, storedCount, u); } @@ -67,14 +102,18 @@ struct CumulativeProbabilitySampler // TractableSampler interface codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { - const codomain_type result = generate(u); - cache.sampledIndex = result; + CdfComparator comp; + comp.oneBefore = density_type(0.0); + comp.upperBound = density_type(1.0); + const codomain_type result = hlsl::upper_bound(cumProbAccessor, 0u, storedCount, u, comp); + cache.oneBefore = comp.oneBefore; + cache.upperBound = comp.upperBound; return result; } density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) { - return pdfAccessor.get(cache.sampledIndex); + return cache.upperBound - cache.oneBefore; } weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) @@ -84,7 +123,9 @@ struct CumulativeProbabilitySampler density_type backwardPdf(const codomain_type v) { - return pdfAccessor.get(v); + const density_type cur = (v < storedCount) ? cumProbAccessor[v] : density_type(1.0); + const density_type prev = (v > 0u) ? cumProbAccessor[v - 1u] : density_type(0.0); + return cur - prev; } weight_type backwardWeight(const codomain_type v) @@ -92,9 +133,8 @@ struct CumulativeProbabilitySampler return backwardPdf(v); } - CumulativeProbabilityAccessor cumProbAccessor; - PdfAccessor pdfAccessor; - uint32_t size; + CumProbAccessor cumProbAccessor; + uint32_t storedCount; }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/cumulative_probability_builder.h b/include/nbl/builtin/hlsl/sampling/cumulative_probability_builder.h new file mode 100644 index 0000000000..b8f59ceb54 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/cumulative_probability_builder.h @@ -0,0 +1,33 @@ +// 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_SAMPLING_CUMULATIVE_PROBABILITY_BUILDER_H_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_CUMULATIVE_PROBABILITY_BUILDER_H_INCLUDED_ + +#include +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +// Builds a normalized cumulative histogram from an array of non-negative weights. +// Output has N-1 entries (last bucket implicitly 1.0). +template +void computeNormalizedCumulativeHistogram(const T* weights, uint32_t N, T* outCumProb) +{ + std::inclusive_scan(weights, weights + N - 1, outCumProb); + const T normalizationFactor = T(1) / (outCumProb[N - 2] + weights[N - 1]); + std::for_each(outCumProb, outCumProb + N - 1, [normalizationFactor](T& v) { v *= normalizationFactor; }); +} + +} // namespace sampling +} // namespace hlsl +} // namespace nbl + +#endif diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index 2d3097f490..f39fd73698 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -7,6 +7,7 @@ #include #include +#include namespace nbl { @@ -29,7 +30,7 @@ struct Linear struct cache_type { - density_type pdf; + domain_type u; }; static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end), assumed to be at x=0 and x=1 @@ -38,7 +39,7 @@ struct Linear retval.linearCoeffStart = linearCoeffs[0]; retval.linearCoeffDiff = linearCoeffs[1] - linearCoeffs[0]; retval.rcpCoeffSum = scalar_type(1.0) / (linearCoeffs[0] + linearCoeffs[1]); - retval.rcpDiff = -scalar_type(1.0) / retval.linearCoeffDiff; + retval.negRcpDiff = -scalar_type(1.0) / retval.linearCoeffDiff; vector2_type squaredCoeffs = linearCoeffs * linearCoeffs; retval.squaredCoeffStart = squaredCoeffs[0]; retval.squaredCoeffDiff = squaredCoeffs[1] - squaredCoeffs[0]; @@ -47,16 +48,14 @@ struct Linear density_type __pdf(const codomain_type x) { - if (x < scalar_type(0.0) || x > scalar_type(1.0)) - return scalar_type(0.0); + assert(x >= scalar_type(0.0) && x <= scalar_type(1.0)); return scalar_type(2.0) * (linearCoeffStart + x * linearCoeffDiff) * rcpCoeffSum; } codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { - const codomain_type x = hlsl::mix(u, (linearCoeffStart - sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, abs(rcpDiff) < hlsl::numeric_limits::max); - cache.pdf = __pdf(x); - return x; + cache.u = u; + return hlsl::mix(u, (linearCoeffStart - sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * negRcpDiff, abs(negRcpDiff) < hlsl::numeric_limits::max); } domain_type generateInverse(const codomain_type x) @@ -66,7 +65,7 @@ struct Linear density_type forwardPdf(const cache_type cache) { - return cache.pdf; + return scalar_type(2.0) * nbl::hlsl::sqrt(squaredCoeffStart + cache.u * squaredCoeffDiff) * rcpCoeffSum; } weight_type forwardWeight(const cache_type cache) @@ -87,7 +86,7 @@ struct Linear scalar_type linearCoeffStart; scalar_type linearCoeffDiff; scalar_type rcpCoeffSum; - scalar_type rcpDiff; + scalar_type negRcpDiff; scalar_type squaredCoeffStart; scalar_type squaredCoeffDiff; }; diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index ae47e75d98..c6591b7a37 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -34,60 +34,67 @@ struct ProjectedSphericalTriangle struct cache_type { - density_type pdf; + scalar_type abs_cos_theta; + typename Bilinear::cache_type bilinearCache; }; // NOTE: produces a degenerate (all-zero) bilinear patch when the receiver normal faces away // from all three triangle vertices, resulting in NaN PDFs (0 * inf). Callers must ensure // at least one vertex has positive projection onto the receiver normal. - Bilinear computeBilinearPatch() + static ProjectedSphericalTriangle create(NBL_REF_ARG(shapes::SphericalTriangle) shape, const vector3_type receiverNormal, const bool receiverWasBSDF) { - const scalar_type minimumProjSolidAngle = 0.0; + ProjectedSphericalTriangle retval; + retval.sphtri = SphericalTriangle::create(shape); - matrix m = matrix(sphtri.tri_vertices[0], sphtri.tri_vertices[1], sphtri.tri_vertices[2]); + const scalar_type minimumProjSolidAngle = 0.0; + matrix m = matrix(shape.vertices[0], shape.vertices[1], shape.vertices[2]); const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(receiverWasBSDF, hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); + retval.bilinearPatch = Bilinear::create(bxdfPdfAtVertex.yyxz); - return Bilinear::create(bxdfPdfAtVertex.yyxz); + const scalar_type projSA = shape.projectedSolidAngle(receiverNormal); + retval.rcpProjSolidAngle = projSA > scalar_type(0.0) ? scalar_type(1.0) / projSA : scalar_type(0.0); + return retval; } codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { - Bilinear bilinear = computeBilinearPatch(); - typename Bilinear::cache_type bilinearCache; - const vector2_type warped = bilinear.generate(u, bilinearCache); + Bilinear bilinear = bilinearPatch; + const vector2_type warped = bilinear.generate(u, cache.bilinearCache); typename SphericalTriangle::cache_type sphtriCache; const vector3_type L = sphtri.generate(warped, sphtriCache); - // combined weight: sphtri pdf (1/solidAngle) * bilinear pdf at u - cache.pdf = sphtri.forwardPdf(sphtriCache) * bilinear.forwardPdf(bilinearCache); + cache.abs_cos_theta = bilinear.forwardWeight(cache.bilinearCache); return L; } density_type forwardPdf(const cache_type cache) { - return cache.pdf; + return sphtri.rcpSolidAngle * bilinearPatch.forwardPdf(cache.bilinearCache); } weight_type forwardWeight(const cache_type cache) { - return forwardPdf(cache); + return cache.abs_cos_theta * rcpProjSolidAngle; } density_type backwardPdf(const vector3_type L) { - const density_type pdf = sphtri.backwardPdf(L); const vector2_type u = sphtri.generateInverse(L); - Bilinear bilinear = computeBilinearPatch(); - return pdf * bilinear.backwardPdf(u); + return sphtri.rcpSolidAngle * bilinearPatch.backwardPdf(u); } weight_type backwardWeight(const vector3_type L) { - return backwardPdf(L); + cache_type cache; + // cache.bilinearCache; // unused + // approximate abs(cos_theta) via bilinear interpolation at the inverse-mapped point + const vector2_type u = sphtri.generateInverse(L); + cache.abs_cos_theta = bilinearPatch.backwardWeight(u); + return forwardWeight(cache); } sampling::SphericalTriangle sphtri; - vector3_type receiverNormal; - bool receiverWasBSDF; + Bilinear bilinearPatch; + scalar_type rcpProjSolidAngle; }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index d4993610ee..38a22617e8 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -31,10 +31,7 @@ struct SphericalRectangle using density_type = scalar_type; using weight_type = density_type; - struct cache_type - { - density_type pdf; - }; + struct cache_type {}; NBL_CONSTEXPR_STATIC_INLINE scalar_type ClampEps = 1e-5; @@ -63,7 +60,7 @@ struct SphericalRectangle retval.solidAngle = p + q - scalar_type(2.0) * numbers::pi; // flip z axis if r0.z > 0 - retval.r0 = hlsl::promote(-hlsl::abs(retval.r0.z)); + retval.r0.z = -hlsl::abs(retval.r0.z); retval.r1 = retval.r0 + vector3_type(rect.extents.x, rect.extents.y, 0); retval.b0 = n_z[0]; @@ -94,14 +91,12 @@ struct SphericalRectangle const scalar_type hv2 = hv * hv; const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - ClampEps); - cache.pdf = scalar_type(1.0) / solidAngle; - return vector2_type((xu - r0.x), (yv - r0.y)); } density_type forwardPdf(const cache_type cache) { - return cache.pdf; + return scalar_type(1.0) / solidAngle; } weight_type forwardWeight(const cache_type cache) diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 0e922882bb..0cc90d0d78 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -32,19 +32,14 @@ struct SphericalTriangle using density_type = scalar_type; using weight_type = density_type; - struct cache_type - { - density_type pdf; - }; + struct cache_type {}; static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) { SphericalTriangle retval; - vector3_type cos_vertices, sin_vertices; - shapes::SphericalTriangle tri_mut = tri; - retval.solidAngle = tri_mut.solidAngle(cos_vertices, sin_vertices); - retval.cosA = cos_vertices[0]; - retval.sinA = sin_vertices[0]; + retval.rcpSolidAngle = scalar_type(1.0) / tri.solid_angle; + retval.cosA = tri.cos_vertices[0]; + retval.sinA = tri.sin_vertices[0]; retval.tri_vertices[0] = tri.vertices[0]; retval.tri_vertices[1] = tri.vertices[1]; retval.tri_vertices[2] = tri.vertices[2]; @@ -57,6 +52,7 @@ struct SphericalTriangle codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { scalar_type negSinSubSolidAngle, negCosSubSolidAngle; + const scalar_type solidAngle = scalar_type(1.0) / rcpSolidAngle; math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); const scalar_type p = negCosSubSolidAngle * sinA - negSinSubSolidAngle * cosA; @@ -80,17 +76,15 @@ struct SphericalTriangle const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); if (csc_b_s < numeric_limits::max) { - const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(scalar_type(1.0) + cosBC_s * u.y - u.y, scalar_type(-1.0), scalar_type(1.0)); + const scalar_type cosAngleAlongBC_s = scalar_type(1.0) + cosBC_s * u.y - u.y; if (nbl::hlsl::abs(cosAngleAlongBC_s) < scalar_type(1.0)) retval += math::quaternion::slerp_delta(tri_vertices[1], C_s * csc_b_s, cosAngleAlongBC_s); } - cache.pdf = scalar_type(1.0) / solidAngle; - return retval; } - domain_type _generateInverse(const codomain_type L) + domain_type generateInverse(const codomain_type L) { const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri_vertices[1]); const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); @@ -105,13 +99,12 @@ struct SphericalTriangle math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosA, sinA); angle_adder.addAngle(cosB_, sinB_); angle_adder.addAngle(cosC_, sinC_); - const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi) * (scalar_type(1.0) / solidAngle); + const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi) * rcpSolidAngle; const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; const scalar_type sinBC_s_product = sinB_ * sinC_; - // 1 ULP below 1.0, ensures (1.0 - cosBC_s) is strictly positive in float - const scalar_type one_below_one = bit_cast(bit_cast(scalar_type(1)) - uint_type(1)); + const scalar_type one_below_one = ieee754::nextTowardZero(scalar_type(1.0)); const scalar_type cosBC_s = sinBC_s_product > numeric_limits::min ? (cosA + cosB_ * cosC_) / sinBC_s_product : triCosC; const scalar_type v_denom = scalar_type(1.0) - (cosBC_s < one_below_one ? cosBC_s : triCosC); const scalar_type v = (scalar_type(1.0) - cosAngleAlongBC_s) / v_denom; @@ -119,14 +112,9 @@ struct SphericalTriangle return vector2_type(u, v); } - domain_type generateInverse(const codomain_type L) - { - return _generateInverse(L); - } - density_type forwardPdf(const cache_type cache) { - return cache.pdf; + return rcpSolidAngle; } weight_type forwardWeight(const cache_type cache) @@ -136,7 +124,7 @@ struct SphericalTriangle density_type backwardPdf(const codomain_type L) { - return scalar_type(1.0) / solidAngle; + return rcpSolidAngle; } weight_type backwardWeight(const codomain_type L) @@ -144,7 +132,7 @@ struct SphericalTriangle return backwardPdf(L); } - scalar_type solidAngle; + scalar_type rcpSolidAngle; scalar_type cosA; scalar_type sinA; diff --git a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl index 06f9baca29..3d764eac92 100644 --- a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl @@ -63,36 +63,26 @@ struct UniformHemisphere return __generateInverse(_sample); } - static scalar_type __pdf() - { - return T(1.0) / (T(2.0) * numbers::pi); - } - static density_type forwardPdf(const cache_type cache) { - return __pdf(); + return T(0.5) * numbers::inv_pi; } static weight_type forwardWeight(const cache_type cache) { - return __pdf(); + return T(0.5) * numbers::inv_pi; } static density_type backwardPdf(const vector_t3 _sample) { - return __pdf(); + return T(0.5) * numbers::inv_pi; } static weight_type backwardWeight(const codomain_type sample) { - return backwardPdf(sample); + return T(0.5) * numbers::inv_pi; } - template > - static quotient_and_pdf quotientAndPdf() - { - return quotient_and_pdf::create(hlsl::promote(1.0), __pdf()); - } }; template) @@ -100,6 +90,7 @@ struct UniformSphere { using vector_t2 = vector; using vector_t3 = vector; + using hemisphere_t = UniformHemisphere; // BijectiveSampler concept types using scalar_type = T; @@ -108,14 +99,17 @@ struct UniformSphere using density_type = T; using weight_type = density_type; - struct cache_type {}; + using cache_type = typename hemisphere_t::cache_type; static codomain_type __generate(const domain_type _sample) { - T z = T(1.0) - T(2.0) * _sample.x; - T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); - T phi = T(2.0) * numbers::pi * _sample.y; - return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); + // Map _sample.x from [0,1] into hemisphere sample + sign flip: + // upper hemisphere when _sample.x < 0.5, lower when >= 0.5 + const bool chooseLower = _sample.x >= T(0.5); + const T hemiX = chooseLower ? (T(2.0) * _sample.x - T(1.0)) : (T(2.0) * _sample.x); + vector_t3 retval = hemisphere_t::__generate(vector_t2(hemiX, _sample.y)); + retval.z = chooseLower ? (-retval.z) : retval.z; + return retval; } static codomain_type generate(const domain_type _sample) @@ -130,10 +124,12 @@ struct UniformSphere static domain_type __generateInverse(const codomain_type _sample) { - T phi = hlsl::atan2(_sample.y, _sample.x); - const T twopi = T(2.0) * numbers::pi; - phi += hlsl::mix(T(0.0), twopi, phi < T(0.0)); - return vector_t2((T(1.0) - _sample.z) * T(0.5), phi / twopi); + const bool isLower = _sample.z < T(0.0); + const vector_t3 hemiSample = vector_t3(_sample.x, _sample.y, hlsl::abs(_sample.z)); + vector_t2 hemiUV = hemisphere_t::__generateInverse(hemiSample); + // Recover _sample.x: upper hemisphere maps [0,0.5], lower maps [0.5,1] + hemiUV.x = isLower ? (hemiUV.x * T(0.5) + T(0.5)) : (hemiUV.x * T(0.5)); + return hemiUV; } static domain_type generateInverse(const codomain_type _sample) @@ -141,36 +137,26 @@ struct UniformSphere return __generateInverse(_sample); } - static T __pdf() - { - return T(1.0) / (T(4.0) * numbers::pi); - } - static density_type forwardPdf(const cache_type cache) { - return __pdf(); + return T(0.5) * hemisphere_t::forwardPdf(cache); } static weight_type forwardWeight(const cache_type cache) { - return __pdf(); + return T(0.5) * hemisphere_t::forwardWeight(cache); } static density_type backwardPdf(const vector_t3 _sample) { - return __pdf(); + return T(0.5) * hemisphere_t::backwardPdf(_sample); } static weight_type backwardWeight(const codomain_type sample) { - return backwardPdf(sample); + return T(0.5) * hemisphere_t::backwardWeight(sample); } - template > - static quotient_and_pdf quotientAndPdf() - { - return quotient_and_pdf::create(hlsl::promote(1.0), __pdf()); - } }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl index 5d157a06a1..7ba5ae7079 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl @@ -26,58 +26,49 @@ struct SphericalTriangle using vector3_type = vector; static SphericalTriangle create(const vector3_type vertices[3], const vector3_type origin) + { + const vector3_type normalizedVerts[3] = { + nbl::hlsl::normalize(vertices[0] - origin), + nbl::hlsl::normalize(vertices[1] - origin), + nbl::hlsl::normalize(vertices[2] - origin) + }; + return create(normalizedVerts); + } + + static SphericalTriangle create(const vector3_type normalizedVertices[3]) { SphericalTriangle retval; - retval.vertices[0] = nbl::hlsl::normalize(vertices[0] - origin); - retval.vertices[1] = nbl::hlsl::normalize(vertices[1] - origin); - retval.vertices[2] = nbl::hlsl::normalize(vertices[2] - origin); + retval.vertices[0] = normalizedVertices[0]; + retval.vertices[1] = normalizedVertices[1]; + retval.vertices[2] = normalizedVertices[2]; retval.cos_sides = vector3_type(hlsl::dot(retval.vertices[1], retval.vertices[2]), hlsl::dot(retval.vertices[2], retval.vertices[0]), hlsl::dot(retval.vertices[0], retval.vertices[1])); const vector3_type sin_sides2 = hlsl::promote(1.0) - retval.cos_sides * retval.cos_sides; retval.csc_sides = hlsl::rsqrt(sin_sides2); - return retval; - } - - // checks if any angles are small enough to disregard - bool pyramidAngles() - { - return hlsl::any >(csc_sides >= hlsl::promote(numeric_limits::max)); - } - - vector3_type __getCosVertices() - { - // using Spherical Law of Cosines (TODO: do we need to clamp anymore? since the pyramid angles method introduction?) - return hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); - } - - scalar_type solidAngle(NBL_REF_ARG(vector3_type) cos_vertices, NBL_REF_ARG(vector3_type) sin_vertices) - { - if (pyramidAngles()) - return 0.f; // Both vertices and angles at the vertices are denoted by the same upper case letters A, B, and C. The angles A, B, C of the triangle are equal to the angles between the planes that intersect the surface of the sphere or, // equivalently, the angles between the tangent vectors of the great circle arcs where they meet at the vertices. Angles are in radians. The angles of proper spherical triangles are (by convention) less than PI - cos_vertices = hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); // using Spherical Law of Cosines (TODO: do we need to clamp anymore? since the pyramid angles method introduction?) - sin_vertices = hlsl::sqrt(hlsl::promote(1.0) - cos_vertices * cos_vertices); + retval.cos_vertices = hlsl::clamp((retval.cos_sides - retval.cos_sides.yzx * retval.cos_sides.zxy) * retval.csc_sides.yzx * retval.csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); // using Spherical Law of Cosines (TODO: do we need to clamp anymore? since the pyramid angles method introduction?) + retval.sin_vertices = hlsl::sqrt(hlsl::promote(1.0) - retval.cos_vertices * retval.cos_vertices); - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cos_vertices[0], sin_vertices[0]); - angle_adder.addAngle(cos_vertices[1], sin_vertices[1]); - angle_adder.addAngle(cos_vertices[2], sin_vertices[2]); - return angle_adder.getSumofArccos() - numbers::pi; + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(retval.cos_vertices[0], retval.sin_vertices[0]); + angle_adder.addAngle(retval.cos_vertices[1], retval.sin_vertices[1]); + angle_adder.addAngle(retval.cos_vertices[2], retval.sin_vertices[2]); + retval.solid_angle = angle_adder.getSumofArccos() - numbers::pi; + + return retval; } - scalar_type solidAngle() + // checks if any angles are small enough to disregard + bool pyramidAngles() NBL_CONST_MEMBER_FUNC { - vector3_type dummy0,dummy1; - return solidAngle(dummy0,dummy1); + return hlsl::any >(csc_sides >= hlsl::promote(numeric_limits::max)); } - scalar_type projectedSolidAngle(const vector3_type receiverNormal, NBL_REF_ARG(vector3_type) cos_vertices) + scalar_type projectedSolidAngle(const vector3_type receiverNormal) NBL_CONST_MEMBER_FUNC { if (pyramidAngles()) return 0.f; - cos_vertices = __getCosVertices(); - matrix awayFromEdgePlane; awayFromEdgePlane[0] = hlsl::cross(vertices[1], vertices[2]) * csc_sides[0]; awayFromEdgePlane[1] = hlsl::cross(vertices[2], vertices[0]) * csc_sides[1]; @@ -101,8 +92,13 @@ struct SphericalTriangle } vector3_type vertices[3]; + // angles of vertices with origin, so the sides are INSIDE the sphere vector3_type cos_sides; vector3_type csc_sides; + // angles between arcs on the sphere, so angles in the TANGENT plane at each vertex + vector3_type cos_vertices; + vector3_type sin_vertices; + scalar_type solid_angle; }; } diff --git a/include/nbl/core/sampling/cumulative_probability_builder.h b/include/nbl/core/sampling/cumulative_probability_builder.h deleted file mode 100644 index fe2191bb7a..0000000000 --- a/include/nbl/core/sampling/cumulative_probability_builder.h +++ /dev/null @@ -1,53 +0,0 @@ -// 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_CORE_SAMPLING_CUMULATIVE_PROBABILITY_BUILDER_H_INCLUDED_ -#define _NBL_CORE_SAMPLING_CUMULATIVE_PROBABILITY_BUILDER_H_INCLUDED_ - -#include - -namespace nbl -{ -namespace core -{ -namespace sampling -{ - -// Builds the CDF and PDF arrays from an array of non-negative weights. -// -// Parameters: -// weights - input weights (non-negative, at least one must be > 0) -// N - number of entries -// outCumProb - [out] cumulative probability array, N-1 entries -// (last bucket implicitly 1.0) -// outPdf - [out] normalized PDF per entry: weight[i] / sum(weights), N entries -template -struct CumulativeProbabilityBuilder -{ - static void build(const T* weights, uint32_t N, T* outCumProb, T* outPdf) - { - T totalWeight = T(0); - for (uint32_t i = 0; i < N; i++) - totalWeight += weights[i]; - - const T rcpTotalWeight = T(1) / totalWeight; - - for (uint32_t i = 0; i < N; i++) - outPdf[i] = weights[i] * rcpTotalWeight; - - // N-1 stored entries (last bucket is implicitly 1.0) - T cumulative = T(0); - for (uint32_t i = 0; i < N - 1; i++) - { - cumulative += outPdf[i]; - outCumProb[i] = cumulative; - } - } -}; - -} // namespace sampling -} // namespace core -} // namespace nbl - -#endif From b52f3c4aeceb3a2da32c89f1d01f0f3ab9e1f20d Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Fri, 27 Mar 2026 01:55:57 +0300 Subject: [PATCH 30/33] addressed more comments --- examples_tests | 2 +- include/nbl/builtin/hlsl/algorithm.hlsl | 38 ++-- .../builtin/hlsl/bxdf/base/lambertian.hlsl | 6 +- .../builtin/hlsl/sampling/alias_table.hlsl | 14 +- .../hlsl/sampling/alias_table_builder.h | 14 +- .../nbl/builtin/hlsl/sampling/bilinear.hlsl | 37 ++-- .../hlsl/sampling/box_muller_transform.hlsl | 26 ++- .../hlsl/sampling/concentric_mapping.hlsl | 174 +++++++++--------- .../hlsl/sampling/cos_weighted_spheres.hlsl | 11 +- .../hlsl/sampling/cumulative_probability.hlsl | 92 ++++----- .../sampling/cumulative_probability_builder.h | 8 +- include/nbl/builtin/hlsl/sampling/linear.hlsl | 28 ++- .../builtin/hlsl/sampling/polar_mapping.hlsl | 64 +++++++ .../projected_spherical_triangle.hlsl | 42 +++-- .../hlsl/sampling/quotient_and_pdf.hlsl | 6 +- .../hlsl/sampling/spherical_rectangle.hlsl | 10 +- .../hlsl/sampling/spherical_triangle.hlsl | 12 +- .../hlsl/sampling/uniform_spheres.hlsl | 55 +++--- .../builtin/hlsl/sampling/value_and_pdf.hlsl | 8 +- .../hlsl/shapes/spherical_triangle.hlsl | 4 +- 20 files changed, 355 insertions(+), 296 deletions(-) create mode 100644 include/nbl/builtin/hlsl/sampling/polar_mapping.hlsl diff --git a/examples_tests b/examples_tests index 60e0a3506c..da109c5050 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 60e0a3506c59182c434326c4451bb5dcd786f4ab +Subproject commit da109c5050e1c3361b852278a2a18c3983e51891 diff --git a/include/nbl/builtin/hlsl/algorithm.hlsl b/include/nbl/builtin/hlsl/algorithm.hlsl index ab715ca0da..d5c1d9cba6 100644 --- a/include/nbl/builtin/hlsl/algorithm.hlsl +++ b/include/nbl/builtin/hlsl/algorithm.hlsl @@ -92,12 +92,12 @@ NBL_CONSTEXPR_FUNC void swap(NBL_REF_ARG(T) lhs, NBL_REF_ARG(T) rhs) namespace impl { -template +template struct bound_t { - static bound_t setup(uint32_t begin, const uint32_t end, const typename Accessor::value_type _value, const Comparator _comp) + static bound_t setup(uint32_t begin, const uint32_t end, const typename Accessor::value_type _value, const Comparator _comp) { - bound_t retval; + bound_t retval; retval.comp = _comp; retval.value = _value; retval.it = begin; @@ -110,7 +110,7 @@ struct bound_t { if (isNPoT(len)) { - const uint32_t newLen = 0x1u< -struct lower_to_upper_comparator_transform_t -{ - bool operator()(const typename Accessor::value_type lhs, const typename Accessor::value_type rhs) - { - return !comp(rhs,lhs); - } - - Comparator comp; -}; - } template uint32_t lower_bound(NBL_REF_ARG(Accessor) accessor, const uint32_t begin, const uint32_t end, const typename Accessor::value_type value, NBL_REF_ARG(Comparator) comp) { - impl::bound_t implementation = impl::bound_t::setup(begin,end,value,comp); + impl::bound_t implementation = impl::bound_t::setup(begin,end,value,comp); const uint32_t retval = implementation(accessor); comp = implementation.comp; return retval; @@ -174,10 +171,9 @@ uint32_t lower_bound(NBL_REF_ARG(Accessor) accessor, const uint32_t begin, const template uint32_t upper_bound(NBL_REF_ARG(Accessor) accessor, const uint32_t begin, const uint32_t end, const typename Accessor::value_type value, NBL_REF_ARG(Comparator) comp) { - impl::lower_to_upper_comparator_transform_t transformedComparator; - transformedComparator.comp = comp; - const uint32_t retval = lower_bound >(accessor,begin,end,value,transformedComparator); - comp = transformedComparator.comp; + impl::bound_t implementation = impl::bound_t::setup(begin,end,value,comp); + const uint32_t retval = implementation(accessor); + comp = implementation.comp; return retval; } diff --git a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl index 3b15cd95fe..526b969045 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl @@ -76,11 +76,13 @@ struct SLambertianBase quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { + typename sampling::ProjectedHemisphere::cache_type cache; + cache.L_z = _sample.getNdotL(_clamp); scalar_type p; NBL_IF_CONSTEXPR (IsBSDF) - p = sampling::ProjectedSphere::backwardPdf(vector(0, 0, _sample.getNdotL(_clamp))); + p = sampling::ProjectedSphere::forwardPdf(cache); else - p = sampling::ProjectedHemisphere::backwardPdf(vector(0, 0, _sample.getNdotL(_clamp))); + p = sampling::ProjectedHemisphere::forwardPdf(cache); return quotient_pdf_type::create(scalar_type(1.0), p); } quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC diff --git a/include/nbl/builtin/hlsl/sampling/alias_table.hlsl b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl index 21acdd956f..3e54db4bbd 100644 --- a/include/nbl/builtin/hlsl/sampling/alias_table.hlsl +++ b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl @@ -7,6 +7,7 @@ #include #include +#include #include namespace nbl @@ -33,6 +34,7 @@ namespace sampling // storage of the codomain (sampled index) which is already the return value. template && concepts::accessors::GenericReadAccessor && concepts::accessors::GenericReadAccessor && concepts::accessors::GenericReadAccessor) @@ -63,7 +65,7 @@ struct AliasTable } // BasicSampler interface - codomain_type generate(const domain_type u) + codomain_type generate(const domain_type u) NBL_CONST_MEMBER_FUNC { const scalar_type scaled = u * tableSizeMinusUlp; const codomain_type bin = codomain_type(scaled); @@ -89,31 +91,31 @@ struct AliasTable } // TractableSampler interface - codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { const codomain_type result = generate(u); pdfAccessor.template get(result, cache.pdf); return result; } - density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) + density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { return cache.pdf; } - weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) + weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { return cache.pdf; } - density_type backwardPdf(const codomain_type v) + density_type backwardPdf(const codomain_type v) NBL_CONST_MEMBER_FUNC { scalar_type pdf; pdfAccessor.template get(v, pdf); return pdf; } - weight_type backwardWeight(const codomain_type v) + weight_type backwardWeight(const codomain_type v) NBL_CONST_MEMBER_FUNC { return backwardPdf(v); } diff --git a/include/nbl/builtin/hlsl/sampling/alias_table_builder.h b/include/nbl/builtin/hlsl/sampling/alias_table_builder.h index 88c49c0893..d02d21488c 100644 --- a/include/nbl/builtin/hlsl/sampling/alias_table_builder.h +++ b/include/nbl/builtin/hlsl/sampling/alias_table_builder.h @@ -27,21 +27,21 @@ namespace sampling template struct AliasTableBuilder { - static void build(const T* weights, uint32_t N, T* outProbability, uint32_t* outAlias, T* outPdf, uint32_t* workspace) + static void build(std::span weights, T* outProbability, uint32_t* outAlias, T* outPdf, uint32_t* workspace) { T totalWeight = T(0); - for (uint32_t i = 0; i < N; i++) + for (uint32_t i = 0; i < weights.size(); i++) totalWeight += weights[i]; const T rcpTotalWeight = T(1) / totalWeight; // Compute PDFs, scaled probabilities, and partition into small/large in one pass uint32_t smallEnd = 0; - uint32_t largeBegin = N; - for (uint32_t i = 0; i < N; i++) + uint32_t largeBegin = weights.size(); + for (uint32_t i = 0; i < weights.size(); i++) { outPdf[i] = weights[i] * rcpTotalWeight; - outProbability[i] = outPdf[i] * T(N); + outProbability[i] = outPdf[i] * T(weights.size()); if (outProbability[i] < T(1)) workspace[smallEnd++] = i; @@ -50,7 +50,7 @@ struct AliasTableBuilder } // Pair small and large entries - while (smallEnd > 0 && largeBegin < N) + while (smallEnd > 0 && largeBegin < weights.size()) { const uint32_t s = workspace[--smallEnd]; const uint32_t l = workspace[largeBegin]; @@ -76,7 +76,7 @@ struct AliasTableBuilder outProbability[s] = T(1); outAlias[s] = s; } - while (largeBegin < N) + while (largeBegin < weights.size()) { const uint32_t l = workspace[largeBegin++]; outProbability[l] = T(1); diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index 3a5d085ac3..072f2cc1ca 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -7,6 +7,7 @@ #include #include +#include #include namespace nbl @@ -32,7 +33,8 @@ struct Bilinear struct cache_type { - density_type pdf; + scalar_type normalizedStart; + typename Linear::cache_type linearXCache; }; static Bilinear create(const vector4_type bilinearCoeffs) @@ -46,49 +48,40 @@ struct Bilinear return retval; } - codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { - typename Linear::cache_type linearCache; + typename Linear::cache_type linearYCache; vector2_type p; - p.y = lineary.generate(u.y, linearCache); + p.y = lineary.generate(u.y, linearYCache); const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); Linear linearx = Linear::create(ySliceEndPoints); - p.x = linearx.generate(u.x, linearCache); + p.x = linearx.generate(u.x, cache.linearXCache); - cache.pdf = nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], p.x) * fourOverTwiceAreasUnderXCurveSum; + // pre-multiply by normalization so forwardPdf is just addition + cache.normalizedStart = ySliceEndPoints[0] * fourOverTwiceAreasUnderXCurveSum; + cache.linearXCache.diffTimesX *= fourOverTwiceAreasUnderXCurveSum; return p; } - domain_type generateInverse(const codomain_type p) + density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC { - vector2_type u; - const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); - Linear linearx = Linear::create(ySliceEndPoints); - u.x = linearx.generateInverse(p.x); - u.y = lineary.generateInverse(p.y); - - return u; - } - - density_type forwardPdf(const cache_type cache) - { - return cache.pdf; + return cache.normalizedStart + cache.linearXCache.diffTimesX; } - weight_type forwardWeight(const cache_type cache) + weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC { return forwardPdf(cache); } - density_type backwardPdf(const codomain_type p) + density_type backwardPdf(const codomain_type p) NBL_CONST_MEMBER_FUNC { const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); return nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], p.x) * fourOverTwiceAreasUnderXCurveSum; } - weight_type backwardWeight(const codomain_type p) + weight_type backwardWeight(const codomain_type p) NBL_CONST_MEMBER_FUNC { return backwardPdf(p); } diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index c5568980d0..531550cea7 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -30,6 +30,7 @@ struct BoxMullerTransform struct cache_type { + vector2_type direction; // (cosPhi, sinPhi) scalar_type u_x; }; @@ -41,37 +42,42 @@ struct BoxMullerTransform return retval; } - codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { scalar_type sinPhi, cosPhi; math::sincos(scalar_type(2.0) * numbers::pi * u.y - numbers::pi, sinPhi, cosPhi); - const codomain_type outPos = vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(scalar_type(-2.0) * nbl::hlsl::log(u.x)) * stddev; + cache.direction = vector2_type(cosPhi, sinPhi); cache.u_x = u.x; - return outPos; + return cache.direction * nbl::hlsl::sqrt(scalar_type(-2.0) * nbl::hlsl::log(u.x)) * stddev; } - density_type forwardPdf(const cache_type cache) + density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC { return halfRcpStddev2 * numbers::inv_pi * cache.u_x; } - vector2_type separateForwardPdf(const cache_type cache, const codomain_type outPos) + vector2_type separateForwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC { - return separateBackwardPdf(outPos); + const scalar_type normalization = nbl::hlsl::sqrt(halfRcpStddev2 * numbers::inv_pi); + const vector2_type dir2 = cache.direction * cache.direction; + return normalization * vector2_type( + nbl::hlsl::pow(cache.u_x, dir2.x), + nbl::hlsl::pow(cache.u_x, dir2.y) + ); } - weight_type forwardWeight(const cache_type cache) + weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC { return forwardPdf(cache); } - density_type backwardPdf(const codomain_type outPos) + density_type backwardPdf(const codomain_type outPos) NBL_CONST_MEMBER_FUNC { const scalar_type normalization = halfRcpStddev2 * numbers::inv_pi; return normalization * nbl::hlsl::exp(-halfRcpStddev2 * nbl::hlsl::dot(outPos, outPos)); } - vector2_type separateBackwardPdf(const codomain_type outPos) + vector2_type separateBackwardPdf(const codomain_type outPos) NBL_CONST_MEMBER_FUNC { const scalar_type normalization = nbl::hlsl::sqrt(halfRcpStddev2 * numbers::inv_pi); const vector2_type outPos2 = outPos * outPos; @@ -81,7 +87,7 @@ struct BoxMullerTransform ); } - weight_type backwardWeight(const codomain_type outPos) + weight_type backwardWeight(const codomain_type outPos) NBL_CONST_MEMBER_FUNC { return backwardPdf(outPos); } diff --git a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl index c91c61573f..f67d240ec7 100644 --- a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl @@ -16,95 +16,97 @@ namespace hlsl namespace sampling { +// Based on: Shirley & Chiu, "A Low Distortion Map Between Disk and Square", 1997 +// See also: Clarberg, "Fast Equal-Area Mapping of the (Hemi)Sphere using SIMD", 2008 +// http://fileadmin.cs.lth.se/graphics/research/papers/2008/simdmapping/clarberg_simdmapping08_preprint.pdf template struct ConcentricMapping { - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - using vector4_type = vector; - - // BijectiveSampler concept types - using domain_type = vector2_type; - using codomain_type = vector2_type; - using density_type = scalar_type; - using weight_type = density_type; - - struct cache_type - { - // TODO: should we cache `r`? - }; - - static codomain_type generate(const domain_type _u, NBL_REF_ARG(cache_type) cache) - { - //map [0;1]^2 to [-1;1]^2 - domain_type u = 2.0f * _u - hlsl::promote >(1.0); - - vector p; - if (hlsl::all >(glsl::equal(u, hlsl::promote >(0.0)))) - p = hlsl::promote >(0.0); - else - { - T r; - T theta; - if (hlsl::abs(u.x) > hlsl::abs(u.y)) - { - r = u.x; - theta = 0.25 * numbers::pi * (u.y / u.x); - } - else - { - r = u.y; - theta = 0.5 * numbers::pi - 0.25 * numbers::pi * (u.x / u.y); - } - - p = r * vector(hlsl::cos(theta), hlsl::sin(theta)); - } - - return p; - } - - // Overload for BasicSampler - static codomain_type generate(domain_type _u) - { - cache_type dummy; - return generate(_u, dummy); - } - - static domain_type generateInverse(const codomain_type p) - { - const T theta = hlsl::atan2(p.y, p.x); // -pi -> pi - const T r = hlsl::sqrt(p.x * p.x + p.y * p.y); - const T PiOver4 = T(0.25) * numbers::pi; - const T rDivPi4 = r / PiOver4; - - vector u; - // TODO: should reduce branching somehow? - if (hlsl::abs(theta) < PiOver4 || hlsl::abs(theta) > T(3) * PiOver4) - { - const T A = ieee754::copySign(rDivPi4, p.x); - u.x = ieee754::copySign(r, p.x); - if (p.x < T(0)) - u.y = theta * A + ieee754::copySign(numbers::pi, -p.y) * A; - else - u.y = theta * A; - } - else - { - const T A = ieee754::copySign(rDivPi4, p.y); - u.y = ieee754::copySign(r, p.y); - u.x = ieee754::copySign(T(0.5) * numbers::pi, p.y) * A - theta * A; - } - - return (u + hlsl::promote >(1.0)) * T(0.5); - } - - // The PDF of Shirley mapping is constant (1/PI on the unit disk) - static density_type forwardPdf(cache_type cache) { return numbers::inv_pi; } - static density_type backwardPdf(codomain_type v) { return numbers::inv_pi; } - - static weight_type forwardWeight(cache_type cache) { return forwardPdf(cache); } - static weight_type backwardWeight(codomain_type v) { return backwardPdf(v); } + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; + + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type + { + // TODO: should we cache `r`? + }; + + static codomain_type generate(const domain_type _u, NBL_REF_ARG(cache_type) cache) + { + // map [0,1]^2 to [-1,1]^2 + const vector2_type u = scalar_type(2) * _u - hlsl::promote(scalar_type(1)); + + const scalar_type a = u.x; + const scalar_type b = u.y; + + // dominant axis selection + const bool cond = a * a > b * b; + const scalar_type dominant = hlsl::select(cond, a, b); + const scalar_type minor = hlsl::select(cond, b, a); + + const scalar_type safe_dominant = dominant != scalar_type(0) ? dominant : scalar_type(0); + const scalar_type ratio = minor / safe_dominant; + + const scalar_type angle = scalar_type(0.25) * numbers::pi * ratio; + const scalar_type c = hlsl::cos(angle); + const scalar_type s = hlsl::sin(angle); + + // final selection without branching + const scalar_type x = hlsl::select(cond, c, s); + const scalar_type y = hlsl::select(cond, s, c); + + return dominant * vector2_type(x, y); + } + + // Overload for BasicSampler + static codomain_type generate(domain_type _u) + { + cache_type dummy; + return generate(_u, dummy); + } + + static domain_type generateInverse(const codomain_type p) + { + const scalar_type r = hlsl::sqrt(p.x * p.x + p.y * p.y); + + const scalar_type ax = hlsl::abs(p.x); + const scalar_type ay = hlsl::abs(p.y); + + // swapped = ay > ax + const bool swapped = ay > ax; + + // branchless selection + const scalar_type num = hlsl::select(swapped, ax, ay); + const scalar_type denom = hlsl::select(swapped, ay, ax); + + // angle in [0, pi/4] + const scalar_type phi = hlsl::atan2(num, denom); + + const scalar_type minor_val = r * phi / (scalar_type(0.25) * numbers::pi); + + // reconstruct a,b using select instead of branching + const scalar_type a_base = hlsl::select(swapped, minor_val, r); + const scalar_type b_base = hlsl::select(swapped, r, minor_val); + + const scalar_type a = ieee754::copySign(a_base, p.x); + const scalar_type b = ieee754::copySign(b_base, p.y); + + return (vector2_type(a, b) + hlsl::promote(scalar_type(1))) * scalar_type(0.5); + } + + // The PDF of Shirley mapping is constant (1/PI on the unit disk) + static density_type forwardPdf(cache_type cache) { return numbers::inv_pi; } + static density_type backwardPdf(codomain_type v) { return numbers::inv_pi; } + + static weight_type forwardWeight(cache_type cache) { return forwardPdf(cache); } + static weight_type backwardWeight(codomain_type v) { return backwardPdf(v); } }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl index 7da6d5c4c7..30c1d732f0 100644 --- a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl @@ -64,6 +64,8 @@ struct ProjectedHemisphere static density_type backwardPdf(const codomain_type L) { + assert(L.z > 0); + cache_type c; c.L_z = L.z; return forwardPdf(c); @@ -82,7 +84,7 @@ struct ProjectedSphere using vector_t3 = vector; using hemisphere_t = ProjectedHemisphere; - // BijectiveSampler concept types + // BackwardTractableSampler concept types (not bijective: z input is consumed for hemisphere selection) using scalar_type = T; using domain_type = vector_t3; using codomain_type = vector_t3; @@ -109,13 +111,6 @@ struct ProjectedSphere return L; } - static domain_type generateInverse(const codomain_type L) - { - // NOTE: incomplete information to recover exact z component; we only know which hemisphere L came from, - // so we return a canonical value (0.0 for upper, 1.0 for lower) that round-trips correctly through __generate - return vector_t3(hemisphere_t::generateInverse(L), hlsl::mix(T(1.0), T(0.0), L.z > T(0.0))); - } - static density_type forwardPdf(const cache_type cache) { cache_type hc; diff --git a/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl index 85b0c4c73b..bd03c30add 100644 --- a/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl @@ -7,7 +7,7 @@ #include #include -#include +#include namespace nbl { @@ -16,28 +16,6 @@ namespace hlsl namespace sampling { -namespace concepts -{ - -// clang-format off -#define NBL_CONCEPT_NAME CumulativeProbabilityAccessor -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename) -#define NBL_CONCEPT_TPLT_PRM_NAMES (T)(Scalar) -#define NBL_CONCEPT_PARAM_0 (accessor, T) -#define NBL_CONCEPT_PARAM_1 (index, uint32_t) -NBL_CONCEPT_BEGIN(2) -#define accessor NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -#define index NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 -NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE)(T::value_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((accessor[index]), ::nbl::hlsl::is_same_v, Scalar))); -#undef index -#undef accessor -#include -// clang-format on - -} // namespace concepts - // Discrete sampler using cumulative probability lookup via upper_bound. // // Samples a discrete index in [0, N) with probability proportional to @@ -49,13 +27,14 @@ NBL_CONCEPT_END( // // Satisfies TractableSampler and ResamplableSampler (not BackwardTractableSampler: // the mapping is discrete). -template) +template) struct CumulativeProbabilitySampler { using scalar_type = T; - using domain_type = scalar_type; - using codomain_type = uint32_t; + using domain_type = Domain; + using codomain_type = Codomain; using density_type = scalar_type; using weight_type = density_type; @@ -65,25 +44,6 @@ struct CumulativeProbabilitySampler density_type upperBound; }; - // Stateful comparator that tracks the CDF values seen during binary search. - // upper_bound uses lower_to_upper_comparator_transform_t which calls !comp(rhs, lhs), - // so our operator() receives (value=u, rhs=cumProb[testPoint]). - struct CdfComparator - { - bool operator()(const density_type value, const density_type rhs) - { - const bool retval = value < rhs; - if (retval) - upperBound = rhs; - else - oneBefore = rhs; - return retval; - } - - density_type oneBefore; - density_type upperBound; - }; - static CumulativeProbabilitySampler create(NBL_CONST_REF_ARG(CumProbAccessor) _cumProbAccessor, uint32_t _size) { CumulativeProbabilitySampler retval; @@ -93,16 +53,31 @@ struct CumulativeProbabilitySampler } // BasicSampler interface - codomain_type generate(const domain_type u) + codomain_type generate(const domain_type u) NBL_CONST_MEMBER_FUNC { // upper_bound returns first index where cumProb > u return hlsl::upper_bound(cumProbAccessor, 0u, storedCount, u); } // TractableSampler interface - codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { - CdfComparator comp; + // Stateful comparator that tracks the CDF values seen during binary search. + struct CdfComparator + { + bool operator()(const density_type value, const density_type rhs) + { + const bool retval = value < rhs; + if (retval) + upperBound = rhs; + else + oneBefore = rhs; + return retval; + } + + density_type oneBefore; + density_type upperBound; + } comp; comp.oneBefore = density_type(0.0); comp.upperBound = density_type(1.0); const codomain_type result = hlsl::upper_bound(cumProbAccessor, 0u, storedCount, u, comp); @@ -111,24 +86,31 @@ struct CumulativeProbabilitySampler return result; } - density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) + density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { return cache.upperBound - cache.oneBefore; } - weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) + weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { return forwardPdf(cache); } - density_type backwardPdf(const codomain_type v) + density_type backwardPdf(const codomain_type v) NBL_CONST_MEMBER_FUNC { - const density_type cur = (v < storedCount) ? cumProbAccessor[v] : density_type(1.0); - const density_type prev = (v > 0u) ? cumProbAccessor[v - 1u] : density_type(0.0); - return cur - prev; + density_type retval = density_type(1.0); + if (v < storedCount) + cumProbAccessor.template get(v, retval); + if (v) + { + density_type prev; + cumProbAccessor.template get(v - 1u, prev); + retval -= prev; + } + return retval; } - weight_type backwardWeight(const codomain_type v) + weight_type backwardWeight(const codomain_type v) NBL_CONST_MEMBER_FUNC { return backwardPdf(v); } diff --git a/include/nbl/builtin/hlsl/sampling/cumulative_probability_builder.h b/include/nbl/builtin/hlsl/sampling/cumulative_probability_builder.h index b8f59ceb54..a511fc2d8c 100644 --- a/include/nbl/builtin/hlsl/sampling/cumulative_probability_builder.h +++ b/include/nbl/builtin/hlsl/sampling/cumulative_probability_builder.h @@ -7,6 +7,7 @@ #include #include +#include #include namespace nbl @@ -19,9 +20,12 @@ namespace sampling // Builds a normalized cumulative histogram from an array of non-negative weights. // Output has N-1 entries (last bucket implicitly 1.0). template -void computeNormalizedCumulativeHistogram(const T* weights, uint32_t N, T* outCumProb) +void computeNormalizedCumulativeHistogram(std::span weights, T* outCumProb) { - std::inclusive_scan(weights, weights + N - 1, outCumProb); + const auto N = weights.size(); + if (N < 2) + return; + std::inclusive_scan(weights.begin(), weights.end() - 1, outCumProb); const T normalizationFactor = T(1) / (outCumProb[N - 2] + weights[N - 1]); std::for_each(outCumProb, outCumProb + N - 1, [normalizationFactor](T& v) { v *= normalizationFactor; }); } diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index f39fd73698..7bb5467dfb 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -22,7 +22,7 @@ struct Linear using scalar_type = T; using vector2_type = vector; - // BijectiveSampler concept types + // BackwardTractableSampler concept types using domain_type = scalar_type; using codomain_type = scalar_type; using density_type = scalar_type; @@ -30,7 +30,7 @@ struct Linear struct cache_type { - domain_type u; + scalar_type diffTimesX; }; static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end), assumed to be at x=0 and x=1 @@ -46,39 +46,35 @@ struct Linear return retval; } - density_type __pdf(const codomain_type x) + density_type __pdf(const codomain_type x) NBL_CONST_MEMBER_FUNC { assert(x >= scalar_type(0.0) && x <= scalar_type(1.0)); return scalar_type(2.0) * (linearCoeffStart + x * linearCoeffDiff) * rcpCoeffSum; } - codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { - cache.u = u; - return hlsl::mix(u, (linearCoeffStart - sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * negRcpDiff, abs(negRcpDiff) < hlsl::numeric_limits::max); + const codomain_type x = hlsl::mix(u, (linearCoeffStart - sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * negRcpDiff, abs(negRcpDiff) < hlsl::numeric_limits::max); + cache.diffTimesX = linearCoeffDiff * x; + return x; } - domain_type generateInverse(const codomain_type x) + density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC { - return x * (scalar_type(2.0) * linearCoeffStart + linearCoeffDiff * x) * rcpCoeffSum; + return scalar_type(2.0) * (linearCoeffStart + cache.diffTimesX) * rcpCoeffSum; } - density_type forwardPdf(const cache_type cache) - { - return scalar_type(2.0) * nbl::hlsl::sqrt(squaredCoeffStart + cache.u * squaredCoeffDiff) * rcpCoeffSum; - } - - weight_type forwardWeight(const cache_type cache) + weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC { return forwardPdf(cache); } - density_type backwardPdf(const codomain_type x) + density_type backwardPdf(const codomain_type x) NBL_CONST_MEMBER_FUNC { return __pdf(x); } - weight_type backwardWeight(const codomain_type x) + weight_type backwardWeight(const codomain_type x) NBL_CONST_MEMBER_FUNC { return backwardPdf(x); } diff --git a/include/nbl/builtin/hlsl/sampling/polar_mapping.hlsl b/include/nbl/builtin/hlsl/sampling/polar_mapping.hlsl new file mode 100644 index 0000000000..875c4fae19 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/polar_mapping.hlsl @@ -0,0 +1,64 @@ +// 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_SAMPLING_POLAR_MAPPING_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_POLAR_MAPPING_INCLUDED_ + +#include "nbl/builtin/hlsl/tgmath.hlsl" +#include "nbl/builtin/hlsl/numbers.hlsl" + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +template +struct PolarMapping +{ + using scalar_type = T; + using vector2_type = vector; + + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type {}; + + static codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + { + const scalar_type r = hlsl::sqrt(u.x); + const scalar_type phi = scalar_type(2) * numbers::pi * u.y; + return vector2_type(r * hlsl::cos(phi), r * hlsl::sin(phi)); + } + + static codomain_type generate(const domain_type u) + { + cache_type dummy; + return generate(u, dummy); + } + + static domain_type generateInverse(const codomain_type p) + { + const scalar_type r2 = p.x * p.x + p.y * p.y; + scalar_type phi = hlsl::atan2(p.y, p.x); + phi += hlsl::mix(scalar_type(0), scalar_type(2) * numbers::pi, phi < scalar_type(0)); + return vector2_type(r2, phi * (scalar_type(0.5) * numbers::inv_pi)); + } + + static density_type forwardPdf(cache_type cache) { return numbers::inv_pi; } + static density_type backwardPdf(codomain_type v) { return numbers::inv_pi; } + + static weight_type forwardWeight(cache_type cache) { return forwardPdf(cache); } + static weight_type backwardWeight(codomain_type v) { return backwardPdf(v); } +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl + +#endif diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index c6591b7a37..6e4d58ee88 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -18,7 +18,12 @@ namespace hlsl namespace sampling { -template +// Template parameter `UsePdfAsWeight`: when true (default), forwardWeight/backwardWeight +// return the PDF instead of the projected-solid-angle MIS weight. +// TODO: the projected-solid-angle MIS weight (UsePdfAsWeight=false) has been shown to be +// poor in practice. Once confirmed by testing, remove the false path and stop storing +// receiverNormal, receiverWasBSDF, and rcpProjSolidAngle as members. +template struct ProjectedSphericalTriangle { using scalar_type = T; @@ -41,22 +46,24 @@ struct ProjectedSphericalTriangle // NOTE: produces a degenerate (all-zero) bilinear patch when the receiver normal faces away // from all three triangle vertices, resulting in NaN PDFs (0 * inf). Callers must ensure // at least one vertex has positive projection onto the receiver normal. - static ProjectedSphericalTriangle create(NBL_REF_ARG(shapes::SphericalTriangle) shape, const vector3_type receiverNormal, const bool receiverWasBSDF) + static ProjectedSphericalTriangle create(NBL_REF_ARG(shapes::SphericalTriangle) shape, const vector3_type _receiverNormal, const bool _receiverWasBSDF) { - ProjectedSphericalTriangle retval; + ProjectedSphericalTriangle retval; retval.sphtri = SphericalTriangle::create(shape); + retval.receiverNormal = _receiverNormal; + retval.receiverWasBSDF = _receiverWasBSDF; const scalar_type minimumProjSolidAngle = 0.0; matrix m = matrix(shape.vertices[0], shape.vertices[1], shape.vertices[2]); - const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(receiverWasBSDF, hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); + const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(_receiverWasBSDF, hlsl::mul(m, _receiverNormal), hlsl::promote(minimumProjSolidAngle)); retval.bilinearPatch = Bilinear::create(bxdfPdfAtVertex.yyxz); - const scalar_type projSA = shape.projectedSolidAngle(receiverNormal); + const scalar_type projSA = shape.projectedSolidAngle(_receiverNormal); retval.rcpProjSolidAngle = projSA > scalar_type(0.0) ? scalar_type(1.0) / projSA : scalar_type(0.0); return retval; } - codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { Bilinear bilinear = bilinearPatch; const vector2_type warped = bilinear.generate(u, cache.bilinearCache); @@ -66,35 +73,38 @@ struct ProjectedSphericalTriangle return L; } - density_type forwardPdf(const cache_type cache) + density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC { return sphtri.rcpSolidAngle * bilinearPatch.forwardPdf(cache.bilinearCache); } - weight_type forwardWeight(const cache_type cache) + weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC { + if (UsePdfAsWeight) + return forwardPdf(cache); return cache.abs_cos_theta * rcpProjSolidAngle; } - density_type backwardPdf(const vector3_type L) + density_type backwardPdf(const vector3_type L) NBL_CONST_MEMBER_FUNC { const vector2_type u = sphtri.generateInverse(L); return sphtri.rcpSolidAngle * bilinearPatch.backwardPdf(u); } - weight_type backwardWeight(const vector3_type L) + weight_type backwardWeight(const vector3_type L) NBL_CONST_MEMBER_FUNC { - cache_type cache; - // cache.bilinearCache; // unused - // approximate abs(cos_theta) via bilinear interpolation at the inverse-mapped point - const vector2_type u = sphtri.generateInverse(L); - cache.abs_cos_theta = bilinearPatch.backwardWeight(u); - return forwardWeight(cache); + if (UsePdfAsWeight) + return backwardPdf(L); + const scalar_type minimumProjSolidAngle = 0.0; + const scalar_type abs_cos_theta = math::conditionalAbsOrMax(receiverWasBSDF, hlsl::dot(receiverNormal, L), minimumProjSolidAngle); + return abs_cos_theta * rcpProjSolidAngle; } sampling::SphericalTriangle sphtri; Bilinear bilinearPatch; scalar_type rcpProjSolidAngle; + vector3_type receiverNormal; + bool receiverWasBSDF; }; } // namespace sampling diff --git a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl index 056f5a91c7..7521048b37 100644 --- a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl +++ b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl @@ -38,10 +38,10 @@ struct quotient_and_pdf return retval; } - Q quotient() { return _quotient; } - P pdf() { return _pdf; } + Q quotient() NBL_CONST_MEMBER_FUNC { return _quotient; } + P pdf() NBL_CONST_MEMBER_FUNC { return _pdf; } - Q value() + Q value() NBL_CONST_MEMBER_FUNC { return _quotient * _pdf; } diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index 38a22617e8..d9d0bf079f 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -68,7 +68,7 @@ struct SphericalRectangle return retval; } - codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[2]); angle_adder.addCosine(cosGamma[3]); @@ -94,22 +94,22 @@ struct SphericalRectangle return vector2_type((xu - r0.x), (yv - r0.y)); } - density_type forwardPdf(const cache_type cache) + density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC { return scalar_type(1.0) / solidAngle; } - weight_type forwardWeight(const cache_type cache) + weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC { return forwardPdf(cache); } - density_type backwardPdf(const codomain_type L) + density_type backwardPdf(const codomain_type L) NBL_CONST_MEMBER_FUNC { return scalar_type(1.0) / solidAngle; } - weight_type backwardWeight(const codomain_type L) + weight_type backwardWeight(const codomain_type L) NBL_CONST_MEMBER_FUNC { return backwardPdf(L); } diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 0cc90d0d78..c76dbd440e 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -49,7 +49,7 @@ struct SphericalTriangle return retval; } - codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { scalar_type negSinSubSolidAngle, negCosSubSolidAngle; const scalar_type solidAngle = scalar_type(1.0) / rcpSolidAngle; @@ -84,7 +84,7 @@ struct SphericalTriangle return retval; } - domain_type generateInverse(const codomain_type L) + domain_type generateInverse(const codomain_type L) NBL_CONST_MEMBER_FUNC { const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri_vertices[1]); const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); @@ -112,22 +112,22 @@ struct SphericalTriangle return vector2_type(u, v); } - density_type forwardPdf(const cache_type cache) + density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC { return rcpSolidAngle; } - weight_type forwardWeight(const cache_type cache) + weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC { return forwardPdf(cache); } - density_type backwardPdf(const codomain_type L) + density_type backwardPdf(const codomain_type L) NBL_CONST_MEMBER_FUNC { return rcpSolidAngle; } - weight_type backwardWeight(const codomain_type L) + weight_type backwardWeight(const codomain_type L) NBL_CONST_MEMBER_FUNC { return backwardPdf(L); } diff --git a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl index 3d764eac92..ce259bd78b 100644 --- a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl @@ -17,6 +17,27 @@ namespace hlsl namespace sampling { +namespace impl +{ +template +vector directionFromZandPhi(const T z, const T phiSample) +{ + const T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); + const T phi = T(2.0) * numbers::pi * phiSample; + return vector(r * hlsl::cos(phi), r * hlsl::sin(phi), z); +} + +template +T phiSampleFromDirection(const T x, const T y) +{ + T phi = hlsl::atan2(y, x); + const T twopi = T(2.0) * numbers::pi; + phi /= twopi; + phi += hlsl::select(phi < T(0.0), T(1.0), T(0.0)); + return phi; +} +} + template) struct UniformHemisphere { @@ -34,10 +55,7 @@ struct UniformHemisphere static codomain_type __generate(const domain_type _sample) { - T z = _sample.x; - T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); - T phi = T(2.0) * numbers::pi * _sample.y; - return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); + return impl::directionFromZandPhi(_sample.x, _sample.y); } static codomain_type generate(const domain_type _sample) @@ -52,10 +70,7 @@ struct UniformHemisphere static domain_type __generateInverse(const codomain_type _sample) { - T phi = hlsl::atan2(_sample.y, _sample.x); - const T twopi = T(2.0) * numbers::pi; - phi += hlsl::mix(T(0.0), twopi, phi < T(0.0)); - return vector_t2(_sample.z, phi / twopi); + return vector_t2(_sample.z, impl::phiSampleFromDirection(_sample.x, _sample.y)); } static domain_type generateInverse(const codomain_type _sample) @@ -75,11 +90,13 @@ struct UniformHemisphere static density_type backwardPdf(const vector_t3 _sample) { + assert(_sample.z > 0); return T(0.5) * numbers::inv_pi; } - static weight_type backwardWeight(const codomain_type sample) + static weight_type backwardWeight(const codomain_type _sample) { + assert(_sample.z > 0); return T(0.5) * numbers::inv_pi; } @@ -103,13 +120,7 @@ struct UniformSphere static codomain_type __generate(const domain_type _sample) { - // Map _sample.x from [0,1] into hemisphere sample + sign flip: - // upper hemisphere when _sample.x < 0.5, lower when >= 0.5 - const bool chooseLower = _sample.x >= T(0.5); - const T hemiX = chooseLower ? (T(2.0) * _sample.x - T(1.0)) : (T(2.0) * _sample.x); - vector_t3 retval = hemisphere_t::__generate(vector_t2(hemiX, _sample.y)); - retval.z = chooseLower ? (-retval.z) : retval.z; - return retval; + return impl::directionFromZandPhi(T(1.0) - T(2.0) * _sample.x, _sample.y); } static codomain_type generate(const domain_type _sample) @@ -124,12 +135,8 @@ struct UniformSphere static domain_type __generateInverse(const codomain_type _sample) { - const bool isLower = _sample.z < T(0.0); - const vector_t3 hemiSample = vector_t3(_sample.x, _sample.y, hlsl::abs(_sample.z)); - vector_t2 hemiUV = hemisphere_t::__generateInverse(hemiSample); - // Recover _sample.x: upper hemisphere maps [0,0.5], lower maps [0.5,1] - hemiUV.x = isLower ? (hemiUV.x * T(0.5) + T(0.5)) : (hemiUV.x * T(0.5)); - return hemiUV; + // Inverse of z = 1 - 2*x => x = (1 - z) / 2 + return vector_t2((T(1.0) - _sample.z) * T(0.5), impl::phiSampleFromDirection(_sample.x, _sample.y)); } static domain_type generateInverse(const codomain_type _sample) @@ -152,9 +159,9 @@ struct UniformSphere return T(0.5) * hemisphere_t::backwardPdf(_sample); } - static weight_type backwardWeight(const codomain_type sample) + static weight_type backwardWeight(const codomain_type _sample) { - return T(0.5) * hemisphere_t::backwardWeight(sample); + return T(0.5) * hemisphere_t::backwardWeight(_sample); } }; diff --git a/include/nbl/builtin/hlsl/sampling/value_and_pdf.hlsl b/include/nbl/builtin/hlsl/sampling/value_and_pdf.hlsl index a037c0e3d8..8c54d34c5e 100644 --- a/include/nbl/builtin/hlsl/sampling/value_and_pdf.hlsl +++ b/include/nbl/builtin/hlsl/sampling/value_and_pdf.hlsl @@ -25,8 +25,8 @@ struct value_and_rcpPdf return retval; } - V value() { return _value; } - P rcpPdf() { return _rcpPdf; } + V value() NBL_CONST_MEMBER_FUNC { return _value; } + P rcpPdf() NBL_CONST_MEMBER_FUNC { return _rcpPdf; } V _value; P _rcpPdf; @@ -45,8 +45,8 @@ struct value_and_pdf return retval; } - V value() { return _value; } - P pdf() { return _pdf; } + V value() NBL_CONST_MEMBER_FUNC { return _value; } + P pdf() NBL_CONST_MEMBER_FUNC { return _pdf; } V _value; P _pdf; diff --git a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl index 7ba5ae7079..12243622bf 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl @@ -32,10 +32,10 @@ struct SphericalTriangle nbl::hlsl::normalize(vertices[1] - origin), nbl::hlsl::normalize(vertices[2] - origin) }; - return create(normalizedVerts); + return createFromUnitSphereVertices(normalizedVerts); } - static SphericalTriangle create(const vector3_type normalizedVertices[3]) + static SphericalTriangle createFromUnitSphereVertices(const vector3_type normalizedVertices[3]) { SphericalTriangle retval; retval.vertices[0] = normalizedVertices[0]; From 9b4ee22b1575a6b6106eb71e67c7eb14324bf508 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Fri, 27 Mar 2026 02:18:48 +0300 Subject: [PATCH 31/33] update examples_tests --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index da109c5050..b33f66a84e 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit da109c5050e1c3361b852278a2a18c3983e51891 +Subproject commit b33f66a84e2438bd530e687eb9c3b85b4dc78274 From a07e433a6a1290d383d369eee4891eb8ad60a0b0 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Sat, 28 Mar 2026 22:03:32 +0300 Subject: [PATCH 32/33] changed forwardPdf/Weight signature to take L, better core::to_string() for floats, separeted SMaxError into a separate file with support to other types than float --- examples_tests | 2 +- .../builtin/hlsl/bxdf/base/lambertian.hlsl | 7 +- .../builtin/hlsl/sampling/alias_table.hlsl | 4 +- .../nbl/builtin/hlsl/sampling/bilinear.hlsl | 6 +- .../hlsl/sampling/box_muller_transform.hlsl | 6 +- .../hlsl/sampling/concentric_mapping.hlsl | 4 +- .../nbl/builtin/hlsl/sampling/concepts.hlsl | 30 ++++--- .../hlsl/sampling/cos_weighted_spheres.hlsl | 35 ++++---- .../hlsl/sampling/cumulative_probability.hlsl | 6 +- include/nbl/builtin/hlsl/sampling/linear.hlsl | 6 +- .../builtin/hlsl/sampling/polar_mapping.hlsl | 4 +- .../projected_spherical_triangle.hlsl | 15 ++-- .../hlsl/sampling/spherical_rectangle.hlsl | 6 +- .../hlsl/sampling/spherical_triangle.hlsl | 6 +- .../hlsl/sampling/uniform_spheres.hlsl | 12 +-- .../nbl/builtin/hlsl/testing/max_error.hlsl | 82 +++++++++++++++++++ include/nbl/system/to_string.h | 10 +++ src/nbl/builtin/CMakeLists.txt | 1 + 18 files changed, 169 insertions(+), 73 deletions(-) create mode 100644 include/nbl/builtin/hlsl/testing/max_error.hlsl diff --git a/examples_tests b/examples_tests index f76b427823..ff397d05b6 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit f76b42782352ab6d305114c5199e4d946a17db7e +Subproject commit ff397d05b6157353b66bbccf0b964cd92b691608 diff --git a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl index 526b969045..24f7b49dd2 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl @@ -76,13 +76,14 @@ struct SLambertianBase quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(isotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { + // only z component matters: ProjectedHemisphere::forwardPdf reads v.z + const vector3_type L = vector3_type(0, 0, _sample.getNdotL(_clamp)); typename sampling::ProjectedHemisphere::cache_type cache; - cache.L_z = _sample.getNdotL(_clamp); scalar_type p; NBL_IF_CONSTEXPR (IsBSDF) - p = sampling::ProjectedSphere::forwardPdf(cache); + p = sampling::ProjectedSphere::forwardPdf(L, cache); else - p = sampling::ProjectedHemisphere::forwardPdf(cache); + p = sampling::ProjectedHemisphere::forwardPdf(L, cache); return quotient_pdf_type::create(scalar_type(1.0), p); } quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC diff --git a/include/nbl/builtin/hlsl/sampling/alias_table.hlsl b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl index 3e54db4bbd..63a177b95f 100644 --- a/include/nbl/builtin/hlsl/sampling/alias_table.hlsl +++ b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl @@ -98,12 +98,12 @@ struct AliasTable return result; } - density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC + density_type forwardPdf(const codomain_type v, NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { return cache.pdf; } - weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC + weight_type forwardWeight(const codomain_type v, NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { return cache.pdf; } diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index 072f2cc1ca..0585422b44 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -65,14 +65,14 @@ struct Bilinear return p; } - density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC + density_type forwardPdf(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { return cache.normalizedStart + cache.linearXCache.diffTimesX; } - weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC + weight_type forwardWeight(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { - return forwardPdf(cache); + return forwardPdf(v, cache); } density_type backwardPdf(const codomain_type p) NBL_CONST_MEMBER_FUNC diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index 531550cea7..3b7447c9f7 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -51,7 +51,7 @@ struct BoxMullerTransform return cache.direction * nbl::hlsl::sqrt(scalar_type(-2.0) * nbl::hlsl::log(u.x)) * stddev; } - density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC + density_type forwardPdf(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { return halfRcpStddev2 * numbers::inv_pi * cache.u_x; } @@ -66,9 +66,9 @@ struct BoxMullerTransform ); } - weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC + weight_type forwardWeight(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { - return forwardPdf(cache); + return forwardPdf(v, cache); } density_type backwardPdf(const codomain_type outPos) NBL_CONST_MEMBER_FUNC diff --git a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl index f67d240ec7..03c2f38a84 100644 --- a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl @@ -102,10 +102,10 @@ struct ConcentricMapping } // The PDF of Shirley mapping is constant (1/PI on the unit disk) - static density_type forwardPdf(cache_type cache) { return numbers::inv_pi; } + static density_type forwardPdf(const codomain_type v, cache_type cache) { return numbers::inv_pi; } static density_type backwardPdf(codomain_type v) { return numbers::inv_pi; } - static weight_type forwardWeight(cache_type cache) { return forwardPdf(cache); } + static weight_type forwardWeight(const codomain_type v, cache_type cache) { return forwardPdf(v, cache); } static weight_type backwardWeight(codomain_type v) { return backwardPdf(v); } }; diff --git a/include/nbl/builtin/hlsl/sampling/concepts.hlsl b/include/nbl/builtin/hlsl/sampling/concepts.hlsl index 1645867362..13014bb280 100644 --- a/include/nbl/builtin/hlsl/sampling/concepts.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concepts.hlsl @@ -104,16 +104,21 @@ NBL_CONCEPT_END( // redundant recomputation. If there is no common computation between // generate and backwardPdf, the cache simply stores the codomain value. // -// For constant-pdf samplers, forwardPdf(cache) == __pdf() (cache ignored). +// For constant-pdf samplers, forwardPdf(sample, cache) == __pdf() (both args ignored). // For complex samplers, cache carries intermediate values derived from // the domain input and forwardPdf computes the pdf from those. // +// The codomain_type sample parameter is passed alongside the cache to help +// the GPU compiler with dead code elimination: if generate's return value +// and a copy inside the cache would otherwise occupy separate registers, +// passing the sample explicitly makes the identity visible to the compiler. +// // Required types: // domain_type, codomain_type, density_type, cache_type // // Required methods: // codomain_type generate(domain_type u, out cache_type cache) -// density_type forwardPdf(cache_type cache) +// density_type forwardPdf(codomain_type sample, cache_type cache) // ============================================================================ // clang-format off @@ -122,19 +127,22 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (_sampler, T) #define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) -#define NBL_CONCEPT_PARAM_2 (cache, typename T::cache_type) -NBL_CONCEPT_BEGIN(3) +#define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) +#define NBL_CONCEPT_PARAM_3 (cache, typename T::cache_type) +NBL_CONCEPT_BEGIN(4) #define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 -#define cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) ((NBL_CONCEPT_REQ_TYPE)(T::density_type)) ((NBL_CONCEPT_REQ_TYPE)(T::cache_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.generate(u, cache)), ::nbl::hlsl::is_same_v, typename T::codomain_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.forwardPdf(cache)), ::nbl::hlsl::is_same_v, typename T::density_type))); + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.forwardPdf(v, cache)), ::nbl::hlsl::is_same_v, typename T::density_type))); #undef cache +#undef v #undef u #undef _sampler #include @@ -158,7 +166,7 @@ NBL_CONCEPT_END( // // Required methods: // codomain_type generate(domain_type u, out cache_type cache) -// weight_type forwardWeight(cache_type cache) - forward weight for MIS +// weight_type forwardWeight(codomain_type v, cache_type cache) - forward weight for MIS // weight_type backwardWeight(codomain_type v) - backward weight for RIS // ============================================================================ @@ -181,7 +189,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::cache_type)) ((NBL_CONCEPT_REQ_TYPE)(T::weight_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.generate(u, cache)), ::nbl::hlsl::is_same_v, typename T::codomain_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(cache)), ::nbl::hlsl::is_same_v, typename T::weight_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(v, cache)), ::nbl::hlsl::is_same_v, typename T::weight_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardWeight(v)), ::nbl::hlsl::is_same_v, typename T::weight_type))); #undef cache #undef v @@ -207,7 +215,7 @@ NBL_CONCEPT_END( // // Required methods (in addition to TractableSampler): // density_type backwardPdf(codomain_type v) - evaluate pdf at codomain value v -// weight_type forwardWeight(cache_type cache) - weight for MIS, reuses generate cache +// weight_type forwardWeight(codomain_type v, cache_type cache) - weight for MIS, reuses generate cache // weight_type backwardWeight(codomain_type v) - weight for RIS, evaluated at v // ============================================================================ @@ -228,7 +236,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(TractableSampler, T)) ((NBL_CONCEPT_REQ_TYPE)(T::weight_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(cache)), ::nbl::hlsl::is_same_v, typename T::weight_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(v, cache)), ::nbl::hlsl::is_same_v, typename T::weight_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardWeight(v)), ::nbl::hlsl::is_same_v, typename T::weight_type))); #undef cache #undef v @@ -247,7 +255,7 @@ NBL_CONCEPT_END( // of the Jacobian matrix of the inverse equals the reciprocal of the // absolute value of the determinant of the Jacobian matrix of the forward // mapping (the Jacobian is an NxM matrix, not a scalar): -// backwardPdf(v) == 1.0 / forwardPdf(cache) (where v == generate(u, cache)) +// backwardPdf(v) == 1.0 / forwardPdf(v, cache) (where v == generate(u, cache)) // // Required methods (in addition to BackwardTractableSampler): // domain_type generateInverse(codomain_type v) diff --git a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl index 30c1d732f0..4adf667f88 100644 --- a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl @@ -30,7 +30,6 @@ struct ProjectedHemisphere struct cache_type { - scalar_type L_z; }; static codomain_type __generate(const domain_type _sample) @@ -42,9 +41,7 @@ struct ProjectedHemisphere static codomain_type generate(const domain_type _sample, NBL_REF_ARG(cache_type) cache) { - const codomain_type L = __generate(_sample); - cache.L_z = L.z; - return L; + return __generate(_sample); } static domain_type generateInverse(const codomain_type L) @@ -52,23 +49,21 @@ struct ProjectedHemisphere return ConcentricMapping::generateInverse(L.xy); } - static density_type forwardPdf(const cache_type cache) + static density_type forwardPdf(const codomain_type v, const cache_type cache) { - return cache.L_z * numbers::inv_pi; + return v.z * numbers::inv_pi; } - static weight_type forwardWeight(const cache_type cache) + static weight_type forwardWeight(const codomain_type v, const cache_type cache) { - return forwardPdf(cache); + return forwardPdf(v, cache); } static density_type backwardPdf(const codomain_type L) { assert(L.z > 0); - cache_type c; - c.L_z = L.z; - return forwardPdf(c); + return forwardPdf(L, c); } static weight_type backwardWeight(const codomain_type L) @@ -106,28 +101,26 @@ struct ProjectedSphere static codomain_type generate(NBL_REF_ARG(domain_type) _sample, NBL_REF_ARG(cache_type) cache) { - const codomain_type L = __generate(_sample); - cache.L_z = L.z; - return L; + return __generate(_sample); } - static density_type forwardPdf(const cache_type cache) + static density_type forwardPdf(const codomain_type v, const cache_type cache) { + codomain_type absV = v; + absV.z = hlsl::abs(v.z); cache_type hc; - hc.L_z = hlsl::abs(cache.L_z); - return T(0.5) * hemisphere_t::forwardPdf(hc); + return T(0.5) * hemisphere_t::forwardPdf(absV, hc); } - static weight_type forwardWeight(const cache_type cache) + static weight_type forwardWeight(const codomain_type v, const cache_type cache) { - return forwardPdf(cache); + return forwardPdf(v, cache); } static density_type backwardPdf(const codomain_type L) { cache_type c; - c.L_z = L.z; - return forwardPdf(c); + return forwardPdf(L, c); } static weight_type backwardWeight(const codomain_type L) diff --git a/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl index bd03c30add..c7936949a3 100644 --- a/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl @@ -86,14 +86,14 @@ struct CumulativeProbabilitySampler return result; } - density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC + density_type forwardPdf(const codomain_type v, NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { return cache.upperBound - cache.oneBefore; } - weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC + weight_type forwardWeight(const codomain_type v, NBL_CONST_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { - return forwardPdf(cache); + return forwardPdf(v, cache); } density_type backwardPdf(const codomain_type v) NBL_CONST_MEMBER_FUNC diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index 7bb5467dfb..459d27f5cb 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -59,14 +59,14 @@ struct Linear return x; } - density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC + density_type forwardPdf(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { return scalar_type(2.0) * (linearCoeffStart + cache.diffTimesX) * rcpCoeffSum; } - weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC + weight_type forwardWeight(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { - return forwardPdf(cache); + return forwardPdf(v, cache); } density_type backwardPdf(const codomain_type x) NBL_CONST_MEMBER_FUNC diff --git a/include/nbl/builtin/hlsl/sampling/polar_mapping.hlsl b/include/nbl/builtin/hlsl/sampling/polar_mapping.hlsl index 875c4fae19..b30a183488 100644 --- a/include/nbl/builtin/hlsl/sampling/polar_mapping.hlsl +++ b/include/nbl/builtin/hlsl/sampling/polar_mapping.hlsl @@ -50,10 +50,10 @@ struct PolarMapping return vector2_type(r2, phi * (scalar_type(0.5) * numbers::inv_pi)); } - static density_type forwardPdf(cache_type cache) { return numbers::inv_pi; } + static density_type forwardPdf(const codomain_type v, cache_type cache) { return numbers::inv_pi; } static density_type backwardPdf(codomain_type v) { return numbers::inv_pi; } - static weight_type forwardWeight(cache_type cache) { return forwardPdf(cache); } + static weight_type forwardWeight(const codomain_type v, cache_type cache) { return forwardPdf(v, cache); } static weight_type backwardWeight(codomain_type v) { return backwardPdf(v); } }; diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index 6e4d58ee88..43ea34515c 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -40,6 +40,7 @@ struct ProjectedSphericalTriangle struct cache_type { scalar_type abs_cos_theta; + vector2_type warped; typename Bilinear::cache_type bilinearCache; }; @@ -66,22 +67,22 @@ struct ProjectedSphericalTriangle codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) NBL_CONST_MEMBER_FUNC { Bilinear bilinear = bilinearPatch; - const vector2_type warped = bilinear.generate(u, cache.bilinearCache); + cache.warped = bilinear.generate(u, cache.bilinearCache); typename SphericalTriangle::cache_type sphtriCache; - const vector3_type L = sphtri.generate(warped, sphtriCache); - cache.abs_cos_theta = bilinear.forwardWeight(cache.bilinearCache); + const vector3_type L = sphtri.generate(cache.warped, sphtriCache); + cache.abs_cos_theta = bilinear.forwardWeight(cache.warped, cache.bilinearCache); return L; } - density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC + density_type forwardPdf(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { - return sphtri.rcpSolidAngle * bilinearPatch.forwardPdf(cache.bilinearCache); + return sphtri.rcpSolidAngle * bilinearPatch.forwardPdf(cache.warped, cache.bilinearCache); } - weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC + weight_type forwardWeight(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { if (UsePdfAsWeight) - return forwardPdf(cache); + return forwardPdf(v, cache); return cache.abs_cos_theta * rcpProjSolidAngle; } diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index d9d0bf079f..8f6542566c 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -94,14 +94,14 @@ struct SphericalRectangle return vector2_type((xu - r0.x), (yv - r0.y)); } - density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC + density_type forwardPdf(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { return scalar_type(1.0) / solidAngle; } - weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC + weight_type forwardWeight(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { - return forwardPdf(cache); + return forwardPdf(v, cache); } density_type backwardPdf(const codomain_type L) NBL_CONST_MEMBER_FUNC diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index c76dbd440e..3a760dc503 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -112,14 +112,14 @@ struct SphericalTriangle return vector2_type(u, v); } - density_type forwardPdf(const cache_type cache) NBL_CONST_MEMBER_FUNC + density_type forwardPdf(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { return rcpSolidAngle; } - weight_type forwardWeight(const cache_type cache) NBL_CONST_MEMBER_FUNC + weight_type forwardWeight(const codomain_type v, const cache_type cache) NBL_CONST_MEMBER_FUNC { - return forwardPdf(cache); + return forwardPdf(v, cache); } density_type backwardPdf(const codomain_type L) NBL_CONST_MEMBER_FUNC diff --git a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl index ce259bd78b..9606e77708 100644 --- a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl @@ -78,12 +78,12 @@ struct UniformHemisphere return __generateInverse(_sample); } - static density_type forwardPdf(const cache_type cache) + static density_type forwardPdf(const codomain_type v, const cache_type cache) { return T(0.5) * numbers::inv_pi; } - static weight_type forwardWeight(const cache_type cache) + static weight_type forwardWeight(const codomain_type v, const cache_type cache) { return T(0.5) * numbers::inv_pi; } @@ -144,14 +144,14 @@ struct UniformSphere return __generateInverse(_sample); } - static density_type forwardPdf(const cache_type cache) + static density_type forwardPdf(const codomain_type v, const cache_type cache) { - return T(0.5) * hemisphere_t::forwardPdf(cache); + return T(0.5) * hemisphere_t::forwardPdf(v, cache); } - static weight_type forwardWeight(const cache_type cache) + static weight_type forwardWeight(const codomain_type v, const cache_type cache) { - return T(0.5) * hemisphere_t::forwardWeight(cache); + return T(0.5) * hemisphere_t::forwardWeight(v, cache); } static density_type backwardPdf(const vector_t3 _sample) diff --git a/include/nbl/builtin/hlsl/testing/max_error.hlsl b/include/nbl/builtin/hlsl/testing/max_error.hlsl new file mode 100644 index 0000000000..beb0e3004b --- /dev/null +++ b/include/nbl/builtin/hlsl/testing/max_error.hlsl @@ -0,0 +1,82 @@ +#ifndef _NBL_BUILTIN_HLSL_TESTING_MAX_ERROR_INCLUDED_ +#define _NBL_BUILTIN_HLSL_TESTING_MAX_ERROR_INCLUDED_ + +#include +#include +#include +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace testing +{ + +struct SMaxError +{ + float64_t abs = 0.0; + float64_t rel = 0.0; + + void updateScalar(float64_t expected, float64_t tested) + { + abs = hlsl::max(hlsl::abs(expected - tested), abs); + if (expected != 0.0 && tested != 0.0) + rel = hlsl::max(hlsl::max(expected / tested, tested / expected) - 1.0, rel); + } +}; + +namespace impl +{ + +template +struct MaxErrorUpdater; + +template +NBL_PARTIAL_REQ_TOP(concepts::FloatingPointLikeScalar) +struct MaxErrorUpdater) > +{ + static void __call(NBL_REF_ARG(SMaxError) record, NBL_CONST_REF_ARG(FloatingPoint) expected, NBL_CONST_REF_ARG(FloatingPoint) tested) + { + record.updateScalar(float64_t(expected), float64_t(tested)); + } +}; + +template +NBL_PARTIAL_REQ_TOP(concepts::FloatingPointLikeVectorial) +struct MaxErrorUpdater) > +{ + static void __call(NBL_REF_ARG(SMaxError) record, NBL_CONST_REF_ARG(FloatingPointVector) expected, NBL_CONST_REF_ARG(FloatingPointVector) tested) + { + using traits = nbl::hlsl::vector_traits; + for (uint32_t i = 0; i < traits::Dimension; ++i) + MaxErrorUpdater::__call(record, expected[i], tested[i]); + } +}; + +template +NBL_PARTIAL_REQ_TOP(concepts::Matricial && concepts::FloatingPointLikeScalar::scalar_type>) +struct MaxErrorUpdater && concepts::FloatingPointLikeScalar::scalar_type>) > +{ + static void __call(NBL_REF_ARG(SMaxError) record, NBL_CONST_REF_ARG(FloatingPointMatrix) expected, NBL_CONST_REF_ARG(FloatingPointMatrix) tested) + { + using traits = nbl::hlsl::matrix_traits; + for (uint32_t i = 0; i < traits::RowCount; ++i) + MaxErrorUpdater::__call(record, expected[i], tested[i]); + } +}; + +} + +template +void updateMaxError(NBL_REF_ARG(SMaxError) record, NBL_CONST_REF_ARG(T) expected, NBL_CONST_REF_ARG(T) tested) +{ + impl::MaxErrorUpdater::__call(record, expected, tested); +} + +} +} +} + +#endif diff --git a/include/nbl/system/to_string.h b/include/nbl/system/to_string.h index 2a06ace5e5..1f8988566e 100644 --- a/include/nbl/system/to_string.h +++ b/include/nbl/system/to_string.h @@ -1,6 +1,7 @@ #ifndef _NBL_SYSTEM_TO_STRING_INCLUDED_ #define _NBL_SYSTEM_TO_STRING_INCLUDED_ +#include #include #include #include @@ -21,6 +22,15 @@ struct to_string_helper } }; +template +struct to_string_helper +{ + static std::string __call(const T& value) + { + return std::format("{}", value); + } +}; + template<> struct to_string_helper { diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index 839c5e3778..4f14bb5f5d 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -399,5 +399,6 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/relative_approx_compa LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/approx_compare.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/orientation_compare.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/vector_length_compare.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/max_error.hlsl") ADD_CUSTOM_BUILTIN_RESOURCES(nblBuiltinResourceData NBL_RESOURCES_TO_EMBED "${NBL_ROOT_PATH}/include" "nbl/builtin" "nbl::builtin" "${NBL_ROOT_PATH_BINARY}/include" "${NBL_ROOT_PATH_BINARY}/src" "STATIC" "INTERNAL") From 1b46ed920c7cfddb3d5aaef8819723856c3f1bc9 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Sat, 28 Mar 2026 22:05:36 +0300 Subject: [PATCH 33/33] update examples_tests --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index ff397d05b6..cfefa021df 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit ff397d05b6157353b66bbccf0b964cd92b691608 +Subproject commit cfefa021dfe3a0b6e18b44a37243ab92119ed57d