Unity通用渲染管线(URP)系列(八)——复杂的贴图(Masks, Details, and Normals)

2020-12-24 14:35:43 浏览数 (1)

目录

· 1 电路材质

· 1.1 反照率

· 1.2 自发光

· 2 遮罩贴图

· 2.1 MODS

· 2.2 遮罩输入

· 2.3 金属度

· 2.4 平滑度

· 2.5 遮挡

· 3 细节贴图

· 3.1 细节UV坐标

· 3.2 细节反照率

· 3.3 细节平滑度

· 3.4 淡化细节

· 4 法线贴图

· 4.1 采样法线

· 4.2 切线空间

· 4.3 阴影偏差的插值法线

· 4.4 细节化法线

· 5 可选贴图

· 5.1 法线贴图

· 5.2 输入配置

· 5.3 可选的遮罩贴图

· 5.4 可选细节

本文重点: 创建类电路材质。 添加对MODS遮罩贴图的支持。 介绍次要细节贴图。 执行切线空间法线贴图。

这是有关创建自定义可编程渲染管道的系列教程的第八部分。通过增加对遮罩,细节和法线贴图的支持,可以创建复杂的表面。

本教程是CatLikeCoding系列的一部分,原文地址见文章底部。

本教程使用Unity 2019.2.21f1制作。

(电路的艺术印象)

修正

尽管代码没有问题,但着色器编译器始终错误地警告一些潜在的未初始化值。有时这是由于中间函数的return语句引起的。我重写了Shadows中的第一个GetBakedShadows函数以消除警告:

由于相同的原因,我还调整了GetDirectionalShadowAttenuation:

此外,Core RP Library 6.9.2版使用了float类型而不是real类型的定向滤波器设置功能,因此我更新并更改了FilterDirectionalShadow以使其匹配。

1 电路材质

到现在为止,我们一直使用非常简单的材质来测试RP。但是它也应该支持复杂的材质,以便我们可以表示更多有意思的表面。在本教程中,我们将在一些纹理的帮助下创建一种类似电路的艺术材质。

1.1 反照率

材质的基础是其反照率贴图。它由几层不同的绿色组成,在上面有一些金色。除了一些棕色污渍之外,每个颜色区域是统一的,这会让区分随后添加的细节变得更加容易。

(反照率贴图)

使用该反照率贴图,并使用我们的Lit着色器创建新材质。我将其平铺设置为2 x 1,以便让正方形纹理环绕一个球体而不会被拉伸得太多。但默认球体的极点总是会变形很多,这是无法避免的。

(电路球)

1.2 自发光

前面已经支持了自发光贴图,所以我们也使用起来。下面是一个在金色电路顶部添加浅蓝色照明图案的贴图。

(自发光贴图)

将其分配给材质,并将自发光颜色设置为白色,以便使其可见。

(自发光电路)

2 遮罩贴图

目前,我们还没有办法做更多的事情来让我们的材质变更有意思。金色电路应该是金属的,但绿色电路板不是,可是我们目前只能配置统一的金属度和平滑度值。我们需要其他贴图来支持在整个表面上对其进行更改。

(金属度1,平滑度0.95)

2.1 MODS

我们为金属材质添加一个单独的贴图,为平滑度添加另一个贴图,但是两者都只需要一个通道,因此我们可以将它们合并为一个贴图。该贴图称为遮罩贴图,其各个通道遮盖了不同的着色器属性。我们使用与Unity的HDRP相同的格式,后者是MODS映射。此代表金属,遮挡,细节和平滑度,以该顺序存储在RGBA通道中。

下面我们电路的这种贴图。它的所有通道中都有数据,但是目前我们仅使用其R和A通道。由于此纹理包含的是遮罩数据而不是颜色,因此请确保已禁用其sRGB(颜色纹理)纹理导入属性。不这样做会导致GPU在对纹理进行采样时错误地应用伽马到线性转换。

(MODS遮罩贴图)

将遮罩贴图的属性添加到“Lit”。因为这是一个遮罩,我们使用白色作为默认颜色,就不会改变任何颜色。

(遮罩的着色器属性)

2.2 遮罩输入

向LitInput添加一个GetMask函数,该函数仅对遮罩纹理进行采样并返回它。

在继续之前,我们还要整理一下LitInput代码。使用名称参数定义INPUT_PROP宏,以提供使用UNITY_ACCESS_INSTANCED_PROP宏的简写。

现在,我们可以简化所有getter函数的代码。我只显示了在GetBase中检索_BaseMap_ST的更改。

此更改也可以应用于UnlitInput中的代码。

2.3 金属度

LitPass不需要知道某些属性是否依赖于遮罩贴图。各个功能可以在需要时检索遮罩。在GetMetallic中执行此操作,通过乘法使用遮罩贴图的R通道计算其结果。

(只有金色的电路是金属)

金属贴图通常是二进制的。在我们的案例中,金色电路是全金属的,而绿色电路板不是。

2.4 平滑度

在GetSmoothness中执行相同的操作,这一次依赖于遮罩的A通道。金色电路很平滑,而绿色电路板却不平滑。

(使用中的平滑度贴图)

2.5 遮挡

遮罩的G通道包含遮挡数据。这个想法是,诸如间隙和孔之类的较小的凹陷区域大部分会被对象的其余部分遮盖,但是如果这些特征由纹理表示,它就会被光照忽略。那么遮罩会提供丢失的遮挡数据。添加一个新的GetOcclusion函数来获取它,默认返回零代表没有遮挡。

将遮挡数据添加到Surface结构。

并在LitPassFragment中对其进行初始化。

遮挡仅适用于间接环境照明。直射光不受影响,因此,当光源直接对准间隙时,间隙不会保持较暗的状态。因此,我们仅使用遮挡来调制IndirectBRDF的结果。

(全遮挡)

在确认它具有GetOcclusion功能后,返回掩码的G通道。

(使用中的遮挡贴图)

绿板的某些部分低于其他部分,因此应将其遮挡一点。区域很大,让遮挡贴图处于最大强度以使效果清晰可见,但结果太强又不合理。与其创建具有更好遮挡数据的另一个遮罩贴图,不如将遮挡强度滑块属性添加到我们的着色器中。

(遮挡滑块,降低至0.5)

将其添加到UnityPerMaterial缓冲区。

然后调整GetOcclusion,以便它使用该属性来调制遮罩数据。在这种情况下,滑块控制遮罩的强度,因此,如果将其设置为零,则应完全忽略遮罩。我们可以通过基于强度在遮罩和1之间进行插值来实现。

(一半的遮挡强度)

3 细节贴图

下一步是在我们的材质中添加一些细节。对此,我们对细节纹理进行采样,并使用比基础贴图更高的Tiling并将其与基础和遮罩数据组合在一起。当表面近距离观察时,这会让表面更加有意思,并且它还能提供更高分辨率的信息,这时,底图本身将显示为像素化。

细节应该只会稍微修改表面特性,所以我们再次将数据合并到一个非彩色贴图中。HDRP使用ANySNx格式,这意味着它在R中存储反照率调制,在B中存储平滑度调制,并在AG中存储细节法向矢量的XY分量。但是我们的贴图不会包含法线向量,因此我们仅使用RB通道。因此,它是RGB纹理,而不是RGBA。

3.1 细节UV坐标

因为细节贴图应该比基础贴图使用更高的Tiling,所以它需要自己的tiling 和offset。为此添加材质属性,这次没有NoScaleOffset属性。它的默认值不会引起任何变化,这是通过使用linearGrey获得的,因为0.5的值将被视为中性。

(细节贴图属性)

我们能不能仅缩放基本UV? 可以,这也是可能的,甚至可能很方便。但是,对于细节使用完全独立的UV坐标可提供最大的灵活性。尽管这很少见,但它也可以独立使用细节贴图而不依赖于基础贴图。 将所需的纹理,采样器状态和缩放偏移属性添加到LitInput,以及TransformDetailUV函数以转换细节纹理坐标。

然后添加一个GetDetail函数以检索给定UV细节的所有细节数据。

在LitPassVertex中转换坐标,然后通过Varyings传递坐标。

3.2 细节反照率

要向反照率添加细节,我们需要为GetBase添加细节UV参数,默认情况下将其设置为零,以使现有代码不会中断。首先,将所有细节直接添加到基础贴图,然后再考虑颜色。

然后在LitPassFragment中将细节UV传递给它。

(添加了反照率细节)

现在确认了细节数据已正确采样,但是我们还没有正确解释它。首先,值为0.5是中性的。较高的值应增加或变亮,而较低的值应减少或变暗。进行此工作的第一步是在GetDetail中将详细信息值范围从0~1转换为-1~1。

其次,只有R通道会影响反照率,将其推向黑色或白色。这可以通过根据颜色的符号用0或1内插颜色来完成。这样,内插器就是绝对细节值。这只会影响反照率,而不影响基地的Alpha通道。

(插值后的反照率)

这很有效,而且很明显,因为我们的细节贴图非常强。但是增亮效果似乎比增暗效果更强。那是因为我们正在线性空间中应用修改。在伽马空间中执行此操作将更好地匹配视觉上相等的分布。我们可以通过对反照率的平方根进行插值,然后进行平方来对此进行近似。

(直接插值,黑暗部分更强了一些)

目前,这些细节已应用到整个表面,但好处是,大多数金色的电路均不受影响。这就是细节遮罩的用途,它存储在遮罩贴图的B通道中。我们可以通过将其分解为插值器来应用它。

(遮罩细节)

我们的细节现在是在最大可能的强度下,这有点过于强了。那我们再用一个细节反照率强度滑块属性以按比例缩小它们。

将其添加到UnityPerMaterial,然后将其与GetBase中的详细信息相乘。

(反照率细节缩放至0.2)

3.3 细节平滑度

为平滑度添加细节的方法和之前相同。首先,还要为其添加强度滑块属性。

然后将该属性添加到UnityPerMaterial,在GetSmoothness中检索缩放的细节,并以相同的方式进行插值。这次需要细节贴图的B通道。

也让LitPassFragment将详细信息UV传递给GetSmoothness。

(平滑度细节:0.2VS全强度)

3.4 淡化细节

仅当外观足够大时,细节才有意义。如果细节太小,则不应该应用,因为这会产生嘈杂的结果。Mip映射通常会使数据模糊,但是对于细节,我们想更进一步,将它们进行淡化。

(全强度的细节效果)

如果启用了细节纹理的“ Fadeout Mip Maps”导入选项,则Unity可以为我们自动淡化细节。它将显示一个范围滑块,控制淡入淡出的开始和结束的等级。Unity只是将mip贴图插值为灰色,这意味着该贴图变为中性。为此,必须将纹理的“过滤器模式”设置为“ Trilinear”,但这应该会是自动的。

(淡化细节)

4 法线贴图

即使我们已经让表面复杂很多了,它看起来仍然很平坦,因为它确实如此。照明与表面法线交互,该法线在每个三角形上平滑插值。如果照明也与其较小的特征相互作用,我们的表面将更加有可信度。可以通过添加对法线贴图的支持来做到这一点。

通常,法线贴图是从高多边形密度3D模型生成的,将其烘焙为低多边形模型以供实时使用。丢失的高多边形几何体的法线向量在法线图中烘焙。可替代地,法线贴图也可以通过程序生成。这是我们电路的这种贴图。导入后将其“纹理类型”设置为“法线贴图”。

(法线贴图)

该贴图遵循标准的切线空间法线贴图的约定,即将上轴(在这种情况下称为Z)存储在B通道中,而右XY轴和前XY轴则存储在RG中。就像细节贴图一样,法线成分的-1~1范围也会被转换,所以0.5是中点。因此,平坦区域显得偏蓝。

4.1 采样法线

要对法线进行采样,我们必须向着色器添加一个法线贴图纹理属性,默认情况下,bump 会代表一个平面贴图。还要添加一个普通比例属性,以便我们可以控制贴图的强度。

(法线贴图和缩放)

存储常规信息的最直接方法如上所述,即RGB通道中的XYZ,但这不是最有效的方法。如果我们假设法线向量始终指向上而永不指向下方,则可以忽略向上的分量,并从其他两个分量中得出。然后可以将这些通道以压缩纹理格式存储,以使精度损失最小。XY存储在RG或AG中,具体取决于纹理格式。这将改变纹理的外观,但是Unity编辑器仅显示原始贴图的预览和缩略图。

法线贴图是否更改取决于目标平台。如果贴图未更改,则定义UNITY_NO_DXT5nm。如果是这样,我们可以使用UnpackNormalRGB函数来转换采样的普通数据,否则我们可以使用UnpackNormalmapRGorAG。两者都有一个Sample和一个scale参数,并且在Core RP库的Packing文件中定义。向“Common”添加一个函数,该函数使用这些函数来解码普通数据。

DXT5nm是什么意思? DXT5(也称为BC3)是一种压缩格式,将纹理划分为4×4像素的块。每个块都有两种颜色近似,每个像素可进行插值。用于颜色的位数在每个通道中有所不同。R和B分别获得5位,G获得6位,而A获得8位。这就是X坐标移至A通道的原因之一。另一个原因是RGB通道获得一个查找表,而A通道获得其自己的查找表。这样可以使X和Y分量保持隔离。 当DXT5用于存储法线向量时,称为DXT5nm。但是,当使用高压缩质量时,Unity更喜欢BC7压缩。此模式的工作原理相同,但每个通道的位数可能会有所不同。因此,不需要移动X通道。最终纹理的结局更大,因为两个通道都使用了更多位,从而提高了纹理质量。

现在,向LitInput添加法线贴图,法线比例尺和GetNormalTS函数,并检索和解码法线向量。

4.2 切线空间

由于纹理环绕着几何体,因此它们在对象和世界空间中的方向不统一。因此,存储法线的空间会弯曲以匹配几何图形的表面。唯一的常数是该空间与表面相切,这就是为什么它被称为切线空间的原因。该空间的Y上轴与表面法线匹配。除此之外,它还必须具有与表面相切的X右轴。如果我们有这两个,则可以从中生成Z向前轴。

由于切线空间的X轴不是恒定的,因此需要将其定义为网格顶点数据的一部分。它存储为四分量切线向量。它的XYZ组件定义对象空间中的轴。它的W分量为-1或1,用于控制Z轴指向的方向。这用于翻转大多数(比如动物)具有双侧对称性的网格的法线贴图,因此相同的贴图可用于网格的两侧,从而将所需的纹理大小减半。

因此,如果我们具有世界空间法线和切向量,则可以构造从切线到世界空间的转换矩阵。为此,我们可以使用现有的CreateTangentToWorld函数,将法线,切线XYZ和切线W作为参数传递给它。然后,可以使用切线空间法线和转换矩阵作为参数来调用TransformTangentToWorld。将执行所有这些操作的功能添加到Common。

接下来,在LitPass中将具有TANGENT语义的对象空间切向量添加到Attributes中并将世界空间切线添加到Varyings中。

可以通过调用TransformObjectToWorldDir在LitPassVertex中将切线向量的XYZ部分转换为世界空间。

最后,通过调用LitPassFragment中的NormalTangentToWorld获得最终的贴图法线。

(法线贴图增加后的球体)

4.3 阴影偏差的插值法线

扰动法线向量适合照亮表面,但是我们也可以使用片段法线来偏移阴影采样。但应该使用原始的表面法线。因此,将其字段添加到Surface。

在LitPassFragment中分配法线向量。在这种情况下,我们通常可以跳过对向量的归一化处理,因为大多数网格的顶点法线没有每个三角形都弯曲得太多,以至于会对阴影偏差产生负面影响。

然后在GetCascadedShadow中使用此向量。

4.4 细节化法线

我们还可以包含法线贴图以获取细节信息。尽管HDRP在一张贴图中将法线细节与反照率和平滑度结合在一起,但我们这里将使用单独的纹理。将导入的纹理转换为法线贴图,然后启用“Fadeout Mip Maps”,以使其像其他细节一样淡出。

(细节法线贴图)

为什么不合并两个贴图? 虽然这样效率更高,但生成这样的贴图却更加困难。生成Mip贴图时,应将法向矢量与其他数据通道区别对待,而Unity的纹理导入器无法做到这一点。而且,在使Mip贴图淡化时,Unity会忽略Alpha通道,因此该通道中的数据将不会正确变淡。因此,需要在Unity外部或使用脚本自行生成Mip映射。即便那样,我们仍然需要手动解码法线数据,而不是依赖UnpackNormalmapRGorAG。我不会在本教程中介绍这些内容。

为贴图添加着色器属性,并添加法线scale。

(细节法线属性,设置为强度的一半)

通过添加UV细节参数并采样细节贴图来调整GetNormalTS。这时,我们可以通过将遮罩当做细节法线强度来应用遮罩。之后,再将两个法线结合起来,通过调用BlendNormalRNM与原始法线和局部法线来实现。此功能可围绕基础法线来旋转细节法线。

最后,将UV细节传递给GetNormalTS。

(细节化后的法线)

5 可选贴图

并非每种材质都需要用到我们当前支持的所有贴图。未分配贴图意味着结果不会修改,但是着色器仍使用默认纹理来完成所有工作。通过添加一些着色器功能来控制着色器使用哪些贴图,可以避免不必要的工作。Unity的着色器会根据在编辑器中分配的贴图自动执行此操作,但是我们将通过显式切换来控制它。

5.1 法线贴图

我们从法线贴图开始,这是最昂贵的功能。添加一个着色器属性切换开关,链接到适当关键字。

(启用了可选的法线贴图)

添加一个匹配着色器属性的Pragma到CustomLit通道中, 其他Pass均不需要映射法线,因此不应获得该功能。

在LitPassFragment中,根据关键字使用切线空间法线或仅对插值法线进行归一化。

另外,如果可以,请省略Varyings中的切线向量。尽量避免从Attributes中忽略它,如果没有使用它,它会在那里自动被忽略。

5.2 输入配置

此时,我们应该重新考虑如何将数据传递到LitInput的getter函数。我们最终可能会使用或不使用多个数据的任何组合,而这必须要以某种方式进行交互。通过引入InputConfig结构来实现此目的,首先将基础和细节UV坐标捆绑在一起。还要创建一个方便的GetInputConfig函数,该函数返回给定基础UV和可选细节UV的配置。

现在调整除TransformBaseUV和TransformDetailUV之外的所有LitInput函数,使其具有单个配置参数。这里我只展示了对GetBase的更改。

然后调整LitPassFragment,使其使用新的配置方法。

调整其他通道(MetaPass,ShadowCasterPass和UnlitPass)以也使用新方法。这意味着我们还必须让UnlitPass也使用新方法。

5.3 可选的遮罩贴图

接下来,通过向其添加布尔值到InputConfig来使遮罩贴图成为可选的,默认情况下将其设置为false。

我们可以通过在GetMask中简单地返回1来避免对掩码进行采样。这假定遮罩切换为常数,因此不会在着色器中引起分支。

在我们的着色器中为其添加一个切换开关。

以及CustomLit传递中的相关杂项。

(可选的遮罩贴图)

现在,仅在需要时打开LitPassFragment中的遮罩。

5.4 可选细节

使用相同的方法,向InputConfig添加细节切换,默认情况下再次禁用。

仅在需要时在GetDetail中对细节图进行采样,否则返回零。

这样可以避免对细节图进行采样,但是仍然可以合并细节。要停止此操作,还可以跳过GetBase中的相关代码。

在GetSmoothness中,也需要。

并在GetNormalTS中。

然后将细节的切换属性添加到着色器。

再次具有CustomLit中随附的着色器功能。

(可选细节)

现在,只有在定义了相关关键字时,才需要在Varyings中包含详细UV。

最后,仅在需要时在LitPassFragment中包含细节信息。

本文翻译自 Jasper Flick的系列教程

原文地址:

https://catlikecoding.com/unity/tutorials

0 人点赞