纹理

请遵循以下最佳实践来优化 Android 游戏中纹理的外观和性能。

纹理是 3D 美术的核心元素。要想让 3D 游戏能够在尽可能多的设备上顺畅运行,首先要对 3D 美术进行精心设计,使其能够充分利用图形处理器。本指南重点介绍在移动设备上可对纹理采取哪些优化措施和最佳实践来提高游戏性能并最大限度降低功耗,同时保持较高的视觉质量。

本文中的部分内容基于由 Arm Limited 贡献并拥有版权的作品。

创建纹理图集

纹理图集是一种精心设计的纹理,它包含多个图形对象(如 3D 网格或 2D 精灵图)的图片数据。我们使用图集纹理来组合来自每个对象的图片,而不是让每个对象都有自己的纹理。

共享纹理图集的网格
图 1. 渲染场景中的黄色突出显示区域(左侧)勾勒出共用纹理图集(右侧)的网格。

最大限度地减少游戏帧的绘制调用次数是实现最佳渲染性能的一个重要因素。对不同对象使用同一纹理是将这些对象合并至单次绘制调用的一个因素。减少绘制调用次数对于受 CPU 限制的游戏来说特别重要,因为每次绘制调用在由图形驱动程序处理时都会产生 CPU 开销。纹理图集还可以减少游戏的运行时数据中的纹理资源文件数。数百甚至数千个纹理可以合并到数量少得多的纹理图集文件中。

创建 3D 网格时,您应规划纹理图集布局。如果是在制作网格资源之前编制的图集,则网格资源必须是按照纹理图集展开的 UV。如果是在编制网格资源之后创建的图集(使用绘图软件中的合并或图集创建工具),则需要根据纹理重新排列 UV 岛。

引擎专用绘制调用批处理

Unity 游戏引擎具有绘制调用批处理功能,该功能可以自动组合对象。若要符合自动批处理的条件,对象必须共用同一种材质(包括纹理),并且被标记为静态。

Unreal Engine 4 需要针对批处理进行手动设置。您可以先在 3D 软件中合并对象,然后再将其导入 Unreal。Unreal 还包含 UE4 Actor Merging 工具,该工具可以组合网格,并创建纹理图集文件。

生成 mipmap

mipmap 是纹理的低分辨率版本。给定纹理的一系列 mipmap 称为 mipmap 链。链中每个后续 mipmap 级别的分辨率都比前一个级别要低。mipmap 用于在渲染过程中实现纹理 LOD(细节级别)。当经过 mipmap 处理的纹理绑定到某个纹理阶段时,图形硬件将使用由某个 fragment 占用的纹理空间从 mipmap 链中选择一个级别。当渲染 3D 场景时,与离镜头较近的同一对象相比,离镜头较远的对象使用的 mipmap 分辨率较低。

与未经过 mipmap 处理的纹理相比,经过 mipmap 处理的纹理占用的内存较多。额外的 mipmap 级别会使纹理的内存占用量增加 33%。如果绘制纹理时与镜头的距离是固定的,那么生成 mipmap 就是不必要地占用内存。

一个 mipmap 链,来自基础分辨率为 512x512 像素的纹理
图 2. 一个 mipmap 链,纹理的基础分辨率为 512x512 像素。

正确使用 mipmap 可以提高 GPU 性能。如果可以使用低分辨率级别的 mipmap,就能够减少内存带宽用量,并改进纹理缓存驻留。

此外,mipmap 处理还可以通过减少纹理走样(锯齿)来提高视觉质量。您可以在离镜头较远的区域中观察到纹理走样,它表现为一种闪烁效果。

纹理走样的示例
图 3. 纹理走样的示例。一张图片在渲染时未采用 mipmap(左侧),另一张图片在渲染时采用了 mipmap(右侧)。您可以在左侧图片中的红色矩形内观察到纹理走样。

引擎专用 mipmap 详细信息

Unreal Engine 4 要求:若要使用 mipmap 处理,纹理尺寸必须是 2 的幂(例如 512x1024 和 128x128)。如果纹理尺寸其中之一或两个都不是 2 的幂,则系统不会生成 mipmap 链。

在创建 mipmap 时,Unity 引擎会自动调整纹理尺寸不是 2 的幂。请确保源纹理文件的尺寸是 2 的幂,以避免这种扩缩。

选择适当的纹理过滤模式

纹理过滤是一项硬件渲染功能,它会影响所渲染三角形的视觉外观。恰当地使用纹理过滤可以提高场景的视觉质量。有多种纹理过滤模式,每种模式都在渲染改进和成本之间取得了不同的平衡。成本包括计算时间和内存带宽。三种常用的纹理过滤模式分别是:最近邻(点)、双线性和三线性。各向异性是一种额外的纹理过滤方法,这种方法可以与双线性或三线性过滤结合使用。

最近邻

最近邻纹理过滤模式是最简单同时也是成本最低的纹理过滤模式。最近邻纹理过滤模式会使用源纹理中指定的坐标对单个纹素进行采样。使用最近邻纹理过滤模式渲染的三角形会有块状或像素化的外观,特别是在靠近镜头渲染时。

双线性

双线性过滤会对源纹理中指定坐标周围的四个纹素进行采样。系统会计算这四个纹素的平均值,以确定相应 fragment 的纹理颜色。双线性过滤可使像素之间的渐变更流畅,从而避免最近邻过滤所产生的块状外观。靠近镜头渲染的三角形会显得模糊,而不是像素化。由于额外的纹素样本和平均值计算,双线性过滤的成本高于最近邻过滤。

最近邻过滤与双线性过滤的比较
图 4. 最近邻纹理过滤(左侧)与双线性纹理过滤(右侧)的比较。

三线性

当您渲染一个网格时,如果各个顶点与镜头的距离不同,那么可能会在渲染过程中选择多个 mipmap 级别。两个 mipmap 级别之间的变化可能会导致在过渡点出现明显的尖锐切口。三线性过滤通过在两个不同的 mipmap 级别执行双线性过滤并根据结果计算插值来使此类过渡变缓和。多个 mipmap 级别和插值的使用使得三线性过滤的计算成本高于双线性过滤。

双线性过滤与三线性过滤的比较
图 5. 双线性纹理过滤(左侧)与三线性纹理过滤(右侧)的比较。缩放的区域对比了沿着 mipmap 过渡渲染的差异。

各向异性

各向异性过滤可以提高带纹理的网格的视觉质量,这些网格以相对于镜头的极端角度渲染。地平面就是这种网格的一个常见例子。各向异性过滤需要经过 mipmap 处理的纹理来发挥作用。可以配置在渲染过程中应用的各向异性过滤的比率或级别。各向异性过滤的成本会随着级别的提高而增加。

1 倍各向异性过滤与 2 倍各向异性过滤的比较
图 6. 双线性/1 倍各向异性过滤(左侧)与双线性/2 倍各向异性过滤(右侧)的比较

模式选择策略

双线性过滤通常能够在性能与视觉质量之间实现最佳平衡。三线性过滤需要明显更多的内存带宽,因而应该有选择地使用这种模式。在许多情况下,双线性过滤结合 2 倍各向异性过滤的显示效果和性能比三线性过滤结合 1 倍各向异性过滤要好。如果将各向异性过滤级别提高到超过 2 倍,成本会极其高昂,因而应该针对关键的游戏资源非常有选择地执行这种过滤。

纹理过滤可能最多占 GPU 总耗电量的一半,尽可能选择比较简单的纹理过滤模式是降低游戏电量需求的一种绝佳方式。

优化纹理大小

请确保您的纹理尺寸尽可能小,同时仍然能够达到您想要的图片质量。请查看您的纹理资源,检查纹理尺寸是否超出合理范围。这一原则同时适用于离散纹理和图集纹理。如果您的游戏支持许多设备,这些设备囊括各种各样的分辨率和性能能力,那么不妨考虑针对适当的设备类别创建纹理资源的低分辨率和高分辨率版本。

如果渲染的网格在其材质中使用多个纹理,那么不妨考虑有选择地降低某些纹理的分辨率。例如,使用 1024x1024 的漫射纹理时,有可能将粗糙度或金属贴图纹理减小到 512x512 之后,对图片质量产生的影响微乎其微。请验证所有此类大小调整实验所产生的影响,以确保它们不会危及您想要达到的质量级别。

使用适当的颜色空间

许多用于纹理编制的软件包使用 sRGB 颜色空间来运行和导出。作为颜色处理的漫射纹理可能会使用 sRGB 颜色空间。并非作为颜色处理的纹理(如金属、粗糙度或法线贴图)不应以 sRGB 颜色空间导出。

游戏引擎纹理设置包含一个用于设置纹理是否使用 sRGB 颜色空间的参数。

Unity 和 Unreal Engine 4 中的 sRGB 纹理设置
图 7. Unity(左侧)和 Unreal Engine 4(右侧)中的 sRGB 纹理设置。

由于此类纹理的像素数据并不用作颜色数据,因此使用 sRGB 颜色空间会生成错误的画面。

以线性方式渲染与以 sRGB 颜色空间渲染粗糙度金属贴图的比较
图 8. 线性(非 sRGB)粗糙度金属贴图(左侧)和 sRGB 粗糙度金属贴图(右侧)。右侧的反射看起来不正确。

使用纹理压缩

纹理压缩是一种应用于未压缩像素数据的图片压缩算法,生成的纹理可以在渲染过程中由图形硬件快速解压缩。有效使用纹理压缩可以减少内存用量并提高性能,同时对视觉质量的影响微乎其微。Android 最常用的三种纹理压缩算法分别是:ETC1、ETC2 和 ASTC。对于现代游戏来说,ASTC 通常是最好的主要方案,如果游戏的目标设备不支持 ASTC,那么 ETC2 就是一种备选方案。

ETC1

所有 Android 设备都支持 ETC1。ETC1 仅支持 RGB 颜色数据的一种每像素四位模式。ETC1 不支持 alpha 通道。许多支持 ETC1 的游戏引擎允许指定第二个 ETC1 纹理,用来表示 alpha 通道数据。

ETC2

超过 90% 的现用 Android 设备支持 ETC2。不支持 OpenGL ES 3.0 API 的非常旧的设备无法使用 ETC2。与 ETC1 相比,ETC2 添加了:

  • alpha 通道支持,八位和一位“穿透”
  • RGB 和 RGBA 纹理的 sRGB 版本
  • 一个和两个通道(R11 和 RG11)纹理

ASTC

超过 75% 的现用 Android 设备支持 ASTC。ASTC 具有可配置的压缩块大小,这样可让您进行精细控制,以平衡给定纹理的压缩比率与图片质量。通常,与 ETC2 相比,占用同样多的内存时,ASTC 能够达到较高的质量,而达到相近的质量时,ASTC 占用的内存较少。

使用同一源图片的纹理压缩格式的视觉比较
图 9. 未压缩的图片(左侧,大小为 17MB)、使用 ETC1 压缩的图片(中间,大小为 3MB)和使用 ASTC 压缩的图片(右侧,大小为 2.5MB)的比较。

纹理压缩速度

如果您的游戏具有大量的纹理,那么纹理压缩可能需要很长时间。ETC 和 ASTC 都有可选择的压缩质量设置。设置的质量越高,需要的压缩时间就越长。在开发过程中,在创建重要的 build 之前,您可能需要先降低质量级别以缩短压缩时间,然后再提高质量级别。

游戏引擎中的纹理压缩

如果您使用游戏引擎,您可能必须在项目级别选择纹理压缩格式(ETC 或 ASTC)。如需支持多种压缩格式以实现最大的兼容性,可能需要执行额外的操作。Google Play Asset Delivery 的纹理压缩格式定位功能有助于在游戏中包含多种格式,而在安装时只将最佳格式提供给各个设备。

展开 UV

应让 UV 岛尽可能直。这在以下几个方面对纹理有帮助:

  • 打包 UV 岛更容易,从而减少浪费的空间。
  • 直 UV 可以减少纹理的“阶梯效应”。
  • 良好的 UV 打包可以确保从纹理获得最佳分辨率。
  • 更高质量的纹理制作,即使 UV 因拉直而略有失真。
未优化的 UV 岛与优化后的 UV 岛的比较
图 10. 未优化的 UV 岛(左侧)和拉直/展开的 UV 岛(右侧)。

模型上明显的纹理接缝很难看。请设法将所有 UV 接缝放在不太显眼的位置。为了帮助创建更好的法线贴图,请在边缘尖锐的位置分割 UV 岛,并在岛周围留下一些空间。

避免察觉不到的细节

创建美术图片时,请不要添加看不到的细节,特别是对于专为屏幕较小的设备而设计的游戏。为一个在房间角落里几乎看不到的小椅子模型制作繁复详细的 4096x4096 纹理是一种浪费。在某些情况下,您可能需要扩大边缘(添加额外的高光)和阴影以改善形状感知。

对在远处渲染的模型使用小纹理
图 11. 对在远处渲染的士兵模型使用缺少过多细节的 256x256 小纹理。

烘焙细节

与个人计算机或游戏机相比,移动设备的屏幕较小且图形硬件性能较低。与其在运行时计算环境光遮蔽或镜面高光等效果,不如考虑在可能的情况下将其“烘焙”成漫射纹理。这样有助于提高性能,还能确保细节的可见性。

将高光和环境光遮蔽烘焙成漫射纹理
图 12. 高光和环境光遮蔽已烘焙到漫射纹理(左侧)和游戏内渲染(右侧)中。

使用色调调节

如果您能够创建自定义着色器,并且某些网格具有类似或统一的配色方案,那么不妨考虑对适用的网格使用色调调节。在进行色调调节时,系统会使用灰度纹理,这种纹理占用的纹理内存比 RGB 纹理要少。着色器会应用每个顶点的颜色数据来为网格着色。另一种色调调节方法是使用 RGB 蒙版,并根据蒙版的颜色范围来应用纹理。

在运行时进行色调调节的灰度纹理
图 13. 在运行时为柱子模型(右侧)进行色调调节的灰度纹理(左侧)。

打包纹理通道

在渲染具有多个纹理的材质时,应寻找机会将仅使用单个颜色通道的纹理组合成使用全部三个颜色通道的单个纹理。这样不仅可以减少内存用量,而且还能减少由 fragment 着色器执行的纹理采样器操作次数。

三个单通道纹理组合成一个多通道纹理
图 14. 三个单通道纹理(左侧)组合成一个多通道纹理(右侧)。将环境光遮蔽数据分配给了红色,将粗糙度/平滑度贴图分配给了绿色,将金属贴图分配给了蓝色。

打包时,应将包含最多细节的数据分配给绿色通道。由于人眼对绿色更敏感,因此图形硬件通常会将更多的位分配给绿色通道。例如,粗糙度/平滑度贴图包含的细节通常比金属贴图要多,因而更适合分配给绿色通道。

对于使用 alpha 通道的材质,如果您只在打包的纹理中使用两个通道,那么不妨考虑将 alpha 通道数据放入打包的纹理而不是漫射纹理。根据漫射纹理的格式,这样可以帮助您减小其大小,或者通过省略 alpha 通道数据来提高其视觉质量。

alpha 通道打包到另一个纹理中
图 15. alpha 通道不透明贴图连同粗糙度/平滑度贴图和金属贴图一起打包到一个纹理中。

请确保将打包的纹理设置为线性 RGB 颜色空间而不是 sRGB。

创建法线贴图

法线贴图这种技术无需使用额外的几何图形就能赋予 3D 模型细节外观。褶皱或螺栓等可能需要建模许多三角形的特征可以使用法线贴图来模拟。根据游戏的艺术风格和方向,法线贴图可能适合,也可能不适合。

使用和不使用法线贴图渲染的模型
图 16. 不使用法线贴图渲染的模型(左侧)和使用法线贴图渲染的同一模型(中间)以及法线贴图纹理(右侧)。

法线贴图确实会产生一些性能成本,应该在低端设备上谨慎使用。法线贴图需要额外的纹理,从而导致额外的纹理采样和 fragment 着色器计算。

法线贴图最佳做法

下面是关于创建法线贴图的一些最佳实践:

使用笼子

笼子是低多边形模型的较大或推出版本。它需要包围高多边形模型,以便在法线贴图烘焙过程中发挥很好的作用。笼子用于限制法线贴图烘焙过程中的光线投射距离,并帮助避免法线贴图上的法线接缝裂开的问题。

一个围绕低多边形网格的笼子
图 17. 一个围绕低多边形网格的笼子。
使用法线贴图渲染的模型,带笼子和不带笼子的效果
图 18. 使用法线贴图渲染且生成后带笼子的模型(左侧)与使用法线贴图渲染且生成后不带笼子的模型(右侧)的比较。

按网格名称进行烘焙匹配

如果您的烘焙软件支持,请按网格名称进行烘焙匹配。此功能可以缓解错误法线贴图投影的问题。当对象彼此离得太近时,它们可能会意外地将其法线贴图投影到错误的面上。按网格名称进行匹配可以确保只在正确的表面上进行烘焙。如需详细了解 Substance Painter 中的这一功能,请参阅此页。如需详细了解 Marmoset Toolbag 中的这一功能,请参阅此页

分解网格

如果您在烘焙时无法按网格名称进行匹配,那么不妨考虑分解网格。分解网格会使各个部分彼此远离,这样法线贴图就不会投影到错误的表面上。如果您还针对环境光遮蔽进行烘焙,那么可能需要使用未分解的网格单独执行该烘焙。

分解的网格,用于法线贴图烘焙
图 19. 分解的网格,用于法线贴图烘焙

最大限度地减少接缝

硬边缘上的连续 UV 会导致产生明显的接缝,应在硬边缘上分割 UV 以最大限度地减轻这种影响。设置平滑组时,一般来讲,应使角度小于 90 度。UV 接缝需要在三角形上有不同的平滑组。