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