Unity可编程渲染管线系列(四)聚光灯阴影(阴影贴图)

目录

1 一个带有阴影的聚光灯

1.1 阴影贴图

1.2 阴影命令缓冲区

1.3 设置 渲染目标

1.4 配置视图和投影矩阵

1.5 渲染阴影投射器

2 阴影投射器通道

2.1 阴影包含文件

2.2 第二个通道

3 采样阴影贴图

3.1 从世界坐标到阴影坐标

3.2 采样深度

3.3 阴影淡化

4 阴影设置

4.1 阴影贴图大小

4.2 阴影偏差

4.3 阴影强度

4.4 软阴影

5 更多带有阴影的灯光

5.1 逐灯光的阴影数据

5.2 包含灯光

5.3 渲染所有的阴影贴图

5.4 使用正确的阴影数据

5.5 阴影贴图集

6 动态平铺

6.1 计数 阴影 平铺

6.2 分隔阴影贴图

6.3 平铺为1就相当于没有

6.4 着色器关键字

本文重点:

1、渲染到纹理并从纹理中读取。

2、从灯光角度渲染。

3、为阴影投射器添加着色器通道。

4、采样阴影贴图。

5、支持硬阴影和软阴影的混合。

6、在一个图集中合并多达十六个阴影贴图。

这是涵盖Unity的可脚本化渲染管线的教程系列的第四部分。在其中,我们将添加对多达16个带阴影的聚光灯的支持。

本教程是CatLikeCoding系列的一部分,原文地址见文章底部。“原创”标识意为原创翻译而非原创教程。

本教程使用Unity 2018.3.0f2制作。

(带有阴影的三个聚光灯)

1 一个带有阴影的聚光灯

阴影非常重要,既可以增加真实感,又可以使对象之间的空间关系更加明显。没有阴影,很难分辨是物体漂浮在表面上还是在表面上。

Rendering 7,Shadows教程说明了阴影如何在Unity的默认渲染管线中工作,但是对于我们的单通道正向渲染器,这种完全相同的方法不起作用。但返回阅读以获取阴影贴图的相关知识仍然很有用。在本教程中,我们将仅限于聚光灯的阴影,因为阴影是最不复杂的。

我们首先要一个支持阴影的光,因此要创建一个包含几个对象和一个聚光灯的场景。平面对象对于接收阴影很有用。所有物体都使用我们的不透明Lit材质。

(单个聚光灯 还没有阴影)

1.1 阴影贴图

有不少处理阴影的方法,但是我们将继续使用阴影贴图的默认方法。这意味着我们将从灯光的角度渲染场景。我们仅对此次渲染的深度信息感兴趣,因为它告诉我们光线在撞击表面之前到达的距离。任何更远的地方都在阴影中。

要使用阴影贴图,我们必须先创建阴影贴图,然后再使用普通相机进行渲染。为了以后能够对阴影贴图进行采样,我们必须渲染为单独的渲染纹理,而不是通常的帧缓冲区。向MyPipeline添加一个RenderTexture字段以保留对阴影贴图纹理的引用。

使用上下文作为参数,创建一个单独的方法来渲染阴影。它要做的第一件事就是控制渲染纹理。我们将通过调用静态RenderTexture.GetTemporary方法来实现。要么创建新的渲染纹理,要么重新使用尚未清理的旧纹理。因为我们很可能在每帧都需要阴影贴图,所以它会一直重复使用。

向RenderTexture.GetTemporary提供地图的宽度和高度,用于深度通道的位数,最后是纹理格式。我们将从512×512的固定大小开始。深度通道将使用16位,因此它是高精度的。在创建阴影贴图时,请使用RenderTextureFormat.Shadowmap格式。

确保将纹理的滤镜模式设置为双线性,并将其环绕模式设置为钳制。

阴影贴图将在常规场景之前渲染,因此在设置常规摄影机之前但在剔除之后在Render中调用RenderShadows。

另外,请确保在提交上下文后释放渲染纹理。如果此时有阴影贴图,则将其传递到RenderTexture.ReleaseTemporary方法并清除我们的字段。

1.2 阴影命令缓冲区

我们将为所有阴影工作使用单独的命令缓冲区,因此我们可以在帧调试器的单独部分中看到阴影和常规渲染。

就像我们进行常规渲染一样,阴影渲染将在BeginSample和EndSample命令之间进行。

1.3 设置 渲染目标

在渲染阴影之前,我们首先要告诉GPU渲染到阴影贴图。一种方便的方法是通过使用命令缓冲区和阴影贴图作为参数调用CoreUtils.SetRenderTarget。从清除贴图开始,请在BeginSample之前调用它,以便不显示帧调试器和额外的嵌套渲染阴影级别。

我们只关心深度通道,因此仅需要清除该通道。通过将ClearFlag.Depth添加为SetRenderTarget的第三个参数来表明这一点。

尽管不是必需的,但我们也可以更精确地了解纹理的负载和存储要求。我们不在乎它的来源,因为无论如何我们都可以清除它,我们可以使用RenderBufferLoadAction.DontCare指出。这使得基于图块的GPU变得更有效率。而且我们需要稍后从纹理中采样,因此需要将其保存在内存中,我们将使用RenderBufferStoreAction.Store进行指示。将它们添加为第三个和第四个参数。

现在,在常规摄影机渲染之前,阴影映射的清除动作会显示在帧调试器中。

(清除阴影贴图)

1.4 配置视图和投影矩阵

我们的想法是从光源的角度进行渲染,这意味着我们将聚光灯当作照相机使用。因此,必须提供适当的视图和投影矩阵。我们可以通过使用灯光索引作为参数,在剔除结果上调用ComputeSpotShadowMatricesAndCullingPrimitives来检索这些矩阵。由于场景中只有一个聚光灯,因此我们只需提供零即可。视图和投影矩阵可通过两个输出参数使用。除此之外,还有第三个ShadowSplitData输出参数。我们不需要它,必须提供输出参数。

一旦有了矩阵,就可以通过调用阴影命令缓冲区上的SetViewProjectionMatrices来设置它们,执行并清除它。

1.5 渲染阴影投射器

有了正确的矩阵,我们可以继续渲染所有的阴影对象。我们通过在上下文上调用DrawShadows来实现。该方法具有DrawShadowsSettings参考参数,我们可以通过构造函数方法创建该参数,该方法将剔除结果和光照索引作为参数。

仅当我们的聚光灯的阴影类型设置为硬或软时,此方法才有效。如果将其设置为none,则Unity将报错,说它不是有效的阴影投射灯。

(灯光开启阴影)

2 阴影投射器通道

此时,受光照影响的所有对象都应渲染到阴影贴图中,但是帧调试器告诉我们这没有发生。这是因为DrawShadows使用ShadowCaster着色器通道,而我们的着色器当前没有这样的通道。

2.1 阴影包含文件

要创建阴影投射器通道,请复制Lit.hlsl文件并将其重命名为ShadowCaster.hlsl。我们只关心深度信息,因此请从新文件中删除与片段位置无关的所有内容。片段程序的输出仅为零。还重命名其传递函数,并包括防护定义。

这足以渲染阴影,但是阴影投射者有可能与附近的地方相交,从而导致阴影中出现孔洞。为了防止这种情况,我们必须将顶点钳位到顶点程序中的附近。这是通过获取剪辑空间位置的Z坐标和W坐标的最大值来完成的。

然而,这由于剪辑空间的细节而变得复杂。最直观的方式是将近裁剪平面处的深度值视为零,并随着距离的增加而增加。但这实际上是除OpenGL API以外的所有方法的反函数,在近平面处该值为1。对于OpenGL,近平面值为-1。我们可以依靠通过Common.hlsl包含的UNITY_REVERSED_Z和UNITY_NEAR_CLIP_VALUE宏来处理所有情况。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值