计算机图形:纹理与表面细节
纹理技术
为什么需要纹理?
因为大多数对象表面并不光滑,单纯的光照、表面绘制技术并不能真实模拟对象,因而需要添加纹理.
如何添加纹理?
添加纹理的方式,称为添加表面细节,有以下方法:
- 把一些小物体(如花苞、花、刺等)添加到大表面上
- 用小的多边形区域组成表面图案
- 将纹理数组或强度修改过程映射到一个对象表面 —— 纹理映射
- 修改表面法向量,生成局部的凹凸效果 —— 凹凸映射
- 修改表面法向量、切向量,显示木材等材料表面方向性纹理
纹理映射
纹理就是图案。
将纹理模式映射到对象表面上. 纹理模式可由一个矩形数组定义,也可用修改对象表面光强度值的过程定义,这种方法称为纹理映射(texture mapping)或图案映射(pattern mapping).
纹理可以是一维、二维或三维图案. 任意纹理描述称为纹理空间(texture space),用0~1.0范围的纹理坐标(texture coordinates)来表示.
纹理描述的一个成分称为纹理元(texel). 纹理元不同场景有不同含义:有时与一组颜色分量对应的纹理空间的一个位置,如RGB三角形,称为一个纹理元;有时单个纹理数组元素,如RGB颜色中的分量,也称为一个纹理元.
tips: 每个RGB包含3元素R、G、B分量,每个分量通常占用1byte.
线性纹理图案
- 什么是一维线性纹理?
线性纹理指用一维数组连续存储多个RGB颜色分量,纹理坐标按百分比存储颜色序号.
对于一个线性图案,纹理空间用单个s坐标值表示. s=0表示第一组的RGB颜色,s=0.5表示中间的RGB颜色,s=1.0表示最后一组.
- 应用
常用于图案条纹、围住圆柱的图案带,一条孤立线段的颜色图案.
表面纹理图案
- 什么是二维表面纹理图案?
常用矩形颜色图案定义表面区域纹理,纹理空间用二维(s,t)坐标表示. s, t的范围0~1.0
每个纹理坐标对应一个RGB颜色. 纹理坐标(0, 0)表示原点,代表第一组颜色分量;(1.0, 1.0)代表最后一组颜色分量. 原点可在左下角,也可在左上角.
三次样条曲面或球面等对象的表面位置用uv对象空间坐标表示,投影像素位置用xy笛卡尔坐标表示.
- 如何将纹理图案映射到对象表面,并在屏幕上显示?
2种方法:
1)将纹理映射到对象表面,再到投影平面;
2)将像素区间映射到对象表面,再将该表面区域映射到纹理空间.
将纹理图案映射到像素坐标,也称为纹理扫描(texture scanning);将像素坐标映射到纹理空间的映射称为像素次序扫描(pixel-order scanning)、逆扫描(inverse scanning)或图像次序扫描(image-order scanning).
二维纹理空间、对象空间和像空间的坐标系统关系:
tips: 纹理映射,就是指图中纹理-表面变换对应的映射.
从纹理空间到对象空间的仿射变换:
类似地,对象空间到像空间的变换是合并观察、投影变换.
例,将一个表面纹理由纹理空间映射到圆柱体表面,并最终映射到像空间.
设纹理空间范围: \(s\in [0,1.0], t\in[0,1.0]\),圆柱坐标(对象空间):
在笛卡尔坐标系中,圆柱体可用参数表示:
建立了对象空间到像空间的变换. 如何建立纹理空间到对象空间的变换?
可用将纹理矩形左下角(原点)(0, 0)、右上角(1.0, 1.0)与像空间的左下角(r,0,0)、右上角(r, 1.0, 1.0)对齐.
tips: 有个隐含条件:投影平面坐标系xOy与图中xyz坐标的xOy平面重合.
对于任一点(s,t),有
如何求观察-投影变换的逆变换?纹理映射的逆变换?
观察-投影变换的逆变换,即将(x,y,z)坐标表示(u,v)坐标
体纹理图案
- 什么是三维体纹理图案?
除线性、表面图案,还可为空间三维区域指定一组颜色. 这种纹理称为体纹理图案(volume texture patterns)或实体纹理(solid textures).
体纹理图案通过三维纹理坐标(s,t,r)指定,范围都是[0,1.0],形成一个单位立方体.
体纹理可提供内部视图,如剖切显示、切片,使得三维对象的显示中带有纹理图案,如砖块、木头等.
- 如何将体纹理图案映射到三维块?
可将纹理空间的八个角(4个面共8个角)的坐标赋予场景的8个空间位置;
或者,将纹理空间的一个平面映射到场景中一个平面区域.
纹理缩减图案
动画等场景中,对象大小经常改变,同时,需要重新纹理映射. 当有纹理的对象缩小时,原来的纹理图案应用于较小的区域会导致纹理变形. 为了避免这种问题,可创建一系列纹理缩减图案(texture reduction patterns),在对象缩小时使用.
通常,每个缩减图案是之前的一半. 如最大时对应16x16的图案,那么可建立尺寸为8x8,4x4,2x2,1x1的另外几个图案. 这些缩减图案,常称为MIP图(MIP maps)或mip图(mip maps).
凹凸映射
纹理映射能用于模拟精致的表面细节,但对于模拟粗糙的物体表面,如橘子、草莓、葡萄干等不合适. 而凹凸映射能有效模拟.
凹凸映射(Bump Mapping)指至少两种不同的控制每个纹理元素的表面法线的方法. 核心是用扰动函数并且在光照模型计算中使用扰动法向量.
凹凸映射技术中,纹理图用来扰动每个像素的法向量,实现像素级的光照计算精度.
构造凹凸图
将扰动法向量的高分辨率信息保存在存放三维向量的二维数组中,该二维数组称为凹凸图或法向量图. 如\((x, y, \bm{n})\)表示对象空间中的\((x, y)\)位置处法向量\(\bm{n}\),其中\(\bm{n}\)就是三维向量. 原始的每个向量表示三角形的任一点的插值法向量.
凹凸图中的向量(0,0,1)表示未扰动的法向量,其他任何向量表示对影响光照公式计算结果的法向量的扰动.
tips: 三角形顶点的切向量空间(局部坐标系)中,z轴//法向量⊥三角形平面,而向量(0, 0, 1)代表单位法向量.
- 从高度图到凹凸图
通常,高度图(Height Map)更容易制作,但不适合实时计算,需要从高度图中提取的法向量,以得到凹凸图. 高度图由每个像素上的平直表面的高度组成,例如(x,y,h)表示坐标为(x,y)点对应高度为h,其中,x与s、y与t方向对应.
如何用高度图计算相邻像素间的高度差?
可计算s、t方向的切向量. 用H(i, j)表示尺寸为\(w×h\)像素的高度图在(i, j)位置的高度值,那么在该点处沿s、t方向的切向量\(\bm{S}(i, j)、\bm{t}(i, j)\)分别为:
其中,a为比例系数,可用于修改高度值范围,从而控制法向量扰动的明显程度.
为了简化描述,令\(S_z, T_z\)分别为\(\bm{S}(i, j), \bm{T}(i, j)\)的z分量
则法向量可用外积得到:
用了向量外积的坐标计算(参见解析几何笔记:向量的外积). 证明如下:
设3D正交坐标系Ⅰ\([O; \bm{e_1}, \bm{e_2}, \bm{e_3}]\)下,2个向量\(\bm{X}=(a_1,b_1,c_1), \bm{Y}=(a_2,b_2,c_2)\),则
∵S、T方向垂直
∴可将\(\bm{S}(i, j)=(1, 0, S_z), \bm{T}(i, j)=(0, 1, T_z)\)坐标代入:
即得证.
可得(i,j)处单位法向量:
其中,\(S_z=aH(i+1, j)-aH(i-1, j), T_z=aH(i, j+1)-aH(i, j-1)\).
顶点空间(待完成)
帧映射
帧映射(Frame Mapping)是一种增加表面细节的方法,凹凸映射的扩展. 可用于建立分等值曲面,模拟木纹图案、大理石等材质的条纹. 而凹凸和方向扰动,可通过查表获得.
基本思想:同时扰动:
1)扰动表面法向量\(\bm{N}\);
2)扰动\(\bm{T}、\bm{B}\).
注:\(\bm{N,T,B}\)构成一个的本地正交坐标系,表面切向量\(\bm{T}\)、双法线向量\(\bm{B}=\bm{T}\times \bm{N}\).
OpenGL函数
纹理函数扩充集,只支持RGBA颜色模型.
线纹理函数
- 定义纹理(数组)
用颜色数组来定义线性纹理数组.
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, nTexColors, 0, dataFormat, dataType, lineTexArray);
glEnable(GL_TEXTURE_1D);
// 原型
void glTexImage1D(GLenum target, GLint level, GLint internalformat,
GLsizei width, GLint border, GLint format,
GLenum type, const GLvoid *pixels);
目标纹理target必须是GL_TEXTURE_1D,表明正在为一维对象(即线段)定义一个纹理数组. 如果不清楚系统是否支持,可用glTextImage1D + GL_PROXY_TEXTURE_1D先查询.
细节级别level为0,代表基础图像级别. 级别n代表第n个mipmap缩减图像(参见前面的纹理缩减图案).
纹理中颜色分量的数量internalformat,必须为1,2,3,4或者指定的符号常量如GL_RGB、GL_RGBA等.
纹理图像的宽度width,必须为2n+2(border),代表纹理图案中纹理颜色数量. 纹理图像的高度为1.
边框宽度border,必须为0或1.
像素数据格式format,必须为9个符号常量如GL_RED、GL_GREEN、GL_BLUE、GL_RGB、GL_RGBA等.
像素数据类型type,只能是以下符号常量GL_UNSIGNED_BYTE、GL_BYTE、GL_BITMAP、GL_UNSIGNED_SHORT、GL_SHORT、GL_UNSIGNED_INT、GL_INT 和 GL_FLOAT.
pixels是指向像素数据的指针,通常是一个数组,大小为level * width(border=0).
- 设置纹理参数
纹理元素的区域可能与像素区域有多种映射关系,可能放大(MAG)或缩小(MIN)纹理图案以适合目标像素区域. 而glTexParameteri函数用来设置这部分参数,可简化纹理映射时的计算.
// 放大纹理图案
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 缩小纹理图案
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// 原型
void glTexParameteri(GLenum target, GLenum pname, GLint param);
目标纹理target必须是GL_TEXTURE_1D(线性纹理) or GL_TEXTURE_2D(表面纹理).
纹理参数名pname可以是:
1)GL_TEXTURE_MIN_FILTER 当像素区域 > 纹理元素的区域时,将使用纹理缩小函数. 有6个纹理缩小函数,其中2个使用最近的四个纹理元素来计算纹理值,另4个使用mipmap. Mipmap用glTexImage1D/glTexImage2D定义.
2)GL_TEXTURE_MAG_FILTER 当像素区域 <= 纹理元素的区域时,将使用纹理放大函数,方式为GL_NEAREST或GL_LINEAR(对应param值).
3)GL_TEXTURE_WRAP_S 将纹理坐标的包装参数设置为GL_CLAMP或GL_REPEAT. GL_CLAMP将坐标固定到[0,1],GL_REPEAT将忽略s坐标整数部分,且用小数部分创建重复模式.
4)GL_TEXTURE_WRAP_T 将纹理坐标t的包装参数设置为GL_CLAMP或GL_REPEAT.
- 设置当前纹理坐标
针对线性纹理,设置当前纹理坐标.
glTexCoord1*(s);
后缀码表示参数s的数据类型,支持b(字节)、s(短整数)、i(整数)、f(浮点数)、d(双精度浮点数).
s是纹理坐标,如果s是数组,则后缀码可+v. 默认值0.0. 要将线性纹理图案映射到世界坐标系的位置,可将s赋给一条线段的端点.
可在glBegin/glEnd之间调用.
- 示例:交替使用绿色和红色的四元素线性纹理图案
线段交替使用绿色和红色.
void display()
{
glClear(GL_COLOR_BUFFER_BIT);
// glColor3f(0.0, 0.0, 0.0);
glColor3f(1.0, 1.0, 1.0); // 线段用白色绘制
GLint k;
GLubyte texLine[16]; // 16个元素的纹理数组, 每4个元素形成一组RGBA信息
GLfloat endPt1[3] = {20, 10, 0};
GLfloat endPt2[3] = {400, 300, 0};
/* 定义交替的绿色、红色的四元数线性纹理图案 */
for (k = 0; k <= 2; k += 2) {
texLine[4 * k] = 0;
texLine[4 * k + 1] = 255;
texLine[4 * k + 2] = 0;
texLine[4 * k + 3] = 255;
}
for (k = 1; k <= 3; k += 2) {
texLine[4 * k] = 255;
texLine[4 * k + 1] = 0;
texLine[4 * k + 2] = 0;
texLine[4 * k + 3] = 0;
}
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, texLine);
glEnable(GL_TEXTURE_1D);
glBegin(GL_LINES); // 绘制线段
glTexCoord1f(0.0); // 0.0 是线性纹理坐标s起点
glVertex3fv(endPt1);
glTexCoord1f(1.0); // 1.0 是线性纹理坐标s终点
glVertex3fv(endPt2);
glEnd();
glDisable(GL_TEXTURE_1D);
glFlush();
}
注意:线段需用白色绘制,纹理映射后仅显示纹理颜色.
表面纹理函数
- 定义二维纹理数组
与线性纹理的区别是,必须指定二维纹理数组的宽(列数)和高(行数). 没有边界时,宽、高必须是2的幂次;有边界时,宽、高必须是2+2的幂次.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, dataFormat, dataType, surfTexArray);
glEnable(GL_TEXTURE_2D);
第三个参数GL_RGBA表面使用RGBA颜色分量,说明该图案没有边界且不是mipmap. 因此,存储在surfTexArray的数组尺寸是 4×宽×高.
- 设置纹理参数
类似线性纹理图案,表面纹理图案与目标像素区域有多种映射关系,可用glTexParameteri设置这部分参数.
如,下面代码使用最近颜色显示投影表面位置
// 放大纹理图案
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 缩小纹理图案
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- 设置纹理坐标
设置当前纹理坐标(s, t),在glBegin/glEnd之间调用,能绑定当前顶点与该纹理坐标.
注意:纹理空间中,纹理图案的左下角(0, 0)、右上角(1.0, 1.0),对应纹理数组第一组颜色和最后一组颜色.
glTexCoord2*(s, t);
- 示例:红、绿、蓝、黄四色交替的表面纹理图案
void display()
{
glClear(GL_COLOR_BUFFER_BIT);
//glColor3f(0.0, 0.0, 0.0);
glColor3f(1.0, 1.0, 1.0);
GLubyte texArray[32][32][4];
GLint k, j;
GLfloat vertex1[3] = {10, 10, 0};
GLfloat vertex2[3] = {400, 10, 0};
GLfloat vertex3[3] = {10, 300, 0};
GLfloat vertex4[3] = {400, 300, 0};
// 定义红、绿、蓝、黄交替出现的纹理数组
for (k = 0; k < 8; k++) {
for (j = 0; j < 32; j++) {
texArray[k][j][0] = 255;
texArray[k][j][1] = 0;
texArray[k][j][2] = 0;
texArray[k][j][3] = 255;
}
}
for (k = 8; k < 8*2; k++) {
for (j = 0; j < 32; j++) {
texArray[k][j][0] = 0;
texArray[k][j][1] = 255;
texArray[k][j][2] = 0;
texArray[k][j][3] = 0;
}
}
for (k = 8 * 2; k < 8 * 3; k++) {
for (j = 0; j < 32; j++) {
texArray[k][j][0] = 0;
texArray[k][j][1] = 0;
texArray[k][j][2] = 255;
texArray[k][j][3] = 255;
}
}
for (k = 8 * 3; k < 8 * 4; k++) {
for (j = 0; j < 32; j++) {
texArray[k][j][0] = 255;
texArray[k][j][1] = 255;
texArray[k][j][2] = 0;
texArray[k][j][3] = 0;
}
}
// 设置纹理参数, 指定放大、缩小函数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// 创建纹理图案
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, texArray);
glEnable(GL_TEXTURE_2D);
// 绘制矩形并进行表面纹理映射
glBegin(GL_QUAD_STRIP);
glTexCoord2f(0.0, 0.0);
glVertex3fv(vertex1);
glTexCoord2f(1.0, 0.0);
glVertex3fv(vertex2);
glTexCoord2f(1.0, 1.0);
glVertex3fv(vertex3);
glTexCoord2f(0.0, 1.0);
glVertex3fv(vertex4);
glEnd();
glDisable(GL_TEXTURE_1D);
glFlush();
}
完整程序参见:ComputerGraphics/chapter18 | Gitee
体纹理函数
三维纹理空间函数是二维纹理函数的扩充. 函数接口与二维纹理空间函数大体一致,不过出现“2D”的地方(函数名或符号常量),需要修改为"3D".
纹理图案的颜色选项
glTextImage1D,2D,3D第三个参数internalformat,可用来指定纹理图案的每个元素的颜色分量的一般格式、数量,支持约40个符号常量.
每个纹理元素可以是一组RGBA值、RGB值、单个alpha值、单个红色强度值、单个/一对亮度值等,或者指定bit位的尺寸,如常量GL_R3_G3_B2指定1byte的RGB颜色中有3bit用于红色、3bit用于绿色、2bit用于蓝色.
参数format,指定像素数据的特殊格式. 支持11个符号常量(microsoft文档只提到9个),可以用指向颜色表的索引、单个alpha值、单个亮度值等.
参数dataType,指定内存中像素数组pixels的元素的数据类型和位尺寸,支持20个符号常量,常用有GL_BYTE、GL_INT、GL_FLOAT等.
纹理映射选项
当纹理图案映射到的对象也有自身颜色时,如何决定最终的颜色?
可以通过glTexEnvi设置,与当前对象的颜色混合,或取代对象颜色.
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, applicationMethod);
applicationMethod有4个可选操作:
1)GL_REPLACE,表示纹理颜色、亮度、光强或alpha值取代对象对应值(取决于纹理图案的元素格式internalformat).
2)GL_MODULATE(默认值),表示用纹理颜色调制当前对象的颜色值,结果依赖于纹理图案的元素格式,如alpha值调制alpha值,强度值调制强度值. 如果对象颜色是白色(对象默认颜色),则调制操作生成与取反相同的结果,依赖于如何指定纹理图案的元素.
3)GL_DECAL,将RGBA的alpha值作为透明系数,对象可看做对背景中的纹理是透明的. 如果该纹理图案仅包含RGB值而无A值,则该纹理颜色取代对象颜色;如果纹理图案仅包含alpha值,则该映射没有意义.
4)GL_BLEND,用指定的颜色blendingColor与片元进行颜色混合.
glTexEnv*(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, blendingColor);
后缀码由混合颜色的数据类型决定是i或f,如果是数组要+v.
最终颜色 = 纹理的颜色 × 常量颜色 + (1.0 - 纹理颜色) × 原来的颜色.
例如,下面代码使用黄色与对象颜色进行混合
// e.g. 黄色与对象颜色进行混合
GLfloat color[] = {1.0, 1.0, 0.0, 1.0}; // 黄色
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
纹理环绕
当纹理空间的坐标超出0~1.0范围时,可补充纹理空间中描述的图案:
glTexParameter*(texSpace, texWrapCoord, GL_REPEAT);
这样,仅使用纹理空间坐标值的小数部分(0~1.0)补充图案.
参数texSpace表示纹理目标,只能是GL_TEXTURE_1D/2D/3D.
参数texWrapCoord通过GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T、GL_TEXTURE_WRAP_R分别设置纹理空间中(s, t, r)坐标.
第三个参数为GL_CLAMP时,将纹理坐标强制到0~1.0内,如果坐标值>1.0,则赋值1.0;如果<0,则赋值0.
为GL_REPEAT(默认)时,使用0~1.0小数部分重复创建问题图案.
复制帧缓存中的纹理图案
将帧缓冲区中的像素复制到二维纹理图案中.
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x0, y0, texWidth, texHeight, 0);
// 原型
void glCopyTexImage2D(GLenum target, GLint level, GLenum internalFormat,
GLint x, GLint y, GLsizei width, GLsizei height,
GLint border
);
第二个参数level为0,表明这个图案是基础图案,而不是缩减的mipmap.
最后一个参数border为0,表明没有边框.
帧缓冲区代表屏幕的一帧数据,而要复制的帧缓冲区部分对应的矩形的左下角作为坐标原点,由参数x, y指定,width、height指定其尺寸.
如果我想复制帧缓冲区的多个部分,最后形成二维纹理图案,而不是仅来自帧缓冲区的一个矩形部分,该怎么办?
可以多次调用glCopyTexSubImage2D实现,将帧缓冲区指定矩形拷贝到要生成的二维纹理图案的指定位置. 源位置(x0, y0)(左下角)、源矩形大小texSubWidth × texSubHeight,目标位置由(xTexElement, yTexElement)确定.
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, xTexElement, yTexElement, x0, y0, texSubWidth, texSubHeight);
纹理坐标数组
前面用glTexCoord设定单个纹理坐标,能不能像顶点数组一样,一次指定多个纹理坐标?
答案是可以的,使用纹理坐标数组.
- 激活纹理坐标数组
纹理坐标数组需要先激活,再才能使用.
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
关闭用glDisableClientState.
- 指定纹理坐标数组
glTexCoordPointer(nCoords, dataType, offset, texCoordArray);
nCoords 每个数组元素的坐标数,即坐标维度,只能是1、2、3或4. 默认值4,表示以齐次坐标形式指定纹理空间,其空间位置是前3个坐标值/第4个坐标值.
dataType 每个数组元素的数据类型,支持GL_SHORT、 GL_INT、 GL_FLOAT和 GL_DOUBLE.
offset 是texCoordArray数组的位置偏移,默认值0.
texCoordArray 指向数组的第一个元素的第一个坐标的指针.
纹理图案命名
创建了多个纹理图案后,如何使用某个纹理呢?
可以对纹理图案命名(正整数),通过纹理名引用纹理.
- 为纹理图案指定名称
调用glBindTexture可以创建命名纹理,如果指定的纹理名已创建,则该纹理名绑定到新的纹理.
例如,下面代码创建纹理名后,又绑定到新的纹理图案
// 为当前纹理图案命名
glBindTexture(GL_TEXTURE_1D, 3);
// 创建新纹理图案作为当前纹理图案
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, texLine);
// 将纹理名"3"重新绑定到当前纹理图案
glBindTexture(GL_TEXTURE_1D, 3);
- (批量)自动生成纹理名
还可以让OpenGL自动为图案生成名称,而非由调用者挑选,这样调用者不必费心知道哪些纹理名已被使用.
例如,下面代码自动生成1个纹理名并绑定到当前纹理图案
static GLuint texName;
// 生成1个新的纹理名, 保存到texName
glGenTextures(1, &texName);
// 绑定自动生成的纹理名到当前纹理图案
glBindTexture(GL_TEXTURE_2D, texName);
下面代码自动生成6个纹理名,并用其中一个绑定到当前纹理图案
static GLuint texNamesArray[6];
// 生成6个新的纹理名, 保存到数组texNamesArray
glGenTextures(6, texNamesArray);
// 绑定索引为3的纹理名到当前纹理图案
glBindTexture(GL_TEXTURE_2D, texNamesArray[3]);
- 删除纹理名
纹理图案使用完后,可调用glDeleteTextures删除.
// nTextures 要删除的纹理名数量
// texNamesArray 存放要删除的纹理名数组
glDeleteTextures(nTextures, texNamesArray);
- 查询纹理名是否已被使用
// texName 待查询纹理名
// 已被使用, 则返回GL_TRUE; 未被使用或者出错, 返回GL_FALSE
glIsTexture(texName);
纹理子图案
有没有一种方法引用纹理图案的一部分,而不创建新的纹理图案?
可以使用glTexSubImage2D创建子图案,从而修改原始图案的任意部分或全部.
例如,下面代码指定一组RGBA颜色值取代二维纹理的一部分,该部分没有边界、不是mipmap.
glTexSubImage2D(GL_TEXTURE_2D, 0, xTexElement, yTexElement,
GL_RGBA, texSubWidth, texSubHeight, 0,
dataFormat, dataType, subSurfTexArray);
xTexElement、yTexElement 用于选择原始图案中纹理元素的整数坐标位置,其中坐标(0, 0)是纹理图案的左下角.
texSubWidth、texSubHeight 决定子图案的宽、高.
subSurfTexArray 是子纹理图案的数组,由于是GL_RGBA格式元素,所以每个颜色元素4个分量,数组对应总的颜色元素个数4 × texSubWidth × texSubWidth、texSubHeight. 可通过对该数组的修改,实现对子图案的修改,最终体现为对纹理图案的修改.
其他参数同glTextImage.
纹理缩减图案函数
缩小的对象尺寸,可用函数建立一系列纹理缩减图案,称为mip图. 方法:
1)手动生成. 反复调用glTexImage创建尺寸更小的纹理图案,其中第二个参数level为前一次调用+1,即级数+1,表示尺寸缩小一半.
2)自动生成. 调用GLU函数gluBuild2DMipmaps
例如,下面代码为16x16表面纹理自动生成RGBA缩减图案:一组4个图案,缩减后尺寸分别为8x8,4x4,2x2,1x1.
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, 16, 16, GL_RGBA, GL_UNSIGNED_BYTE, surfTexArray);
也可以设定级数范围,而不是生成所有缩减图案.
gluBuild2DMipmapLevels(GL_TEXTURE_2D, GL_RGBA, 16, 16, GL_RGBA, GL_UNSIGNED_BYTE, 0, minLevel, maxLevel, surfTexArray);
其中,minLevel和maxLevel指定级数范围.
什么时候,如何使用纹理缩减图案?
对象缩小时,纹理也需要随之缩小,此时,需要对纹理进行插值以获得更平滑的图像,而glTexParameter + GL_TEXTURE_MIN_FILTER用于指定插值算法. 常见取值有:
1)GL_NEAREST 最近邻插值,选择最靠近像素中心的纹理单元的颜色值.(未使用mipmap)
2)GL_LINEAR 线性插值,选择最靠近像素中心的2x2纹理单元进行加权平均.(未使用mipmap)
3)GL_NEAREST_MIPMAP_NEAREST 选择一个尺寸最接近的mipmap,再用最靠近像素中心的纹理元素的颜色.(使用一个mipmap)
4)GL_LINEAR_MIPMAP_NEAREST 选择尺寸最接近的mipmap,再用最靠近像素中心的2x2纹理单元加权平均. (使用一个mipmap)
5)GL_NEAREST_MIPMAP_LINEAR 选择尺寸最接近的2个的mipmap,再用它们靠近像素中心的纹理元素,进行线性匀和.(使用两个mipmap)
6)GL_LINEAR_MIPMAP_LINEAR 选择尺寸最接近的2个mipmap,计算出2个纹理各自的值(2x2纹理单元加权平均),然后对2个计算结果再次进行线性匀和.(效果最好,但最慢,使用两个mipmap)
例如,下面代码让纹理子程序使用尽可能接近像素尺寸(MIPMAP_NEAREST)的缩减图案,再将该缩减图案中最靠近的纹理元素(GL_NEAREST)的颜色赋值给像素.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
纹理边界
多个纹理或一个纹理的多个复制应用于一个对象时,可能相邻图案边界会出现走样问题. 通过纹理边界的颜色匹配,可以避免走样. 纹理边界包括2个特性:宽度,颜色.
宽度由glTexImage*创建纹理时border参数指定,颜色由glTexParameter*进行设置.
例如,下面代码为一个二维纹理图案指定边界颜色
GLfloat borderColor[4] = {1.0, 0.0, 0.0, 1.0}; // 红色
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
其中,borderColor是四元素RGBA颜色分量. 默认边界颜色是黑色(0.0, 0.0, 0.0, 0.0).
也可以用glTexSubImage将一个相邻图案的颜色值复制到另一个图案边界,边界颜色也可以直接由glTexImage指定的纹理数组中赋值.
代理纹理
APP使用纹理时,往往需要向显卡申请大量资源. 有时不确定继续申请资源时,显卡是否能满足要求,此时可以使用代理纹理.
注意:如果用代理纹理分配资源成功,那么实际纹理不一定成功;但代理纹理失败,则实际纹理一定失败. 还需要取决于运行时情形.
如此,可以提早发现显卡是否有足够资源来处理该图案. 方法是将glTexImage函数第一个参数指定为符号常量,对于二维图案,是GL_PROXY_TEXTURE_2D;对于一维或三维,修改对应后缀为1D或3D即可.
如下面代码使用纹理代理,查询系统是否可以为二维图案指定的高度.
GLint texHeight;
glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA12, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
// 查询是否能为二维图案指定的高度texHeight
glGetTeLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_RGBA12, GL_TEXTURE_HEIGHT, &texHeight);
其中,如果系统不能提供所需要的图案高度,则texHeight返回0;如果能,则返回所需要的值.
类似地,还能用符号常量GL_TEXTURE_WIDTH, _DEPTH, BORDER, BLUE_SIZE查询对应的图案参数.
齐次纹理坐标
所谓齐次坐标,指用n+1维向量表示原本n维向量. 如三维齐次坐标:(x,y,z,h),则对应三维坐标:(x/h, y/h, z/h).
纹理空间的齐次坐标,在多投影效果混合在一个显示中时很有用. 此时,纹理坐标和场景坐标的变换都一样使用4x4矩阵变换,能简化计算.
设置四维纹理空间坐标,用三维齐次坐标表示:
glTexCoord4*(s, t, r, h);
参考
[1] 伦吉尔,E.).3D游戏与计算机图形学中的数学方法(第3版)[M].清华大学出版社,2016.
[2] glteximage1d | microsoft docs
[3] gltexparameteri | microsoft docs
[4] DaveShreiner,李军,徐波.OpenGL编程指南(原书第7版)[M].机械工业出版社,2013.