在计算机图形学中,纹理化就是使用一些图片,函数或者其它的一些数据,修改表面表现的一个过程
6.1 The Texturing Pipeline
纹理是通过修改shader 中算法中的值,来最终改变渲染结果的
纹理中的像素叫texel,纹理和屏幕上的pixel 区别开来,并没有其它含义
纹理管线的流程是:首先 获取模型上的某一点的位置,这个位置是在模型坐标空间下的,因为随着模型动,纹理也在动,然后经过 projector 方法,获得了纹理坐标(uv坐标,从0-1),这个过程又叫mapping,然后经过一些列的 correspond 方法,把纹理坐标转换到 texture 空间下(也就是具体的texel坐标,范围是width-hight,比如1920*1080),然后用该空间下的坐标,从纹理中获取存储的值,这些值有可能在经过一些处理
上面的过程就是整个过程,最后一步不是必须的
这个图就是上述过程
6.1.1 The Projector Function
projector function 一般由建模师控制,通过把一个三维的坐标,转换成一个二维的uv坐标,这个过程一般由建模软件提供,在建模的时候就处理了
常用地projector 方法包括 spherical, cylindrical, planar projections
不光是点的坐标可以用来作为projector 方法的输入参数,其它参数也可以用来当做projector的参数,比如顶点的法线,视角方向,projector 的最终目标就是生成正确的uv坐标 ,顶点坐标只是其中的一种方式
一般来说,一个模型用一种projector 方法就够了,但是对于复杂的模型,模型师需要拆分模型,然后应用不同的projector方法,比如
正如前面所说,projector 过程一般在建模软件中就执行了,然后把uv坐标存储在顶点数据中,但是可以在shader中执行,比如uv动画,来改变顶点的uv
但是这种自动映射,会在边缘处有些缝隙,导致uv不正确,所以一般建模师都会展开贴图,然后平面映射,目的就是让顶点的uv坐标更正确
uv 坐标不仅仅是二维的,也有可能是三维的,uvw ,w 用来表示映射的深度值,也有可能是四维的,uvwq,q用来表示齐次坐标空间下的坐标
另一个很重要的纹理坐标空间--directional,在该空间下,通过输入一个方向值,来获取点的坐标,将这种空间形象化的一种方法是将其作为单位球体上的点,每个点的法线代表在该位置访问纹理的方向. 使用方向参数化的最常见纹理类型是cube map
同时一维的纹理坐标也有它的用途,比如地形可以从里面获取地形的高度
这些uv 坐标在三角形面内进行差值,然后获取纹理值,在差值之前,我们需要 corresponder functions 把它们转换到texture image 坐标空间下
6.1.2 The Corresponder Function
Corresponder functions把uv坐标转换到 texture-space locations 坐标下
我们知道如果uv 在【0-1】内是很好映射的,但是如果uv 超过了这个范围,该如何表现呢?
相对应的corresponder function 有
? wrap (DirectX), repeat (OpenGL), or tile—重复
? mirror—过程就像0-1,1-0,0-1,1-0.
? clamp (DirectX) or clamp to edge (OpenGL)—限制在【0-1】
? border (DirectX) or clamp to border (OpenGL)—超过边界的会绘制成边界颜色,这个颜色可自定义
Repeated 带来的一个问题时,当重复超过三次以上,就会显得不那么真实,避免这种问题的方法就是,把该贴图和一个不使用repeated 的贴图一起使用,从而削弱这种周期性的表现
6.1.3 Texture Values
在这之后,就是真正获取到纹理中的texel值了
6.2 Image Texturing
我们通过texture2D(u,v)来获取纹理中对应的值,这个方法里面进行了 相对应的corresponder function,有一点不同的是,DX 是左上角为(0,0)点 ,OpenGL是左下角为(0,0)点
dependent texture read:当像素着色器单独访问一个纹理时,而不是经过顶点着色器传入的纹理坐标,就会产生依赖读取,也就是该纹理坐标的值依赖于之前纹理坐标的值,比如 一个纹理可能改变了法线坐标,同时反过来也影响了 访问 cube map 的纹理坐标,这个操作在之前是不支持的,现在虽然支持,但是也影响性能。
6.2.1 Magnification
比如你的纹理大小是48*48的,但是覆盖的模型表面是480*480的,这时候,底层图形处理系统,就会放大该纹理,以适应模型表面,但是这时候就涉及到一个texel 对应多个pixel 的问题,原来一个texel 对应一个pixel ,颜色值正好,现在就涉及到其它9个pixel 的颜色取值问题
常用的filter算法是:nearest neighbor和 bilinear interpolation 和cubic convolution
cubic convolution 用一个4 × 4 or 5 × 5 的texel 数组,来计算最终的颜色值,比较精准,但是硬件一般不支持,可以在shader 中执行
左边的使用nearest neighbor ,只采样邻近的单个texel值 作为 该pixel的值,产生了像素化的效果
中间的使用bilinear interpolation,通过采取邻近四个texel的值,然后进行水平垂直方向上的差值,获得最后的color,结果就是会模糊
最右边的使用bicubic filter ,同样也能移除像素画的效果,但是他要比 bilinear filters花费更高的性能
比如我们获取到的pixel 坐标为(pu,pv) = (81.92,74.24),也就是它在(81.42, 73.74)的坐标空间原点下,那么该texel的坐标为(81, 73) 到 (82, 74).而该点具体的颜色值,在(0.42,0.74)的位置,首先分为两步,我们要计算左下角和右上角的颜色值,然后进行差值
然后计算一个权重,当前texel 的权重为 0.42 × 0.74, 顺时针依次为 0.42 × 0.26, 0.58 × 0.26, and 0.58 × 0.74, 加起来正好 1.0.
解决图片模糊的方法可以使用额外的一张细节贴图
6.2.2 Minification
当一个pixel 对应多个texel 的时候,需要综合所有的texel 来决定最终pixel的值
一个方法就是nearest neighbor, 和放大是一样的,选择里pixel中心的texel 作为最终的颜色值,会造成锯齿的效果,如下图
另一种方法是 bilinear interpolation, 和 magnification filter.一样,通过对四个texel采样,得到最终的颜色,但是如果一个pixel 覆盖超过了四个texel 也会出现锯齿问题
为了解决锯齿问题,我们上一张讨论过,提高采样率,我们避免出现锯齿的最低情况,就是一个texel 对应一个 pixel
所有纹理抗锯齿算法的最终思想都是一样的,就是预处理纹理,生成不同的数据结构,来适应不同的情况,但是这也会增加处理的时间和内存大小.
Mipmapping
“Mip” 表示 multum in parvo, 拉丁语是 “many things in a small place”的意思
使用mipmapping ,原始图像会分成多个不同等级的小图像,在最低一层,也就是0级,纹理大小是原始大小的四分之一,
需要注意的一点是,在mipmapping的时候,需要把颜色空间转换到线性空间,生成mipmapping之后,再转换成gamma 空间保存,这里面有一个转换的过程
For this reason, it is important to convert such textures from sRGB to linear space (Section 5.6), perform all mipmap filtering in that space, and convert the final results back into sRGB color space for storage.
这里使用pixel 框来表示具体的texel ,其实并不准确,因为周围的texel 也会影响该pixel的颜色值
DX中用d来表示mipmapping的等级,等级越高,也到金字塔尖,也就是更模糊,但是d 并不一直是整数,也可能是小数,表示在两个mipmapping 之间,这时会采样两个mipmap,然后进行线性插值,这个方法叫做 trilinear interpolation
mipmap 的缺点就是 overblurring. 如果一个pixel 仅仅在一个轴向上覆盖了多个texel,但是mipmap 会在两个轴向上缩小。
Summed-Area Table
该方法可以避免overblurring 问题,它创建了一个纹理大小的数组,然后每一个值都存储了更高京都的颜色值,然后决定该pixel 的颜色值,是通过计算覆盖的texel的平均值
x , y 是texel 在矩形中的位置坐标,s(x,y)是表中的颜色值
summed-area table 是 anisotropic filtering(各向异性) 算法的一个例子, 这种算法在非正方形区域上检索texel值, SAT 在水平上或者垂直上效率更高
同时也要注意 summed-area tables 对于大小为16 × 16或更小的纹理,需要至少两倍的内存,对于更大的纹理需要更高的精度。
6.2.6 Texture Compression
一个直接解决内存过大,或者带宽、缓存问题的的方法 就是 纹理压缩,通过让GPU在运行当中解压缩纹理,可以增大缓存大小,我们压缩的主要目的 是为了减少带宽,增大缓存大小,次级目的是为了添加更大的纹理,比如一个512*512的纹理,如果没有压缩,一个pixel 占用3byte ,一共需要768kb,而压缩的1024*1024的纹理,压缩比是6:1才使用512KB
对于不同的图像文件格式,如JPEG和PNG,有各种各样的图像压缩方法,但在硬件中实现这些解码是非常耗性能的
DXTC,DX10之后叫做BC(Block Compress) ,是DX 、OpenGL中标准的压缩方法,因为很多GPU 都支持它
DXTC 发展到现在,一共有七种变体,分别使用不同的情况,但是它们拥有共同的指标,只不过数值不一样,在DXTC中,每4*4个texel ,称为一个block,也叫tile,每一个block都是单独压缩,压缩是基于差值的,对于每一个压缩的block中,存储两个值,比如颜色,block中的每一个texel 都存储一个interpolation factor(差值因子),然后在这两个值中取颜色值
压缩是因为:只存储两种颜色以及每个像素的短索引值,如下图,也就是每一个块存储两个颜色,就那BC1来说,存储了两个RGB值,每一个通道所占的位数分别为5,6,5,然后就是indices ,每一个texel 占用2个bit,16个就是32bit,使用该差值因子,从两个存储的颜色值中获取该texel的颜色,所以它相对于不压缩的24位的纹理,压缩比为6:1
BC6H 是为了压缩HDR(原通道每一个占16bit),BC7是为了LDR
BC1-5的缺点就是降低了图像的质量,用4bit/8bit 来表示16个texel ,如果这16个texel 中的颜色值差别很大,就会造成精度丢失,还有一个原因就是存储的两个颜色,都是一条线上的颜色值,但是它并不能代表整个块中的颜色值, BC6H and BC7 支持多条线上的颜色值,从而获取到的颜色更精准
在OpenGL ES,采取了另一种压缩方法,Ericsson texture compression (ETC),它和DXTC一样,每一个 4 × 4 的block ,采用 64 bits,每一个texel 4 bits . 但是它每一个 2 × 4 block (or 4 × 2, depending on which gives best quality) 存储一个基本色stores a base color. 每一个block 还存储了从a small static lookup table挑选的4个常量值,block中的每一个texel 也可以从表中选择一个值,它改变了像素的光照条件,生成的图像质量和DXTC 差不多
在OpenGL ES 3.0中出现的ETC2算法中,使用未使用的位组合(unused bit combinations)来为原有的ETC算法增加更多的模式。一个未使用的位组合(unused bit combinations)是压缩表示(compressed representation),它解压缩到与另一个压缩表示相同的图像,
比如,在BC1中如果存储的两个颜色值是相同的,就表明这个block是一个常量颜色值,这样其实只用存储一个颜色值就够了, 在 ETC中,一个颜色值可以通过signed number 从另一个颜色中获取到 . ETC2 增加了四种颜色的两种新模式, Ericsson alpha compression (EAC) 仅仅压缩图像 的单个通道,比如alpha 通道,每个像素存储4个bit
压缩法线需要注意一下,因为法线是单位法线,所以一个值可以由另外两个值获取到,所以只用存储两个值,xy,我们假设z 值是正的,在切线空间下,确实如此,所以法线纹理一般是切线空间下的法线
PVRTC 是为iPhone 或者iPad 准备的压缩格式,每一个texel占用2-4bit,其关键思想是通过对图像的两个低频(平滑)信号,对相邻的texel data block插值得到。然后在图像上的两个信号之间进行插值。
Adaptive scalable texture compression (ASTC)把 n × m texels的block压缩到 128 bits. block 从 4 × 4 到 12 × 12, 压缩率也从 0.89 btp 到8 bpt. ASTC 可以处理1-4个通道的texture, LDR and HDR textures也可以.
所有的压缩方法都是有损的,也就是质量要比之前低,压缩要比解压缩花费更多的时间,因为解压是通过固定功能的硬件执行的,有的压缩时间长,有的压缩时间段,所以一般是离线压缩,但也有运行时压缩,比如,天空盒的纹理,每几秒就压缩解压缩一次。
6.3 Procedural Texturing
给定一个 texture-space 的坐标位置, 查找image的所以值,是获取texture 值得一种方法,另一种方法是执行一个方法,因此定义了procedural texture.
现在更多的使用贴图纹理,是因为GPU的处理能力的提高, 然而,GPU架构正在向更简单的计算和(相对地)更昂贵的内存访问发展。这些趋势使得程序纹理在实时应用中得到了更多的应用
这方面应用最多的是Volume textures,因为Volume textures 需要更多的内存,它是三维的,
Alpha to coverage 它是利用该片元的透明度,来决定该片元的百分比,比如该片元75%是透明的,那么该片元的四分三是透明的,剩下的四分之一是不透明的,不透明的部分完全剔除他后面的部分,这对草和树叶非常有效
对于任何的 alpha mapping, 理解它是如何通过 bilinear interpolation 来影响颜色值的,是非常重要的。
比如两个相邻的texel,一个 rgbα = (255,0,0,255) 一个 rgbα = (0,0,0,2), 接近完全透明,那个该texel 的值并不是 (127, 0, 0, 128), 而是完全的红色,在差值之前就乘以了它的透明度.假如旁边的texel 换成 rgbα = (0, 255, 0, 2), 带有一点点绿色,如果不预先乘以透明度,最后的结果是 (127,127,0,128) ,直接变成了黄色,如果旁边是(0,2,0,2), 预乘以透明度,得到的差值结果是 (127, 1, 0, 128). 也就是乘以该透明通道所占的比例
6.7 Bump Mapping
所有的这些纹理,都是逐像素的去改变它片元的值,
它提供了一个比纹理映射更三维的外观,但没有添加任何额外的三角形面。
一个物体的细节可以从三个维度上来表述:
宏观上是整体的形状,中观上是一小部分的像素,微观上是单个像素的表现
这三种状态是动态切换的,因为我们的视角在变化,和物体之间的距离也在变化
不同种类的 bump mapping 之间的不同,只要在它们表述细节特点的方式
它直接改变顶点的法线,从而间接更改了光照的计算方式,顶点的法线还是原来的,我们更改的是在光照计算当中的法线值
6.7.1 Blinn’s Methods
Blinn’s original bump mapping中每一个texel存储了两个值:bu 和 bv, 它是通过给法线增加一个偏移,从而达到改变法线的目的.
另一种凹凸贴图,是使用一个 heightfield 来修改法线的方向,每一个黑白点都是代表一个高度,白色代表高,黑色代表低,它是通过与相邻texel 高度的差值,来获取水平和垂直方向的偏移,这也是最原始的改变法线方向的方法
6.7.2 Normal Mapping
这是最直接改变法线的方式,直接存储了在渲染计算中需要用到的法线,因为法线纹理像素的颜色值是在[0-255]之间,但是法线是[-1,1]之间,所以就需要一个颜色值与法线值之间的一个映射关系,-1 代表0 , 0代表128 ,1 代表255 ,很多法线都是浅蓝色,表示它的法线和原来比没有变化,也就是[0,0,1],也就是法线贴图中存储的法线,并没有改变原来法线的值
法线贴图分为两种类型,一种是存储了世界空间下的法线,这个在渲染中可以直接从中获取值,很少使用,或者是模型空间下的法线,但是,这种不好的地方就是 它跟着模型走,如果模型变了,它就不适用了,比如还了一个其它的模型
另一种是存储切线空间下的法线,这个就是要把光照,视角方向转换到切下空间下运算,而且z 防线永远是正的,
6.8 Parallax Mapping
视差指的是物体的位置随着视角的移动而相对地移动。随着视角的移动,凸起应该看起来有高度。视差映射的关键思想是通过检查一个像素中可见物体的高度来进行有根据的猜测
在视差贴图中,bump被存储在一个heightfield texture中,也可以和其它的值合并在同一张贴图中,反正它就占用一个通道。当观察一个指定的像素时,检测该像素的高度,然后以此来改变该表面其它pixel的uv坐标,从而呈现出不同的状态
改变方式如下图:
Given a texture-coordinate location p, an adjusted heightfield height h, and a normalized view vector v with a height value vz and horizontal component vxy, the new parallax-adjusted texture coordinate padj is
它就是根据你视角的方向,改变了该位置的uv ,也就是获得了一个新的法线法线方向,从而改变了光照效果
但是如果当你靠近水平方向观看时,因为新得到的位置与原始地面位置的高度差不多。因为Vz接近于0
6.8.1 Parallax Occlusion Mapping