UnityGame/Library/PackageCache/com.unity.render-pipelines.core/Runtime/Utilities/LightUnitUtils.cs
2024-10-27 10:53:47 +03:00

527 lines
20 KiB
C#

using System;
namespace UnityEngine.Rendering
{
/// <summary>
/// Light Unit Utils contains functions and definitions to facilitate conversion between different light intensity units.
/// </summary>
public static class LightUnitUtils
{
static float k_LuminanceToEvFactor => Mathf.Log(100f / ColorUtils.s_LightMeterCalibrationConstant, 2);
static float k_EvToLuminanceFactor => -k_LuminanceToEvFactor;
/// <summary>
/// The solid angle of a full sphere in steradians.
/// </summary>
public const float SphereSolidAngle = 4.0f * Mathf.PI;
/// <summary>
/// Get the unit that light intensity is measured in, for a specific light type.
/// </summary>
/// <param name="lightType">The type of light to get the native light unit for.</param>
/// <returns>The native unit of that light types intensity.</returns>
public static LightUnit GetNativeLightUnit(LightType lightType)
{
switch (lightType)
{
// Punctual lights
case LightType.Spot:
case LightType.Point:
case LightType.Pyramid:
return LightUnit.Candela;
// Directional lights
case LightType.Directional:
case LightType.Box:
return LightUnit.Lux;
// Area lights
case LightType.Rectangle:
case LightType.Disc:
case LightType.Tube:
return LightUnit.Nits;
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>
/// Check if a light types intensity can be converted to/from a light unit.
/// </summary>
/// <param name="lightType">Light type to check.</param>
/// <param name="lightUnit">Unit to check.</param>
/// <returns>True if light unit is supported.</returns>
public static bool IsLightUnitSupported(LightType lightType, LightUnit lightUnit)
{
const int punctualUnits = 1 << (int)LightUnit.Lumen |
1 << (int)LightUnit.Candela |
1 << (int)LightUnit.Lux |
1 << (int)LightUnit.Ev100;
const int directionalUnits = 1 << (int)LightUnit.Lux;
const int areaUnits = 1 << (int)LightUnit.Lumen |
1 << (int)LightUnit.Nits |
1 << (int)LightUnit.Ev100;
int lightUnitFlag = 1 << (int)lightUnit;
switch (lightType)
{
// Punctual lights
case LightType.Point:
case LightType.Spot:
case LightType.Pyramid:
return (lightUnitFlag & punctualUnits) > 0;
// Directional lights
case LightType.Directional:
case LightType.Box:
return (lightUnitFlag & directionalUnits) > 0;
// Area lights
case LightType.Rectangle:
case LightType.Disc:
case LightType.Tube:
return (lightUnitFlag & areaUnits) > 0;
default:
return false;
}
}
/// <summary>
/// Get the solid angle of a Point light.
/// </summary>
/// <returns>4 * Pi steradians.</returns>
public static float GetSolidAngleFromPointLight()
{
return SphereSolidAngle;
}
/// <summary>
/// Get the solid angle of a Spot light.
/// </summary>
/// <param name="spotAngle">The spot angle in degrees.</param>
/// <returns>Solid angle in steradians.</returns>
public static float GetSolidAngleFromSpotLight(float spotAngle)
{
double angle = Math.PI * spotAngle / 180.0;
double solidAngle = 2.0 * Math.PI * (1.0 - Math.Cos(angle * 0.5));
return (float)solidAngle;
}
/// <summary>
/// Get the solid angle of a Pyramid light.
/// </summary>
/// <param name="spotAngle">The spot angle in degrees.</param>
/// <param name="aspectRatio">The aspect ratio of the pyramid.</param>
/// <returns>Solid angle in steradians.</returns>
public static float GetSolidAngleFromPyramidLight(float spotAngle, float aspectRatio)
{
if (aspectRatio < 1.0f)
{
aspectRatio = (float)(1.0 / aspectRatio);
}
double angleA = Math.PI * spotAngle / 180.0;
double length = Math.Tan(0.5 * angleA) * aspectRatio;
double angleB = Math.Atan(length) * 2.0;
double solidAngle = 4.0 * Math.Asin(Math.Sin(angleA * 0.5) * Math.Sin(angleB * 0.5));
return (float)solidAngle;
}
internal static float GetSolidAngle(LightType lightType, bool spotReflector, float spotAngle, float aspectRatio)
{
return lightType switch
{
LightType.Spot => spotReflector ? GetSolidAngleFromSpotLight(spotAngle) : SphereSolidAngle,
LightType.Pyramid => spotReflector ? GetSolidAngleFromPyramidLight(spotAngle, aspectRatio) : SphereSolidAngle,
LightType.Point => GetSolidAngleFromPointLight(),
_ => throw new ArgumentException("Solid angle is undefined for lights of type " + lightType)
};
}
/// <summary>
/// Get the projected surface area of a Rectangle light.
/// </summary>
/// <param name="rectSizeX">The width of the rectangle.</param>
/// <param name="rectSizeY">The height of the rectangle.</param>
/// <returns>Surface area.</returns>
public static float GetAreaFromRectangleLight(float rectSizeX, float rectSizeY)
{
return Mathf.Abs(rectSizeX * rectSizeY) * Mathf.PI;
}
/// <summary>
/// Get the projected surface area of a Rectangle light.
/// </summary>
/// <param name="rectSize">The size of the rectangle.</param>
/// <returns>Projected surface area.</returns>
public static float GetAreaFromRectangleLight(Vector2 rectSize)
{
return GetAreaFromRectangleLight(rectSize.x, rectSize.y);
}
/// <summary>
/// Get the projected surface area of a Disc light.
/// </summary>
/// <param name="discRadius">The radius of the disc.</param>
/// <returns>Projected surface area.</returns>
public static float GetAreaFromDiscLight(float discRadius)
{
return discRadius * discRadius * Mathf.PI * Mathf.PI;
}
/// <summary>
/// Get the projected surface area of a Tube light.
/// </summary>
/// <remarks>Note that Tube lights have no physical surface area.
/// Instead this method returns a value suitable for Nits&lt;=&gt;Lumen unit conversion.</remarks>
/// <param name="tubeLength">The length of the tube.</param>
/// <returns>4 * Pi * (tube length).</returns>
public static float GetAreaFromTubeLight(float tubeLength)
{
// Line lights expect radiance (W / (sr * m^2)) in the shader.
// In the UI, we specify luminous flux (power) in lumens.
// First, it needs to be converted to radiometric units (radian flux, W).
//
// Then we must recall how to compute power from radiance:
//
// radiance = differential_power / (differential_projected_area * differential_solid_angle),
// radiance = differential_power / (differential_area * differential_solid_angle * <N, L>),
// power = Integral{area, Integral{hemisphere, radiance * <N, L>}}.
//
// Unlike line lights, our line lights have no surface area, so the integral becomes:
//
// power = Integral{length, Integral{sphere, radiance}}.
//
// For an isotropic line light, radiance is constant, therefore:
//
// power = length * (4 * Pi) * radiance,
// radiance = power / (length * (4 * Pi)).
return Mathf.Abs(tubeLength) * 4.0f * Mathf.PI;
}
/// <summary>
/// Convert intensity in Lumen to Candela.
/// </summary>
/// <param name="lumen">Intensity in Lumen.</param>
/// <param name="solidAngle">Light solid angle in steradians.</param>
/// <returns>Intensity in Candela.</returns>
public static float LumenToCandela(float lumen, float solidAngle)
{
return lumen / solidAngle;
}
/// <summary>
/// Convert intensity in Candela to Lumen.
/// </summary>
/// <param name="candela">Intensity in Candela.</param>
/// <param name="solidAngle">Light solid angle in steradians.</param>
/// <returns>Intensity in Lumen.</returns>
public static float CandelaToLumen(float candela, float solidAngle)
{
return candela * solidAngle;
}
/// <summary>
/// Convert intensity in Lumen to Nits.
/// </summary>
/// <param name="lumen">Intensity in Lumen.</param>
/// <param name="area">Projected surface area of the light source.</param>
/// <returns>Intensity in Nits.</returns>
public static float LumenToNits(float lumen, float area)
{
return lumen / area;
}
/// <summary>
/// Convert intensity in Nits to Lumen.
/// </summary>
/// <param name="nits">Intensity in Nits.</param>
/// <param name="area">Projected surface area of the light source.</param>
/// <returns>Intensity in Lumen.</returns>
public static float NitsToLumen(float nits, float area)
{
return nits * area;
}
/// <summary>
/// Convert intensity in Lux to Candela.
/// </summary>
/// <param name="lux">Intensity in Lux.</param>
/// <param name="distance">Distance between light and surface.</param>
/// <returns>Intensity in Candela.</returns>
public static float LuxToCandela(float lux, float distance)
{
return lux * (distance * distance);
}
/// <summary>
/// Convert intensity in Candela to Lux.
/// </summary>
/// <param name="candela">Intensity in Lux.</param>
/// <param name="distance">Distance between light and surface.</param>
/// <returns>Intensity in Lux.</returns>
public static float CandelaToLux(float candela, float distance)
{
return candela / (distance * distance);
}
/// <summary>
/// Convert intensity in Ev100 to Nits.
/// </summary>
/// <param name="ev100">Intensity in Ev100.</param>
/// <returns>Intensity in Nits.</returns>
public static float Ev100ToNits(float ev100)
{
return Mathf.Pow(2.0f, ev100 + k_EvToLuminanceFactor);
}
/// <summary>
/// Convert intensity in Nits to Ev100.
/// </summary>
/// <param name="nits">Intensity in Nits.</param>
/// <returns>Intensity in Ev100.</returns>
public static float NitsToEv100(float nits)
{
return Mathf.Log(nits, 2) + k_LuminanceToEvFactor;
}
/// <summary>
/// Convert intensity in Ev100 to Candela.
/// </summary>
/// <param name="ev100">Intensity in Ev100.</param>
/// <returns>Intensity in Candela.</returns>
public static float Ev100ToCandela(float ev100)
{
return Ev100ToNits(ev100);
}
/// <summary>
/// Convert intensity in Candela to Ev100.
/// </summary>
/// <param name="candela">Intensity in Candela.</param>
/// <returns>Intensity in Ev100.</returns>
public static float CandelaToEv100(float candela)
{
return NitsToEv100(candela);
}
internal static float ConvertIntensityInternal(float intensity, LightUnit fromUnit, LightUnit toUnit,
LightType lightType, float area, float luxAtDistance, float solidAngle)
{
if (!IsLightUnitSupported(lightType, fromUnit) || !IsLightUnitSupported(lightType, toUnit))
{
throw new ArgumentException("Converting " + fromUnit + " to " + toUnit
+ " is undefined for lights of type " + lightType);
}
if (fromUnit == toUnit)
{
return intensity;
}
switch (fromUnit)
{
case LightUnit.Lumen:
{
switch (toUnit)
{
case LightUnit.Candela:
{
// Lumen => Candela:
return LumenToCandela(intensity, solidAngle);
}
case LightUnit.Lux:
{
// Lumen => Candela => Lux
float candela = LumenToCandela(intensity, solidAngle);
return CandelaToLux(candela, luxAtDistance);
}
case LightUnit.Nits:
{
// Lumen => Nits
return LumenToNits(intensity, area);
}
case LightUnit.Ev100:
{
// Lumen => Candela/Nits => Ev100
float candelaNits = lightType switch
{
LightType.Point or LightType.Spot or LightType.Pyramid =>
LumenToCandela(intensity, solidAngle),
LightType.Rectangle or LightType.Disc or LightType.Tube =>
LumenToNits(intensity, area),
_ =>
throw new ArgumentException("Converting from Lumen to Ev100 is undefined for light type "
+ lightType)
};
return NitsToEv100(candelaNits);
}
default:
throw new ArgumentOutOfRangeException(nameof(toUnit), toUnit, null);
}
}
case LightUnit.Candela:
{
switch (toUnit)
{
case LightUnit.Lumen:
{
// Candela => Lumen
return CandelaToLumen(intensity, solidAngle);
}
case LightUnit.Lux:
{
// Candela => Lux
return CandelaToLux(intensity, luxAtDistance);
}
case LightUnit.Ev100:
{
// Candela => Ev100
return NitsToEv100(intensity);
}
default:
throw new ArgumentOutOfRangeException(nameof(toUnit), toUnit, null);
}
}
case LightUnit.Lux:
{
switch (toUnit)
{
case LightUnit.Lumen:
{
// Lux => Candela => Lumen
float candela = LuxToCandela(intensity, luxAtDistance);
return CandelaToLumen(candela, solidAngle);
}
case LightUnit.Candela:
{
// Lux => Candela
return LuxToCandela(intensity, luxAtDistance);
}
case LightUnit.Ev100:
{
// Lux => Candela => Ev100
float candela = LuxToCandela(intensity, luxAtDistance);
return NitsToEv100(candela);
}
default:
throw new ArgumentOutOfRangeException(nameof(toUnit), toUnit, null);
}
}
case LightUnit.Nits:
{
switch (toUnit)
{
case LightUnit.Lumen:
{
return NitsToLumen(intensity, area);
}
case LightUnit.Ev100:
{
return NitsToEv100(intensity);
}
default:
throw new ArgumentOutOfRangeException(nameof(toUnit), toUnit, null);
}
}
case LightUnit.Ev100:
{
switch (toUnit)
{
case LightUnit.Lumen:
{
// Ev100 => Candela/Nits => Lumen
float candelaOrNits = Ev100ToNits(intensity);
return lightType switch
{
LightType.Point or LightType.Spot or LightType.Pyramid =>
CandelaToLumen(candelaOrNits, solidAngle),
LightType.Rectangle or LightType.Disc or LightType.Tube =>
NitsToLumen(candelaOrNits, area),
_ =>
throw new ArgumentException("Converting from Lumen to Ev100 is undefined for light type "
+ lightType)
};
}
case LightUnit.Nits:
case LightUnit.Candela:
{
// Ev100 => Candela/Nits
return Ev100ToNits(intensity);
}
case LightUnit.Lux:
{
// Ev100 => Candela => Lux
float candela = Ev100ToNits(intensity);
return CandelaToLux(candela, luxAtDistance);
}
default:
throw new ArgumentOutOfRangeException(nameof(toUnit), toUnit, null);
}
}
default:
throw new ArgumentOutOfRangeException(nameof(fromUnit), fromUnit, null);
}
}
/// <summary>
/// Convert intensity from one unit to another using the parameters of a given Light.
/// </summary>
/// <param name="light">Light to use parameters from.</param>
/// <param name="intensity">Intensity to be converted.</param>
/// <param name="fromUnit">Unit to convert from.</param>
/// <param name="toUnit">Unit to convert to.</param>
/// <returns>Converted intensity.</returns>
public static float ConvertIntensity(Light light, float intensity, LightUnit fromUnit, LightUnit toUnit)
{
LightType lightType = light.type;
float area = lightType switch
{
LightType.Rectangle => GetAreaFromRectangleLight(light.areaSize),
LightType.Disc => GetAreaFromDiscLight(light.areaSize.x), // Disc radius is stored in areaSize.x
LightType.Tube => GetAreaFromTubeLight(light.areaSize.x), // Tube length is stored in areaSize.x
_ => 0.0f
};
float luxAtDistance = light.luxAtDistance;
float solidAngle = lightType switch
{
LightType.Spot or LightType.Pyramid or LightType.Point => GetSolidAngle(lightType, light.enableSpotReflector,
light.spotAngle, light.areaSize.x), // Pyramid aspect ratio is store in areaSize.x
_ => 0.0f
};
return ConvertIntensityInternal(intensity, fromUnit, toUnit, lightType, area, luxAtDistance, solidAngle);
}
}
}