一般规律是:
方向光 Directional Light:正交投影
聚光灯 Spot Light:透视投影
点光源 Point Light:透视投影 / cube shadow
矩形光 Rect Light:类似聚光/透视投影的 shadow setup
1、方向光
UE 也会用类似思想,但不一定就是这句 GLSL 代码。
你这句:
float bias = max(0.002, 0.01 * (1.0 - dot(normal, LIGHT_DIR)));
意思是:
表面越斜着面对光源,bias 越大
表面越正对光源,bias 越小
方向光 shadow 的核心一句话:
方向光没有真实位置。
UE 会根据当前相机视锥,给每个 cascade 算一个虚拟 shadow 中心,
然后用方向光方向 + 正交投影生成 shadowmap。
整体流程:
主相机视锥
↓
按距离切成多个 cascade
↓
每个 cascade 算一个视锥切片
↓
用切片 8 个角点拟合包围球
↓
包围球中心 = 虚拟 shadow 中心
↓
包围球半径 = 正交投影范围
↓
沿方向光方向渲染 shadowmap
图上看是这样:
主相机视锥,俯视图
Camera
●
\
\ Cascade 0 Cascade 1 Cascade 2
\ 近处,高精度 中距离,中精度 远处,低精度
\-----------|-----------------|----------------------|
\ | | |
\ | | |
\--------|-----------------|----------------------|
Near Split1 Split2 Far
每个 cascade 都有自己的 shadowmap。近处覆盖小,所以清晰;远处覆盖大,所以模糊。
单独看一个 cascade:
某个 cascade 的视锥切片
SplitFar
P4 ------------- P6
\ /
\ /
\ ● / ● = CascadeSphere.Center
\ /
P0 ----- P2
SplitNear
UE 会用这个视锥切片的 8 个角点拟合一个包围球:
SplitFar
P4 ------------- P6
\ ___ /
\ / \ /
\| ● |/ ● = 包围球中心
\ ___ /
P0 ----- P2
SplitNear
这个 ● 就是 UE 的虚拟 shadow 中心。
源码概念上就是:
PreShadowTranslation = -Bounds.Center;
WorldToLight = 根据方向光方向构造的旋转矩阵;
Scales = 1.0f / Bounds.W;
其中:
Bounds.Center = 当前 cascade 的虚拟 shadow 中心
Bounds.W = 包围球半径
然后方向光从自己的方向"看"这个区域:
方向光方向
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
正交 shadow camera 覆盖范围
+--------------------------------+
| |
| cascade 包围球 |
| ___ |
| / \ |
| | ● | |
| \ ___ / |
| |
+--------------------------------+
● = shadow 中心
矩形大小 ≈ Bounds.W * 2
重点是:方向光用正交投影,不靠"灯光距离"决定图片大小。
透视相机:
离得近大,离得远小。
方向光正交相机:
离多远都一样大。
所以 shadowmap 的画面大小由这个决定:
X/Y 范围:Bounds.W,也就是包围球半径
Z 范围:MinSubjectZ / MaxSubjectZ / MinLightW
可以理解成一个盒子:
方向光方向
↓
+-------------------+
| |
| 正交投影盒子 | ← X/Y 决定 shadowmap 覆盖大小
| |
+-------------------+
↑ ↑
Near Far ← Z 决定深度范围
还有一个关键点:视锥切片是锥形 / 梯形,但 shadowmap 是矩形,所以一定会浪费一部分区域。
视锥切片:
+-------------+
\ /
\ /
\ /
+-----+
正交 shadowmap:
+-----------------------+
| _______ |
| / \ |
| | +-------+ | |
| | \ / | |
| | \ / | |
| \___+-+___/ |
+-----------------------+
UE 为什么用包围球,而不是刚好贴合视锥的矩形?
因为稳定。
更紧的 AABB:
精度高,但相机转动时容易抖。
包围球:
浪费一点精度,但相机旋转时更稳定。
所以 UE 的取舍是:
牺牲一部分 shadowmap texel
换取阴影稳定性
最后,UE 还会做 texel snapping:
把 shadow 中心对齐到 shadowmap texel 网格
减少相机移动时的阴影闪烁
最终你可以这样记:
方向光 CSM = 多个正交 shadow camera
每个 cascade:
1. 取主相机视锥的一段
2. 算 8 个角点
3. 拟合包围球
4. 球心作为 shadow 虚拟中心
5. 半径作为正交投影大小
6. 沿方向光方向渲染 shadowmap
7. 做 texel snapping 稳定阴影
总结:

相机视锥 slice
↓
用球包住这个 slice
↓
球心 C
↓
方向光确定 LightSpace 的方向
↓
用一个沿方向光摆正的正交盒子包住这个球
↓
正交投影生成 shadowmap
相机决定要阴影的 cascade 区域,UE 给它套球;方向光决定 LightSpace;再用 LightSpace 下的正交盒子包住这个球,渲染 shadowmap。
点光源 Shadow 怎么生成
传统做法是 cubemap shadow。
可以理解为:
在点光源位置放 6 个 90° 相机,分别朝六个方向拍深度图。
+X
-X
+Y
-Y
+Z
-Z
每个 face:
ViewOrigin = LightPosition;
ViewDirection = CubeFaceDirection;
FOV = 90°;
Near = small value;
Far = LightRadius;
最后得到的是一张 cubemap shadowmap。
3. Shadowmap 里存什么
点光源 shadowmap 存的是:
从点光源出发,沿某个方向看到的最近遮挡物距离。
P 像素
/
/
█ 遮挡物
/
● PointLight
如果遮挡物比像素更靠近灯,那么像素在阴影里。
Spot Light 可以理解成:点光源 + 只照一个锥形范围。
它比点光源简单,因为它不需要 6 张 cubemap,只需要从灯的位置朝一个方向拍 1 张透视 shadowmap。
1. Spotlight 的形状
Spot Light 有位置,也有方向。
SpotLight
●
/|\
/ | \
/ | \
/ | \
/____|____\
光照锥体
它只照这个锥体里面的物体。
2. Spotlight Shadow 怎么生成
把 spotlight 当成一个相机:
ViewOrigin = LightPosition;
ViewDirection = LightDirection;
FOV = SpotOuterConeAngle * 2;
Near = small value;
Far = LightRadius;
然后从这个"灯光相机"拍一张深度图。
● SpotLight
/ \
/ \
/ \
/_______\
这张图就是 spotlight shadowmap
所以 spotlight shadow 是:
1 张透视投影 shadowmap。
3. Shadowmap 里存什么
shadowmap 里存的是:
从 spotlight 出发,在锥体方向里看到的最近遮挡物深度。
● SpotLight
\
\
█ 遮挡物
\
P 像素
如果遮挡物比 P 更靠近灯,那么 P 在阴影里。
RectLight 是什么
RectLight 是一个有宽高的矩形面积光:
┌──────────┐
│ RectLight│
└──────────┘
↓
它不是一个点,而是一整块面在发光。
2. 对一个像素 P 怎么算贡献
对像素 P 来说,RectLight 的贡献主要看:
这个矩形光源在 P 看来占了多大视角
也就是立体角。
P 越近、越正对矩形:
看到的矩形越大
光照越强
P 越远、越偏离中线:
看到的矩形越小/越斜
光照越弱
3. UE 不是逐点采样
UE 不会这样暴力算:
for 每个矩形采样点:
累加对 P 的光照
而是:
取矩形四个角
↓
从 P 指向四个角
↓
normalize 到单位球面
↓
形成一个球面四边形
↓
用解析近似算整体贡献
一句话:
UE 用四个角描述整个矩形在 P 眼里的范围,而不是在矩形上采很多点。
- UE 传统路径更像复用 PointLight shadow
可以理解成:
从 RectLight 中心生成局部光源 shadow depth
类似 point light cubemap / one-pass point shadow
也就是近似:
6 个 90° face 覆盖周围
不是一张真正的 180° shadowmap。
- 最终只用 RectLight 正面区域
虽然 shadow 数据可能像 point light 那样准备,但光照时会判断 RectLight 朝向:
dot(ToLight, RectLightDirection)
背面区域不受 RectLight 影响,相当于被丢掉。
- 所以它有浪费
因为 RectLight 只照正面,但 cubemap-style shadow 会覆盖更多方向。
优点:复用 point light shadow 系统,稳定简单
缺点:有性能浪费,不是真正物理面积阴影
核心一句:
RectLight 的光照按矩形面积算,但传统实时 shadow 更像复用 point light 的 cubemap/one-pass shadow;物理上只需要正面半球,工程上常准备更多方向,再在光照阶段只使用正面区域。