Unity 渲染教程07阴影.docx

上传人:b****6 文档编号:8896440 上传时间:2023-02-02 格式:DOCX 页数:51 大小:439.45KB
下载 相关 举报
Unity 渲染教程07阴影.docx_第1页
第1页 / 共51页
Unity 渲染教程07阴影.docx_第2页
第2页 / 共51页
Unity 渲染教程07阴影.docx_第3页
第3页 / 共51页
Unity 渲染教程07阴影.docx_第4页
第4页 / 共51页
Unity 渲染教程07阴影.docx_第5页
第5页 / 共51页
点击查看更多>>
下载资源
资源描述

Unity 渲染教程07阴影.docx

《Unity 渲染教程07阴影.docx》由会员分享,可在线阅读,更多相关《Unity 渲染教程07阴影.docx(51页珍藏版)》请在冰豆网上搜索。

Unity 渲染教程07阴影.docx

Unity渲染教程07阴影

Unity渲染教程(七):

阴影

译者:

崔嘉艺(milan21)   审校:

王磊(未来的未来)

∙研究下Unity渲染阴影的方式。

∙投射平行光阴影。

∙接收平行光阴影。

∙添加对聚光灯和点光源的阴影的支持

这是关于渲染基础的系列教程的第七部分。

这个系列教程的上一部分讲的是法线贴图。

现在让我们一起看下阴影的实现。

这个系列教程是使用Unity5.4.0f3开发的。

在渲染的时候,物体能够投射阴影是一件好事情。

平行光阴影

虽然我们的光照着色器现在能够产生相当真实的结果,但是它孤立地评估每个表面片段。

它假定来自每个光源的光线最终到达到每个表面片段。

但只在这些光线不被其他东西阻挡的情况下,这个判断才是正确的。

一些光线被其他东西阻挡。

当一个物体位于光源和另外一个物体之间的时候,这个物体可以阻止部分或全部光线到达另外一个物体。

照亮第一个物体的光线不再可用于照亮第二个物体。

结果就是,第二个物体至少有部分区域未被照亮。

不亮的区域位于第一个物体的阴影之中。

为了描述这一点,我们经常说第一个物体在第二个物体上投射阴影。

在现实的情况中,在完全照明和完全阴影的空间之间存在一个过渡区域,这个过渡区域被称为半阴影区。

半阴影区的存在,是因为所有光源都有一个体积。

光源具有体积导致的结果就是,存在一些仅有部分光源可见的区域,这意味着它们被部分地遮蔽。

光源越大,表面距离阴影投射提的距离越远,被遮蔽的区域也就越大。

带有半阴影区的阴影。

Unity不支持半阴影。

Unity支持的是软阴影,但软阴影是一种阴影滤波技术,而不是半阴影的模拟。

启用阴影

如果场景没有阴影的话,很难看到对象之间的空间关系。

为了说明这一点,我创建了一个简单的场景,在这个简单的场景厘米那有几个拉伸的立方体。

我在这些立方体上方放了四排球。

中间两行是浮动的球体,而最外面的两行通过圆柱体将球体与它们下方的立方体相连。

这些对象具有Unity的默认材质。

整个场景有两个方向光,一个是默认的方向光,和一个略弱的黄色方向光。

这些光源是在以前的教程中使用过的相同的光源。

目前,整个项目范围内的阴影都被禁用了。

我们在之前的教程中设置过这一点。

环境光的强度也设置为零,这使得我们更容易能看到阴影。

两个方向光,没有阴影没有环境光的效果。

阴影是项目级别的质量设置的一部分,通过编辑/项目设置/质量可以找到具体的设置项。

我们将在高质量等级中启用阴影。

高质量等级意味着支持硬阴影和软阴影、使用高分辨率、稳定的适配投影、150的投影距离以及四个级联阴影贴图。

阴影的质量设置。

还要确保两个方向光源都设置为投射软阴影。

它们的分辨率应该取决于质量设置。

对每个光源投射的阴影进行设置。

当两个方向光源投射阴影的时候,所有对象之间的空间关系变得更加清楚。

整个场景也变得更加真实,看起来也更加有趣。

带有阴影的场景效果

阴影贴图

Unity是如何将这些阴影添加到场景中取得?

标准着色器显然有一些方法来确定光源发出的光线是否被阻挡。

你可以通过在场景中投射一条射线,从射线是否能到达表面片段来判断一个点是否在阴影中。

如果这条射线在到达表面片段之前命中了某物,那么光源往这个方向发出的光线就会被阻挡。

这是物理引擎可以做到的,但是对于每个表面片段和每个光源都这样做是非常不切实际的。

而且在得到这些结果之后,你必须将得到的结果通过某种方式传递到图形处理器之中。

有几种技术能够支持实时阴影。

每种技术都有它自己的优点和缺点。

Unity使用的是现在最常见的技术,即阴影贴图。

这意味着Unity以纹理方式存储阴影信息。

我们现在将研究下Unity的阴影具体是如何实现的。

通过“窗口/帧调试器”打开帧调试器,启用阴影,并查看渲染步骤中的层次结构。

查看启用阴影的帧与不启用阴影的帧之间的差异。

启用阴影的帧与不启用阴影的帧对于渲染过程的差异

当禁用阴影的时候,所有的物体都会照常进行渲染。

我们已经很熟悉这个过程了。

但是当启用阴影的时候,渲染过程变得更加复杂。

渲染阶段变多了,还多了非常多的绘制调用。

所以渲染阴影是非常昂贵的!

渲染到深度贴图

当启用平行光阴影的渲染选项的时候,Unity开始在渲染过程加入一个深度渲染Pass。

所得到的结果会放一张与屏幕分辨率相匹配的纹理贴图中去。

这个深度渲染Pass会渲染整个场景,但是仅记录每个片段的深度信息。

这是与图形处理器用来确定片段与先前渲染的片段的深度相对关系相同的信息。

这个数据对应于片段在裁剪空间中的Z坐标。

裁剪空间是定义相机可以看到的区域的空间。

深度信息存储为0到1范围内的值。

当查看纹理贴图的时候,离相机比较近的纹理像素显示为比较暗的点。

离相机比较近的纹理像素显示为比较亮的点。

在相机的近平面设置为5的时候的深度纹理贴图

什么是裁剪空间?

裁剪空间决定了相机可以看到的区域。

当你在场景视图中选择主摄像机的时候,你将在主摄像机的前面看到一个金字塔线框,这个金字塔线框就是指示这个摄像机可以看到的区域。

近平面值很大的时候,相机的视图。

在裁剪空间中,这个金字塔是一个规则的立方体。

模型-视图-投影矩阵用于将网格顶点转换到这个空间之中。

它被称为裁剪空间,因为在这个金字塔之外的所有内容都会被剪裁掉,因为这个金字塔之外的所有内容都是不可见的。

这个信息实际上与阴影没有直接关系,但Unity会在稍后使用这个信息。

渲染到阴影贴图之中

Unity渲染的下一个内容是第一个光源的阴影贴图。

稍后,它将渲染第二个光源的阴影贴图。

再一次,整个场景都被渲染了一遍,并且同样也是只有深度信息被存储在纹理贴图之中。

然而,这一次是从光源的位置来渲染整个场景。

最为特别的是,光源作为摄像机。

这意味着深度值告诉我们的是在光线射到某物之前到底行进了有多远。

这可以用来确定是否有东西被遮挡住!

 

会对法线贴图有什么影响么?

阴影贴图记录的是实际几何体的深度信息。

法线贴图会给物体添加粗糙表面的错觉,而阴影贴图会忽略它们。

因此,阴影不受法线贴图的影响。

因为我们使用的是平行光源,他们的相机是正交的。

因此,没有透视投影,并且与光源相机的确切位置没有关系。

Unity将定位光源相机的位置,以便光源相机看到正常相机视图中的所有对象。

两张阴影贴图,分别是四个视点下看到的结果。

事实上, Unity不只对每个光源都要渲染整个场景一次。

而是对每个光源都要渲染整个场景四次!

纹理贴图被分成四个象限,每个象限从不同的角度进行渲染。

这是因为我们选择使用四个级联阴影。

如果你要切换到两个级联阴影,场景将对每个光源渲染整个场景两次。

如果没有级联阴影的话,它只是对每个光源渲染整个场景一次。

当我们观察阴影的质量的时候,我们将看到为什么Unity会这么做。

收集阴影

我们现在有从相机的视角得到的场景的深度信息。

我们也有从每个光源的视角得到的场景的深度信息。

当然,这些数据存储在不同的裁剪空间之中,但我们知道这些空间的相对位置和方向。

所以我们可以从一个空间转换到另一个空间中去。

这允许我们从两个视角来比较深度的测量。

从概念上讲,我们有两个向量应该在同一点结束。

如果他们这样确实在同一点结束的话,从相机和光源的视角都可以看到这一点,所以这一点应该被照亮。

如果光源发出的射线在到达这一点之前结束,则意味着光源发出的射线被阻挡,也就意味着该点被遮蔽了。

当现场相机无法看到一个点的时候怎么办呢?

这些点隐藏在更靠近相机的其他点的后面。

场景深度纹理只包含那些最靠近相机的点。

因此,没有必要来浪费时间去评估这些隐藏的点上。

对于每个光源而言的场景空间的阴影。

Unity通过渲染覆盖整个视图的单个四边形来创建这些纹理。

它使用Hidden/Internal-ScreenSpaceShadows这个着色器进行这个pass的渲染。

从场景相机和光源的深度纹理贴图中进行每个片段的采样、进行比较、并将最终的阴影值渲染为屏幕空间的阴影贴图。

被照亮的纹理像素设置为1,阴影贴图的纹理像素设置为0。

此时,Unity还可以执行过滤,来创建那些柔和的阴影。

为什么Unity在渲染和收集阴影之间交替?

每个光源需要自己的屏幕空间的阴影贴图。

但从光艳的角度渲染的阴影贴图可以重复使用。

对阴影贴图进行采样

最后,Unity完成了对阴影的渲染。

现在场景被正常渲染,只是有一个变化。

光源的颜色会乘以存储在阴影贴图中的值。

当这个光源应该被遮挡的时候,这会消除这个光源的影响。

每个被渲染的片段都会对阴影贴图进行采样。

最终会被其他物体隐藏的片段会放在后面进行绘制。

因此,这些片段可以最终接收到对它们进行隐藏的对象投射的阴影。

当通过帧调试器单步调试的时候,你可以看到这一点。

你还可以看到阴影出现在实际投射这些阴影的对象之前。

当然,这些错误只有在渲染这一帧的过程中才会显示。

一旦这一帧的渲染完成,所得到的图像就是正确的。

一个被部分渲染的帧,包含着奇怪的阴影。

阴影质量

当从光源的角度渲染整个场景的时候,光源的方向与场景相机的方向不匹配。

因此,阴影贴图的纹理像素不与最终图像的纹理像素一一对应。

阴影贴图的分辨率也与最终图像的分辨率不同。

最终图像的分辨率由显示设置来决定。

而阴影贴图的分辨率是由阴影质量设置来决定的。

当阴影贴图的纹理像素最终渲染的时候比最终图像的纹理像素大的时候,它们将变得非常明显。

阴影的边缘将被出现走样。

这在使用硬阴影的时候最为明显。

 硬阴影和软阴影的效果对比。

为了尽可能使这个走样更明显,让我们更改阴影质量设置,所以我们只在场景中得到硬阴影,分辨率为最低,并且没有级联。

低阴影质量设置下的阴影效果。

现在很明显,阴影是作为纹理贴图存储的。

此外,阴影出现在他们不应该出现的地方。

我们稍后会研究一下。

在阴影越靠近场景摄像机的地方,阴影的纹理像素就变得越大。

这是因为阴影贴图目前覆盖场景摄像机可见的整个区域。

我们可以通过在质量设置减少阴影覆盖的区域来提高相机附近的阴影质量。

距离减少为25的时候得到的阴影效果。

通过将阴影限制到接近场景摄像机的区域,我们可以使用相同的阴影贴图来覆盖更小的区域。

结果就是,我们得到效果更好的阴影。

但我们失去了在远离场景摄像机区域的阴影。

阴影随着这些区域接近最大距离而消失。

在最理想的情况下,我们能够在接近场景摄像机的区域得到高质量的阴影,同时还能在远离场景摄像机的区域保持阴影。

因为远处的阴影最终渲染到更小的屏幕区域,那些阴影可以使用较低分辨率的阴影贴图。

这是通过级联阴影做的。

启用级联阴影的时候,多个阴影贴图将渲染到同一纹理贴图之中。

每个阴影贴图只用于一定距离的区域。

使用四个级联阴影的低精度阴影贴图所得到的渲染效果。

当使用四个级联阴影的时候,结果看起来好多了,即使我们仍然使用的是相同的纹理分辨率。

我们只是更有效地使用了贴图中的纹理像素。

缺点是我们现在必须再多渲染三次场景。

当渲染到屏幕空间的阴影贴图的时候,Unity会从正确的级联阴影中进行采样。

你可以通过寻找阴影纹理尺寸的突然变化来找到一个级联阴影结束和另一个级联阴影开始的位置。

你可以通过质量设置控制级联阴影带所在的范围,因为级联阴影带所在的范围会作为阴影距离的一部分。

你还可以通过更改渲染模式,而在场景视图中可视化级联阴影带所在的范围。

使用杂项/级联阴影就可以除了渲染的物体之外,还能渲染场景上方的级联阴影的颜色。

级联区域的示意图,调整后显示有三个级联区域。

我该如何更改场景视图的显示模式?

在场景视图窗口的左上角有一个下拉列表。

默认情况下,它设置为“渲染”。

级联区域的形状取决于阴影投影质量设置。

阴影投影质量设置的默认值为“稳定适配”。

在这个模式下,可以根据渲染点到相机位置的距离来选择级联区域。

另一个选项是“关闭适配”,那么级联区域的形状会使用相机的深度来决定。

这会产生在相机的观察方向上的矩形区域带。

“关闭适配”下得到的阴影效果。

这种配置允许更有效地使用阴影贴图,这会导致更高质量的阴影。

但是,阴影的投影现在取决于相机的位置和方向。

这样做的结果就是,当相机移动或旋转的时候,阴影贴图也会随着改变。

如果你能看到阴影的纹理像素,你会注意到它们在移动。

这种效应被称为shadowedgeswimming,有时候可以非常明显的看到。

这就是为什么默认选项是其他模式。

不选择“关闭适配”,阴影也会依赖于相机的位置?

阴影确实会依赖于相机的位置,但Unity可以对齐地图,以便当相机的位置变化的时候,阴影贴图的纹理像素看上去是不动的。

当然,级联区域会跟着移动,所以级联区域之间的转变点会随着相机的位置的变化而变化。

但如果你根本就没有注意到级联区域的话,你也不会注意到级联区域之间的转变点移动。

阴影瑕疵

当我们使用低质量的硬阴影的时候,我们看到一些阴影出现在他们不应该出现的地方。

不幸的是,不管质量设置如何,这种情况都可能发生。

阴影贴图中的每个纹理像素表示的是光线射到表面的点。

然而,纹理像素不是单一的点。

阴影贴图中的纹理像素最终会覆盖更大的区域。

它们与光的方向对齐,而不是与表面对齐。

这样导致的结果是,它们可能最终粘附在或是穿过诸如黑暗碎片的表面。

当阴影贴图中的纹理像素的一部分最终从投射阴影的表面戳出时,这些表面看起来像是对自己投射了阴影。

这被称为阴影瑕疵。

阴影贴图导致的瑕疵。

阴影瑕疵的另外一个来源是数值精度限制。

当涉及非常小的距离的阴影的计算的时候,这些数值精度方面的限制会导致不正确的结果。

根本不使用偏移的时候,所导致的严重的瑕疵

防止这个问题的一种方法是通过在渲染阴影贴图的时候添加深度偏移。

深度偏移被添加到从光到阴影投射表面的距离中,这样做会将阴影推入到表面中去。

偏移以后的阴影贴图。

默认情况下,阴影偏移是按每个光源进行配置,这个值被设置为0.05。

按每个光源进行配置的阴影属性。

比较低的偏移量可能回产生阴影偏移,但比较大的偏移量会引入另外一个问题。

当阴影投射物体被推离光源的时候,它们的阴影也会被随之推开。

因此,阴影将不会完美地与对象对齐。

当使用比较低的偏移量的时候,这还不是那么糟糕。

但比较大的偏移量可以使它看起来像是阴影从投射它们的对象断开连接一样。

这种效果被称为peterpanning。

比较大的偏移量会导致peterpanning。

除了这个距离偏移意外,还有一个法线偏移。

这是对阴影投射体的微调。

这种偏移将阴影投射体的顶点沿着它们的法线向内推。

这也减少了自阴影的出生,但将阴影投射体的顶点沿着它们的法线向内推也会使阴影更小,并可能导致在阴影中出现孔。

什么是最佳偏移设置?

根本就没有什么最佳偏移设置。

不幸的是,你必须通过试验来找到适合你项目的最佳偏移设置。

Unity的默认设置可能有效,但默认设置也可能产生不可接受的结果。

不同的质量设置也可能产生不同的结果。

抗锯齿

你是否在质量设置中启用了抗锯齿?

如果你启动了抗锯齿的话,那么你可能会发现阴影贴图这种技术存在的另外一个问题。

那就是它们不能与标准的抗锯齿技术混合在一起使用。

当使用抗锯齿时候出现的锯齿情况

当你在质量设置中启用抗锯齿的时候,Unity将使用多重采样抗锯齿技术,也就是MSAA。

多重采样抗锯齿技术通过沿着三角形边缘执行一些超采样来去除三角形边缘的混叠。

细节纹理贴图对多重采样抗锯齿技术没有什么影响。

重要的是,当Unity渲染屏幕空间的阴影贴图时候,它会使用覆盖整个视图的单个四边形。

因此,在屏幕空间的阴影贴图中根本就没有三角形边缘,因此多重采样抗锯齿技术不会影响屏幕空间阴影贴图。

多重采样抗锯齿技术只对最终图像起作用,但是阴影值是从屏幕空间的阴影贴图中直接获取的。

当靠近较暗表面的一个比较亮的表面被遮蔽的时候,这就会变得非常明显。

明暗几何之间的边缘是反锯齿的,但是阴影边缘不是。

分别是没有使用抗锯齿技术、使用了多重采样抗锯齿技术和使用了快速近似抗锯齿技术的效果对比图。

依赖图像后处理的抗锯齿方法(比如快速近似抗锯齿技术)就没有这个问题,因为这些抗锯齿方法是在整个场景被渲染以后才进行处理的。

这是否意味着我不能混合使用多重采样抗锯齿技术与平行光阴影?

你可以混合使用多重采样抗锯齿技术与平行光阴影,但你会遇到上述问题。

在某些情况下,这些问题可能不明显。

让我们举个简单的例子来说明一下,当所有表面的颜色大致相同的时候,阴影的瑕疵将是很微小的当然,你仍然会得到有锯齿的阴影边缘。

投射阴影

现在我们知道Unity如何为平行光光创建阴影了,现在是时候为我们自己的着色器添加对平行光阴影的支持了。

目前,我的第一个光照着色器既不投射也不接收阴影。

让我们先处理投射阴影的问题。

我改变了示例场景中的球体和圆柱体,以便他们可以使用我们的材质。

所以现在示例场景中的球体和圆柱体不再投射阴影。

使用了我们的材质以后,示例场景中的球体和圆柱体不再投射阴影。

我们知道Unity会为平行光阴影多次渲染场景。

其中一次渲染场景是为了深度pass,并且还会为每个光源每个级联阴影贴图渲染场景一次。

屏幕空间的阴影贴图是一个屏幕空间效果,不会涉及我们。

为了支持所有相关的pass,我们必须向我们的着色器添加一个pass,在这个pass中光照模式设置为ShadowCaster。

因为我们只对深度值感兴趣,所以它会比我们的其他pass更加简单。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

SubShader{

 

        Pass{

            Tags{

                "LightMode"="ForwardBase"

            }

 

            …

        }

 

        Pass{

            Tags{

                "LightMode"="ForwardAdd"

            }

 

            …

        }

 

        Pass{

            Tags{

                "LightMode"="ShadowCaster"

            }

 

            CGPROGRAM

 

            #pragmatarget3.0

 

            #pragmavertexMyShadowVertexProgram

            #pragmafragmentMyShadowFragmentProgram

 

            #include"MyShadows.cginc"

 

            ENDCG

        }

    }

让我们把阴影程序它们自己的导入文件命名为MyShadows.cginc。

这个导入文件很简单。

顶点程序像往常一样将位置从对象空间转换为裁剪空间,不做任何其他操作。

而片段程序实际上不需要做任何事情,所以只是返回零。

图形处理器会为我们记录深度值。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

#if!

defined(MY_SHADOWS_INCLUDED)

#defineMY_SHADOWS_INCLUDED

 

#include"UnityCG.cginc"

 

structVertexData{

    float4position:

POSITION;

};

 

float4MyShadowVertexProgram(VertexDatav):

SV_POSITION{

    returnmul(UNITY_MATRIX_MVP,v.position);

}

 

half4MyShadowFragmentProgram():

SV_TARGET{

    return0;

}

 

#endif

这已经足够投射平行光阴影了。

投射平行光阴影。

偏移

我们还必须支持阴影的偏移。

在深度pass渲染期间,这个阴影的偏移为零,但是当渲染阴影贴图的时候,阴影的偏移会相对光源进行设置。

我们可以通过应用深度偏差到顶点着色器中顶点在裁剪空间的位置来实现这一点。

为了支持阴影的深度偏移,我们可以使用在UnityCG中定义的UnityApplyLinearShadowBias函数。

1

2

3

4

float4MyShadowVertexProgram(VertexDatav):

SV_POSITION{

    float4position=mul(UNITY_MATRIX_MVP,v.position);

    returnUnityApplyLinearShadowBias(position);

}

 

UnityApplyLinearShadowBias是如何工作的?

这个函数是增加裁剪空间中的Z坐标的值。

让这个事情变复杂的原因是它正在使用的是齐次坐标。

它必须对透视投影进行补偿,以便偏移不随着渲染的位置离相机的距离变化而变化。

它还必须确保结果不会超出范围。

1

2

3

4

5

6

float4UnityApplyLinearShadowBias(float4clipPos){

    clipPos.z+=saturate(unity_LightShadowBias.x/clipPos.w);

    floatclamped=max(clipPos.z,clipPos.w*UNITY_NEAR_CLIP_VALUE);

    clipPos.z=lerp(clipPos.z,clamped,unity_LightShadowBias.y);

    returnclipPos;

}

 

为了支持阴影的法线偏差,我们必须根据法线向量来移动顶点的位置。

所以我们必须在顶点数据中加上法线数据。

然后我们可以使用UnityClipSpaceShadowCasterPos函数来应用这个法线偏差。

这个函数也是在UnityCG中定义的。

1

2

3

4

5

6

7

8

9

structVertexData{

    float4position:

POSITION;

    float3normal:

NORMAL;

};

 

float4MyShadowVertexProgram(VertexDatav):

SV_POSITION{

    float4position=UnityClipSpaceShadowCasterPos(v.position.xyz,v.normal);

    returnUnityApplyLinearShadowBias(position);

}

 

UnityClipSpaceShadowCasterPos是如何工作的?

这个函数将顶点的位置转换到世界空间中去,然后应用正常偏置,再转换到裁剪空间中去。

精确的偏移取决于法线和光线方向之间的角度,以及阴影纹理像素的尺寸。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 高等教育 > 农学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1