@@ -40,7 +40,8 @@ float IntegrateEdge(float3 V1, float3 V2)
4040// N.b.: this function accounts for horizon clipping.
4141float DiffuseSphereLightIrradiance (float sinSqSigma, float cosOmega)
4242{
43- float irradiance;
43+ // Clamp to avoid visual artifacts.
44+ sinSqSigma = min (sinSqSigma, 0.999 );
4445
4546#if 0 // Ref: Area Light Sources for Real-Time Graphics, page 4 (1996).
4647 float sinSqOmega = saturate (1 - cosOmega * cosOmega);
@@ -56,7 +57,7 @@ float DiffuseSphereLightIrradiance(float sinSqSigma, float cosOmega)
5657 float omega = acos (cosOmega);
5758 float gamma = asin (sinGamma);
5859
59- if (omega < 0 || omega >= HALF_PI + sigma)
60+ if (omega >= HALF_PI + sigma)
6061 {
6162 // Full horizon occlusion (case #4).
6263 return 0 ;
@@ -68,7 +69,7 @@ float DiffuseSphereLightIrradiance(float sinSqSigma, float cosOmega)
6869 if (omega < HALF_PI - sigma)
6970 {
7071 // No horizon occlusion (case #1).
71- irradiance = e;
72+ return e;
7273 }
7374 else
7475 {
@@ -78,47 +79,60 @@ float DiffuseSphereLightIrradiance(float sinSqSigma, float cosOmega)
7879 if (omega < HALF_PI)
7980 {
8081 // Partial horizon occlusion (case #2).
81- irradiance = e + INV_PI * (g - h);
82+ return saturate ( e + INV_PI * (g - h) );
8283 }
8384 else
8485 {
8586 // Partial horizon occlusion (case #3).
86- irradiance = INV_PI * (g + h);
87+ return saturate ( INV_PI * (g + h) );
8788 }
8889 }
89- #else // Ref: Moving Frostbite to Physically Based Rendering, page 47 (2015).
90- float cosSqOmega = cosOmega * cosOmega;
90+ #else // Ref: Moving Frostbite to Physically Based Rendering, page 47 (2015, optimized ).
91+ float cosSqOmega = cosOmega * cosOmega; // y^2
9192
9293 [branch]
93- if (cosSqOmega > sinSqSigma)
94+ if (cosSqOmega > sinSqSigma) // (y^2)>x
9495 {
95- irradiance = sinSqSigma * saturate (cosOmega);
96+ return sinSqSigma * saturate (cosOmega); // x*Clip[y,{0,1}]
9697 }
9798 else
9899 {
99- float cotanOmega = cosOmega * rsqrt (1 - cosSqOmega);
100+ float cotSqSigma = rcp (sinSqSigma) - 1 ; // 1/x-1
101+ float tanSqSigma = rcp (cotSqSigma); // x/(1-x)
102+ float sinSqOmega = 1 - cosSqOmega; // 1-y^2
100103
101- float x = rcp (sinSqSigma) - 1 ;
102- float y = -cotanOmega * sqrt (x);
103- float z = sqrt (1 - cosSqOmega * rcp (sinSqSigma));
104+ float w = sinSqOmega * tanSqSigma; // (1-y^2)*(x/(1-x))
105+ float x = -cosOmega * rsqrt (w); // -y*Sqrt[(1/x-1)/(1-y^2)]
106+ float y = sqrt (sinSqOmega * tanSqSigma - cosSqOmega); // Sqrt[(1-y^2)*(x/(1-x))-y^2]
107+ float z = y * cotSqSigma; // Sqrt[(1-y^2)*(x/(1-x))-y^2]*(1/x-1)
104108
105- irradiance = INV_PI * ((cosOmega * acos (y) - z * sqrt (x)) * sinSqSigma + atan (z * rsqrt (x)));
109+ float a = cosOmega * acos (x) - z; // y*ArcCos[-y*Sqrt[(1/x-1)/(1-y^2)]]-Sqrt[(1-y^2)*(x/(1-x))-y^2]*(1/x-1)
110+ float b = atan (y); // ArcTan[Sqrt[(1-y^2)*(x/(1-x))-y^2]]
111+
112+ // Replacing max() with saturate() results in a 12 cycle SGPR forwarding stall on PS4.
113+ return max (INV_PI * (a * sinSqSigma + b), 0 ); // (a/Pi)*x+(b/Pi)
106114 }
107115#endif
108- return max (irradiance, 0 );
109116}
110117
111118// Expects non-normalized vertex positions.
112119float PolygonIrradiance (float4x3 L)
113120{
114121#ifdef SPHERE_LIGHT_APPROXIMATION
115- for (int i = 0 ; i < 4 ; i++)
122+ float h = saturate (L[0 ].z) + saturate (L[1 ].z) + saturate (L[2 ].z) + saturate (L[3 ].z);
123+
124+ [branch]
125+ if (h == 0 ) { return 0 ; } // Perform horizon clipping
126+
127+ [unroll]
128+ for (uint i = 0 ; i < 4 ; i++)
116129 {
117130 L[i] = normalize (L[i]);
118131 }
119132
120133 float3 F = float3 (0 , 0 , 0 );
121134
135+ [unroll]
122136 for (uint edge = 0 ; edge < 4 ; edge++)
123137 {
124138 float3 V1 = L[edge];
@@ -131,7 +145,18 @@ float PolygonIrradiance(float4x3 L)
131145 float sinSqSigma = sqrt (f2);
132146 float cosOmega = clamp (F.z * rsqrt (f2), -1 , 1 );
133147
134- return DiffuseSphereLightIrradiance (sinSqSigma, cosOmega);
148+ #if 0
149+ return DiffuseSphereLightIrradiance (sinSqSigma, cosOmega);
150+ #else
151+ float x = sinSqSigma;
152+ float y = cosOmega;
153+
154+ float b = x * (0.5 + 0.5 * y); // Bilinear approximation of a sphere light
155+ float z = b * (0.5 + 0.5 * y); // Our approximation of a rectangular light
156+ float r = x * y; // The reference value for an unoccluded light
157+
158+ return max (r, lerp (z * z, z, saturate (h))); // Horizon fade
159+ #endif
135160#else
136161 // 1. ClipQuadToHorizon
137162
0 commit comments