Files
Shopping_UnityVR/Packages/com.baddog.rendering.arealight/Runtime/Passes/AreaShadowUtils.cs
2026-04-16 04:58:10 +09:00

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);
}
}
}