using System;
using System.Collections.Generic;
using Unity.Mathematics;
namespace UnityEngine.PathTracing.Core
{
internal struct LightFalloffDesc
{
public float LUTRange;
public Experimental.GlobalIllumination.FalloffType FalloffType;
public readonly override int GetHashCode()
{
return HashCode.Combine(LUTRange, FalloffType);
}
}
internal class LightFalloffLUT
{
// Inverse squared falloff: minimum distance to light to avoid division by zero
public const float DistThresholdSqr = 0.0001f; // 1cm (in Unity 1 is 1m) so this is 0.01^2
// Legacy Unity falloff: where the falloff down to zero should start
private const float ToZeroFadeStart = 0.8f * 0.8f;
// Legacy Unity falloff: constants for OpenGL attenuation
private const float ConstantFac = 1.000f;
private const float QuadraticFac = 25.0f;
// Calculate the quadratic attenuation factor for a light with a specified range
private static float CalculateLightQuadFac(float range)
{
return QuadraticFac / (range * range);
}
private static float LightAttenuateNormalized(float distSqr)
{
// match the vertex lighting falloff
float atten = 1 / (ConstantFac + CalculateLightQuadFac(1.0f) * distSqr);
// ...but vertex one does not falloff to zero at light's range;
// So force it to falloff to zero at the edges.
if (distSqr >= ToZeroFadeStart)
{
if (distSqr > 1)
atten = 0;
else
atten *= 1 - (distSqr - ToZeroFadeStart) / (1 - ToZeroFadeStart);
}
return atten;
}
public static float LegacyUnityFalloff(float normalizedDistance)
{
float clampedDist = math.clamp(normalizedDistance, 0.0f, 1.0f);
return LightAttenuateNormalized(clampedDist * clampedDist);
}
public static float SmoothDistanceAttenuation(float squaredDistance, float invSqrAttenuationRadius)
{
float factor = squaredDistance * invSqrAttenuationRadius;
float smoothFactor = math.saturate(1.0f - factor * factor);
return smoothFactor * smoothFactor;
}
public static float InverseSquaredFalloffSmooth(float squaredDistance, float invSqrAttenuationRadius)
{
float attenuation = 1.0f / (math.max(DistThresholdSqr, squaredDistance));
// Non physically based hack to limit light influence to attenuationRadius. As we approach the range we fade out the light.
return attenuation * SmoothDistanceAttenuation(squaredDistance, invSqrAttenuationRadius);
}
public static float InverseSquaredFalloff(float squaredDistance)
{
return 1.0f / (math.max(DistThresholdSqr, squaredDistance));
}
public static float[] BuildLightFalloffLUTs(LightFalloffDesc[] lightFalloffDescs, uint lightFalloffLUTLength = 1024)
{
List<float> lightFalloffData = new();
foreach (var lightFalloffDesc in lightFalloffDescs)
{
float range = lightFalloffDesc.LUTRange;
switch (lightFalloffDesc.FalloffType)
{
case Experimental.GlobalIllumination.FalloffType.InverseSquaredNoRangeAttenuation:
{
for (uint k = 0; k < lightFalloffLUTLength; ++k)
{
float normalizedTableDistance = (float)k / (float)(lightFalloffLUTLength - 1);
float distance = range * normalizedTableDistance;
float value = InverseSquaredFalloff(distance * distance);
lightFalloffData.Add(value);
}
}
break;
case Experimental.GlobalIllumination.FalloffType.InverseSquared:
{
float invSqrAttenuationRadius = 1.0f / math.max(DistThresholdSqr, range * range);
for (uint k = 0; k < lightFalloffLUTLength; ++k)
{
float normalizedTableDistance = (float)k / (float)(lightFalloffLUTLength - 1);
float distance = range * normalizedTableDistance;
float value = InverseSquaredFalloffSmooth(distance * distance, invSqrAttenuationRadius);
lightFalloffData.Add(value);
}
}
break;
case Experimental.GlobalIllumination.FalloffType.Linear:
{
for (uint k = 0; k < lightFalloffLUTLength; ++k)
{
float linear = 1.0f - ((float)k / (float)(lightFalloffLUTLength - 1));
lightFalloffData.Add(linear);
}
}
break;
case Experimental.GlobalIllumination.FalloffType.Legacy:
default:
{
for (uint k = 0; k < lightFalloffLUTLength; ++k)
{
float normalizedTableDistance = (float)k / (float)(lightFalloffLUTLength - 1);
float value = LegacyUnityFalloff(normalizedTableDistance);
lightFalloffData.Add(value);
}
}
break;
}
// Change the last value to 0 to limit the light influence to the range
lightFalloffData[^1] = 0.0f;
}
return lightFalloffData.ToArray();
}
}
}