From 951f880ceb3afb1e9f876f5cc58ab8ffb564501c Mon Sep 17 00:00:00 2001 From: Deepanshu Pal <40927968+DeepanshuPal@users.noreply.github.com> Date: Thu, 8 Oct 2026 07:26:32 +0530 Subject: [PATCH 1/3] Fix real spherical harmonics basis normalization --- .../Utils/Math/SphericalHarmonics.slang | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Source/Falcor/Utils/Math/SphericalHarmonics.slang b/Source/Falcor/Utils/Math/SphericalHarmonics.slang index 3a69a754bb..fa096a48ee 100644 --- a/Source/Falcor/Utils/Math/SphericalHarmonics.slang +++ b/Source/Falcor/Utils/Math/SphericalHarmonics.slang @@ -55,25 +55,25 @@ float eval_SH(uint idx, float3 p) switch (idx) { // l = 0 - case 0: return 1.f / (2.f * M_1_SQRTPI); // m = 0 + case 0: return 1.f / (2.f * M_SQRTPI); // m = 0 // l = 1 - case 1: return p.y * sqrt(3.f) / (2.f * M_1_SQRTPI); // m =-1 - case 2: return p.z * sqrt(3.f) / (2.f * M_1_SQRTPI); // m = 0 - case 3: return p.x * sqrt(3.f) / (2.f * M_1_SQRTPI); // m = 1 + case 1: return p.y * sqrt(3.f) / (2.f * M_SQRTPI); // m =-1 + case 2: return p.z * sqrt(3.f) / (2.f * M_SQRTPI); // m = 0 + case 3: return p.x * sqrt(3.f) / (2.f * M_SQRTPI); // m = 1 // l = 2 - case 4: return p.x * p.y * sqrt(15.f) / (2.f * M_1_SQRTPI); // m =-2 - case 5: return p.y * p.z * sqrt(15.f) / (2.f * M_1_SQRTPI); // m =-1 - case 6: return (3.f * p.z * p.z - 1.f) * sqrt(5.f) / (4.f * M_1_SQRTPI); // m = 0 - case 7: return p.x * p.z * sqrt(15.f) / (2.f * M_1_SQRTPI); // m = 1 - case 8: return (p.x * p.x - p.y * p.y) * sqrt(15.f) / (4.f * M_1_SQRTPI); // m = 2 + case 4: return p.x * p.y * sqrt(15.f) / (2.f * M_SQRTPI); // m =-2 + case 5: return p.y * p.z * sqrt(15.f) / (2.f * M_SQRTPI); // m =-1 + case 6: return (3.f * p.z * p.z - 1.f) * sqrt(5.f) / (4.f * M_SQRTPI); // m = 0 + case 7: return p.x * p.z * sqrt(15.f) / (2.f * M_SQRTPI); // m = 1 + case 8: return (p.x * p.x - p.y * p.y) * sqrt(15.f) / (4.f * M_SQRTPI); // m = 2 // l = 3 - case 9: return p.y * (3.f * p.x * p.x - p.y * p.y) * sqrt(70.f) / (8.f * M_1_SQRTPI); // m =-3 - case 10: return p.x * p.y * p.z * sqrt(105.f) / (2.f * M_1_SQRTPI); // m =-2 - case 11: return p.y * (5.f * p.z * p.z - 1.f) * sqrt(42.f) / (8.f * M_1_SQRTPI); // m =-1 - case 12: return p.z * (5.f * p.z * p.z - 3.f) * sqrt(7.f) / (4.f * M_1_SQRTPI); // m = 0 - case 13: return p.x * (5.f * p.z * p.z - 1.f) * sqrt(42.f) / (8.f * M_1_SQRTPI); // m = 1 - case 14: return p.z * (p.x * p.x - p.y * p.y) * sqrt(105.f) / (4.f * M_1_SQRTPI); // m = 2 - case 15: return p.x * (p.x * p.x - 3.f * p.y * p.y) * sqrt(70.f) / (8.f * M_1_SQRTPI); // m = 3 + case 9: return p.y * (3.f * p.x * p.x - p.y * p.y) * sqrt(70.f) / (8.f * M_SQRTPI); // m =-3 + case 10: return p.x * p.y * p.z * sqrt(105.f) / (2.f * M_SQRTPI); // m =-2 + case 11: return p.y * (5.f * p.z * p.z - 1.f) * sqrt(42.f) / (8.f * M_SQRTPI); // m =-1 + case 12: return p.z * (5.f * p.z * p.z - 3.f) * sqrt(7.f) / (4.f * M_SQRTPI); // m = 0 + case 13: return p.x * (5.f * p.z * p.z - 1.f) * sqrt(42.f) / (8.f * M_SQRTPI); // m = 1 + case 14: return p.z * (p.x * p.x - p.y * p.y) * sqrt(105.f) / (4.f * M_SQRTPI); // m = 2 + case 15: return p.x * (p.x * p.x - 3.f * p.y * p.y) * sqrt(70.f) / (8.f * M_SQRTPI); // m = 3 } // clang-format on return 0.f; From 9e80da15a42b5c186a25fbddb290d3b3cdc90225 Mon Sep 17 00:00:00 2001 From: Deepanshu Pal <40927968+DeepanshuPal@users.noreply.github.com> Date: Thu, 8 Oct 2026 07:26:53 +0530 Subject: [PATCH 2/3] Test spherical harmonics normalization through degree three --- .../Tests/Utils/SphericalHarmonicsTests.cpp | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Source/Tools/FalcorTest/Tests/Utils/SphericalHarmonicsTests.cpp diff --git a/Source/Tools/FalcorTest/Tests/Utils/SphericalHarmonicsTests.cpp b/Source/Tools/FalcorTest/Tests/Utils/SphericalHarmonicsTests.cpp new file mode 100644 index 0000000000..589e93ba28 --- /dev/null +++ b/Source/Tools/FalcorTest/Tests/Utils/SphericalHarmonicsTests.cpp @@ -0,0 +1,69 @@ +/*************************************************************************** + # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +#include "Testing/UnitTest.h" +#include + +namespace Falcor +{ +GPU_TEST(SphericalHarmonics_Normalization) +{ + const std::vector directions = { + float3(1.f, 0.f, 0.f), float3(0.f, 1.f, 0.f), float3(0.f, 0.f, 1.f), normalize(float3(1.f, 2.f, 3.f)) + }; + constexpr uint32_t basisCount = 16; + ctx.createProgram("Tests/Utils/SphericalHarmonicsTests.cs.slang", "testSphericalHarmonics"); + ctx.allocateStructuredBuffer("directions", (uint32_t)directions.size(), directions.data()); + ctx.allocateStructuredBuffer("result", (uint32_t)directions.size() * basisCount); + ctx.runProgram((uint32_t)directions.size() * basisCount); + + const auto result = ctx.readBuffer("result"); + const float pi = std::acos(-1.f); + constexpr float epsilon = 1e-5f; + for (uint32_t sample = 0; sample < directions.size(); ++sample) + { + const float y00 = result[sample * basisCount]; + const float expectedY00 = 1.f / std::sqrt(4.f * pi); + EXPECT_GE(y00, expectedY00 - epsilon) << "sample = " << sample; + EXPECT_LE(y00, expectedY00 + epsilon) << "sample = " << sample; + + // Addition theorem for an orthonormal real SH basis on the unit sphere. + for (uint32_t degree = 0; degree < 4; ++degree) + { + float sum = 0.f; + for (uint32_t index = degree * degree; index < (degree + 1) * (degree + 1); ++index) + { + const float value = result[sample * basisCount + index]; + sum += value * value; + } + const float expected = (2.f * degree + 1.f) / (4.f * pi); + EXPECT_GE(sum, expected - epsilon) << "sample = " << sample << ", degree = " << degree; + EXPECT_LE(sum, expected + epsilon) << "sample = " << sample << ", degree = " << degree; + } + } +} +} // namespace Falcor From 579c672479c3680fbeba71e0e160f8a8f17b5d91 Mon Sep 17 00:00:00 2001 From: Deepanshu Pal <40927968+DeepanshuPal@users.noreply.github.com> Date: Thu, 8 Oct 2026 07:27:48 +0530 Subject: [PATCH 3/3] Add shader for spherical harmonics regression test --- .../Utils/SphericalHarmonicsTests.cs.slang | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Source/Tools/FalcorTest/Tests/Utils/SphericalHarmonicsTests.cs.slang diff --git a/Source/Tools/FalcorTest/Tests/Utils/SphericalHarmonicsTests.cs.slang b/Source/Tools/FalcorTest/Tests/Utils/SphericalHarmonicsTests.cs.slang new file mode 100644 index 0000000000..24ba62f1cd --- /dev/null +++ b/Source/Tools/FalcorTest/Tests/Utils/SphericalHarmonicsTests.cs.slang @@ -0,0 +1,38 @@ +/*************************************************************************** + # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **************************************************************************/ +import Utils.Math.SphericalHarmonics; + +StructuredBuffer directions; +RWStructuredBuffer result; + +[numthreads(1, 1, 1)] void testSphericalHarmonics(uint3 threadId : SV_DispatchThreadID) +{ + const uint index = threadId.x % 16; + const uint sample = threadId.x / 16; + result[threadId.x] = eval_SH(index, directions[sample]); +}