阴影的基本原理
一、现实中阴影产生的规则
现实中阴影的产生规则 现实中的阴影产生规则是在不考虑光线反射的前提下 当一个光源发射的一条光线遇到一个不透明物体 A 时,这条光线就不能够再继续照亮其他物体了(物体 B 的一部分) 相当于光线被更靠近光源的 A 物体挡住了,一些这时物体 A 就会向旁边的物体 B 投射阴影 也就是说,阴影区域的产生就是因为光线无法到达
二、Shadow Mapping 技术
基于 阴影区域的产生就是因为光线无法到达 的这个规则 前辈 Lance Williams(兰斯·威廉姆斯)在 1978 年时最早提出了 Shadow Mapping(阴影贴图)技术。这是计算机图形学中第一个提出的通用阴影算法。 Shadow Mapping 的基本原理: 将摄像机的位置放在和光源重合的位置上 那么场景中关于这个光源的阴影区域就是摄像机看不到的位置 注意:一般情况下,点光源用透视投影,平行光用正交投影 
Shadow Mapping 在 Unity 中的本质 其实就是生成一张深度图(阴影映射纹理),一般存于显存中 这张深度图记录了从该光源位置出发,能看到的场景中距离它最近表面的位置 (一般记录其深度信息,值转换为 0~1 之间,0 最近,1 最远) 
三、Unity 中如何应用 Shadow Mapping 技术
阴影映射纹理的生成是由光源完成的。 在每帧渲染的早期阶段,Unity 会对每个能够投射阴影的光源创建一个对应的摄像机视角,这个视角用于捕获从光源位置看到的场景。平行光设置为正交摄像机,点光源可能会设置多个视角,捕获多个方向的立方体阴影贴图。Unity 会渲染场景的深度信息,这些深度信息表示从光源到场景中每个物体的距离不考虑颜色信息,最终存储在显存中。
实时阴影映射纹理需要每帧更新,但对于静态光源和静态场景,可以使用预烘焙的阴影贴图,减少实时计算的开销。 有了 阴影映射纹理 后,我们只需要在 Pass 中将顶点位置变换到光源空间下,得到顶点在光源空间下的三维位置信息。 然后使用 X 和 Y 分量对阴影映射纹理进行采样,得到阴影纹理中该位置的深度信息。 如果阴影纹理中取出的深度值小于该顶点的深度值,那么说明该顶点位于阴影中。(深度图中值为 0~1 之间,0 最近,1 最远)
四、Unity 中实现阴影的原理
注意: 是在 Unity 当中,使用的并不只是纯粹的 Shadow Mapping 技术 还会使用由微软研究院提出的 (首次提出是在 2011 年) Screen Space Shadow Mapping( SSSM ) 翻译过来就是:屏幕空间阴影映射技术(它是基于 Shadow Mapping 技术的一种拓展和改进技术。)
1. Screen Space Shadow Mapping 技术
并不是所有设备都支持 SSSM 技术 在之后编写阴影相关 Shader 时,Unity 内部会帮助我们判断对应平台是否支持 不支持时会默认使用 Shadow Mapping 处理阴影
基本原理
- 基于光源位置生成的 阴影映射纹理
- 基于渲染游戏画面时得到的 屏幕空间深度图
- 将屏幕像素位置 变换到 光源空间下
- 对 屏幕空间深度图 和 阴影映射纹理 进行采样 比较深度值 决定最后的阴影处理效果
SSSM(屏幕空间阴影映射技术)基于 Shadow Mapping 技术的基础上 需要多生成一张深度图 —— 屏幕空间深度图。 在屏幕空间阴影映射技术中 会和 Shadow Mapping 一样 为每个光源生成对应的阴影映射纹理(从光源视角生成)。 并且还会生成一张屏幕空间深度图 这张屏幕空间深度图中 记录了从摄像机视角看到的每个像素的深度值(即每个像素点到相机的距离) 更确切的说是每个像素点对应的场景中的顶点离摄像机的深度值 (深度值 0~1 之间, 0 表示离摄像机近裁剪面最近的距离,1 表示摄像机远裁剪面 也就是最远的距离)
当有了 阴影映射纹理 和 屏幕空间深度图后 我们将利用他们携带的信息来决定最终的阴影效果。 其中一件非常重要的事,就是坐标转换。 我们需要把 屏幕空间的像素位置 变换到 光源空间下 然后在 光源空间 下 比较每个像素的深度值 和 阴影映射纹理中的值, 如果当前像素的深度值大于光源深度图中的值,说明该像素在阴影中
注意:当屏幕空间中的像素位置变换到光源空间下时, 可能不在光源空间的可见范围内,这时我们无需进行比较判断,该像素不用进行阴影处理
2. Unity 中如何实现阴影
- Unity 会调用 LightMode(灯光模式) 被设置为 ShadowCaster(阴影投射器)的 Pass (渲染通道) 来生成对应的阴影映射纹理(Shadow Mapping 技术),以便在后续的阴影计算中使用 阴影映射纹理的计算过程往往不需要我们手动处理 Unity 中提供了对应的阴影相关的宏帮助我们进行计算 我们直接调用它们即可 注意: 如果 Shader 中没有 LightMode 为 ShadowCaster 的 Pass,会在 Shader 中的 Fallback 指定的 Shader 中继续寻找,直到找到对应 Pass。如果没有找到,那么该物体就无法向其他物体投射阴影, 因为阴影映射纹理计算中就不会计算该物体的信息。(但是该物体仍可以接收其他物体的投影)
- 对于 支持 SSSM(屏幕空间阴影映射技术)的设备来说 除了 阴影映射纹理外,还需要屏幕空间深度图。 而屏幕空间深度图通常由摄像机在渲染过程中自动生成,并存储在摄像机的深度纹理中 我们只需要在计算时 从光源的阴影映射纹理 以及 屏幕空间深度图 中进行采样比较深度即可
注意点:
物体接收来自其他物体的阴影 和 物体向其它物体投射阴影 是两个过程:
- 物体接收来自其他物体的阴影 必须在 Shader 中对阴影映射纹理(SM 或 SSSM 中的阴影图)进行采样 把采样结果和最后的光照结果相乘来产生阴影效果
- 物体向其它物体投射阴影 必须将该物体加入到光源的阴影映射纹理的计算中,必须要有 LightMode(灯光模式)被设置为 ShadowCaster(阴影投射器)的 Pass (渲染通道),这样才能让其他物体在对阴影映射纹理采样时,得到该物体的相关信息。
我们需要在 Unity 中对光源和物体进行一些设置
- 保证光源能够生成阴影映射纹理 —— 光源组件上设置 Shadow Type(阴影类型)

- 保证物体能接收其他物体的阴影 —— 网格渲染器组件上勾选 Receive Shadows(接收阴影)

- 保证物体向其它物体投射阴影 —— 网格渲染器组件上设置 Cast Shadows(投射阴影)

- 保证光源能够生成阴影映射纹理 —— 光源组件上设置 Shadow Type(阴影类型)
当有了 阴影映射纹理 和 屏幕空间深度图后 我们将利用他们携带的信息来决定最终的阴影效果。 其中一件非常重要的事,就是坐标转换。 我们需要把 屏幕空间的像素位置 变换到 光源空间下 然后在 光源空间 下 比较每个像素的深度值 和 阴影映射纹理中的值, 如果当前像素的深度值大于光源深度图中的值,说明该像素在阴影中