阴影纹理判断深度那些我们都懂。我的疑惑是对于平行光,如何确定阴影贴图要覆盖的范围。
豆包回答:
左右 / 上下范围 :由 Shadow Distance 和主相机的视场角决定,以主相机为中心,向平行光方向扩展出一个覆盖主相机视锥在 Shadow Distance 内所有区域的立方体,确保主相机能看到的范围内,阴影都能被正确生成。
我感觉就是对场景的所有模型都判断深度,对于一个平行光方向,只用生成一次阴影纹理就能一直使用。如果转动光源,就会看见右下角有Global illumination processing,可能就是在重新生成ShadowMap。
豆包提供了这个脚本,用于标识平行光源阴影视锥。
cs
using UnityEngine;
using UnityEditor;
[RequireComponent(typeof(Light))]
public class DirectionalShadowFrustumVisualizer : MonoBehaviour
{
[Header("可视化设置")]
[Tooltip("视锥线条颜色")]
public Color frustumColor = Color.cyan;
[Tooltip("线条宽度(仅Scene视图生效)")]
public float lineWidth = 2f;
private Light _dirLight;
private Camera _mainCamera;
// 视锥的8个顶点
private Vector3[] _frustumCorners = new Vector3[8];
private void OnEnable()
{
_dirLight = GetComponent<Light>();
// 校验是否为平行光
if (_dirLight.type != LightType.Directional)
{
Debug.LogError("该脚本仅支持平行光!");
enabled = false;
return;
}
_mainCamera = Camera.main;
}
// Scene视图绘制视锥
private void OnDrawGizmos()
{
if (!_dirLight || !_mainCamera) return;
// 获取全局Shadow Distance
float shadowDistance = QualitySettings.shadowDistance;
// 获取平行光的阴影近裁剪面
float shadowNearPlane = _dirLight.shadowNearPlane;
// 1. 计算主相机在Shadow Distance范围内的视锥投影边界
CalculateMainCameraFrustumBounds(shadowDistance, out Vector3 camFrustumMin, out Vector3 camFrustumMax);
// 2. 计算平行光虚拟相机的正交视锥参数
Vector3 lightDir = _dirLight.transform.forward;
// 视锥中心:主相机位置 + 平行光方向 * 阴影距离的一半
Vector3 frustumCenter = _mainCamera.transform.position + lightDir * shadowDistance * 0.5f;
// 视锥尺寸:基于主相机视锥的宽度、高度,以及阴影近/远裁剪面的距离
Vector3 frustumSize = new Vector3(
camFrustumMax.x - camFrustumMin.x,
camFrustumMax.y - camFrustumMin.y,
shadowDistance - shadowNearPlane
);
// 3. 计算视锥的8个顶点
BuildFrustumCorners(frustumCenter, lightDir, frustumSize, shadowNearPlane);
// 4. 绘制视锥线条
Gizmos.color = frustumColor;
Handles.color = frustumColor;
//Handles.lineThickness = lineWidth;
// 绘制视锥的12条边
DrawLine(0, 1);
DrawLine(1, 3);
DrawLine(3, 2);
DrawLine(2, 0);
DrawLine(4, 5);
DrawLine(5, 7);
DrawLine(7, 6);
DrawLine(6, 4);
DrawLine(0, 4);
DrawLine(1, 5);
DrawLine(2, 6);
DrawLine(3, 7);
}
// 计算主相机在指定距离下的视锥投影边界
private void CalculateMainCameraFrustumBounds(float distance, out Vector3 min, out Vector3 max)
{
float halfFOV = _mainCamera.fieldOfView * 0.5f * Mathf.Deg2Rad;
float aspect = _mainCamera.aspect;
float halfHeight = Mathf.Tan(halfFOV) * distance;
float halfWidth = halfHeight * aspect;
min = new Vector3(-halfWidth, -halfHeight, distance);
max = new Vector3(halfWidth, halfHeight, distance);
// 转换到世界空间
min = _mainCamera.transform.TransformVector(min);
max = _mainCamera.transform.TransformVector(max);
}
// 构建正交视锥的8个顶点
private void BuildFrustumCorners(Vector3 center, Vector3 lightDir, Vector3 size, float nearPlane)
{
Vector3 halfSize = size * 0.5f;
Vector3 up = Vector3.up;
Vector3 right = Vector3.Cross(lightDir, up).normalized;
up = Vector3.Cross(right, lightDir).normalized;
// 近裁剪面4个顶点
_frustumCorners[0] = center - right * halfSize.x - up * halfSize.y - lightDir * (halfSize.z - nearPlane);
_frustumCorners[1] = center + right * halfSize.x - up * halfSize.y - lightDir * (halfSize.z - nearPlane);
_frustumCorners[2] = center - right * halfSize.x + up * halfSize.y - lightDir * (halfSize.z - nearPlane);
_frustumCorners[3] = center + right * halfSize.x + up * halfSize.y - lightDir * (halfSize.z - nearPlane);
// 远裁剪面4个顶点
_frustumCorners[4] = center - right * halfSize.x - up * halfSize.y + lightDir * halfSize.z;
_frustumCorners[5] = center + right * halfSize.x - up * halfSize.y + lightDir * halfSize.z;
_frustumCorners[6] = center - right * halfSize.x + up * halfSize.y + lightDir * halfSize.z;
_frustumCorners[7] = center + right * halfSize.x + up * halfSize.y + lightDir * halfSize.z;
}
// 绘制单条线段
private void DrawLine(int indexA, int indexB)
{
Handles.DrawLine(_frustumCorners[indexA], _frustumCorners[indexB]);
}
}
动了一下相机,视锥会跟着变化,规则挺复杂。
