OpenGL ES手册翻译---3.栅格化(三)

3.7 纹理

纹理查找表把一个或者多个指定图像的部分映射到碎片或者顶点上。通过指定(s,t,r)纹理坐标,采样图像在该坐标位置上的颜色,在着色器中完成映射。纹理查询表通常是用在更改碎片的RGBA颜色,有时候也在着色器中因为一些其他的目的使用。

着色器支持纹理使用,至少是在顶点着色器上(见2.10.5)有MAX_VERTEX_TEXTURE_IMAGE_UNITS图像,在碎片着色器上有MAX_TEXTURE_IMAGE_UNITS图像(见3.8.2)。多组纹理坐标由普通的顶点属性指定,或者通过着色器计算;这些坐标用来采样单独的图像。

下面的小节(这里到3.7.7节)描述对单一纹理的操作,包括图像到纹理映射的介绍和退昂在采集时使用过滤的方法。这里介绍的操作都是通过着色器分别采样每个纹理的应用。

在着色器中采样纹理的细节在OpenGL ES Shading Language Specification中介绍。

函数

void ActiveTexture(enum texture);

是一个激活纹理图像单元的选择器,ACTIVE_TEXTURE。每个纹理图像单元有在3.7节中定义的所有纹理状态组成。

活跃纹理单元选择器选择被函数访问的纹理图像单元,这些函数是关于纹理图像处理的,在3.7节中有定义。这些函数,包括所有TexImage函数的变体函数,BindTexture,和所有这种类型的查询函数。如果ACTIVE_TEXTURE的当前值对应的纹理图像单元的数量大于等于依赖实现的常量MAX_COMBINED_TEXTURE_IMAGE_UNITS,这样的函数将会产生INVELID_OPERATION错误。

如果指定了无效的纹理,ActiveTexture会产生INVALID_ENUM错误。texutureTEXTUREi的符号化常量,表示纹理图像单元i正在被修改。常量遵守等式:TEXTUREi = TEXTURE0 + i,i的范围是0到MAX_COMBINED_TEXTURE_IMAGE_UNITS-1

查询活跃纹理图像单元选择器的状态是一个单一的整数。初始值是TEXTURE0

3.7.1 纹理图像的指定

函数

void TexImage2D(enum target,int level, int internalformat, sizei width, sizei height, int border, enum format, enum type, void *data);

用来指定一个纹理图像。target对二维的纹理,一定是TEXTURE_2D,对于立方体的纹理映射,一定是TEXTURE_CUBE_MAP_POSITIVE_X,TEXTURE_CUBE_MAP_NEGATIVE_X,TEXTURE_CUBE_MAP_POSITIVE_Y,TEXTURE_CUBE_MAP_NEGATIVE_Y,TEXTURE_CUBE_MAP_POSITIVE_Z,TEXTURE_CUBE_MAP_NEGATIVE_Z中的一个。format,type,data表示图像数据格式,类型,和图像数据在内存中的指针,在3.6.2中有描述。

一个二维的纹理由单一的二维纹理图像组成。一个立方体纹理贴图,是一组六个二维的纹理图像。六个立方体纹理目标形成一个单一的立方体贴图纹理,每个目标命名立方体贴图的不同面。上面列出的TEXTURE_CUBE_MAP_的目标靶,会更新合适的立体贴图面的2D纹理图像。要注意,六个立体贴图的二维图像命令,比如TEXTURE_CUBE_MAP_POSITIVE_X会在指定,更新或者查询立方体贴图六个二维图像时被使用,但当开启立方体贴图纹理或者绑定到一个立方体贴图纹理对象时(立方体贴图被当做是整体访问而不是一个特殊的二维图像),TEXTURE_CUBE_MAP是被指定的。

TexImage2Dtarget参数是六个立方体贴图的二维图像目标靶中的一个时,如果widthheight的参数不相等,将会产生INVALID_VALUE错误。

内存中的分组被认为按照像矩形排列的。这个矩形就是一副图像,图像的尺寸和组织由TexImage2D通过widthheight来指定。

被选择的组被按照3.6.2节中描述的那样处理,在最终被扩展到RGBA。这样产生的每个R,G,B,A的的值被钳制到[0,1]内。

通道选择自R,G,B,A的值,来获取由internalformat指定的基本内部格式的纹理,纹理的基本内部格式必须匹配format;在纹理图像处理期间,在格式之间是不支持转换的。表3.8总结了作为纹理图像的一个基本内部格式的函数时,R,G,B,A的值在纹理通道上的映射。internalformat可能是在表3.8中列出的五种内部格式的符号化常量的其中一个。给internalformat指定的值如果不是表中列出的,就会产生INVALID_VALUE错误。如果internalformatformat不匹配,会产生INVALID_OPERATION错误。

image

GL按照纹理选择的内部通道组成存储最终的纹理。内部通道结构的分配会根据TexImage2D的参数(除了target)发生变化,但一定不会是其他状态的函数,建立之后就不能被改变了。分配的空间一定是不变性的;当纹理图像被相同的参数值指定时,一定要选择同一块分配的空间。

图像自身(data指针指向)是一些成组的数据的序列。第一组数据是纹理图像的左下角。接下来的分组将宽为width的行从左到右填满。height行从底到高堆积形成图像。当最终的R,G,B,A通道计算出了一个分组,他们就会被分配到如表3.8中描述的texel中去。从0开始,每个得出的第N个纹素被分配一个内部的整数坐标:
i = (N \; mod \; width) \\ j = (\lfloor \frac{N}{width} \rfloor \; mod \;height)
因此,图像的最后一行就是索引j就是图像的高。

每个颜色通道被近似转换成一个有n比特的修正点值,n是在图像排列中的通道分配的存储空间的比特数。我们假设,使用不动点表示法表示把每个值k/(2^n-1)表示为k(比如1.0在二进制中表示所有值的字符串),其中k\in\{0,1,...,2^n-1\}

level参数在TexImage2D中是一个level-of-detail的整数数字。下面在Mipmapping上讨论细节的等级。主要的纹理图像有一个数字为0的细节等级,被称为level zero array(或者0等级图像数组)。如果level参数比0小,产生INVALID_VALUE参数。如果level比0打,而widthheight不是二的幂,也会产生INVALID_VALUE错误。

如果TexImage2Dborder参数不是0,会产生INVALID_VALUE错误。

如果w_t,h_t是图像的宽和高,w_t,h_t比0小,会产生INVALID_VALUE错误。

对于参数为k的0级别的图像阵列,二维纹理图像的最大允许的宽高至少是2^{k-lod},k是MAX_TEXUTRE_SIZE的以2为底的对数,lod是图像阵列的详细等级。比k大的任何详细等级的图像阵列都有可能是0。如果指定图像在存储的时候过大,将会产生INVALID_VALUE错误。

立方体贴图纹理的允许的最大宽高一定是相同的,对于参数为k的0级别的图像阵列至少是2^{k-lod},k是MAX_CUBE_MAP_TEXTURE_SIZE的以2为底的对数。

只有如果单一的图像阵列可以被支持时,0等级的图像阵列才是可以被允许实现的。另外的关于等级1或者更大等级的图像阵列的创建的约束条件在3.7.10节中会详细讲解。

图像指针指向给GL的图像,这些图像被解码然后被拷贝到GL内部的内存中。

我们把解码后的图像称作texture array。一个纹理数组有如上定义的宽和高w_t,h_t

纹理数组的元素(i,j)被称为texel(纹素)。在纹理化碎片时的纹理值由碎片相关联的坐标(s,t)所决定,但并不是一定要相应一些实际的纹素。见图3.6。

image

如果TexImage2Ddata参数是一个空指针(在C实现中是一个0值的指针),将会使用指定的target,level,internalformat,width,height生成纹理数组,但是没有图像内容。这种情况下,在客户端内存中没有像素值,也没有像素处理操作被执行。会产生错误,但是,却感觉数据指针是有效的。

3.7.2 选择纹理图像规定函数

纹理图像也可以用从帧缓冲中获取图像数据来指定,已经存在的纹理图像的矩形子区域也可以被重新指定。

函数

void CopyTexImage2D(enum target, int level, enum internalformat, int x, int y, sizei width, sizei height, int border);

正好就是定义了TexImage2D操作下的纹理数组,只是图像数据是从帧缓冲中获取的而不是客户端中,target一定是TEXTURE_2D,TEXTURE_CUBE_MAP_POSITIVE_X,TEXTURE_CUBE_MAP_NEGATIVE_X,TEXTURE_CUBE_MAP_POSITIVE_Y,TEXTURE_CUBE_MAP_NEGATIVE_Y,TEXTURE_CUBE_MAP_POSITIVE_Z,TEXTURE_CUBE_MAP_NEGATIVE_Z中的一个。x,y,width,heightReadPixel的相关参数相同(参考4.3.1节),指定了帧缓冲区域中要拷贝的图像的宽高和左下角坐标(x,y)。确切来讲,图像是从帧缓冲的颜色缓冲中获取的,就好像把这些参数设置给ReadPixel并把fromat参数设置为RGBA一样,在转换为RGBA值后图像获取就会停止。接下来的处理过程和TexImage2D中描述的一样,从结果像素分组中的R,G,B,A值嵌拉开始。参数level,internalformat,border用一样的值指定,和在TexImage2D中的参数是一样的意思。interformat受到更多的限制,这样颜色缓冲区通道在转换为internalformat时可以丢掉,但是新的颜色通道不能被添加。比如,RGB颜色缓冲区可以被用来创造LUMINANCE或者RGB的纹理,但是不能用来创造ALPHA,LUMINANCE_ALPHA,RGBA的纹理。表3.9总结了允许的帧缓冲和基本内部格式的结合方式。如果帧缓冲格式和基本纹理格式不匹配,会产生INVALID_OPERATION错误。width,height,border这些参数的限制和TexImage2D的参数是相同的。

image

CopyTexImage2Dtarget参数是六个立方体贴图的二维图像目标靶,在width,height不相等时,会产生INVALID_VALUDE的错误。

另外两个函数:

void TexSubImage2D(enum target,int level, int xoffset, int yoffset, sizei width, sizei height, enum format, enum type, void *data);

void CopyTexSubImage2D(enum target, int level, int xoffset, int yoffset, int x, int y sizei width, sizei height);

仅仅是重新指定了存在的纹理矩阵的矩形子区域。在internalformat,width, height这些指定的纹理矩阵的参数上都没有发生变化,在超出子区域的范围的纹素值也不会发生变化。 TexSubImage2D,CopyTexSubImage2Dtarget参数一定是TEXTURE_2D,TEXTURE_CUBE_MAP_POSITIVE_X,TEXTURE_CUBE_MAP_NEGATIVE_X,TEXTURE_CUBE_MAP_POSITIVE_Y,TEXTURE_CUBE_MAP_NEGATIVE_Y,TEXTURE_CUBE_MAP_POSITIVE_Z,TEXTURE_CUBE_MAP_NEGATIVE_Z中的一个。每个函数的level参数指定要修改的纹理矩阵的详细等级。如果level比0小或者比最大纹理宽度或者高度的以2为底的对数大,会产生INVALID_VALUE错误。

函数TexSubImage2D的参数width,height,format, type, data都和TexImage2D对应的参数是匹配的,意味着他们可以有相同的值,并产生相同的作用。

函数CopyTexSubImage2D的参数x,y,width, heightCopyTexImage2D对应的参数是相同的。每个TexSubImage函数用和对应的TexImage完全相同的方式解释和处理像素组,除非给为例通道分配的R,G,B,A像素组的值被纹理矩阵的internalformat参数控制了,而不是该函数的参数控制。纹理矩阵使用TexSubImage函数参数fromatinternalformat时应用的约束和错误,被重新指定用在TexImage对应的函数的参数formatinternalformat上。

函数TexSubImage2D,CopyTexSubImage2D的参数xoffsetyoffset指定了纹理矩阵的widthheight高的矩形子区域中左下角的纹素坐标,如图3.6。w_t,h_t代指纹理矩阵的宽高,x,y,w,h指xoffset, yosset, width, height参数的值,满足下面的关系将会产生INVALID_VALUE错误。
x<0 \\ x+w>w_t \\ y<0 \\ y+h>h_t

从0计数,给第n个像素组分配内部整数坐标为[i,j]的纹素:
i = x + (n \,mod\,w) \\ j = y + (\lfloor \frac{n}{w} \rfloor \, mod \, h)

如果绑定到FRAMEBUFFER_BINDING的对象不是完整的帧缓冲(见4.4.5节),调用CopyTexImage2D或者CopyTexSubImage2D会产生INVALID_FRAMEBUFFER_OPERATION错误。

  • 纹理拷贝的反馈回路

如果目标纹理图像等级也绑定到了要读的帧缓冲的选定的缓冲区上,调用CopyTexImage2D或者CopyTexSubImage2D会导致未定义的行为。这种情况在4.4.4节中的反馈回路的详细描述中会有更多的讨论。

3.7.3 压缩纹理图像

纹理图像也可以使用按照已知压缩图像格式存储的图像数据来指定或者修改。GL没有定义指定的压缩格式,但是压缩格式可以由GL扩展来定义。有一种可以获取压缩格式token值的机制;通过查询NUM_COMPRESSED_TEXTURE_FORMATS的值可以获取到指定的压缩内部格式支持的数目。渲染器支持的特定的压缩内部格式可以通过查询COMPRESSED_TEXTURE_FORMATS获取。查询返回的结果值就是internalformat参数相关且能被CompressedTexImage2D函数接受的,而且适合通用目的的用处。渲染器不会枚举那些有在使用之前需要特殊理解的限制的格式。

函数

void CompressedTexImage2D(enum target, int level, enum internalformat, sizei width, sizei height, int border, sizei imageSize, void *data);

定义了一张纹理图像,这张图像的输入的数据按照指定的压缩图像格式被存储的。target,level,internalformat,width,height,border参数和TexImage2D是一样的。data指向被压缩的图像数据中,这些图像数据是按照internalformat相关的压缩图像格式存储起来的。

对于所有的压缩内部格式,压缩图像将会根据internalformat的定义解码。压缩纹理图像被看做是一个imageSize大小的ubyte的数组,这个数组的起始地址为data。当解码压缩纹理图像时,所有的像素存储和像素转移都会被忽略。如果imageSize参数和格式,维度以及压缩图像的内容不一致,会产生INVALID_VALUE的错误结果。如果压缩图像没有根据定义的图像格式编码,调用函数的结果是未定义的。

在使用压缩图像规范调用参数时,指定的压缩内部格式会强制实行一些格式相关的限制。比如,压缩图像格式可能不会允许宽和高的值不是4的倍数。这些限制将会在定义压缩内部格式的扩展规范书中记录。违背这些限制将会导致INVALID_OPERATION错误。

任何由特定压缩内部格式强制实行的限制认真对待图像内容是不变的,意味着如果GL接受并存储一张压缩格式的纹理图像,CompressedTexImage2D将会接受任何合适的编码压缩纹理图像,这些图像的宽,高,压缩图像大小都是相同的,存储时的压缩内部格式也在同一个纹理详细等级上。

  • 重新指定压缩纹理的子图像

函数

void CompressedTexSubImage2D(enum target, int level, int xoffset, int yoffset, sizei width, sizei height, enum format, sizei imageSize, void *data);

用已知的压缩图格式存储的数据,重新指定了已经存在的纹理矩阵的一个矩形区域。target,level,xoffset,yoffest,width,height,format参数的意义和TexSubImage2D是相同。data指针指向和format相关的压缩图像格式的压缩图像数据。

dataimageSize参数指向的图像数据被认为是由CompresseTexImage2D提供的。这个函数不提供图像格式转换,所以如果format不匹配要被修改的纹理图像的内部格式,将会产生* INVALID_OPERATION错误。如果imageSize参数和格式,维度,压缩图像的内容(太少或者太多的数据)不一致,将会产生INVALID_VALUE*。

至于CompressedTexImage的调用,压缩内部格式在使用压缩图像规范调用和参数是,有额外附加的限制。任何这样的限制在定义压缩内部格式的规格书中都会有记录;违反这些限制将会导致INVALID_OPERATION错误。

指定压缩内部格式强制的要求的限制对于处理图像内容是不变的,意味着如果GL存储或者接受了一个压缩格式下的纹理图像,CompressedTexSubImage2D将会接受任何合适的编码压缩的纹理图像,这些纹理图像有相同的宽,高,压缩图像大小,存储的压缩内部格式在同一纹理级别。

调用CompressedTexSubImage2D图像时,如果xoffset或者yoffset不等于0,或者widthheight和纹理的宽高各自不匹配,将会导致INVALID_OPERATION错误。通过调用修改的区域以为的任何纹素的内容都是未定义的。这些限制,可能在特定的压缩内部格式且这些格式的图像很容易修改时,比较宽松。

3.7.4 纹理参数

当纹理在被指定或者改变,当纹理被应用到着色器上是,变化的参数控制着纹理矩阵是如何被处理的。参数通过下面函数设置

void TexParameter{if}(enum target, enum pname, T param);
void texParameter{if}v(enum target, enum pname, T params);

target是目标靶,一定是TEXTURE_2D或者TEXTURE_CUBE_MAP中的一个。pname是指定参数设置的符号化常量;可能的常量和相应的参数总结在表3.10中。在函数的第一种形式中,param是一个设置单值参数的值;函数的第二种形式中,param是一个类型依靠设置的参数的参数数组。

image

立方体贴图纹理的纹理参数全部应用在立方体贴图上;六个不同的二维纹理图像使用立方体贴图自己的纹理参数。

3.7.5 立方体贴图纹理选择

当在着色器中使用立方体纹理贴图,(s,t,r)纹理坐标被当做是从立方体的中心发出的方向向量(r_x,r_y,r_z)(q坐标是可以忽略的,因为它值放缩向量但不影响方向)。在纹理应用期间,插入的每个碎片的方向向量根据最大量值坐标方向(主轴方向)会选择立方体贴图的面的二维的图像中的一个。如果两个或者多个坐标有相同的量值,实现时可以定义规则来消除这种情形的歧义。规则必须决定性的且只依赖于(r_x,r_y,r_z)。在3.11表中的目标靶一列解释了主轴方向如何映射到一个特定的立方体贴图目标靶的二维图像上。

image

使用在表3.11中指定的由主轴方向决定的s_c,t_c,m_a,更新的(s,t)通过下面算是计算:
s = \frac{1}{2}(\frac{s_c}{|m_a|}+1) \\ t = \frac{1}{2}(\frac{t_c}{|m_a|}+1)
新的值(s t)通过使用3.7.6到3.7.8节中给出的规则,来发现在已经确定的朝向面的二维纹理图像的纹理值。

3.7.6 纹理的包裹模式

包裹模式分别由TEXTURE_WRAP_STEXTURE_WRAP_T的值定义,包裹模式会影响纹理坐标s和t的解释。下面描述每种模式的效果。

  • 包裹模式 REPEAT

包裹模式REPEAT会忽略纹理坐标系的整数部分,仅仅使用小数部分。(对于数字f,f的小数部分是f-|f|,不考虑f的符号;记得\lfloor\rfloor函数是往-\infty朝向截断的)。
REPEAT是所有纹理坐标的默认行为。

  • 包裹模式 CLAMP_TO_EDGE

包裹模式ClAMP_TO_EDGE把纹理坐标钳制在所有的mip图级别中,这样,纹理过滤器就不会采集纹理图像以外的值。返回的颜色在钳制时仅仅来自于纹理图像边缘的纹素。

纹理坐标被钳制在[min, max]的范围中,最小值被定义为
min = \frac {1}{2N}
N是在钳制方向的纹理图像的大小。最大值被定义为
max = 1-min
所以钳制总是关于纹理坐标的[0,1]映射区间对称。

  • 包裹模式 MIRRORED_REPEAT

包裹模式MIRRORED_REPEAT先会对纹理坐标做镜像,值f的镜像计算如下:
mirror(f) = \begin{cases} f-|f|, & \lfloor f \rfloor \text {是偶数} \\ 1-(f-|f|), & \lfloor f \rfloor \text {是奇数} \end{cases}

然后如同上述描述的包裹模式CLAMP_TO_EDGE那样,被镜像的坐标被固定起来。

3.7.7 纹理缩小化

在图元上使用纹理意味着要把纹理图像空间映射到帧缓冲图像空间。总体上来说,这个映射过程涉及到采样纹理图像的重建,然后在帧缓冲空间映射过程的相同的包裹模式,然后是一个滤波器,最后一步,在应用到碎片之前,是一副被滤波的,被包裹的,被重建的图像的重采样。在GL中,这个映射由两个简单的过滤方案来近似。一种方案的选择是基于从纹理空间到帧缓冲空间的映射是否被认为是放大或者缩小了纹理图像。

  • 放缩因子和细节等级

这种行为被一个放缩因\rho(x,y)和细节等级参数\lambda(x,y)管理,公式是:
\lambda(x,y) = \log_2[\rho(x,y)] \qquad (3.11)
如果\lambda(x,y)小于或者等于常数c(在3.7.8节中描述),纹理图像就被认为是放大了;如果大于c,纹理图像就是被缩小了。

让函数s(x,y)把在图元内的每组窗口坐标(x,y)和纹理坐标s关联起来;t(x,y)类似。让u(x,y) = w_t \times s(x,y), v(x,y) = h_t \times t(x,y),w_t,h_t是0级别图像矩阵的宽高。对于一个多边形,窗口坐标(x,y)在碎片上的\rho计算:

\rho = \max \{ \sqrt{ (\frac{\partial u}{\partial x})^2+ (\frac{\partial v}{\partial x})^2},\sqrt{(\frac{\partial u}{\partial y})^2 + (\frac {\partial v}{\partial y})^2} \} \qquad (3.12)
\partial u / \partial x是u关于窗口x的导数,其他的导数也相同。

对于直线,公式是:
\rho = \sqrt{(\frac{\partial u}{\partial x}\Delta x +\frac{\partial u}{\partial y}\Delta y)^2,(\frac{\partial v}{\partial x}\Delta x +\frac{\partial v}{\partial y}\Delta y)^2} / l, \qquad (3.13)
其中\Delta x = x_2-x_1, \Delta y = y_2-y_1(x_1,y_1),(x_2,y_2)是部分的窗口坐标,l = \sqrt{\Delta x^2+\Delta y^2}。对于一个点来说,\rho \equiv 1

尽管通常认为3.12和3.13会给出纹理过程中最好的值,但是他们的实现是不能被实践的。因此,在服从下面这些条件是,实现时可以用函数f(x,y)近似为理想的\rho

  1. f(x,y)在每个|\partial u / \partial x|,|\partial u / \partial y|,|\partial v / \partial x|,|\partial v / \partial y|都是连续单调的。
  2. m_u = \max \{ |\frac{\partial u}{\partial x}|,|\frac{\partial u}{\partial y}|\},m_v = \max \{ |\frac{\partial v}{\partial x}|,|\frac{\partial v}{\partial y}|\},其中,\max{\{m_u,m_v\}} \le f(x,y) \le m_u+m_v.

\lambda指代缩小时,分配给TEXTURE_MIN_FILTER的值被用来决定碎片的纹理是怎样选择的。当TEXTURE_MIN_FILTERNEAREST时,坐标(s,t)指定的在0等级阵列中距离最近的(曼哈顿距离)纹素就会被获取到。这意味着在(i,j)位置的纹素会变成纹理值,i的公式:
i = \begin{cases} \lfloor u \rfloor, & s<1 \\ w_t-1, & s=1 \end{cases} \qquad (3.14)
(注意TEXTURE_WRAP_SREPEAT,0 \le s <1)同样的,j的公式为:
i = \begin{cases} \lfloor v \rfloor, & t<1 \\ h_t-1, & t=1 \end{cases} \qquad (3.15)

TEXTURE_MIN_FILTERLINEAR,在0等级图像阵列中2X2的方形纹素就会被选择。这个方块如同3.7.6中那样首先获取第一次包裹的纹理坐标,然后计算:
i_0 = \begin{cases} \lfloor u-1/2 \rfloor \,mod \, w_t,& \text {TEXTURE_WRAP_S is REPEAT} \\ \lfloor u-1/2 \rfloor, &\text{otherwise} \end{cases}
j_0 = \begin{cases} \lfloor v-1/2 \rfloor \,mod \, h_t,&\text{TEXTURE_WRAP_T is REPEAT} \\ \lfloor v-1/2 \rfloor, &\text{otherwise} \end{cases}
i_1 = \begin{cases} (i_0+1)\,mod \, w_t,&\text{TEXTURE_WRAP_S is REPEAT} \\ i_0+1 , &\text{otherwise} \end{cases}
j_1 = \begin{cases} (j_0+1) \,mod \, h_t,&\text{TEXTURE_WRAP_T is REPEAT} \\ j_0+1 , &\text{otherwise} \end{cases}
\alpha = frac(u-1/2) \\ \beta = frac(v-1/2)
其中frac(x)表示x的小数部分。
纹理值\tau的公式如下:
\tau = (1-\alpha)(1-\beta)\tau_{i0j0}+\alpha(1-\beta)\tau_{i1j0}+(1-\alpha)\beta\tau_{i0j1}+\alpha\beta\tau_{i1j1} \quad(3.16)
其中\tau_{ij}是在纹理图像的(i,j)位置的纹素。

  • 反馈回路渲染过程

一个渲染反馈回路发生在当一个纹理附着在当前边界帧缓冲对象的附着点上。这种情况下渲染结果是未定义的。准确的条件在4.4.4节中有详细描述。

  • Mip映射

TEXTURE_MIN_FILTER的值NEAREST_MIPMAP_NEAREST,NEAREST_MIPMAP_LINEAR,LINEAR_MIPMAP_NEAREST,LINEAR_MIPMAP_LINEAR,每一个都需要用到mipmap。mipmap是一组表现相同图像的顺序图像阵列;每个图像阵列的分辨率都比前一个低一点。如果0级别的图像阵列有分辨率为w_b \times h_b,那么在mipmap中会有\lfloor log_2(\max(w_b,h_b)) \rfloor + 1个图像阵列。每个随后的图像阵列的的分辨率为:
\max(1, \lfloor \frac{w_b}{2^i} \rfloor) \times \max(1,\lfloor \frac{h_b}{2^i} \rfloor)
知道最后一个图像阵列的维度为1 \times 1

在使用TexImage2D或者CopyTexImage2D时可以定义在mip图中的每个图像阵列;被设置的图像阵列使用参数level来指定细节的等级。从原始纹理数组的等级0,到通过公式q = \lfloor log_2(\max(w_b,h_b)) \rfloor来计算每个单元增量,这些细节等级的数字表示一组图像阵列,正如描述过的那样,每个图像阵列都是前一个一半的分辨率(如果有小数部分,就去下一个整数)。所有从0到q的图像阵列都是被定义的,在3.7.10中会讨论。

如果在mip图中的任一图像阵列的维度不是2的平方(例如,像上面描述的那样向下取整),这个mip图就会被当做不是二的指数型的纹理。这种纹理在纹理包裹模式和滤波时会有一些限制,在3.8.2节中介绍。

mip图和细节等级结合使用,来近似的将比较合适的过滤过的纹理应用到碎片上。从最小到最大的变化发生时,让\lambda的值为c。(因为这个讨论和最小化有关,我们只关心当\lambda >c\lambda的值)。

对于mip图的滤波器NEAREST_MIPMAP_NEARESTLINEAR_MIPMAP_NEAREST,在mip图中的第d个图像被选择时,有
d = \begin{cases} 0, & \lambda \le \frac{1}{2} \\ \lceil \lambda + \frac{1}{2}\rceil - 1, & \lambda > \frac{1}{2}, \lambda \le q + \frac{1}{2} \\ q, & \lambda > q + \frac{1}{2} \end{cases} \qquad (3.17)
NEARESTLINEAR的过滤规则然后被用在数组阵列的选择中。

对于mip图的滤波器NEAREST_MIPMAP_LINEARLINEAR_MIPMAP_LINEAR,d_1,d_2等级的mip图总是被选择的,有

d_1 = \begin{cases} q, & \lambda \ge q \\ \lfloor \lambda \rfloor, & otherwise \end{cases} \qquad (3.18) \\ d_2 = \begin{cases} q, & \lambda \ge q \\ d_1+1, & otherwise \end{cases} \qquad (3.19)

NEAREST或者LINEAR滤波器的规则被应用到每个被选择的图像阵列上,产生两个相对应纹理值\tau_1,\tau_2。最终的纹理值计算公式为:
\tau = [1-frac(\lambda)]\tau_1 + frac(\lambda)\tau_2.

3.7.8 纹理最大化

\lambda指放大系数,TEXTURE_MAG_FILTER的值会决定纹理值的获取方式。TEXTURE_MAG_FILTER有两种可能的值:NEARESTLINEARNEAREST的表现和TEXTURE_MIN_FILTERNEAREST(公式3.14和3.15中使用的)完全一样;LINEAR的表现和TEXTURE_MIN_FILTERLINEAR(公式3.16中使用的)完全一样。0等级的图像阵列总是用在最大化中。

最后有一个c值的选择问题,是点的放大还是缩小的转换点。如果放大滤波器是由LINEAR给出的,缩小滤波器是由NEAREST_MIPMAP_NEAREST或者NEAREST_MIPMAP_LINEAR给出的,那么c=0.5。这样做是为了保证一个缩小的纹理不会比放大的纹理更“尖锐”。否则c=0。

3.7.9 纹理帧缓冲附着

如果下面的条件都成立,纹理值会被认为是无定义的:

  • 当前的FRAMEBUFFER_BINDING命名了一个应用程序创建的帧缓冲对象F。
  • 纹理附着在帧缓冲对象F的附着点A上
  • TEXTURE_MIN_FILTERNEAREST或者LINEAR,附着点A的FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL的值是0;或者TEXTURE_MIN_FILTERNEAREST_MIPMAP_NEAREST,NEAREST_MIPMAP_LINEAR,LINEAR_MIPMAP_NEAREST,LINEAR_MIPMAP_LINEAR,而且A的FREAMBUFFER_ATTACHMENT_TEXTURE_LEVEL值是在0到最后的mip等级的闭区间中。

3.7.10 纹理的完整性和不是2的指数型纹理

纹理的完整性是指,如果所有的图像阵列和纹理应用所使用的纹理所必要的纹理参数都被一致定义过。

二维的纹理的完整性需要下面的条件为真:

  • 从0到q的mip图像阵列数组(q的定义在3.7.7节中的Mipmapping的定义中讨论过)中的每个阵列都被指定了相同的格式,内部格式和类型。
  • 图像阵列的维数和3.7.7节中Mipmapping中描述的一样按照序列排列。
  • 每个0等级的图像整理的维数是正的。

对于立方体贴图纹理,一个纹理的“立方体完整性”需要满足一下条件:

  • 六个纹理图像的每一个的0级别阵列使得立方体贴图有相等的,正数的方形维度。
  • 每个0级别阵列都被指定了相同的格式,内部格式和类型。

最后,一个立方体贴图纹理如果需要mip图立方体完整性,就是在立方体完整性基础上,增加一条,就是六个纹理图像中的每一个都被认为是拥有独立的完整性的。

  • 完整性对纹理应用的影响

在顶点和碎片着色器中执行的纹理查询表受到被采样的纹理的完整性的影响,在2.10.5和3.8.2中有描述。

  • 完整性对纹理图像规范的影响

如果完整的图像序列和要求的可被支持的图像阵列是一致的,实现可以允许创建详细等级为1或者比1大的纹理图像阵列。

Mip图的产生

Mip图可以通过函数产生:

void GenerateMipmap(enum target);

target是目标靶,必须是TEXTURE_2D或者TEXTURE_CUBE_MAP

GenerateMipmap从等级为0的图像阵列中导出了mip图阵列的完整集合(定义在3.7.10节)。1到q的阵列等级被导出的图像阵列代替,不用考虑他们的之前的内容。通过这种计算,0级数组阵列保持不变。

导出的mip图数组阵列的内部格式都和他们的0级数组阵列相同,导出的数组的维数遵循3.7.10节中描述的要求。

导出的mip图数组的内容是通过对0级阵列的重复的过滤的简化计算出来的。不需要特殊的过滤算法,一个空的滤波算法被推荐为默认滤波。

对于立方体贴图,如果target的纹理不是完整的立方体,会产生INVALID_OPERATION错误。

如果0等级阵列的宽或者高不是2或者2的幂,会产生INVALID_OPERATION的错误。

如果0级阵列按照压缩的内部格式存储,会产生INVALID_OPERATION错误。

3.7.12 纹理状态

纹理必须的状态分为两种策略。首先,有mipmap数组的七个集合(一个是二维的目标靶,六个是立方体贴图的纹理目标靶)以及他们的数目。每个图像都和他们相关联,一个宽,一个高,一个描述图像内部格式的整数,六个整数值描述图像的红,绿,蓝,透明度,亮度,强度通道的分辨率,每个通道的类型用一个整数描述,图像是否被压缩用一个布尔值描述,压缩图形的大小用一个整数描述。每个处室的纹理数组是空的(宽高为0)。然后,有两组纹理属性;每组属性由被选择的最小化和最大化滤镜,以及对s和t的包裹模式组成。在初始状态中,TEXTURE_MIN_FILTER的值是NEAREST_MIPMAP_LINEAR,TEXTURE_MAG_FILTER的值是LINEAR。s和t的包裹模式都被设置为REPEAT

3.7.13 纹理对象

除了默认的TEXTURE_2DTEXTURE_CUBE_MAP,命名为二维或者立体贴图的纹理对象都可以被创建和操作。纹理对象的命名空间是无符号整数,GL保留了0。

一个纹理对象通过给TEXTURE_2D或者TEXTURE_CUBE_MAP绑定一个没有使用过的名字就可以创建了。绑定是通过下面函数起作用的:

void BindTexture(enum target, uint texture);

target要设置想要的纹理目标靶,texutre要设置没有使用过的名字。得到的纹理对象是一个新的状态向量,在3.7.12节中列出来所有构成的状态的值,并设置了同样的初始值。如果新的纹理对象绑定了TEXTURE_2D,TEXTURE_CUBEMAP*,新的纹理对象就会保留一个二维的或者立方体的纹理,直到被删为止。

BindTexture也可以来绑定一个已经存在的纹理对象,无论是TEXTURE_2D还是TEXTURE_CUBE_MAP都可以。如果试图绑定一个和指定的target维度不同的纹理对象,将会产生INVALID_OPERATION错误。如果绑定是成功的,那么绑定的纹理对象的状态没有发生变化,而且之前绑定在target的纹理对象也会破裂。

当纹理对象被绑定,对目标靶的GL操作会对绑定的对象产生作用,对目标靶查询会使得目标靶的绑定对象返回状态。

在初始状态下,TEXTURE_2D或者TEXTURE_CUBE_MAP各自有两个二维的或者立方体贴图纹理的状态向量和他们相联系。为了访问这些初始的纹理不被丢失,他们都会被当做是纹理对象,他们的名字都是0。初始化的二维的或者立方体贴图纹理就这样分别在TEXTURE_2D或者TEXTURE_CUBE_MAP上,在0绑定到相应的目标靶上时,操作,查询和应用。

纹理对象通过调用下面函数删除:

void DeleteTexture(sizei n, uint *textures);

textures包含了n个要被删除纹理对象的名字。在纹理对象被删除后,是没有内容或者维数的,它的名字也再次变成了未被使用过的。如果一个纹理正绑定在TEXTURE_2D或者TEXTURE_CUBE_MAP被删除,就像使用BindTexture使用相同的targettexture设置为0是一样的。在textures中没有使用的名称被静默忽略,值为0也是一样的。

函数

void GenTextures(sizei n, uint *textures);

textures中返回了之前没有被使用的n个纹理对象名字。这些名字会被标记为已经使用过的,这样仅仅是为了函数GenTextures,但他们要求纹理状态指示在第一次被绑定时使用,就像他们没有用过一样。

纹理对象的命名空间,包括初始化的纹理对象,在所有的纹理单元中共享。一个纹理对象可能同时绑定到超过一个纹理单元上。在纹理对象绑定后,在目标靶上的对象的GL操作会影响其他的绑定着相同的纹理对象的单元。

纹理绑定受到状态的设置ACTIVE_TEXTURE的影响。

如果纹理对象被删除了,就好像是绑定在这个纹理对象的所有纹理单元都重新被绑定到了纹理对象0上。

推荐阅读更多精彩内容