前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基础渲染系列(三)多样化的表现——组合纹理

基础渲染系列(三)多样化的表现——组合纹理

作者头像
放牛的星星
发布2020-07-10 17:12:54
2.6K0
发布2020-07-10 17:12:54
举报
文章被收录于专栏:壹种念头

本文重点内容:

采样多纹理 应用细节纹理 处理线性空间中的颜色 使用Splat 贴图

这是渲染系列的第三篇文章,上一节介绍了着色器和纹理。我们已经看到了如何使用单一的纹理制作一个用平坦的表面完成的复杂显示的例子,现在我们更进一步,一次同时使用多个。

本教程使用Unity 5.4.0b15。(译注:2018.4.6没有问题)

(组合多个纹理)

1 细节纹理

纹理很好用,但是有局限性。因为无论实际显示大小是多少,它们都只能用固定数量的纹理像素。如果将它们缩小,我们可以使用mipmap使其保持良好外观。但是,当放大时,它们就会变得模糊。我们不能凭空的创造出更多细节,因此这是没有办法可以解决。但真的是那样吗?

当然,我们可以使用更大的纹理。更多的纹理元素意味着更多的细节。但是再大也是有上限的。而且,存储大量的、近处才需要的额外数据,是一种极大的浪费。

增加纹理像素密度的另一种方法是平铺纹理。然后,你可以将其变小,显然你会得到一个重复的图案。但这可能不会在近距离观察时特别明显。毕竟,当你站着,鼻子接触墙壁时,你只会看到整个墙壁的一小部分。

因此,我们应该能够通过将不同的平铺纹理相结合来增加细节。要验证这个方式,让我们使用图案明显的纹理。这是一个用方格组成的网格。使用默认的导入设置进行抓取并将其放入你的项目中。我稍微扭曲了网格线,使其变得更有趣并使它可以感知到平铺效果。

(轻微扭曲的网格纹理)

复制“My First Shader ”,并将其重命名为“Textured With Detail”。从现在开始,我们将使用此新着色器。

使用此着色器创建新的材质,然后为其分配网格纹理。

(细节材质,网格纹理)

将材质分配给四边形并对其进行查看。从远处看,它会很好。但是近距离,它将变得越来越模糊。除了缺少细节之外,由纹理压缩引起的失真也会变得很明显。

(网格特写,显示低纹理像素密度和DXT1失真)

1.1 多纹理采样

此时,我们只是在采样一个纹理样本,并将其返回给片段着色器。现在,我们将采样的颜色暂时存储在临时变量中。

可以通过引入平铺纹理来增加纹理像素密度。让我们简单地执行第二个纹理样本,该样本的平铺度是原始样本的十倍。实际上应该替换原始颜色,这里暂时不添加。

这就产生了一个小的多的网格。在它变的更糟糕之前,可以先近距离的观察一下。虽然网格是不规则的,但很明显能看出来这是一个重复的图案。

(硬编码的平铺)

请注意,此时我们正在执行两个纹理采样,但最终仅使用了其中一个。这似乎很浪费。但真是这样嘛?看一下已编译的顶点程序。就像在上一教程中一样,我将包括OpenGLCore和Direct3D 11的相关编译代码。

编译后的代码中只有一个纹理采样。没错,编译器为我们删除了不必要的代码!实际上,它在返回最终结果之前,会丢弃所有未使用的东西。

当然,我们不是要替换原始采样,而是要合并两个采样,将它们相乘即可。但在此之前,我们先再加一个小插曲,先使用完全相同的UV坐标对纹理采样两次。

看看着色器编译器做了啥?

这一次也只进行了一次纹理采样。编译器检测到重复的代码并对其进行了优化。因此纹理仅采样一次。结果存储在寄存器中并重新使用。即使使用中间变量等,编译器也足够聪明,可以检测到此类代码重复。它将所有内容追溯到其原始输入。然后,它会尽可能高效地重组所有内容。

现在放回第二个采样的×10 UV坐标。我们最终将看到大型和小型网格的结合。

(将两个不同的图块相乘)

由于纹理样本不再相同,因此编译器也必须使用其中两个。

1.2 分离细节纹理

将两个纹理相乘时,结果会变更暗。除非至少一种纹理是白色的。这是因为纹理像素的每个颜色通道的值都在0到1之间。在向纹理添加细节时,你可能希望通过变暗,也可以通过变亮来实现。

要使原始纹理变亮,你需要大于1的值。假设最大为2,这会使原始颜色加倍。可以通过在将细节样本与原始颜色相乘之前加倍细节样本来支持。

(双倍细节)

这种方法要求我们重新解释用于细节的纹理。乘以1不会改变任何东西。但是,当我们将细节样本加倍时,现在适用于½。这意味着纯灰色(而非白色)纹理不会产生任何变化。所有低于½的值将使结果变暗,而高于½的任何值将使结果变亮。

因此,我们需要一个特殊的细节纹理,该纹理以灰色为中心。下面是网格的这种纹理。

(网格细节纹理)

细节纹理必须是灰度的吗? 它们不必是灰度的,但通常是灰度的。灰度细节纹理将通过变暗和变亮来严格调整原始颜色。这是相对直接的方式。与非灰色的颜色相乘会产生较不直观的结果。彩色细节纹理用于产生细微的颜色偏移。

要使用此单独的细节纹理,我们必须向着色器添加第二个纹理属性。使用灰色作为默认值,因为这不会改变主纹理的外观。

为我们的材质指定细节纹理,并将其平铺设置为10。

(两个纹理)

当然,我们必须添加变量以访问细节纹理及平铺、偏移数据。

1.3 使用两套UV

用细节纹理的平铺和偏移数据来取代硬编码的x10。在顶点程序中像计算最终UV一样计算最终细节UV。这意味着我们需要增加一个附加的UV对。

通过使用细节纹理的平铺和偏移来转换原始UV,可以创建新的细节UV。

注意在两个编译器顶点程序中如何定义两个UV输出。OpenGLCore使用两个输出,如你所料,vs_TEXCOORD0和vs_TEXCOORD1。相反,Direct3D 11仅使用单个输出o1。我通常在这些代码段中省略了注释部分,但这些注释说明了其工作方式。

这意味着两个UV对都打包到一个输出寄存器中。第一个终止于X和Y通道,第二个终止于Z和W通道。这是可以的,因为寄存器始终是四个数字一组。Direct3D 11编译器利用了这一优势。

可以像这样手动打包输出吗? 是的,可以输出任何想要的东西。因此,将逻辑上分开的信息打包在单个四值输出数据结构中是可行的。如果内插最终成为瓶颈的话,则使用较少的输出寄存器可能会提高着色器的性能。

手动打包输出的常见原因是因为只有几个内插器可用。Shader Model 2硬件支持8个通用插值器,而Shader Model 3硬件支持10个。复杂的着色器可能会受到该限制。

现在,我们可以在片段程序中使用额外的UV对了。

我们的着色器现在可以正常使用了。根据细节纹理,主纹理现在变得更亮和更暗。

(更亮和更暗效果)

1.4 淡化细节

添加细节的想法是,它们可以改善材质的外观,使其近距离处会放大。但不应在远处看到它们或将其放大,因为这会使平铺变得十分明显。因此,我们需要一种随着纹理的显示尺寸减小而淡化细节的方法。通过将细节纹理渐变为灰色来实现此目的,因为这样不会导致颜色变化。

我们之前已经做到了!现在需要做的就是在细节纹理的导入设置中启用Fadeout Mip Maps。请注意,这也会自动将滤镜模式切换为三线性,以便逐渐淡化为灰色。

(淡化细节)

网格使从详细到不详细的过渡非常明显,但是你一般不会注意到它。例如,这是大理石材料的主要纹理和细节纹理。导入它们,并使用与网格纹理相同的纹理导入设置。

(大理石纹理)

当我们的材质使用了这些纹理之后,细节纹理的淡化将不再明显。

(大理石材质)

由于细节纹理,大理石在近距离时看起来要好得多。

(近距离没有和有细节纹理的区别)

1.5 线性颜色空间

现在的着色器在gamma颜色空间中渲染场景时,可以正常工作,但是如果切换到线性颜色空间,则着色器会出错。错误信息为:你使用的颜色空间是项目范围的设置。它是在播放器设置的“Other Settings”面板中配置的,可以通过“Edit/ Project Settings / Player”进行访问。

(选择色彩空间)

什么是伽玛空间? 伽玛空间是指经过伽玛校正的颜色。伽玛校正是对光强度的调整。最简单的方法是将原始值提高到一定的幂,即value gamma。伽马值为1表示没有变化。伽马值为2表示原始值是平方的。

最初引入此转换是为了适应CRT显示监视器的非线性特性。另一个好处是,它也大致对应于我们的眼睛对不同光强度的敏感程度。我们注意到暗色之间的差异大于亮色之间的差异。因此,有意义的是将更多数字位用于较暗的值而不是较亮的值。指数运算可以通过在较大范围内拉伸较低的值,同时压缩较高的值来实现此目的。

sRGB是使用最广泛的图像颜色格式。它使用的公式比简单的幂运算更复杂,但是它存储的平均伽玛值为1 / 2.2的颜色。在许多情况下,这是一个合理的近似值。要将数据转换回原始颜色,请应用2.2的伽玛校正。

(使用gamma 1 / 2.2进行编码,并使用gamma 2.2进行解码)

Unity假定纹理和颜色存储为sRGB。在伽玛空间中渲染时,着色器直接访问原始颜色和纹理数据。这就是我们到目前为止所假设的。

但在线性空间中渲染时,这不再成立。GPU将纹理样本转换为线性空间。同样,Unity还将材质颜色属性转换为线性空间。然后,着色器将使用这些线性颜色进行操作。之后,片段程序的输出会被转换回伽玛空间。

使用线性颜色的优点之一是它可以实现更逼真的照明计算。那是因为光的相互作用在现实生活中是线性的,而不是指数的。但在这里,它会弄乱我们的细节材质。切换到线性空间后,它变得更暗了。为什么会这样?

(Gamma vs. linear 空间)

因为我们将细节纹理样本加倍,所以½的值不会更改主纹理。但是,转换为线性空间会将其更改为½2.2(½的2.2次幂)≈0.22附近。加倍大约为0.44,远小于1。这就是变暗的原因。

可以通过在细节纹理的导入设置中启用“Bypass sRGB Sampling”来解决此错误。这样可以防止从伽马转换为线性空间,因此着色器将始终访问原始图像数据。但是,细节纹理是sRGB图像,因此结果仍然是错误的。

最好的解决方案是重新调整细节颜色,使它们再次围绕1居中。我们可以通过乘以1 /(½2.2)(½的2.2次幂)≈4.59而不是乘以2来做到这一点。但是,只有在线性空间中渲染时才必须这样做。

幸运的是,UnityCG定义了一个统一变量,该变量将包含要乘以的正确数字。它是一个float4,其rgb分量视情况而定为2或大约4.59。由于伽马校正未应用于Alpha通道,因此始终为2。

进行此更改后,无论我们在哪种颜色空间中渲染,我们的细节材质看起来都将相同。

2 纹理Splatting

细节纹理的局限性在于,整个表面都使用相同的细节。这对于均匀的表面(如大理石板)效果很好。但是,如果你的材质外观不统一,或者你不想在各处使用相同的细节的时候呢。

考虑一个大地形。它可以有草,沙,岩石,雪等。你希望近距离详细地描述那些地形类型。但是,覆盖整个地形的纹理将永远没有足够的纹理像素。可以通过为每种表面类型使用单独的纹理并将其平铺来解决。但是你如何知道在哪里使用哪种纹理?

假设我们有一个具有两种不同表面类型的地形。在任何时候,我们都必须决定要使用的表面纹理是第一个还第二个。可以用一个布尔值来表示。如果将其设置为true,则使用第一个纹理,否则使用第二个纹理。使用灰度纹理来存储此选择。值1代表第一纹理,而值0代表第二纹理。实际上,我们可以使用这些值在两个纹理之间进行线性插值。然后介于0和1之间的值表示两个纹理之间的混合。这使得平滑过渡成为可能。

这样的纹理称为splat贴图。就像你将多个地形要素溅撒到画布上一样。由于是插值,该贴图甚至不需要高分辨率。下面是一个小的示例贴图。

(二进制 splat 贴图)

将其添加到项目后,导入类型切换为高级。启用“Bypass sRGB Sampling ”并指示应在线性空间中生成其mipmap。这是必需的,因为该纹理不代表sRGB颜色,而是代表选择。因此,在线性空间中渲染时,不应该对其进行转换。另外,将其“Wrap Mode”设置为“ clamp”,因为我们不会平铺此地图。

(导入设置)

通过复制“My First shader ”并更改其名称来创建新的“ Texture Splatting”着色器。因为地形通常不会统是统一的颜色,所以去掉该功能。

制作一个使用此着色器的新材质,并将splat贴图指定为其主要纹理。因为我们还没有更改着色器,所以它只会显示贴图。

(展示了Splat贴图)

2.1 增加纹理

为了能够在两个纹理之间进行选择,我们必须将它们作为属性添加到着色器中。将它们命名为Texture1和Texture2。

你可以为它们使用任何纹理。我只是选择了我们已经拥有的网格和大理石纹理。

(两个叠加的纹理)

当然,我们可以为添加到着色器中的每个纹理获得平铺和偏移控件。实际上,我们可以为每个纹理分别支持单独的平铺和偏移。但这会需要我们将更多数据从顶点传递到片段着色器,或计算像素着色器中的UV调整。但是通常地形的所有纹理的平铺相同。而且,Splat贴图完全没有平铺。因此,我们只需要一个平铺和偏移来控制实例。

将属性添加到着色器属性,就像在C#代码中一样。NoScaleOffset属性将按照其名称所示进行操作。它却将平铺和偏移称为比例和偏移。这个命名是不一致的。

将此属性添加到额外的纹理中,并保留主纹理的平铺和偏移输入。

这个想法是,平铺和偏移控件显示在我们的着色器检查器的顶部。当它们位于splat贴图旁边时,我们实际上会将它们应用于其他纹理。设置一些平铺值,例如4。

(没有额外的平铺和偏移控件)

现在,我们必须将采样器变量添加到我们的着色器代码中。但是不必添加它们相应的_ST变量。

为了检查我们是否确实可以以此方式对两个纹理进行采样,请更改片段着色器,以便将它们加在一起。

(两个纹理相加)

2.2 使用Splat贴图

要采样splat贴图,我们还必须将未经修改的UV从顶点程序传递到片段程序。

然后,我们可以在对其他纹理进行采样之前对splat贴图进行采样。

确定值为1表示第一个纹理。由于我们的Splat贴图是单色的,因此我们可以使用任何RGB通道来检索此值。我们就使用R通道并将其与纹理相乘。

(调制第一个纹理)

现在,第一个纹理由splat贴图进行了调制。要完成插值,必须将另一个纹理乘以1-R。

(调制两个纹理)

2.3 RGB Splat贴图

我们的splat材质现在仅支持两种纹理。那可以支持更多吗?当然,我们现在仅使用了R通道,那么我们如何同时添加G和B通道呢?然后(1,0,0)代表第一纹理,(0,1,0)代表第二纹理,而(0,0,1)代表第三纹理。为了在这三个之间获得正确的插值,我们只需要确保RGB通道总是相加1。

当我们仅使用一个通道时,我们可以支持两个纹理。那是因为第二个纹理的权重是通过1-R得出的。该技巧适用于任意数量的通道。因此可以通过1-R-G-B支持另一种纹理。

这将产生具有三种颜色和黑色的Splat贴图。只要三个通道加起来不超过1,它就是有效的贴图。下面是一张这样的贴图,导入它并使用与以前相同的导入设置。

(RBG Splat 贴图)

当R + G + B超过1时会发生什么? 那么前三个纹理的组合会过强。同时,第四个纹理将被剔除而不是被添加。如果错误很小,那么你将不会注意到,结果也足够好。示例RGB贴图实际上并不完美,但你应该不会注意到。纹理压缩会引入更多错误,但还是那句,它很难被注意到。

我们也可以使用Alpha通道吗? 确实可以!这意味着一个RGBA splat贴图最多可以支持五种不同的地形类型。但是对于本教程来说,四个就足够了。

如果要使用五个以上的纹理,则必须使用多个splat贴图。虽然是可行的,但最终会产生大量纹理样本。此时,可以用更好的技术实现,例如纹理阵列。

为了支持RGB Splat贴图,我们必须向着色器添加两个其他纹理。我为它们分配了大理石细节和测试纹理。

(四个纹理)

将所需的变量添加到着色器。不需要额外的_ST变量。

在片段程序内,添加额外的纹理样本。现在,第二个示例使用G通道,第三个示例使用B通道。最终样本用(1- R-G-B)调制。

(4个纹理 融合)

为什么混合区域在线性颜色空间中看起来不同? 我们的splat贴图会绕过sRGB采样,因此混合不应该取决于我们使用的颜色空间,对吗?图示贴图确实不受影响。但是混合发生的色彩空间确实发生了变化。

对于伽玛空间渲染,样本将在伽玛空间中混合,仅此而已。但是,当在线性空间中进行渲染时,它们首先会转换为线性空间,然后进行混合,然后再转换回伽玛空间。结果略有不同。在线性空间中,混合也是线性的。但是在伽玛空间中,混合会偏向深色。

现在,你知道了如何应用细节纹理以及如何将多个纹理与splat贴图混合。也可以组合使用这些方法。

可以向splat着色器添加四个细节纹理,并使用贴图在它们之间进行混合。当然,这需要四个额外的纹理样本,因此它不是无代价提供的。

你还可以使用贴图来控制在何处应用细节纹理,以及在何处省略细节纹理。在这种情况下,只需要单色贴图,并且它可以用作蒙版。当单个纹理包含代表不同材质的区域,但其规模不像地形这么巨大时比较有用。例如,如果我们的大理石纹理也包含金属碎片,那么就不希望在其中应用大理石细节。

下一章节介绍光照。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 壹种念头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 细节纹理
    • 1.1 多纹理采样
      • 1.2 分离细节纹理
        • 1.3 使用两套UV
          • 1.4 淡化细节
            • 1.5 线性颜色空间
            • 2 纹理Splatting
              • 2.1 增加纹理
                • 2.2 使用Splat贴图
                  • 2.3 RGB Splat贴图
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档