GPU编程中如何进行纹理优化?

纹理是GPU编程中最常见的资源类型之一,因此针对纹理,我们可以有很多优化的方式。无论是哪种优化方式,都会基于以下核心思路:
(1)显存减少
(2)带宽降低
(3)缓存友好
(4)采样次数减少

以上几点并不是完全独立的概念。比如当我们降低贴图尺寸的时候,首先最直观的是我们降低了显存占用;当显存占用降低时,传输的数据量就会减少,带宽压力就会变小;此外,显存越小,缓存命中率也会更高。

另一方面,纹理有多种类型,在优化过程中,我们可以将其概述为两类,一类是美术资产,另一类是程序创建的纹理,针对这两种不同类型的纹理,我们有不同的优化策略。

纹理占用

纹理优化一方面的思路就是从纹理本身下手。

① 制定美术纹理资源大小格式标准。纹理分辨率并不是越大越好的,它受限于纹理采样的屏幕大小;

② 对效果影响不大的情况下,可以选择降低一些Render Target的分辨率,比如一些后处理或粒子绘制;

③ 对纹理进行压缩处理。对于美术纹理,一般会使用常见的几个块状压缩算法,通过CPU压缩,并在GPU中解压并读取,因为是按块压缩,所以解压时也只需要按所需块解压即可。这通常需要硬件支持解压。同时,硬件可能也会支持FrameBuffer的自动压缩。

纹理mipmap

从信号学的角度来看,为了不失真地恢复信号,采样频率应该大于模拟信号最高频率的2倍。纹理信息虽然本身并不是模拟信号,但纹理采样和信号采样也具有一定的相似之处。当我们以较低频率去采样高频纹理时,由于采样的像素是不连续的,就有可能出现失真的现象。

所谓的“较低频率”采样往往发生在离相机较远的物体上,采样的不连续可能会导致:
① 静态效果存在失真
② 运动时会发生闪烁
③ 缓存命中率低

为了解决这个问题,我们引入了mipmap的机制,即多级渐远纹理,也就是在原纹理的基础上,生成一系列纹理,每个纹理是前一个纹理大小的1/2,这些系列纹理一般是算法自动生成的。

引入mipmap后:

① 内存占用会增加,且理论上不会超过原来的2倍,但通过纹理池流式加载优化后可以降低内存占用;

② 带宽压力理论上会减小,因为原本需要传输高精度纹理,现在只需要传输低精度纹理;

③ 采样纹理时会在一系列纹理中选择合适大小的纹理进行采样,选择的策略包含如下几种:
(1)使用最邻近的多级渐近纹理采样;
(2)在两个最接近像素大小的多级渐远纹理间进行线性插值;

选择线性插值的方式,运动时闪烁的现象就会得到优化,但采样次数会增加。

在生成mipmap纹理时,如果单纯使用简单的降采样,在效果上和不做mipmap就没有太多差距。一般而言,会有多种压缩方式,包括锐化/模糊多种不同类型的压缩。

因此,除了一些和相机位置无关的纹理贴图(比如天空盒),绝大多数纹理都应该默认生成mipmap。

纹理过滤

纹理坐标是分布在(0,1) 之间的值。这是一个与分辨率无关的值,因此uv坐标和像素是无法一一对应的,我们面临着究竟应该采样哪个像素的问题。

通常有以下两种采样方式:
(1)邻近过滤
(2)线性过滤

在实际采样中,我们可能遇到屏幕纹理像素大于或者小于实际纹理像素的情况。我们可以为这两种不同的情况指定不同的纹理过滤形式。

当物体表面倾斜(三角形法线和视线接近垂直)时,由于uv坐标的变化率有较大差异,使用传统的双线性纹理采样会出现失真的现象。

为了解决这一问题,我们可以使用各向异性采样过滤技术,对最大变化方向会采样更多的纹理。我们可以指定采样的品质,品质越高,采样的次数也会越多。

当我们在shader中调用一次纹理采样的接口时,根据我们纹理过滤的策略不同,底层可能执行了不同次数的纹理采样。如下所示:

纹理过滤 采样次数
最邻近采样 1
双线性采样 4
三线性采样 8
各向异性采样 1X 8
各向异性采样 2X 16
各向异性采样 4X 32
各向异性采样 8X 64
各向异性采样 16X 128


可以看到三线性采样和各向异性采样消耗非常大,如果不是特别有必要,应该尽量避免使用。

纹理采样和绑定

① 贴图合通道
为了尽可能减少采样次数和绑定次数,利用所有贴图通道,可以把把访问时具有关联性的数据存放到一起。
比如原先是1个2通道的纹理和2个1通道的纹理,可以合并为一张纹理。
法线通常压缩为2通道存储。
没有用到alpha通道的纹理存储为RGB,而不是RGBA。

② 使用Texture Array / Bindless Texture/Texture Altas
这两者主要是优化纹理的绑定次数,可以把多张贴图打包到一个图集,texturearray会限制大小和格式一致。但采样的时候还是需要单独采样的。

③ 减少贴图使用
一些可以用数学计算得到的效果可以不使用纹理;
如果一张纹理可以通过另一张纹理的简单计算得到,则不需要两张纹理;
如果可以用多张小纹理混合能够表达比较丰富的效果,就不要采样整体烘焙的形式,比如地形;
概括来说,就是尽可能使用程序化制作的思路去制作材质,而不是所有东西都靠美术去绘制。

④ 纹理采样的硬件优化
在像素着色器中,硬件会根据顶点传入的坐标值预加载贴图,因此读取贴图的效率会很高。但如果使用了其它uv坐标,就等于没有用到这个优化。

纹理与Buffer

考虑到纹理通常是块状存储,Buffer通常是线性存储。为了能够更好地命中缓存,应该根据实际使用情况选择纹理或者Buffer来存储数据。

纹理通常用于存储美术纹理资产,Buffer通常用于存储不应压缩的数据信息。

本文摘自:CSDN博主「ZJU_fish1996」的原创文章《[引擎开发] 深入GPU和渲染优化(基础篇)》,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ZJU_fish1996/article/details/109269448

最新文章