From 5eeb47351e29cb6ee02f8d8319f131a2c012b5a2 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Thu, 16 Apr 2026 14:58:47 +0200 Subject: [PATCH 1/2] make NEE work in ex 31 with Global L solid angle sampling of spherical rect --- .../hlsl/next_event_estimator.hlsl | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl b/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl index c8bee786c..29aca1824 100644 --- a/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl +++ b/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl @@ -246,6 +246,7 @@ template struct ShapeSampling { using scalar_type = T; + using vector2_type = vector; using vector3_type = vector; static ShapeSampling create(NBL_CONST_REF_ARG(Shape) rect) @@ -262,48 +263,56 @@ struct ShapeSampling matrix rectNormalBasis; vector rectExtents; rect.getNormalBasis(rectNormalBasis, rectExtents); + shapes::SphericalRectangle sphR0; sphR0.origin = rect.offset; sphR0.extents = rectExtents; sphR0.basis = rectNormalBasis; - scalar_type solidAngle = sphR0.solidAngle(ray.origin).value; - if (solidAngle > numeric_limits::min) - pdf = 1.f / solidAngle; - else - pdf = bit_cast(numeric_limits::infinity); - return pdf; + + // 1.f/0.f gives infinity no special checks needed + return 1.f / sphR0.solidAngle(ray.origin).value; } template vector3_type generate_and_pdf(NBL_REF_ARG(scalar_type) pdf, NBL_REF_ARG(scalar_type) newRayMaxT, NBL_CONST_REF_ARG(vector3_type) origin, NBL_CONST_REF_ARG(Aniso) interaction, NBL_CONST_REF_ARG(vector3_type) xi) { - const vector3_type N = rect.getNormalTimesArea(); - const vector3_type origin2origin = rect.offset - origin; - matrix rectNormalBasis; vector rectExtents; rect.getNormalBasis(rectNormalBasis, rectExtents); + shapes::SphericalRectangle sphR0; sphR0.origin = rect.offset; sphR0.extents = rectExtents; sphR0.basis = rectNormalBasis; - vector3_type L = hlsl::promote(0.0); + // sampling::SphericalRectangle ssph = sampling::SphericalRectangle::create(sphR0, origin); - if ( ssph.solidAngle > numeric_limits::min) + typename sampling::SphericalRectangle::cache_type cache; + + const vector3_type origin2origin = rect.offset - origin; + vector3_type L = hlsl::promote(0.0); + const bool FastVersion = true; + if (FastVersion) { - typename sampling::SphericalRectangle::cache_type cache; - const vector3_type localDir = ssph.generate(xi.xy, cache); - // not sure if generate() can produce NaN/inf when solidAngle > min - assert(!hlsl::any(hlsl::isinf(localDir) || hlsl::isnan(localDir))); - // transform local direction to world space - L = localDir.x * rectNormalBasis[0] + localDir.y * rectNormalBasis[1] + localDir.z * rectNormalBasis[2]; - pdf = ssph.forwardPdf(xi.xy, cache); + // actually the slowest + //L = ssph.generate(xi.xy, cache); + //newRayMaxT = ssph.computeHitT(L); + + // fastest + const vector3_type localL = ssph.generateNormalizedLocal(xi.xy,cache,newRayMaxT); + L = hlsl::mul(hlsl::transpose(ssph.basis),localL); } else - pdf = bit_cast(numeric_limits::infinity); + { + L = ssph.generateUnnormalized(xi.xy,cache); + const scalar_type rcpLen = hlsl::rsqrt(hlsl::dot(L,L)); + newRayMaxT = 1.f / rcpLen; + L *= rcpLen; + } + // prevent self intersections against the emitter + newRayMaxT -= 0.0001f; - newRayMaxT = hlsl::dot(N, origin2origin) / hlsl::dot(N, L); + pdf = ssph.forwardPdf(xi.xy,cache); return L; } @@ -322,7 +331,7 @@ struct EffectivePolygonMethod NBL_CONSTEXPR_STATIC_INLINE NEEPolygonMethod value = PPM_SOLID_ANGLE; }; - +#if 0 // Projected solid angle NEE for rectangles using "Practical Warps": // bilinear warp over 4-corner NdotL + spherical rectangle sampling. // Same grazing-angle limitations as the triangle variant -- see comments @@ -398,7 +407,7 @@ struct ShapeSampling Shape rect; }; - +#endif template struct NextEventEstimator From 89ecce14443c216b30ff84b837b899045bb5513f Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Fri, 17 Apr 2026 03:26:32 +0200 Subject: [PATCH 2/2] prep for rendering with PSA rectangle --- .../hlsl/next_event_estimator.hlsl | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl b/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl index 29aca1824..91d2a2d5e 100644 --- a/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl +++ b/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl @@ -177,9 +177,7 @@ struct ShapeSampling const vector3_type tri_vertices[3] = {tri.vertex0, tri.vertex1, tri.vertex2}; shapes::SphericalTriangle st = shapes::SphericalTriangle::create(tri_vertices, ray.origin); sampling::ProjectedSphericalTriangle pst = sampling::ProjectedSphericalTriangle::create(st, ray.normalAtOrigin, ray.wasBSDFAtOrigin); - const scalar_type pdf = pst.backwardPdf(L); - // if `pdf` is NAN then the triangle's projected solid angle was close to 0.0, if its close to INF then the triangle was very small - return pdf < numeric_limits::max ? pdf : numeric_limits::max; + return pst.backwardWeight(L); } template @@ -331,7 +329,6 @@ struct EffectivePolygonMethod NBL_CONSTEXPR_STATIC_INLINE NEEPolygonMethod value = PPM_SOLID_ANGLE; }; -#if 0 // Projected solid angle NEE for rectangles using "Practical Warps": // bilinear warp over 4-corner NdotL + spherical rectangle sampling. // Same grazing-angle limitations as the triangle variant -- see comments @@ -361,21 +358,12 @@ struct ShapeSampling sphR0.extents = rectExtents; sphR0.basis = rectNormalBasis; sampling::ProjectedSphericalRectangle psr = sampling::ProjectedSphericalRectangle::create(sphR0, ray.origin, ray.normalAtOrigin, ray.wasBSDFAtOrigin); - // Reconstruct normalized [0,1]^2 position on the rectangle from the ray direction - const vector3_type N = rect.getNormalTimesArea(); - const scalar_type t = hlsl::dot(N, rect.offset - ray.origin) / hlsl::dot(N, ray.direction); - const vector3_type hitPoint = ray.origin + ray.direction * t; - const vector3_type localHit = hitPoint - rect.offset; - const vector p = vector(hlsl::dot(localHit, rectNormalBasis[0]) / rectExtents.x, hlsl::dot(localHit, rectNormalBasis[1]) / rectExtents.y); - const scalar_type pdf = psr.backwardPdf(p); - return pdf < numeric_limits::max ? pdf : numeric_limits::max; + return psr.backwardWeight(ray.direction); } template vector3_type generate_and_pdf(NBL_REF_ARG(scalar_type) pdf, NBL_REF_ARG(scalar_type) newRayMaxT, NBL_CONST_REF_ARG(vector3_type) origin, NBL_CONST_REF_ARG(Aniso) interaction, NBL_CONST_REF_ARG(vector3_type) xi) { - const vector3_type N = rect.getNormalTimesArea(); - const vector3_type origin2origin = rect.offset - origin; matrix rectNormalBasis; vector rectExtents; @@ -384,30 +372,40 @@ struct ShapeSampling sphR0.origin = rect.offset; sphR0.extents = rectExtents; sphR0.basis = rectNormalBasis; - vector3_type L = hlsl::promote(0.0); sampling::ProjectedSphericalRectangle psr = sampling::ProjectedSphericalRectangle::create(sphR0, origin, interaction.getN(), interaction.isMaterialBSDF()); - const scalar_type solidAngle = psr.sphrect.solidAngle; - if (solidAngle > numeric_limits::min) + typename sampling::ProjectedSphericalRectangle::cache_type cache; + + const vector3_type origin2origin = rect.offset - origin; + vector3_type L = hlsl::promote(0.0); + const bool FastVersion = true; + if (FastVersion) { - typename sampling::ProjectedSphericalRectangle::cache_type cache; - const vector3_type localDir = psr.generate(xi.xy, cache); - // not sure if generate() can produce NaN/inf when solidAngle > min - assert(!hlsl::any(hlsl::isinf(localDir) || hlsl::isnan(localDir))); - // transform local direction to world space - L = localDir.x * rectNormalBasis[0] + localDir.y * rectNormalBasis[1] + localDir.z * rectNormalBasis[2]; - pdf = psr.forwardPdf(xi.xy, cache); + // actually the slowest + //L = psr.generate(xi.xy, cache); + //newRayMaxT = psr.sphrect.computeHitT(L); + + // fastest + const vector3_type localL = psr.generateNormalizedLocal(xi.xy,cache,newRayMaxT); + // hopefully CSE kicks in for the `UsePdfAsWeight==true` + L = hlsl::mul(hlsl::transpose(psr.sphrect.basis),localL); } else - pdf = bit_cast(numeric_limits::infinity); - - newRayMaxT = hlsl::dot(N, origin2origin) / hlsl::dot(N, L); + { + L = psr.generateUnnormalized(xi.xy,cache); + const scalar_type rcpLen = hlsl::rsqrt(hlsl::dot(L,L)); + newRayMaxT = 1.f / rcpLen; + L *= rcpLen; + } + // prevent self intersections against the emitter + newRayMaxT -= 0.0001f; + + pdf = psr.forwardPdf(xi.xy,cache); return L; } Shape rect; }; -#endif template struct NextEventEstimator