实时渲染 — 纹理(Texture)

纹理映射(Texture Mapping)

纹理(Texture) 相当于着色物体的"皮肤",负责提供基础颜色,而为了方便表示纹理(可以想象下,一个3D物体的皮肤其实是可以展开成一张平面),往往使用一个二维颜色数组去表示纹理。

于是纹理平面就有了自己的坐标系(纹理空间),通常用u、v表示坐标轴。 

在之前的着色(Shading)计算中,光照计算只是计算了物体受到多少光照的影响(可以粗略理解成亮度,因为有些光本身也会提供颜色),而纹理则可以给这些物体提供基础颜色。

为了使用纹理,对于每个三角形顶点vertex属性额外需要存储u、v坐标以便映射到纹理空间(由于三角形也是一个平面,因此非常方便映射到平面的纹理空间),三角形内的点则只需要根据三角重心坐标插值也能算出对应的u、v坐标。这样,三角形每个像素点就可以找到纹理中对应位置的颜色。

在某些情形下,我们可能需要将空间中的曲面(一般是指凸多面体)映射到纹理平面,即 f: R^3 
ightarrow R^2; (x,y,z)
ightarrow (u,v) ;  ,那么就需要借助 球形贴图(Spherical Map) 或者 立方体贴图(Cube Map)

球形贴图(Spherical Map)

所谓球形贴图,就是以球的形式映射贴图。

例如一个球面展开后,会得到这样一张贴图(但是球形贴图看起来会有一种扭曲现象,表现不直观):

球形贴图映射的思路:

  1. 假设一个单位球包围了物体中心,当物体中心看向物体表面某个位置 (x,y,z)时,从中心朝这个位置发出一条射线,此时射线会与单位球相交于某点。
  2. 根据射线与单位球体的交点坐标 (xo,yo,zo),推算出交点所在的偏航角和俯角 (yaw,pitch),然后来映射成在球形贴图对应的 (u,v) 坐标点。

立方体贴图(Cube Map)

立方体贴图,通过使用构成立方体六个表面的六张贴图存储周围环境影像:

立方体贴图映射的思路:

  1. 假设一个单位立方体包围了物体中心,当物体中心看向物体表面某个位置 (x,y,z)时,从中心朝这个位置发出一条射线,此时射线会与单位立方体相交于某点。
  2. 根据视线与单位立方体的交点坐标 (xo,yo,zo),在xo、yo、zo中取绝对值最大的那个分量,根据它的符号来判定来确定要映射在哪一个面。

 

        3.确定映射在第几个面后,剩余另外两个分量便是来映射成在第几张立方体贴图中的 (u,v) 坐标点。

思考:为什么游戏中的天空盒经常使用立方体贴图而不是圆形贴图?

天空盒(skybox)经常使用立方体贴图(cube mapping)而不是球形贴图(spherical mapping)的主要原因是性能和易于映射:

1. 处理和渲染效率:立方体贴图在GPU上的处理效率非常高。与球形贴图相比,立方体贴图可以更容易地与现代图形硬件集成,因为立方体的六个面直接对应于纹理坐标,这意味着查找和过滤纹理像素(texels)时更简单、更快。图形硬件可以自动管理立方体的边缘和纹理过滤器,而球形贴图则通常需要更复杂的数学转换和额外的纹理渐变处理。

2. 映射简便:在立方体贴图中,每个方向(上、下、左、右、前、后)都是映射到一个平面的,这使得将真实世界的环境映射到立方体贴图上成了一种相对直观的过程。立方体的六个平面可以很容易地与3D建模和渲染软件相结合,从而创建无缝的全景贴图。而球形贴图由于其弯曲的性质,经常需要复杂的变形和校正以防止失真。

3. 避免失真:球形贴图会在极点区域产生拉伸和失真,因为整个球体的表面需要映射到一个平面上。立方体贴图不会出现此类问题,因为每个面都是一个平面,天空盒的各个面可以无缝连接,提供一个均匀分布的视觉效果。

4. GPU原生支持:许多图形处理单元(GPUs)原生支持立方体贴图,提供特殊的硬件加速功能,如立方体贴图过滤和无缝边缘采样。这种支持可以最大限度地减少制作天空盒时的计算负担。

5. 内存和存储效率:通常情况下,存储一个立方体贴图所需的内存会比存储同等分辨率的球形贴图要少。因为球形贴图需要更多的像素来覆盖极端区域,以避免失真,而这些额外的像素实际上并不总是被有效地利用。

6. 简化光照和反射计算:在计算反射或环境光照时,使用立方体贴图可以直接利用反射向量的方向来快速查询相应面的纹理值。复杂的数学转换在这种情况下被避免了。

因此,基于以上原因,许多3D引擎和图形应用选择使用立方体贴图来实现天空盒。尽管如此,技术进步可能改变最佳实践,但截至目前,立方体贴图在渲染天空盒方面保持了一定的优势。

纹理走样问题

纹理映射的一个问题是,当纹理颜色变化多且高分辨率(高频信息多),而渲染目标物体的像素量少(采样频率过低)时,很容易出现锯齿现象和摩尔纹现象。

换句话说,当屏幕一个像素点(如对应下图的斜四边形)实际对应纹理很大片的纹素区域时,只通过像素点中心采样得到的纹理颜色结果不能准确表示整片区域的纹理颜色。

使用超采样的方法可以避免走样问题,然而需要付出极大开销:

Mipmap

Mipmap技术则可以让纹理采样进行快速的近似正方形范围查询,它需要预先提供多层分辨率各不同(每层纹理长宽都比上层缩小一半)的纹理(用D表示它们的层级)

注:Mipmap技术的纹理额外空间开销为原分辨率纹理的1/3

这样当屏幕像素点对应纹理中较大片纹素区域时,可以选择相应高层级的纹理(低分辨率纹理),这样就能近似的对应低分辨率纹理中的一个纹素而非对应高分辨率纹理的一大片纹素区域,从而也就能近似表示这片区域的纹理颜色。

选择层级的方式则是计算屏幕像素点对应在纹理坐标中的最大微分(du、dv中取变化最大的),从而得到近似正方形的边长L,最后通过log2函数可以得出层级D,这样就可以选择合适的Mipmap层级。

为了避免着色三角形时而远时而近,导致频繁切换Mipmap层级产生突兀的变化,实际上还需要使用了层级之间的插值(根据D值),这样就可实现两个层级之间切换的平滑过渡。

各向异性过滤(Ripmap)

Mipmap的一个缺点是,它只适合近似正方形范围查询,当一个屏幕像素点对应的纹素范围是别的形状(尤其长条形状)时,很容易导致近似正方形覆盖的区域比实际覆盖区域大得多,从而造成过度模糊现象(例如下图中远处的格子已经模糊地看不见黑色区域)

各向异性过滤的原理:在Mipmap的基础上提供横向伸缩和纵向伸缩的纹理层级(以适应横着和竖着的长条形状);但是这只能减少上述过度模糊现象的发生,因为实际渲染中还有斜向的长条形状或者其它难以近似的形状。

注:各向异性技术的纹理额外空间开销为原分辨率纹理的3倍

此外,还有一种少见的各向异性过滤方法:EWA过滤,大概原理就是任意一个不规则形状都可以拆解成不同的圆形,虽然通过多次查询的效果很好,但是性能代价非常高。

纹理应用技术(Texture Application)

纹理通常被认为是物体的“皮肤”,因为本质上它是一个二维数组,存储的元素是颜色。但是随着纹理技术的发展,纹理衍生成了一个广泛的意义:存储某种数据的N维数组。下面各种技术便是将纹理应用到不同方面的体现。

天空盒(SkyBox)

现实世界中,有些物体非常远(例如远处的山、树林、天空),无论观察者怎么移动,这个物体的大小是几乎没有什么变化的。这些物体往往是做成场景的背景图,一般被称为 天空盒(SkyBox)

天空盒渲染原理:

  1. 先准备6个面的天空背景制成 Cube Map。
  2. 将一个不会旋转、大小随意的立方体物体始终罩在摄像机的周围(让摄像机始终位于这个立方体的中心位置)。
  3. 每一帧摄像机渲染该立方体时,shading 应采用立方体贴图映射方式来采样 Cube Map。

渲染该立方体时应当选择不写入深度方式(天空盒在渲染时应该为最远的深度,换句话说不应该遮挡任何其他物体)+取消背面消隐(因为在渲染立方体内部表面)。

优化:天空盒渲染应该放在最后的渲染顺序,可以减少很多像素overdraw开销(放在最后的渲染顺序可以直接丢弃大量像素,而不必对这些迟早要丢弃的像素进行着色)

环境映射(Environment Mapping)

现实世界的环境光是非常复杂的,存在大量经过多次反射后到达着色物体后再到达人眼的间接光(例如光照在窗户上,窗户再反射到杯子上,杯子再反射回人眼),这个过程中会把反射经过的物体颜色按一定权重混合在一起(因此看到的杯子混合了窗户的颜色),最后在着色物体形成近似“镜面反射”的效果(本质上就是接受了复杂环境光的结果)

为了实现接受环境光的效果,我们可以用一个贴图来存放环境光信息,即 环境贴图(Environment Map) 或 反射贴图(Reflection Map),在给物体着色的时候不仅采样普通纹理,也采样环境贴图,按一定比例混合这两者的颜色(例如物体材质越光滑,镜面反射效果越强)。

对于360°方向均需要镜面反射现象的物体(如金属球),应采用立方体贴图方式映射;对于单一方向需要反射现象的物体(如一面镜子),可以使用普通方式映射。

如何生成立方体贴图方式的环境贴图(一个最简单的方式):设置6个摄像机位于物体中心,每帧分别朝六个方向渲染得到的图像输出到立方体贴图里对应的面上。

优化:一般的环境贴图并不需要高精度的环境光信息,因此可以使用分辨率更低的环境贴图(这样生成环境贴图的开销也会减少不少)。

光照贴图(Light Map)

在平时的渲染中,高质量的光照(例如光线追踪, 辐射度, AO,阴影等算法)计算量无疑是庞大的,实时计算这些光照性能开销巨大,但是光照贴图给我们提供了一个预计算的方法:

  1. 烘培(Bake):预先计算这些复杂的光照,并保存进一个专门存放光照计算结果的光照贴图(Light Map)。
  2. 当物体在着色的时候,不仅采样正常纹理+Light Map,便可以让物体拥有高质量的着色。

在Unity中,如果我们把有网格组件的GameObject勾上static属性时,该物体会被认为是参与烘培的物体,从而为它预计算出光照贴图(也包括重新计算其它static物体的光照贴图)

好处:

  • 烘培光照,可以减少大量运行时的开销。

代价:

  • 光照是静态的,不能动态变化(预计算特点),因此只适用于静态物体。
  • 烘培(即预计算光照)的用时是漫长的,需要开发人员的耐心 
  • 若场景同时包含静态物体和动态物体,需要考虑静态光照与对动态光照的结合算法。

环境光遮蔽贴图(Ambient Occlusion Texture Map)

环境光遮蔽(Ambient Occlusion)本质上也属于环境光的一部分计算,只不过是表示遮蔽的部分(而非光照的部分)。这种屏蔽的来源是来物体和物体(包括局部物体)相交或靠近的时候遮挡周围漫反射光线的效果,可以解决或改善场景中缝隙、褶皱与墙角、角线以及细小物体等的表现不清晰问题,综合改善暗部阴影细节,增强空间的层次感、真实感。

而通过环境光遮蔽贴图,我们可以预先计算环境光屏蔽结果,并将计算结果存放于贴图中。等着色的时候便可以通过采样环境光遮蔽贴图来得到遮蔽系数,从而增添一些暗部阴影细节。

凹凸映射(Bump Mapping)

所谓凹凸贴图,就是通过改变表面各点的法线,使本来是平的东西看起来有凹凸的效果,是一种欺骗眼睛的技术(因为物体结构并没有发生改变)。例如这个球看起来凹凸不平,实际它的几何结构就是光滑的球,只是通过凹凸贴图改变了各像素点的法线方向,从而导致光照结果不一,产生出凹凸不平的视觉效果:

高度贴图(Height Map)

高度贴图中存储的元素是高度 H???,用于表示局部高度偏移。对输入位置 p=(u,v)?? 通过一定公式就能得到其位置上的法线 n。

  1. 先计算出p点上u、v方向的切线值:frac{dp}{du} = c1 ast left [ H(u+1,v) - H(u,v) 
ight ];frac{dp}{dv} = c2 ast left [ H(u,v+1) - H(u,v) 
ight ]
  2. 则 p 点的法线(切线空间下)为:n_{tangent}(p) = Normalize(-frac{dp}{du},-frac{dp}{dv},1)

实际上,切线空间下的法线往往还不可以直接使用在光照计算,这是因为光线、视野等方向的向量往往实在模型空间下的,需要将所有这些方向统一坐标空间才可共同参与光照计算。后面切线空间的法线贴图会给出转换切线空间和模型空间的方式。

模型空间的法线贴图(Object-Space Normal Map)

高度贴图的一个问题是它需要经过较复杂的计算才能得出法线(开销略大),而法线贴图的做法则是直接存储表面法线的方向(相当于预先计算好法线)。

不过,在存储法线时需要将法线方向的分量[-1,1]映射到像素分量[0,1]:

pixel = (n+1)/2

同理,党对法线纹理进行采样后,也需要一次反映射,用于得到原先的法线方向

n = pixel ast 2 - 1

切线空间的法线贴图(Tangent-Space Normal Map)

切线空间:对每个模型顶点,它都有一个属于自己的切线空间,其原点是顶点本身,z轴就是法线方向(normal),x轴就是顶点的切线方向(tangent),y轴就是法线和切线叉积所得,也就是副切线(bitangent)。

而另一种法线贴图则是记录切线空间下的法线(第一种记录的表面法线是在模型空间下的),换句话说,切线空间的法线本质上就是在记录"相对"的法线信息(而模型空间的法线本质上则是记录"绝对"的法线信息)

将该切线空间下的法线 n_{tangent} 转换为模型空间下的法线 n 的方法:

1.B = N 	imes T 其中N,T分别为顶点上的原始的法线、切线(tangent),B则称为副切线(bitangent)。

2. n = left [ T,B,N 
ight ]cdot n_{tangent}

好处:

  • 自由度高、可重用:模型空间下的Normal Map记录的是绝对法线信息,仅可以用于创建它的那个模型;而切线空间下的Normal Map记录的是相对法线信息,可用于任何朝向的任何模型/面。
  • 可进行UV动画:可以移动一个纹理的UV坐标来实现凹凸移动的效果。UV动画在水或者火山熔岩这种类型的物体长经常用到。
  • 可压缩:由于Tangent-Space Normal Map中法线的Z方向总是正方向的,因此我们可以仅存储XY方向,而推导得到Z方向(通过向量标准化)。

代价:

  • 切线空间的法线贴图需要计算额外的空间变换:在计算光照时,需要统一各种方向向量所在的坐标空间,而法线贴图中存储的法线是切线空间下的方向,因此需要额外计算(模型空间下的法线贴图则不需要)。

位移映射(Displacement Mapping)

位移贴图(Displacement Map)则是真正修改模型几何的贴图,它存储了每个像素的位移量(实际上也是一种高度贴图),通过修改顶点位置,从而产生新的模型。

代价(相比于Bump Mapping):

  • 修改顶点位置,需要重新计算由此分出来的新三角形
  • 产生更多数量的三角形,开销大大增加

阴影映射(Shadow Mapping)

之前介绍的光照计算中是不包含阴影的,而阴影映射(Shadow Mapping)则是常见的实现阴影计算的手段,这里不在详细介绍了。

3D纹理(3D Textures) 

3D柏林噪声(3D Perlin Noise)生成纹理

体积渲染(Volume Rendering)