namespace UnityEngine.Rendering.Universal { public class DebugCascadeShadow : MonoBehaviour { public Camera targetCamera; public bool showCullingSpheres = true; public bool showCameraFrustums = true; public bool showCascadeFrustums = true; [Range(0f, 4f)] public int showMaxCascade = 4; public Color[] cascadeColors = new Color[] { new Color(1, 0, 0, 0.5f), // 红 new Color(0, 1, 0, 0.5f), // 绿 new Color(0, 0.5f, 1, 0.5f), // 蓝 new Color(1, 1, 0, 0.5f) // 黄 }; public static Vector3 cascadeSplits; public static Vector4[] cullingSpheres = new Vector4[4]; public static Matrix4x4[] cascadeVps = new Matrix4x4[4]; Plane[][] planes = new Plane[][] { new Plane[] { new(),new(),new(),new(),new(),new() }, new Plane[] { new(),new(),new(),new(),new(),new() }, new Plane[] { new(),new(),new(),new(),new(),new() }, new Plane[] { new(),new(),new(),new(),new(),new() }, }; void ExtractFrustumPlanes(in Matrix4x4 VP, in Plane[] planes) { // Left clipping plane planes[0] = new Plane( new Vector3(VP.m30 + VP.m00, VP.m31 + VP.m01, VP.m32 + VP.m02), VP.m33 + VP.m03); // Right clipping plane planes[1] = new Plane( new Vector3(VP.m30 - VP.m00, VP.m31 - VP.m01, VP.m32 - VP.m02), VP.m33 - VP.m03); // Top clipping plane planes[2] = new Plane( new Vector3(VP.m30 - VP.m10, VP.m31 - VP.m11, VP.m32 - VP.m12), VP.m33 - VP.m13); // Bottom clipping plane planes[3] = new Plane( new Vector3(VP.m30 + VP.m10, VP.m31 + VP.m11, VP.m32 + VP.m12), VP.m33 + VP.m13); // Near clipping plane planes[4] = new Plane( new Vector3(VP.m20, VP.m21, VP.m22), VP.m23); // Far clipping plane planes[5] = new Plane( new Vector3(VP.m30 - VP.m20, VP.m31 - VP.m21, VP.m32 - VP.m22), VP.m33 - VP.m23); } protected static void ExtractFrustumPlanesStereo(in Matrix4x4 leftVP, in Matrix4x4 rightVP, in Plane[] planes) { // Left clipping plane planes[0] = new Plane( new Vector3(leftVP.m30 + leftVP.m00, leftVP.m31 + leftVP.m01, leftVP.m32 + leftVP.m02), leftVP.m33 + leftVP.m03); // Right clipping plane planes[1] = new Plane( new Vector3(rightVP.m30 - rightVP.m00, rightVP.m31 - rightVP.m01, rightVP.m32 - rightVP.m02), rightVP.m33 - rightVP.m03); // Top clipping plane planes[2] = new Plane( new Vector3(leftVP.m30 - leftVP.m10, leftVP.m31 - leftVP.m11, leftVP.m32 - leftVP.m12), leftVP.m33 - leftVP.m13); // Bottom clipping plane planes[3] = new Plane( new Vector3(leftVP.m30 + leftVP.m10, leftVP.m31 + leftVP.m11, leftVP.m32 + leftVP.m12), leftVP.m33 + leftVP.m13); // Near clipping plane planes[4] = new Plane( new Vector3(leftVP.m20, leftVP.m21, leftVP.m22), leftVP.m23); // Far clipping plane planes[5] = new Plane( new Vector3(leftVP.m30 - leftVP.m20, leftVP.m31 - leftVP.m21, leftVP.m32 - leftVP.m22), leftVP.m33 - leftVP.m23); } protected void DrawFrustum(in Matrix4x4 vp) { Matrix4x4 clip2world = vp.inverse; Vector4[] corners = { new(-1, -1, -1, 1), new(1, -1, -1, 1), new(1, 1, -1, 1), new(-1, 1, -1, 1), new(-1, -1, 1, 1), new(1, -1, 1, 1), new(1, 1, 1, 1), new(-1, 1, 1, 1), }; for (int i = 0; i < corners.Length; i++) { corners[i] = clip2world * corners[i]; corners[i] /= corners[i].w; } Gizmos.DrawLine(corners[0], corners[1]); Gizmos.DrawLine(corners[1], corners[2]); Gizmos.DrawLine(corners[2], corners[3]); Gizmos.DrawLine(corners[3], corners[0]); Gizmos.DrawLine(corners[4], corners[5]); Gizmos.DrawLine(corners[5], corners[6]); Gizmos.DrawLine(corners[6], corners[7]); Gizmos.DrawLine(corners[7], corners[4]); Gizmos.DrawLine(corners[0], corners[4]); Gizmos.DrawLine(corners[1], corners[5]); Gizmos.DrawLine(corners[2], corners[6]); Gizmos.DrawLine(corners[3], corners[7]); } void OnDrawGizmos() { if (targetCamera == null) return; if (showCullingSpheres) { for (int i = 0; i < cullingSpheres.Length; i++) { if (i > showMaxCascade - 1) { break; } if (cullingSpheres[i].w <= 0) continue; Gizmos.color = cascadeColors[i % cascadeColors.Length]; Vector3 sphereCenter = cullingSpheres[i]; float sphereRadius = cullingSpheres[i].w; Gizmos.DrawWireSphere(sphereCenter, sphereRadius); } } if (showCascadeFrustums) { for (int i = 0; i < cascadeVps.Length; i++) { if (i > showMaxCascade - 1) { break; } var vp = cascadeVps[i]; Gizmos.color = cascadeColors[i % cascadeVps.Length]; DrawFrustum(vp); ExtractFrustumPlanes(vp, planes[i]); } } if (showCameraFrustums) { float lastSplit = targetCamera.nearClipPlane; for (int i = 0; i < 3; i++) { if (i > showMaxCascade - 1) { break; } if (cascadeSplits[i] <= 0) break; Gizmos.color = cascadeColors[i % cascadeColors.Length]; DrawCascadeFrustum(targetCamera, lastSplit, cascadeSplits[i]); lastSplit = cascadeSplits[i]; } } } private void DrawCascadeFrustum(Camera cam, float startDist, float endDist) { // 计算视锥体角点 Vector3[] nearCorners = GetFrustumCorners(cam, startDist); Vector3[] farCorners = GetFrustumCorners(cam, endDist); // 绘制视锥体线框 Gizmos.DrawLine(nearCorners[0], farCorners[0]); Gizmos.DrawLine(nearCorners[1], farCorners[1]); Gizmos.DrawLine(nearCorners[2], farCorners[2]); Gizmos.DrawLine(nearCorners[3], farCorners[3]); // 绘制近/远平面 DrawFrustumPlane(nearCorners); DrawFrustumPlane(farCorners); } private Vector3[] GetFrustumCorners(Camera cam, float distance) { Vector3[] corners = new Vector3[4]; Transform camTransform = cam.transform; float halfFOV = cam.fieldOfView * 0.5f * Mathf.Deg2Rad; float aspect = cam.aspect; float height = distance * Mathf.Tan(halfFOV); float width = height * aspect; // 计算相对于相机的角点 corners[0] = camTransform.forward * distance + camTransform.up * height - camTransform.right * width; // 左下 corners[1] = camTransform.forward * distance + camTransform.up * height + camTransform.right * width; // 右下 corners[2] = camTransform.forward * distance - camTransform.up * height + camTransform.right * width; // 右上 corners[3] = camTransform.forward * distance - camTransform.up * height - camTransform.right * width; // 左上 // 转换到世界坐标 for (int i = 0; i < 4; i++) { corners[i] = camTransform.position + corners[i]; } return corners; } private void DrawFrustumPlane(Vector3[] corners) { Gizmos.DrawLine(corners[0], corners[1]); Gizmos.DrawLine(corners[1], corners[2]); Gizmos.DrawLine(corners[2], corners[3]); Gizmos.DrawLine(corners[3], corners[0]); } } }