Added UtsAreaLight.hlsl;

Fixed the problem that the appearence of direct diffuse is incorrect when area light intensity is low.

Changed the file name of PBR.hlsl to UtsPBR.hlsl
Changed the file name of EnvLighting.hlsl to UtsEnvLighting.hlsl
This commit is contained in:
Misaki
2025-01-18 14:14:00 +09:00
parent 12a03e9c3c
commit e6b58cb321
8 changed files with 224 additions and 131 deletions

View File

@@ -16,7 +16,8 @@
#endif #endif
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoopDef.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoopDef.hlsl"
#include "EnvLighting.hlsl" #include "UtsEnvLighting.hlsl"
#include "UtsAreaLight.hlsl"
#include "HDRPToonFunction.hlsl" #include "HDRPToonFunction.hlsl"
#ifdef _WRITE_TRANSPARENT_MOTION_VECTOR #ifdef _WRITE_TRANSPARENT_MOTION_VECTOR
@@ -505,7 +506,6 @@ void Frag(PackedVaryingsToPS packedInput,
uint v_lightListOffset = 0; uint v_lightListOffset = 0;
uint v_lightIdx = lightStart; uint v_lightIdx = lightStart;
float channelAlpha = 0.0f;
[loop] // vulkan shader compiler can not unroll. [loop] // vulkan shader compiler can not unroll.
while (v_lightListOffset < lightCount) while (v_lightListOffset < lightCount)
{ {
@@ -538,8 +538,8 @@ void Frag(PackedVaryingsToPS packedInput,
UTSLightData utsLightData; UTSLightData utsLightData;
utsLightData.lightColor = additionalLightColor; utsLightData.lightColor = additionalLightColor;
utsLightData.lightDirection = lightDirection; utsLightData.lightDirection = lightDirection;
utsLightData.diffuseDimmer = s_lightData.diffuseDimmer * lightColor.a; utsLightData.diffuseDimmer = saturate(s_lightData.diffuseDimmer * lightColor.a);
utsLightData.specularDimmer = s_lightData.specularDimmer * lightColor.a; utsLightData.specularDimmer = saturate(s_lightData.specularDimmer * lightColor.a);
utsLightData.shadowTint = s_lightData.shadowTint; utsLightData.shadowTint = s_lightData.shadowTint;
utsLightData.penumbraTint = s_lightData.penumbraTint; utsLightData.penumbraTint = s_lightData.penumbraTint;
@@ -636,29 +636,14 @@ void Frag(PackedVaryingsToPS packedInput,
float4 ltcValue; float4 ltcValue;
float4x3 lightVerts;
float3x3 invM = transpose(preLightData.ltcTransformDiffuse);
float3 A = mul(invM, right);
float3 B = mul(invM, up);
float3 C = mul(invM, center);
lightVerts[0] = C - halfWidth * A - halfHeight * B; // LL
lightVerts[1] = lightVerts[0] + (2 * halfHeight) * B; // UL
lightVerts[2] = lightVerts[1] + (2 * halfWidth) * A; // UR
lightVerts[3] = lightVerts[2] - (2 * halfHeight) * B; // LR
float3 F = UTSPolygonFormFactor(lightVerts, float3(0,0,1), 4);
float l = length(F * PI);
float alpha = saturate(max(0, (l * l) / (l + 1)));
// Diffuse // Diffuse
ltcValue = EvaluateLTC_Area(isRectLight, center, right, up, halfWidth, halfHeight, transpose(preLightData.ltcTransformDiffuse), /*bsdfData.perceptualRoughness*/ 1.0f, s_lightData.cookieMode, s_lightData.cookieScaleOffset); ltcValue = UTS_EvaluateLTC_Area(isRectLight, center, right, up, halfWidth, halfHeight, transpose(preLightData.ltcTransformDiffuse), /*bsdfData.perceptualRoughness*/ 1.0f, true, s_lightData.cookieMode, s_lightData.cookieScaleOffset);
utsLightData.diffuseDimmer *= alpha * intensity; utsLightData.diffuseDimmer *= saturate(ltcValue.a * intensity);
utsLightData.lightColor *= ltcValue.rgb; utsLightData.lightColor *= ltcValue.rgb;
// Specular // Specular
ltcValue = EvaluateLTC_Area(isRectLight, center, right, up, halfWidth, halfHeight, transpose(preLightData.ltcTransformSpecular[0]), bsdfData.perceptualRoughness, s_lightData.cookieMode, s_lightData.cookieScaleOffset); ltcValue = UTS_EvaluateLTC_Area(isRectLight, center, right, up, halfWidth, halfHeight, transpose(preLightData.ltcTransformSpecular[0]), bsdfData.perceptualRoughness, false, s_lightData.cookieMode, s_lightData.cookieScaleOffset);
utsLightData.specularDimmer *= ltcValue.a * intensity; utsLightData.specularDimmer *= saturate(ltcValue.a * intensity);
if (isRectLight) if (isRectLight)
{ {
@@ -801,8 +786,8 @@ void Frag(PackedVaryingsToPS packedInput,
#endif #endif
// Ambient // Ambient
utsAggregateLighting.indirectDiffuse = ComputeIndirectDiffuse(posInput, bsdfData, V) * _ID_Intensity; utsAggregateLighting.indirectDiffuse = EvaluateIndirectDiffuse(posInput, bsdfData, V) * _ID_Intensity;
utsAggregateLighting.indirectSpecular = ComputeIndirectSpecular(context, posInput, preLightData, bsdfData, surfaceData, builtinData, V) * _IR_Intensity; utsAggregateLighting.indirectSpecular = EvaluateIndirectSpecular(context, posInput, preLightData, bsdfData, surfaceData, builtinData, V) * _IR_Intensity;
float3 finalColorWoEmissive = AccumulateAggregateLighting(utsAggregateLighting); float3 finalColorWoEmissive = AccumulateAggregateLighting(utsAggregateLighting);

View File

@@ -2,8 +2,9 @@
//nobuyuki@unity3d.com //nobuyuki@unity3d.com
//toshiyuki@unity3d.com (Universal RP/HDRP) //toshiyuki@unity3d.com (Universal RP/HDRP)
#include "PBR.hlsl" #include "UtsPBR.hlsl"
void UTS_OtherLights(LightLoopContext lightLoopContext, FragInputs input, UTSLightData utsLightData, SurfaceData surfaceData, BSDFData bsdfData, int lightType, float3 i_normalDir, float notDirectional, out float channelOutAlpha, inout UTSAggregateLighting utsAggregateLighting) void UTS_OtherLights(LightLoopContext lightLoopContext, FragInputs input, UTSLightData utsLightData, SurfaceData surfaceData, BSDFData bsdfData, int lightType, float3 i_normalDir, float notDirectional,
out float channelOutAlpha, inout UTSAggregateLighting utsAggregateLighting)
{ {
channelOutAlpha = 1.0f; channelOutAlpha = 1.0f;
@@ -52,6 +53,7 @@ void UTS_OtherLights(LightLoopContext lightLoopContext, FragInputs input, UTSLig
//v.2.0.5: //v.2.0.5:
float3 addPassLightColor; float3 addPassLightColor;
if (lightType == GPULIGHTTYPE_TUBE) if (lightType == GPULIGHTTYPE_TUBE)
{ {
addPassLightColor = (0.5f * preLightData.diffuseFGD + 0.5f) / PI * additionalLightColor.rgb; addPassLightColor = (0.5f * preLightData.diffuseFGD + 0.5f) / PI * additionalLightColor.rgb;
@@ -65,15 +67,15 @@ void UTS_OtherLights(LightLoopContext lightLoopContext, FragInputs input, UTSLig
addPassLightColor = _HalfLambert_var * additionalLightColor.rgb; addPassLightColor = _HalfLambert_var * additionalLightColor.rgb;
} }
float pureIntencity = max(0.001, (0.299 * additionalLightColor.r + 0.587 * additionalLightColor.g + 0.114 * additionalLightColor.b)); float pureIntensity = max(0.001, (0.299 * additionalLightColor.r + 0.587 * additionalLightColor.g + 0.114 * additionalLightColor.b));
float3 lightColor = max(float3(0.0, 0.0, 0.0), lerp(addPassLightColor, lerp(float3(0.0, 0.0, 0.0), min(addPassLightColor, addPassLightColor / pureIntencity), notDirectional), _Is_Filter_LightColor)); float3 lightColor = max(float3(0.0, 0.0, 0.0), lerp(addPassLightColor, lerp(float3(0.0, 0.0, 0.0), min(addPassLightColor, addPassLightColor / pureIntensity), notDirectional), _Is_Filter_LightColor));
float3 halfDirection = normalize(viewDirection + lightDirection); // has to be recalced here. float3 halfDirection = normalize(viewDirection + lightDirection); // has to be recalced here.
//v.2.0.5: //v.2.0.5:
_1st_ShadeColor_Step = saturate(_1st_ShadeColor_Step + _StepOffset); _1st_ShadeColor_Step = saturate(_1st_ShadeColor_Step + _StepOffset);
_2nd_ShadeColor_Step = saturate(_2nd_ShadeColor_Step + _StepOffset); _2nd_ShadeColor_Step = saturate(_2nd_ShadeColor_Step + _StepOffset);
// //
//v.2.0.5: If Added lights is directional, set 0 as _LightIntensity //v.2.0.5: If Added lights is directional, set 0 as _LightIntensity
float _LightIntensity = lerp(0, pureIntencity, notDirectional); float _LightIntensity = lerp(0, pureIntensity, notDirectional);
//v.2.0.5: Filtering the high intensity zone of PointLights //v.2.0.5: Filtering the high intensity zone of PointLights
float3 Set_LightColor = lightColor; float3 Set_LightColor = lightColor;
// //
@@ -133,8 +135,8 @@ void UTS_OtherLights(LightLoopContext lightLoopContext, FragInputs input, UTSLig
float _2ndColorFeatherForMask = lerp(_2nd_ShadeColor_Feather, 0.0f, max(_SecondShadeOverridden, _ComposerMaskMode)); float _2ndColorFeatherForMask = lerp(_2nd_ShadeColor_Feather, 0.0f, max(_SecondShadeOverridden, _ComposerMaskMode));
// //
float Set_FinalShadowMask = saturate((1.0 + ((Set_ShadingGrade - (_1st_ShadeColor_Step - _1stColorFeatherForMask)) * (0.0 - 1.0)) / (_1st_ShadeColor_Step - (_1st_ShadeColor_Step - _1stColorFeatherForMask)))); float Set_FinalShadowMask = saturate(1.0 + (Set_ShadingGrade - (_1st_ShadeColor_Step - _1stColorFeatherForMask)) * (0.0 - 1.0) / (_1st_ShadeColor_Step - (_1st_ShadeColor_Step - _1stColorFeatherForMask)));
float Set_ShadeShadowMask = saturate((1.0 + ((Set_ShadingGrade - (_2nd_ShadeColor_Step - _2ndColorFeatherForMask)) * (0.0 - 1.0)) / (_2nd_ShadeColor_Step - (_2nd_ShadeColor_Step - _2ndColorFeatherForMask)))); // 1st and 2nd Shades Mask float Set_ShadeShadowMask = saturate(1.0 + (Set_ShadingGrade - (_2nd_ShadeColor_Step - _2ndColorFeatherForMask)) * (0.0 - 1.0) / (_2nd_ShadeColor_Step - (_2nd_ShadeColor_Step - _2ndColorFeatherForMask))); // 1st and 2nd Shades Mask
//SGM //SGM
@@ -149,13 +151,12 @@ void UTS_OtherLights(LightLoopContext lightLoopContext, FragInputs input, UTSLig
Set_ShadeShadowMask Set_ShadeShadowMask
), ),
Set_FinalShadowMask); Set_FinalShadowMask);
#ifdef UTS_LAYER_VISIBILITY #ifdef UTS_LAYER_VISIBILITY
float Set_2nd_ShadeAlpha = _SecondShadeVisible; float Set_2nd_ShadeAlpha = _SecondShadeVisible;
channelOutAlpha = channelOutAlpha =
lerp(Set_BaseColorAlpha, lerp(Set_1st_ShadeAlpha, Set_2nd_ShadeAlpha, Set_ShadeShadowMask), Set_FinalShadowMask); lerp(Set_BaseColorAlpha, lerp(Set_1st_ShadeAlpha, Set_2nd_ShadeAlpha, Set_ShadeShadowMask), Set_FinalShadowMask);
#endif #endif
//utsAggregateLighting.directDiffuse = utsLightData.diffuseDimmer;
//return;
//v.2.0.6: Add HighColor if _Is_Filter_HiCutPointLightColor is False //v.2.0.6: Add HighColor if _Is_Filter_HiCutPointLightColor is False
@@ -229,17 +230,20 @@ void UTS_OtherLights(LightLoopContext lightLoopContext, FragInputs input, UTSLig
//Specular Term //Specular Term
float3 specularTerm = 0; float3 specularTerm = 0;
#ifndef _PBR_Mode_OFF
if(lightType == GPULIGHTTYPE_RECTANGLE || lightType == GPULIGHTTYPE_TUBE) if(lightType == GPULIGHTTYPE_RECTANGLE || lightType == GPULIGHTTYPE_TUBE)
{ {
specularTerm = preLightData.specularFGD * Set_LightColor * utsLightData.specularDimmer; specularTerm = preLightData.specularFGD * Set_LightColor;
#ifdef _PBR_Mode_TOON #ifdef _PBR_Mode_TOON
specularTerm = StepFeatherToon(specularTerm, _ToonSpecularStep, _ToonSpecularFeather); specularTerm = StepFeatherToon(specularTerm, _ToonSpecularStep, _ToonSpecularFeather);
#endif #endif
} }
else else
{ {
specularTerm = ComputeSpecularTerm(V, lightDirection, bsdfData) * Set_LightColor * utsLightData.specularDimmer; specularTerm = ComputeSpecularTerm(V, lightDirection, bsdfData) * Set_LightColor;
} }
#endif
specularTerm = specularTerm * (1.0 - Set_FinalShadowMask) * PI * surfaceData.specularColor; specularTerm = specularTerm * (1.0 - Set_FinalShadowMask) * PI * surfaceData.specularColor;
diffuseTerm = diffuseTerm * albedoIntensity; diffuseTerm = diffuseTerm * albedoIntensity;

View File

@@ -0,0 +1,165 @@
#ifndef UTS_AREA_LIGHT_INLCUDE
#define UTS_AREA_LIGHT_INLCUDE
// The output is *not* normalized by the factor of 1/TWO_PI (this is done by the PolygonFormFactor function).
real3 UTS_ComputeEdgeFactor(real3 V1, real3 V2)
{
real V1oV2 = dot(V1, V2);
real3 V1xV2 = cross(V1, V2); // Plane normal (tangent to the unit sphere)
real sqLen = saturate(1 - V1oV2 * V1oV2); // length(V1xV2) = abs(sin(angle))
real rcpLen = rsqrt(max(FLT_EPS, sqLen)); // Make sure it is finite
#if 0
real y = rcpLen * acos(V1oV2);
#else
// Let y[x_] = ArcCos[x] / Sqrt[1 - x^2].
// Range reduction: since ArcCos[-x] == Pi - ArcCos[x], we only need to consider x on [0, 1].
real x = abs(V1oV2);
// Limit[y[x], x -> 1] == 1,
// Limit[y[x], x -> 0] == Pi/2.
// The approximation is exact at the endpoints of [0, 1].
// Max. abs. error on [0, 1] is 1.33e-6 at x = 0.0036.
// Max. rel. error on [0, 1] is 8.66e-7 at x = 0.0037.
real y = HALF_PI + x * (-0.99991 + x * (0.783393 + x * (-0.649178 + x * (0.510589 + x * (-0.326137 + x * (0.137528 + x * -0.0270813))))));
if (V1oV2 < 0)
{
y = rcpLen * PI - y;
}
#endif
return V1xV2 * y;
}
// Input: 3-5 vertices in the coordinate frame centered at the shaded point.
// Output: signed vector irradiance.
// No horizon clipping is performed.
real3 UTS_PolygonFormFactor(real4x3 L, real3 L4, uint n, bool isDiffuse)
{
// The length cannot be zero since we have already checked
// that the light has a non-zero effective area,
// and thus its plane cannot pass through the origin.
L[0] = normalize(L[0]);
L[1] = normalize(L[1]);
L[2] = normalize(L[2]);
switch (n)
{
case 3:
L[3] = L[0];
break;
case 4:
L[3] = normalize(L[3]);
L4 = L[0];
break;
case 5:
L[3] = normalize(L[3]);
L4 = normalize(L4);
break;
}
// If the magnitudes of a pair of edge factors are
// nearly the same, catastrophic cancellation may occur:
// https://en.wikipedia.org/wiki/Catastrophic_cancellation
// For the same reason, the value of the cross product of two
// nearly collinear vectors is prone to large errors.
// Therefore, the algorithm is inherently numerically unstable
// for area lights that shrink to a line (or a point) after
// projection onto the unit sphere.
real3 F = UTS_ComputeEdgeFactor(L[0], L[1]);
F += UTS_ComputeEdgeFactor(L[1], L[2]);
F += UTS_ComputeEdgeFactor(L[2], L[3]);
if (n >= 4)
F += UTS_ComputeEdgeFactor(L[3], L4);
if (n == 5)
F += UTS_ComputeEdgeFactor(L4, L[0]);
return lerp(INV_TWO_PI * F, PI * F, isDiffuse); // The output may be projected onto the tangent plane (F.z) to yield signed irradiance.
}
// See "Real-Time Area Lighting: a Journey from Research to Production", slide 102.
// Turns out, despite the authors claiming that this function "calculates an approximation of
// the clipped sphere form factor", that is simply not true.
// First of all, above horizon, the function should then just return 'F.z', which it does not.
// Secondly, if we use the correct function called DiffuseSphereLightIrradiance(), it results
// in severe light leaking if the light is placed vertically behind the camera.
// So this function is clearly a hack designed to work around these problems.
real UTS_PolygonIrradianceFromVectorFormFactor(float3 F)
{
float l = length(F);
return max(0, (l * l) / (l + 1));
}
// Expects non-normalized vertex positions.
// Output: F is the signed vector irradiance.
real UTS_PolygonIrradiance(real4x3 L, bool isDiffuse, out real3 F)
{
//APPROXIMATE_POLY_LIGHT_AS_SPHERE_LIGHT
F = UTS_PolygonFormFactor(L, float3(0,0,1), 4, isDiffuse);
return UTS_PolygonIrradianceFromVectorFormFactor(F); // Accounts for the horizon.
}
float4 UTS_EvaluateLTC_Area(bool isRectLight, float3 center, float3 right, float3 up, float halfLength, float halfHeight,
float3x3 invM, float perceptualRoughness, bool isDiffuse, int cookieMode, float4 cookieScaleOffset)
{
float3 ortho = cross(center, right);
float orthoSq = dot(ortho, ortho);
// Check whether the light is in a vertical orientation.
bool quit = (orthoSq == 0);
// Check whether the light is entirely below the surface.
// We must test twice, since a linear transformation
// may bring the light above the surface (a side-effect).
quit = quit || (center.z + halfLength * abs(right.z) + halfHeight * abs(up.z) <= 0);
float4 ltcValue = float4(1, 1, 1, 0);
if (quit && !isDiffuse)
{
return ltcValue;
}
// Perform a sparse matrix multiplication.
float3 C = mul(invM, center);
float3 A = mul(invM, right);
float3 B = mul(invM, up);
// Check whether the light is entirely below the surface.
// We must test twice, since a linear transformation
// may bring the light below the surface (as expected).
if (C.z + halfLength * abs(A.z) + halfHeight * abs(B.z) <= 0 && !isDiffuse)
{
return ltcValue;
}
if (isRectLight)
{
float4x3 lightVerts;
lightVerts[0] = C - halfLength * A - halfHeight * B; // LL
lightVerts[1] = lightVerts[0] + (2 * halfHeight) * B; // UL
lightVerts[2] = lightVerts[1] + (2 * halfLength) * A; // UR
lightVerts[3] = lightVerts[2] - (2 * halfHeight) * B; // LR
float3 formFactor;
// Polygon irradiance in the transformed configuration.
ltcValue.a = UTS_PolygonIrradiance(lightVerts, isDiffuse, formFactor);
if (cookieMode != COOKIEMODE_NONE)
{
ltcValue.rgb = SampleAreaLightCookie(cookieScaleOffset, lightVerts, formFactor, perceptualRoughness);
}
}
else // Line light
{
float w = ComputeLineWidthFactor(invM, ortho, orthoSq);
ltcValue.a = I_diffuse_line(C, A, halfLength) * w;
}
return ltcValue;
}
#endif

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: afada69a218dd594ea0f17e7c639c533
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,3 +1,6 @@
#ifndef UTS_ENV
#define UTS_ENV
// _preIntegratedFGD and _CubemapLD are unique for each BRDF // _preIntegratedFGD and _CubemapLD are unique for each BRDF
IndirectLighting EvaluateBSDF_Env(LightLoopContext lightLoopContext, IndirectLighting EvaluateBSDF_Env(LightLoopContext lightLoopContext,
float3 V, PositionInputs posInput, float3 V, PositionInputs posInput,
@@ -127,7 +130,7 @@ float3 ComputeFresnelLerp(float3 c0, float3 c1, float cosA)
return lerp(c0, c1, t); return lerp(c0, c1, t);
} }
float3 ComputeIndirectDiffuse(PositionInputs posInput, BSDFData bsdfData, float3 V) float3 EvaluateIndirectDiffuse(PositionInputs posInput, BSDFData bsdfData, float3 V)
{ {
float3 indirectDiffuse = 0.0; float3 indirectDiffuse = 0.0;
@@ -184,7 +187,7 @@ float3 ComputeIndirectDiffuse(PositionInputs posInput, BSDFData bsdfData, float3
return indirectDiffuse; return indirectDiffuse;
} }
float3 ComputeIndirectSpecular(LightLoopContext lightLoopContext, PositionInputs posInput, PreLightData preLightData, BSDFData bsdfData, SurfaceData surfaceData, BuiltinData builtinData, float3 V) float3 EvaluateIndirectSpecular(LightLoopContext lightLoopContext, PositionInputs posInput, PreLightData preLightData, BSDFData bsdfData, SurfaceData surfaceData, BuiltinData builtinData, float3 V)
{ {
#if defined(_PBR_Mode_OFF) || defined(_PBR_Mode_TOON) #if defined(_PBR_Mode_OFF) || defined(_PBR_Mode_TOON)
return 0; return 0;
@@ -235,3 +238,5 @@ float3 ComputeIndirectSpecular(LightLoopContext lightLoopContext, PositionInputs
return indirectSpecular; return indirectSpecular;
#endif #endif
} }
#endif

View File

@@ -1,9 +1,12 @@
#ifndef UTS_PBR
#define UTS_PBR
#define ColorSpaceDielectricSpec half4(0.22, 0.22, 0.22, 0.779) #define ColorSpaceDielectricSpec half4(0.22, 0.22, 0.22, 0.779)
float3 schlick(float f0, float hl) { float3 schlick(float f0, float hl) {
real x = 1.0 - hl; float x = 1.0 - hl;
real x2 = x * x; float x2 = x * x;
real x5 = x * x2 * x2; float x5 = x * x2 * x2;
return (1.0 - f0) * x5 + f0; return (1.0 - f0) * x5 + f0;
} }
@@ -187,80 +190,4 @@ half3 FitWithCurveApprox(half NdotL, half Curvature)
} }
#endif #endif
// The output is *not* normalized by the factor of 1/TWO_PI (this is done by the PolygonFormFactor function).
real3 UTSComputeEdgeFactor(real3 V1, real3 V2)
{
real subtendedAngle;
real V1oV2 = dot(V1, V2);
real3 V1xV2 = cross(V1, V2); // Plane normal (tangent to the unit sphere)
real sqLen = saturate(1 - V1oV2 * V1oV2); // length(V1xV2) = abs(sin(angle))
real rcpLen = rsqrt(max(FLT_EPS, sqLen)); // Make sure it is finite
#if 0
real y = rcpLen * acos(V1oV2);
#else
// Let y[x_] = ArcCos[x] / Sqrt[1 - x^2].
// Range reduction: since ArcCos[-x] == Pi - ArcCos[x], we only need to consider x on [0, 1].
real x = abs(V1oV2);
// Limit[y[x], x -> 1] == 1,
// Limit[y[x], x -> 0] == Pi/2.
// The approximation is exact at the endpoints of [0, 1].
// Max. abs. error on [0, 1] is 1.33e-6 at x = 0.0036.
// Max. rel. error on [0, 1] is 8.66e-7 at x = 0.0037.
real y = HALF_PI + x * (-0.99991 + x * (0.783393 + x * (-0.649178 + x * (0.510589 + x * (-0.326137 + x * (0.137528 + x * -0.0270813))))));
if (V1oV2 < 0)
{
y = rcpLen * PI - y;
}
#endif #endif
return V1xV2 * y;
}
// Input: 3-5 vertices in the coordinate frame centered at the shaded point.
// Output: signed vector irradiance.
// No horizon clipping is performed.
real3 UTSPolygonFormFactor(real4x3 L, real3 L4, uint n)
{
// The length cannot be zero since we have already checked
// that the light has a non-zero effective area,
// and thus its plane cannot pass through the origin.
L[0] = normalize(L[0]);
L[1] = normalize(L[1]);
L[2] = normalize(L[2]);
switch (n)
{
case 3:
L[3] = L[0];
break;
case 4:
L[3] = normalize(L[3]);
L4 = L[0];
break;
case 5:
L[3] = normalize(L[3]);
L4 = normalize(L4);
break;
}
// If the magnitudes of a pair of edge factors are
// nearly the same, catastrophic cancellation may occur:
// https://en.wikipedia.org/wiki/Catastrophic_cancellation
// For the same reason, the value of the cross product of two
// nearly collinear vectors is prone to large errors.
// Therefore, the algorithm is inherently numerically unstable
// for area lights that shrink to a line (or a point) after
// projection onto the unit sphere.
real3 F = UTSComputeEdgeFactor(L[0], L[1]);
F += UTSComputeEdgeFactor(L[1], L[2]);
F += UTSComputeEdgeFactor(L[2], L[3]);
if (n >= 4)
F += UTSComputeEdgeFactor(L[3], L4);
if (n == 5)
F += UTSComputeEdgeFactor(L4, L[0]);
return F; // The output may be projected onto the tangent plane (F.z) to yield signed irradiance.
}