
1. 传统阴影映射
- 阴影映射是一种深度图技术:将光源作为投影源,渲染出一个深度纹理,记录光源视角下能看到的最近表面深度信息。前向渲染中,当开启某个光源的阴影时,Unity 会为该光源生成阴影映射纹理。
- 对应的阴影效应通过一个 ShadowCaster Pass 实现,该 Pass 渲染结果用于阴影纹理或深度纹理,而不是直接显示在帧缓存中。
2. 屏幕空间阴影映射(Screenspace Shadow Map)
- 屏幕空间阴影映射起源于延迟渲染的思路,现在也可用于前向渲染,但需要显卡支持多重渲染目标(MRT)。
- 做法是先获取投射阴影的光源阴影映射纹理和相机深度纹理。随后通过比较屏幕深度信息,生成屏幕空间的阴影图。如果屏幕深度图中某点对应的表面在阴影纹理中的深度值之后,表明该区域处于光源遮挡之下,从而在屏幕空间形成阴影。
- 若想让某物体接收来自其他物体的阴影,需要在着色器中对阴影图进行采样。由于阴影图是基于屏幕坐标的,需要先将表面坐标从模型空间变换到屏幕空间,再进行采样。
3VSport. Unity 生成阴影的要点
- 开启阴影生成与接收:在对象的设置中开启 Cast Shadows 与 Receive Shadows。
- 着色器中调用阴影:即便先前的光照代码未显式写入阴影逻辑,系统会通过内置的阴影回调实现阴影效果。通常可以直接使用内置的 ShadowCaster Pass,或者将 Fallback 设置为 VertexLit,以便在着色器中获得阴影。
- 双面阴影(Two Sided):默认情况下,只有面向光源的一面会投射阴影。若要对平面等对象实现双面阴影,需要将阴影投射设置为 Two Sided,并在着色器中进行相应实现。实现时需要引入内置文件、在顶点输出结构中添加阴影纹理坐标的采样、在定点着色器中添加必要的内置宏、在片元着色器中继续使用内置宏,并在返回阶段将阴影颜色与漫反射和高光相乘。
- 需要注意的是,阴影相关的宏和实现依赖于 Unity 的 AutoLight.cginc 等内置文件。不同光源类型、是否启用纹理等情况会有多版本的声明,确保变量名与宏所期望的一致以能够正确计算阴影。
4. 接收阴影
- 接收阴影的核心在于在片元阶段对阴影图进行采样,并结合环境光、漫反射等光照分量,得到最终的着色结果。阴影计算通常会通过内置的阴影函数和传递的 v2f 结构体来实现,确保屏幕空间或光源空间的坐标能正确映射到阴影纹理上。
5. Addtional Pass 中的阴影部分更新
- 为了实现更复杂的衰减与阴影组合,常在 Base Pass 之外新增一个 Addtional Pass,将阴影与衰减的计算分离。通过在该阶段嵌入阴影和光照衰减的计算代码,可以更灵活地控制阴影的表现与衰减的采样方式。
6. 帧调试器查看阴影绘制
- 使用帧调试工具,逐步检查阴影绘制流程,确认 ShadowMap 的生成、阴影纹理的填充以及阴影在屏幕上的采样是否正确,便于定位问题并优化性能。
7. 透明物体的阴影处理
- 透明度测试的物体:若材质对某些像素进行透明度测试(Alpha Test),阴影投射通常仍然有效。在 Unity 的内置着色器中,阴影投射逻辑会考虑到裁剪后的深度信息,因此需要在着色器中提供透明度相关的裁剪属性(如 alphaCutoff)以实现正确的阴影结果;同时 Cast Shadows 需设为 Two Sided 以获得正确的面向光源的阴影。
- 透明度混合的物体:对于使用透明度混合(Alpha Blending)的半透明对象,大多数内置着色器并不包含阴影投射的 Pass,因此它们通常不会投射或接收阴影。为实现阴影效果,常采用变通做法,即让不透明物体的阴影 Pass 对半透明对象也生效,从而间接实现阴影投射的视觉效果。这一做法在高保真场景中需要权衡性能与视觉需求。
