本篇对《Real-Time Rendering》一书中纹理相关知识进行概括总结。主要内容包括:
纹理管线(Texture Pipeline)
纹理缓存(Texture Caching)
纹理压缩(Texture Compression)
体纹理(Volume Texture)
立方体贴图(Cube Map)
程序纹理(Procedural Texturing)
凹凸贴图(Bump Mapping)及其改进
1 纹理管线
简单来说,纹理(Texturing)是一种针对物体表面属性进行“建模”的高效技术。图像纹理中的像素通常被称为纹素(Texels),以区别于屏幕上的像素。根据 Kershaw 的术语,通过将投影方程(projector function)运用于空间中的点 ,从而得到一组称为参数空间值(parameter-spacevalues)的关于纹理的数值。这个过程就称为贴图(Mapping,也称映射 ),也就是纹理贴图(Texture Mapping,也称纹理映射 )这个词的由来。纹理贴图可以用一个通用的纹理管线来进行描述。纹理贴图过程的初始点是空间中的一个位置。这个位置可以基于世界空间,但是更常见的是基于模型空间。因为若此位置是基于模型空间的,当模型移动时,其纹理才会随之移动。
下图展示了单个纹理的通用管线:
- 第一步。通过将投影方程(projector function)运用于空间中的点 ,从而得到一组称为参数空间值(parameter-space values)的关于纹理的数值,即 uv 坐标,在(0, 1)范围内。
- 第二步。在使用这些新值访问纹理之前,可以使用一个或者多个映射函数(corresponder function)将参数空间值(parameter-space values )转换到纹理空间,即对 uv坐标进行平移和缩放以映射到纹理空间中。
- 第三步。使用这些纹理空间值(texture-space locations)从纹理中获取相应的值(obtain value)。例如,可以使用图像纹理的数组索引来检索像素值。
- 第四步。再使用值变换函数(value transform function)对检索结果进行值变换,最后使用得到的新值来改变表面属性,如材质或者着色法线等等。
下图通过一个例子描述了上述过程:
1.1 投影函数
作为纹理管线的第一步,投影函数的功能就是将空间中的三维点转化为纹理坐标,也就是获取表面的位置并将其投影到参数空间中。
在常规情况下,投影函数通常在美术建模阶段使用,并将投影结果存储于顶点数据中。也就是说,在软件开发过程中,我们一般不会去用投影函数计算得到投影结果,而是直接使用在美术建模过程中,已经存储在模型顶点数据中的投影结果。
1.2 映射函数
映射函数(The Corresponder Function)的作用是将参数空间坐标(parameter-space coordinates)转换为纹理空间位置(texture space locations)。我们知道图像会出现在物体表面的 (u,v) 位置上,且 uv 值的正常范围在 [0, 1) 范围内。超出这个值域的纹理,其显示方式便可以由映射函数(The Corresponder Function)来决定。
在 OpenGL 中,这类映射函数称为“封装模式(Warapping Mode)”,在 Direct3D 中,这类函数叫做“寻址模式(Texture Addressing Mode)”。最常见的映射函数有以下几种:
- 重复寻址模式,wrap (DirectX),repeat (OpenGL)。图像在表面上重复出现。
- 镜像寻址模式,mirror。图像在物体表面上不断重复,但每次重复时对图像进行镜像或者反转。
- 夹取寻址模式,clamp (DirectX),clamp to edge (OpenGL)。夹取纹理寻址模式将纹理坐标夹取在 [0.0, 1.0] 之间,也就是说,在[0.0, 1.0] 之间就是把纹理复制一遍,然后对于 [0.0, 1.0] 之外的内容,将边缘的内容沿着 u 轴和 v 轴进行延伸。
- 边框颜色寻址模式,border (DirectX),clamp to border (OpenGL)。边框颜色寻址模式就是在 [0.0, 1.0] 之间绘制纹理,然后 [0.0, 1.0] 之外的内容就用边框颜色填充。
下图展示了上述四种寻址模式:
另外,每个纹理轴可以使用不同的映射函数。例如在 u 轴使用重复寻址模式,在 v 轴使用取寻址模式。
2 纹理缓存
一个复杂的应用程序可能需要相当数量的纹理。快速纹理存储器的数量因系统而异,但你会发现它们永远不够用。于是有了各种各样的纹理缓存(texture caching)技术,但我们一直在上传纹理到内存的开销和纹理单次消耗的内存量之间寻求一个好的平衡点。比如,一个由纹理贴图的多边形对象,初始化在离相机很远的位置,程序也许会只加载 mipmap 中更小的子纹理,就可以很完美的完成这个对象的显示了。
一些基本的建议是——保持纹理在不需要放大再用的前提下尽可能小,并尝试基于多边形将纹理分组。即便所有纹理都一直存储在内存中,这种预防措施也可能会提高处理器的缓存性能。
常见的缓存策略有以下几种,许多都和操作系统中的缓存策略一致:
- 最近最少使用策略(Least Recently Used ,LRU)。LRU 是纹理缓存方案中常用的一种策略,其原理为:加载到图形加速器的内存中的每个纹理都被给出一个时间戳,记录最后一次访问以渲染图像的时间。当需要空间来加载新的纹理时,首先卸载最旧时间戳的纹理。一些 API 还允许为每个纹理设置一个优先级:如果两个纹理的时间戳相同,则优先级较低的纹理首先被卸载。 设置优先级可以帮助避免不必要的纹理交换。
- 最近最常使用策略(Most Recently Used,MRU)。MRU 如其名称一样,总是替换掉使用的最少的纹理缓存。鉴于如果在当前帧中载入纹理,会发生抖动(Thrashing)的情况。所谓抖动就是纹理数据集远大于缓存空间,这样就会出现频繁替换纹理缓存的现象,就称为抖动。这时,LRU 策略是一种非常不好的策略,因为在每帧画面中会对每张纹理图像进行交换。在这种情况下,可以采用 MRU 策略,直到在画面中没有纹理交换时为止,再然后切换回 LRU。
- 预取策略(Prefetching)。加载纹理花费显着的时间,特别是在需将纹理转换为硬件原生格式时。 纹理加载在每个框架可以有很大的不同。在单个帧中加载大量纹理使得难以保持恒定的帧速率。一种解决方案是使用预取(prefetching),在将来需要预期的情况下,预计未来的需求然后加载纹理,将加载过程分摊在多帧中。
- 裁剪图策略(Clipmap)。对于飞行模拟和地型模拟系统,图像数据集可能会非常巨大。传统的方法是将这些图像分解成更小的硬件可以处理的瓦片地图(tiles)。Tanner 等人提出了一种一种称为裁剪图(clipmap)的改进数据结构。其思想是,将整个数据集视为一个 mipmap,但是对于任何特定视图,只需要 mipmap 的较低级别的一小部分即可。支持 DirectX 10 的 GPU 就能够实现 clipmap 技术。
3 纹理压缩
直接解决内存和带宽问题和缓存问题的一个解决方案是固定速率纹理压缩(Fixed-rate Texture Compression)。通过硬件解码压缩纹理,纹理可以需要更少的纹理内存,从而增加有效的高速缓存大小。至少这样的纹理使用起来更高效,因为他们在访问时消耗更少的内存带宽。
纹理压缩算法种类繁多,但基本的共同点是:把纹理按 4x4 个单元(纹素)大小划分为块。每个块对应一张四色查找表,表中存有两个标准 RGB565 格式表示的 16 位颜色,另外使用标准插入算法在插入两个新的颜色值,由此构成四色查找表。4x4 大小的纹理块中每个单元(像素点)用两个 bit 表示,每一个都代表四色查找表中的一种颜色。可以看出,实质上是利用每个单元(像素点)中的两个 bit 来索引四色查找表中的颜色值。
这些压缩技术可以应用于立方体或体积图,以及二维纹理。而其主要缺点是它们是有损的压缩。 也就是说,原始图像通常不能从压缩版本检索。 仅使用四个或八个内插值来表示 16 个像素。 如果一个瓦片贴图有更大的数值,相较压缩前就会有一些损失。 在实践中,如果正常使用这些压缩方案,一般需给出可接受的图像保真度。
4 体纹理
体纹理(volume texture),也称为三维纹理(3D texture),是传统二维纹理(2D texture)在逻辑上的扩展。二维纹理是一张简单的位图图片,用于为三维模型提供表面点的颜色值;而一个三维纹理,可以被认为由很多张 2D 纹理组成,用于描述三维空间数据的图片。三维纹理通过三维纹理坐标进行访问。
虽然体纹理具有更高的储存要求,并且滤波成本更高,但它们具有一些独特的优势:
- 使用体纹理,可以跳过为三维网格确定良好二维参数的复杂过程,因为三维位置可以直接用作纹理坐标,从而避免了二维参数化中通常会发生的变形和接缝问题。
- 体纹理也可用于表示诸如木材或大理石的材料的体积结构。使用三维纹理实现出的这些模型,看起来会很逼真,浑然天成。
体纹理的缺点也很明显:
- 使用体纹理作为表面纹理会非常低效,因为三维纹理中的绝大多数样本都没起到作用。
5 立方体贴图
立方体贴图(cube map)也称立方体纹理(cube texture),是一种特殊的纹理技术,它用 6 幅二维纹理图像构成一个以原点为中心的纹理立方体,这每个 2D 纹理是一个立方体(cube)的一个面。对于每个片段,纹理坐标 (s, t, r) 被当作方向向量看待,每个纹素(texel)都表示从原点所看到的纹理立方体上的图像。
下图以两种方式展示了立方体贴图:
可以使用三分量纹理坐标向量来访问立方体贴图中的数据,该矢量指定了从立方体中心向外指向的光线的方向。选择具有最大绝对值的纹理坐标对应的相应的面。例如给定矢量(-3.2, 5.1, -8.4),选择绝对值最大的 -8.4 所在的 -Z 面,然后将其他两维除以最大的绝对值,就映射到了(-1, 1),然后再映射到(0, 1)范围就可以在 -Z 面上得到一个纹理坐标了。
6 程序纹理
给定纹理空间位置,进行图像查找是生成纹理值的一种方法。另一种方法是对函数进行求值,从而得到一个程序贴图纹理(procedural texture)。
程序贴图纹理也称为过程纹理,是用计算机算法生成的,旨在创建用于纹理映射的自然元素(例如木材,大理石,花岗岩,金属,石头等)的真实表面或三维物体而创建的纹理图像。通常,会使用分形噪声(fractal noise)和湍流扰动函数(turbulence functions)这类“随机性”的函数来生成程序贴图纹理。
程序纹理通常用于离线渲染应用程序,而图像纹理在实时渲染中更为常见。这是由于在现代 GPU 中的图像纹理硬件有着极高效率,其可以在一秒钟内执行数十亿个纹理访问。然而,GPU 架构正在朝着更便宜的计算能力和(相对)更昂贵的存储器访问而发展。这将使程序纹理在实时应用程序中更常见,尽管它们不可能完全替代图像纹理。
关于程序纹理中常见的噪声,之后有专门的文章总结。
7 凹凸贴图及其改进
与凹凸贴图相关的改进技术包括:位移贴图、法线贴图、视差贴图、浮雕贴图等,除了位移贴图方法以外,其他的几种改进一般都是通过修改每像素着色方程来实现,关键思想是访问纹理来修改表面的法线,而不是改变光照方程中的颜色分量。物体表面的几何法线保持不变,我们修改的只是着色方程中使用的法线值。他们比单独的纹理有更好的三维感官,但是显然还是比不上实际的三维几何体。下面将分别说明这几种方法。
7.1 凹凸贴图
凹凸贴图是通过改变表面光照方程的法线,而不是表面的几何法线,或对每个待渲染的像素在计算照明之前都要加上一个从高度图中找到的扰动,来模拟凹凸不平的视觉特征,如褶皱、波浪等等。
Blinn 于 1978 年提出了凹凸贴图方法。使用凹凸贴图,是为了给光滑的平面,在不增加顶点的情况下,增加一些凹凸的变化。他的原理是通过法向量的变化,来产生光影的变化,从而产生凹凸感。实际上并没有几何顶点上的变化。所以下图中球体边缘还是光滑的:
7.2 位移贴图
移位贴图(Displacement Mapping)也有人称为置换贴图,或称高度纹理贴图(Heightfield Texturing)。这种方法类似于法线贴图,移位贴图的每一个纹素中存储了一个向量,这个向量代表了对应顶点的位移。注意,此处的纹素并不是与像素一一对应,而是与顶点一一对应,因此,纹理的纹素个数与网格的顶点个数是相等的。在 Vertex Shader 阶段,获取每个顶点对应的纹素中的位移向量,施加到局部坐标系下的顶点上,然后进行世界视点投影变换即可。位移贴图真正改变了顶点位置。
7.3 法线贴图
法线贴图(Normal mapping)是凸凹贴图(Bump mapping)技术的一种应用。简单来说,Normal Map 直接将正确的 Normal 值保存到一张纹理中去,那么在使用的时候直接从贴图中取即可。为了使法线贴图能应用于不同的物体,所以法线贴图中存储的法线值一般是表面顶点切线空间下的法线值,所谓切线空间是指以顶点切线为 x 轴,副切线为 y 轴,顶点法线为 z 轴的空间,相比于存储模型空间下的法线值,切线空间下的法线值是一个相对值,可以应用到不同的模型上而不至于出错。
上图展示了基于法线贴图的凹凸映射,左侧为法线贴图,每个颜色通道实际上是表面法线坐标。红色通道是 x 偏差; 红色越多,正常点越多。 绿色是 y 偏差,蓝色是 z。 右边是使用法线贴图生成的图像。 请注意立方体顶部的扁平外观,法线贴图显然没有改变几何形状。
7.4 视差贴图
视差贴图(Parallax Mapping),又称为 Offset Mapping,或 virtual displacement mapping,视差贴图是一种改进的 Bump Mappin 技术,相较于普通的凹凸贴图,视差贴图技术得到凹凸效果得会更具真实感(如石墙的纹理将有更明显的深度)。视差贴图是通过替换渲染多边形上的顶点处的纹理坐标来实现的,而这个替换依赖于一个关于切线空间中的视角(相对于表面法线的角度)和在该点上的高度图的方程。简单来说,Parallax Mapping 利用 Height Map 进行了近似的 Texture Offset。如下图所示:
7.5 浮雕贴图
浮雕贴图(Relief Mapping),有人把它誉为凹凸贴图的极致。我们知道,Parallax Mapping 是针对 Normal Mapping 的改进,利用 HeightMap 进行了近似的 Texture Offset。而 Relief Mapping 是精确的 Texture Offset,所以在表现力上比较完美。
Parallax Mapping 能够提供比 Bump Mapping 更多的深度,尤其相比于小视角下,但是如果想提供更深的深度,Parallax Mapping 就无能为力了,Relief Mapping 则可以很好的胜任。相较于 Parallax Mapping,浮雕贴图可以实现更深的凹凸深度,并且还可以做出自阴影和闭塞效果。当然算法也稍稍有点复杂,具体细节可以参考这篇中文文献:Relief mapping:凹凸贴图的极致,而如果要用一句话概括 Relief Mapping,将会是:“在 Shader 里做光线追踪”。
下图是法线贴图和浮雕贴图的对比,浮雕贴图可以实现自阴影:
下图是视差贴图和浮雕贴图的对比,浮雕贴图可以实现更深的凹凸深度: