From 75740d214de60e03cd2bcd738186a508c1c4d2f3 Mon Sep 17 00:00:00 2001 From: yum Date: Tue, 11 Nov 2025 21:46:04 -0800 Subject: implement hazel "Spherical Harmonics for Lighting" --- lighting.cginc | 72 +++++++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 33 deletions(-) (limited to 'lighting.cginc') diff --git a/lighting.cginc b/lighting.cginc index 5a31a25..87a3dfc 100644 --- a/lighting.cginc +++ b/lighting.cginc @@ -96,6 +96,33 @@ float3 getIndirectSpecular(v2f i, float roughness, float3 view_dir, float3 refle return env_refl; } +// Geomerics SH evaluation +// https://community.arm.com/cfs-file/__key/telligent-evolution-components-attachments/01-2066-00-00-00-01-27-70/Simplifying_2D00_Spherical_2D00_Harmonics_2D00_for_2D00_Lighting.pdf +float shEvaluateDiffuseL1Geomerics(float L0, float3 L1, float3 n) { + // average energy + float R0 = max(L0, 0); + + // avg direction of incoming light + float3 R1 = 0.5f * L1; + + // directional brightness + float lenR1 = length(R1); + + // linear angle between normal and direction 0-1 + float q = dot(normalize(R1), n) * 0.5 + 0.5; + q = saturate(q); + + // power for q + // lerps from 1 (linear) to 3 (cubic) based on directionality + float p = 1.0f + 2.0f * lenR1 / R0; + + // dynamic range constant + // should vary between 4 (highly directional) and 0 (ambient) + float a = (1.0f - lenR1 / R0) / (1.0f + lenR1 / R0); + + return R0 * (a + (1.0f - a) * (p + 1.0f) * pow(q, p)); +} + float3 yumSH9(float4 n, float3 worldPos, inout LightIndirect light) { [branch] if (_UdonLightVolumeEnabled) { @@ -106,44 +133,23 @@ float3 yumSH9(float4 n, float3 worldPos, inout LightIndirect light) { dot(light.L01b, n.xyz)); } - // No light volumes - use SH9 - // Unity gives us the first three bands (L0-L2) of SH coefficients as follows: - // unity_SHA*.w: L0 coefficients - // unity_SHA*.xyz: L1 coefficients - // unity_SHB*: first four of the L2 coefficients - // unity_SHC: last L2 coefficient - - // L0 band + // Geomerics SH evaluation + // Extract L0 and optionally incorporate part of L2 float3 L0 = float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w); + float3 L0L2 = float3(unity_SHBr.z, unity_SHBg.z, unity_SHBb.z) / 3.0; + L0 = L0 + L0L2; // useL2 = true - // L1 band - float3x3 L1m = float3x3(unity_SHAr.xyz, unity_SHAg.xyz, unity_SHAb.xyz); - float3 L1 = mul(L1m, n); - - // L2 band - float4 v = float4(n.x * n.y, n.y * n.z, n.z * n.z, n.x * n.z); - float3 L2 = - float3(dot(unity_SHBr.xyzw, v), dot(unity_SHBg.xyzw, v), dot(unity_SHBb.xyzw, v)) + - unity_SHC.xyz * (n.x * n.x - n.y * n.y); - - // TODO expose this as a parameter - float wrap_term = 0.0f; - - // Original coefficients: 1, 2/3, 1/4. - // Wrapped coefficients: 1, (2-w)/3, ((1-w)^2)/4. - float l1_wrap = 1.0f - wrap_term * 0.75f; - L1 *= l1_wrap; - - float l2_wrap_base = 1.0f - wrap_term; - float l2_wrap = l2_wrap_base * l2_wrap_base; - L2 *= l2_wrap; + float3 finalSH; + finalSH.r = shEvaluateDiffuseL1Geomerics(L0.r, unity_SHAr.xyz, n.xyz); + finalSH.g = shEvaluateDiffuseL1Geomerics(L0.g, unity_SHAg.xyz, n.xyz); + finalSH.b = shEvaluateDiffuseL1Geomerics(L0.b, unity_SHAb.xyz, n.xyz); light.L00 = L0; - light.L01r = unity_SHAr.xyz * l1_wrap; - light.L01g = unity_SHAg.xyz * l1_wrap; - light.L01b = unity_SHAb.xyz * l1_wrap; + light.L01r = unity_SHAr.xyz; + light.L01g = unity_SHAg.xyz; + light.L01b = unity_SHAb.xyz; - return L0 + L1 + L2; + return finalSH; } float4 getIndirectDiffuse(v2f i, Pbr pbr, inout LightIndirect light) { -- cgit v1.2.3