纹理映射(Texture Mapping)
纹理(Texture) 相当于着色物体的"皮肤",负责提供基础颜色,而为了方便表示纹理(可以想象下,一个3D物体的皮肤其实是可以展开成一张平面),往往使用一个二维颜色数组去表示纹理。
于是纹理平面就有了自己的坐标系(纹理空间),通常用u、v表示坐标轴。
在之前的着色(Shading)计算中,光照计算只是计算了物体受到多少光照的影响(可以粗略理解成亮度,因为有些光本身也会提供颜色),而纹理则可以给这些物体提供基础颜色。
为了使用纹理,对于每个三角形顶点vertex属性额外需要存储u、v坐标以便映射到纹理空间(由于三角形也是一个平面,因此非常方便映射到平面的纹理空间),三角形内的点则只需要根据三角重心坐标插值也能算出对应的u、v坐标。这样,三角形每个像素点就可以找到纹理中对应位置的颜色。
在某些情形下,我们可能需要将空间中的曲面(一般是指凸多面体)映射到纹理平面,即 \(f:\R ^3 \rightarrow \R ^2\) ; \((x,y,z)\rightarrow(u,v)\) ,那么就需要借助 球形贴图(Spherical Map) 或者 立方体贴图(Cube Map)。
球形贴图(Spherical Map)
所谓球形贴图,就是以球的形式映射贴图。
例如一个球面展开后,会得到这样一张贴图(但是球形贴图看起来会有一种扭曲现象,表现不直观):
球形贴图映射的思路:
- 假设一个单位球包围了物体中心,当物体中心看向物体表面某个位置 \((x,y,z)\) 时,从中心朝这个位置发出一条射线,此时射线会与单位球相交于某点。
- 根据射线与单位球体的交点坐标 \((x_o,y_o,z_o)\),推算出交点所在的偏航角和俯仰角 \((yaw,pitch)\),然后来映射成在球形贴图对应的 \((u,v)\) 坐标点。
此处的物体一般是指凸多面体网格,而渲染中的曲面(包括球面)实际上也是又若干个三角面组成,因此也算是多面体网格。
立方体贴图(Cube Map)
立方体贴图,通过使用构成立方体六个表面的六张贴图存储周围环境影像:
立方体贴图映射的思路:
- 假设一个单位立方体包围了物体中心,当物体中心看向物体表面某个位置 \((x,y,z)\) 时,从中心朝这个位置发出一条射线,此时射线会与单位立方体相交于某点。
- 根据视线与单位立方体的交点坐标 \((x_o,y_o,z_o)\),在\(x_o\)、\(y_o\)、\(z_o\)中取绝对值最大的那个分量,根据它的符号来判定来确定要映射在哪一个面。
- 确定映射在第几个面后,剩余另外两个分量便是来映射成在第几张立方体贴图中的 \((u,v)\) 坐标点。
纹理走样问题
纹理映射的一个问题是,当纹理颜色变化多且高分辨率(高频信息多),而渲染目标物体的像素量少(采样频率过低)时,很容易出现锯齿现象和摩尔纹现象。
换句话说,当屏幕一个像素点(如对应下图的斜四边形)实际对应纹理很大片的纹素区域时,只通过像素点中心采样得到的纹理颜色结果不能准确表示整片区域的纹理颜色。
使用超采样的方法可以避免走样问题,然而需要付出极大开销:
Mipmap
Mipmap技术则可以让纹理采样进行快速的近似正方形范围查询,它需要预先提供多层分辨率各不同(每层纹理长宽都比上层缩小一半)的纹理(用D表示它们的层级)
注:Mipmap技术的纹理额外空间开销为原分辨率纹理的1/3
这样当屏幕像素点对应纹理中较大片纹素区域时,可以选择相应高层级的纹理(低分辨率纹理),这样就能近似的对应低分辨率纹理中的一个纹素而非对应高分辨率纹理的一大片纹素区域,从而也就能近似表示这片区域的纹理颜色。
选择层级的方式则是计算屏幕像素点对应在纹理坐标中的最大微分(du、dv中取变化最大的),从而得到近似正方形的边长L,最后通过log2函数可以得出层级D,这样就可以选择合适的Mipmap层级。
为了避免着色三角形时而远时而近,导致频繁切换Mipmap层级产生突兀的变化,实际上还需要使用了层级之间的插值(根据D值),这样就可实现两个层级之间切换的平滑过渡。
各向异性过滤(Ripmap)
Mipmap的一个缺点是,它只适合近似正方形范围查询,当一个屏幕像素点对应的纹素范围是别的形状(尤其长条形状)时,很容易导致近似正方形覆盖的区域比实际覆盖区域大得多,从而造成过度模糊现象(例如下图中远处的格子已经模糊地看不见黑色区域)
各向异性过滤的原理:在Mipmap的基础上提供横向伸缩和纵向伸缩的纹理层级(以适应横着和竖着的长条形状);但是这只能减少上述过度模糊现象的发生,因为实际渲染中还有斜向的长条形状或者其它难以近似的形状。
注:各向异性技术的纹理额外空间开销为原分辨率纹理的3倍
此外,还有一种少见的各向异性过滤方法:EWA过滤,大概原理就是任意一个不规则形状都可以拆解成不同的圆形,虽然通过多次查询的效果很好,但是性能代价非常高。
纹理应用技术(Texture Application)
纹理通常被认为是物体的“皮肤”,因为本质上它是一个二维数组,存储的元素是颜色。但是随着纹理技术的发展,纹理衍生成了一个广泛的意义:存储某种数据的N维数组。下面各种技术便是将纹理应用到不同方面的体现。
天空盒(SkyBox)
现实世界中,有些物体非常远(例如远处的山、树林、天空),无论观察者怎么移动,这个物体的大小是几乎没有什么变化的。这些物体往往是做成场景的背景图,一般被称为 天空盒(SkyBox)。
天空盒渲染原理:
- 先准备6个面的天空背景制成 Cube Map。
- 将一个不会旋转、大小随意的立方体物体始终罩在摄像机的周围(让摄像机始终位于这个立方体的中心位置)。
- 每一帧摄像机渲染该立方体时,shading 应采用立方体贴图映射方式来采样 Cube Map。
渲染该立方体时应当选择不写入深度方式(天空盒在渲染时应该为最远的深度,换句话说不应该遮挡任何其他物体)+取消背面消隐(因为在渲染立方体内部表面)。
优化:
- 天空盒渲染应该放在最后的渲染顺序,可以减少很多像素overdraw开销(放在最后的渲染顺序可以直接丢弃大量像素,而不必对这些迟早要丢弃的像素进行着色)
环境映射(Environment Mapping)
现实世界的环境光是非常复杂的,存在大量经过多次反射后到达着色物体后再到达人眼的间接光(例如光照在窗户上,窗户再反射到杯子上,杯子再反射回人眼),这个过程中会把反射经过的物体颜色按一定权重混合在一起(因此看到的杯子混合了窗户的颜色),最后在着色物体形成近似“镜面反射”的效果(本质上就是接受了复杂环境光的结果)。
为了实现接受环境光的效果,我们可以用一个贴图来存放环境光信息,即 环境贴图(Environment Map) 或 反射贴图(Reflection Map),在给物体着色的时候不仅采样普通纹理,也采样环境贴图,按一定比例混合这两者的颜色(例如物体材质越光滑,镜面反射效果越强)。
对于360°方向均需要镜面反射现象的物体(如金属球),应采用立方体贴图方式映射;对于单一方向需要反射现象的物体(如一面镜子),可以使用普通方式映射。
如何生成立方体贴图方式的环境贴图(一个最简单的方式):
- 设置6个摄像机位于物体中心,每帧分别朝六个方向渲染得到的图像输出到立方体贴图里对应的面上。
优化:
- 一般的环境贴图并不需要高精度的环境光信息,因此可以使用分辨率更低的环境贴图(这样生成环境贴图的开销也会减少不少)。
光照贴图(Light Map)
在平时的渲染中,高质量的光照(例如光线追踪, 辐射度, AO,阴影等算法)计算量无疑是庞大的,实时计算这些光照性能开销巨大,但是光照贴图给我们提供了一个预计算的方法:
- 烘培(Bake):预先计算这些复杂的光照,并保存进一个专门存放光照计算结果的光照贴图(Light Map)。
- 当物体在着色的时候,不仅采样正常纹理+Light Map,便可以让物体拥有高质量的着色。
在Unity中,如果我们把有网格组件的GameObject勾上static属性时,该物体会被认为是参与烘培的物体,从而为它预计算出光照贴图(也包括重新计算其它static物体的光照贴图)
好处:
- 烘培光照,可以减少大量运行时的开销。
代价:
- 光照是静态的,不能动态变化(预计算特点),因此只适用于静态物体。
- 烘培(即预计算光照)的用时是漫长的,需要开发人员的耐心