332 lines
12 KiB
C#
332 lines
12 KiB
C#
using Unity.Mathematics;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.Rendering.Universal;
|
|
|
|
namespace BadDog.Rendering.AreaLight
|
|
{
|
|
public static class AreaShadowUtils
|
|
{
|
|
internal const int k_MinimumAreaLightShadowResolution = 16;
|
|
internal const float k_Sqrt2 = 1.4142135623730950488016887242097f;
|
|
|
|
public const float k_DefaultAreaLightShadowCone = 120.0f;
|
|
public const float k_MinAreaLightShadowCone = 90.0f;
|
|
public const float k_MaxAreaLightShadowCone = 179.0f;
|
|
public const float k_MinShadowNearPlane = 0.01f;
|
|
public const float k_MaxShadowNearPlane = 10.0f;
|
|
|
|
public static bool IsValidShadowCastingLight(LightData lightData, int index, LightType lightType,
|
|
LightShadows lightShadows, float shadowStrength)
|
|
{
|
|
if (index == lightData.mainLightIndex)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (lightType != LightType.Rectangle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return lightShadows != LightShadows.None && shadowStrength > 0f;
|
|
}
|
|
|
|
public static bool IsValidShadowCastingLight(in VisibleLight visibleLight, int mainLightIndex, int visibleLightIndex)
|
|
{
|
|
if (visibleLightIndex == mainLightIndex)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (visibleLight.lightType != LightType.Rectangle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Light light = visibleLight.light;
|
|
if (!BGAreaLightManager.TryGetRegisteredAreaLight(light, out _))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return CanLightCastShadows(light);
|
|
}
|
|
|
|
public static bool CanLightCastShadows(Light light)
|
|
{
|
|
if (light == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return light.shadows != LightShadows.None && light.shadowStrength > 0f;
|
|
}
|
|
|
|
public static int MinimalAreaLightShadowResolution()
|
|
{
|
|
return k_MinimumAreaLightShadowResolution;
|
|
}
|
|
|
|
public static bool TryGetAreaLightParameters(Light light, out float shadowCone, out Vector2 areaSize)
|
|
{
|
|
shadowCone = k_DefaultAreaLightShadowCone;
|
|
areaSize = Vector2.one;
|
|
|
|
if (light == null || light.type != LightType.Rectangle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!BGAreaLightManager.TryGetRegisteredAreaLight(light, out var areaLight))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
areaSize = areaLight.GetSize();
|
|
shadowCone = Mathf.Clamp(areaLight.GetShadowCone(), k_MinAreaLightShadowCone, k_MaxAreaLightShadowCone);
|
|
|
|
return true;
|
|
}
|
|
|
|
public static float GetAreaLightOffsetForShadows(Vector2 areaSize, float coneAngle)
|
|
{
|
|
float halfMinSize = Mathf.Min(areaSize.x, areaSize.y) * 0.5f;
|
|
float halfAngle = coneAngle * 0.5f;
|
|
float cotanHalfAngle = 1.0f / Mathf.Tan(halfAngle * Mathf.Deg2Rad);
|
|
float offset = halfMinSize * cotanHalfAngle;
|
|
|
|
return -offset;
|
|
}
|
|
|
|
public static void ExtractRectangleAreaLightMatrix(
|
|
VisibleLight visibleLight,
|
|
float forwardOffset,
|
|
float areaLightShadowCone,
|
|
float shadowNearPlane,
|
|
Vector2 areaSize,
|
|
int shadowResolution,
|
|
float normalBiasMax,
|
|
bool reverseZ,
|
|
out Matrix4x4 shadowMatrix,
|
|
out Matrix4x4 viewMatrix,
|
|
out Matrix4x4 projMatrix,
|
|
out ShadowSplitData splitData)
|
|
{
|
|
Vector2 viewportSize = new Vector2(shadowResolution, shadowResolution);
|
|
float aspectRatio = Mathf.Approximately(areaSize.y, 0f) ? 1.0f : areaSize.x / areaSize.y;
|
|
float spotAngle = areaLightShadowCone;
|
|
visibleLight.spotAngle = spotAngle;
|
|
float guardAngle = CalcGuardAnglePerspective(visibleLight.spotAngle, viewportSize.x, 1, normalBiasMax,
|
|
180.0f - visibleLight.spotAngle);
|
|
|
|
shadowMatrix = ExtractSpotLightMatrixInternal(
|
|
visibleLight,
|
|
forwardOffset,
|
|
visibleLight.spotAngle,
|
|
shadowNearPlane,
|
|
guardAngle,
|
|
aspectRatio,
|
|
reverseZ,
|
|
out viewMatrix,
|
|
out projMatrix,
|
|
out splitData);
|
|
}
|
|
|
|
public static Matrix4x4 GetShadowTransform(Matrix4x4 proj, Matrix4x4 view)
|
|
{
|
|
if (SystemInfo.usesReversedZBuffer)
|
|
{
|
|
proj.m20 = -proj.m20;
|
|
proj.m21 = -proj.m21;
|
|
proj.m22 = -proj.m22;
|
|
proj.m23 = -proj.m23;
|
|
}
|
|
|
|
Matrix4x4 worldToShadow = proj * view;
|
|
|
|
Matrix4x4 textureScaleAndBias = Matrix4x4.identity;
|
|
textureScaleAndBias.m00 = 0.5f;
|
|
textureScaleAndBias.m11 = 0.5f;
|
|
textureScaleAndBias.m22 = 0.5f;
|
|
textureScaleAndBias.m03 = 0.5f;
|
|
textureScaleAndBias.m13 = 0.5f;
|
|
textureScaleAndBias.m23 = 0.5f;
|
|
|
|
return textureScaleAndBias * worldToShadow;
|
|
}
|
|
|
|
internal static bool FastApproximately(float a, float b)
|
|
{
|
|
return Mathf.Abs(a - b) < 0.000001f;
|
|
}
|
|
|
|
internal static bool FastApproximately(Vector4 a, Vector4 b)
|
|
{
|
|
return FastApproximately(a.x, b.x)
|
|
&& FastApproximately(a.y, b.y)
|
|
&& FastApproximately(a.z, b.z)
|
|
&& FastApproximately(a.w, b.w);
|
|
}
|
|
|
|
|
|
static Matrix4x4 ExtractSpotLightMatrixInternal(
|
|
VisibleLight vl,
|
|
float forwardOffset,
|
|
float spotAngle,
|
|
float nearPlane,
|
|
float guardAngle,
|
|
float aspectRatio,
|
|
bool reverseZ,
|
|
out Matrix4x4 view,
|
|
out Matrix4x4 proj,
|
|
out ShadowSplitData splitData)
|
|
{
|
|
splitData = new ShadowSplitData();
|
|
splitData.cullingSphere.Set(0.0f, 0.0f, 0.0f, float.NegativeInfinity);
|
|
splitData.cullingPlaneCount = 6;
|
|
|
|
Matrix4x4 localToWorldOffset = vl.localToWorldMatrix;
|
|
CoreMatrixUtils.MatrixTimesTranslation(ref localToWorldOffset, Vector3.forward * forwardOffset);
|
|
view = localToWorldOffset.inverse;
|
|
view.m20 *= -1;
|
|
view.m21 *= -1;
|
|
view.m22 *= -1;
|
|
view.m23 *= -1;
|
|
|
|
proj = ExtractSpotLightProjectionMatrix(vl.range - forwardOffset, spotAngle, nearPlane - forwardOffset,
|
|
aspectRatio, guardAngle);
|
|
|
|
Matrix4x4 deviceProjMatrix = GetGPUProjectionMatrix(proj, false, reverseZ);
|
|
Matrix4x4 deviceViewProj = CoreMatrixUtils.MultiplyPerspectiveMatrix(deviceProjMatrix, view);
|
|
|
|
splitData.cullingMatrix = deviceViewProj;
|
|
splitData.cullingNearPlane = nearPlane - forwardOffset;
|
|
|
|
return GetShadowTransform(proj, view);
|
|
}
|
|
|
|
static float CalcGuardAnglePerspective(float angleInDeg, float resolution, float filterWidth,
|
|
float normalBiasMax, float guardAngleMaxInDeg)
|
|
{
|
|
float angleInRad = angleInDeg * 0.5f * Mathf.Deg2Rad;
|
|
float res = 2.0f / resolution;
|
|
float texelSize = math.cos(angleInRad) * res;
|
|
float beta = normalBiasMax * texelSize * k_Sqrt2;
|
|
float guardAngle = math.atan(beta);
|
|
texelSize = math.tan(angleInRad + guardAngle) * res;
|
|
guardAngle = math.atan((resolution + math.ceil(filterWidth)) * texelSize * 0.5f) * 2.0f * Mathf.Rad2Deg -
|
|
angleInDeg;
|
|
guardAngle *= 2.0f;
|
|
|
|
return guardAngle < guardAngleMaxInDeg ? guardAngle : guardAngleMaxInDeg;
|
|
}
|
|
|
|
static Matrix4x4 ExtractSpotLightProjectionMatrix(float range, float spotAngle, float nearPlane,
|
|
float aspectRatio, float guardAngle)
|
|
{
|
|
float fov = spotAngle + guardAngle;
|
|
float nearZ = Mathf.Max(nearPlane, k_MinShadowNearPlane);
|
|
|
|
float e = 1.0f / Mathf.Tan(fov / 180.0f * Mathf.PI / 2.0f);
|
|
float a = aspectRatio;
|
|
float n = nearZ;
|
|
float f = n + range;
|
|
|
|
Matrix4x4 mat = new Matrix4x4();
|
|
|
|
if (a < 1)
|
|
{
|
|
mat.m00 = e;
|
|
mat.m11 = e * a;
|
|
}
|
|
else
|
|
{
|
|
mat.m00 = e / a;
|
|
mat.m11 = e;
|
|
}
|
|
|
|
mat.m22 = -(f + n) / (f - n);
|
|
mat.m23 = -2 * f * n / (f - n);
|
|
mat.m32 = -1;
|
|
|
|
return mat;
|
|
}
|
|
|
|
static Matrix4x4 GetGPUProjectionMatrix(Matrix4x4 projectionMatrix, bool invertY, bool reverseZ)
|
|
{
|
|
float4x4 gpuProjectionMatrix = math.transpose(projectionMatrix);
|
|
if (invertY)
|
|
{
|
|
gpuProjectionMatrix.c1 = -gpuProjectionMatrix.c1;
|
|
}
|
|
|
|
float multiplier = reverseZ ? -0.5f : 0.5f;
|
|
gpuProjectionMatrix.c2 = gpuProjectionMatrix.c2 * multiplier + gpuProjectionMatrix.c3 * 0.5f;
|
|
|
|
return math.transpose(gpuProjectionMatrix);
|
|
}
|
|
|
|
internal static Vector4 GetRectangleLightShadowBias(ref VisibleLight shadowLight, ref ShadowData shadowData, float shadowResolution, float nearPlane, AreaLightShadowSettings.BGAreaLightShadowMode shadowFilterMode)
|
|
{
|
|
Light unityLight = shadowLight.light;
|
|
|
|
float userDepthBias = 0.0f;
|
|
float userNormalBias = 0.0f;
|
|
|
|
if (unityLight != null)
|
|
{
|
|
userDepthBias = unityLight.shadowBias;
|
|
userNormalBias = unityLight.shadowNormalBias;
|
|
|
|
if (BGAreaLightManager.TryGetRegisteredAreaLight(unityLight, out var areaLight))
|
|
{
|
|
userDepthBias = areaLight.GetShadowDepthBias();
|
|
userNormalBias = areaLight.GetShadowNormalBias();
|
|
}
|
|
}
|
|
|
|
float spotLikeAngle = k_DefaultAreaLightShadowCone;
|
|
Vector2 size = Vector2.one;
|
|
if (TryGetAreaLightParameters(shadowLight.light, out float areaCone, out size))
|
|
{
|
|
spotLikeAngle = areaCone;
|
|
}
|
|
|
|
float aspectRatio = Mathf.Approximately(size.y, 0f) ? 1.0f : (size.x / size.y);
|
|
float guardAngle = CalcGuardAnglePerspective(spotLikeAngle, shadowResolution, 1, userNormalBias, 180.0f - spotLikeAngle);
|
|
|
|
float actualNearPlane = Mathf.Max(nearPlane, k_MinShadowNearPlane);
|
|
Matrix4x4 proj = ExtractSpotLightProjectionMatrix(
|
|
shadowLight.range,
|
|
spotLikeAngle,
|
|
actualNearPlane,
|
|
aspectRatio,
|
|
guardAngle);
|
|
Matrix4x4 deviceProjectionYFlip = GetGPUProjectionMatrix(proj, true, SystemInfo.usesReversedZBuffer);
|
|
|
|
float worldTexelSize = 2.0f / Mathf.Max(deviceProjectionYFlip.m00, 1e-6f) / Mathf.Max(shadowResolution, 1.0f) * k_Sqrt2;
|
|
float depthBias = -userDepthBias * worldTexelSize;
|
|
float normalBias = -userNormalBias * worldTexelSize;
|
|
|
|
if (shadowFilterMode != AreaLightShadowSettings.BGAreaLightShadowMode.Off)
|
|
{
|
|
float kernelRadius = shadowFilterMode switch
|
|
{
|
|
AreaLightShadowSettings.BGAreaLightShadowMode.PCF2x2 => 1.5f, // 2x2 PCF
|
|
AreaLightShadowSettings.BGAreaLightShadowMode.Tent5x5 => 2.5f, // 5x5 Tent
|
|
AreaLightShadowSettings.BGAreaLightShadowMode.Tent7x7 => 3.5f, // 7x7 Tent
|
|
AreaLightShadowSettings.BGAreaLightShadowMode.PCSS => 3.5f, // PCSS (similar to 7x7)
|
|
_ => 2.5f
|
|
};
|
|
|
|
depthBias *= kernelRadius;
|
|
normalBias *= kernelRadius;
|
|
}
|
|
|
|
return new Vector4(depthBias, normalBias, (float)LightType.Rectangle, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
|