From 9be65a049ada37fa6156b575ab4ff97285253fe7 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 1 Dec 2025 16:15:12 +0700 Subject: [PATCH 001/101] changed quaternion struct name, brought in quaternion stuff from przemek's remove_core_matrix branch, added create from mat3x3 --- .../nbl/builtin/hlsl/math/quaternions.hlsl | 139 +++++++++++++++++- .../transformation_matrix_utils.hlsl | 21 +-- .../hlsl/sampling/spherical_triangle.hlsl | 4 +- 3 files changed, 147 insertions(+), 17 deletions(-) diff --git a/include/nbl/builtin/hlsl/math/quaternions.hlsl b/include/nbl/builtin/hlsl/math/quaternions.hlsl index aca8d1ff3c..502e56c8e2 100644 --- a/include/nbl/builtin/hlsl/math/quaternions.hlsl +++ b/include/nbl/builtin/hlsl/math/quaternions.hlsl @@ -14,23 +14,152 @@ namespace hlsl namespace math { +template +struct truncated_quaternion +{ + using this_t = truncated_quaternion; + using scalar_type = T; + using data_type = vector; + + static this_t create() + { + this_t q; + q.data = data_type(0.0, 0.0, 0.0); + return q; + } + + data_type data; +}; + template -struct quaternion_t +struct quaternion { - using this_t = quaternion_t; + using this_t = quaternion; using scalar_type = T; using data_type = vector; using vector3_type = vector; using matrix_type = matrix; - static this_t createFromTruncated(const vector3_type first3Components) + static this_t create() + { + this_t q; + q.data = data_type(0.0, 0.0, 0.0, 1.0); + return q; + } + + static this_t create(scalar_type x, scalar_type y, scalar_type z, scalar_type w) + { + this_t q; + q.data = data_type(x, y, z, w); + return q; + } + + static this_t create(NBL_CONST_REF_ARG(this_t) other) + { + return other; + } + + static this_t create(scalar_type pitch, scalar_type yaw, scalar_type roll) + { + const scalar_type rollDiv2 = roll * scalar_type(0.5); + const scalar_type sr = hlsl::sin(rollDiv2); + const scalar_type cr = hlsl::cos(rollDiv2); + + const scalar_type pitchDiv2 = pitch * scalar_type(0.5); + const scalar_type sp = hlsl::sin(pitchDiv2); + const scalar_type cp = hlsl::cos(pitchDiv2); + + const scalar_type yawDiv2 = yaw * scalar_type(0.5); + const scalar_type sy = hlsl::sin(yawDiv2); + const scalar_type cy = hlsl::cos(yawDiv2); + + this_t output; + output.data[0] = cr * sp * cy + sr * cp * sy; // x + output.data[1] = cr * cp * sy - sr * sp * cy; // y + output.data[2] = sr * cp * cy - cr * sp * sy; // z + output.data[3] = cr * cp * cy + sr * sp * sy; // w + + return output; + } + + static this_t create(NBL_CONST_REF_ARG(matrix_type) m) + { + using AsUint = typename unsigned_integer_of_size::type; + const scalar_type m00 = m[0][0], m11 = m[1][1], m22 = m[2][2]; + const scalar_type neg_m00 = bit_cast(bit_cast(m00)^0x80000000u); + const scalar_type neg_m11 = bit_cast(bit_cast(m11)^0x80000000u); + const scalar_type neg_m22 = bit_cast(bit_cast(m22)^0x80000000u); + const data_type Qx = data_type(m00, m00, neg_m00, neg_m00); + const data_type Qy = data_type(m11, neg_m11, m11, neg_m11); + const data_type Qz = data_type(m22, neg_m22, neg_m22, m22); + + const data_type tmp = hlsl::promote(1.0) + Qx + Qy + Qz; + const data_type invscales = hlsl::promote(0.5) / hlsl::sqrt(tmp); + const data_type scales = tmp * invscales * hlsl::promote(0.5); + + // TODO: speed this up + this_t retval; + if (tmp.x > scalar_type(0.0)) + { + retval.data.x = (m[2][1] - m[1][2]) * invscales.x; + retval.data.y = (m[0][2] - m[2][0]) * invscales.x; + retval.data.z = (m[1][0] - m[0][1]) * invscales.x; + retval.data.w = scales.x; + } + else + { + if (tmp.y > scalar_type(0.0)) + { + retval.data.x = scales.y; + retval.data.y = (m[0][1] + m[1][0]) * invscales.y; + retval.data.z = (m[2][0] + m[0][2]) * invscales.y; + retval.data.w = (m[2][1] - m[1][2]) * invscales.y; + } + else if (tmp.z > scalar_type(0.0)) + { + retval.data.x = (m[0][1] + m[1][0]) * invscales.z; + retval.data.y = scales.z; + retval.data.z = (m[0][2] - m[2][0]) * invscales.z; + retval.data.w = (m[1][2] + m[2][1]) * invscales.z; + } + else + { + retval.data.x = (m[0][2] + m[2][0]) * invscales.w; + retval.data.y = (m[1][2] + m[2][1]) * invscales.w; + retval.data.z = scales.w; + retval.data.w = (m[1][0] - m[0][1]) * invscales.w; + } + } + + retval.data = hlsl::normalize(retval.data); + return retval; + } + + static this_t createFromTruncated(NBL_CONST_REF_ARG(truncated_quaternion) first3Components) { this_t retval; - retval.data.xyz = first3Components; - retval.data.w = hlsl::sqrt(scalar_type(1.0) - hlsl::dot(first3Components, first3Components)); + retval.data.xyz = first3Components.data; + retval.data.w = hlsl::sqrt(scalar_type(1.0) - hlsl::dot(first3Components.data, first3Components.data)); return retval; } + this_t operator*(scalar_type scalar) + { + this_t output; + output.data = data * scalar; + return output; + } + + this_t operator*(NBL_CONST_REF_ARG(this_t) other) + { + return this_t::create( + data.w * other.data.w - data.x * other.x - data.y * other.data.y - data.z * other.data.z, + data.w * other.data.x + data.x * other.w + data.y * other.data.z - data.z * other.data.y, + data.w * other.data.y - data.x * other.z + data.y * other.data.w + data.z * other.data.x, + data.w * other.data.z + data.x * other.y - data.y * other.data.x + data.z * other.data.w + ); + } + static this_t lerp(const this_t start, const this_t end, const scalar_type fraction, const scalar_type totalPseudoAngle) { using AsUint = typename unsigned_integer_of_size::type; diff --git a/include/nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl b/include/nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl index 1ad16dc28d..b1f4ad94dc 100644 --- a/include/nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl +++ b/include/nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl @@ -2,6 +2,7 @@ #define _NBL_BUILTIN_HLSL_TRANSFORMATION_MATRIX_UTILS_INCLUDED_ #include +#include namespace nbl { @@ -125,28 +126,28 @@ inline matrix buildCameraLookAtMatrixRH( //! Replaces curent rocation and scale by rotation represented by quaternion `quat`, leaves 4th row and 4th colum unchanged template -inline void setRotation(matrix& outMat, NBL_CONST_REF_ARG(core::quaternion) quat) +inline void setRotation(matrix& outMat, NBL_CONST_REF_ARG(math::quaternion) quat) { static_assert(N == 3 || N == 4); outMat[0] = vector( - 1 - 2 * (quat.y * quat.y + quat.z * quat.z), - 2 * (quat.x * quat.y - quat.z * quat.w), - 2 * (quat.x * quat.z + quat.y * quat.w), + 1 - 2 * (quat.data.y * quat.data.y + quat.data.z * quat.data.z), + 2 * (quat.data.x * quat.data.y - quat.data.z * quat.data.w), + 2 * (quat.data.x * quat.data.z + quat.data.y * quat.data.w), outMat[0][3] ); outMat[1] = vector( - 2 * (quat.x * quat.y + quat.z * quat.w), - 1 - 2 * (quat.x * quat.x + quat.z * quat.z), - 2 * (quat.y * quat.z - quat.x * quat.w), + 2 * (quat.data.x * quat.data.y + quat.data.z * quat.data.w), + 1 - 2 * (quat.data.x * quat.data.x + quat.data.z * quat.data.z), + 2 * (quat.data.y * quat.data.z - quat.data.x * quat.data.w), outMat[1][3] ); outMat[2] = vector( - 2 * (quat.x * quat.z - quat.y * quat.w), - 2 * (quat.y * quat.z + quat.x * quat.w), - 1 - 2 * (quat.x * quat.x + quat.y * quat.y), + 2 * (quat.data.x * quat.data.z - quat.data.y * quat.data.w), + 2 * (quat.data.y * quat.data.z + quat.data.x * quat.data.w), + 1 - 2 * (quat.data.x * quat.data.x + quat.data.y * quat.data.y), outMat[2][3] ); } diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 0c86b69793..c31e194788 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -51,7 +51,7 @@ struct SphericalTriangle { 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_t::slerp_delta(tri.vertex0, tri.vertex2 * csc_b, cosAngleAlongAC); + C_s += math::quaternion::slerp_delta(tri.vertex0, tri.vertex2 * csc_b, cosAngleAlongAC); } vector3_type retval = tri.vertex1; @@ -61,7 +61,7 @@ struct SphericalTriangle { 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_t::slerp_delta(tri.vertex1, C_s * csc_b_s, cosAngleAlongBC_s); + retval += math::quaternion::slerp_delta(tri.vertex1, C_s * csc_b_s, cosAngleAlongBC_s); } return retval; } From 3e03467410a4f0b73c169af0067b4488afeecdc2 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 2 Dec 2025 12:05:11 +0700 Subject: [PATCH 002/101] _static_create quaternion to matrix, create quaternion from axis-angle --- .../nbl/builtin/hlsl/math/quaternions.hlsl | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/include/nbl/builtin/hlsl/math/quaternions.hlsl b/include/nbl/builtin/hlsl/math/quaternions.hlsl index 502e56c8e2..823b5bff17 100644 --- a/include/nbl/builtin/hlsl/math/quaternions.hlsl +++ b/include/nbl/builtin/hlsl/math/quaternions.hlsl @@ -59,6 +59,18 @@ struct quaternion return other; } + // angle: Rotation angle expressed in radians. + // axis: Rotation axis, must be normalized. + static this_t create(scalar_type angle, const vector3_type axis) + { + this_t q; + const scalar_type sinTheta = hlsl::sin(angle * 0.5); + const scalar_type cosTheta = hlsl::cos(angle * 0.5); + q.data = data_type(axis.x * sinTheta, axis.y * sinTheta, axis.z * sinTheta, cosTheta); + return q; + } + + static this_t create(scalar_type pitch, scalar_type yaw, scalar_type roll) { const scalar_type rollDiv2 = roll * scalar_type(0.5); @@ -227,6 +239,19 @@ struct quaternion }; } + +namespace impl +{ +template +struct static_cast_helper, math::quaternion > +{ + static inline matrix cast(math::quaternion q) + { + return q.constructMatrix(); + } +}; +} + } } From 34d1385f9baa3463085a494c4077442c5c141945 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 2 Dec 2025 16:46:46 +0700 Subject: [PATCH 003/101] added more quaternion funcs: transformVec, inverse, normalize + static cast to/from truncated quat --- .../nbl/builtin/hlsl/math/quaternions.hlsl | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/include/nbl/builtin/hlsl/math/quaternions.hlsl b/include/nbl/builtin/hlsl/math/quaternions.hlsl index 823b5bff17..8070626cc6 100644 --- a/include/nbl/builtin/hlsl/math/quaternions.hlsl +++ b/include/nbl/builtin/hlsl/math/quaternions.hlsl @@ -40,6 +40,8 @@ struct quaternion using vector3_type = vector; using matrix_type = matrix; + using AsUint = typename unsigned_integer_of_size::type; + static this_t create() { this_t q; @@ -66,7 +68,7 @@ struct quaternion this_t q; const scalar_type sinTheta = hlsl::sin(angle * 0.5); const scalar_type cosTheta = hlsl::cos(angle * 0.5); - q.data = data_type(axis.x * sinTheta, axis.y * sinTheta, axis.z * sinTheta, cosTheta); + q.data = data_type(axis * sinTheta, cosTheta); return q; } @@ -96,7 +98,6 @@ struct quaternion static this_t create(NBL_CONST_REF_ARG(matrix_type) m) { - using AsUint = typename unsigned_integer_of_size::type; const scalar_type m00 = m[0][0], m11 = m[1][1], m22 = m[2][2]; const scalar_type neg_m00 = bit_cast(bit_cast(m00)^0x80000000u); const scalar_type neg_m11 = bit_cast(bit_cast(m11)^0x80000000u); @@ -147,7 +148,7 @@ struct quaternion return retval; } - static this_t createFromTruncated(NBL_CONST_REF_ARG(truncated_quaternion) first3Components) + static this_t create(NBL_CONST_REF_ARG(truncated_quaternion) first3Components) { this_t retval; retval.data.xyz = first3Components.data; @@ -174,7 +175,6 @@ struct quaternion static this_t lerp(const this_t start, const this_t end, const scalar_type fraction, const scalar_type totalPseudoAngle) { - using AsUint = typename unsigned_integer_of_size::type; const AsUint negationMask = hlsl::bit_cast(totalPseudoAngle) & AsUint(0x80000000u); const data_type adjEnd = hlsl::bit_cast(hlsl::bit_cast(end.data) ^ negationMask); @@ -208,6 +208,13 @@ struct quaternion return retval; } + vector3_type transformVector(const vector3_type v) + { + scalar_type scale = hlsl::length(data); + vector3_type direction = data.xyz; + return v * scale + hlsl::cross(direction, v * data.w + hlsl::cross(direction, v)) * scalar_type(2.0); + } + matrix_type constructMatrix() { matrix_type mat; @@ -235,6 +242,23 @@ struct quaternion return precompPart * cosAngle + hlsl::cross(planeNormal, precompPart); } + this_t inverse() + { + this_t retval; + retval.data.x = bit_cast(bit_cast(data.x)^0x80000000u); + retval.data.y = bit_cast(bit_cast(data.y)^0x80000000u); + retval.data.z = bit_cast(bit_cast(data.z)^0x80000000u); + retval.data.w = data.w; + return retval; + } + + static this_t normalize(NBL_CONST_REF_ARG(this_t) q) + { + this_t retval; + retval.data = hlsl::normalize(q.data); + return retval; + } + data_type data; }; @@ -242,6 +266,29 @@ struct quaternion namespace impl { + +template +struct static_cast_helper, math::truncated_quaternion > +{ + static inline math::quaternion cast(math::truncated_quaternion q) + { + return math::quaternion::create(q); + } +}; + +template +struct static_cast_helper, math::quaternion > +{ + static inline math::truncated_quaternion cast(math::quaternion q) + { + math::truncated_quaternion t; + t.data.x = t.data.x; + t.data.y = t.data.y; + t.data.z = t.data.z; + return t; + } +}; + template struct static_cast_helper, math::quaternion > { From 61540d64d42c0bf0d546a326698004e58c9f94c2 Mon Sep 17 00:00:00 2001 From: Przemog1 Date: Thu, 11 Dec 2025 17:07:59 +0100 Subject: [PATCH 004/101] Modified `LoadableImage` and `ResolveAccessorBase` concepts --- .../concepts/accessors/loadable_image.hlsl | 6 +-- include/nbl/builtin/hlsl/rwmc/resolve.hlsl | 47 +++++++++---------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl index 8c7251214d..924ee240d0 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl @@ -25,8 +25,8 @@ namespace accessors // declare concept #define NBL_CONCEPT_NAME LoadableImage -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)(int32_t) -#define NBL_CONCEPT_TPLT_PRM_NAMES (U)(T)(Dims) +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)(int32_t)(int32_t) +#define NBL_CONCEPT_TPLT_PRM_NAMES (U)(T)(Dims)(Components) // not the greatest syntax but works #define NBL_CONCEPT_PARAM_0 (a,U) #define NBL_CONCEPT_PARAM_1 (uv,vector) @@ -38,7 +38,7 @@ NBL_CONCEPT_BEGIN(3) #define uv NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define layer NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer)), ::nbl::hlsl::is_same_v, vector)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer)), ::nbl::hlsl::is_same_v, vector)) ); #undef layer #undef uv diff --git a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl index d8f777d277..64cec7dfe3 100644 --- a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl @@ -16,21 +16,21 @@ namespace rwmc { // declare concept #define NBL_CONCEPT_NAME ResolveAccessorBase -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)(int32_t) -#define NBL_CONCEPT_TPLT_PRM_NAMES (T)(VectorScalarType)(Dims) +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)(int32_t)(int32_t) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T)(VectorScalarType)(Dims)(Components) // not the greatest syntax but works #define NBL_CONCEPT_PARAM_0 (a,T) -#define NBL_CONCEPT_PARAM_1 (scalar,VectorScalarType) +#define NBL_CONCEPT_PARAM_1 (vec,vector) // start concept - NBL_CONCEPT_BEGIN(2) +NBL_CONCEPT_BEGIN(2) // need to be defined AFTER the concept begins #define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -#define scalar NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define vec NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR)((a.calcLuma(vector(scalar, scalar, scalar))))) + ((NBL_CONCEPT_REQ_EXPR)((a.calcLuma(vec)))) ); #undef a -#undef scalar +#undef vec #include /* ResolveAccessor is required to: @@ -38,14 +38,15 @@ NBL_CONCEPT_END( * - implement function called `calcLuma` which calculates luma from a 3 component pixel value */ -template -NBL_BOOL_CONCEPT ResolveAccessor = ResolveAccessorBase && concepts::accessors::LoadableImage; +template +NBL_BOOL_CONCEPT ResolveAccessor = ResolveAccessorBase && concepts::accessors::LoadableImage; template struct ResolveAccessorAdaptor { using output_scalar_type = OutputScalar; - using output_type = vector; + NBL_CONSTEXPR int32_t Components = 3; + using output_type = vector; NBL_CONSTEXPR int32_t image_dimension = 2; RWTexture2DArray cascade; @@ -63,13 +64,13 @@ struct ResolveAccessorAdaptor int16_t2 cascadeImageDimension = int16_t2(imgWidth, imgHeight); if (any(uv < int16_t2(0, 0)) || any(uv > cascadeImageDimension)) - return vector(0, 0, 0, 0); + return promote(0); return cascade.Load(int32_t3(uv, int32_t(layer))); } }; -template && ResolveAccessor) +template && ResolveAccessor) struct Resolver { using output_type = OutputColorTypeVec; @@ -92,12 +93,10 @@ struct Resolver output_type operator()(NBL_REF_ARG(CascadeAccessor) acc, const int16_t2 coord) { - using scalar_t = typename vector_traits::scalar_type; - scalar_t reciprocalBaseI = 1.f; CascadeSample curr = __sampleCascade(acc, coord, 0u, reciprocalBaseI); - output_type accumulation = output_type(0.0f, 0.0f, 0.0f); + output_type accumulation = promote(0.0f); scalar_t Emin = params.initialEmin; scalar_t prevNormalizedCenterLuma, prevNormalizedNeighbourhoodAverageLuma; @@ -162,15 +161,15 @@ struct Resolver CascadeSample __sampleCascade(NBL_REF_ARG(CascadeAccessor) acc, int16_t2 coord, uint16_t cascadeIndex, scalar_t reciprocalBaseI) { output_type neighbourhood[9]; - neighbourhood[0] = acc.template get(coord + int16_t2(-1, -1), cascadeIndex).xyz; - neighbourhood[1] = acc.template get(coord + int16_t2(0, -1), cascadeIndex).xyz; - neighbourhood[2] = acc.template get(coord + int16_t2(1, -1), cascadeIndex).xyz; - neighbourhood[3] = acc.template get(coord + int16_t2(-1, 0), cascadeIndex).xyz; - neighbourhood[4] = acc.template get(coord + int16_t2(0, 0), cascadeIndex).xyz; - neighbourhood[5] = acc.template get(coord + int16_t2(1, 0), cascadeIndex).xyz; - neighbourhood[6] = acc.template get(coord + int16_t2(-1, 1), cascadeIndex).xyz; - neighbourhood[7] = acc.template get(coord + int16_t2(0, 1), cascadeIndex).xyz; - neighbourhood[8] = acc.template get(coord + int16_t2(1, 1), cascadeIndex).xyz; + neighbourhood[0] = acc.template get(coord + int16_t2(-1, -1), cascadeIndex); + neighbourhood[1] = acc.template get(coord + int16_t2(0, -1), cascadeIndex); + neighbourhood[2] = acc.template get(coord + int16_t2(1, -1), cascadeIndex); + neighbourhood[3] = acc.template get(coord + int16_t2(-1, 0), cascadeIndex); + neighbourhood[4] = acc.template get(coord + int16_t2(0, 0), cascadeIndex); + neighbourhood[5] = acc.template get(coord + int16_t2(1, 0), cascadeIndex); + neighbourhood[6] = acc.template get(coord + int16_t2(-1, 1), cascadeIndex); + neighbourhood[7] = acc.template get(coord + int16_t2(0, 1), cascadeIndex); + neighbourhood[8] = acc.template get(coord + int16_t2(1, 1), cascadeIndex); // numerical robustness float32_t3 excl_hood_sum = ((neighbourhood[0] + neighbourhood[1]) + (neighbourhood[2] + neighbourhood[3])) + From 6953879dade4967e4ca0afd2451cc18b13214d87 Mon Sep 17 00:00:00 2001 From: Przemog1 Date: Mon, 22 Dec 2025 13:14:17 +0100 Subject: [PATCH 005/101] Small RWMC fixes --- .../nbl/builtin/hlsl/concepts/accessors/storable_image.hlsl | 6 +++--- include/nbl/builtin/hlsl/rwmc/resolve.hlsl | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/nbl/builtin/hlsl/concepts/accessors/storable_image.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/storable_image.hlsl index 7eda9b9303..900352d993 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/storable_image.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/storable_image.hlsl @@ -18,13 +18,13 @@ namespace accessors { // declare concept #define NBL_CONCEPT_NAME StorableImage -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)(int32_t) -#define NBL_CONCEPT_TPLT_PRM_NAMES (U)(T)(Dims) +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)(int32_t)(int32_t) +#define NBL_CONCEPT_TPLT_PRM_NAMES (U)(T)(Dims)(Components) // not the greatest syntax but works #define NBL_CONCEPT_PARAM_0 (a,U) #define NBL_CONCEPT_PARAM_1 (uv,vector) #define NBL_CONCEPT_PARAM_2 (layer,uint16_t) -#define NBL_CONCEPT_PARAM_3 (data,vector) +#define NBL_CONCEPT_PARAM_3 (data,vector) // start concept NBL_CONCEPT_BEGIN(4) // need to be defined AFTER the cocnept begins diff --git a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl index 64cec7dfe3..34d7c95960 100644 --- a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl @@ -27,7 +27,7 @@ NBL_CONCEPT_BEGIN(2) #define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define vec NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR)((a.calcLuma(vec)))) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.calcLuma(vec)), ::nbl::hlsl::is_same_v, VectorScalarType)) ); #undef a #undef vec From 3d0c45441a8d9fc950e1e88acc31fb928389c650 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 20 Jan 2026 15:56:23 +0700 Subject: [PATCH 006/101] changes to linear, bilinear, box muller for pdf and backward pdf --- .../nbl/builtin/hlsl/sampling/bilinear.hlsl | 21 ++++++++++-------- .../hlsl/sampling/box_muller_transform.hlsl | 6 +++++ include/nbl/builtin/hlsl/sampling/linear.hlsl | 22 ++++++++++--------- .../projected_spherical_triangle.hlsl | 8 +++---- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index a74869990f..fd3632b994 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -28,34 +28,37 @@ struct Bilinear { Bilinear retval; retval.bilinearCoeffs = bilinearCoeffs; - retval.twiceAreasUnderXCurve = vector2_type(bilinearCoeffs[0] + bilinearCoeffs[1], bilinearCoeffs[2] + bilinearCoeffs[3]); + 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 generate(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)); + 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; } - scalar_type pdf(const vector2_type u) + scalar_type backwardPdf(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]); + 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; } // unit square: x0y0 x1y0 // x0y1 x1y1 vector4_type bilinearCoeffs; // (x0y0, x0y1, x1y0, x1y1) - vector2_type twiceAreasUnderXCurve; + vector2_type bilinearCoeffDiffs; + vector2_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 9474642f4c..cdd87ee4dc 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -28,6 +28,12 @@ struct BoxMullerTransform 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; + 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; }; diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index 6c3cf1fad9..7127701117 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -21,26 +21,28 @@ struct Linear using scalar_type = T; using vector2_type = vector; - static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end) + 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; - retval.linearCoeffStart = linearCoeffs[0]; - retval.rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); + scalar_type rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); + retval.linearCoeffStartOverDiff = linearCoeffs[0] * rcpDiff; vector2_type squaredCoeffs = linearCoeffs * linearCoeffs; - retval.squaredCoeffStart = squaredCoeffs[0]; - retval.squaredCoeffDiff = squaredCoeffs[1] - squaredCoeffs[0]; + 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, (linearCoeffStart - hlsl::sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, hlsl::abs(rcpDiff) < numeric_limits::max); + return hlsl::mix(u, (linearCoeffStartOverDiff - hlsl::sqrt(squaredCoeffStartOverDiff + u * squaredCoeffDiffOverDiff)), hlsl::abs(linearCoeffStartOverDiff) < numeric_limits::max); } - scalar_type linearCoeffStart; - scalar_type rcpDiff; - scalar_type squaredCoeffStart; - scalar_type squaredCoeffDiff; + // 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; }; } diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index e60fe28423..b82a10be44 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -49,11 +49,11 @@ struct ProjectedSphericalTriangle // pre-warp according to proj solid angle approximation vector4_type patch = computeBilinearPatch(receiverNormal, isBSDF); Bilinear bilinear = Bilinear::create(patch); - u = bilinear.generate(rcpPdf, _u); + 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; + rcpPdf = solidAngle / bilinear.backwardPdf(u); return L; } @@ -73,7 +73,7 @@ struct ProjectedSphericalTriangle vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); Bilinear bilinear = Bilinear::create(patch); - return pdf * bilinear.pdf(u); + return pdf * bilinear.backwardPdf(u); } scalar_type pdf(const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) @@ -83,7 +83,7 @@ struct ProjectedSphericalTriangle vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); Bilinear bilinear = Bilinear::create(patch); - return pdf * bilinear.pdf(u); + return pdf * bilinear.backwardPdf(u); } shapes::SphericalTriangle tri; From bea01f9c4f020ae0b3ff3f1e1d9545d117b4d6d1 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 20 Jan 2026 16:58:42 +0700 Subject: [PATCH 007/101] get polarized fresnel values by component --- include/nbl/builtin/hlsl/bxdf/fresnel.hlsl | 63 +++++++++++----------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl index 33faa79efc..57e21f04aa 100644 --- a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl @@ -431,14 +431,6 @@ struct Conductor return (rs2 + rp2) * hlsl::promote(0.5); } - // OrientedEtaRcps getRefractionOrientedEtaRcps() NBL_CONST_MEMBER_FUNC - // { - // OrientedEtaRcps rcpEta; - // rcpEta.value = hlsl::promote(1.0) / eta; - // rcpEta.value2 = rcpEta.value * rcpEta.value; - // return rcpEta; - // } - T eta; T etak2; T etaLen2; @@ -563,31 +555,44 @@ struct iridescent_helper const vector_type scale = scalar_type(1.0)/eta12; const vector_type cosTheta2_2 = hlsl::promote(1.0) - hlsl::promote(scalar_type(1.0)-cosTheta_1*cosTheta_1) * scale * scale; notTIR = cosTheta2_2 > hlsl::promote(0.0); - cosTheta_2 = hlsl::sqrt(hlsl::max(cosTheta2_2, hlsl::promote(0.0))); + cosTheta_2 = hlsl::mix(hlsl::promote(0.0), hlsl::sqrt(cosTheta2_2), notTIR); } - if (hlsl::any(notTIR)) + NBL_UNROLL for (uint32_t i = 0; i < vector_traits::Dimension; i++) { - Dielectric::__polarized(eta12 * eta12, hlsl::promote(cosTheta_1), R12p, R12s); - - // Reflected part by the base - // if kappa==0, base material is dielectric - NBL_IF_CONSTEXPR(SupportsTransmission) - Dielectric::__polarized(eta23 * eta23, cosTheta_2, R23p, R23s); + // Check for total internal reflection + if (notTIR[i]) + { + using monochrome_type = vector; + monochrome_type p12, s12, p23, s23; + Dielectric::__polarized(hlsl::promote(eta12[i] * eta12[i]), hlsl::promote(cosTheta_1), p12, s12); + + const monochrome_type eta23_2 = hlsl::promote(eta23[i] * eta23[i]); + + // Reflected part by the base + // if kappa==0, base material is dielectric + NBL_IF_CONSTEXPR(SupportsTransmission) + Dielectric::__polarized(eta23_2, hlsl::promote(cosTheta_2[i]), p23, s23); + else + { + const monochrome_type etaLen2 = eta23_2 + hlsl::promote(etak23[i] * etak23[i]); + Conductor::__polarized(hlsl::promote(eta23[i]), etaLen2, hlsl::promote(cosTheta_2[i]), p23, s23); + } + + R12p[i] = p12[0]; + R12s[i] = s12[0]; + R23p[i] = p23[0]; + R23s[i] = s23[0]; + } else { - vector_type etaLen2 = eta23 * eta23 + etak23 * etak23; - Conductor::__polarized(eta23, etaLen2, cosTheta_2, R23p, R23s); + R12s[i] = scalar_type(0.0); + R12p[i] = scalar_type(0.0); + R23s[i] = scalar_type(0.0); + R23p[i] = scalar_type(0.0); } } - // Check for total internal reflection - const vector_type notTIRFactor = vector_type(notTIR); // 0 when TIR, 1 otherwise - R12s = R12s * notTIRFactor; - R12p = R12p * notTIRFactor; - R23s = R23s * notTIRFactor; - R23p = R23p * notTIRFactor; - // Compute the transmission coefficients vector_type T121p = hlsl::promote(1.0) - R12p; vector_type T121s = hlsl::promote(1.0) - R12s; @@ -703,14 +708,6 @@ struct Iridescent getRefractionOrientedEtaRcps() NBL_CONST_MEMBER_FUNC - // { - // OrientedEtaRcps rcpEta; - // rcpEta.value = hlsl::promote(1.0) / base_type::eta13; - // rcpEta.value2 = rcpEta.value * rcpEta.value; - // return rcpEta; - // } - vector_type getEtak23() NBL_CONST_MEMBER_FUNC { return etak23; From 5a36766e04df1dbcb51b1f311550a47874563eef Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 21 Jan 2026 11:44:21 +0700 Subject: [PATCH 008/101] precompute some phase shift constants --- include/nbl/builtin/hlsl/bxdf/fresnel.hlsl | 23 +++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl index 57e21f04aa..bbb0fd8413 100644 --- a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl @@ -508,10 +508,8 @@ struct iridescent_helper using vector_type = T; // returns phi, the phase shift for each plane of polarization (p,s) - static void phase_shift(const vector_type ior1, const vector_type ior2, const vector_type iork2, const vector_type cosTheta, NBL_REF_ARG(vector_type) phiS, NBL_REF_ARG(vector_type) phiP) + static void phase_shift(const vector_type ior1, const vector_type ior2, const vector_type iork2, const vector_type cosTheta, const vector_type cosTheta2, const vector_type sinTheta2, NBL_REF_ARG(vector_type) phiS, NBL_REF_ARG(vector_type) phiP) { - const vector_type cosTheta2 = cosTheta * cosTheta; - const vector_type sinTheta2 = hlsl::promote(1.0) - cosTheta2; const vector_type ior1_2 = ior1*ior1; const vector_type ior2_2 = ior2*ior2; const vector_type iork2_2 = iork2*iork2; @@ -523,6 +521,7 @@ struct iridescent_helper const vector_type a = hlsl::sqrt(a2); const vector_type b = hlsl::sqrt(b2); + // TODO: very optimizable, especially atan2 usage phiS = hlsl::atan2(scalar_type(2.0) * ior1 * b * cosTheta, a2 + b2 - ior1_2*cosTheta2); const vector_type k2_plus_one = hlsl::promote(1.0) + iork2_2; phiP = hlsl::atan2(scalar_type(2.0) * ior1 * ior2_2 * cosTheta * (scalar_type(2.0) * iork2 * a - (hlsl::promote(1.0) - iork2_2) * b), @@ -549,11 +548,11 @@ struct iridescent_helper { const scalar_type cosTheta_1 = clampedCosTheta; vector_type R12p, R23p, R12s, R23s; - vector_type cosTheta_2; + vector_type cosTheta_2, cosTheta2_2; vector::Dimension> notTIR; { const vector_type scale = scalar_type(1.0)/eta12; - const vector_type cosTheta2_2 = hlsl::promote(1.0) - hlsl::promote(scalar_type(1.0)-cosTheta_1*cosTheta_1) * scale * scale; + cosTheta2_2 = hlsl::promote(1.0) - hlsl::promote(scalar_type(1.0)-cosTheta_1*cosTheta_1) * scale * scale; notTIR = cosTheta2_2 > hlsl::promote(0.0); cosTheta_2 = hlsl::mix(hlsl::promote(0.0), hlsl::sqrt(cosTheta2_2), notTIR); } @@ -604,8 +603,18 @@ struct iridescent_helper vector_type I = hlsl::promote(0.0); // Evaluate the phase shift - phase_shift(ior1, ior2, hlsl::promote(0.0), hlsl::promote(cosTheta_1), phi21s, phi21p); - phase_shift(ior2, ior3, iork3, cosTheta_2, phi23s, phi23p); + { + const vector_type ct = hlsl::promote(cosTheta_1); + const vector_type ct2 = ct * ct; + const vector_type st2 = hlsl::promote(1.0) - ct2; + phase_shift(ior1, ior2, hlsl::promote(0.0), ct, ct2, st2, phi21s, phi21p); + } + { + const vector_type ct = hlsl::promote(cosTheta_2); + const vector_type ct2 = cosTheta2_2; + const vector_type st2 = hlsl::promote(1.0) - ct2; + phase_shift(ior2, ior3, iork3, ct, ct2, st2, phi23s, phi23p); + } phi21p = hlsl::promote(numbers::pi) - phi21p; phi21s = hlsl::promote(numbers::pi) - phi21s; From d079f5b5defa09a0325c8a4d14925016d8705a34 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 21 Jan 2026 12:05:35 +0700 Subject: [PATCH 009/101] removed morton struct superseded by new one --- include/nbl/builtin/hlsl/math/morton.hlsl | 68 ----------------------- 1 file changed, 68 deletions(-) delete mode 100644 include/nbl/builtin/hlsl/math/morton.hlsl diff --git a/include/nbl/builtin/hlsl/math/morton.hlsl b/include/nbl/builtin/hlsl/math/morton.hlsl deleted file mode 100644 index 4a6cb5dfd3..0000000000 --- a/include/nbl/builtin/hlsl/math/morton.hlsl +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O. -// This file is part of the "Nabla Engine". -// For conditions of distribution and use, see copyright notice in nabla.h -#ifndef _NBL_BUILTIN_HLSL_MATH_MORTON_INCLUDED_ -#define _NBL_BUILTIN_HLSL_MATH_MORTON_INCLUDED_ - -#include "nbl/builtin/hlsl/cpp_compat.hlsl" - -namespace nbl -{ -namespace hlsl -{ -namespace math -{ - -namespace impl -{ - -template -struct MortonComponent; - -template -struct MortonComponent -{ - static T decode2d(T x) - { - x &= 0x55555555u; - x = (x ^ (x >> 1u)) & 0x33333333u; - x = (x ^ (x >> 2u)) & 0x0f0f0f0fu; - x = (x ^ (x >> 4u)) & 0x00ff00ffu; - return x; - } -}; - -template -struct MortonComponent -{ - static T decode2d(T x) - { - x &= 0x55555555u; - x = (x ^ (x >> 1u)) & 0x33333333u; - x = (x ^ (x >> 2u)) & 0x0f0f0f0fu; - x = (x ^ (x >> 4u)) & 0x00ff00ffu; - x = (x ^ (x >> 8u)) & 0x0000ffffu; - x = (x ^ (x >> 16u)); - return x; - } -}; - -} - -template -struct Morton -{ - using vector2_type = vector; - using component_type = impl::MortonComponent; - - static vector2_type decode2d(T x) - { - return vector2_type(component_type::decode2d(x), component_type::decode2d(x >> 1u)); - } -}; - -} -} -} - -#endif From 3f8fd482fe6ea8d6e1f3e1e524478d0d2a999a5f Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 21 Jan 2026 15:08:09 +0700 Subject: [PATCH 010/101] changes to solid angle method name, simplified a lot of code in spherical triangle --- .../projected_spherical_triangle.hlsl | 7 ++- .../hlsl/sampling/spherical_triangle.hlsl | 28 +++++---- .../hlsl/shapes/spherical_rectangle.hlsl | 4 +- .../hlsl/shapes/spherical_triangle.hlsl | 58 +++++++------------ 4 files changed, 45 insertions(+), 52 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index b82a10be44..d01712c2f8 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -60,9 +60,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 5770403cd2..4d0a5eafec 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -46,32 +46,35 @@ struct SphericalTriangle 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; + 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.vertex0, tri.vertex2 * csc_b, cosAngleAlongAC); + C_s += math::quaternion::slerp_delta(tri.vertices[0], tri.vertices[2] * csc_b, cosAngleAlongAC); } - vector3_type retval = tri.vertex1; - const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri.vertex1); + 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.vertex1, C_s * csc_b_s, cosAngleAlongBC_s); + 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) { - 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; - rcpPdf = tri.solidAngleOfTriangle(cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c); + rcpPdf = tri.solidAngle(cos_vertices, sin_vertices); return generate(rcpPdf, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, u); } @@ -80,9 +83,9 @@ struct SphericalTriangle { pdf = 1.0 / solidAngle; - const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertex1); + 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.vertex0); + 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_); @@ -104,10 +107,13 @@ struct SphericalTriangle vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, const vector3_type L) { - 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 generateInverse(pdf, solidAngle, cos_vertices, sin_vertices, cos_a, cos_c, csc_b, csc_c, L); } 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 c06f199f144d831c8eac739d393aba0e78104703 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 21 Jan 2026 16:44:34 +0700 Subject: [PATCH 011/101] simplify call to iridescent helper through base struct --- include/nbl/builtin/hlsl/bxdf/fresnel.hlsl | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl index bbb0fd8413..0865e7f5e1 100644 --- a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl @@ -663,6 +663,13 @@ struct iridescent_base using scalar_type = typename vector_traits::scalar_type; using vector_type = T; + template + T __call(const vector_type iork3, const vector_type etak23, const scalar_type clampedCosTheta) NBL_CONST_MEMBER_FUNC + { + return impl::iridescent_helper::template __call(D, ior1, ior2, ior3, iork3, + eta12, eta23, etak23, clampedCosTheta); + } + vector_type D; vector_type ior1; vector_type ior2; @@ -672,6 +679,14 @@ struct iridescent_base vector_type eta23; // thin-film -> base material IOR vector_type eta13; }; + + +// workaround due to DXC bug: github.com/microsoft/DirectXShaderCompiler/issues/5966 +template +T __iridescent_base__call_const(NBL_CONST_REF_ARG(iridescent_base) _this, const T iork3, const T etak23, const typename vector_traits::scalar_type clampedCosTheta) +{ + return _this.template __call(iork3, etak23, clampedCosTheta); +} } template @@ -713,8 +728,7 @@ struct Iridescent::template __call(base_type::D, base_type::ior1, base_type::ior2, base_type::ior3, base_type::iork3, - base_type::eta12, base_type::eta23, getEtak23(), clampedCosTheta); + return impl::__iridescent_base__call_const(this, base_type::iork3, getEtak23(), clampedCosTheta); } vector_type getEtak23() NBL_CONST_MEMBER_FUNC @@ -761,8 +775,7 @@ struct Iridescent::template __call(base_type::D, base_type::ior1, base_type::ior2, base_type::ior3, getEtak23(), - base_type::eta12, base_type::eta23, getEtak23(), clampedCosTheta); + return impl::__iridescent_base__call_const(this, getEtak23(), getEtak23(), clampedCosTheta); } scalar_type getRefractionOrientedEta() NBL_CONST_MEMBER_FUNC { return base_type::eta13[0]; } From 407d0fd6b18f519cc0bc21d8bc730837239afd57 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 22 Jan 2026 11:35:14 +0700 Subject: [PATCH 012/101] 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 4d0a5eafec..2f2d12e1df 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -29,27 +29,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); } @@ -68,18 +71,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; @@ -90,16 +90,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); @@ -107,18 +107,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; }; } From 795b9fa56048133704d9ee65ebf8a4554eb6ac48 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 22 Jan 2026 12:24:57 +0700 Subject: [PATCH 013/101] spherical rectangle stores origin, extent, basis and takes observer instead --- .../hlsl/sampling/spherical_rectangle.hlsl | 23 +++++++++-------- .../hlsl/shapes/spherical_rectangle.hlsl | 25 ++++++++++--------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index f9e3d2f7ae..bcd4f19b7a 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -32,10 +32,11 @@ struct SphericalRectangle return retval; } - vector2_type generate(const vector2_type rectangleExtents, const vector2_type uv, NBL_REF_ARG(scalar_type) S) + vector2_type generate(const vector3_type observer, 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); + 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], @@ -57,27 +58,27 @@ struct SphericalRectangle 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); + // 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 * 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 = rect.r0.y / hlsl::sqrt(d_2 + rect.r0.y * rect.r0.y); + 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 - rect.r0.x) / rectangleExtents.x, (yv - rect.r0.y) / rectangleExtents.y); + return vector2_type((xu - r0.x) / rect.extents.x, (yv - r0.y) / rect.extents.y); } shapes::SphericalRectangle rect; 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 f9b949eaf1657d1b6c04adef1fa7bc93ab82561a Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 22 Jan 2026 14:28:42 +0700 Subject: [PATCH 014/101] 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 140c5c65943def635fce4871bf96c4f389fc72a9 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 22 Jan 2026 16:22:39 +0700 Subject: [PATCH 015/101] removed unused code --- .../transformation_matrix_utils.hlsl | 235 ------------------ src/nbl/builtin/CMakeLists.txt | 1 - 2 files changed, 236 deletions(-) delete mode 100644 include/nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl diff --git a/include/nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl b/include/nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl deleted file mode 100644 index 1ad16dc28d..0000000000 --- a/include/nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl +++ /dev/null @@ -1,235 +0,0 @@ -#ifndef _NBL_BUILTIN_HLSL_TRANSFORMATION_MATRIX_UTILS_INCLUDED_ -#define _NBL_BUILTIN_HLSL_TRANSFORMATION_MATRIX_UTILS_INCLUDED_ - -#include - -namespace nbl -{ -namespace hlsl -{ -//TODO: stolen from cameraz branch, don't have epsilonEqual here, maybe uncomment when merging from imguizmo-lights branch -//// TODO: -> move somewhere else and nbl:: to implement it -//template -//bool isOrthoBase(const T& x, const T& y, const T& z, const E epsilon = 1e-6) -//{ -// auto isNormalized = [](const auto& v, const auto& epsilon) -> bool -// { -// return glm::epsilonEqual(glm::length(v), 1.0, epsilon); -// }; -// -// auto isOrthogonal = [](const auto& a, const auto& b, const auto& epsilon) -> bool -// { -// return glm::epsilonEqual(glm::dot(a, b), 0.0, epsilon); -// }; -// -// return isNormalized(x, epsilon) && isNormalized(y, epsilon) && isNormalized(z, epsilon) && -// isOrthogonal(x, y, epsilon) && isOrthogonal(x, z, epsilon) && isOrthogonal(y, z, epsilon); -//} -//// <- - -template -matrix getMatrix3x4As4x4(const matrix& mat) -{ - matrix output; - for (int i = 0; i < 3; ++i) - output[i] = mat[i]; - output[3] = float32_t4(0.0f, 0.0f, 0.0f, 1.0f); - - return output; -} - -template -matrix getMatrix3x3As4x4(const matrix& mat) -{ - matrix output; - for (int i = 0; i < 3; ++i) - output[i] = float32_t4(mat[i], 1.0f); - output[3] = float32_t4(0.0f, 0.0f, 0.0f, 1.0f); - - return output; -} - -template -inline vector getCastedVector(const vector& in) -{ - vector out; - - for (int i = 0; i < N; ++i) - out[i] = (Tout)(in[i]); - - return out; -} - -template -inline matrix getCastedMatrix(const matrix& in) -{ - matrix out; - - for (int i = 0; i < N; ++i) - out[i] = getCastedVector(in[i]); - - return out; -} - -// TODO: use portable_float when merged -//! multiplies matrices a and b, 3x4 matrices are treated as 4x4 matrices with 4th row set to (0, 0, 0 ,1) -template -inline matrix concatenateBFollowedByA(const matrix& a, const matrix& b) -{ - const auto a4x4 = getMatrix3x4As4x4(a); - const auto b4x4 = getMatrix3x4As4x4(b); - return matrix(mul(a4x4, b4x4)); -} - -// /Arek: glm:: for normalize till dot product is fixed (ambiguity with glm namespace + linker issues) - -template -inline matrix buildCameraLookAtMatrixLH( - const vector& position, - const vector& target, - const vector& upVector) -{ - const vector zaxis = glm::normalize(target - position); - const vector xaxis = glm::normalize(hlsl::cross(upVector, zaxis)); - const vector yaxis = hlsl::cross(zaxis, xaxis); - - matrix r; - r[0] = vector(xaxis, -hlsl::dot(xaxis, position)); - r[1] = vector(yaxis, -hlsl::dot(yaxis, position)); - r[2] = vector(zaxis, -hlsl::dot(zaxis, position)); - - return r; -} - -template -inline matrix buildCameraLookAtMatrixRH( - const vector& position, - const vector& target, - const vector& upVector) -{ - const vector zaxis = glm::normalize(position - target); - const vector xaxis = glm::normalize(hlsl::cross(upVector, zaxis)); - const vector yaxis = hlsl::cross(zaxis, xaxis); - - matrix r; - r[0] = vector(xaxis, -hlsl::dot(xaxis, position)); - r[1] = vector(yaxis, -hlsl::dot(yaxis, position)); - r[2] = vector(zaxis, -hlsl::dot(zaxis, position)); - - return r; -} - -// TODO: test, check if there is better implementation -// TODO: move quaternion to nbl::hlsl -// TODO: why NBL_REF_ARG(MatType) doesn't work????? - -//! Replaces curent rocation and scale by rotation represented by quaternion `quat`, leaves 4th row and 4th colum unchanged -template -inline void setRotation(matrix& outMat, NBL_CONST_REF_ARG(core::quaternion) quat) -{ - static_assert(N == 3 || N == 4); - - outMat[0] = vector( - 1 - 2 * (quat.y * quat.y + quat.z * quat.z), - 2 * (quat.x * quat.y - quat.z * quat.w), - 2 * (quat.x * quat.z + quat.y * quat.w), - outMat[0][3] - ); - - outMat[1] = vector( - 2 * (quat.x * quat.y + quat.z * quat.w), - 1 - 2 * (quat.x * quat.x + quat.z * quat.z), - 2 * (quat.y * quat.z - quat.x * quat.w), - outMat[1][3] - ); - - outMat[2] = vector( - 2 * (quat.x * quat.z - quat.y * quat.w), - 2 * (quat.y * quat.z + quat.x * quat.w), - 1 - 2 * (quat.x * quat.x + quat.y * quat.y), - outMat[2][3] - ); -} - -template -inline void setTranslation(matrix& outMat, NBL_CONST_REF_ARG(vector) translation) -{ - static_assert(N == 3 || N == 4); - - outMat[0].w = translation.x; - outMat[1].w = translation.y; - outMat[2].w = translation.z; -} - - -template -inline matrix buildProjectionMatrixPerspectiveFovRH(float fieldOfViewRadians, float aspectRatio, float zNear, float zFar) -{ - const float h = core::reciprocal(tanf(fieldOfViewRadians * 0.5f)); - _NBL_DEBUG_BREAK_IF(aspectRatio == 0.f); //division by zero - const float w = h / aspectRatio; - - _NBL_DEBUG_BREAK_IF(zNear == zFar); //division by zero - - matrix m; - m[0] = vector(w, 0.f, 0.f, 0.f); - m[1] = vector(0.f, -h, 0.f, 0.f); - m[2] = vector(0.f, 0.f, -zFar / (zFar - zNear), -zNear * zFar / (zFar - zNear)); - m[3] = vector(0.f, 0.f, -1.f, 0.f); - - return m; -} -template -inline matrix buildProjectionMatrixPerspectiveFovLH(float fieldOfViewRadians, float aspectRatio, float zNear, float zFar) -{ - const float h = core::reciprocal(tanf(fieldOfViewRadians * 0.5f)); - _NBL_DEBUG_BREAK_IF(aspectRatio == 0.f); //division by zero - const float w = h / aspectRatio; - - _NBL_DEBUG_BREAK_IF(zNear == zFar); //division by zero - - matrix m; - m[0] = vector(w, 0.f, 0.f, 0.f); - m[1] = vector(0.f, -h, 0.f, 0.f); - m[2] = vector(0.f, 0.f, zFar / (zFar - zNear), -zNear * zFar / (zFar - zNear)); - m[3] = vector(0.f, 0.f, 1.f, 0.f); - - return m; -} - -template -inline matrix buildProjectionMatrixOrthoRH(float widthOfViewVolume, float heightOfViewVolume, float zNear, float zFar) -{ - _NBL_DEBUG_BREAK_IF(widthOfViewVolume == 0.f); //division by zero - _NBL_DEBUG_BREAK_IF(heightOfViewVolume == 0.f); //division by zero - _NBL_DEBUG_BREAK_IF(zNear == zFar); //division by zero - - matrix m; - m[0] = vector(2.f / widthOfViewVolume, 0.f, 0.f, 0.f); - m[1] = vector(0.f, -2.f / heightOfViewVolume, 0.f, 0.f); - m[2] = vector(0.f, 0.f, -1.f / (zFar - zNear), -zNear / (zFar - zNear)); - m[3] = vector(0.f, 0.f, 0.f, 1.f); - - return m; -} - -template -inline matrix buildProjectionMatrixOrthoLH(float widthOfViewVolume, float heightOfViewVolume, float zNear, float zFar) -{ - _NBL_DEBUG_BREAK_IF(widthOfViewVolume == 0.f); //division by zero - _NBL_DEBUG_BREAK_IF(heightOfViewVolume == 0.f); //division by zero - _NBL_DEBUG_BREAK_IF(zNear == zFar); //division by zero - - matrix m; - m[0] = vector(2.f / widthOfViewVolume, 0.f, 0.f, 0.f); - m[1] = vector(0.f, -2.f / heightOfViewVolume, 0.f, 0.f); - m[2] = vector(0.f, 0.f, 1.f / (zFar - zNear), -zNear / (zFar - zNear)); - m[3] = vector(0.f, 0.f, 0.f, 1.f); - - return m; -} - -} -} - -#endif \ No newline at end of file diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index 9a439987b5..ac0903589a 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -222,7 +222,6 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/format.hlsl") #linear algebra LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/math/linalg/fast_affine.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/math/linalg/transform.hlsl") -LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/math/linalg/matrix_utils/transformation_matrix_utils.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/math/linalg/matrix_runtime_traits.hlsl") # TODO: rename `equations` to `polynomials` probably LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/math/functions.hlsl") From 2ce08d92d186c83f5c92e57dc25300f2be1dbe60 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 23 Jan 2026 14:31:28 +0700 Subject: [PATCH 016/101] path tracer (unidirectional) and concepts moved from example --- .../builtin/hlsl/path_tracing/concepts.hlsl | 205 +++++++++++++ .../path_tracing/default_accumulator.hlsl | 43 +++ .../hlsl/path_tracing/unidirectional.hlsl | 290 ++++++++++++++++++ src/nbl/builtin/CMakeLists.txt | 4 + 4 files changed, 542 insertions(+) create mode 100644 include/nbl/builtin/hlsl/path_tracing/concepts.hlsl create mode 100644 include/nbl/builtin/hlsl/path_tracing/default_accumulator.hlsl create mode 100644 include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl new file mode 100644 index 0000000000..b3dc2dd055 --- /dev/null +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -0,0 +1,205 @@ +#ifndef _NBL_BUILTIN_HLSL_PATH_TRACING_CONCEPTS_INCLUDED_ +#define _NBL_BUILTIN_HLSL_PATH_TRACING_CONCEPTS_INCLUDED_ + +#include + +namespace nbl +{ +namespace hlsl +{ +namespace path_tracing +{ +namespace concepts +{ + +#define NBL_CONCEPT_NAME RandGenerator +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (rand, T) +NBL_CONCEPT_BEGIN(1) +#define rand NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::rng_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((rand()), ::nbl::hlsl::is_same_v, typename T::return_type)) +); +#undef rand +#include + +#define NBL_CONCEPT_NAME RayGenerator +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (raygen, T) +#define NBL_CONCEPT_PARAM_1 (randVec, typename T::vector3_type) +NBL_CONCEPT_BEGIN(2) +#define raygen NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define randVec NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::ray_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((raygen.generate(randVec)), ::nbl::hlsl::is_same_v, typename T::ray_type)) +); +#undef randVec +#undef raygen +#include + +#define NBL_CONCEPT_NAME Intersector +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (intersect, T) +#define NBL_CONCEPT_PARAM_1 (ray, typename T::ray_type) +#define NBL_CONCEPT_PARAM_2 (scene, typename T::scene_type) +NBL_CONCEPT_BEGIN(3) +#define intersect NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::scene_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::ray_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::id_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceRay(ray, scene)), ::nbl::hlsl::is_same_v, typename T::id_type)) +); +#undef scene +#undef ray +#undef intersect +#include + +#define NBL_CONCEPT_NAME MaterialSystem +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (matsys, T) +#define NBL_CONCEPT_PARAM_1 (_sample, typename T::sample_type) +#define NBL_CONCEPT_PARAM_2 (matid, uint32_t) +#define NBL_CONCEPT_PARAM_3 (aniso_inter, typename T::anisotropic_interaction_type) +#define NBL_CONCEPT_PARAM_4 (iso_inter, typename T::isotropic_interaction_type) +#define NBL_CONCEPT_PARAM_5 (aniso_cache, typename T::anisocache_type) +#define NBL_CONCEPT_PARAM_6 (iso_cache, typename T::isocache_type) +#define NBL_CONCEPT_PARAM_7 (params, typename T::create_params_t) +#define NBL_CONCEPT_PARAM_8 (u, typename T::vector3_type) +NBL_CONCEPT_BEGIN(9) +#define matsys NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define _sample NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define matid NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define aniso_inter NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 +#define iso_inter NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 +#define aniso_cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 +#define iso_cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 +#define params NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_8 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::quotient_pdf_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::measure_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::anisotropic_interaction_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::isotropic_interaction_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::anisocache_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::isocache_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::create_params_t)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.eval(matid, params, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::measure_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.generate(matid, params, aniso_inter, u, aniso_cache)), ::nbl::hlsl::is_same_v, typename T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.quotient_and_pdf(matid, params, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::quotient_pdf_type)) +); +#undef u +#undef params +#undef iso_cache +#undef aniso_cache +#undef iso_inter +#undef aniso_inter +#undef matid +#undef _sample +#undef matsys +#include + +#define NBL_CONCEPT_NAME NextEventEstimator +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (nee, T) +#define NBL_CONCEPT_PARAM_1 (ray, typename T::ray_type) +#define NBL_CONCEPT_PARAM_2 (scene, typename T::scene_type) +#define NBL_CONCEPT_PARAM_3 (id, uint32_t) +#define NBL_CONCEPT_PARAM_4 (pdf, typename T::scalar_type) +#define NBL_CONCEPT_PARAM_5 (quo_pdf, typename T::quotient_pdf_type) +#define NBL_CONCEPT_PARAM_6 (v, typename T::vector3_type) +#define NBL_CONCEPT_PARAM_7 (interaction, typename T::interaction_type) +#define NBL_CONCEPT_PARAM_8 (b, bool) +NBL_CONCEPT_BEGIN(9) +#define nee NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 +#define pdf NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 +#define quo_pdf NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 +#define interaction NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 +#define b NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_8 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::scene_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::light_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::ray_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::spectral_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::quotient_pdf_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::interaction_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferredEvalAndPdf(pdf, scene, id, ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.generate_and_quotient_and_pdf(quo_pdf, pdf, scene, id, v, interaction, b, v, id)), ::nbl::hlsl::is_same_v, typename T::sample_type)) +); +#undef b +#undef interaction +#undef v +#undef quo_pdf +#undef pdf +#undef id +#undef scene +#undef ray +#undef nee +#include + +#define NBL_CONCEPT_NAME Accumulator +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (acc, T) +#define NBL_CONCEPT_PARAM_1 (sampleCount, uint32_t) +#define NBL_CONCEPT_PARAM_2 (_sample, typename T::input_sample_type) +NBL_CONCEPT_BEGIN(3) +#define acc NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define sampleCount NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define _sample NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::input_sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((acc.addSample(sampleCount, _sample)), ::nbl::hlsl::is_same_v, void)) +); +#undef _sample +#undef sampleCount +#undef acc +#include + +#define NBL_CONCEPT_NAME Scene +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (scene, T) +#define NBL_CONCEPT_PARAM_1 (intersectP, typename T::vector3_type) +#define NBL_CONCEPT_PARAM_2 (id, typename T::id_type) +NBL_CONCEPT_BEGIN(3) +#define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define intersectP NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::id_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getBsdfLightIDs(id)), ::nbl::hlsl::is_same_v, uint32_t)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getNormal(id, intersectP)), ::nbl::hlsl::is_same_v, typename T::vector3_type)) +); +#undef id +#undef intersectP +#undef scene +#include + +} +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/path_tracing/default_accumulator.hlsl b/include/nbl/builtin/hlsl/path_tracing/default_accumulator.hlsl new file mode 100644 index 0000000000..d9df50e023 --- /dev/null +++ b/include/nbl/builtin/hlsl/path_tracing/default_accumulator.hlsl @@ -0,0 +1,43 @@ +#ifndef _NBL_BUILTIN_HLSL_DEFAULT_ACCUMULATOR_INCLUDED_ +#define _NBL_BUILTIN_HLSL_DEFAULT_ACCUMULATOR_INCLUDED_ + +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace path_tracing +{ + +template) +struct DefaultAccumulator +{ + using input_sample_type = OutputTypeVec; + using output_storage_type = OutputTypeVec; + using this_t = DefaultAccumulator; + using scalar_type = typename vector_traits::scalar_type; + + static this_t create() + { + this_t retval; + retval.accumulation = promote(0.0f); + + return retval; + } + + void addSample(uint32_t sampleCount, input_sample_type _sample) + { + scalar_type rcpSampleSize = 1.0 / (sampleCount); + accumulation += (_sample - accumulation) * rcpSampleSize; + } + + output_storage_type accumulation; +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl new file mode 100644 index 0000000000..8e9e207005 --- /dev/null +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -0,0 +1,290 @@ +#ifndef _NBL_BUILTIN_HLSL_PATH_TRACING_UNIDIRECTIONAL_INCLUDED_ +#define _NBL_BUILTIN_HLSL_PATH_TRACING_UNIDIRECTIONAL_INCLUDED_ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace path_tracing +{ + +// TODO: unsure what to do with this, awaiting refactor or turning into concept +template +struct Tolerance +{ + NBL_CONSTEXPR_STATIC_INLINE T INTERSECTION_ERROR_BOUND_LOG2 = -8.0; + + static T __common(uint32_t depth) + { + T depthRcp = 1.0 / T(depth); + return INTERSECTION_ERROR_BOUND_LOG2; + } + + static T getStart(uint32_t depth) + { + return nbl::hlsl::exp2(__common(depth)); + } + + static T getEnd(uint32_t depth) + { + return 1.0 - nbl::hlsl::exp2(__common(depth) + 1.0); + } +}; + +template && concepts::RayGenerator && + concepts::Intersector && concepts::MaterialSystem && + concepts::NextEventEstimator && concepts::Accumulator && + concepts::Scene) +struct Unidirectional +{ + using this_t = Unidirectional; + using randgen_type = RandGen; + using raygen_type = RayGen; + using intersector_type = Intersector; + using material_system_type = MaterialSystem; + using nee_type = NextEventEstimator; + using scene_type = Scene; + + using scalar_type = typename MaterialSystem::scalar_type; + using vector3_type = vector; + using monochrome_type = vector; + using measure_type = typename MaterialSystem::measure_type; + using output_storage_type = typename Accumulator::output_storage_type; // ? + using sample_type = typename NextEventEstimator::sample_type; + using ray_dir_info_type = typename sample_type::ray_dir_info_type; + using ray_type = typename RayGen::ray_type; + using id_type = typename Intersector::id_type; + using light_type = typename NextEventEstimator::light_type; + using bxdfnode_type = typename MaterialSystem::bxdfnode_type; + using anisotropic_interaction_type = typename MaterialSystem::anisotropic_interaction_type; + using isotropic_interaction_type = typename anisotropic_interaction_type::isotropic_interaction_type; + using anisocache_type = typename MaterialSystem::anisocache_type; + using isocache_type = typename anisocache_type::isocache_type; + using quotient_pdf_type = typename NextEventEstimator::quotient_pdf_type; + + using diffuse_op_type = typename MaterialSystem::diffuse_op_type; + using conductor_op_type = typename MaterialSystem::conductor_op_type; + using dielectric_op_type = typename MaterialSystem::dielectric_op_type; + + vector3_type rand3d(uint32_t protoDimension, uint32_t _sample, uint32_t i) + { + using sequence_type = sampling::QuantizedSequence; + uint32_t address = glsl::bitfieldInsert(protoDimension, _sample, MAX_DEPTH_LOG2, MAX_SAMPLES_LOG2); + sequence_type tmpSeq = vk::RawBufferLoad(pSampleBuffer + (address + i) * sizeof(sequence_type)); + // return tmpSeq.template decode(randGen()); + return sampling::decode(tmpSeq,randGen()); + } + + scalar_type getLuma(NBL_CONST_REF_ARG(vector3_type) col) + { + return hlsl::dot(colorspace::scRGBtoXYZ[1], col); + } + + // TODO: probably will only work with isotropic surfaces, need to do aniso + bool closestHitProgram(uint32_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(scene_type) scene) + { + const id_type objectID = ray.objectID; + const vector3_type intersection = ray.origin + ray.direction * ray.intersectionT; + + uint32_t bsdfLightIDs = scene.getBsdfLightIDs(objectID); + vector3_type N = scene.getNormal(objectID, intersection); + N = nbl::hlsl::normalize(N); + ray_dir_info_type V; + V.setDirection(-ray.direction); + isotropic_interaction_type iso_interaction = isotropic_interaction_type::create(V, N); + iso_interaction.luminosityContributionHint = colorspace::scRGBtoXYZ[1]; + anisotropic_interaction_type interaction = anisotropic_interaction_type::create(iso_interaction); + + vector3_type throughput = ray.payload.throughput; + + // emissive + const uint32_t lightID = glsl::bitfieldExtract(bsdfLightIDs, 16, 16); + if (lightID != light_type::INVALID_ID) + { + float _pdf; + ray.payload.accumulation += nee.deferredEvalAndPdf(_pdf, scene, lightID, ray) * throughput / (1.0 + _pdf * _pdf * ray.payload.otherTechniqueHeuristic); + } + + const uint32_t bsdfID = glsl::bitfieldExtract(bsdfLightIDs, 0, 16); + if (bsdfID == bxdfnode_type::INVALID_ID) + return false; + + bxdfnode_type bxdf = materialSystem.bxdfs[bsdfID]; + + // TODO: ifdef kill diffuse specular paths + + const bool isBSDF = material_system_type::isBSDF(bxdf.materialType); + + vector3_type eps0 = rand3d(depth, _sample, 0u); + vector3_type eps1 = rand3d(depth, _sample, 1u); + + // thresholds + const scalar_type bxdfPdfThreshold = 0.0001; + const scalar_type lumaContributionThreshold = getLuma(colorspace::eotf::sRGB((vector3_type)1.0 / 255.0)); // OETF smallest perceptible value + const vector3_type throughputCIE_Y = colorspace::sRGBtoXYZ[1] * throughput; // TODO: this only works if spectral_type is dim 3 + const measure_type eta = bxdf.params.ior1 / bxdf.params.ior0; + const scalar_type monochromeEta = hlsl::dot(throughputCIE_Y, eta) / (throughputCIE_Y.r + throughputCIE_Y.g + throughputCIE_Y.b); // TODO: imaginary eta? + + // sample lights + const scalar_type neeProbability = 1.0; // BSDFNode_getNEEProb(bsdf); + scalar_type rcpChoiceProb; + sampling::PartitionRandVariable partitionRandVariable; + partitionRandVariable.leftProb = neeProbability; + if (!partitionRandVariable(eps0.z, rcpChoiceProb) && depth < 2u) + { + uint32_t randLightID = uint32_t(float32_t(randGen.rng()) / numeric_limits::max) * nee.lightCount; + quotient_pdf_type neeContrib_pdf; + scalar_type t; + sample_type nee_sample = nee.generate_and_quotient_and_pdf( + neeContrib_pdf, t, + scene, randLightID, intersection, interaction, + isBSDF, eps0, depth + ); + + // We don't allow non watertight transmitters in this renderer + bool validPath = nee_sample.getNdotL() > numeric_limits::min && nee_sample.isValid(); + // but if we allowed non-watertight transmitters (single water surface), it would make sense just to apply this line by itself + bxdf::fresnel::OrientedEtas orientedEta = bxdf::fresnel::OrientedEtas::create(interaction.getNdotV(), hlsl::promote(monochromeEta)); + anisocache_type _cache = anisocache_type::template create(interaction, nee_sample, orientedEta); + validPath = validPath && _cache.getAbsNdotH() >= 0.0; + bxdf.params.eta = monochromeEta; + + if (neeContrib_pdf.pdf < numeric_limits::max) + { + if (nbl::hlsl::any(hlsl::isnan(nee_sample.getL().getDirection()))) + ray.payload.accumulation += vector3_type(1000.f, 0.f, 0.f); + else if (nbl::hlsl::all((vector3_type)69.f == nee_sample.getL().getDirection())) + ray.payload.accumulation += vector3_type(0.f, 1000.f, 0.f); + else if (validPath) + { + // example only uses isotropic bxdfs + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bxdf.materialType, bxdf.params, nee_sample, interaction.isotropic, _cache.iso_cache); + neeContrib_pdf.quotient *= bxdf.albedo * throughput * bsdf_quotient_pdf.quotient; + const scalar_type otherGenOverChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb; + const scalar_type otherGenOverLightAndChoice = otherGenOverChoice / bsdf_quotient_pdf.pdf; + neeContrib_pdf.quotient *= otherGenOverChoice / (1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice); // balance heuristic + + // TODO: ifdef NEE only + // neeContrib_pdf.quotient *= otherGenOverChoice; + + ray_type nee_ray; + nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); + nee_ray.direction = nee_sample.getL().getDirection(); + nee_ray.intersectionT = t; + if (bsdf_quotient_pdf.pdf < numeric_limits::max && getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold && intersector_type::traceRay(nee_ray, scene).id == -1) + ray.payload.accumulation += neeContrib_pdf.quotient; + } + } + } + + // return false; // NEE only + + // sample BSDF + scalar_type bxdfPdf; + vector3_type bxdfSample; + { + anisocache_type _cache; + sample_type bsdf_sample = materialSystem.generate(bxdf.materialType, bxdf.params, interaction, eps1, _cache); + + if (!bsdf_sample.isValid()) + return false; + + // example only uses isotropic bxdfs + // the value of the bsdf divided by the probability of the sample being generated + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bxdf.materialType, bxdf.params, bsdf_sample, interaction.isotropic, _cache.iso_cache); + throughput *= bxdf.albedo * bsdf_quotient_pdf.quotient; + bxdfPdf = bsdf_quotient_pdf.pdf; + bxdfSample = bsdf_sample.getL().getDirection(); + } + + // additional threshold + const float lumaThroughputThreshold = lumaContributionThreshold; + if (bxdfPdf > bxdfPdfThreshold && getLuma(throughput) > lumaThroughputThreshold) + { + ray.payload.throughput = throughput; + scalar_type otherTechniqueHeuristic = neeProbability / bxdfPdf; // numerically stable, don't touch + ray.payload.otherTechniqueHeuristic = otherTechniqueHeuristic * otherTechniqueHeuristic; + + // trace new ray + ray.origin = intersection + bxdfSample * (1.0/*kSceneSize*/) * Tolerance::getStart(depth); + ray.direction = bxdfSample; + if (nee_type::IsPolygonMethodProjectedSolidAngle) + { + ray.normalAtOrigin = interaction.getN(); + ray.wasBSDFAtOrigin = isBSDF; + } + return true; + } + + return false; + } + + void missProgram(NBL_REF_ARG(ray_type) ray) + { + vector3_type finalContribution = ray.payload.throughput; + // #ifdef USE_ENVMAP + // vec2 uv = SampleSphericalMap(_immutable.direction); + // finalContribution *= textureLod(envMap, uv, 0.0).rgb; + // #else + const vector3_type kConstantEnvLightRadiance = vector3_type(0.15, 0.21, 0.3); // TODO: match spectral_type + finalContribution *= kConstantEnvLightRadiance; + ray.payload.accumulation += finalContribution; + // #endif + } + + // Li + void sampleMeasure(uint32_t sampleIndex, uint32_t maxDepth, NBL_CONST_REF_ARG(scene_type) scene, NBL_REF_ARG(Accumulator) accumulator) + { + //scalar_type meanLumaSq = 0.0; + vector3_type uvw = rand3d(0u, sampleIndex, 0u); + ray_type ray = rayGen.generate(uvw); + + // bounces + bool hit = true; + bool rayAlive = true; + for (int d = 1; (d <= maxDepth) && hit && rayAlive; d += 2) + { + ray.intersectionT = numeric_limits::max; + ray.objectID = intersector_type::traceRay(ray, scene); + + hit = ray.objectID.id != -1; + if (hit) + rayAlive = closestHitProgram(1, sampleIndex, ray, scene); + } + if (!hit) + missProgram(ray); + + const uint32_t sampleCount = sampleIndex + 1; + accumulator.addSample(sampleCount, ray.payload.accumulation); + + // TODO: visualize high variance + + // TODO: russian roulette early exit? + } + + NBL_CONSTEXPR_STATIC_INLINE uint32_t MAX_DEPTH_LOG2 = 4u; + NBL_CONSTEXPR_STATIC_INLINE uint32_t MAX_SAMPLES_LOG2 = 10u; + + randgen_type randGen; + raygen_type rayGen; + material_system_type materialSystem; + nee_type nee; + + uint64_t pSampleBuffer; +}; + +} +} +} + +#endif diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index ac0903589a..46ce7f271b 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -308,6 +308,10 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/oren_nayar. LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/smooth_dielectric.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/iridescent.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/delta_distribution.hlsl") +#path tracing +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/path_tracing/concepts.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/path_tracing/unidirectional.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/path_tracing/default_accumulator.hlsl") #subgroup LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/subgroup/ballot.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/subgroup/basic.hlsl") From 1a7b1859a41dd2a3b0271d7218b91e2e2d7b02a8 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 23 Jan 2026 14:34:55 +0700 Subject: [PATCH 017/101] use correct quantized sequence decode --- include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 8e9e207005..9690f8175b 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -81,8 +81,7 @@ struct Unidirectional using sequence_type = sampling::QuantizedSequence; uint32_t address = glsl::bitfieldInsert(protoDimension, _sample, MAX_DEPTH_LOG2, MAX_SAMPLES_LOG2); sequence_type tmpSeq = vk::RawBufferLoad(pSampleBuffer + (address + i) * sizeof(sequence_type)); - // return tmpSeq.template decode(randGen()); - return sampling::decode(tmpSeq,randGen()); + return tmpSeq.template decode(randGen()); } scalar_type getLuma(NBL_CONST_REF_ARG(vector3_type) col) From 3350351ac46dc04ebacbc94f5b0c395015621fce Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 23 Jan 2026 16:40:08 +0700 Subject: [PATCH 018/101] moved raygen out as basic ray gen with gaussian filter --- .../hlsl/path_tracing/basic_ray_gen.hlsl | 50 +++++++++++++++++++ .../hlsl/path_tracing/gaussian_filter.hlsl | 45 +++++++++++++++++ .../hlsl/path_tracing/unidirectional.hlsl | 11 +++- src/nbl/builtin/CMakeLists.txt | 2 + 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl create mode 100644 include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl diff --git a/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl b/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl new file mode 100644 index 0000000000..e451789977 --- /dev/null +++ b/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl @@ -0,0 +1,50 @@ +#ifndef _NBL_BUILTIN_HLSL_PATH_TRACING_BASIC_RAYGEN_INCLUDED_ +#define _NBL_BUILTIN_HLSL_PATH_TRACING_BASIC_RAYGEN_INCLUDED_ + +#include + +namespace nbl +{ +namespace hlsl +{ +namespace path_tracing +{ + +template +struct BasicRayGenerator +{ + using this_t = BasicRayGenerator; + using ray_type = Ray; + using scalar_type = typename Ray::scalar_type; + using vector3_type = typename Ray::vector3_type; + + using vector2_type = vector; + using vector4_type = vector; + using matrix4x4_type = matrix; + + ray_type generate(const vector3_type randVec) + { + ray_type ray; + ray.origin = camPos; + + vector4_type tmp = NDC; + GaussianFilter filter = GaussianFilter::create(2.5, 1.5); // stochastic reconstruction filter + tmp.xy += pixOffsetParam * filter.sample(randVec); + // for depth of field we could do another stochastic point-pick + tmp = nbl::hlsl::mul(invMVP, tmp); + ray.direction = nbl::hlsl::normalize(tmp.xyz / tmp.w - camPos); + + return ray; + } + + vector2_type pixOffsetParam; + vector3_type camPos; + vector4_type NDC; + matrix4x4_type invMVP; +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl b/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl new file mode 100644 index 0000000000..ff19e89d82 --- /dev/null +++ b/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl @@ -0,0 +1,45 @@ +#ifndef _NBL_BUILTIN_HLSL_PATH_TRACING_GAUSSIAN_FILTER_INCLUDED_ +#define _NBL_BUILTIN_HLSL_PATH_TRACING_GAUSSIAN_FILTER_INCLUDED_ + +#include + +namespace nbl +{ +namespace hlsl +{ +namespace path_tracing +{ + +template) +struct GaussianFilter +{ + using this_t = GaussianFilter; + using scalar_type = T; + + using vector2_type = vector; + + static this_t create(const scalar_type gaussianFilterCutoff, const scalar_type stddev) + { + this_t retval; + retval.truncation = hlsl::exp(-0.5 * gaussianFilterCutoff * gaussianFilterCutoff); + retval.boxMuller.stddev = stddev; + return retval; + } + + vector2_type sample(const vector randVec) + { + vector2_type remappedRand = randVec.xy; + remappedRand.x *= 1.0 - truncation; + remappedRand.x += truncation; + return boxMuller(remappedRand); + } + + scalar_type truncation; + nbl::hlsl::sampling::BoxMullerTransform boxMuller; +}; + +} +} +} + +#endif diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 9690f8175b..8e15056237 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -217,7 +217,7 @@ struct Unidirectional // trace new ray ray.origin = intersection + bxdfSample * (1.0/*kSceneSize*/) * Tolerance::getStart(depth); ray.direction = bxdfSample; - if (nee_type::IsPolygonMethodProjectedSolidAngle) + NBL_IF_CONSTEXPR (nee_type::IsPolygonMethodProjectedSolidAngle) { ray.normalAtOrigin = interaction.getN(); ray.wasBSDFAtOrigin = isBSDF; @@ -232,7 +232,7 @@ struct Unidirectional { vector3_type finalContribution = ray.payload.throughput; // #ifdef USE_ENVMAP - // vec2 uv = SampleSphericalMap(_immutable.direction); + // vec2 uv = SampleSphericalMap(ray.direction); // finalContribution *= textureLod(envMap, uv, 0.0).rgb; // #else const vector3_type kConstantEnvLightRadiance = vector3_type(0.15, 0.21, 0.3); // TODO: match spectral_type @@ -247,6 +247,13 @@ struct Unidirectional //scalar_type meanLumaSq = 0.0; vector3_type uvw = rand3d(0u, sampleIndex, 0u); ray_type ray = rayGen.generate(uvw); + ray.initPayload(); + + NBL_IF_CONSTEXPR (nee_type::IsPolygonMethodProjectedSolidAngle) + { + ray.normalAtOrigin = hlsl::promote(0.0); + ray.wasBSDFAtOrigin = false; + } // bounces bool hit = true; diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index 46ce7f271b..0cf81b1a05 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -312,6 +312,8 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/bxdf/transmission/delta_distr LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/path_tracing/concepts.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/path_tracing/unidirectional.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/path_tracing/default_accumulator.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/path_tracing/gaussian_filter.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/path_tracing/basic_ray_gen.hlsl") #subgroup LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/subgroup/ballot.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/subgroup/basic.hlsl") From 5ed90fab3fad8aecc7e3d06084ccfec7a990604d Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 13 Feb 2026 10:47:35 +0700 Subject: [PATCH 019/101] moved nan check from ndotl to ldoth --- include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl b/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl index 5d5083af02..1741ef789d 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl @@ -202,9 +202,10 @@ struct SCookTorrance // fail if samples have invalid paths const scalar_type NdotL = hlsl::mix(scalar_type(2.0) * VdotH * localH.z - NdotV, localH.z * (VdotH * rcpEta.value[0] + LdotH) - NdotV * rcpEta.value[0], transmitted); + assert(!hlsl::isnan(NdotL)); // VNDF sampling guarantees that `VdotH` has same sign as `NdotV` // and `transmitted` controls the sign of `LdotH` relative to `VdotH` by construction (reflect -> same sign, or refract -> opposite sign) - if (hlsl::isnan(NdotL) || ComputeMicrofacetNormal::isTransmissionPath(NdotV, NdotL) != transmitted) + if (ComputeMicrofacetNormal::isTransmissionPath(NdotV, NdotL) != transmitted) { valid = false; return sample_type::createInvalid(); // should check if sample direction is invalid @@ -308,6 +309,8 @@ struct SCookTorrance bool transmitted = partitionRandVariable(z, rcpChoiceProb); const scalar_type LdotH = hlsl::mix(VdotH, ieee754::copySign(hlsl::sqrt(rcpEta.value2[0]*VdotH*VdotH + scalar_type(1.0) - rcpEta.value2[0]), -VdotH), transmitted); + if (hlsl::isnan(LdotH)) + return sample_type::createInvalid(); bool valid; sample_type s = __generate_common(interaction, localH, NdotV, VdotH, LdotH, transmitted, rcpEta, valid); if (valid) From b8f0c9ffaa6a92747ef5e8b51331d250cc700aee Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 13 Feb 2026 10:50:09 +0700 Subject: [PATCH 020/101] minor fixes because this is ref in hlsl and pointer in c++ --- include/nbl/builtin/hlsl/bxdf/fresnel.hlsl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl index 0865e7f5e1..cd0255b6a3 100644 --- a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl @@ -635,6 +635,7 @@ struct iridescent_helper NBL_UNROLL for (int m=1; m<=2; ++m) { Cm *= r123p; + // TODO: common subexpression, maybe we can hoist it out somehow? Sm = hlsl::promote(2.0) * evalSensitivity(hlsl::promote(scalar_type(m))*D, hlsl::promote(scalar_type(m))*(phi23p+phi21p)); I += Cm*Sm; } @@ -728,7 +729,12 @@ struct Iridescent(this, base_type::iork3, getEtak23(), clampedCosTheta); +#else + return impl::__iridescent_base__call_const(*this, base_type::iork3, getEtak23(), clampedCosTheta); +#endif } vector_type getEtak23() NBL_CONST_MEMBER_FUNC @@ -775,7 +781,11 @@ struct Iridescent(this, getEtak23(), getEtak23(), clampedCosTheta); +#else + return impl::__iridescent_base__call_const(*this, getEtak23(), getEtak23(), clampedCosTheta); +#endif } scalar_type getRefractionOrientedEta() NBL_CONST_MEMBER_FUNC { return base_type::eta13[0]; } From 78dcf935c17d7e1856a277228c611bade43b36a7 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 13 Feb 2026 16:12:57 +0700 Subject: [PATCH 021/101] revert linear back to old version because rcpDiff can be negative --- .../nbl/builtin/hlsl/sampling/bilinear.hlsl | 4 ++-- include/nbl/builtin/hlsl/sampling/linear.hlsl | 20 +++++++++---------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index fd3632b994..e1ed03c5e9 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -30,7 +30,7 @@ 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; } @@ -57,7 +57,7 @@ struct Bilinear // 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/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index 7127701117..289ea75485 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -24,25 +24,23 @@ 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.rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); 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 linearCoeffStartOverDiff; - scalar_type squaredCoeffStartOverDiff; - scalar_type squaredCoeffDiffOverDiff; + scalar_type linearCoeffStart; + scalar_type rcpDiff; + scalar_type squaredCoeffStart; + scalar_type squaredCoeffDiff; }; } From cf936ed0526b2b331e344356cef7a53fd8fe1043 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 13 Feb 2026 16:56:39 +0700 Subject: [PATCH 022/101] fix conditionalAbsOrMax for vectors --- include/nbl/builtin/hlsl/math/functions.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/nbl/builtin/hlsl/math/functions.hlsl b/include/nbl/builtin/hlsl/math/functions.hlsl index a1c51d4e51..f7db44b9fb 100644 --- a/include/nbl/builtin/hlsl/math/functions.hlsl +++ b/include/nbl/builtin/hlsl/math/functions.hlsl @@ -152,7 +152,7 @@ struct conditionalAbsOrMax_helper(x); - const Uint32VectorWithDimensionOfT mask = cond ? _static_cast(numeric_limits::max >> 1) : _static_cast(numeric_limits::max); + const Uint32VectorWithDimensionOfT mask = cond ? promote(numeric_limits::max >> 1) : promote(numeric_limits::max); const Uint32VectorWithDimensionOfT condAbsAsUint = xAsUintVec & mask; T condAbs = bit_cast(condAbsAsUint); From 5e52a6f2a31c603c68c84319481f28ad74d0f7e0 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 13 Feb 2026 16:57:43 +0700 Subject: [PATCH 023/101] refactor changes to various sampling functions so it runs --- .../hlsl/path_tracing/unidirectional.hlsl | 22 ++++-------- .../projected_spherical_triangle.hlsl | 34 ++++++------------- .../hlsl/sampling/spherical_triangle.hlsl | 2 +- 3 files changed, 18 insertions(+), 40 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 8e15056237..a7b491fa74 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -110,8 +110,11 @@ struct Unidirectional const uint32_t lightID = glsl::bitfieldExtract(bsdfLightIDs, 16, 16); if (lightID != light_type::INVALID_ID) { - float _pdf; - ray.payload.accumulation += nee.deferredEvalAndPdf(_pdf, scene, lightID, ray) * throughput / (1.0 + _pdf * _pdf * ray.payload.otherTechniqueHeuristic); + scalar_type _pdf; + measure_type emissive = nee.deferredEvalAndPdf(_pdf, scene, lightID, ray) * throughput; + scalar_type _pdfSq = hlsl::mix(_pdf, _pdf * _pdf, _pdf < numeric_limits::max); + emissive /= (1.0 + _pdfSq * ray.payload.otherTechniqueHeuristic); + ray.payload.accumulation += emissive; } const uint32_t bsdfID = glsl::bitfieldExtract(bsdfLightIDs, 0, 16); @@ -120,8 +123,6 @@ struct Unidirectional bxdfnode_type bxdf = materialSystem.bxdfs[bsdfID]; - // TODO: ifdef kill diffuse specular paths - const bool isBSDF = material_system_type::isBSDF(bxdf.materialType); vector3_type eps0 = rand3d(depth, _sample, 0u); @@ -135,7 +136,7 @@ struct Unidirectional const scalar_type monochromeEta = hlsl::dot(throughputCIE_Y, eta) / (throughputCIE_Y.r + throughputCIE_Y.g + throughputCIE_Y.b); // TODO: imaginary eta? // sample lights - const scalar_type neeProbability = 1.0; // BSDFNode_getNEEProb(bsdf); + const scalar_type neeProbability = bxdf.getNEEProb(); scalar_type rcpChoiceProb; sampling::PartitionRandVariable partitionRandVariable; partitionRandVariable.leftProb = neeProbability; @@ -160,11 +161,7 @@ struct Unidirectional if (neeContrib_pdf.pdf < numeric_limits::max) { - if (nbl::hlsl::any(hlsl::isnan(nee_sample.getL().getDirection()))) - ray.payload.accumulation += vector3_type(1000.f, 0.f, 0.f); - else if (nbl::hlsl::all((vector3_type)69.f == nee_sample.getL().getDirection())) - ray.payload.accumulation += vector3_type(0.f, 1000.f, 0.f); - else if (validPath) + if (validPath) { // example only uses isotropic bxdfs quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bxdf.materialType, bxdf.params, nee_sample, interaction.isotropic, _cache.iso_cache); @@ -173,9 +170,6 @@ struct Unidirectional const scalar_type otherGenOverLightAndChoice = otherGenOverChoice / bsdf_quotient_pdf.pdf; neeContrib_pdf.quotient *= otherGenOverChoice / (1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice); // balance heuristic - // TODO: ifdef NEE only - // neeContrib_pdf.quotient *= otherGenOverChoice; - ray_type nee_ray; nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); nee_ray.direction = nee_sample.getL().getDirection(); @@ -186,8 +180,6 @@ struct Unidirectional } } - // return false; // NEE only - // sample BSDF scalar_type bxdfPdf; vector3_type bxdfSample; diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index d01712c2f8..116dbd3cd9 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -29,7 +29,7 @@ struct ProjectedSphericalTriangle static ProjectedSphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) { ProjectedSphericalTriangle retval; - retval.tri = tri; + retval.sphtri = sampling::SphericalTriangle::create(tri); return retval; } @@ -37,13 +37,13 @@ struct ProjectedSphericalTriangle { 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(isBSDF, 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) + vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, scalar_type solidAngle, scalar_type cos_c, scalar_type csc_b, const vector3_type receiverNormal, bool isBSDF, const vector2_type _u) { vector2_type u; // pre-warp according to proj solid angle approximation @@ -52,7 +52,7 @@ struct ProjectedSphericalTriangle 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); + const vector3_type L = sphtri.generate(cos_c, csc_b, u); rcpPdf = solidAngle / bilinear.backwardPdf(u); return L; @@ -60,26 +60,13 @@ struct ProjectedSphericalTriangle 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); + const scalar_type cos_c = sphtri.tri.cos_sides[2]; + const scalar_type csc_b = sphtri.tri.csc_sides[1]; + const scalar_type solidAngle = sphtri.tri.solidAngle(); + return generate(rcpPdf, solidAngle, cos_c, csc_b, 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 receiverNormal, bool receiverWasBSDF, const vector3_type L) { scalar_type pdf; const vector2_type u = sphtri.generateInverse(pdf, L); @@ -89,7 +76,6 @@ struct ProjectedSphericalTriangle return pdf * bilinear.backwardPdf(u); } - shapes::SphericalTriangle tri; sampling::SphericalTriangle sphtri; }; diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 2f2d12e1df..2b2dc0ccb6 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -84,7 +84,7 @@ struct SphericalTriangle 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 csc_a_ = nbl::hlsl::rsqrt(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; From 86e3146265274a505f7390bd278d1b9345add72d Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 13 Feb 2026 17:08:53 +0700 Subject: [PATCH 024/101] some fixes to pt concept type names --- include/nbl/builtin/hlsl/path_tracing/concepts.hlsl | 11 ++++++----- .../nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index b3dc2dd055..535939cc5f 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -56,8 +56,8 @@ NBL_CONCEPT_BEGIN(3) NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::scene_type)) ((NBL_CONCEPT_REQ_TYPE)(T::ray_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::id_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceRay(ray, scene)), ::nbl::hlsl::is_same_v, typename T::id_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceRay(ray, scene)), ::nbl::hlsl::is_same_v, typename T::object_handle_type)) ); #undef scene #undef ray @@ -69,7 +69,7 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (matsys, T) #define NBL_CONCEPT_PARAM_1 (_sample, typename T::sample_type) -#define NBL_CONCEPT_PARAM_2 (matid, uint32_t) +#define NBL_CONCEPT_PARAM_2 (matid, typename T::material_id_type) #define NBL_CONCEPT_PARAM_3 (aniso_inter, typename T::anisotropic_interaction_type) #define NBL_CONCEPT_PARAM_4 (iso_inter, typename T::isotropic_interaction_type) #define NBL_CONCEPT_PARAM_5 (aniso_cache, typename T::anisocache_type) @@ -88,6 +88,7 @@ NBL_CONCEPT_BEGIN(9) #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_8 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::material_id_type)) ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) ((NBL_CONCEPT_REQ_TYPE)(T::quotient_pdf_type)) ((NBL_CONCEPT_REQ_TYPE)(T::measure_type)) @@ -181,14 +182,14 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (scene, T) #define NBL_CONCEPT_PARAM_1 (intersectP, typename T::vector3_type) -#define NBL_CONCEPT_PARAM_2 (id, typename T::id_type) +#define NBL_CONCEPT_PARAM_2 (id, typename T::object_handle_type) NBL_CONCEPT_BEGIN(3) #define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define intersectP NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::id_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getBsdfLightIDs(id)), ::nbl::hlsl::is_same_v, uint32_t)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getNormal(id, intersectP)), ::nbl::hlsl::is_same_v, typename T::vector3_type)) ); diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index a7b491fa74..4426250513 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -63,7 +63,7 @@ struct Unidirectional using sample_type = typename NextEventEstimator::sample_type; using ray_dir_info_type = typename sample_type::ray_dir_info_type; using ray_type = typename RayGen::ray_type; - using id_type = typename Intersector::id_type; + using object_handle_type = typename Intersector::object_handle_type; using light_type = typename NextEventEstimator::light_type; using bxdfnode_type = typename MaterialSystem::bxdfnode_type; using anisotropic_interaction_type = typename MaterialSystem::anisotropic_interaction_type; @@ -92,7 +92,7 @@ struct Unidirectional // TODO: probably will only work with isotropic surfaces, need to do aniso bool closestHitProgram(uint32_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(scene_type) scene) { - const id_type objectID = ray.objectID; + const object_handle_type objectID = ray.objectID; const vector3_type intersection = ray.origin + ray.direction * ray.intersectionT; uint32_t bsdfLightIDs = scene.getBsdfLightIDs(objectID); From addde12192a474a8a27bad7f58d48c3ba1f3a9d5 Mon Sep 17 00:00:00 2001 From: Przemog1 Date: Mon, 16 Feb 2026 05:10:24 +0100 Subject: [PATCH 025/101] RWMC fixes --- examples_tests | 2 +- .../concepts/accessors/loadable_image.hlsl | 6 +- .../builtin/hlsl/rwmc/CascadeAccumulator.hlsl | 80 +++++++++---------- .../builtin/hlsl/rwmc/ResolveParameters.hlsl | 34 ++++---- .../hlsl/rwmc/SplattingParameters.hlsl | 10 ++- include/nbl/builtin/hlsl/rwmc/resolve.hlsl | 32 ++++---- 6 files changed, 85 insertions(+), 79 deletions(-) diff --git a/examples_tests b/examples_tests index dd7de7a89c..4246192d21 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit dd7de7a89cfa5a59970dde4d4744ecf746d77a4a +Subproject commit 4246192d213c0c42a508257bd219c1fca0cb238f diff --git a/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl index 924ee240d0..f7b5122c81 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl @@ -47,8 +47,8 @@ NBL_CONCEPT_END( // declare concept #define NBL_CONCEPT_NAME MipmappedLoadableImage -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)(int32_t) -#define NBL_CONCEPT_TPLT_PRM_NAMES (U)(T)(Dims) +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)(int32_t)(int32_t) +#define NBL_CONCEPT_TPLT_PRM_NAMES (U)(T)(Dims)(Components) // not the greatest syntax but works #define NBL_CONCEPT_PARAM_0 (a,U) #define NBL_CONCEPT_PARAM_1 (uv,vector) @@ -62,7 +62,7 @@ NBL_CONCEPT_BEGIN(4) #define layer NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 #define level NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,level)) , ::nbl::hlsl::is_same_v, vector)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,level)) , ::nbl::hlsl::is_same_v, vector)) ); #undef level #undef layer diff --git a/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl b/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl index 593e267a26..cdbb8271d2 100644 --- a/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl @@ -14,36 +14,38 @@ namespace rwmc { template) -struct CascadeAccumulator +struct DefaultCascades { - struct CascadeEntry - { - uint32_t cascadeSampleCounter[CascadeCount]; - CascadeLayerType data[CascadeCount]; + using layer_type = CascadeLayerType; - void addSampleIntoCascadeEntry(CascadeLayerType _sample, uint32_t lowerCascadeIndex, float lowerCascadeLevelWeight, float higherCascadeLevelWeight, uint32_t sampleCount) - { - const float reciprocalSampleCount = 1.0f / float(sampleCount); + uint32_t cascadeSampleCounter[CascadeCount]; + CascadeLayerType data[CascadeCount]; - uint32_t lowerCascadeSampleCount = cascadeSampleCounter[lowerCascadeIndex]; - data[lowerCascadeIndex] += (_sample * lowerCascadeLevelWeight - (sampleCount - lowerCascadeSampleCount) * data[lowerCascadeIndex]) * reciprocalSampleCount; - cascadeSampleCounter[lowerCascadeIndex] = sampleCount; + void addSampleIntoCascadeEntry(CascadeLayerType _sample, uint16_t lowerCascadeIndex, SplattingParameters::scalar_t lowerCascadeLevelWeight, SplattingParameters::scalar_t higherCascadeLevelWeight, uint32_t sampleCount) + { + const float reciprocalSampleCount = 1.0f / float(sampleCount); - uint32_t higherCascadeIndex = lowerCascadeIndex + 1u; - if (higherCascadeIndex < CascadeCount) - { - uint32_t higherCascadeSampleCount = cascadeSampleCounter[higherCascadeIndex]; - data[higherCascadeIndex] += (_sample * higherCascadeLevelWeight - (sampleCount - higherCascadeSampleCount) * data[higherCascadeIndex]) * reciprocalSampleCount; - cascadeSampleCounter[higherCascadeIndex] = sampleCount; - } + uint32_t lowerCascadeSampleCount = cascadeSampleCounter[lowerCascadeIndex]; + data[lowerCascadeIndex] += (_sample * lowerCascadeLevelWeight - (sampleCount - lowerCascadeSampleCount) * data[lowerCascadeIndex]) * reciprocalSampleCount; + cascadeSampleCounter[lowerCascadeIndex] = sampleCount; + + uint16_t higherCascadeIndex = lowerCascadeIndex + 1u; + if (higherCascadeIndex < CascadeCount) + { + uint32_t higherCascadeSampleCount = cascadeSampleCounter[higherCascadeIndex]; + data[higherCascadeIndex] += (_sample * higherCascadeLevelWeight - (sampleCount - higherCascadeSampleCount) * data[higherCascadeIndex]) * reciprocalSampleCount; + cascadeSampleCounter[higherCascadeIndex] = sampleCount; } - }; + } +}; - using cascade_layer_scalar_type = typename vector_traits::scalar_type; - using this_t = CascadeAccumulator; - using input_sample_type = CascadeLayerType; - using output_storage_type = CascadeEntry; - using initialization_data = SplattingParameters; +template +struct CascadeAccumulator +{ + using input_sample_type = typename CascadesType::layer_type; + using cascade_layer_scalar_type = typename vector_traits::scalar_type; + using this_t = CascadeAccumulator; + using output_storage_type = CascadesType; output_storage_type accumulation; SplattingParameters splattingParameters; @@ -53,39 +55,35 @@ struct CascadeAccumulator this_t retval; for (int i = 0; i < CascadeCount; ++i) { - retval.accumulation.data[i] = promote(0.0f); + retval.accumulation.data[i] = promote(0.0f); retval.accumulation.cascadeSampleCounter[i] = 0u; } retval.splattingParameters = settings; return retval; } - - cascade_layer_scalar_type getLuma(NBL_CONST_REF_ARG(CascadeLayerType) col) - { - return hlsl::dot(hlsl::transpose(colorspace::scRGBtoXYZ)[1], col); - } // most of this code is stolen from https://cg.ivd.kit.edu/publications/2018/rwmc/tool/split.cpp void addSample(uint32_t sampleCount, input_sample_type _sample) { const float32_t2 unpackedParams = hlsl::unpackHalf2x16(splattingParameters.packedLog2); - const cascade_layer_scalar_type log2Start = unpackedParams[0]; - const cascade_layer_scalar_type log2Base = unpackedParams[1]; - const cascade_layer_scalar_type luma = getLuma(_sample); - const cascade_layer_scalar_type log2Luma = log2(luma); - const cascade_layer_scalar_type cascade = log2Luma * 1.f / log2Base - log2Start / log2Base; - const cascade_layer_scalar_type clampedCascade = clamp(cascade, 0, CascadeCount - 1); + const SplattingParameters::scalar_t log2Start = unpackedParams[0]; + const SplattingParameters::scalar_t rcpLog2Base = unpackedParams[1]; + const SplattingParameters::scalar_t luma = splattingParameters.getLuma(_sample); + const SplattingParameters::scalar_t log2Luma = log2(luma); + const SplattingParameters::scalar_t cascade = log2Luma * rcpLog2Base - log2Start * rcpLog2Base; + const SplattingParameters::scalar_t lastCascade = CascadeCount - 1; + const SplattingParameters::scalar_t clampedCascade = clamp(cascade, 0, lastCascade); // c<=0 -> 0, c>=Count-1 -> Count-1 - uint32_t lowerCascadeIndex = floor(cascade); + uint16_t lowerCascadeIndex = floor(cascade); // 0 whenever clamped or `cascade` is integer (when `clampedCascade` is integer) - cascade_layer_scalar_type higherCascadeWeight = clampedCascade - floor(clampedCascade); + SplattingParameters::scalar_t higherCascadeWeight = clampedCascade - floor(clampedCascade); // never 0 thanks to magic of `1-fract(x)` - cascade_layer_scalar_type lowerCascadeWeight = cascade_layer_scalar_type(1) - higherCascadeWeight; + SplattingParameters::scalar_t lowerCascadeWeight = SplattingParameters::scalar_t(1) - higherCascadeWeight; // handle super bright sample case - if (cascade > CascadeCount - 1) - lowerCascadeWeight = exp2(log2Start + log2Base * (CascadeCount - 1) - log2Luma); + if (cascade > lastCascade) + lowerCascadeWeight = exp2(log2Start + (1.0f/rcpLog2Base) * (lastCascade) - log2Luma); accumulation.addSampleIntoCascadeEntry(_sample, lowerCascadeIndex, lowerCascadeWeight, higherCascadeWeight, sampleCount); } diff --git a/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl index 7509eac493..17e3f8d720 100644 --- a/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl @@ -12,7 +12,22 @@ namespace rwmc struct ResolveParameters { - uint32_t lastCascadeIndex; + static ResolveParameters create(float base, uint32_t sampleCount, float minReliableLuma, float kappa) + { + ResolveParameters retval; + retval.initialEmin = minReliableLuma; + retval.reciprocalBase = 1.f / base; + const float N = float(sampleCount); + retval.reciprocalN = 1.f / N; + retval.reciprocalKappa = 1.f / kappa; + // if not interested in exact expected value estimation (kappa!=1.f), can usually accept a bit more variance relative to the image brightness we already have + // allow up to ~ more energy in one sample to lessen bias in some cases + retval.colorReliabilityFactor = base + (1.f - base) * retval.reciprocalKappa; + retval.NOverKappa = N * retval.reciprocalKappa; + + return retval; + } + float initialEmin; // a minimum image brightness that we always consider reliable float reciprocalBase; float reciprocalN; @@ -21,23 +36,6 @@ struct ResolveParameters float NOverKappa; }; -ResolveParameters computeResolveParameters(float base, uint32_t sampleCount, float minReliableLuma, float kappa, uint32_t cascadeSize) -{ - ResolveParameters retval; - retval.lastCascadeIndex = cascadeSize - 1u; - retval.initialEmin = minReliableLuma; - retval.reciprocalBase = 1.f / base; - const float N = float(sampleCount); - retval.reciprocalN = 1.f / N; - retval.reciprocalKappa = 1.f / kappa; - // if not interested in exact expected value estimation (kappa!=1.f), can usually accept a bit more variance relative to the image brightness we already have - // allow up to ~ more energy in one sample to lessen bias in some cases - retval.colorReliabilityFactor = base + (1.f - base) * retval.reciprocalKappa; - retval.NOverKappa = N * retval.reciprocalKappa; - - return retval; -} - } } } diff --git a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl index c549d83be6..74b062a2de 100644 --- a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl @@ -12,10 +12,18 @@ namespace rwmc struct SplattingParameters { + using scalar_t = float32_t; + // float16_t log2Start; 0 - // float16_t log2Base; 1 + // float16_t rcpLog2Base; 1 // pack as Half2x16 int32_t packedLog2; + + template + scalar_t getLuma(NBL_CONST_REF_ARG(CascadeLayerType) col) + { + return hlsl::dot(hlsl::transpose(colorspace::scRGBtoXYZ)[1], col); + } }; } diff --git a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl index 34d7c95960..a3b4236dca 100644 --- a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl @@ -16,8 +16,8 @@ namespace rwmc { // declare concept #define NBL_CONCEPT_NAME ResolveAccessorBase -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)(int32_t)(int32_t) -#define NBL_CONCEPT_TPLT_PRM_NAMES (T)(VectorScalarType)(Dims)(Components) +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)(int32_t) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T)(VectorScalarType)(Components) // not the greatest syntax but works #define NBL_CONCEPT_PARAM_0 (a,T) #define NBL_CONCEPT_PARAM_1 (vec,vector) @@ -28,6 +28,7 @@ NBL_CONCEPT_BEGIN(2) #define vec NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.calcLuma(vec)), ::nbl::hlsl::is_same_v, VectorScalarType)) + ((NBL_CONCEPT_REQ_TYPE)(T::output_t)) ); #undef a #undef vec @@ -39,14 +40,14 @@ NBL_CONCEPT_END( */ template -NBL_BOOL_CONCEPT ResolveAccessor = ResolveAccessorBase && concepts::accessors::LoadableImage; +NBL_BOOL_CONCEPT ResolveAccessor = ResolveAccessorBase && concepts::accessors::LoadableImage; template struct ResolveAccessorAdaptor { - using output_scalar_type = OutputScalar; + using output_scalar_t = OutputScalar; NBL_CONSTEXPR int32_t Components = 3; - using output_type = vector; + using output_t = vector; NBL_CONSTEXPR int32_t image_dimension = 2; RWTexture2DArray cascade; @@ -57,24 +58,24 @@ struct ResolveAccessorAdaptor } template - output_type get(vector uv, uint16_t layer) + output_t get(vector uv, uint16_t layer) { uint32_t imgWidth, imgHeight, layers; cascade.GetDimensions(imgWidth, imgHeight, layers); int16_t2 cascadeImageDimension = int16_t2(imgWidth, imgHeight); if (any(uv < int16_t2(0, 0)) || any(uv > cascadeImageDimension)) - return promote(0); + return promote(0); return cascade.Load(int32_t3(uv, int32_t(layer))); } }; -template && ResolveAccessor) +template) struct Resolver { - using output_type = OutputColorTypeVec; - using scalar_t = typename vector_traits::scalar_type; + using output_t = typename CascadeAccessor::output_t; + using scalar_t = typename vector_traits::scalar_type; struct CascadeSample { @@ -91,19 +92,20 @@ struct Resolver return retval; } - output_type operator()(NBL_REF_ARG(CascadeAccessor) acc, const int16_t2 coord) + output_t operator()(NBL_REF_ARG(CascadeAccessor) acc, const int16_t2 coord) { scalar_t reciprocalBaseI = 1.f; CascadeSample curr = __sampleCascade(acc, coord, 0u, reciprocalBaseI); - output_type accumulation = promote(0.0f); + output_t accumulation = promote(0.0f); scalar_t Emin = params.initialEmin; scalar_t prevNormalizedCenterLuma, prevNormalizedNeighbourhoodAverageLuma; - for (int16_t i = 0u; i <= params.lastCascadeIndex; i++) + NBL_UNROLL + for (int16_t i = 0u; i <= CascadeCount - 1; i++) { const bool notFirstCascade = i != 0; - const bool notLastCascade = i != params.lastCascadeIndex; + const bool notLastCascade = i != (CascadeCount - 1); CascadeSample next; if (notLastCascade) @@ -160,7 +162,7 @@ struct Resolver CascadeSample __sampleCascade(NBL_REF_ARG(CascadeAccessor) acc, int16_t2 coord, uint16_t cascadeIndex, scalar_t reciprocalBaseI) { - output_type neighbourhood[9]; + output_t neighbourhood[9]; neighbourhood[0] = acc.template get(coord + int16_t2(-1, -1), cascadeIndex); neighbourhood[1] = acc.template get(coord + int16_t2(0, -1), cascadeIndex); neighbourhood[2] = acc.template get(coord + int16_t2(1, -1), cascadeIndex); From 94a5bfb6857ae4e38f7d7fa63cca27fae143547f Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 16 Feb 2026 14:24:43 +0700 Subject: [PATCH 026/101] type alias for id of material and light, also reduced arguments to mat system --- .../builtin/hlsl/path_tracing/concepts.hlsl | 25 ++++++++++--------- .../hlsl/path_tracing/unidirectional.hlsl | 10 ++++---- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 535939cc5f..ce1a6c2906 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -74,9 +74,8 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_PARAM_4 (iso_inter, typename T::isotropic_interaction_type) #define NBL_CONCEPT_PARAM_5 (aniso_cache, typename T::anisocache_type) #define NBL_CONCEPT_PARAM_6 (iso_cache, typename T::isocache_type) -#define NBL_CONCEPT_PARAM_7 (params, typename T::create_params_t) -#define NBL_CONCEPT_PARAM_8 (u, typename T::vector3_type) -NBL_CONCEPT_BEGIN(9) +#define NBL_CONCEPT_PARAM_7 (u, typename T::vector3_type) +NBL_CONCEPT_BEGIN(8) #define matsys NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define _sample NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define matid NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 @@ -84,8 +83,7 @@ NBL_CONCEPT_BEGIN(9) #define iso_inter NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 #define aniso_cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 #define iso_cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 -#define params NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 -#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_8 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) ((NBL_CONCEPT_REQ_TYPE)(T::material_id_type)) @@ -97,12 +95,11 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::anisocache_type)) ((NBL_CONCEPT_REQ_TYPE)(T::isocache_type)) ((NBL_CONCEPT_REQ_TYPE)(T::create_params_t)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.eval(matid, params, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::measure_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.generate(matid, params, aniso_inter, u, aniso_cache)), ::nbl::hlsl::is_same_v, typename T::sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.quotient_and_pdf(matid, params, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::quotient_pdf_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.eval(matid, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::measure_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.generate(matid, aniso_inter, u, aniso_cache)), ::nbl::hlsl::is_same_v, typename T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.quotient_and_pdf(matid, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::quotient_pdf_type)) ); #undef u -#undef params #undef iso_cache #undef aniso_cache #undef iso_inter @@ -118,13 +115,14 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_PARAM_0 (nee, T) #define NBL_CONCEPT_PARAM_1 (ray, typename T::ray_type) #define NBL_CONCEPT_PARAM_2 (scene, typename T::scene_type) -#define NBL_CONCEPT_PARAM_3 (id, uint32_t) +#define NBL_CONCEPT_PARAM_3 (id, typename T::light_id_type) #define NBL_CONCEPT_PARAM_4 (pdf, typename T::scalar_type) #define NBL_CONCEPT_PARAM_5 (quo_pdf, typename T::quotient_pdf_type) #define NBL_CONCEPT_PARAM_6 (v, typename T::vector3_type) #define NBL_CONCEPT_PARAM_7 (interaction, typename T::interaction_type) #define NBL_CONCEPT_PARAM_8 (b, bool) -NBL_CONCEPT_BEGIN(9) +#define NBL_CONCEPT_PARAM_9 (depth, uint32_t) +NBL_CONCEPT_BEGIN(10) #define nee NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 @@ -134,19 +132,22 @@ NBL_CONCEPT_BEGIN(9) #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 #define interaction NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 #define b NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_8 +#define depth NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_9 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) ((NBL_CONCEPT_REQ_TYPE)(T::scene_type)) ((NBL_CONCEPT_REQ_TYPE)(T::light_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::light_id_type)) ((NBL_CONCEPT_REQ_TYPE)(T::ray_type)) ((NBL_CONCEPT_REQ_TYPE)(T::spectral_type)) ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) ((NBL_CONCEPT_REQ_TYPE)(T::quotient_pdf_type)) ((NBL_CONCEPT_REQ_TYPE)(T::interaction_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferredEvalAndPdf(pdf, scene, id, ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.generate_and_quotient_and_pdf(quo_pdf, pdf, scene, id, v, interaction, b, v, id)), ::nbl::hlsl::is_same_v, typename T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.generate_and_quotient_and_pdf(quo_pdf, pdf, scene, id, v, interaction, b, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_type)) ); +#undef depth #undef b #undef interaction #undef v diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 4426250513..583f49110c 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -123,7 +123,7 @@ struct Unidirectional bxdfnode_type bxdf = materialSystem.bxdfs[bsdfID]; - const bool isBSDF = material_system_type::isBSDF(bxdf.materialType); + const bool isBSDF = materialSystem.isBSDF(bsdfID); vector3_type eps0 = rand3d(depth, _sample, 0u); vector3_type eps1 = rand3d(depth, _sample, 1u); @@ -157,14 +157,14 @@ struct Unidirectional bxdf::fresnel::OrientedEtas orientedEta = bxdf::fresnel::OrientedEtas::create(interaction.getNdotV(), hlsl::promote(monochromeEta)); anisocache_type _cache = anisocache_type::template create(interaction, nee_sample, orientedEta); validPath = validPath && _cache.getAbsNdotH() >= 0.0; - bxdf.params.eta = monochromeEta; + materialSystem.bxdfs[bsdfID].params.eta = monochromeEta; if (neeContrib_pdf.pdf < numeric_limits::max) { if (validPath) { // example only uses isotropic bxdfs - quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bxdf.materialType, bxdf.params, nee_sample, interaction.isotropic, _cache.iso_cache); + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bsdfID, nee_sample, interaction.isotropic, _cache.iso_cache); neeContrib_pdf.quotient *= bxdf.albedo * throughput * bsdf_quotient_pdf.quotient; const scalar_type otherGenOverChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb; const scalar_type otherGenOverLightAndChoice = otherGenOverChoice / bsdf_quotient_pdf.pdf; @@ -185,14 +185,14 @@ struct Unidirectional vector3_type bxdfSample; { anisocache_type _cache; - sample_type bsdf_sample = materialSystem.generate(bxdf.materialType, bxdf.params, interaction, eps1, _cache); + sample_type bsdf_sample = materialSystem.generate(bsdfID, interaction, eps1, _cache); if (!bsdf_sample.isValid()) return false; // example only uses isotropic bxdfs // the value of the bsdf divided by the probability of the sample being generated - quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bxdf.materialType, bxdf.params, bsdf_sample, interaction.isotropic, _cache.iso_cache); + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bsdfID, bsdf_sample, interaction.isotropic, _cache.iso_cache); throughput *= bxdf.albedo * bsdf_quotient_pdf.quotient; bxdfPdf = bsdf_quotient_pdf.pdf; bxdfSample = bsdf_sample.getL().getDirection(); From 12279911ed6a30c2ce6472dbb087cf3106e7c1b0 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 16 Feb 2026 16:03:36 +0700 Subject: [PATCH 027/101] change nee to return a struct because of multiple return values --- .../builtin/hlsl/path_tracing/concepts.hlsl | 39 ++++++++----------- .../hlsl/path_tracing/unidirectional.hlsl | 18 +++++---- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index ce1a6c2906..c493d1698a 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -114,25 +114,19 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (nee, T) #define NBL_CONCEPT_PARAM_1 (ray, typename T::ray_type) -#define NBL_CONCEPT_PARAM_2 (scene, typename T::scene_type) -#define NBL_CONCEPT_PARAM_3 (id, typename T::light_id_type) -#define NBL_CONCEPT_PARAM_4 (pdf, typename T::scalar_type) -#define NBL_CONCEPT_PARAM_5 (quo_pdf, typename T::quotient_pdf_type) -#define NBL_CONCEPT_PARAM_6 (v, typename T::vector3_type) -#define NBL_CONCEPT_PARAM_7 (interaction, typename T::interaction_type) -#define NBL_CONCEPT_PARAM_8 (b, bool) -#define NBL_CONCEPT_PARAM_9 (depth, uint32_t) -NBL_CONCEPT_BEGIN(10) +#define NBL_CONCEPT_PARAM_2 (id, typename T::light_id_type) +#define NBL_CONCEPT_PARAM_3 (v, typename T::vector3_type) +#define NBL_CONCEPT_PARAM_4 (interaction, typename T::interaction_type) +#define NBL_CONCEPT_PARAM_5 (is_bsdf, bool) +#define NBL_CONCEPT_PARAM_6 (depth, uint32_t) +NBL_CONCEPT_BEGIN(7) #define nee NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 -#define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 -#define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 -#define pdf NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 -#define quo_pdf NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 -#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 -#define interaction NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 -#define b NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_8 -#define depth NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_9 +#define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 +#define interaction NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 +#define is_bsdf NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 +#define depth NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) @@ -144,17 +138,16 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) ((NBL_CONCEPT_REQ_TYPE)(T::quotient_pdf_type)) ((NBL_CONCEPT_REQ_TYPE)(T::interaction_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferredEvalAndPdf(pdf, scene, id, ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.generate_and_quotient_and_pdf(quo_pdf, pdf, scene, id, v, interaction, b, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::eval_pdf_return_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::sample_quotient_return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_eval_and_pdf(id, ray)), ::nbl::hlsl::is_same_v, typename T::eval_pdf_return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.generate_and_quotient_and_pdf(id, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) ); #undef depth -#undef b +#undef is_bsdf #undef interaction #undef v -#undef quo_pdf -#undef pdf #undef id -#undef scene #undef ray #undef nee #include diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 583f49110c..f0659eb321 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -110,9 +110,9 @@ struct Unidirectional const uint32_t lightID = glsl::bitfieldExtract(bsdfLightIDs, 16, 16); if (lightID != light_type::INVALID_ID) { - scalar_type _pdf; - measure_type emissive = nee.deferredEvalAndPdf(_pdf, scene, lightID, ray) * throughput; - scalar_type _pdfSq = hlsl::mix(_pdf, _pdf * _pdf, _pdf < numeric_limits::max); + typename nee_type::eval_pdf_return_type ret = nee.deferred_eval_and_pdf(lightID, ray); + measure_type emissive = ret.radiance * throughput; + scalar_type _pdfSq = hlsl::mix(ret.pdf, ret.pdf * ret.pdf, ret.pdf < numeric_limits::max); emissive /= (1.0 + _pdfSq * ray.payload.otherTechniqueHeuristic); ray.payload.accumulation += emissive; } @@ -143,13 +143,13 @@ struct Unidirectional if (!partitionRandVariable(eps0.z, rcpChoiceProb) && depth < 2u) { uint32_t randLightID = uint32_t(float32_t(randGen.rng()) / numeric_limits::max) * nee.lightCount; - quotient_pdf_type neeContrib_pdf; - scalar_type t; - sample_type nee_sample = nee.generate_and_quotient_and_pdf( - neeContrib_pdf, t, - scene, randLightID, intersection, interaction, + typename nee_type::sample_quotient_return_type ret = nee.generate_and_quotient_and_pdf( + randLightID, intersection, interaction, isBSDF, eps0, depth ); + scalar_type t = ret.newRayMaxT; + sample_type nee_sample = ret.sample_; + quotient_pdf_type neeContrib_pdf = ret.quotient_pdf; // We don't allow non watertight transmitters in this renderer bool validPath = nee_sample.getNdotL() > numeric_limits::min && nee_sample.isValid(); @@ -241,6 +241,8 @@ struct Unidirectional ray_type ray = rayGen.generate(uvw); ray.initPayload(); + nee.scene = scene; + NBL_IF_CONSTEXPR (nee_type::IsPolygonMethodProjectedSolidAngle) { ray.normalAtOrigin = hlsl::promote(0.0); From 676831ec4caba8994a7a6db95f071f6bdced3caf Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 16 Feb 2026 17:11:11 +0700 Subject: [PATCH 028/101] scene does the mapping from object id to light/mat id --- .../builtin/hlsl/path_tracing/concepts.hlsl | 3 ++- .../hlsl/path_tracing/unidirectional.hlsl | 23 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index c493d1698a..d3901ca1aa 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -184,7 +184,8 @@ NBL_CONCEPT_BEGIN(3) NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getBsdfLightIDs(id)), ::nbl::hlsl::is_same_v, uint32_t)) + ((NBL_CONCEPT_REQ_TYPE)(T::mat_light_id_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getMatLightIDs(id)), ::nbl::hlsl::is_same_v, typename T::mat_light_id_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getNormal(id, intersectP)), ::nbl::hlsl::is_same_v, typename T::vector3_type)) ); #undef id diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index f0659eb321..7ccd591a15 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -95,7 +95,7 @@ struct Unidirectional const object_handle_type objectID = ray.objectID; const vector3_type intersection = ray.origin + ray.direction * ray.intersectionT; - uint32_t bsdfLightIDs = scene.getBsdfLightIDs(objectID); + typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(objectID); vector3_type N = scene.getNormal(objectID, intersection); N = nbl::hlsl::normalize(N); ray_dir_info_type V; @@ -107,23 +107,22 @@ struct Unidirectional vector3_type throughput = ray.payload.throughput; // emissive - const uint32_t lightID = glsl::bitfieldExtract(bsdfLightIDs, 16, 16); - if (lightID != light_type::INVALID_ID) + if (matLightID.isLight()) { - typename nee_type::eval_pdf_return_type ret = nee.deferred_eval_and_pdf(lightID, ray); + typename nee_type::eval_pdf_return_type ret = nee.deferred_eval_and_pdf(matLightID.lightID, ray); measure_type emissive = ret.radiance * throughput; scalar_type _pdfSq = hlsl::mix(ret.pdf, ret.pdf * ret.pdf, ret.pdf < numeric_limits::max); emissive /= (1.0 + _pdfSq * ray.payload.otherTechniqueHeuristic); ray.payload.accumulation += emissive; } - const uint32_t bsdfID = glsl::bitfieldExtract(bsdfLightIDs, 0, 16); - if (bsdfID == bxdfnode_type::INVALID_ID) + if (!matLightID.isBxDF()) return false; - bxdfnode_type bxdf = materialSystem.bxdfs[bsdfID]; + const uint32_t matID = matLightID.matID; + bxdfnode_type bxdf = materialSystem.bxdfs[matID]; - const bool isBSDF = materialSystem.isBSDF(bsdfID); + const bool isBSDF = materialSystem.isBSDF(matID); vector3_type eps0 = rand3d(depth, _sample, 0u); vector3_type eps1 = rand3d(depth, _sample, 1u); @@ -157,14 +156,14 @@ struct Unidirectional bxdf::fresnel::OrientedEtas orientedEta = bxdf::fresnel::OrientedEtas::create(interaction.getNdotV(), hlsl::promote(monochromeEta)); anisocache_type _cache = anisocache_type::template create(interaction, nee_sample, orientedEta); validPath = validPath && _cache.getAbsNdotH() >= 0.0; - materialSystem.bxdfs[bsdfID].params.eta = monochromeEta; + materialSystem.bxdfs[matID].params.eta = monochromeEta; if (neeContrib_pdf.pdf < numeric_limits::max) { if (validPath) { // example only uses isotropic bxdfs - quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bsdfID, nee_sample, interaction.isotropic, _cache.iso_cache); + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, nee_sample, interaction.isotropic, _cache.iso_cache); neeContrib_pdf.quotient *= bxdf.albedo * throughput * bsdf_quotient_pdf.quotient; const scalar_type otherGenOverChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb; const scalar_type otherGenOverLightAndChoice = otherGenOverChoice / bsdf_quotient_pdf.pdf; @@ -185,14 +184,14 @@ struct Unidirectional vector3_type bxdfSample; { anisocache_type _cache; - sample_type bsdf_sample = materialSystem.generate(bsdfID, interaction, eps1, _cache); + sample_type bsdf_sample = materialSystem.generate(matID, interaction, eps1, _cache); if (!bsdf_sample.isValid()) return false; // example only uses isotropic bxdfs // the value of the bsdf divided by the probability of the sample being generated - quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bsdfID, bsdf_sample, interaction.isotropic, _cache.iso_cache); + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, bsdf_sample, interaction.isotropic, _cache.iso_cache); throughput *= bxdf.albedo * bsdf_quotient_pdf.quotient; bxdfPdf = bsdf_quotient_pdf.pdf; bxdfSample = bsdf_sample.getL().getDirection(); From 3a90b0cd2877a14c0b14c2ce3e17ae3d28d8f15a Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Mon, 16 Feb 2026 16:05:39 +0100 Subject: [PATCH 029/101] Finalize RWMC review fixes --- examples_tests | 2 +- .../builtin/hlsl/rwmc/CascadeAccumulator.hlsl | 55 ++-- .../builtin/hlsl/rwmc/ResolveParameters.hlsl | 39 ++- .../hlsl/rwmc/SplattingParameters.hlsl | 54 ++-- include/nbl/builtin/hlsl/rwmc/resolve.hlsl | 258 +++++++++--------- 5 files changed, 225 insertions(+), 183 deletions(-) diff --git a/examples_tests b/examples_tests index 4246192d21..130e049899 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 4246192d213c0c42a508257bd219c1fca0cb238f +Subproject commit 130e049899be16b50a5d27d7c67310a24d953530 diff --git a/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl b/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl index cdbb8271d2..e4bc9aa4fa 100644 --- a/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl @@ -13,28 +13,36 @@ namespace hlsl namespace rwmc { -template) +template && concepts::UnsignedIntegralScalar) struct DefaultCascades { using layer_type = CascadeLayerType; + using sample_count_type = SampleCountType; + NBL_CONSTEXPR uint32_t cascade_count = CascadeCount; - uint32_t cascadeSampleCounter[CascadeCount]; - CascadeLayerType data[CascadeCount]; + sample_count_type cascadeSampleCounter[cascade_count]; + CascadeLayerType data[cascade_count]; + + void clear(uint32_t cascadeIx) + { + cascadeSampleCounter[cascadeIx] = sample_count_type(0u); + data[cascadeIx] = promote(0.0f); + } void addSampleIntoCascadeEntry(CascadeLayerType _sample, uint16_t lowerCascadeIndex, SplattingParameters::scalar_t lowerCascadeLevelWeight, SplattingParameters::scalar_t higherCascadeLevelWeight, uint32_t sampleCount) { - const float reciprocalSampleCount = 1.0f / float(sampleCount); + const SplattingParameters::scalar_t reciprocalSampleCount = SplattingParameters::scalar_t(1.0f) / SplattingParameters::scalar_t(sampleCount); - uint32_t lowerCascadeSampleCount = cascadeSampleCounter[lowerCascadeIndex]; + sample_count_type lowerCascadeSampleCount = cascadeSampleCounter[lowerCascadeIndex]; data[lowerCascadeIndex] += (_sample * lowerCascadeLevelWeight - (sampleCount - lowerCascadeSampleCount) * data[lowerCascadeIndex]) * reciprocalSampleCount; - cascadeSampleCounter[lowerCascadeIndex] = sampleCount; + cascadeSampleCounter[lowerCascadeIndex] = sample_count_type(sampleCount); - uint16_t higherCascadeIndex = lowerCascadeIndex + 1u; - if (higherCascadeIndex < CascadeCount) + uint16_t higherCascadeIndex = lowerCascadeIndex + uint16_t(1u); + if (higherCascadeIndex < cascade_count) { - uint32_t higherCascadeSampleCount = cascadeSampleCounter[higherCascadeIndex]; + sample_count_type higherCascadeSampleCount = cascadeSampleCounter[higherCascadeIndex]; data[higherCascadeIndex] += (_sample * higherCascadeLevelWeight - (sampleCount - higherCascadeSampleCount) * data[higherCascadeIndex]) * reciprocalSampleCount; - cascadeSampleCounter[higherCascadeIndex] = sampleCount; + cascadeSampleCounter[higherCascadeIndex] = sample_count_type(sampleCount); } } }; @@ -43,9 +51,9 @@ template struct CascadeAccumulator { using input_sample_type = typename CascadesType::layer_type; - using cascade_layer_scalar_type = typename vector_traits::scalar_type; using this_t = CascadeAccumulator; using output_storage_type = CascadesType; + NBL_CONSTEXPR uint32_t cascade_count = output_storage_type::cascade_count; output_storage_type accumulation; SplattingParameters splattingParameters; @@ -53,11 +61,8 @@ struct CascadeAccumulator static this_t create(NBL_CONST_REF_ARG(SplattingParameters) settings) { this_t retval; - for (int i = 0; i < CascadeCount; ++i) - { - retval.accumulation.data[i] = promote(0.0f); - retval.accumulation.cascadeSampleCounter[i] = 0u; - } + for (uint32_t i = 0u; i < cascade_count; ++i) + retval.accumulation.clear(i); retval.splattingParameters = settings; return retval; @@ -66,16 +71,16 @@ struct CascadeAccumulator // most of this code is stolen from https://cg.ivd.kit.edu/publications/2018/rwmc/tool/split.cpp void addSample(uint32_t sampleCount, input_sample_type _sample) { - const float32_t2 unpackedParams = hlsl::unpackHalf2x16(splattingParameters.packedLog2); - const SplattingParameters::scalar_t log2Start = unpackedParams[0]; - const SplattingParameters::scalar_t rcpLog2Base = unpackedParams[1]; - const SplattingParameters::scalar_t luma = splattingParameters.getLuma(_sample); + const SplattingParameters::scalar_t baseRootOfStart = splattingParameters.baseRootOfStart(); + const SplattingParameters::scalar_t rcpLog2Base = splattingParameters.rcpLog2Base(); + const SplattingParameters::scalar_t luma = splattingParameters.calcLuma(_sample); const SplattingParameters::scalar_t log2Luma = log2(luma); - const SplattingParameters::scalar_t cascade = log2Luma * rcpLog2Base - log2Start * rcpLog2Base; - const SplattingParameters::scalar_t lastCascade = CascadeCount - 1; + const SplattingParameters::scalar_t log2BaseRootOfStart = log2(baseRootOfStart); + const SplattingParameters::scalar_t cascade = log2Luma * rcpLog2Base - log2BaseRootOfStart; + const SplattingParameters::scalar_t lastCascade = cascade_count - 1u; const SplattingParameters::scalar_t clampedCascade = clamp(cascade, 0, lastCascade); // c<=0 -> 0, c>=Count-1 -> Count-1 - uint16_t lowerCascadeIndex = floor(cascade); + uint16_t lowerCascadeIndex = uint16_t(floor(clampedCascade)); // 0 whenever clamped or `cascade` is integer (when `clampedCascade` is integer) SplattingParameters::scalar_t higherCascadeWeight = clampedCascade - floor(clampedCascade); // never 0 thanks to magic of `1-fract(x)` @@ -83,7 +88,7 @@ struct CascadeAccumulator // handle super bright sample case if (cascade > lastCascade) - lowerCascadeWeight = exp2(log2Start + (1.0f/rcpLog2Base) * (lastCascade) - log2Luma); + lowerCascadeWeight = exp2((log2BaseRootOfStart + lastCascade) / rcpLog2Base - log2Luma); accumulation.addSampleIntoCascadeEntry(_sample, lowerCascadeIndex, lowerCascadeWeight, higherCascadeWeight, sampleCount); } @@ -95,4 +100,4 @@ struct CascadeAccumulator } } -#endif \ No newline at end of file +#endif diff --git a/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl index 17e3f8d720..c9a79d1149 100644 --- a/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl @@ -1,7 +1,8 @@ #ifndef _NBL_BUILTIN_HLSL_RWMC_RESOLVE_PARAMETERS_HLSL_INCLUDED_ -#define _NBL_BUILTIN_HLSL_RWMC_RESOLVE_PARAMETERS_HLSL_INCLUDED_ - -#include "nbl/builtin/hlsl/cpp_compat.hlsl" +#define _NBL_BUILTIN_HLSL_RWMC_RESOLVE_PARAMETERS_HLSL_INCLUDED_ + +#include "nbl/builtin/hlsl/cpp_compat.hlsl" +#include namespace nbl { @@ -10,11 +11,13 @@ namespace hlsl namespace rwmc { -struct ResolveParameters -{ - static ResolveParameters create(float base, uint32_t sampleCount, float minReliableLuma, float kappa) - { - ResolveParameters retval; +struct ResolveParameters +{ + using scalar_t = float32_t; + + static ResolveParameters create(float base, uint32_t sampleCount, float minReliableLuma, float kappa) + { + ResolveParameters retval; retval.initialEmin = minReliableLuma; retval.reciprocalBase = 1.f / base; const float N = float(sampleCount); @@ -25,12 +28,18 @@ struct ResolveParameters retval.colorReliabilityFactor = base + (1.f - base) * retval.reciprocalKappa; retval.NOverKappa = N * retval.reciprocalKappa; - return retval; - } - - float initialEmin; // a minimum image brightness that we always consider reliable - float reciprocalBase; - float reciprocalN; + return retval; + } + + template + scalar_t calcLuma(NBL_CONST_REF_ARG(SampleType) col) + { + return hlsl::dot(hlsl::transpose(colorspace::scRGBtoXYZ)[1], col); + } + + float initialEmin; // a minimum image brightness that we always consider reliable + float reciprocalBase; + float reciprocalN; float reciprocalKappa; float colorReliabilityFactor; float NOverKappa; @@ -40,4 +49,4 @@ struct ResolveParameters } } -#endif \ No newline at end of file +#endif diff --git a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl index 74b062a2de..a7c95e9e27 100644 --- a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl @@ -1,7 +1,8 @@ #ifndef _NBL_BUILTIN_HLSL_RWMC_SPLATTING_PARAMETERS_HLSL_INCLUDED_ -#define _NBL_BUILTIN_HLSL_RWMC_SPLATTING_PARAMETERS_HLSL_INCLUDED_ - -#include "nbl/builtin/hlsl/cpp_compat.hlsl" +#define _NBL_BUILTIN_HLSL_RWMC_SPLATTING_PARAMETERS_HLSL_INCLUDED_ + +#include "nbl/builtin/hlsl/cpp_compat.hlsl" +#include namespace nbl { @@ -10,24 +11,39 @@ namespace hlsl namespace rwmc { -struct SplattingParameters -{ - using scalar_t = float32_t; - - // float16_t log2Start; 0 - // float16_t rcpLog2Base; 1 - // pack as Half2x16 - int32_t packedLog2; - - template - scalar_t getLuma(NBL_CONST_REF_ARG(CascadeLayerType) col) - { - return hlsl::dot(hlsl::transpose(colorspace::scRGBtoXYZ)[1], col); - } -}; +struct SplattingParameters +{ + using scalar_t = float32_t; + + // float16_t baseRootOfStart; 0 + // float16_t rcpLog2Base; 1 + // pack as Half2x16 + int32_t packedLog2; + + float32_t2 unpackedLog2Parameters() + { + return hlsl::unpackHalf2x16(packedLog2); + } + + scalar_t baseRootOfStart() + { + return unpackedLog2Parameters()[0]; + } + + scalar_t rcpLog2Base() + { + return unpackedLog2Parameters()[1]; + } + + template + scalar_t calcLuma(NBL_CONST_REF_ARG(CascadeLayerType) col) + { + return hlsl::dot(hlsl::transpose(colorspace::scRGBtoXYZ)[1], col); + } +}; } } } -#endif \ No newline at end of file +#endif diff --git a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl index a3b4236dca..ac65c2ed03 100644 --- a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl @@ -1,93 +1,105 @@ -#ifndef _NBL_BUILTIN_HLSL_RWMC_RESOLVE_HLSL_INCLUDED_ -#define _NBL_BUILTIN_HLSL_RWMC_RESOLVE_HLSL_INCLUDED_ - -#include "nbl/builtin/hlsl/cpp_compat.hlsl" -#include -#include -#include -#include -#include +#ifndef _NBL_BUILTIN_HLSL_RWMC_RESOLVE_HLSL_INCLUDED_ +#define _NBL_BUILTIN_HLSL_RWMC_RESOLVE_HLSL_INCLUDED_ + +#include "nbl/builtin/hlsl/cpp_compat.hlsl" +#include +#include +#include namespace nbl { namespace hlsl { -namespace rwmc -{ - // declare concept -#define NBL_CONCEPT_NAME ResolveAccessorBase -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)(int32_t) -#define NBL_CONCEPT_TPLT_PRM_NAMES (T)(VectorScalarType)(Components) -// not the greatest syntax but works -#define NBL_CONCEPT_PARAM_0 (a,T) -#define NBL_CONCEPT_PARAM_1 (vec,vector) -// start concept -NBL_CONCEPT_BEGIN(2) -// need to be defined AFTER the concept begins -#define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -#define vec NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 -NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.calcLuma(vec)), ::nbl::hlsl::is_same_v, VectorScalarType)) - ((NBL_CONCEPT_REQ_TYPE)(T::output_t)) -); -#undef a -#undef vec -#include - -/* ResolveAccessor is required to: -* - satisfy `LoadableImage` concept requirements -* - implement function called `calcLuma` which calculates luma from a 3 component pixel value -*/ - -template -NBL_BOOL_CONCEPT ResolveAccessor = ResolveAccessorBase && concepts::accessors::LoadableImage; - -template -struct ResolveAccessorAdaptor -{ - using output_scalar_t = OutputScalar; - NBL_CONSTEXPR int32_t Components = 3; +namespace rwmc +{ +// declare concept +#define NBL_CONCEPT_NAME ResolveLumaParamsBase +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T)(SampleType) +#define NBL_CONCEPT_PARAM_0 (a,T) +#define NBL_CONCEPT_PARAM_1 (sample,SampleType) +NBL_CONCEPT_BEGIN(2) +#define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define sample NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::scalar_t)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(concepts::FloatingPointScalar, T::scalar_t)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template calcLuma(sample)), ::nbl::hlsl::is_same_v, typename T::scalar_t)) +); +#undef sample +#undef a +#include + +template +NBL_BOOL_CONCEPT ResolveLumaParams = ResolveLumaParamsBase; + +// declare concept +#define NBL_CONCEPT_NAME ResolveAccessorBase +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (a,T) +#define NBL_CONCEPT_PARAM_1 (uv,vector) +#define NBL_CONCEPT_PARAM_2 (layer,uint16_t) +NBL_CONCEPT_BEGIN(3) +#define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define uv NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define layer NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::output_t)) + ((NBL_CONCEPT_REQ_TYPE)(T::output_scalar_t)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer)), ::nbl::hlsl::is_same_v, typename T::output_t)) +); +#undef layer +#undef uv +#undef a +#include + +template +NBL_BOOL_CONCEPT ResolveAccessor = ResolveAccessorBase && concepts::accessors::LoadableImage; + +template +struct ResolveAccessorAdaptor +{ + using output_scalar_t = OutputScalar; + NBL_CONSTEXPR int32_t Components = 3; using output_t = vector; NBL_CONSTEXPR int32_t image_dimension = 2; - - RWTexture2DArray cascade; - - float32_t calcLuma(NBL_REF_ARG(float32_t3) col) - { - return hlsl::dot(colorspace::scRGB::ToXYZ()[1], col); - } - - template - output_t get(vector uv, uint16_t layer) - { + + RWTexture2DArray cascade; + + template + output_t get(vector uv, uint16_t layer) + { uint32_t imgWidth, imgHeight, layers; - cascade.GetDimensions(imgWidth, imgHeight, layers); - int16_t2 cascadeImageDimension = int16_t2(imgWidth, imgHeight); - - if (any(uv < int16_t2(0, 0)) || any(uv > cascadeImageDimension)) - return promote(0); + cascade.GetDimensions(imgWidth, imgHeight, layers); + int16_t2 cascadeImageDimension = int16_t2(imgWidth, imgHeight); + + if (any(uv < int16_t2(0, 0)) || any(uv >= cascadeImageDimension)) + return promote(0); return cascade.Load(int32_t3(uv, int32_t(layer))); } }; -template) -struct Resolver -{ - using output_t = typename CascadeAccessor::output_t; - using scalar_t = typename vector_traits::scalar_type; - - struct CascadeSample - { - float32_t3 centerValue; - float normalizedCenterLuma; - float normalizedNeighbourhoodAverageLuma; - }; - - static Resolver create(NBL_REF_ARG(ResolveParameters) resolveParameters) - { - Resolver retval; - retval.params = resolveParameters; +template && ResolveLumaParams) +struct Resolver +{ + using output_t = typename CascadeAccessor::output_t; + using output_scalar_t = typename vector_traits::scalar_type; + using scalar_t = typename ResolveParamsType::scalar_t; + NBL_CONSTEXPR static uint16_t last_cascade = CascadeCount - 1u; + + struct CascadeSample + { + output_t centerValue; + float normalizedCenterLuma; + float normalizedNeighbourhoodAverageLuma; + }; + + static Resolver create(NBL_REF_ARG(ResolveParamsType) resolveParameters) + { + Resolver retval; + retval.params = resolveParameters; return retval; } @@ -97,18 +109,18 @@ struct Resolver scalar_t reciprocalBaseI = 1.f; CascadeSample curr = __sampleCascade(acc, coord, 0u, reciprocalBaseI); - output_t accumulation = promote(0.0f); - scalar_t Emin = params.initialEmin; - - scalar_t prevNormalizedCenterLuma, prevNormalizedNeighbourhoodAverageLuma; - NBL_UNROLL - for (int16_t i = 0u; i <= CascadeCount - 1; i++) - { - const bool notFirstCascade = i != 0; - const bool notLastCascade = i != (CascadeCount - 1); - - CascadeSample next; - if (notLastCascade) + output_t accumulation = promote(0.0f); + scalar_t Emin = params.initialEmin; + + scalar_t prevNormalizedCenterLuma, prevNormalizedNeighbourhoodAverageLuma; + NBL_UNROLL + for (uint16_t i = 0u; i <= last_cascade; i++) + { + const bool notFirstCascade = i != 0; + const bool notLastCascade = i != last_cascade; + + CascadeSample next; + if (notLastCascade) { reciprocalBaseI *= params.reciprocalBase; next = __sampleCascade(acc, coord, int16_t(i + 1), reciprocalBaseI); @@ -132,11 +144,11 @@ struct Resolver globalReliability += next.normalizedNeighbourhoodAverageLuma; } // check if above minimum sampling threshold (avg 9 sample occurences in 3x3 neighbourhood), then use per-pixel reliability (NOTE: tertiary op is in reverse) - reliability = globalReliability < params.reciprocalN ? globalReliability : localReliability; - { - const scalar_t accumLuma = acc.calcLuma(accumulation); - if (accumLuma > Emin) - Emin = accumLuma; + reliability = globalReliability < params.reciprocalN ? globalReliability : localReliability; + { + const scalar_t accumLuma = params.template calcLuma(accumulation); + if (accumLuma > Emin) + Emin = accumLuma; const scalar_t colorReliability = Emin * reciprocalBaseI * params.colorReliabilityFactor; @@ -153,40 +165,40 @@ struct Resolver curr = next; } - return accumulation; - } - - ResolveParameters params; + return accumulation; + } + + ResolveParamsType params; // pseudo private stuff: - CascadeSample __sampleCascade(NBL_REF_ARG(CascadeAccessor) acc, int16_t2 coord, uint16_t cascadeIndex, scalar_t reciprocalBaseI) - { - output_t neighbourhood[9]; - neighbourhood[0] = acc.template get(coord + int16_t2(-1, -1), cascadeIndex); - neighbourhood[1] = acc.template get(coord + int16_t2(0, -1), cascadeIndex); - neighbourhood[2] = acc.template get(coord + int16_t2(1, -1), cascadeIndex); - neighbourhood[3] = acc.template get(coord + int16_t2(-1, 0), cascadeIndex); - neighbourhood[4] = acc.template get(coord + int16_t2(0, 0), cascadeIndex); - neighbourhood[5] = acc.template get(coord + int16_t2(1, 0), cascadeIndex); - neighbourhood[6] = acc.template get(coord + int16_t2(-1, 1), cascadeIndex); - neighbourhood[7] = acc.template get(coord + int16_t2(0, 1), cascadeIndex); - neighbourhood[8] = acc.template get(coord + int16_t2(1, 1), cascadeIndex); - - // numerical robustness - float32_t3 excl_hood_sum = ((neighbourhood[0] + neighbourhood[1]) + (neighbourhood[2] + neighbourhood[3])) + - ((neighbourhood[5] + neighbourhood[6]) + (neighbourhood[7] + neighbourhood[8])); - - CascadeSample retval; - retval.centerValue = neighbourhood[4]; - retval.normalizedNeighbourhoodAverageLuma = retval.normalizedCenterLuma = acc.calcLuma(neighbourhood[4]) * reciprocalBaseI; - retval.normalizedNeighbourhoodAverageLuma = (acc.calcLuma(excl_hood_sum) * reciprocalBaseI + retval.normalizedNeighbourhoodAverageLuma) / 9.f; - return retval; - } -}; + CascadeSample __sampleCascade(NBL_REF_ARG(CascadeAccessor) acc, int16_t2 coord, uint16_t cascadeIndex, scalar_t reciprocalBaseI) + { + output_t neighbourhood[9]; + neighbourhood[0] = acc.template get(coord + int16_t2(-1, -1), cascadeIndex); + neighbourhood[1] = acc.template get(coord + int16_t2(0, -1), cascadeIndex); + neighbourhood[2] = acc.template get(coord + int16_t2(1, -1), cascadeIndex); + neighbourhood[3] = acc.template get(coord + int16_t2(-1, 0), cascadeIndex); + neighbourhood[4] = acc.template get(coord + int16_t2(0, 0), cascadeIndex); + neighbourhood[5] = acc.template get(coord + int16_t2(1, 0), cascadeIndex); + neighbourhood[6] = acc.template get(coord + int16_t2(-1, 1), cascadeIndex); + neighbourhood[7] = acc.template get(coord + int16_t2(0, 1), cascadeIndex); + neighbourhood[8] = acc.template get(coord + int16_t2(1, 1), cascadeIndex); + + // numerical robustness + output_t excl_hood_sum = ((neighbourhood[0] + neighbourhood[1]) + (neighbourhood[2] + neighbourhood[3])) + + ((neighbourhood[5] + neighbourhood[6]) + (neighbourhood[7] + neighbourhood[8])); + + CascadeSample retval; + retval.centerValue = neighbourhood[4]; + retval.normalizedNeighbourhoodAverageLuma = retval.normalizedCenterLuma = params.template calcLuma(neighbourhood[4]) * reciprocalBaseI; + retval.normalizedNeighbourhoodAverageLuma = (params.template calcLuma(excl_hood_sum) * reciprocalBaseI + retval.normalizedNeighbourhoodAverageLuma) / 9.f; + return retval; + } +}; } } } -#endif \ No newline at end of file +#endif From 187ced659281777cd98d5b3d449b7ab1aea47990 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Mon, 16 Feb 2026 16:42:32 +0100 Subject: [PATCH 030/101] Restrict Resolver template parameters to review scope --- include/nbl/builtin/hlsl/rwmc/resolve.hlsl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl index ac65c2ed03..84b0d44580 100644 --- a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl @@ -81,12 +81,12 @@ struct ResolveAccessorAdaptor } }; -template && ResolveLumaParams) +template && ResolveLumaParams) struct Resolver { using output_t = typename CascadeAccessor::output_t; using output_scalar_t = typename vector_traits::scalar_type; - using scalar_t = typename ResolveParamsType::scalar_t; + using scalar_t = typename ResolveParameters::scalar_t; NBL_CONSTEXPR static uint16_t last_cascade = CascadeCount - 1u; struct CascadeSample @@ -96,7 +96,7 @@ struct Resolver float normalizedNeighbourhoodAverageLuma; }; - static Resolver create(NBL_REF_ARG(ResolveParamsType) resolveParameters) + static Resolver create(NBL_REF_ARG(ResolveParameters) resolveParameters) { Resolver retval; retval.params = resolveParameters; @@ -168,7 +168,7 @@ struct Resolver return accumulation; } - ResolveParamsType params; + ResolveParameters params; // pseudo private stuff: From a60406459b66c073e4f4ce929364a1387ecca13c Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 17 Feb 2026 13:59:59 +0700 Subject: [PATCH 031/101] renamed constants in camelcase --- .../nbl/builtin/hlsl/path_tracing/default_accumulator.hlsl | 2 +- include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/default_accumulator.hlsl b/include/nbl/builtin/hlsl/path_tracing/default_accumulator.hlsl index d9df50e023..e4174a7c12 100644 --- a/include/nbl/builtin/hlsl/path_tracing/default_accumulator.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/default_accumulator.hlsl @@ -29,7 +29,7 @@ struct DefaultAccumulator void addSample(uint32_t sampleCount, input_sample_type _sample) { - scalar_type rcpSampleSize = 1.0 / (sampleCount); + scalar_type rcpSampleSize = scalar_type(1.0) / _static_cast(sampleCount); accumulation += (_sample - accumulation) * rcpSampleSize; } diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 7ccd591a15..448217b24e 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -79,7 +79,7 @@ struct Unidirectional vector3_type rand3d(uint32_t protoDimension, uint32_t _sample, uint32_t i) { using sequence_type = sampling::QuantizedSequence; - uint32_t address = glsl::bitfieldInsert(protoDimension, _sample, MAX_DEPTH_LOG2, MAX_SAMPLES_LOG2); + uint32_t address = glsl::bitfieldInsert(protoDimension, _sample, MaxDepthLog2, MaxSamplesLog2); sequence_type tmpSeq = vk::RawBufferLoad(pSampleBuffer + (address + i) * sizeof(sequence_type)); return tmpSeq.template decode(randGen()); } @@ -271,8 +271,8 @@ struct Unidirectional // TODO: russian roulette early exit? } - NBL_CONSTEXPR_STATIC_INLINE uint32_t MAX_DEPTH_LOG2 = 4u; - NBL_CONSTEXPR_STATIC_INLINE uint32_t MAX_SAMPLES_LOG2 = 10u; + NBL_CONSTEXPR_STATIC_INLINE uint32_t MaxDepthLog2 = 4u; + NBL_CONSTEXPR_STATIC_INLINE uint32_t MaxSamplesLog2 = 10u; randgen_type randGen; raygen_type rayGen; From 87c7fb18e33d476946c7bcb510a10298669ce3b0 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 17 Feb 2026 16:22:59 +0700 Subject: [PATCH 032/101] intersector returns a struct with intersection point, interaction, hit bool --- .../builtin/hlsl/path_tracing/concepts.hlsl | 3 +- .../hlsl/path_tracing/unidirectional.hlsl | 37 +++++++------------ 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index d3901ca1aa..a5a5be275d 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -57,7 +57,8 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::scene_type)) ((NBL_CONCEPT_REQ_TYPE)(T::ray_type)) ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceRay(ray, scene)), ::nbl::hlsl::is_same_v, typename T::object_handle_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::intersect_data_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceRay(ray, scene)), ::nbl::hlsl::is_same_v, typename T::intersect_data_type)) ); #undef scene #undef ray diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 448217b24e..426bba759f 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -59,23 +59,17 @@ struct Unidirectional using vector3_type = vector; using monochrome_type = vector; using measure_type = typename MaterialSystem::measure_type; - using output_storage_type = typename Accumulator::output_storage_type; // ? using sample_type = typename NextEventEstimator::sample_type; using ray_dir_info_type = typename sample_type::ray_dir_info_type; using ray_type = typename RayGen::ray_type; using object_handle_type = typename Intersector::object_handle_type; - using light_type = typename NextEventEstimator::light_type; + using intersect_data_type = typename Intersector::intersect_data_type; using bxdfnode_type = typename MaterialSystem::bxdfnode_type; using anisotropic_interaction_type = typename MaterialSystem::anisotropic_interaction_type; using isotropic_interaction_type = typename anisotropic_interaction_type::isotropic_interaction_type; using anisocache_type = typename MaterialSystem::anisocache_type; - using isocache_type = typename anisocache_type::isocache_type; using quotient_pdf_type = typename NextEventEstimator::quotient_pdf_type; - using diffuse_op_type = typename MaterialSystem::diffuse_op_type; - using conductor_op_type = typename MaterialSystem::conductor_op_type; - using dielectric_op_type = typename MaterialSystem::dielectric_op_type; - vector3_type rand3d(uint32_t protoDimension, uint32_t _sample, uint32_t i) { using sequence_type = sampling::QuantizedSequence; @@ -90,23 +84,19 @@ struct Unidirectional } // TODO: probably will only work with isotropic surfaces, need to do aniso - bool closestHitProgram(uint32_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(scene_type) scene) + bool closestHitProgram(uint32_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(intersect_data_type) intersectData, NBL_CONST_REF_ARG(scene_type) scene) { - const object_handle_type objectID = ray.objectID; - const vector3_type intersection = ray.origin + ray.direction * ray.intersectionT; - - typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(objectID); - vector3_type N = scene.getNormal(objectID, intersection); - N = nbl::hlsl::normalize(N); - ray_dir_info_type V; - V.setDirection(-ray.direction); - isotropic_interaction_type iso_interaction = isotropic_interaction_type::create(V, N); + const vector3_type intersection = intersectData.intersection; + + isotropic_interaction_type iso_interaction = intersectData.iso_interaction; + anisotropic_interaction_type interaction = intersectData.aniso_interaction; iso_interaction.luminosityContributionHint = colorspace::scRGBtoXYZ[1]; - anisotropic_interaction_type interaction = anisotropic_interaction_type::create(iso_interaction); + interaction.isotropic.luminosityContributionHint = colorspace::scRGBtoXYZ[1]; vector3_type throughput = ray.payload.throughput; // emissive + typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(ray.objectID); if (matLightID.isLight()) { typename nee_type::eval_pdf_return_type ret = nee.deferred_eval_and_pdf(matLightID.lightID, ray); @@ -173,7 +163,7 @@ struct Unidirectional nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); nee_ray.direction = nee_sample.getL().getDirection(); nee_ray.intersectionT = t; - if (bsdf_quotient_pdf.pdf < numeric_limits::max && getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold && intersector_type::traceRay(nee_ray, scene).id == -1) + if (bsdf_quotient_pdf.pdf < numeric_limits::max && getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold && !intersector_type::traceRay(nee_ray, scene).foundHit) ray.payload.accumulation += neeContrib_pdf.quotient; } } @@ -233,7 +223,7 @@ struct Unidirectional } // Li - void sampleMeasure(uint32_t sampleIndex, uint32_t maxDepth, NBL_CONST_REF_ARG(scene_type) scene, NBL_REF_ARG(Accumulator) accumulator) + void sampleMeasure(uint32_t sampleIndex, uint32_t maxDepth, NBL_REF_ARG(Accumulator) accumulator) { //scalar_type meanLumaSq = 0.0; vector3_type uvw = rand3d(0u, sampleIndex, 0u); @@ -254,11 +244,11 @@ struct Unidirectional for (int d = 1; (d <= maxDepth) && hit && rayAlive; d += 2) { ray.intersectionT = numeric_limits::max; - ray.objectID = intersector_type::traceRay(ray, scene); + intersect_data_type intersection = intersector_type::traceRay(ray, scene); - hit = ray.objectID.id != -1; + hit = intersection.foundHit; if (hit) - rayAlive = closestHitProgram(1, sampleIndex, ray, scene); + rayAlive = closestHitProgram(d, sampleIndex, ray, intersection, scene); } if (!hit) missProgram(ray); @@ -278,6 +268,7 @@ struct Unidirectional raygen_type rayGen; material_system_type materialSystem; nee_type nee; + scene_type scene; uint64_t pSampleBuffer; }; From 9d98071b66862aa77496b6f1147faf06b861abf9 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 17 Feb 2026 16:39:56 +0700 Subject: [PATCH 033/101] randGen should return a unorm float, everything that rand3d() does with quantized sequence too --- .../builtin/hlsl/path_tracing/concepts.hlsl | 7 +++++-- .../hlsl/path_tracing/unidirectional.hlsl | 19 ++++--------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index a5a5be275d..6cc9fd5b1f 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -16,13 +16,16 @@ namespace concepts #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (rand, T) -NBL_CONCEPT_BEGIN(1) +#define NBL_CONCEPT_PARAM_1 (sample_, uint32_t) +NBL_CONCEPT_BEGIN(2) #define rand NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define sample_ NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::rng_type)) ((NBL_CONCEPT_REQ_TYPE)(T::return_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((rand()), ::nbl::hlsl::is_same_v, typename T::return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((rand(sample_, sample_, sample_)), ::nbl::hlsl::is_same_v, typename T::return_type)) ); +#undef sample_ #undef rand #include diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 426bba759f..cc6133a1e5 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -70,20 +69,12 @@ struct Unidirectional using anisocache_type = typename MaterialSystem::anisocache_type; using quotient_pdf_type = typename NextEventEstimator::quotient_pdf_type; - vector3_type rand3d(uint32_t protoDimension, uint32_t _sample, uint32_t i) - { - using sequence_type = sampling::QuantizedSequence; - uint32_t address = glsl::bitfieldInsert(protoDimension, _sample, MaxDepthLog2, MaxSamplesLog2); - sequence_type tmpSeq = vk::RawBufferLoad(pSampleBuffer + (address + i) * sizeof(sequence_type)); - return tmpSeq.template decode(randGen()); - } - scalar_type getLuma(NBL_CONST_REF_ARG(vector3_type) col) { return hlsl::dot(colorspace::scRGBtoXYZ[1], col); } - // TODO: probably will only work with isotropic surfaces, need to do aniso + // TODO: will only work with isotropic surfaces, need to do aniso bool closestHitProgram(uint32_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(intersect_data_type) intersectData, NBL_CONST_REF_ARG(scene_type) scene) { const vector3_type intersection = intersectData.intersection; @@ -114,8 +105,8 @@ struct Unidirectional const bool isBSDF = materialSystem.isBSDF(matID); - vector3_type eps0 = rand3d(depth, _sample, 0u); - vector3_type eps1 = rand3d(depth, _sample, 1u); + vector3_type eps0 = randGen(depth, _sample, 0u); + vector3_type eps1 = randGen(depth, _sample, 1u); // thresholds const scalar_type bxdfPdfThreshold = 0.0001; @@ -226,7 +217,7 @@ struct Unidirectional void sampleMeasure(uint32_t sampleIndex, uint32_t maxDepth, NBL_REF_ARG(Accumulator) accumulator) { //scalar_type meanLumaSq = 0.0; - vector3_type uvw = rand3d(0u, sampleIndex, 0u); + vector3_type uvw = randGen(0u, sampleIndex, 0u); ray_type ray = rayGen.generate(uvw); ray.initPayload(); @@ -269,8 +260,6 @@ struct Unidirectional material_system_type materialSystem; nee_type nee; scene_type scene; - - uint64_t pSampleBuffer; }; } From acd91aad1b920b0cc3e04a7fa01a96c7296de17e Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 17 Feb 2026 17:02:42 +0700 Subject: [PATCH 034/101] refactor ray struct to be polymorphic on polygon method, new methods --- .../hlsl/path_tracing/basic_ray_gen.hlsl | 7 +++---- .../hlsl/path_tracing/unidirectional.hlsl | 17 ++++------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl b/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl index e451789977..ab1da9febf 100644 --- a/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl @@ -24,15 +24,14 @@ struct BasicRayGenerator ray_type generate(const vector3_type randVec) { - ray_type ray; - ray.origin = camPos; - vector4_type tmp = NDC; GaussianFilter filter = GaussianFilter::create(2.5, 1.5); // stochastic reconstruction filter tmp.xy += pixOffsetParam * filter.sample(randVec); // for depth of field we could do another stochastic point-pick tmp = nbl::hlsl::mul(invMVP, tmp); - ray.direction = nbl::hlsl::normalize(tmp.xyz / tmp.w - camPos); + + ray_type ray; + ray.initData(camPos, hlsl::normalize(tmp.xyz / tmp.w - camPos), hlsl::promote(0.0), false); return ray; } diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index cc6133a1e5..28a97ea3fe 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -187,13 +187,10 @@ struct Unidirectional ray.payload.otherTechniqueHeuristic = otherTechniqueHeuristic * otherTechniqueHeuristic; // trace new ray - ray.origin = intersection + bxdfSample * (1.0/*kSceneSize*/) * Tolerance::getStart(depth); - ray.direction = bxdfSample; - NBL_IF_CONSTEXPR (nee_type::IsPolygonMethodProjectedSolidAngle) - { - ray.normalAtOrigin = interaction.getN(); - ray.wasBSDFAtOrigin = isBSDF; - } + vector3_type origin = intersection + bxdfSample * (1.0/*kSceneSize*/) * Tolerance::getStart(depth); + vector3_type direction = bxdfSample; + + ray.initData(origin, direction, interaction.getN(), isBSDF); return true; } @@ -223,12 +220,6 @@ struct Unidirectional nee.scene = scene; - NBL_IF_CONSTEXPR (nee_type::IsPolygonMethodProjectedSolidAngle) - { - ray.normalAtOrigin = hlsl::promote(0.0); - ray.wasBSDFAtOrigin = false; - } - // bounces bool hit = true; bool rayAlive = true; From 70b90d66b329069d5f60cc15bb424da795ef7b50 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Tue, 17 Feb 2026 17:15:25 +0100 Subject: [PATCH 035/101] Finalize RWMC review fixes --- examples_tests | 2 +- .../accessors/anisotropically_sampled.hlsl | 2 +- .../concepts/accessors/loadable_image.hlsl | 2 +- .../hlsl/concepts/accessors/mip_mapped.hlsl | 2 +- .../builtin/hlsl/rwmc/CascadeAccumulator.hlsl | 46 ++--- .../builtin/hlsl/rwmc/ResolveParameters.hlsl | 54 +++--- .../hlsl/rwmc/SplattingParameters.hlsl | 24 ++- include/nbl/builtin/hlsl/rwmc/resolve.hlsl | 165 +++++++++--------- 8 files changed, 154 insertions(+), 143 deletions(-) diff --git a/examples_tests b/examples_tests index 130e049899..40ba7631e3 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 130e049899be16b50a5d27d7c67310a24d953530 +Subproject commit 40ba7631e305bbc5f10dfd07c96040e87b9ca4c8 diff --git a/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl index e6019d056c..b6b0716143 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl @@ -47,4 +47,4 @@ NBL_CONCEPT_END( } } } -#endif \ No newline at end of file +#endif diff --git a/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl index f7b5122c81..16fdf41c47 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl @@ -73,4 +73,4 @@ NBL_CONCEPT_END( } } } -#endif \ No newline at end of file +#endif diff --git a/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl index c49e66617b..81fb6e8502 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl @@ -44,4 +44,4 @@ NBL_CONCEPT_END( } } } -#endif \ No newline at end of file +#endif diff --git a/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl b/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl index e4bc9aa4fa..4be0fdab13 100644 --- a/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl @@ -3,7 +3,6 @@ #include #include #include -#include #include namespace nbl @@ -13,15 +12,15 @@ namespace hlsl namespace rwmc { -template && concepts::UnsignedIntegralScalar) +template && concepts::UnsignedIntegralScalar) struct DefaultCascades { using layer_type = CascadeLayerType; using sample_count_type = SampleCountType; - NBL_CONSTEXPR uint32_t cascade_count = CascadeCount; + NBL_CONSTEXPR_STATIC_INLINE uint32_t CascadeCount = CascadeCountValue; - sample_count_type cascadeSampleCounter[cascade_count]; - CascadeLayerType data[cascade_count]; + sample_count_type cascadeSampleCounter[CascadeCount]; + CascadeLayerType data[CascadeCount]; void clear(uint32_t cascadeIx) { @@ -38,7 +37,7 @@ struct DefaultCascades cascadeSampleCounter[lowerCascadeIndex] = sample_count_type(sampleCount); uint16_t higherCascadeIndex = lowerCascadeIndex + uint16_t(1u); - if (higherCascadeIndex < cascade_count) + if (higherCascadeIndex < CascadeCount) { sample_count_type higherCascadeSampleCount = cascadeSampleCounter[higherCascadeIndex]; data[higherCascadeIndex] += (_sample * higherCascadeLevelWeight - (sampleCount - higherCascadeSampleCount) * data[higherCascadeIndex]) * reciprocalSampleCount; @@ -50,20 +49,24 @@ struct DefaultCascades template struct CascadeAccumulator { + using scalar_t = typename SplattingParameters::scalar_t; using input_sample_type = typename CascadesType::layer_type; using this_t = CascadeAccumulator; - using output_storage_type = CascadesType; - NBL_CONSTEXPR uint32_t cascade_count = output_storage_type::cascade_count; - output_storage_type accumulation; + using cascades_type = CascadesType; + NBL_CONSTEXPR_STATIC_INLINE uint32_t CascadeCount = cascades_type::CascadeCount; + NBL_CONSTEXPR_STATIC_INLINE scalar_t LastCascade = scalar_t(CascadeCount - 1u); + cascades_type accumulation; SplattingParameters splattingParameters; + SplattingParameters::SPrecomputed splattingParametersPrecomputed; static this_t create(NBL_CONST_REF_ARG(SplattingParameters) settings) { this_t retval; - for (uint32_t i = 0u; i < cascade_count; ++i) + for (uint32_t i = 0u; i < CascadeCount; ++i) retval.accumulation.clear(i); retval.splattingParameters = settings; + retval.splattingParametersPrecomputed = settings.template precompute(); return retval; } @@ -71,24 +74,21 @@ struct CascadeAccumulator // most of this code is stolen from https://cg.ivd.kit.edu/publications/2018/rwmc/tool/split.cpp void addSample(uint32_t sampleCount, input_sample_type _sample) { - const SplattingParameters::scalar_t baseRootOfStart = splattingParameters.baseRootOfStart(); - const SplattingParameters::scalar_t rcpLog2Base = splattingParameters.rcpLog2Base(); - const SplattingParameters::scalar_t luma = splattingParameters.calcLuma(_sample); - const SplattingParameters::scalar_t log2Luma = log2(luma); - const SplattingParameters::scalar_t log2BaseRootOfStart = log2(baseRootOfStart); - const SplattingParameters::scalar_t cascade = log2Luma * rcpLog2Base - log2BaseRootOfStart; - const SplattingParameters::scalar_t lastCascade = cascade_count - 1u; - const SplattingParameters::scalar_t clampedCascade = clamp(cascade, 0, lastCascade); + const scalar_t luma = splattingParameters.calcLuma(_sample); + const scalar_t log2Luma = log2(luma); + const scalar_t cascade = log2Luma * splattingParametersPrecomputed.RcpLog2Base - splattingParametersPrecomputed.Log2BaseRootOfStart; + const scalar_t clampedCascade = clamp(cascade, scalar_t(0), LastCascade); + const scalar_t clampedCascadeFloor = floor(clampedCascade); // c<=0 -> 0, c>=Count-1 -> Count-1 - uint16_t lowerCascadeIndex = uint16_t(floor(clampedCascade)); + uint16_t lowerCascadeIndex = uint16_t(clampedCascadeFloor); // 0 whenever clamped or `cascade` is integer (when `clampedCascade` is integer) - SplattingParameters::scalar_t higherCascadeWeight = clampedCascade - floor(clampedCascade); + scalar_t higherCascadeWeight = clampedCascade - clampedCascadeFloor; // never 0 thanks to magic of `1-fract(x)` - SplattingParameters::scalar_t lowerCascadeWeight = SplattingParameters::scalar_t(1) - higherCascadeWeight; + scalar_t lowerCascadeWeight = scalar_t(1) - higherCascadeWeight; // handle super bright sample case - if (cascade > lastCascade) - lowerCascadeWeight = exp2((log2BaseRootOfStart + lastCascade) / rcpLog2Base - log2Luma); + if (cascade > LastCascade) + lowerCascadeWeight = exp2(splattingParametersPrecomputed.BrightSampleLumaBias - log2Luma); accumulation.addSampleIntoCascadeEntry(_sample, lowerCascadeIndex, lowerCascadeWeight, higherCascadeWeight, sampleCount); } diff --git a/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl index c9a79d1149..8a16cdc2f5 100644 --- a/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl @@ -2,7 +2,7 @@ #define _NBL_BUILTIN_HLSL_RWMC_RESOLVE_PARAMETERS_HLSL_INCLUDED_ #include "nbl/builtin/hlsl/cpp_compat.hlsl" -#include +#include namespace nbl { @@ -11,42 +11,42 @@ namespace hlsl namespace rwmc { -struct ResolveParameters +struct SResolveParameters { using scalar_t = float32_t; - static ResolveParameters create(float base, uint32_t sampleCount, float minReliableLuma, float kappa) + static SResolveParameters create(scalar_t base, uint32_t sampleCount, scalar_t minReliableLuma, scalar_t kappa) { - ResolveParameters retval; - retval.initialEmin = minReliableLuma; - retval.reciprocalBase = 1.f / base; - const float N = float(sampleCount); - retval.reciprocalN = 1.f / N; - retval.reciprocalKappa = 1.f / kappa; - // if not interested in exact expected value estimation (kappa!=1.f), can usually accept a bit more variance relative to the image brightness we already have - // allow up to ~ more energy in one sample to lessen bias in some cases - retval.colorReliabilityFactor = base + (1.f - base) * retval.reciprocalKappa; - retval.NOverKappa = N * retval.reciprocalKappa; - + SResolveParameters retval; + retval.initialEmin = minReliableLuma; + retval.reciprocalBase = 1.f / base; + const scalar_t N = scalar_t(sampleCount); + retval.reciprocalN = 1.f / N; + retval.reciprocalKappa = 1.f / kappa; + // if not interested in exact expected value estimation (kappa!=1.f), can usually accept a bit more variance relative to the image brightness we already have + // allow up to ~ more energy in one sample to lessen bias in some cases + retval.colorReliabilityFactor = base + (1.f - base) * retval.reciprocalKappa; + retval.NOverKappa = N * retval.reciprocalKappa; + return retval; } - template + template scalar_t calcLuma(NBL_CONST_REF_ARG(SampleType) col) { - return hlsl::dot(hlsl::transpose(colorspace::scRGBtoXYZ)[1], col); + return hlsl::dot(hlsl::transpose(Colorspace::ToXYZ())[1], col); } - float initialEmin; // a minimum image brightness that we always consider reliable - float reciprocalBase; - float reciprocalN; - float reciprocalKappa; - float colorReliabilityFactor; - float NOverKappa; -}; - -} -} -} + scalar_t initialEmin; // a minimum image brightness that we always consider reliable + scalar_t reciprocalBase; + scalar_t reciprocalN; + scalar_t reciprocalKappa; + scalar_t colorReliabilityFactor; + scalar_t NOverKappa; +}; + +} +} +} #endif diff --git a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl index a7c95e9e27..7cbd52f0a4 100644 --- a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl @@ -2,7 +2,7 @@ #define _NBL_BUILTIN_HLSL_RWMC_SPLATTING_PARAMETERS_HLSL_INCLUDED_ #include "nbl/builtin/hlsl/cpp_compat.hlsl" -#include +#include namespace nbl { @@ -14,6 +14,12 @@ namespace rwmc struct SplattingParameters { using scalar_t = float32_t; + struct SPrecomputed + { + scalar_t RcpLog2Base; + scalar_t Log2BaseRootOfStart; + scalar_t BrightSampleLumaBias; + }; // float16_t baseRootOfStart; 0 // float16_t rcpLog2Base; 1 @@ -35,10 +41,22 @@ struct SplattingParameters return unpackedLog2Parameters()[1]; } - template + template + SPrecomputed precompute() + { + const scalar_t LastCascade = scalar_t(CascadeCount - 1u); + const float32_t2 unpacked = unpackedLog2Parameters(); + SPrecomputed retval; + retval.RcpLog2Base = unpacked[1]; + retval.Log2BaseRootOfStart = log2(unpacked[0]); + retval.BrightSampleLumaBias = (retval.Log2BaseRootOfStart + LastCascade) / retval.RcpLog2Base; + return retval; + } + + template scalar_t calcLuma(NBL_CONST_REF_ARG(CascadeLayerType) col) { - return hlsl::dot(hlsl::transpose(colorspace::scRGBtoXYZ)[1], col); + return hlsl::dot(hlsl::transpose(Colorspace::ToXYZ())[1], col); } }; diff --git a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl index 84b0d44580..2b2d090fb1 100644 --- a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl @@ -17,98 +17,84 @@ namespace rwmc #define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T)(SampleType) #define NBL_CONCEPT_PARAM_0 (a,T) -#define NBL_CONCEPT_PARAM_1 (sample,SampleType) -NBL_CONCEPT_BEGIN(2) +NBL_CONCEPT_BEGIN(1) #define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -#define sample NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::scalar_t)) - ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(concepts::FloatingPointScalar, T::scalar_t)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template calcLuma(sample)), ::nbl::hlsl::is_same_v, typename T::scalar_t)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(concepts::FloatingPointScalar, typename T::scalar_t)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)( + (a.template calcLuma(::nbl::hlsl::experimental::declval())), + ::nbl::hlsl::is_same_v, + typename T::scalar_t + )) ); -#undef sample #undef a #include template NBL_BOOL_CONCEPT ResolveLumaParams = ResolveLumaParamsBase; -// declare concept -#define NBL_CONCEPT_NAME ResolveAccessorBase -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) -#define NBL_CONCEPT_TPLT_PRM_NAMES (T) -#define NBL_CONCEPT_PARAM_0 (a,T) -#define NBL_CONCEPT_PARAM_1 (uv,vector) -#define NBL_CONCEPT_PARAM_2 (layer,uint16_t) -NBL_CONCEPT_BEGIN(3) -#define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -#define uv NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 -#define layer NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 -NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_TYPE)(T::output_t)) - ((NBL_CONCEPT_REQ_TYPE)(T::output_scalar_t)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer)), ::nbl::hlsl::is_same_v, typename T::output_t)) -); -#undef layer -#undef uv -#undef a -#include - template -NBL_BOOL_CONCEPT ResolveAccessor = ResolveAccessorBase && concepts::accessors::LoadableImage; - -template -struct ResolveAccessorAdaptor +NBL_BOOL_CONCEPT ResolveAccessor = concepts::accessors::MipmappedLoadableImage; + +template) +struct SResolveAccessorAdaptor { using output_scalar_t = OutputScalar; - NBL_CONSTEXPR int32_t Components = 3; - using output_t = vector; - NBL_CONSTEXPR int32_t image_dimension = 2; + NBL_CONSTEXPR_STATIC_INLINE int32_t Components = 3; + using output_t = vector; + NBL_CONSTEXPR_STATIC_INLINE int32_t image_dimension = 2; - RWTexture2DArray cascade; + template + void get(vector uv, uint16_t layer, uint16_t level, NBL_REF_ARG(output_t) value) + { + typename AccessorType::output_t sampled; + accessor.template get(uv, layer, level, sampled); + value = sampled.xyz; + } template - output_t get(vector uv, uint16_t layer) + output_t get(vector uv, uint16_t layer, uint16_t level) { - uint32_t imgWidth, imgHeight, layers; - cascade.GetDimensions(imgWidth, imgHeight, layers); - int16_t2 cascadeImageDimension = int16_t2(imgWidth, imgHeight); + output_t value; + get(uv, layer, level, value); + return value; + } - if (any(uv < int16_t2(0, 0)) || any(uv >= cascadeImageDimension)) - return promote(0); - - return cascade.Load(int32_t3(uv, int32_t(layer))); - } -}; - -template && ResolveLumaParams) -struct Resolver + AccessorType accessor; +}; + +template && + ResolveLumaParams +) +struct SResolver { using output_t = typename CascadeAccessor::output_t; using output_scalar_t = typename vector_traits::scalar_type; - using scalar_t = typename ResolveParameters::scalar_t; - NBL_CONSTEXPR static uint16_t last_cascade = CascadeCount - 1u; + using scalar_t = typename SResolveParameters::scalar_t; + NBL_CONSTEXPR_STATIC_INLINE uint16_t last_cascade = uint16_t(CascadeCount - 1u); - struct CascadeSample + struct SCascadeSample { output_t centerValue; - float normalizedCenterLuma; - float normalizedNeighbourhoodAverageLuma; + scalar_t normalizedCenterLuma; + scalar_t normalizedNeighbourhoodAverageLuma; }; - static Resolver create(NBL_REF_ARG(ResolveParameters) resolveParameters) + static SResolver create(NBL_REF_ARG(SResolveParameters) resolveParameters) { - Resolver retval; + SResolver retval; retval.params = resolveParameters; - - return retval; + + return retval; } - output_t operator()(NBL_REF_ARG(CascadeAccessor) acc, const int16_t2 coord) - { - scalar_t reciprocalBaseI = 1.f; - CascadeSample curr = __sampleCascade(acc, coord, 0u, reciprocalBaseI); - + output_t operator()(NBL_REF_ARG(CascadeAccessor) acc, const int16_t2 coord) + { + scalar_t reciprocalBaseI = 1.f; + SCascadeSample curr = __sampleCascade(acc, coord, 0u, reciprocalBaseI); + output_t accumulation = promote(0.0f); scalar_t Emin = params.initialEmin; @@ -119,10 +105,10 @@ struct Resolver const bool notFirstCascade = i != 0; const bool notLastCascade = i != last_cascade; - CascadeSample next; + SCascadeSample next; if (notLastCascade) - { - reciprocalBaseI *= params.reciprocalBase; + { + reciprocalBaseI *= params.reciprocalBase; next = __sampleCascade(acc, coord, int16_t(i + 1), reciprocalBaseI); } @@ -168,31 +154,38 @@ struct Resolver return accumulation; } - ResolveParameters params; + SResolveParameters params; // pseudo private stuff: - CascadeSample __sampleCascade(NBL_REF_ARG(CascadeAccessor) acc, int16_t2 coord, uint16_t cascadeIndex, scalar_t reciprocalBaseI) + SCascadeSample __sampleCascade(NBL_REF_ARG(CascadeAccessor) acc, int16_t2 coord, uint16_t cascadeIndex, scalar_t reciprocalBaseI) { - output_t neighbourhood[9]; - neighbourhood[0] = acc.template get(coord + int16_t2(-1, -1), cascadeIndex); - neighbourhood[1] = acc.template get(coord + int16_t2(0, -1), cascadeIndex); - neighbourhood[2] = acc.template get(coord + int16_t2(1, -1), cascadeIndex); - neighbourhood[3] = acc.template get(coord + int16_t2(-1, 0), cascadeIndex); - neighbourhood[4] = acc.template get(coord + int16_t2(0, 0), cascadeIndex); - neighbourhood[5] = acc.template get(coord + int16_t2(1, 0), cascadeIndex); - neighbourhood[6] = acc.template get(coord + int16_t2(-1, 1), cascadeIndex); - neighbourhood[7] = acc.template get(coord + int16_t2(0, 1), cascadeIndex); - neighbourhood[8] = acc.template get(coord + int16_t2(1, 1), cascadeIndex); - - // numerical robustness - output_t excl_hood_sum = ((neighbourhood[0] + neighbourhood[1]) + (neighbourhood[2] + neighbourhood[3])) + - ((neighbourhood[5] + neighbourhood[6]) + (neighbourhood[7] + neighbourhood[8])); - - CascadeSample retval; - retval.centerValue = neighbourhood[4]; - retval.normalizedNeighbourhoodAverageLuma = retval.normalizedCenterLuma = params.template calcLuma(neighbourhood[4]) * reciprocalBaseI; - retval.normalizedNeighbourhoodAverageLuma = (params.template calcLuma(excl_hood_sum) * reciprocalBaseI + retval.normalizedNeighbourhoodAverageLuma) / 9.f; + output_t sampleValue; + scalar_t excl_hood_luma_sum = 0.f; + + acc.template get(coord + int16_t2(-1, -1), cascadeIndex, 0u, sampleValue); + excl_hood_luma_sum += params.template calcLuma(sampleValue); + acc.template get(coord + int16_t2(0, -1), cascadeIndex, 0u, sampleValue); + excl_hood_luma_sum += params.template calcLuma(sampleValue); + acc.template get(coord + int16_t2(1, -1), cascadeIndex, 0u, sampleValue); + excl_hood_luma_sum += params.template calcLuma(sampleValue); + acc.template get(coord + int16_t2(-1, 0), cascadeIndex, 0u, sampleValue); + excl_hood_luma_sum += params.template calcLuma(sampleValue); + + SCascadeSample retval; + acc.template get(coord + int16_t2(0, 0), cascadeIndex, 0u, retval.centerValue); + const scalar_t centerLuma = params.template calcLuma(retval.centerValue); + acc.template get(coord + int16_t2(1, 0), cascadeIndex, 0u, sampleValue); + excl_hood_luma_sum += params.template calcLuma(sampleValue); + acc.template get(coord + int16_t2(-1, 1), cascadeIndex, 0u, sampleValue); + excl_hood_luma_sum += params.template calcLuma(sampleValue); + acc.template get(coord + int16_t2(0, 1), cascadeIndex, 0u, sampleValue); + excl_hood_luma_sum += params.template calcLuma(sampleValue); + acc.template get(coord + int16_t2(1, 1), cascadeIndex, 0u, sampleValue); + excl_hood_luma_sum += params.template calcLuma(sampleValue); + + retval.normalizedCenterLuma = centerLuma * reciprocalBaseI; + retval.normalizedNeighbourhoodAverageLuma = (excl_hood_luma_sum + centerLuma) * reciprocalBaseI / 9.f; return retval; } }; From ea2e5251b54d989fcedf0ad2ddf8d0b2cc3c730e Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Tue, 17 Feb 2026 17:44:27 +0100 Subject: [PATCH 036/101] Fix accessor concepts for out-param get --- .../accessors/anisotropically_sampled.hlsl | 7 +++++-- .../concepts/accessors/loadable_image.hlsl | 20 ++++++++++++------- .../hlsl/concepts/accessors/mip_mapped.hlsl | 7 +++++-- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl index b6b0716143..64b919be6a 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl @@ -26,17 +26,20 @@ namespace accessors #define NBL_CONCEPT_PARAM_2 (layer,uint16_t) #define NBL_CONCEPT_PARAM_3 (dU,vector) #define NBL_CONCEPT_PARAM_4 (dV,vector) +#define NBL_CONCEPT_PARAM_5 (outVal,float32_t4) // start concept -NBL_CONCEPT_BEGIN(5) +NBL_CONCEPT_BEGIN(6) // need to be defined AFTER the cocnept begins #define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define uv NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define layer NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 #define dU NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 #define dV NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 +#define outVal NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,dU,dV)) , ::nbl::hlsl::is_same_v, float32_t4>)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,dU,dV,outVal)) , ::nbl::hlsl::is_same_v, void)) ); +#undef outVal #undef dV #undef dU #undef layer diff --git a/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl index 16fdf41c47..4a910a23bd 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl @@ -18,9 +18,9 @@ namespace accessors { // concept `LoadableImage` translates to smth like this: -//template -//concept LoadableImage = requires(U a, vector uv, uint16_t layer) { -// ::nbl::hlsl::is_same_v().template get(uv,layer)), vector>; +//template +//concept LoadableImage = requires(U a, vector uv, uint16_t layer, vector outVal) { +// ::nbl::hlsl::is_same_v().template get(uv,layer,outVal)), void>; //}; // declare concept @@ -31,15 +31,18 @@ namespace accessors #define NBL_CONCEPT_PARAM_0 (a,U) #define NBL_CONCEPT_PARAM_1 (uv,vector) #define NBL_CONCEPT_PARAM_2 (layer,uint16_t) +#define NBL_CONCEPT_PARAM_3 (outVal,vector) // start concept -NBL_CONCEPT_BEGIN(3) +NBL_CONCEPT_BEGIN(4) // need to be defined AFTER the concept begins #define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define uv NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define layer NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define outVal NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer)), ::nbl::hlsl::is_same_v, vector)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,outVal)), ::nbl::hlsl::is_same_v, void)) ); +#undef outVal #undef layer #undef uv #undef a @@ -54,16 +57,19 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_PARAM_1 (uv,vector) #define NBL_CONCEPT_PARAM_2 (layer,uint16_t) #define NBL_CONCEPT_PARAM_3 (level,uint16_t) +#define NBL_CONCEPT_PARAM_4 (outVal,vector) // start concept -NBL_CONCEPT_BEGIN(4) +NBL_CONCEPT_BEGIN(5) // need to be defined AFTER the cocnept begins #define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define uv NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define layer NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 #define level NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 +#define outVal NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,level)) , ::nbl::hlsl::is_same_v, vector)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,level,outVal)) , ::nbl::hlsl::is_same_v, void)) ); +#undef outVal #undef level #undef layer #undef uv diff --git a/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl index 81fb6e8502..be7f997925 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl @@ -25,16 +25,19 @@ namespace accessors #define NBL_CONCEPT_PARAM_1 (uv,vector) #define NBL_CONCEPT_PARAM_2 (layer,uint16_t) #define NBL_CONCEPT_PARAM_3 (level,float) +#define NBL_CONCEPT_PARAM_4 (outVal,float32_t4) // start concept -NBL_CONCEPT_BEGIN(4) +NBL_CONCEPT_BEGIN(5) // need to be defined AFTER the cocnept begins #define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define uv NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define layer NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 #define level NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 +#define outVal NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,level)) , ::nbl::hlsl::is_same_v, float32_t4>)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,level,outVal)) , ::nbl::hlsl::is_same_v, void)) ); +#undef outVal #undef level #undef layer #undef uv From 9399ad3d82718d1feb5cc2d39f85f3fec0b846e2 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Tue, 17 Feb 2026 18:10:43 +0100 Subject: [PATCH 037/101] Address latest RWMC accessor and splatting review comments --- examples_tests | 2 +- .../accessors/anisotropically_sampled.hlsl | 8 +-- .../concepts/accessors/loadable_image.hlsl | 6 +- .../hlsl/concepts/accessors/mip_mapped.hlsl | 2 +- .../builtin/hlsl/rwmc/CascadeAccumulator.hlsl | 18 +++--- .../hlsl/rwmc/SplattingParameters.hlsl | 59 ++++++++----------- include/nbl/builtin/hlsl/rwmc/resolve.hlsl | 30 ++++------ 7 files changed, 52 insertions(+), 73 deletions(-) diff --git a/examples_tests b/examples_tests index 40ba7631e3..c531404a5e 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 40ba7631e305bbc5f10dfd07c96040e87b9ca4c8 +Subproject commit c531404a5ed53bf0d46f208199520fbd814522bf diff --git a/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl index 64b919be6a..76f2c2219a 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/anisotropically_sampled.hlsl @@ -18,15 +18,15 @@ namespace accessors { // declare concept #define NBL_CONCEPT_NAME AnisotropicallySampled -#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(int32_t) -#define NBL_CONCEPT_TPLT_PRM_NAMES (U)(Dims) +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(int32_t)(int32_t) +#define NBL_CONCEPT_TPLT_PRM_NAMES (U)(Dims)(Components) // not the greatest syntax but works #define NBL_CONCEPT_PARAM_0 (a,U) #define NBL_CONCEPT_PARAM_1 (uv,vector) #define NBL_CONCEPT_PARAM_2 (layer,uint16_t) #define NBL_CONCEPT_PARAM_3 (dU,vector) #define NBL_CONCEPT_PARAM_4 (dV,vector) -#define NBL_CONCEPT_PARAM_5 (outVal,float32_t4) +#define NBL_CONCEPT_PARAM_5 (outVal,vector) // start concept NBL_CONCEPT_BEGIN(6) // need to be defined AFTER the cocnept begins @@ -37,7 +37,7 @@ NBL_CONCEPT_BEGIN(6) #define dV NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 #define outVal NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,dU,dV,outVal)) , ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(outVal,uv,layer,dU,dV)) , ::nbl::hlsl::is_same_v, void)) ); #undef outVal #undef dV diff --git a/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl index 4a910a23bd..fcc200ad95 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/loadable_image.hlsl @@ -20,7 +20,7 @@ namespace accessors // concept `LoadableImage` translates to smth like this: //template //concept LoadableImage = requires(U a, vector uv, uint16_t layer, vector outVal) { -// ::nbl::hlsl::is_same_v().template get(uv,layer,outVal)), void>; +// ::nbl::hlsl::is_same_v().template get(outVal,uv,layer)), void>; //}; // declare concept @@ -40,7 +40,7 @@ NBL_CONCEPT_BEGIN(4) #define layer NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 #define outVal NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,outVal)), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(outVal,uv,layer)), ::nbl::hlsl::is_same_v, void)) ); #undef outVal #undef layer @@ -67,7 +67,7 @@ NBL_CONCEPT_BEGIN(5) #define level NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 #define outVal NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,level,outVal)) , ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(outVal,uv,layer,level)) , ::nbl::hlsl::is_same_v, void)) ); #undef outVal #undef level diff --git a/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl b/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl index be7f997925..e8b61d4029 100644 --- a/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl +++ b/include/nbl/builtin/hlsl/concepts/accessors/mip_mapped.hlsl @@ -35,7 +35,7 @@ NBL_CONCEPT_BEGIN(5) #define level NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 #define outVal NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 NBL_CONCEPT_END( - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(uv,layer,level,outVal)) , ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(outVal,uv,layer,level)) , ::nbl::hlsl::is_same_v, void)) ); #undef outVal #undef level diff --git a/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl b/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl index 4be0fdab13..a52c9302d3 100644 --- a/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/CascadeAccumulator.hlsl @@ -28,9 +28,9 @@ struct DefaultCascades data[cascadeIx] = promote(0.0f); } - void addSampleIntoCascadeEntry(CascadeLayerType _sample, uint16_t lowerCascadeIndex, SplattingParameters::scalar_t lowerCascadeLevelWeight, SplattingParameters::scalar_t higherCascadeLevelWeight, uint32_t sampleCount) + void addSampleIntoCascadeEntry(CascadeLayerType _sample, uint16_t lowerCascadeIndex, SSplattingParameters::scalar_t lowerCascadeLevelWeight, SSplattingParameters::scalar_t higherCascadeLevelWeight, uint32_t sampleCount) { - const SplattingParameters::scalar_t reciprocalSampleCount = SplattingParameters::scalar_t(1.0f) / SplattingParameters::scalar_t(sampleCount); + const SSplattingParameters::scalar_t reciprocalSampleCount = SSplattingParameters::scalar_t(1.0f) / SSplattingParameters::scalar_t(sampleCount); sample_count_type lowerCascadeSampleCount = cascadeSampleCounter[lowerCascadeIndex]; data[lowerCascadeIndex] += (_sample * lowerCascadeLevelWeight - (sampleCount - lowerCascadeSampleCount) * data[lowerCascadeIndex]) * reciprocalSampleCount; @@ -49,7 +49,7 @@ struct DefaultCascades template struct CascadeAccumulator { - using scalar_t = typename SplattingParameters::scalar_t; + using scalar_t = typename SSplattingParameters::scalar_t; using input_sample_type = typename CascadesType::layer_type; using this_t = CascadeAccumulator; using cascades_type = CascadesType; @@ -57,16 +57,14 @@ struct CascadeAccumulator NBL_CONSTEXPR_STATIC_INLINE scalar_t LastCascade = scalar_t(CascadeCount - 1u); cascades_type accumulation; - SplattingParameters splattingParameters; - SplattingParameters::SPrecomputed splattingParametersPrecomputed; + SSplattingParameters splattingParameters; - static this_t create(NBL_CONST_REF_ARG(SplattingParameters) settings) + static this_t create(NBL_CONST_REF_ARG(SPackedSplattingParameters) settings) { this_t retval; for (uint32_t i = 0u; i < CascadeCount; ++i) retval.accumulation.clear(i); - retval.splattingParameters = settings; - retval.splattingParametersPrecomputed = settings.template precompute(); + retval.splattingParameters = settings.unpack(); return retval; } @@ -76,7 +74,7 @@ struct CascadeAccumulator { const scalar_t luma = splattingParameters.calcLuma(_sample); const scalar_t log2Luma = log2(luma); - const scalar_t cascade = log2Luma * splattingParametersPrecomputed.RcpLog2Base - splattingParametersPrecomputed.Log2BaseRootOfStart; + const scalar_t cascade = log2Luma * splattingParameters.RcpLog2Base - splattingParameters.Log2BaseRootOfStart; const scalar_t clampedCascade = clamp(cascade, scalar_t(0), LastCascade); const scalar_t clampedCascadeFloor = floor(clampedCascade); // c<=0 -> 0, c>=Count-1 -> Count-1 @@ -88,7 +86,7 @@ struct CascadeAccumulator // handle super bright sample case if (cascade > LastCascade) - lowerCascadeWeight = exp2(splattingParametersPrecomputed.BrightSampleLumaBias - log2Luma); + lowerCascadeWeight = exp2(splattingParameters.BrightSampleLumaBias - log2Luma); accumulation.addSampleIntoCascadeEntry(_sample, lowerCascadeIndex, lowerCascadeWeight, higherCascadeWeight, sampleCount); } diff --git a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl index 7cbd52f0a4..b89c4ebfbe 100644 --- a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl @@ -11,53 +11,42 @@ namespace hlsl namespace rwmc { -struct SplattingParameters +struct SSplattingParameters { using scalar_t = float32_t; - struct SPrecomputed + scalar_t RcpLog2Base; + scalar_t Log2BaseRootOfStart; + scalar_t BrightSampleLumaBias; + + template + scalar_t calcLuma(NBL_CONST_REF_ARG(CascadeLayerType) col) { - scalar_t RcpLog2Base; - scalar_t Log2BaseRootOfStart; - scalar_t BrightSampleLumaBias; - }; + return hlsl::dot(hlsl::transpose(Colorspace::ToXYZ())[1], col); + } +}; +struct SPackedSplattingParameters +{ // float16_t baseRootOfStart; 0 // float16_t rcpLog2Base; 1 // pack as Half2x16 - int32_t packedLog2; - - float32_t2 unpackedLog2Parameters() - { - return hlsl::unpackHalf2x16(packedLog2); - } - - scalar_t baseRootOfStart() - { - return unpackedLog2Parameters()[0]; - } + int32_t PackedLog2; - scalar_t rcpLog2Base() - { - return unpackedLog2Parameters()[1]; - } + // float16_t log2BaseRootOfStart; 2 + // float16_t brightSampleLumaBias; 3 + // pack as Half2x16 + int32_t PackedPrecomputed; - template - SPrecomputed precompute() + SSplattingParameters unpack() { - const scalar_t LastCascade = scalar_t(CascadeCount - 1u); - const float32_t2 unpacked = unpackedLog2Parameters(); - SPrecomputed retval; - retval.RcpLog2Base = unpacked[1]; - retval.Log2BaseRootOfStart = log2(unpacked[0]); - retval.BrightSampleLumaBias = (retval.Log2BaseRootOfStart + LastCascade) / retval.RcpLog2Base; + SSplattingParameters retval; + const float32_t2 unpackedLog2 = hlsl::unpackHalf2x16(PackedLog2); + const float32_t2 unpackedPrecomputed = hlsl::unpackHalf2x16(PackedPrecomputed); + retval.RcpLog2Base = unpackedLog2[1]; + retval.Log2BaseRootOfStart = unpackedPrecomputed[0]; + retval.BrightSampleLumaBias = unpackedPrecomputed[1]; return retval; } - - template - scalar_t calcLuma(NBL_CONST_REF_ARG(CascadeLayerType) col) - { - return hlsl::dot(hlsl::transpose(Colorspace::ToXYZ())[1], col); - } }; } diff --git a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl index 2b2d090fb1..a30bd49e74 100644 --- a/include/nbl/builtin/hlsl/rwmc/resolve.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/resolve.hlsl @@ -46,21 +46,13 @@ struct SResolveAccessorAdaptor NBL_CONSTEXPR_STATIC_INLINE int32_t image_dimension = 2; template - void get(vector uv, uint16_t layer, uint16_t level, NBL_REF_ARG(output_t) value) + void get(NBL_REF_ARG(output_t) value, vector uv, uint16_t layer, uint16_t level) { typename AccessorType::output_t sampled; - accessor.template get(uv, layer, level, sampled); + accessor.template get(sampled, uv, layer, level); value = sampled.xyz; } - template - output_t get(vector uv, uint16_t layer, uint16_t level) - { - output_t value; - get(uv, layer, level, value); - return value; - } - AccessorType accessor; }; @@ -163,25 +155,25 @@ struct SResolver output_t sampleValue; scalar_t excl_hood_luma_sum = 0.f; - acc.template get(coord + int16_t2(-1, -1), cascadeIndex, 0u, sampleValue); + acc.template get(sampleValue, coord + int16_t2(-1, -1), cascadeIndex, 0u); excl_hood_luma_sum += params.template calcLuma(sampleValue); - acc.template get(coord + int16_t2(0, -1), cascadeIndex, 0u, sampleValue); + acc.template get(sampleValue, coord + int16_t2(0, -1), cascadeIndex, 0u); excl_hood_luma_sum += params.template calcLuma(sampleValue); - acc.template get(coord + int16_t2(1, -1), cascadeIndex, 0u, sampleValue); + acc.template get(sampleValue, coord + int16_t2(1, -1), cascadeIndex, 0u); excl_hood_luma_sum += params.template calcLuma(sampleValue); - acc.template get(coord + int16_t2(-1, 0), cascadeIndex, 0u, sampleValue); + acc.template get(sampleValue, coord + int16_t2(-1, 0), cascadeIndex, 0u); excl_hood_luma_sum += params.template calcLuma(sampleValue); SCascadeSample retval; - acc.template get(coord + int16_t2(0, 0), cascadeIndex, 0u, retval.centerValue); + acc.template get(retval.centerValue, coord + int16_t2(0, 0), cascadeIndex, 0u); const scalar_t centerLuma = params.template calcLuma(retval.centerValue); - acc.template get(coord + int16_t2(1, 0), cascadeIndex, 0u, sampleValue); + acc.template get(sampleValue, coord + int16_t2(1, 0), cascadeIndex, 0u); excl_hood_luma_sum += params.template calcLuma(sampleValue); - acc.template get(coord + int16_t2(-1, 1), cascadeIndex, 0u, sampleValue); + acc.template get(sampleValue, coord + int16_t2(-1, 1), cascadeIndex, 0u); excl_hood_luma_sum += params.template calcLuma(sampleValue); - acc.template get(coord + int16_t2(0, 1), cascadeIndex, 0u, sampleValue); + acc.template get(sampleValue, coord + int16_t2(0, 1), cascadeIndex, 0u); excl_hood_luma_sum += params.template calcLuma(sampleValue); - acc.template get(coord + int16_t2(1, 1), cascadeIndex, 0u, sampleValue); + acc.template get(sampleValue, coord + int16_t2(1, 1), cascadeIndex, 0u); excl_hood_luma_sum += params.template calcLuma(sampleValue); retval.normalizedCenterLuma = centerLuma * reciprocalBaseI; From 1125f48d1af4ea3fcb0cc416b128ab3cb8f6beb7 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Tue, 17 Feb 2026 18:41:03 +0100 Subject: [PATCH 038/101] Improve packed splatting parameter naming --- examples_tests | 2 +- .../nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples_tests b/examples_tests index c531404a5e..af29d6546f 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit c531404a5ed53bf0d46f208199520fbd814522bf +Subproject commit af29d6546ff9f9e7beeea633f7e8de27fb879ba7 diff --git a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl index b89c4ebfbe..0b804a1517 100644 --- a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl @@ -30,21 +30,21 @@ struct SPackedSplattingParameters // float16_t baseRootOfStart; 0 // float16_t rcpLog2Base; 1 // pack as Half2x16 - int32_t PackedLog2; + int32_t PackedBaseRootAndRcpLog2Base; // float16_t log2BaseRootOfStart; 2 // float16_t brightSampleLumaBias; 3 // pack as Half2x16 - int32_t PackedPrecomputed; + int32_t PackedLog2BaseRootAndBrightSampleLumaBias; SSplattingParameters unpack() { SSplattingParameters retval; - const float32_t2 unpackedLog2 = hlsl::unpackHalf2x16(PackedLog2); - const float32_t2 unpackedPrecomputed = hlsl::unpackHalf2x16(PackedPrecomputed); - retval.RcpLog2Base = unpackedLog2[1]; - retval.Log2BaseRootOfStart = unpackedPrecomputed[0]; - retval.BrightSampleLumaBias = unpackedPrecomputed[1]; + const float32_t2 unpackedBaseRootAndRcpLog2Base = hlsl::unpackHalf2x16(PackedBaseRootAndRcpLog2Base); + const float32_t2 unpackedLog2BaseRootAndBrightSampleLumaBias = hlsl::unpackHalf2x16(PackedLog2BaseRootAndBrightSampleLumaBias); + retval.RcpLog2Base = unpackedBaseRootAndRcpLog2Base[1]; + retval.Log2BaseRootOfStart = unpackedLog2BaseRootAndBrightSampleLumaBias[0]; + retval.BrightSampleLumaBias = unpackedLog2BaseRootAndBrightSampleLumaBias[1]; return retval; } }; From 31011e8812b464318efc2504f3089e99dc5f5375 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 18 Feb 2026 12:18:35 +0700 Subject: [PATCH 039/101] fix depth increment, nee should return env radiance --- .../nbl/builtin/hlsl/path_tracing/concepts.hlsl | 1 + .../hlsl/path_tracing/unidirectional.hlsl | 16 +++++----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 6cc9fd5b1f..60da47d305 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -146,6 +146,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_eval_and_pdf(id, ray)), ::nbl::hlsl::is_same_v, typename T::eval_pdf_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.generate_and_quotient_and_pdf(id, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.get_environment_radiance(ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ); #undef depth #undef is_bsdf diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 28a97ea3fe..0e75895d46 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -105,8 +105,8 @@ struct Unidirectional const bool isBSDF = materialSystem.isBSDF(matID); - vector3_type eps0 = randGen(depth, _sample, 0u); - vector3_type eps1 = randGen(depth, _sample, 1u); + vector3_type eps0 = randGen(depth * 2u, _sample, 0u); + vector3_type eps1 = randGen(depth * 2u, _sample, 1u); // thresholds const scalar_type bxdfPdfThreshold = 0.0001; @@ -120,7 +120,7 @@ struct Unidirectional scalar_type rcpChoiceProb; sampling::PartitionRandVariable partitionRandVariable; partitionRandVariable.leftProb = neeProbability; - if (!partitionRandVariable(eps0.z, rcpChoiceProb) && depth < 2u) + if (!partitionRandVariable(eps0.z, rcpChoiceProb)) { uint32_t randLightID = uint32_t(float32_t(randGen.rng()) / numeric_limits::max) * nee.lightCount; typename nee_type::sample_quotient_return_type ret = nee.generate_and_quotient_and_pdf( @@ -200,14 +200,8 @@ struct Unidirectional void missProgram(NBL_REF_ARG(ray_type) ray) { vector3_type finalContribution = ray.payload.throughput; - // #ifdef USE_ENVMAP - // vec2 uv = SampleSphericalMap(ray.direction); - // finalContribution *= textureLod(envMap, uv, 0.0).rgb; - // #else - const vector3_type kConstantEnvLightRadiance = vector3_type(0.15, 0.21, 0.3); // TODO: match spectral_type - finalContribution *= kConstantEnvLightRadiance; + finalContribution *= nee.get_environment_radiance(ray); ray.payload.accumulation += finalContribution; - // #endif } // Li @@ -223,7 +217,7 @@ struct Unidirectional // bounces bool hit = true; bool rayAlive = true; - for (int d = 1; (d <= maxDepth) && hit && rayAlive; d += 2) + for (int d = 1; (d <= maxDepth) && hit && rayAlive; d++) { ray.intersectionT = numeric_limits::max; intersect_data_type intersection = intersector_type::traceRay(ray, scene); From 0ee653453f49a506e23bb24a4d849fa76f736eb2 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 18 Feb 2026 14:49:55 +0700 Subject: [PATCH 040/101] minor bug fix to depth, throughputCIE --- .../nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 0e75895d46..da6cc0dce6 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -78,13 +78,13 @@ struct Unidirectional bool closestHitProgram(uint32_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(intersect_data_type) intersectData, NBL_CONST_REF_ARG(scene_type) scene) { const vector3_type intersection = intersectData.intersection; + vector3_type throughput = ray.payload.throughput; + const vector3_type throughputCIE_Y = hlsl::normalize(colorspace::sRGBtoXYZ[1] * throughput); // TODO: this only works if spectral_type is dim 3 isotropic_interaction_type iso_interaction = intersectData.iso_interaction; anisotropic_interaction_type interaction = intersectData.aniso_interaction; - iso_interaction.luminosityContributionHint = colorspace::scRGBtoXYZ[1]; - interaction.isotropic.luminosityContributionHint = colorspace::scRGBtoXYZ[1]; - - vector3_type throughput = ray.payload.throughput; + iso_interaction.luminosityContributionHint = throughputCIE_Y; + interaction.isotropic.luminosityContributionHint = throughputCIE_Y; // emissive typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(ray.objectID); @@ -106,12 +106,11 @@ struct Unidirectional const bool isBSDF = materialSystem.isBSDF(matID); vector3_type eps0 = randGen(depth * 2u, _sample, 0u); - vector3_type eps1 = randGen(depth * 2u, _sample, 1u); + vector3_type eps1 = randGen(depth * 2u + 1u, _sample, 1u); // thresholds const scalar_type bxdfPdfThreshold = 0.0001; const scalar_type lumaContributionThreshold = getLuma(colorspace::eotf::sRGB((vector3_type)1.0 / 255.0)); // OETF smallest perceptible value - const vector3_type throughputCIE_Y = colorspace::sRGBtoXYZ[1] * throughput; // TODO: this only works if spectral_type is dim 3 const measure_type eta = bxdf.params.ior1 / bxdf.params.ior0; const scalar_type monochromeEta = hlsl::dot(throughputCIE_Y, eta) / (throughputCIE_Y.r + throughputCIE_Y.g + throughputCIE_Y.b); // TODO: imaginary eta? From b7ec514cc2bef82f4b5df2eb73be74da678632c5 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 18 Feb 2026 17:12:03 +0700 Subject: [PATCH 041/101] material system handles emissive material of lights, nee does only deferred pdf --- .../builtin/hlsl/path_tracing/concepts.hlsl | 22 +++++++++-------- .../hlsl/path_tracing/unidirectional.hlsl | 24 ++++++++++++------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 60da47d305..a17285f81d 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -120,17 +120,19 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_PARAM_1 (ray, typename T::ray_type) #define NBL_CONCEPT_PARAM_2 (id, typename T::light_id_type) #define NBL_CONCEPT_PARAM_3 (v, typename T::vector3_type) -#define NBL_CONCEPT_PARAM_4 (interaction, typename T::interaction_type) -#define NBL_CONCEPT_PARAM_5 (is_bsdf, bool) -#define NBL_CONCEPT_PARAM_6 (depth, uint32_t) -NBL_CONCEPT_BEGIN(7) +#define NBL_CONCEPT_PARAM_4 (radiance, typename T::spectral_type) +#define NBL_CONCEPT_PARAM_5 (interaction, typename T::interaction_type) +#define NBL_CONCEPT_PARAM_6 (is_bsdf, bool) +#define NBL_CONCEPT_PARAM_7 (depth, uint32_t) +NBL_CONCEPT_BEGIN(8) #define nee NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 -#define interaction NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 -#define is_bsdf NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 -#define depth NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 +#define radiance NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 +#define interaction NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 +#define is_bsdf NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 +#define depth NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) @@ -142,15 +144,15 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) ((NBL_CONCEPT_REQ_TYPE)(T::quotient_pdf_type)) ((NBL_CONCEPT_REQ_TYPE)(T::interaction_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::eval_pdf_return_type)) ((NBL_CONCEPT_REQ_TYPE)(T::sample_quotient_return_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_eval_and_pdf(id, ray)), ::nbl::hlsl::is_same_v, typename T::eval_pdf_return_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.generate_and_quotient_and_pdf(id, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_pdf(id, ray)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.generate_and_quotient_and_pdf(id, radiance, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.get_environment_radiance(ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ); #undef depth #undef is_bsdf #undef interaction +#undef radiance #undef v #undef id #undef ray diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index da6cc0dce6..39099f4499 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -88,21 +88,26 @@ struct Unidirectional // emissive typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(ray.objectID); - if (matLightID.isLight()) + const uint32_t matID = matLightID.matID; + const bool isEmissive = materialSystem.hasEmission(matID); + if (isEmissive) { - typename nee_type::eval_pdf_return_type ret = nee.deferred_eval_and_pdf(matLightID.lightID, ray); - measure_type emissive = ret.radiance * throughput; - scalar_type _pdfSq = hlsl::mix(ret.pdf, ret.pdf * ret.pdf, ret.pdf < numeric_limits::max); - emissive /= (1.0 + _pdfSq * ray.payload.otherTechniqueHeuristic); + measure_type emissive = materialSystem.getEmission(matID, interaction.isotropic); + + const uint32_t lightID = matLightID.lightID; + if (matLightID.isLight()) + { + const scalar_type pdf = nee.deferred_pdf(lightID, ray); + scalar_type pdfSq = hlsl::mix(pdf, pdf * pdf, pdf < numeric_limits::max); + emissive *= ray.foundEmissiveMIS(pdfSq); + } ray.payload.accumulation += emissive; } - if (!matLightID.isBxDF()) + if (!matLightID.isBxDF() || isEmissive) return false; - const uint32_t matID = matLightID.matID; bxdfnode_type bxdf = materialSystem.bxdfs[matID]; - const bool isBSDF = materialSystem.isBSDF(matID); vector3_type eps0 = randGen(depth * 2u, _sample, 0u); @@ -122,8 +127,9 @@ struct Unidirectional if (!partitionRandVariable(eps0.z, rcpChoiceProb)) { uint32_t randLightID = uint32_t(float32_t(randGen.rng()) / numeric_limits::max) * nee.lightCount; + measure_type lightEmission = materialSystem.getEmission(nee.lights[randLightID].emissiveMatID, interaction.isotropic); typename nee_type::sample_quotient_return_type ret = nee.generate_and_quotient_and_pdf( - randLightID, intersection, interaction, + randLightID, lightEmission, intersection, interaction, isBSDF, eps0, depth ); scalar_type t = ret.newRayMaxT; From f71610b381b86b59c541e81e3dc5a17f5292176e Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Wed, 18 Feb 2026 23:08:19 +0300 Subject: [PATCH 042/101] 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 e02ae4134de03f6bce64183619c430584bd1c68b Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 19 Feb 2026 11:15:34 +0700 Subject: [PATCH 043/101] made some vars members to assign, gaussian takes vec2 --- .../nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl | 2 +- .../nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl | 4 ++-- .../nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl b/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl index ab1da9febf..f9f5661bbd 100644 --- a/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl @@ -26,7 +26,7 @@ struct BasicRayGenerator { vector4_type tmp = NDC; GaussianFilter filter = GaussianFilter::create(2.5, 1.5); // stochastic reconstruction filter - tmp.xy += pixOffsetParam * filter.sample(randVec); + tmp.xy += pixOffsetParam * filter.sample(randVec.xy); // for depth of field we could do another stochastic point-pick tmp = nbl::hlsl::mul(invMVP, tmp); diff --git a/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl b/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl index ff19e89d82..6e27749405 100644 --- a/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl @@ -26,9 +26,9 @@ struct GaussianFilter return retval; } - vector2_type sample(const vector randVec) + vector2_type sample(const vector2_type randVec) { - vector2_type remappedRand = randVec.xy; + vector2_type remappedRand = randVec; remappedRand.x *= 1.0 - truncation; remappedRand.x += truncation; return boxMuller(remappedRand); diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 39099f4499..6baf3be7f2 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -71,7 +71,7 @@ struct Unidirectional scalar_type getLuma(NBL_CONST_REF_ARG(vector3_type) col) { - return hlsl::dot(colorspace::scRGBtoXYZ[1], col); + return hlsl::dot(spectralTypeToLumaCoeffs, col); } // TODO: will only work with isotropic surfaces, need to do aniso @@ -79,7 +79,7 @@ struct Unidirectional { const vector3_type intersection = intersectData.intersection; vector3_type throughput = ray.payload.throughput; - const vector3_type throughputCIE_Y = hlsl::normalize(colorspace::sRGBtoXYZ[1] * throughput); // TODO: this only works if spectral_type is dim 3 + const vector3_type throughputCIE_Y = hlsl::normalize(spectralTypeToLumaCoeffs * throughput); isotropic_interaction_type iso_interaction = intersectData.iso_interaction; anisotropic_interaction_type interaction = intersectData.aniso_interaction; @@ -114,8 +114,6 @@ struct Unidirectional vector3_type eps1 = randGen(depth * 2u + 1u, _sample, 1u); // thresholds - const scalar_type bxdfPdfThreshold = 0.0001; - const scalar_type lumaContributionThreshold = getLuma(colorspace::eotf::sRGB((vector3_type)1.0 / 255.0)); // OETF smallest perceptible value const measure_type eta = bxdf.params.ior1 / bxdf.params.ior0; const scalar_type monochromeEta = hlsl::dot(throughputCIE_Y, eta) / (throughputCIE_Y.r + throughputCIE_Y.g + throughputCIE_Y.b); // TODO: imaginary eta? @@ -250,6 +248,10 @@ struct Unidirectional material_system_type materialSystem; nee_type nee; scene_type scene; + + scalar_type bxdfPdfThreshold; + scalar_type lumaContributionThreshold; // OETF smallest perceptible value + measure_type spectralTypeToLumaCoeffs; }; } From b27ec2dfa92ba6b0d6b11f954d543facd8a6fe1b Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 19 Feb 2026 14:03:18 +0700 Subject: [PATCH 044/101] nee chooses the light so takes material system with emissive instead --- .../builtin/hlsl/path_tracing/concepts.hlsl | 13 +++++-- .../hlsl/path_tracing/unidirectional.hlsl | 39 ++++++++----------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index a17285f81d..5b871f062f 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -113,6 +113,11 @@ NBL_CONCEPT_END( #undef matsys #include +namespace impl +{ +struct DummyMaterialSystem {}; +} + #define NBL_CONCEPT_NAME NextEventEstimator #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) @@ -120,7 +125,7 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_PARAM_1 (ray, typename T::ray_type) #define NBL_CONCEPT_PARAM_2 (id, typename T::light_id_type) #define NBL_CONCEPT_PARAM_3 (v, typename T::vector3_type) -#define NBL_CONCEPT_PARAM_4 (radiance, typename T::spectral_type) +#define NBL_CONCEPT_PARAM_4 (matSys, impl::DummyMaterialSystem) #define NBL_CONCEPT_PARAM_5 (interaction, typename T::interaction_type) #define NBL_CONCEPT_PARAM_6 (is_bsdf, bool) #define NBL_CONCEPT_PARAM_7 (depth, uint32_t) @@ -129,7 +134,7 @@ NBL_CONCEPT_BEGIN(8) #define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 -#define radiance NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 +#define matSys NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 #define interaction NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 #define is_bsdf NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 #define depth NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 @@ -146,13 +151,13 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::interaction_type)) ((NBL_CONCEPT_REQ_TYPE)(T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_pdf(id, ray)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.generate_and_quotient_and_pdf(id, radiance, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.template generate_and_quotient_and_pdf(matSys, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.get_environment_radiance(ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ); #undef depth #undef is_bsdf #undef interaction -#undef radiance +#undef matSys #undef v #undef id #undef ray diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 6baf3be7f2..9e6635d3be 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -124,10 +124,8 @@ struct Unidirectional partitionRandVariable.leftProb = neeProbability; if (!partitionRandVariable(eps0.z, rcpChoiceProb)) { - uint32_t randLightID = uint32_t(float32_t(randGen.rng()) / numeric_limits::max) * nee.lightCount; - measure_type lightEmission = materialSystem.getEmission(nee.lights[randLightID].emissiveMatID, interaction.isotropic); - typename nee_type::sample_quotient_return_type ret = nee.generate_and_quotient_and_pdf( - randLightID, lightEmission, intersection, interaction, + typename nee_type::sample_quotient_return_type ret = nee.template generate_and_quotient_and_pdf( + materialSystem, intersection, interaction, isBSDF, eps0, depth ); scalar_type t = ret.newRayMaxT; @@ -135,31 +133,26 @@ struct Unidirectional quotient_pdf_type neeContrib_pdf = ret.quotient_pdf; // We don't allow non watertight transmitters in this renderer - bool validPath = nee_sample.getNdotL() > numeric_limits::min && nee_sample.isValid(); // but if we allowed non-watertight transmitters (single water surface), it would make sense just to apply this line by itself bxdf::fresnel::OrientedEtas orientedEta = bxdf::fresnel::OrientedEtas::create(interaction.getNdotV(), hlsl::promote(monochromeEta)); anisocache_type _cache = anisocache_type::template create(interaction, nee_sample, orientedEta); - validPath = validPath && _cache.getAbsNdotH() >= 0.0; materialSystem.bxdfs[matID].params.eta = monochromeEta; - if (neeContrib_pdf.pdf < numeric_limits::max) + if (neeContrib_pdf.pdf > scalar_type(0.0)) { - if (validPath) - { - // example only uses isotropic bxdfs - quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, nee_sample, interaction.isotropic, _cache.iso_cache); - neeContrib_pdf.quotient *= bxdf.albedo * throughput * bsdf_quotient_pdf.quotient; - const scalar_type otherGenOverChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb; - const scalar_type otherGenOverLightAndChoice = otherGenOverChoice / bsdf_quotient_pdf.pdf; - neeContrib_pdf.quotient *= otherGenOverChoice / (1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice); // balance heuristic - - ray_type nee_ray; - nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); - nee_ray.direction = nee_sample.getL().getDirection(); - nee_ray.intersectionT = t; - if (bsdf_quotient_pdf.pdf < numeric_limits::max && getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold && !intersector_type::traceRay(nee_ray, scene).foundHit) - ray.payload.accumulation += neeContrib_pdf.quotient; - } + // example only uses isotropic bxdfs + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, nee_sample, interaction.isotropic, _cache.iso_cache); + neeContrib_pdf.quotient *= bxdf.albedo * throughput * bsdf_quotient_pdf.quotient; + const scalar_type otherGenOverChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb; + const scalar_type otherGenOverLightAndChoice = otherGenOverChoice / bsdf_quotient_pdf.pdf; + neeContrib_pdf.quotient *= otherGenOverChoice / (1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice); // balance heuristic + + ray_type nee_ray; + nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); + nee_ray.direction = nee_sample.getL().getDirection(); + nee_ray.intersectionT = t; + if (bsdf_quotient_pdf.pdf < numeric_limits::max && getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold && !intersector_type::traceRay(nee_ray, scene).foundHit) + ray.payload.accumulation += neeContrib_pdf.quotient; } } From 233f9b8fe49c2183a611d4701c48d79162310bde Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 19 Feb 2026 15:50:09 +0700 Subject: [PATCH 045/101] fixes to mis usage, moved albedo into material --- .../nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 9e6635d3be..d9f9b85b9c 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -142,16 +142,15 @@ struct Unidirectional { // example only uses isotropic bxdfs quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, nee_sample, interaction.isotropic, _cache.iso_cache); - neeContrib_pdf.quotient *= bxdf.albedo * throughput * bsdf_quotient_pdf.quotient; - const scalar_type otherGenOverChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb; - const scalar_type otherGenOverLightAndChoice = otherGenOverChoice / bsdf_quotient_pdf.pdf; - neeContrib_pdf.quotient *= otherGenOverChoice / (1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice); // balance heuristic + neeContrib_pdf.quotient *= materialSystem.eval(matID, nee_sample, interaction.isotropic, _cache.iso_cache) * rcpChoiceProb; + const scalar_type otherGenOverLightAndChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb / neeContrib_pdf.pdf; + neeContrib_pdf.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic ray_type nee_ray; nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); nee_ray.direction = nee_sample.getL().getDirection(); nee_ray.intersectionT = t; - if (bsdf_quotient_pdf.pdf < numeric_limits::max && getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold && !intersector_type::traceRay(nee_ray, scene).foundHit) + if (getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold && !intersector_type::traceRay(nee_ray, scene).foundHit) ray.payload.accumulation += neeContrib_pdf.quotient; } } @@ -169,7 +168,7 @@ struct Unidirectional // example only uses isotropic bxdfs // 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.isotropic, _cache.iso_cache); - throughput *= bxdf.albedo * bsdf_quotient_pdf.quotient; + throughput *= bsdf_quotient_pdf.quotient; bxdfPdf = bsdf_quotient_pdf.pdf; bxdfSample = bsdf_sample.getL().getDirection(); } From 2e19c8591d7e6b05a1ba0a5ecec99608fc24075b Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 19 Feb 2026 16:01:07 +0700 Subject: [PATCH 046/101] nee shouldn't copy scene again --- include/nbl/builtin/hlsl/path_tracing/concepts.hlsl | 9 ++++++--- .../nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 7 +++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 5b871f062f..3468fdc6e9 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -129,7 +129,8 @@ struct DummyMaterialSystem {}; #define NBL_CONCEPT_PARAM_5 (interaction, typename T::interaction_type) #define NBL_CONCEPT_PARAM_6 (is_bsdf, bool) #define NBL_CONCEPT_PARAM_7 (depth, uint32_t) -NBL_CONCEPT_BEGIN(8) +#define NBL_CONCEPT_PARAM_8 (scene, typename T::scene_type) +NBL_CONCEPT_BEGIN(9) #define nee NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 @@ -138,6 +139,7 @@ NBL_CONCEPT_BEGIN(8) #define interaction NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 #define is_bsdf NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 #define depth NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 +#define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_8 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) @@ -150,10 +152,11 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::quotient_pdf_type)) ((NBL_CONCEPT_REQ_TYPE)(T::interaction_type)) ((NBL_CONCEPT_REQ_TYPE)(T::sample_quotient_return_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_pdf(id, ray)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.template generate_and_quotient_and_pdf(matSys, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_pdf(id, ray, scene)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.template generate_and_quotient_and_pdf(matSys, scene, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.get_environment_radiance(ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ); +#undef scene #undef depth #undef is_bsdf #undef interaction diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index d9f9b85b9c..edd01d0ab9 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -97,7 +97,7 @@ struct Unidirectional const uint32_t lightID = matLightID.lightID; if (matLightID.isLight()) { - const scalar_type pdf = nee.deferred_pdf(lightID, ray); + const scalar_type pdf = nee.deferred_pdf(lightID, ray, scene); scalar_type pdfSq = hlsl::mix(pdf, pdf * pdf, pdf < numeric_limits::max); emissive *= ray.foundEmissiveMIS(pdfSq); } @@ -122,10 +122,11 @@ struct Unidirectional scalar_type rcpChoiceProb; sampling::PartitionRandVariable partitionRandVariable; partitionRandVariable.leftProb = neeProbability; + assert(neeProbability >= 0.0 && neeProbability <= 1.0) if (!partitionRandVariable(eps0.z, rcpChoiceProb)) { typename nee_type::sample_quotient_return_type ret = nee.template generate_and_quotient_and_pdf( - materialSystem, intersection, interaction, + materialSystem, scene, intersection, interaction, isBSDF, eps0, depth ); scalar_type t = ret.newRayMaxT; @@ -207,8 +208,6 @@ struct Unidirectional ray_type ray = rayGen.generate(uvw); ray.initPayload(); - nee.scene = scene; - // bounces bool hit = true; bool rayAlive = true; From f9cd1907020a592181c5194f313e23f0b3a670cf Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 19 Feb 2026 17:03:52 +0700 Subject: [PATCH 047/101] intersector does traceShadowRay as well --- include/nbl/builtin/hlsl/path_tracing/concepts.hlsl | 7 ++++++- include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 3468fdc6e9..13de99cff4 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -52,17 +52,22 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_PARAM_0 (intersect, T) #define NBL_CONCEPT_PARAM_1 (ray, typename T::ray_type) #define NBL_CONCEPT_PARAM_2 (scene, typename T::scene_type) -NBL_CONCEPT_BEGIN(3) +#define NBL_CONCEPT_PARAM_3 (objectID, typename T::object_handle_type) +NBL_CONCEPT_BEGIN(4) #define intersect NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define objectID NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) ((NBL_CONCEPT_REQ_TYPE)(T::scene_type)) ((NBL_CONCEPT_REQ_TYPE)(T::ray_type)) ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) ((NBL_CONCEPT_REQ_TYPE)(T::intersect_data_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceRay(ray, scene)), ::nbl::hlsl::is_same_v, typename T::intersect_data_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceShadowRay(ray, scene, objectID)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ); +#undef objectID #undef scene #undef ray #undef intersect diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index edd01d0ab9..c98b231c39 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -151,8 +151,8 @@ struct Unidirectional nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); nee_ray.direction = nee_sample.getL().getDirection(); nee_ray.intersectionT = t; - if (getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold && !intersector_type::traceRay(nee_ray, scene).foundHit) - ray.payload.accumulation += neeContrib_pdf.quotient; + if (getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold) + ray.payload.accumulation += neeContrib_pdf.quotient * intersector_type::traceShadowRay(nee_ray, scene, ret.lightObjectID); } } From 740ff00deeb28a28061aec710d2e0bcd7353bbca Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 20 Feb 2026 12:06:38 +0700 Subject: [PATCH 048/101] use ray methods instead of calling members --- .../hlsl/path_tracing/unidirectional.hlsl | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index c98b231c39..235aa6894a 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -75,7 +75,7 @@ struct Unidirectional } // TODO: will only work with isotropic surfaces, need to do aniso - bool closestHitProgram(uint32_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(intersect_data_type) intersectData, NBL_CONST_REF_ARG(scene_type) scene) + bool closestHitProgram(uint32_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(intersect_data_type) intersectData) { const vector3_type intersection = intersectData.intersection; vector3_type throughput = ray.payload.throughput; @@ -101,7 +101,7 @@ struct Unidirectional scalar_type pdfSq = hlsl::mix(pdf, pdf * pdf, pdf < numeric_limits::max); emissive *= ray.foundEmissiveMIS(pdfSq); } - ray.payload.accumulation += emissive; + ray.addPayloadContribution(emissive); } if (!matLightID.isBxDF() || isEmissive) @@ -131,7 +131,7 @@ struct Unidirectional ); scalar_type t = ret.newRayMaxT; sample_type nee_sample = ret.sample_; - quotient_pdf_type neeContrib_pdf = ret.quotient_pdf; + quotient_pdf_type neeContrib = ret.quotient_pdf; // We don't allow non watertight transmitters in this renderer // but if we allowed non-watertight transmitters (single water surface), it would make sense just to apply this line by itself @@ -139,20 +139,20 @@ struct Unidirectional anisocache_type _cache = anisocache_type::template create(interaction, nee_sample, orientedEta); materialSystem.bxdfs[matID].params.eta = monochromeEta; - if (neeContrib_pdf.pdf > scalar_type(0.0)) + if (neeContrib.pdf > scalar_type(0.0)) { // example only uses isotropic bxdfs quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, nee_sample, interaction.isotropic, _cache.iso_cache); - neeContrib_pdf.quotient *= materialSystem.eval(matID, nee_sample, interaction.isotropic, _cache.iso_cache) * rcpChoiceProb; - const scalar_type otherGenOverLightAndChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb / neeContrib_pdf.pdf; - neeContrib_pdf.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic + neeContrib.quotient *= materialSystem.eval(matID, nee_sample, interaction.isotropic, _cache.iso_cache) * rcpChoiceProb; + const scalar_type otherGenOverLightAndChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb / neeContrib.pdf; + neeContrib.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic ray_type nee_ray; nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); nee_ray.direction = nee_sample.getL().getDirection(); nee_ray.intersectionT = t; - if (getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold) - ray.payload.accumulation += neeContrib_pdf.quotient * intersector_type::traceShadowRay(nee_ray, scene, ret.lightObjectID); + if (getLuma(neeContrib.quotient) > lumaContributionThreshold) + ray.addPayloadContribution(neeContrib.quotient * intersector_type::traceShadowRay(nee_ray, scene, ret.lightObjectID)); } } @@ -178,9 +178,8 @@ struct Unidirectional const float lumaThroughputThreshold = lumaContributionThreshold; if (bxdfPdf > bxdfPdfThreshold && getLuma(throughput) > lumaThroughputThreshold) { - ray.payload.throughput = throughput; scalar_type otherTechniqueHeuristic = neeProbability / bxdfPdf; // numerically stable, don't touch - ray.payload.otherTechniqueHeuristic = otherTechniqueHeuristic * otherTechniqueHeuristic; + ray.setPayloadMISWeights(throughput, otherTechniqueHeuristic * otherTechniqueHeuristic); // trace new ray vector3_type origin = intersection + bxdfSample * (1.0/*kSceneSize*/) * Tolerance::getStart(depth); @@ -197,7 +196,7 @@ struct Unidirectional { vector3_type finalContribution = ray.payload.throughput; finalContribution *= nee.get_environment_radiance(ray); - ray.payload.accumulation += finalContribution; + ray.addPayloadContribution(finalContribution); } // Li @@ -218,7 +217,7 @@ struct Unidirectional hit = intersection.foundHit; if (hit) - rayAlive = closestHitProgram(d, sampleIndex, ray, intersection, scene); + rayAlive = closestHitProgram(d, sampleIndex, ray, intersection); } if (!hit) missProgram(ray); From d96e3379768108bce1bc983025acc45551f8c6d3 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 20 Feb 2026 12:07:03 +0700 Subject: [PATCH 049/101] minor fixes to check ldoth only when transmit --- .../builtin/hlsl/bxdf/base/cook_torrance_base.hlsl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl b/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl index 1741ef789d..30639d6b7c 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/cook_torrance_base.hlsl @@ -308,9 +308,16 @@ struct SCookTorrance partitionRandVariable.leftProb = reflectance; bool transmitted = partitionRandVariable(z, rcpChoiceProb); - const scalar_type LdotH = hlsl::mix(VdotH, ieee754::copySign(hlsl::sqrt(rcpEta.value2[0]*VdotH*VdotH + scalar_type(1.0) - rcpEta.value2[0]), -VdotH), transmitted); - if (hlsl::isnan(LdotH)) - return sample_type::createInvalid(); + scalar_type LdotH; + if (transmitted) + { + scalar_type det = rcpEta.value2[0]*VdotH*VdotH + scalar_type(1.0) - rcpEta.value2[0]; + if (det < scalar_type(0.0)) + return sample_type::createInvalid(); + LdotH = ieee754::copySign(hlsl::sqrt(det), -VdotH); + } + else + LdotH = VdotH; bool valid; sample_type s = __generate_common(interaction, localH, NdotV, VdotH, LdotH, transmitted, rcpEta, valid); if (valid) From 8a614c3fcf643c30fccaf291890461c16d31361e Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 20 Feb 2026 15:02:40 +0700 Subject: [PATCH 050/101] more getters and setters for ray, result structs --- .../builtin/hlsl/path_tracing/concepts.hlsl | 6 ++-- .../hlsl/path_tracing/unidirectional.hlsl | 30 ++++++++----------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 13de99cff4..7f4cece79c 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -63,8 +63,8 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::scene_type)) ((NBL_CONCEPT_REQ_TYPE)(T::ray_type)) ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::intersect_data_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceRay(ray, scene)), ::nbl::hlsl::is_same_v, typename T::intersect_data_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::closest_hit_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceRay(ray, scene)), ::nbl::hlsl::is_same_v, typename T::closest_hit_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceShadowRay(ray, scene, objectID)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ); #undef objectID @@ -133,7 +133,7 @@ struct DummyMaterialSystem {}; #define NBL_CONCEPT_PARAM_4 (matSys, impl::DummyMaterialSystem) #define NBL_CONCEPT_PARAM_5 (interaction, typename T::interaction_type) #define NBL_CONCEPT_PARAM_6 (is_bsdf, bool) -#define NBL_CONCEPT_PARAM_7 (depth, uint32_t) +#define NBL_CONCEPT_PARAM_7 (depth, uint16_t) #define NBL_CONCEPT_PARAM_8 (scene, typename T::scene_type) NBL_CONCEPT_BEGIN(9) #define nee NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 235aa6894a..5adf629e46 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -62,7 +62,7 @@ struct Unidirectional using ray_dir_info_type = typename sample_type::ray_dir_info_type; using ray_type = typename RayGen::ray_type; using object_handle_type = typename Intersector::object_handle_type; - using intersect_data_type = typename Intersector::intersect_data_type; + using closest_hit_type = typename Intersector::closest_hit_type; using bxdfnode_type = typename MaterialSystem::bxdfnode_type; using anisotropic_interaction_type = typename MaterialSystem::anisotropic_interaction_type; using isotropic_interaction_type = typename anisotropic_interaction_type::isotropic_interaction_type; @@ -75,16 +75,10 @@ struct Unidirectional } // TODO: will only work with isotropic surfaces, need to do aniso - bool closestHitProgram(uint32_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(intersect_data_type) intersectData) + bool closestHitProgram(uint16_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(closest_hit_type) intersectData) { - const vector3_type intersection = intersectData.intersection; - vector3_type throughput = ray.payload.throughput; - const vector3_type throughputCIE_Y = hlsl::normalize(spectralTypeToLumaCoeffs * throughput); - - isotropic_interaction_type iso_interaction = intersectData.iso_interaction; - anisotropic_interaction_type interaction = intersectData.aniso_interaction; - iso_interaction.luminosityContributionHint = throughputCIE_Y; - interaction.isotropic.luminosityContributionHint = throughputCIE_Y; + anisotropic_interaction_type interaction = intersectData.getInteraction(); + isotropic_interaction_type iso_interaction = interaction.isotropic; // emissive typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(ray.objectID); @@ -113,7 +107,9 @@ struct Unidirectional vector3_type eps0 = randGen(depth * 2u, _sample, 0u); vector3_type eps1 = randGen(depth * 2u + 1u, _sample, 1u); - // thresholds + const vector3_type intersectP = intersectData.getPosition(); + vector3_type throughput = ray.getPayloadThroughput(); + const vector3_type throughputCIE_Y = hlsl::normalize(spectralTypeToLumaCoeffs * throughput); const measure_type eta = bxdf.params.ior1 / bxdf.params.ior0; const scalar_type monochromeEta = hlsl::dot(throughputCIE_Y, eta) / (throughputCIE_Y.r + throughputCIE_Y.g + throughputCIE_Y.b); // TODO: imaginary eta? @@ -126,7 +122,7 @@ struct Unidirectional if (!partitionRandVariable(eps0.z, rcpChoiceProb)) { typename nee_type::sample_quotient_return_type ret = nee.template generate_and_quotient_and_pdf( - materialSystem, scene, intersection, interaction, + materialSystem, scene, intersectP, interaction, isBSDF, eps0, depth ); scalar_type t = ret.newRayMaxT; @@ -148,7 +144,7 @@ struct Unidirectional neeContrib.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic ray_type nee_ray; - nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); + nee_ray.origin = intersectP + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); nee_ray.direction = nee_sample.getL().getDirection(); nee_ray.intersectionT = t; if (getLuma(neeContrib.quotient) > lumaContributionThreshold) @@ -182,7 +178,7 @@ struct Unidirectional ray.setPayloadMISWeights(throughput, otherTechniqueHeuristic * otherTechniqueHeuristic); // trace new ray - vector3_type origin = intersection + bxdfSample * (1.0/*kSceneSize*/) * Tolerance::getStart(depth); + vector3_type origin = intersectP + bxdfSample * (1.0/*kSceneSize*/) * Tolerance::getStart(depth); vector3_type direction = bxdfSample; ray.initData(origin, direction, interaction.getN(), isBSDF); @@ -210,12 +206,12 @@ struct Unidirectional // bounces bool hit = true; bool rayAlive = true; - for (int d = 1; (d <= maxDepth) && hit && rayAlive; d++) + for (uint16_t d = 1; (d <= maxDepth) && hit && rayAlive; d++) { ray.intersectionT = numeric_limits::max; - intersect_data_type intersection = intersector_type::traceRay(ray, scene); + closest_hit_type intersection = intersector_type::traceRay(ray, scene); - hit = intersection.foundHit; + hit = intersection.foundHit(); if (hit) rayAlive = closestHitProgram(d, sampleIndex, ray, intersection); } From 76ed2536bf36a62fce1bc455075373b68e1bb7d8 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 20 Feb 2026 16:45:52 +0700 Subject: [PATCH 051/101] made tolerance method a type alias in nee with new static method --- .../builtin/hlsl/path_tracing/concepts.hlsl | 1 + .../hlsl/path_tracing/unidirectional.hlsl | 44 ++++++------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 7f4cece79c..eef1662be9 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -157,6 +157,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::quotient_pdf_type)) ((NBL_CONCEPT_REQ_TYPE)(T::interaction_type)) ((NBL_CONCEPT_REQ_TYPE)(T::sample_quotient_return_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::tolerance_method_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_pdf(id, ray, scene)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.template generate_and_quotient_and_pdf(matSys, scene, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.get_environment_radiance(ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 5adf629e46..fdae6ba266 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -16,29 +16,6 @@ namespace hlsl namespace path_tracing { -// TODO: unsure what to do with this, awaiting refactor or turning into concept -template -struct Tolerance -{ - NBL_CONSTEXPR_STATIC_INLINE T INTERSECTION_ERROR_BOUND_LOG2 = -8.0; - - static T __common(uint32_t depth) - { - T depthRcp = 1.0 / T(depth); - return INTERSECTION_ERROR_BOUND_LOG2; - } - - static T getStart(uint32_t depth) - { - return nbl::hlsl::exp2(__common(depth)); - } - - static T getEnd(uint32_t depth) - { - return 1.0 - nbl::hlsl::exp2(__common(depth) + 1.0); - } -}; - template && concepts::RayGenerator && concepts::Intersector && concepts::MaterialSystem && @@ -68,6 +45,7 @@ struct Unidirectional using isotropic_interaction_type = typename anisotropic_interaction_type::isotropic_interaction_type; using anisocache_type = typename MaterialSystem::anisocache_type; using quotient_pdf_type = typename NextEventEstimator::quotient_pdf_type; + using tolerance_method_type = typename NextEventEstimator::tolerance_method_type; scalar_type getLuma(NBL_CONST_REF_ARG(vector3_type) col) { @@ -81,7 +59,7 @@ struct Unidirectional isotropic_interaction_type iso_interaction = interaction.isotropic; // emissive - typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(ray.objectID); + typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(intersectData.getObjectID()); const uint32_t matID = matLightID.matID; const bool isEmissive = materialSystem.hasEmission(matID); if (isEmissive) @@ -143,10 +121,12 @@ struct Unidirectional const scalar_type otherGenOverLightAndChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb / neeContrib.pdf; neeContrib.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic + const vector3_type origin = intersectP; + const vector3_type direction = nee_sample.getL().getDirection(); ray_type nee_ray; - nee_ray.origin = intersectP + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); - nee_ray.direction = nee_sample.getL().getDirection(); - nee_ray.intersectionT = t; + nee_ray.initData(origin, direction, interaction.getN(), isBSDF); + nee_ray.setT(t); + tolerance_method_type::template adjust(nee_ray, direction, depth); if (getLuma(neeContrib.quotient) > lumaContributionThreshold) ray.addPayloadContribution(neeContrib.quotient * intersector_type::traceShadowRay(nee_ray, scene, ret.lightObjectID)); } @@ -178,10 +158,12 @@ struct Unidirectional ray.setPayloadMISWeights(throughput, otherTechniqueHeuristic * otherTechniqueHeuristic); // trace new ray - vector3_type origin = intersectP + bxdfSample * (1.0/*kSceneSize*/) * Tolerance::getStart(depth); + vector3_type origin = intersectP; vector3_type direction = bxdfSample; - ray.initData(origin, direction, interaction.getN(), isBSDF); + ray.setT(1.0/*kSceneSize*/); + tolerance_method_type::template adjust(ray, direction, depth); + return true; } @@ -190,7 +172,7 @@ struct Unidirectional void missProgram(NBL_REF_ARG(ray_type) ray) { - vector3_type finalContribution = ray.payload.throughput; + vector3_type finalContribution = ray.getPayloadThroughput(); finalContribution *= nee.get_environment_radiance(ray); ray.addPayloadContribution(finalContribution); } @@ -208,7 +190,7 @@ struct Unidirectional bool rayAlive = true; for (uint16_t d = 1; (d <= maxDepth) && hit && rayAlive; d++) { - ray.intersectionT = numeric_limits::max; + ray.setT(numeric_limits::max); closest_hit_type intersection = intersector_type::traceRay(ray, scene); hit = intersection.foundHit(); From da544f81dd521441fa43457169181fd43452f117 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 23 Feb 2026 12:24:09 +0700 Subject: [PATCH 052/101] add more concepts for additional structs in pt --- .../builtin/hlsl/path_tracing/concepts.hlsl | 63 ++++++++++++++++++- .../hlsl/path_tracing/unidirectional.hlsl | 14 ++--- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index eef1662be9..bba3575407 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -46,6 +46,24 @@ NBL_CONCEPT_END( #undef raygen #include +#define NBL_CONCEPT_NAME IntersectorClosestHit +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (closest_hit, T) +NBL_CONCEPT_BEGIN(1) +#define closest_hit NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::interaction_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((closest_hit.foundHit()), ::nbl::hlsl::is_same_v, bool)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((closest_hit.getObjectID()), ::nbl::hlsl::is_same_v, typename T::object_handle_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((closest_hit.getPosition()), ::nbl::hlsl::is_same_v, typename T::vector3_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((closest_hit.getInteraction()), ::nbl::hlsl::is_same_v, typename T::interaction_type)) +); +#undef closest_hit +#include + #define NBL_CONCEPT_NAME Intersector #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) @@ -64,6 +82,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::ray_type)) ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) ((NBL_CONCEPT_REQ_TYPE)(T::closest_hit_type)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(IntersectorClosestHit, typename T::closest_hit_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceRay(ray, scene)), ::nbl::hlsl::is_same_v, typename T::closest_hit_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceShadowRay(ray, scene, objectID)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ); @@ -73,6 +92,19 @@ NBL_CONCEPT_END( #undef intersect #include +#define NBL_CONCEPT_NAME BxdfNode +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (node, T) +NBL_CONCEPT_BEGIN(1) +#define intersect NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.getNEEProb()), ::nbl::hlsl::is_same_v, typename T::scalar_type)) +); +#undef intersect +#include + #define NBL_CONCEPT_NAME MaterialSystem #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) @@ -84,7 +116,8 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_PARAM_5 (aniso_cache, typename T::anisocache_type) #define NBL_CONCEPT_PARAM_6 (iso_cache, typename T::isocache_type) #define NBL_CONCEPT_PARAM_7 (u, typename T::vector3_type) -NBL_CONCEPT_BEGIN(8) +#define NBL_CONCEPT_PARAM_8 (cie_y, typename T::measure_type) +NBL_CONCEPT_BEGIN(9) #define matsys NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define _sample NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define matid NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 @@ -93,6 +126,7 @@ NBL_CONCEPT_BEGIN(8) #define aniso_cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 #define iso_cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 +#define cie_y NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_8 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) ((NBL_CONCEPT_REQ_TYPE)(T::material_id_type)) @@ -103,11 +137,18 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::isotropic_interaction_type)) ((NBL_CONCEPT_REQ_TYPE)(T::anisocache_type)) ((NBL_CONCEPT_REQ_TYPE)(T::isocache_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::bxdfnode_type)) ((NBL_CONCEPT_REQ_TYPE)(T::create_params_t)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(BxdfNode, typename T::bxdfnode_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.eval(matid, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::measure_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.generate(matid, aniso_inter, u, aniso_cache)), ::nbl::hlsl::is_same_v, typename T::sample_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.quotient_and_pdf(matid, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::quotient_pdf_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.getBxDFNode(matid)), ::nbl::hlsl::is_same_v, typename T::bxdfnode_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.hasEmission(matid)), ::nbl::hlsl::is_same_v, bool)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.setMonochromeEta(matid, cie_y)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.getEmission(matid, iso_inter)), ::nbl::hlsl::is_same_v, typename T::measure_type)) ); +#undef cie_y #undef u #undef iso_cache #undef aniso_cache @@ -123,6 +164,25 @@ namespace impl struct DummyMaterialSystem {}; } +#define NBL_CONCEPT_NAME NextEventEstimatorSampleQuotientReturn +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (sqr, T) +NBL_CONCEPT_BEGIN(1) +#define sqr NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::sample_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::quotient_pdf_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sqr.getSample()), ::nbl::hlsl::is_same_v, typename T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sqr.getQuotientPdf()), ::nbl::hlsl::is_same_v, typename T::quotient_pdf_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sqr.getT()), ::nbl::hlsl::is_same_v, typename T::scalar_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sqr.getLightObjectID()), ::nbl::hlsl::is_same_v, typename T::object_handle_type)) +); +#undef sqr +#include + #define NBL_CONCEPT_NAME NextEventEstimator #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) @@ -158,6 +218,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::interaction_type)) ((NBL_CONCEPT_REQ_TYPE)(T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_TYPE)(T::tolerance_method_type)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(NextEventEstimatorSampleQuotientReturn, typename T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_pdf(id, ray, scene)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.template generate_and_quotient_and_pdf(matSys, scene, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.get_environment_radiance(ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index fdae6ba266..a5b1eb715b 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -79,7 +79,7 @@ struct Unidirectional if (!matLightID.isBxDF() || isEmissive) return false; - bxdfnode_type bxdf = materialSystem.bxdfs[matID]; + bxdfnode_type bxdf = materialSystem.getBxDFNode(matID); const bool isBSDF = materialSystem.isBSDF(matID); vector3_type eps0 = randGen(depth * 2u, _sample, 0u); @@ -88,8 +88,6 @@ struct Unidirectional const vector3_type intersectP = intersectData.getPosition(); vector3_type throughput = ray.getPayloadThroughput(); const vector3_type throughputCIE_Y = hlsl::normalize(spectralTypeToLumaCoeffs * throughput); - const measure_type eta = bxdf.params.ior1 / bxdf.params.ior0; - const scalar_type monochromeEta = hlsl::dot(throughputCIE_Y, eta) / (throughputCIE_Y.r + throughputCIE_Y.g + throughputCIE_Y.b); // TODO: imaginary eta? // sample lights const scalar_type neeProbability = bxdf.getNEEProb(); @@ -103,15 +101,15 @@ struct Unidirectional materialSystem, scene, intersectP, interaction, isBSDF, eps0, depth ); - scalar_type t = ret.newRayMaxT; - sample_type nee_sample = ret.sample_; - quotient_pdf_type neeContrib = ret.quotient_pdf; + scalar_type t = ret.getT(); + sample_type nee_sample = ret.getSample(); + quotient_pdf_type neeContrib = ret.getQuotientPdf(); // We don't allow non watertight transmitters in this renderer // but if we allowed non-watertight transmitters (single water surface), it would make sense just to apply this line by itself + const scalar_type monochromeEta = materialSystem.setMonochromeEta(matID, throughputCIE_Y); bxdf::fresnel::OrientedEtas orientedEta = bxdf::fresnel::OrientedEtas::create(interaction.getNdotV(), hlsl::promote(monochromeEta)); anisocache_type _cache = anisocache_type::template create(interaction, nee_sample, orientedEta); - materialSystem.bxdfs[matID].params.eta = monochromeEta; if (neeContrib.pdf > scalar_type(0.0)) { @@ -128,7 +126,7 @@ struct Unidirectional nee_ray.setT(t); tolerance_method_type::template adjust(nee_ray, direction, depth); if (getLuma(neeContrib.quotient) > lumaContributionThreshold) - ray.addPayloadContribution(neeContrib.quotient * intersector_type::traceShadowRay(nee_ray, scene, ret.lightObjectID)); + ray.addPayloadContribution(neeContrib.quotient * intersector_type::traceShadowRay(nee_ray, scene, ret.getLightObjectID())); } } From 15003cb5e8b62313e98591a61d614daa71c456b1 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 23 Feb 2026 15:18:13 +0700 Subject: [PATCH 053/101] added concept for ray type --- .../builtin/hlsl/path_tracing/concepts.hlsl | 38 +++++++++++++++++++ .../hlsl/path_tracing/unidirectional.hlsl | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index bba3575407..099d29e618 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -29,6 +29,41 @@ NBL_CONCEPT_END( #undef rand #include +#define NBL_CONCEPT_NAME Ray +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (ray, T) +#define NBL_CONCEPT_PARAM_1 (v, typename T::vector3_type) +#define NBL_CONCEPT_PARAM_2 (b, bool) +#define NBL_CONCEPT_PARAM_3 (scalar, typename T::scalar_type) +#define NBL_CONCEPT_PARAM_4 (color, typename T::spectral_type) +NBL_CONCEPT_BEGIN(5) +#define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define b NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define scalar NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 +#define color NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::spectral_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.initData(v, v, v, b)), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.initPayload()), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.foundEmissiveMIS(scalar)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.addPayloadContribution(color)), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.getPayloadAccumulatiion()), ::nbl::hlsl::is_same_v, typename T::spectral_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.setPayloadMISWeights(color, scalar)), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.setT(scalar)), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.getT()), ::nbl::hlsl::is_same_v, typename T::scalar_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.getPayloadThroughput()), ::nbl::hlsl::is_same_v, typename T::spectral_type)) +); +#undef color +#undef scalar +#undef b +#undef v +#undef ray +#include + #define NBL_CONCEPT_NAME RayGenerator #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) @@ -40,6 +75,7 @@ NBL_CONCEPT_BEGIN(2) NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) ((NBL_CONCEPT_REQ_TYPE)(T::ray_type)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(Ray, typename T::ray_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((raygen.generate(randVec)), ::nbl::hlsl::is_same_v, typename T::ray_type)) ); #undef randVec @@ -83,6 +119,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) ((NBL_CONCEPT_REQ_TYPE)(T::closest_hit_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(IntersectorClosestHit, typename T::closest_hit_type)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(Ray, typename T::ray_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceRay(ray, scene)), ::nbl::hlsl::is_same_v, typename T::closest_hit_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceShadowRay(ray, scene, objectID)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ); @@ -219,6 +256,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_TYPE)(T::tolerance_method_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(NextEventEstimatorSampleQuotientReturn, typename T::sample_quotient_return_type)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(Ray, typename T::ray_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_pdf(id, ray, scene)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.template generate_and_quotient_and_pdf(matSys, scene, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.get_environment_radiance(ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index a5b1eb715b..9b67488c56 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -199,7 +199,7 @@ struct Unidirectional missProgram(ray); const uint32_t sampleCount = sampleIndex + 1; - accumulator.addSample(sampleCount, ray.payload.accumulation); + accumulator.addSample(sampleCount, ray.getPayloadAccumulatiion()); // TODO: visualize high variance From 0a85285c3d3618029c662f47dd1a1ec2e05ddd80 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 23 Feb 2026 15:55:30 +0700 Subject: [PATCH 054/101] added a create param struct for resolve params --- .../builtin/hlsl/rwmc/ResolveParameters.hlsl | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl index 6ed6dc99ce..afd5bf0573 100644 --- a/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/ResolveParameters.hlsl @@ -15,17 +15,26 @@ struct SResolveParameters { using scalar_t = float32_t; - static SResolveParameters create(scalar_t base, uint32_t sampleCount, scalar_t minReliableLuma, scalar_t kappa) + struct SCreateParams + { + scalar_t minReliableLuma; + scalar_t kappa; + scalar_t start; + scalar_t base; + uint32_t sampleCount; + }; + + static SResolveParameters create(SCreateParams params) { SResolveParameters retval; - retval.initialEmin = minReliableLuma; - retval.reciprocalBase = 1.f / base; - const scalar_t N = scalar_t(sampleCount); + retval.initialEmin = params.minReliableLuma; + retval.reciprocalBase = 1.f / params.base; + const scalar_t N = scalar_t(params.sampleCount); retval.reciprocalN = 1.f / N; - retval.reciprocalKappa = 1.f / kappa; + retval.reciprocalKappa = 1.f / params.kappa; // if not interested in exact expected value estimation (kappa!=1.f), can usually accept a bit more variance relative to the image brightness we already have // allow up to ~ more energy in one sample to lessen bias in some cases - retval.colorReliabilityFactor = base + (1.f - base) * retval.reciprocalKappa; + retval.colorReliabilityFactor = params.base + (1.f - params.base) * retval.reciprocalKappa; retval.NOverKappa = N * retval.reciprocalKappa; return retval; From 60728b73854bb1787c3e197f06a9595dd23760d7 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 23 Feb 2026 16:21:21 +0700 Subject: [PATCH 055/101] renamed intersector traceRay to traceClosestHit --- include/nbl/builtin/hlsl/path_tracing/concepts.hlsl | 2 +- include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 099d29e618..826764edab 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -120,7 +120,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::closest_hit_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(IntersectorClosestHit, typename T::closest_hit_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(Ray, typename T::ray_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceRay(ray, scene)), ::nbl::hlsl::is_same_v, typename T::closest_hit_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceClosestHit(ray, scene)), ::nbl::hlsl::is_same_v, typename T::closest_hit_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceShadowRay(ray, scene, objectID)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ); #undef objectID diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 9b67488c56..fab13dac8d 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -189,7 +189,7 @@ struct Unidirectional for (uint16_t d = 1; (d <= maxDepth) && hit && rayAlive; d++) { ray.setT(numeric_limits::max); - closest_hit_type intersection = intersector_type::traceRay(ray, scene); + closest_hit_type intersection = intersector_type::traceClosestHit(ray, scene); hit = intersection.foundHit(); if (hit) From 8a29fa2de3c69285c712ff6394c47f9a202bd13e Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 24 Feb 2026 10:58:59 +0700 Subject: [PATCH 056/101] concept for material light id --- .../builtin/hlsl/path_tracing/concepts.hlsl | 18 ++++++++++++++++++ .../hlsl/path_tracing/unidirectional.hlsl | 6 +++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 826764edab..a8c126ff72 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -291,6 +291,23 @@ NBL_CONCEPT_END( #undef acc #include +#define NBL_CONCEPT_NAME MaterialLightID +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (id, T) +NBL_CONCEPT_BEGIN(1) +#define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::light_id_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::material_id_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((id.getLightID()), ::nbl::hlsl::is_same_v, typename T::light_id_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((id.getMaterialID()), ::nbl::hlsl::is_same_v, typename T::material_id_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((id.isLight()), ::nbl::hlsl::is_same_v, bool)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((id.isMaterial()), ::nbl::hlsl::is_same_v, bool)) +); +#undef id +#include + #define NBL_CONCEPT_NAME Scene #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) @@ -305,6 +322,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) ((NBL_CONCEPT_REQ_TYPE)(T::mat_light_id_type)) + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(MaterialLightID, typename T::mat_light_id_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getMatLightIDs(id)), ::nbl::hlsl::is_same_v, typename T::mat_light_id_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getNormal(id, intersectP)), ::nbl::hlsl::is_same_v, typename T::vector3_type)) ); diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index fab13dac8d..63f421792e 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -60,13 +60,13 @@ struct Unidirectional // emissive typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(intersectData.getObjectID()); - const uint32_t matID = matLightID.matID; + const typename material_system_type::material_id_type matID = matLightID.getMaterialID(); const bool isEmissive = materialSystem.hasEmission(matID); if (isEmissive) { measure_type emissive = materialSystem.getEmission(matID, interaction.isotropic); - const uint32_t lightID = matLightID.lightID; + const typename nee_type::light_id_type lightID = matLightID.getLightID(); if (matLightID.isLight()) { const scalar_type pdf = nee.deferred_pdf(lightID, ray, scene); @@ -76,7 +76,7 @@ struct Unidirectional ray.addPayloadContribution(emissive); } - if (!matLightID.isBxDF() || isEmissive) + if (!matLightID.isMaterial() || isEmissive) return false; bxdfnode_type bxdf = materialSystem.getBxDFNode(matID); From 9a227eba345d98d250bead3e5fae4a9e26bae9b9 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 24 Feb 2026 16:03:23 +0700 Subject: [PATCH 057/101] removed redundant param for randgen operator --- include/nbl/builtin/hlsl/path_tracing/concepts.hlsl | 11 +++++++---- .../nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 9 ++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index a8c126ff72..585565fe38 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -1,3 +1,6 @@ +// 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_PATH_TRACING_CONCEPTS_INCLUDED_ #define _NBL_BUILTIN_HLSL_PATH_TRACING_CONCEPTS_INCLUDED_ @@ -16,16 +19,16 @@ namespace concepts #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (rand, T) -#define NBL_CONCEPT_PARAM_1 (sample_, uint32_t) +#define NBL_CONCEPT_PARAM_1 (sampleIx, uint32_t) NBL_CONCEPT_BEGIN(2) #define rand NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -#define sample_ NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define sampleIx NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::rng_type)) ((NBL_CONCEPT_REQ_TYPE)(T::return_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((rand(sample_, sample_, sample_)), ::nbl::hlsl::is_same_v, typename T::return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((rand(sampleIx, sampleIx)), ::nbl::hlsl::is_same_v, typename T::return_type)) ); -#undef sample_ +#undef sampleIx #undef rand #include diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 63f421792e..338211e50a 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -1,3 +1,6 @@ +// 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_PATH_TRACING_UNIDIRECTIONAL_INCLUDED_ #define _NBL_BUILTIN_HLSL_PATH_TRACING_UNIDIRECTIONAL_INCLUDED_ @@ -82,8 +85,8 @@ struct Unidirectional bxdfnode_type bxdf = materialSystem.getBxDFNode(matID); const bool isBSDF = materialSystem.isBSDF(matID); - vector3_type eps0 = randGen(depth * 2u, _sample, 0u); - vector3_type eps1 = randGen(depth * 2u + 1u, _sample, 1u); + vector3_type eps0 = randGen(depth * 2u, _sample); + vector3_type eps1 = randGen(depth * 2u + 1u, _sample); const vector3_type intersectP = intersectData.getPosition(); vector3_type throughput = ray.getPayloadThroughput(); @@ -179,7 +182,7 @@ struct Unidirectional void sampleMeasure(uint32_t sampleIndex, uint32_t maxDepth, NBL_REF_ARG(Accumulator) accumulator) { //scalar_type meanLumaSq = 0.0; - vector3_type uvw = randGen(0u, sampleIndex, 0u); + vector3_type uvw = randGen(0u, sampleIndex); ray_type ray = rayGen.generate(uvw); ray.initPayload(); From a5061808110847a3d2ca5ecf90af471003141f78 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 24 Feb 2026 16:57:15 +0700 Subject: [PATCH 058/101] added a create method to pack all the required splatting params --- .../builtin/hlsl/rwmc/SplattingParameters.hlsl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl index 4ce4f43200..91404faf10 100644 --- a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl @@ -37,6 +37,21 @@ struct SPackedSplattingParameters // pack as Half2x16 int32_t PackedLog2BaseRootAndBrightSampleLumaBias; + static SPackedSplattingParameters create(float32_t base, float32_t start, uint32_t cascadeCount) + { + const float32_t rcpLog2Base = 1.0f / hlsl::log2(base); + const float32_t baseRootOfStart = hlsl::exp2(hlsl::log2(start) * rcpLog2Base); + const float32_t log2BaseRootOfStart = hlsl::log2(baseRootOfStart); + const float32_t brightSampleLumaBias = (log2BaseRootOfStart + float32_t(cascadeCount - 1u)) / rcpLog2Base; + float32_t2 packLogs = float32_t2(baseRootOfStart, rcpLog2Base); + float32_t2 packPrecomputed = float32_t2(log2BaseRootOfStart, brightSampleLumaBias); + + SPackedSplattingParameters retval; + retval.PackedBaseRootAndRcpLog2Base = hlsl::packHalf2x16(packLogs); + retval.PackedLog2BaseRootAndBrightSampleLumaBias = hlsl::packHalf2x16(packPrecomputed); + return retval; + } + SSplattingParameters unpack() { SSplattingParameters retval; From 4ceb3f213d13ede00babe1779ae26fce8b1baab5 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 25 Feb 2026 11:05:32 +0700 Subject: [PATCH 059/101] scene returns an interaction, instead of just normal --- include/nbl/builtin/hlsl/path_tracing/concepts.hlsl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 585565fe38..74e5afa60b 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -311,24 +311,33 @@ NBL_CONCEPT_END( #undef id #include +namespace impl +{ +struct DummyRay {}; +} + #define NBL_CONCEPT_NAME Scene #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (scene, T) #define NBL_CONCEPT_PARAM_1 (intersectP, typename T::vector3_type) #define NBL_CONCEPT_PARAM_2 (id, typename T::object_handle_type) -NBL_CONCEPT_BEGIN(3) +#define NBL_CONCEPT_PARAM_3 (ray, impl::DummyRay) +NBL_CONCEPT_BEGIN(4) #define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define intersectP NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) ((NBL_CONCEPT_REQ_TYPE)(T::mat_light_id_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::interaction_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(MaterialLightID, typename T::mat_light_id_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getMatLightIDs(id)), ::nbl::hlsl::is_same_v, typename T::mat_light_id_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getNormal(id, intersectP)), ::nbl::hlsl::is_same_v, typename T::vector3_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.template getInteraction(id, intersectP, ray)), ::nbl::hlsl::is_same_v, typename T::interaction_type)) ); +#undef ray #undef id #undef intersectP #undef scene From 917acc59131cec83cbd2d6c270d586f083844de5 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 25 Feb 2026 11:36:36 +0700 Subject: [PATCH 060/101] 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 bcd4f19b7a..4c3f02e5f2 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -59,7 +59,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 0e0b0ad70fb403169309cb43510ae49eeaa49b3c Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Wed, 25 Feb 2026 07:38:14 +0300 Subject: [PATCH 061/101] 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 062/101] 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 4ea58c84ed7ab8ec7e14d71b22c77656ba878410 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 25 Feb 2026 14:08:59 +0700 Subject: [PATCH 063/101] remove unused variable in packed splatting params --- .../hlsl/rwmc/SplattingParameters.hlsl | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl index 91404faf10..939c30815b 100644 --- a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl @@ -27,39 +27,32 @@ struct SSplattingParameters struct SPackedSplattingParameters { - // float16_t baseRootOfStart; 0 - // float16_t rcpLog2Base; 1 + // float16_t rcpLog2Base; 0 + // float16_t log2BaseRootOfStart; 1 // pack as Half2x16 - int32_t PackedBaseRootAndRcpLog2Base; - - // float16_t log2BaseRootOfStart; 2 - // float16_t brightSampleLumaBias; 3 - // pack as Half2x16 - int32_t PackedLog2BaseRootAndBrightSampleLumaBias; + int32_t PackedRcpLog2BaseAndLog2BaseRoot; + float32_t BrightSampleLumaBias; static SPackedSplattingParameters create(float32_t base, float32_t start, uint32_t cascadeCount) { const float32_t rcpLog2Base = 1.0f / hlsl::log2(base); - const float32_t baseRootOfStart = hlsl::exp2(hlsl::log2(start) * rcpLog2Base); - const float32_t log2BaseRootOfStart = hlsl::log2(baseRootOfStart); + const float32_t log2BaseRootOfStart = hlsl::log2(start) * rcpLog2Base; const float32_t brightSampleLumaBias = (log2BaseRootOfStart + float32_t(cascadeCount - 1u)) / rcpLog2Base; - float32_t2 packLogs = float32_t2(baseRootOfStart, rcpLog2Base); - float32_t2 packPrecomputed = float32_t2(log2BaseRootOfStart, brightSampleLumaBias); + float32_t2 packLogs = float32_t2(rcpLog2Base, log2BaseRootOfStart); SPackedSplattingParameters retval; - retval.PackedBaseRootAndRcpLog2Base = hlsl::packHalf2x16(packLogs); - retval.PackedLog2BaseRootAndBrightSampleLumaBias = hlsl::packHalf2x16(packPrecomputed); + retval.PackedRcpLog2BaseAndLog2BaseRoot = hlsl::packHalf2x16(packLogs); + retval.BrightSampleLumaBias = brightSampleLumaBias; return retval; } SSplattingParameters unpack() { SSplattingParameters retval; - const float32_t2 unpackedBaseRootAndRcpLog2Base = hlsl::unpackHalf2x16(PackedBaseRootAndRcpLog2Base); - const float32_t2 unpackedLog2BaseRootAndBrightSampleLumaBias = hlsl::unpackHalf2x16(PackedLog2BaseRootAndBrightSampleLumaBias); - retval.RcpLog2Base = unpackedBaseRootAndRcpLog2Base[1]; - retval.Log2BaseRootOfStart = unpackedLog2BaseRootAndBrightSampleLumaBias[0]; - retval.BrightSampleLumaBias = unpackedLog2BaseRootAndBrightSampleLumaBias[1]; + const float32_t2 unpackedRcpLog2BaseAndLog2BaseRoot = hlsl::unpackHalf2x16(PackedRcpLog2BaseAndLog2BaseRoot); + retval.RcpLog2Base = unpackedRcpLog2BaseAndLog2BaseRoot[0]; + retval.Log2BaseRootOfStart = unpackedRcpLog2BaseAndLog2BaseRoot[1]; + retval.BrightSampleLumaBias = BrightSampleLumaBias; return retval; } }; From 885e58f62e5a3c20fd1362adaddaf66ab1448de4 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 25 Feb 2026 14:32:53 +0700 Subject: [PATCH 064/101] minor changes to concept for intersector, nee; document concept symbols --- .../builtin/hlsl/path_tracing/concepts.hlsl | 8 +++---- .../hlsl/path_tracing/unidirectional.hlsl | 21 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 74e5afa60b..c0da906b5a 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -123,8 +123,8 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::closest_hit_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(IntersectorClosestHit, typename T::closest_hit_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(Ray, typename T::ray_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceClosestHit(ray, scene)), ::nbl::hlsl::is_same_v, typename T::closest_hit_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceShadowRay(ray, scene, objectID)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceClosestHit(scene, ray)), ::nbl::hlsl::is_same_v, typename T::closest_hit_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((intersect.traceShadowRay(scene, ray, objectID)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ); #undef objectID #undef scene @@ -260,8 +260,8 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::tolerance_method_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(NextEventEstimatorSampleQuotientReturn, typename T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(Ray, typename T::ray_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_pdf(id, ray, scene)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.template generate_and_quotient_and_pdf(matSys, scene, v, interaction, is_bsdf, v, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_pdf(scene, id, ray)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.template generate_and_quotient_and_pdf(scene, matSys, v/*origin*/, interaction, is_bsdf, v/*xi*/, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.get_environment_radiance(ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ); #undef scene diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 338211e50a..6f9291b0b7 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -72,7 +72,7 @@ struct Unidirectional const typename nee_type::light_id_type lightID = matLightID.getLightID(); if (matLightID.isLight()) { - const scalar_type pdf = nee.deferred_pdf(lightID, ray, scene); + const scalar_type pdf = nee.deferred_pdf(scene, lightID, ray); scalar_type pdfSq = hlsl::mix(pdf, pdf * pdf, pdf < numeric_limits::max); emissive *= ray.foundEmissiveMIS(pdfSq); } @@ -101,7 +101,7 @@ struct Unidirectional if (!partitionRandVariable(eps0.z, rcpChoiceProb)) { typename nee_type::sample_quotient_return_type ret = nee.template generate_and_quotient_and_pdf( - materialSystem, scene, intersectP, interaction, + scene, materialSystem, intersectP, interaction, isBSDF, eps0, depth ); scalar_type t = ret.getT(); @@ -129,7 +129,7 @@ struct Unidirectional nee_ray.setT(t); tolerance_method_type::template adjust(nee_ray, direction, depth); if (getLuma(neeContrib.quotient) > lumaContributionThreshold) - ray.addPayloadContribution(neeContrib.quotient * intersector_type::traceShadowRay(nee_ray, scene, ret.getLightObjectID())); + ray.addPayloadContribution(neeContrib.quotient * intersector_type::traceShadowRay(scene, nee_ray, ret.getLightObjectID())); } } @@ -187,18 +187,17 @@ struct Unidirectional ray.initPayload(); // bounces - bool hit = true; - bool rayAlive = true; - for (uint16_t d = 1; (d <= maxDepth) && hit && rayAlive; d++) + bool continuePath = true; + for (uint16_t d = 1; (d <= maxDepth) && continuePath; d++) { ray.setT(numeric_limits::max); - closest_hit_type intersection = intersector_type::traceClosestHit(ray, scene); + closest_hit_type intersection = intersector_type::traceClosestHit(scene, ray); - hit = intersection.foundHit(); - if (hit) - rayAlive = closestHitProgram(d, sampleIndex, ray, intersection); + continuePath = intersection.foundHit(); + if (continuePath) + continuePath &= closestHitProgram(d, sampleIndex, ray, intersection); } - if (!hit) + if (!continuePath) missProgram(ray); const uint32_t sampleCount = sampleIndex + 1; From 9aeb80c6d313d7859a13e63248ee89072b13faab Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 25 Feb 2026 16:42:56 +0700 Subject: [PATCH 065/101] changes to ray methods to separate interaction from init, nee takes an interaction instead of just isBSDF --- .../hlsl/path_tracing/basic_ray_gen.hlsl | 2 +- .../builtin/hlsl/path_tracing/concepts.hlsl | 31 ++++++++++--------- .../hlsl/path_tracing/unidirectional.hlsl | 22 +++++++------ 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl b/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl index f9f5661bbd..898b3b0bd9 100644 --- a/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/basic_ray_gen.hlsl @@ -31,7 +31,7 @@ struct BasicRayGenerator tmp = nbl::hlsl::mul(invMVP, tmp); ray_type ray; - ray.initData(camPos, hlsl::normalize(tmp.xyz / tmp.w - camPos), hlsl::promote(0.0), false); + ray.init(camPos, hlsl::normalize(tmp.xyz / tmp.w - camPos)); return ray; } diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index c0da906b5a..ca6aa888cb 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -32,25 +32,31 @@ NBL_CONCEPT_END( #undef rand #include +namespace impl +{ +struct DummyInteraction {}; +} + #define NBL_CONCEPT_NAME Ray #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (ray, T) #define NBL_CONCEPT_PARAM_1 (v, typename T::vector3_type) -#define NBL_CONCEPT_PARAM_2 (b, bool) +#define NBL_CONCEPT_PARAM_2 (interaction, impl::DummyInteraction) #define NBL_CONCEPT_PARAM_3 (scalar, typename T::scalar_type) #define NBL_CONCEPT_PARAM_4 (color, typename T::spectral_type) NBL_CONCEPT_BEGIN(5) #define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 -#define b NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define interaction NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 #define scalar NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 #define color NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) ((NBL_CONCEPT_REQ_TYPE)(T::spectral_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.initData(v, v, v, b)), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.init(v/*origin*/, v/*direction*/)), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.template initInteraction(interaction)), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.initPayload()), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.foundEmissiveMIS(scalar)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.addPayloadContribution(color)), ::nbl::hlsl::is_same_v, void)) @@ -62,7 +68,7 @@ NBL_CONCEPT_END( ); #undef color #undef scalar -#undef b +#undef interaction #undef v #undef ray #include @@ -183,7 +189,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.eval(matid, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::measure_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.generate(matid, aniso_inter, u, aniso_cache)), ::nbl::hlsl::is_same_v, typename T::sample_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.quotient_and_pdf(matid, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::quotient_pdf_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.getBxDFNode(matid)), ::nbl::hlsl::is_same_v, typename T::bxdfnode_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.getBxDFNode(matid, aniso_inter)), ::nbl::hlsl::is_same_v, typename T::bxdfnode_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.hasEmission(matid)), ::nbl::hlsl::is_same_v, bool)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.setMonochromeEta(matid, cie_y)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.getEmission(matid, iso_inter)), ::nbl::hlsl::is_same_v, typename T::measure_type)) @@ -232,19 +238,17 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_PARAM_3 (v, typename T::vector3_type) #define NBL_CONCEPT_PARAM_4 (matSys, impl::DummyMaterialSystem) #define NBL_CONCEPT_PARAM_5 (interaction, typename T::interaction_type) -#define NBL_CONCEPT_PARAM_6 (is_bsdf, bool) -#define NBL_CONCEPT_PARAM_7 (depth, uint16_t) -#define NBL_CONCEPT_PARAM_8 (scene, typename T::scene_type) -NBL_CONCEPT_BEGIN(9) +#define NBL_CONCEPT_PARAM_6 (depth, uint16_t) +#define NBL_CONCEPT_PARAM_7 (scene, typename T::scene_type) +NBL_CONCEPT_BEGIN(8) #define nee NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 #define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 #define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 #define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 #define matSys NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 #define interaction NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 -#define is_bsdf NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 -#define depth NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 -#define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_8 +#define depth NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 +#define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::scalar_type)) ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) @@ -261,12 +265,11 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(NextEventEstimatorSampleQuotientReturn, typename T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(Ray, typename T::ray_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_pdf(scene, id, ray)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.template generate_and_quotient_and_pdf(scene, matSys, v/*origin*/, interaction, is_bsdf, v/*xi*/, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.template generate_and_quotient_and_pdf(scene, matSys, v/*origin*/, interaction, v/*xi*/, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.get_environment_radiance(ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ); #undef scene #undef depth -#undef is_bsdf #undef interaction #undef matSys #undef v diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 6f9291b0b7..e9e74ee7b4 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -59,7 +59,7 @@ struct Unidirectional bool closestHitProgram(uint16_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(closest_hit_type) intersectData) { anisotropic_interaction_type interaction = intersectData.getInteraction(); - isotropic_interaction_type iso_interaction = interaction.isotropic; + isotropic_interaction_type iso_interaction = interaction.getIsotropic(); // emissive typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(intersectData.getObjectID()); @@ -67,7 +67,7 @@ struct Unidirectional const bool isEmissive = materialSystem.hasEmission(matID); if (isEmissive) { - measure_type emissive = materialSystem.getEmission(matID, interaction.isotropic); + measure_type emissive = materialSystem.getEmission(matID, iso_interaction); const typename nee_type::light_id_type lightID = matLightID.getLightID(); if (matLightID.isLight()) @@ -82,8 +82,8 @@ struct Unidirectional if (!matLightID.isMaterial() || isEmissive) return false; - bxdfnode_type bxdf = materialSystem.getBxDFNode(matID); - const bool isBSDF = materialSystem.isBSDF(matID); + bxdfnode_type bxdf = materialSystem.getBxDFNode(matID, interaction); + // const bool isBSDF = materialSystem.isBSDF(matID); vector3_type eps0 = randGen(depth * 2u, _sample); vector3_type eps1 = randGen(depth * 2u + 1u, _sample); @@ -102,7 +102,7 @@ struct Unidirectional { typename nee_type::sample_quotient_return_type ret = nee.template generate_and_quotient_and_pdf( scene, materialSystem, intersectP, interaction, - isBSDF, eps0, depth + eps0, depth ); scalar_type t = ret.getT(); sample_type nee_sample = ret.getSample(); @@ -117,15 +117,16 @@ struct Unidirectional if (neeContrib.pdf > scalar_type(0.0)) { // example only uses isotropic bxdfs - quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, nee_sample, interaction.isotropic, _cache.iso_cache); - neeContrib.quotient *= materialSystem.eval(matID, nee_sample, interaction.isotropic, _cache.iso_cache) * rcpChoiceProb; + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, nee_sample, iso_interaction, _cache.iso_cache); + neeContrib.quotient *= materialSystem.eval(matID, nee_sample, iso_interaction, _cache.iso_cache) * rcpChoiceProb; const scalar_type otherGenOverLightAndChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb / neeContrib.pdf; neeContrib.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic const vector3_type origin = intersectP; const vector3_type direction = nee_sample.getL().getDirection(); ray_type nee_ray; - nee_ray.initData(origin, direction, interaction.getN(), isBSDF); + nee_ray.init(origin, direction); + nee_ray.template initInteraction(interaction); nee_ray.setT(t); tolerance_method_type::template adjust(nee_ray, direction, depth); if (getLuma(neeContrib.quotient) > lumaContributionThreshold) @@ -145,7 +146,7 @@ struct Unidirectional // example only uses isotropic bxdfs // 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.isotropic, _cache.iso_cache); + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, bsdf_sample, iso_interaction, _cache.iso_cache); throughput *= bsdf_quotient_pdf.quotient; bxdfPdf = bsdf_quotient_pdf.pdf; bxdfSample = bsdf_sample.getL().getDirection(); @@ -161,7 +162,8 @@ struct Unidirectional // trace new ray vector3_type origin = intersectP; vector3_type direction = bxdfSample; - ray.initData(origin, direction, interaction.getN(), isBSDF); + ray.init(origin, direction); + ray.template initInteraction(interaction); ray.setT(1.0/*kSceneSize*/); tolerance_method_type::template adjust(ray, direction, depth); From 575f15b2ed2c5d30d51bcbdccf904f6a0130312f Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 25 Feb 2026 17:08:17 +0700 Subject: [PATCH 066/101] fix normalize throughputCIE, minor changes to method names --- .../builtin/hlsl/path_tracing/concepts.hlsl | 4 ++-- .../hlsl/path_tracing/unidirectional.hlsl | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index ca6aa888cb..54445db042 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -192,7 +192,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.getBxDFNode(matid, aniso_inter)), ::nbl::hlsl::is_same_v, typename T::bxdfnode_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.hasEmission(matid)), ::nbl::hlsl::is_same_v, bool)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.setMonochromeEta(matid, cie_y)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.getEmission(matid, iso_inter)), ::nbl::hlsl::is_same_v, typename T::measure_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.getEmission(matid, aniso_inter)), ::nbl::hlsl::is_same_v, typename T::measure_type)) ); #undef cie_y #undef u @@ -309,7 +309,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((id.getLightID()), ::nbl::hlsl::is_same_v, typename T::light_id_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((id.getMaterialID()), ::nbl::hlsl::is_same_v, typename T::material_id_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((id.isLight()), ::nbl::hlsl::is_same_v, bool)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((id.isMaterial()), ::nbl::hlsl::is_same_v, bool)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((id.canContinuePath()), ::nbl::hlsl::is_same_v, bool)) ); #undef id #include diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index e9e74ee7b4..9d9c540b86 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -64,10 +64,9 @@ struct Unidirectional // emissive typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(intersectData.getObjectID()); const typename material_system_type::material_id_type matID = matLightID.getMaterialID(); - const bool isEmissive = materialSystem.hasEmission(matID); - if (isEmissive) + if (materialSystem.hasEmission(matID)) { - measure_type emissive = materialSystem.getEmission(matID, iso_interaction); + measure_type emissive = materialSystem.getEmission(matID, interaction); const typename nee_type::light_id_type lightID = matLightID.getLightID(); if (matLightID.isLight()) @@ -79,18 +78,23 @@ struct Unidirectional ray.addPayloadContribution(emissive); } - if (!matLightID.isMaterial() || isEmissive) + if (!matLightID.canContinuePath()) return false; bxdfnode_type bxdf = materialSystem.getBxDFNode(matID, interaction); - // const bool isBSDF = materialSystem.isBSDF(matID); vector3_type eps0 = randGen(depth * 2u, _sample); vector3_type eps1 = randGen(depth * 2u + 1u, _sample); const vector3_type intersectP = intersectData.getPosition(); vector3_type throughput = ray.getPayloadThroughput(); - const vector3_type throughputCIE_Y = hlsl::normalize(spectralTypeToLumaCoeffs * throughput); + measure_type throughputCIE_Y = spectralTypeToLumaCoeffs * throughput; + { + scalar_type sum_throughput = throughputCIE_Y[0]; + NBL_UNROLL for (uint16_t i = 1; i < vector_traits::Dimension; i++) + sum_throughput += throughputCIE_Y[i]; + throughputCIE_Y /= sum_throughput; + } // sample lights const scalar_type neeProbability = bxdf.getNEEProb(); @@ -210,9 +214,6 @@ struct Unidirectional // TODO: russian roulette early exit? } - NBL_CONSTEXPR_STATIC_INLINE uint32_t MaxDepthLog2 = 4u; - NBL_CONSTEXPR_STATIC_INLINE uint32_t MaxSamplesLog2 = 10u; - randgen_type randGen; raygen_type rayGen; material_system_type materialSystem; From b5a4feb637eaff20c0d991d732b8acb91f95fb7f Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 26 Feb 2026 12:31:17 +0700 Subject: [PATCH 067/101] use nbl this macro for fresnel iridescent that uses this --- include/nbl/builtin/hlsl/bxdf/fresnel.hlsl | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl index cd0255b6a3..23e5ff327e 100644 --- a/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/fresnel.hlsl @@ -729,12 +729,7 @@ struct Iridescent(this, base_type::iork3, getEtak23(), clampedCosTheta); -#else - return impl::__iridescent_base__call_const(*this, base_type::iork3, getEtak23(), clampedCosTheta); -#endif + return impl::__iridescent_base__call_const(NBL_DEREF_THIS, base_type::iork3, getEtak23(), clampedCosTheta); } vector_type getEtak23() NBL_CONST_MEMBER_FUNC @@ -781,11 +776,7 @@ struct Iridescent(this, getEtak23(), getEtak23(), clampedCosTheta); -#else - return impl::__iridescent_base__call_const(*this, getEtak23(), getEtak23(), clampedCosTheta); -#endif + return impl::__iridescent_base__call_const(NBL_DEREF_THIS, getEtak23(), getEtak23(), clampedCosTheta); } scalar_type getRefractionOrientedEta() NBL_CONST_MEMBER_FUNC { return base_type::eta13[0]; } From 120e17904d65f8aa1c74a418162a273894ce9496 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 26 Feb 2026 12:31:58 +0700 Subject: [PATCH 068/101] added some asserts, material system should take aniso types and decide internally to use iso --- .../nbl/builtin/hlsl/path_tracing/concepts.hlsl | 4 ++-- .../builtin/hlsl/path_tracing/unidirectional.hlsl | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 54445db042..f13c7a24cd 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -186,9 +186,9 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::bxdfnode_type)) ((NBL_CONCEPT_REQ_TYPE)(T::create_params_t)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(BxdfNode, typename T::bxdfnode_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.eval(matid, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::measure_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.eval(matid, _sample, aniso_inter, aniso_cache)), ::nbl::hlsl::is_same_v, typename T::measure_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.generate(matid, aniso_inter, u, aniso_cache)), ::nbl::hlsl::is_same_v, typename T::sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.quotient_and_pdf(matid, _sample, iso_inter, iso_cache)), ::nbl::hlsl::is_same_v, typename T::quotient_pdf_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.quotient_and_pdf(matid, _sample, aniso_inter, aniso_cache)), ::nbl::hlsl::is_same_v, typename T::quotient_pdf_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.getBxDFNode(matid, aniso_inter)), ::nbl::hlsl::is_same_v, typename T::bxdfnode_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.hasEmission(matid)), ::nbl::hlsl::is_same_v, bool)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.setMonochromeEta(matid, cie_y)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 9d9c540b86..a14f136044 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -59,7 +59,6 @@ struct Unidirectional bool closestHitProgram(uint16_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(closest_hit_type) intersectData) { anisotropic_interaction_type interaction = intersectData.getInteraction(); - isotropic_interaction_type iso_interaction = interaction.getIsotropic(); // emissive typename scene_type::mat_light_id_type matLightID = scene.getMatLightIDs(intersectData.getObjectID()); @@ -72,7 +71,8 @@ struct Unidirectional if (matLightID.isLight()) { const scalar_type pdf = nee.deferred_pdf(scene, lightID, ray); - scalar_type pdfSq = hlsl::mix(pdf, pdf * pdf, pdf < numeric_limits::max); + assert(!hlsl::isinf(pdf)); + const scalar_type pdfSq = hlsl::mix(pdf, pdf * pdf, pdf < numeric_limits::max); emissive *= ray.foundEmissiveMIS(pdfSq); } ray.addPayloadContribution(emissive); @@ -101,7 +101,7 @@ struct Unidirectional scalar_type rcpChoiceProb; sampling::PartitionRandVariable partitionRandVariable; partitionRandVariable.leftProb = neeProbability; - assert(neeProbability >= 0.0 && neeProbability <= 1.0) + assert(neeProbability >= 0.0 && neeProbability <= 1.0); if (!partitionRandVariable(eps0.z, rcpChoiceProb)) { typename nee_type::sample_quotient_return_type ret = nee.template generate_and_quotient_and_pdf( @@ -120,9 +120,8 @@ struct Unidirectional if (neeContrib.pdf > scalar_type(0.0)) { - // example only uses isotropic bxdfs - quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, nee_sample, iso_interaction, _cache.iso_cache); - neeContrib.quotient *= materialSystem.eval(matID, nee_sample, iso_interaction, _cache.iso_cache) * rcpChoiceProb; + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, nee_sample, interaction, _cache); + neeContrib.quotient *= materialSystem.eval(matID, nee_sample, interaction, _cache) * rcpChoiceProb; const scalar_type otherGenOverLightAndChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb / neeContrib.pdf; neeContrib.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic @@ -148,9 +147,8 @@ struct Unidirectional if (!bsdf_sample.isValid()) return false; - // example only uses isotropic bxdfs // 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, iso_interaction, _cache.iso_cache); + 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; bxdfSample = bsdf_sample.getL().getDirection(); @@ -161,6 +159,7 @@ struct Unidirectional if (bxdfPdf > bxdfPdfThreshold && getLuma(throughput) > lumaThroughputThreshold) { scalar_type otherTechniqueHeuristic = neeProbability / bxdfPdf; // numerically stable, don't touch + assert(!hlsl::isinf(otherTechniqueHeuristic)); ray.setPayloadMISWeights(throughput, otherTechniqueHeuristic * otherTechniqueHeuristic); // trace new ray From 5e243d9a8f6f4496314cff9288769c47ef9a407e Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 26 Feb 2026 14:22:39 +0700 Subject: [PATCH 069/101] correct pdf method for mat system, added shouldDoMIS for ray payload --- include/nbl/builtin/hlsl/path_tracing/concepts.hlsl | 1 + .../nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index f13c7a24cd..d52c91a31d 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -58,6 +58,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.init(v/*origin*/, v/*direction*/)), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.template initInteraction(interaction)), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.initPayload()), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.shouldDoMIS()), ::nbl::hlsl::is_same_v, bool)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.foundEmissiveMIS(scalar)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.addPayloadContribution(color)), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.getPayloadAccumulatiion()), ::nbl::hlsl::is_same_v, typename T::spectral_type)) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index a14f136044..e0d47dd27e 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -68,12 +68,11 @@ struct Unidirectional measure_type emissive = materialSystem.getEmission(matID, interaction); const typename nee_type::light_id_type lightID = matLightID.getLightID(); - if (matLightID.isLight()) + if (ray.shouldDoMIS() && matLightID.isLight()) { const scalar_type pdf = nee.deferred_pdf(scene, lightID, ray); assert(!hlsl::isinf(pdf)); - const scalar_type pdfSq = hlsl::mix(pdf, pdf * pdf, pdf < numeric_limits::max); - emissive *= ray.foundEmissiveMIS(pdfSq); + emissive *= ray.foundEmissiveMIS(pdf * pdf); } ray.addPayloadContribution(emissive); } @@ -120,9 +119,9 @@ struct Unidirectional if (neeContrib.pdf > scalar_type(0.0)) { - quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, nee_sample, interaction, _cache); + const scalar_type bsdf_pdf = materialSystem.pdf(matID, nee_sample, interaction, _cache); neeContrib.quotient *= materialSystem.eval(matID, nee_sample, interaction, _cache) * rcpChoiceProb; - const scalar_type otherGenOverLightAndChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb / neeContrib.pdf; + const scalar_type otherGenOverLightAndChoice = bsdf_pdf * rcpChoiceProb / neeContrib.pdf; neeContrib.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic const vector3_type origin = intersectP; From 40d585c2fc43d2ddcfc56873a3e9ad6c48b9e29a Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 26 Feb 2026 16:50:20 +0700 Subject: [PATCH 070/101] added some comments regarding future use of MIS, scene takes and intersected ray and gives intersection --- .../nbl/builtin/hlsl/path_tracing/concepts.hlsl | 16 +++++++--------- .../hlsl/path_tracing/unidirectional.hlsl | 6 +++++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index d52c91a31d..50ff3d60d0 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -318,20 +318,19 @@ NBL_CONCEPT_END( namespace impl { struct DummyRay {}; +struct DummyIntersect {}; } #define NBL_CONCEPT_NAME Scene #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (scene, T) -#define NBL_CONCEPT_PARAM_1 (intersectP, typename T::vector3_type) -#define NBL_CONCEPT_PARAM_2 (id, typename T::object_handle_type) -#define NBL_CONCEPT_PARAM_3 (ray, impl::DummyRay) -NBL_CONCEPT_BEGIN(4) +#define NBL_CONCEPT_PARAM_1 (id, typename T::object_handle_type) +#define NBL_CONCEPT_PARAM_2 (ray, impl::DummyRay) +NBL_CONCEPT_BEGIN(3) #define scene NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 -#define intersectP NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 -#define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 -#define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 +#define id NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define ray NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) ((NBL_CONCEPT_REQ_TYPE)(T::object_handle_type)) @@ -339,11 +338,10 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::interaction_type)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(MaterialLightID, typename T::mat_light_id_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.getMatLightIDs(id)), ::nbl::hlsl::is_same_v, typename T::mat_light_id_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.template getInteraction(id, intersectP, ray)), ::nbl::hlsl::is_same_v, typename T::interaction_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((scene.template getIntersection(id, ray)), ::nbl::hlsl::is_same_v, impl::DummyIntersect)) ); #undef ray #undef id -#undef intersectP #undef scene #include diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index e0d47dd27e..d61ecfdbeb 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -55,7 +55,6 @@ struct Unidirectional return hlsl::dot(spectralTypeToLumaCoeffs, col); } - // TODO: will only work with isotropic surfaces, need to do aniso bool closestHitProgram(uint16_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(closest_hit_type) intersectData) { anisotropic_interaction_type interaction = intersectData.getInteraction(); @@ -117,8 +116,13 @@ struct Unidirectional bxdf::fresnel::OrientedEtas orientedEta = bxdf::fresnel::OrientedEtas::create(interaction.getNdotV(), hlsl::promote(monochromeEta)); anisocache_type _cache = anisocache_type::template create(interaction, nee_sample, orientedEta); + // While NEE or other generators are not supposed to pick up Delta lobes by accident, we need the MIS weights to add up to 1 for the non-delta lobes. + // 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)) { + // we'll need an `eval_and_mis_weight` and `quotient_and_mis_weight` const scalar_type bsdf_pdf = materialSystem.pdf(matID, nee_sample, interaction, _cache); neeContrib.quotient *= materialSystem.eval(matID, nee_sample, interaction, _cache) * rcpChoiceProb; const scalar_type otherGenOverLightAndChoice = bsdf_pdf * rcpChoiceProb / neeContrib.pdf; From 8d8b4af53fba23390e2092963b8ea590e5d37209 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 27 Feb 2026 10:48:31 +0700 Subject: [PATCH 071/101] minor name changes of methods --- .../builtin/hlsl/path_tracing/concepts.hlsl | 4 ++-- .../hlsl/path_tracing/unidirectional.hlsl | 18 ++++++------------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 50ff3d60d0..99b57d1bf6 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -56,13 +56,13 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) ((NBL_CONCEPT_REQ_TYPE)(T::spectral_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.init(v/*origin*/, v/*direction*/)), ::nbl::hlsl::is_same_v, void)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.template initInteraction(interaction)), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.template setInteraction(interaction)), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.initPayload()), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.shouldDoMIS()), ::nbl::hlsl::is_same_v, bool)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.foundEmissiveMIS(scalar)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.addPayloadContribution(color)), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.getPayloadAccumulatiion()), ::nbl::hlsl::is_same_v, typename T::spectral_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.setPayloadMISWeights(color, scalar)), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.updateThroughputAndMISWeights(color, scalar)), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.setT(scalar)), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.getT()), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.getPayloadThroughput()), ::nbl::hlsl::is_same_v, typename T::spectral_type)) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index d61ecfdbeb..037a1c9b01 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -85,14 +85,7 @@ struct Unidirectional vector3_type eps1 = randGen(depth * 2u + 1u, _sample); const vector3_type intersectP = intersectData.getPosition(); - vector3_type throughput = ray.getPayloadThroughput(); - measure_type throughputCIE_Y = spectralTypeToLumaCoeffs * throughput; - { - scalar_type sum_throughput = throughputCIE_Y[0]; - NBL_UNROLL for (uint16_t i = 1; i < vector_traits::Dimension; i++) - sum_throughput += throughputCIE_Y[i]; - throughputCIE_Y /= sum_throughput; - } + measure_type throughputCIE_Y = interaction.getLuminosityContributionHint(); // sample lights const scalar_type neeProbability = bxdf.getNEEProb(); @@ -132,7 +125,7 @@ struct Unidirectional const vector3_type direction = nee_sample.getL().getDirection(); ray_type nee_ray; nee_ray.init(origin, direction); - nee_ray.template initInteraction(interaction); + nee_ray.template setInteraction(interaction); nee_ray.setT(t); tolerance_method_type::template adjust(nee_ray, direction, depth); if (getLuma(neeContrib.quotient) > lumaContributionThreshold) @@ -143,6 +136,7 @@ struct Unidirectional // sample BSDF scalar_type bxdfPdf; vector3_type bxdfSample; + vector3_type throughput = ray.getPayloadThroughput(); { anisocache_type _cache; sample_type bsdf_sample = materialSystem.generate(matID, interaction, eps1, _cache); @@ -163,13 +157,13 @@ struct Unidirectional { scalar_type otherTechniqueHeuristic = neeProbability / bxdfPdf; // numerically stable, don't touch assert(!hlsl::isinf(otherTechniqueHeuristic)); - ray.setPayloadMISWeights(throughput, otherTechniqueHeuristic * otherTechniqueHeuristic); + ray.updateThroughputAndMISWeights(throughput, otherTechniqueHeuristic * otherTechniqueHeuristic); // trace new ray vector3_type origin = intersectP; vector3_type direction = bxdfSample; ray.init(origin, direction); - ray.template initInteraction(interaction); + ray.template setInteraction(interaction); ray.setT(1.0/*kSceneSize*/); tolerance_method_type::template adjust(ray, direction, depth); @@ -203,7 +197,7 @@ struct Unidirectional continuePath = intersection.foundHit(); if (continuePath) - continuePath &= closestHitProgram(d, sampleIndex, ray, intersection); + continuePath = closestHitProgram(d, sampleIndex, ray, intersection); } if (!continuePath) missProgram(ray); From 82ee23cfa6f9ef8661a09af5ba7f8c1e186f8e90 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 27 Feb 2026 11:50:30 +0700 Subject: [PATCH 072/101] offset ray origin by geometric normal, not ray dir --- include/nbl/builtin/hlsl/path_tracing/concepts.hlsl | 1 + include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 99b57d1bf6..a4fa54e4b8 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -106,6 +106,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((closest_hit.getObjectID()), ::nbl::hlsl::is_same_v, typename T::object_handle_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((closest_hit.getPosition()), ::nbl::hlsl::is_same_v, typename T::vector3_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((closest_hit.getInteraction()), ::nbl::hlsl::is_same_v, typename T::interaction_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((closest_hit.getGeometricNormal()), ::nbl::hlsl::is_same_v, typename T::vector3_type)) ); #undef closest_hit #include diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 037a1c9b01..beb229e8d9 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -127,7 +127,7 @@ struct Unidirectional nee_ray.init(origin, direction); nee_ray.template setInteraction(interaction); nee_ray.setT(t); - tolerance_method_type::template adjust(nee_ray, direction, depth); + 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())); } @@ -164,8 +164,7 @@ struct Unidirectional vector3_type direction = bxdfSample; ray.init(origin, direction); ray.template setInteraction(interaction); - ray.setT(1.0/*kSceneSize*/); - tolerance_method_type::template adjust(ray, direction, depth); + tolerance_method_type::template adjust(ray, intersectData.getGeometricNormal(), depth); return true; } From f6987fb52f124a66d911b220d9920fd71dbb1333 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 27 Feb 2026 14:46:01 +0700 Subject: [PATCH 073/101] path tracer doesn't store a ray gen, instead takes a generated ray for sampleMeasure --- .../hlsl/path_tracing/unidirectional.hlsl | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index beb229e8d9..e3f3e77a12 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -19,16 +19,15 @@ namespace hlsl namespace path_tracing { -template && concepts::RayGenerator && +template && concepts::Ray && concepts::Intersector && concepts::MaterialSystem && concepts::NextEventEstimator && concepts::Accumulator && concepts::Scene) struct Unidirectional { - using this_t = Unidirectional; + using this_t = Unidirectional; using randgen_type = RandGen; - using raygen_type = RayGen; using intersector_type = Intersector; using material_system_type = MaterialSystem; using nee_type = NextEventEstimator; @@ -40,7 +39,7 @@ struct Unidirectional using measure_type = typename MaterialSystem::measure_type; using sample_type = typename NextEventEstimator::sample_type; using ray_dir_info_type = typename sample_type::ray_dir_info_type; - using ray_type = typename RayGen::ray_type; + using ray_type = Ray; using object_handle_type = typename Intersector::object_handle_type; using closest_hit_type = typename Intersector::closest_hit_type; using bxdfnode_type = typename MaterialSystem::bxdfnode_type; @@ -180,14 +179,10 @@ struct Unidirectional } // Li - void sampleMeasure(uint32_t sampleIndex, uint32_t maxDepth, NBL_REF_ARG(Accumulator) accumulator) + void sampleMeasure(NBL_REF_ARG(ray_type) ray, uint32_t sampleIndex, uint32_t maxDepth, NBL_REF_ARG(Accumulator) accumulator) { - //scalar_type meanLumaSq = 0.0; - vector3_type uvw = randGen(0u, sampleIndex); - ray_type ray = rayGen.generate(uvw); - ray.initPayload(); - // bounces + // note do 1-based indexing because we expect first dimension was consumed to generate the ray bool continuePath = true; for (uint16_t d = 1; (d <= maxDepth) && continuePath; d++) { @@ -210,7 +205,6 @@ struct Unidirectional } randgen_type randGen; - raygen_type rayGen; material_system_type materialSystem; nee_type nee; scene_type scene; From 5922b3888eac5cd73e2648b9763bcf9817ce0437 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 27 Feb 2026 17:07:08 +0700 Subject: [PATCH 074/101] env light id always 0, handle miss program like sampling env light --- include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index e3f3e77a12..d72eb6c93c 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -173,8 +173,11 @@ struct Unidirectional void missProgram(NBL_REF_ARG(ray_type) ray) { - vector3_type finalContribution = ray.getPayloadThroughput(); - finalContribution *= nee.get_environment_radiance(ray); + vector3_type finalContribution = nee.get_environment_radiance(ray); + typename nee_type::light_id_type env_light_id; + env_light_id.id = 0u; + const scalar_type pdf = nee.deferred_pdf(scene, env_light_id, ray); + finalContribution *= ray.foundEmissiveMIS(pdf * pdf); ray.addPayloadContribution(finalContribution); } From f1493eda9386c57282b5debe22a81a5e2674b013 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 20 Jan 2026 15:56:23 +0700 Subject: [PATCH 075/101] 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 076/101] 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 077/101] 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 078/101] 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 079/101] 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 080/101] 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 081/101] 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 082/101] 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 083/101] 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 084/101] 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 085/101] 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 086/101] 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 087/101] 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 088/101] 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 089/101] 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 090/101] 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 091/101] 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 9c18828d925133850a313cb535ca62dc07b4b08b Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 5 Mar 2026 15:21:23 +0700 Subject: [PATCH 092/101] don't handle microfacet cache in path tracer, let mat sys do it; also eval and pdf don't need to take in cache --- .../nbl/builtin/hlsl/path_tracing/concepts.hlsl | 17 ++++++++--------- .../hlsl/path_tracing/unidirectional.hlsl | 17 ++++++----------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index a4fa54e4b8..dc1153c577 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -161,7 +161,7 @@ NBL_CONCEPT_END( #define NBL_CONCEPT_PARAM_2 (matid, typename T::material_id_type) #define NBL_CONCEPT_PARAM_3 (aniso_inter, typename T::anisotropic_interaction_type) #define NBL_CONCEPT_PARAM_4 (iso_inter, typename T::isotropic_interaction_type) -#define NBL_CONCEPT_PARAM_5 (aniso_cache, typename T::anisocache_type) +#define NBL_CONCEPT_PARAM_5 (cache_, typename T::cache_type) #define NBL_CONCEPT_PARAM_6 (iso_cache, typename T::isocache_type) #define NBL_CONCEPT_PARAM_7 (u, typename T::vector3_type) #define NBL_CONCEPT_PARAM_8 (cie_y, typename T::measure_type) @@ -171,7 +171,7 @@ NBL_CONCEPT_BEGIN(9) #define matid NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 #define aniso_inter NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 #define iso_inter NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4 -#define aniso_cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 +#define cache_ NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5 #define iso_cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6 #define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_7 #define cie_y NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_8 @@ -182,15 +182,14 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::quotient_pdf_type)) ((NBL_CONCEPT_REQ_TYPE)(T::measure_type)) ((NBL_CONCEPT_REQ_TYPE)(T::anisotropic_interaction_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::isotropic_interaction_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::anisocache_type)) - ((NBL_CONCEPT_REQ_TYPE)(T::isocache_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::cache_type)) ((NBL_CONCEPT_REQ_TYPE)(T::bxdfnode_type)) ((NBL_CONCEPT_REQ_TYPE)(T::create_params_t)) ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(BxdfNode, typename T::bxdfnode_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.eval(matid, _sample, aniso_inter, aniso_cache)), ::nbl::hlsl::is_same_v, typename T::measure_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.generate(matid, aniso_inter, u, aniso_cache)), ::nbl::hlsl::is_same_v, typename T::sample_type)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.quotient_and_pdf(matid, _sample, aniso_inter, aniso_cache)), ::nbl::hlsl::is_same_v, typename T::quotient_pdf_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.eval(matid, _sample, aniso_inter)), ::nbl::hlsl::is_same_v, typename T::measure_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.generate(matid, aniso_inter, u, cache_)), ::nbl::hlsl::is_same_v, typename T::sample_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.pdf(matid, _sample, aniso_inter)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.quotient_and_pdf(matid, _sample, aniso_inter, cache_)), ::nbl::hlsl::is_same_v, typename T::quotient_pdf_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.getBxDFNode(matid, aniso_inter)), ::nbl::hlsl::is_same_v, typename T::bxdfnode_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.hasEmission(matid)), ::nbl::hlsl::is_same_v, bool)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((matsys.setMonochromeEta(matid, cie_y)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) @@ -199,7 +198,7 @@ NBL_CONCEPT_END( #undef cie_y #undef u #undef iso_cache -#undef aniso_cache +#undef cache_ #undef iso_inter #undef aniso_inter #undef matid diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index d72eb6c93c..933952f689 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -44,8 +44,7 @@ struct Unidirectional using closest_hit_type = typename Intersector::closest_hit_type; using bxdfnode_type = typename MaterialSystem::bxdfnode_type; using anisotropic_interaction_type = typename MaterialSystem::anisotropic_interaction_type; - using isotropic_interaction_type = typename anisotropic_interaction_type::isotropic_interaction_type; - using anisocache_type = typename MaterialSystem::anisocache_type; + using cache_type = typename MaterialSystem::cache_type; using quotient_pdf_type = typename NextEventEstimator::quotient_pdf_type; using tolerance_method_type = typename NextEventEstimator::tolerance_method_type; @@ -102,11 +101,7 @@ struct Unidirectional sample_type nee_sample = ret.getSample(); quotient_pdf_type neeContrib = ret.getQuotientPdf(); - // We don't allow non watertight transmitters in this renderer - // but if we allowed non-watertight transmitters (single water surface), it would make sense just to apply this line by itself - const scalar_type monochromeEta = materialSystem.setMonochromeEta(matID, throughputCIE_Y); - bxdf::fresnel::OrientedEtas orientedEta = bxdf::fresnel::OrientedEtas::create(interaction.getNdotV(), hlsl::promote(monochromeEta)); - anisocache_type _cache = anisocache_type::template create(interaction, nee_sample, orientedEta); + // We don't allow non watertight transmitters in this renderer, one cannot reach a light from the backface (optimization) // While NEE or other generators are not supposed to pick up Delta lobes by accident, we need the MIS weights to add up to 1 for the non-delta lobes. // So we need to weigh the Delta lobes as if the MIS weight is always 1, but other areas regularly. @@ -114,9 +109,9 @@ struct Unidirectional // 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)) { - // we'll need an `eval_and_mis_weight` and `quotient_and_mis_weight` - const scalar_type bsdf_pdf = materialSystem.pdf(matID, nee_sample, interaction, _cache); - neeContrib.quotient *= materialSystem.eval(matID, nee_sample, interaction, _cache) * rcpChoiceProb; + // 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; const scalar_type otherGenOverLightAndChoice = bsdf_pdf * rcpChoiceProb / neeContrib.pdf; neeContrib.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic @@ -137,7 +132,7 @@ struct Unidirectional vector3_type bxdfSample; vector3_type throughput = ray.getPayloadThroughput(); { - anisocache_type _cache; + cache_type _cache; sample_type bsdf_sample = materialSystem.generate(matID, interaction, eps1, _cache); if (!bsdf_sample.isValid()) From 960704dbbd9fd22988d0beea671b99fa74c4ca67 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 5 Mar 2026 16:52:01 +0700 Subject: [PATCH 093/101] luminosity coeff should be spectral_type --- include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 933952f689..a6bde19a7d 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -37,6 +37,7 @@ struct Unidirectional using vector3_type = vector; using monochrome_type = vector; using measure_type = typename MaterialSystem::measure_type; + using spectral_type = typename NextEventEstimator::spectral_type; using sample_type = typename NextEventEstimator::sample_type; using ray_dir_info_type = typename sample_type::ray_dir_info_type; using ray_type = Ray; @@ -209,7 +210,7 @@ struct Unidirectional scalar_type bxdfPdfThreshold; scalar_type lumaContributionThreshold; // OETF smallest perceptible value - measure_type spectralTypeToLumaCoeffs; + spectral_type spectralTypeToLumaCoeffs; }; } From c064b29b141acd077d27306ed7ea1f8b4be613b7 Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Thu, 5 Mar 2026 17:40:29 +0300 Subject: [PATCH 094/101] `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 4d186db76f2a52aac534854c96511db132d95871 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 6 Mar 2026 11:48:45 +0700 Subject: [PATCH 095/101] nee method to get env light id, added minor checks for pdf=0 and stuff --- .../nbl/builtin/hlsl/path_tracing/concepts.hlsl | 11 ++++------- .../hlsl/path_tracing/unidirectional.hlsl | 17 +++++++---------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index dc1153c577..97ed9d44a6 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -5,6 +5,7 @@ #define _NBL_BUILTIN_HLSL_PATH_TRACING_CONCEPTS_INCLUDED_ #include +#include namespace nbl { @@ -32,17 +33,12 @@ NBL_CONCEPT_END( #undef rand #include -namespace impl -{ -struct DummyInteraction {}; -} - #define NBL_CONCEPT_NAME Ray #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) #define NBL_CONCEPT_TPLT_PRM_NAMES (T) #define NBL_CONCEPT_PARAM_0 (ray, T) #define NBL_CONCEPT_PARAM_1 (v, typename T::vector3_type) -#define NBL_CONCEPT_PARAM_2 (interaction, impl::DummyInteraction) +#define NBL_CONCEPT_PARAM_2 (interaction, bxdf::surface_interactions::SIsotropic, typename T::spectral_type>) #define NBL_CONCEPT_PARAM_3 (scalar, typename T::scalar_type) #define NBL_CONCEPT_PARAM_4 (color, typename T::spectral_type) NBL_CONCEPT_BEGIN(5) @@ -56,7 +52,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE)(T::vector3_type)) ((NBL_CONCEPT_REQ_TYPE)(T::spectral_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.init(v/*origin*/, v/*direction*/)), ::nbl::hlsl::is_same_v, void)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.template setInteraction(interaction)), ::nbl::hlsl::is_same_v, void)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.template setInteraction, typename T::spectral_type> >(interaction)), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.initPayload()), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.shouldDoMIS()), ::nbl::hlsl::is_same_v, bool)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.foundEmissiveMIS(scalar)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) @@ -267,6 +263,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(Ray, typename T::ray_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.deferred_pdf(scene, id, ray)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.template generate_and_quotient_and_pdf(scene, matSys, v/*origin*/, interaction, v/*xi*/, depth)), ::nbl::hlsl::is_same_v, typename T::sample_quotient_return_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.get_env_light_id()), ::nbl::hlsl::is_same_v, typename T::light_id_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((nee.get_environment_radiance(ray)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ); #undef scene diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index a6bde19a7d..b9d51c6c41 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -84,7 +84,6 @@ struct Unidirectional vector3_type eps1 = randGen(depth * 2u + 1u, _sample); const vector3_type intersectP = intersectData.getPosition(); - measure_type throughputCIE_Y = interaction.getLuminosityContributionHint(); // sample lights const scalar_type neeProbability = bxdf.getNEEProb(); @@ -102,8 +101,6 @@ struct Unidirectional sample_type nee_sample = ret.getSample(); quotient_pdf_type neeContrib = ret.getQuotientPdf(); - // We don't allow non watertight transmitters in this renderer, one cannot reach a light from the backface (optimization) - // While NEE or other generators are not supposed to pick up Delta lobes by accident, we need the MIS weights to add up to 1 for the non-delta lobes. // 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. @@ -113,8 +110,11 @@ struct Unidirectional // 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; - const scalar_type otherGenOverLightAndChoice = bsdf_pdf * rcpChoiceProb / neeContrib.pdf; - neeContrib.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic + if (!hlsl::isinf(neeContrib.pdf)) + { + const scalar_type otherGenOverLightAndChoice = bsdf_pdf * rcpChoiceProb / neeContrib.pdf; + neeContrib.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic + } const vector3_type origin = intersectP; const vector3_type direction = nee_sample.getL().getDirection(); @@ -169,9 +169,8 @@ struct Unidirectional void missProgram(NBL_REF_ARG(ray_type) ray) { - vector3_type finalContribution = nee.get_environment_radiance(ray); - typename nee_type::light_id_type env_light_id; - env_light_id.id = 0u; + measure_type finalContribution = nee.get_environment_radiance(ray); + typename nee_type::light_id_type env_light_id = nee.get_env_light_id(); const scalar_type pdf = nee.deferred_pdf(scene, env_light_id, ray); finalContribution *= ray.foundEmissiveMIS(pdf * pdf); ray.addPayloadContribution(finalContribution); @@ -198,8 +197,6 @@ struct Unidirectional const uint32_t sampleCount = sampleIndex + 1; accumulator.addSample(sampleCount, ray.getPayloadAccumulatiion()); - // TODO: visualize high variance - // TODO: russian roulette early exit? } From ad5b706a9bdb4a75ae953979bc8bc83fd8a9ce48 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 6 Mar 2026 16:04:17 +0700 Subject: [PATCH 096/101] latest example --- examples_tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_tests b/examples_tests index 2fb0002ff5..aed31a3a4a 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 2fb0002ff5a5b9dffbfbb3dafa85c8ee5e9733e9 +Subproject commit aed31a3a4a725fe0b76fd25ae5672616c9bd00c7 From c65bb4cc260c9dafae383c72540a8d68ac14926d Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 6 Mar 2026 16:40:34 +0700 Subject: [PATCH 097/101] separate payload throughput from emissive MIS, fix inf check --- examples_tests | 2 +- include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/examples_tests b/examples_tests index aed31a3a4a..95b927a232 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit aed31a3a4a725fe0b76fd25ae5672616c9bd00c7 +Subproject commit 95b927a23253bebdccc4f9911adbe371e8ae9a2e diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index b9d51c6c41..43e4cb124e 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -68,6 +68,7 @@ struct Unidirectional const typename nee_type::light_id_type lightID = matLightID.getLightID(); if (ray.shouldDoMIS() && matLightID.isLight()) { + emissive *= ray.getPayloadThroughput(); const scalar_type pdf = nee.deferred_pdf(scene, lightID, ray); assert(!hlsl::isinf(pdf)); emissive *= ray.foundEmissiveMIS(pdf * pdf); @@ -110,7 +111,7 @@ struct Unidirectional // 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 (!hlsl::isinf(neeContrib.pdf)) + 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 @@ -172,7 +173,9 @@ struct Unidirectional measure_type finalContribution = nee.get_environment_radiance(ray); typename nee_type::light_id_type env_light_id = nee.get_env_light_id(); const scalar_type pdf = nee.deferred_pdf(scene, env_light_id, ray); - finalContribution *= ray.foundEmissiveMIS(pdf * pdf); + finalContribution *= ray.getPayloadThroughput(); + if (pdf > scalar_type(0.0)) + finalContribution *= ray.foundEmissiveMIS(pdf * pdf); ray.addPayloadContribution(finalContribution); } From 8a9070e9eccb3547a3a1b8944df27f1bbbb57f47 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 6 Mar 2026 18:14:48 +0700 Subject: [PATCH 098/101] minor change to ray concept, fix include in splatting params file --- examples_tests | 2 +- include/nbl/builtin/hlsl/path_tracing/concepts.hlsl | 2 +- include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples_tests b/examples_tests index 95b927a232..77f4b77500 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 95b927a23253bebdccc4f9911adbe371e8ae9a2e +Subproject commit 77f4b775008a50cda066af5d611e6147a886f52e diff --git a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl index 97ed9d44a6..25ca98772c 100644 --- a/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/concepts.hlsl @@ -55,7 +55,7 @@ NBL_CONCEPT_END( ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.template setInteraction, typename T::spectral_type> >(interaction)), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.initPayload()), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.shouldDoMIS()), ::nbl::hlsl::is_same_v, bool)) - ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.foundEmissiveMIS(scalar)), ::nbl::hlsl::is_same_v, typename T::spectral_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.foundEmissiveMIS(scalar)), ::nbl::hlsl::is_same_v, typename T::scalar_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.addPayloadContribution(color)), ::nbl::hlsl::is_same_v, void)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.getPayloadAccumulatiion()), ::nbl::hlsl::is_same_v, typename T::spectral_type)) ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((ray.updateThroughputAndMISWeights(color, scalar)), ::nbl::hlsl::is_same_v, void)) diff --git a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl index 939c30815b..736d2e0dfe 100644 --- a/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl +++ b/include/nbl/builtin/hlsl/rwmc/SplattingParameters.hlsl @@ -2,6 +2,7 @@ #define _NBL_BUILTIN_HLSL_RWMC_SPLATTING_PARAMETERS_HLSL_INCLUDED_ #include "nbl/builtin/hlsl/cpp_compat.hlsl" +#include "nbl/builtin/hlsl/tgmath.hlsl" #include namespace nbl From d2114f815580ea6ebb3f5fb0e4ed6be5dc91ab9f Mon Sep 17 00:00:00 2001 From: Karim Mohamed Date: Sat, 7 Mar 2026 11:31:17 +0300 Subject: [PATCH 099/101] 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 100/101] 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 101/101] 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