首页 > 其他分享 >计算机图形:纹理与表面细节

计算机图形:纹理与表面细节

时间:2023-12-28 14:36:05浏览次数:33  
标签:bm TEXTURE 纹理 图案 细节 坐标 图形 GL

目录

纹理技术

为什么需要纹理?

因为大多数对象表面并不光滑,单纯的光照、表面绘制技术并不能真实模拟对象,因而需要添加纹理.

如何添加纹理?

添加纹理的方式,称为添加表面细节,有以下方法:

  • 把一些小物体(如花苞、花、刺等)添加到大表面上
  • 用小的多边形区域组成表面图案
  • 将纹理数组或强度修改过程映射到一个对象表面 —— 纹理映射
  • 修改表面法向量,生成局部的凹凸效果 —— 凹凸映射
  • 修改表面法向量、切向量,显示木材等材料表面方向性纹理

纹理映射

纹理就是图案。

将纹理模式映射到对象表面上. 纹理模式可由一个矩形数组定义,也可用修改对象表面光强度值的过程定义,这种方法称为纹理映射(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).

二维纹理空间、对象空间和像空间的坐标系统关系:

img

tips: 纹理映射,就是指图中纹理-表面变换对应的映射.

从纹理空间到对象空间的仿射变换:

\[\begin{aligned} u&=u(s,t)=a_us+b_ut+c_u\\ v&=v(s,t)=a_vs+b_vt+c_v \end{aligned} \]

类似地,对象空间到像空间的变换是合并观察、投影变换.

,将一个表面纹理由纹理空间映射到圆柱体表面,并最终映射到像空间.

设纹理空间范围: \(s\in [0,1.0], t\in[0,1.0]\),圆柱坐标(对象空间):

\[u=θ, v=z, 0\le θ \le \pi/2, 0\le z \le 1 \]

在笛卡尔坐标系中,圆柱体可用参数表示:

\[x=r\cos u,y=r\sin u, z=v \]

建立了对象空间到像空间的变换. 如何建立纹理空间到对象空间的变换?
可用将纹理矩形左下角(原点)(0, 0)、右上角(1.0, 1.0)与像空间的左下角(r,0,0)、右上角(r, 1.0, 1.0)对齐.

tips: 有个隐含条件:投影平面坐标系xOy与图中xyz坐标的xOy平面重合.

对于任一点(s,t),有

\[u=s\cdot \frac{\pi}{2}, v=t \]

如何求观察-投影变换的逆变换?纹理映射的逆变换?

观察-投影变换的逆变换,即将(x,y,z)坐标表示(u,v)坐标

\[u=θ=\arctan \frac{y}{x}, v=z \]

img

体纹理图案

  • 什么是三维体纹理图案?

除线性、表面图案,还可为空间三维区域指定一组颜色. 这种纹理称为体纹理图案(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)\)分别为:

\[\begin{aligned} \bm{S}(i,j)&=(1, 0, aH(i+1, j)-aH(i-1, j))\\ \bm{T}(i,j)&=(0, 1, aH(i, j+1)-aH(i, j-1)) \end{aligned} \]

其中,a为比例系数,可用于修改高度值范围,从而控制法向量扰动的明显程度.

为了简化描述,令\(S_z, T_z\)分别为\(\bm{S}(i, j), \bm{T}(i, j)\)的z分量

则法向量可用外积得到:

\[\begin{aligned} \bm{N}(i, j)&=\bm{S}(i, j)\times \bm{T}(i ,j)\\ &=(1, 0, S_z)\times (0, 1, T_z)\\ &=(-S_z, -T_z, 1) \end{aligned} \]

用了向量外积的坐标计算(参见解析几何笔记:向量的外积). 证明如下:
设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)\),则

\[\begin{aligned} \bm{X}\times \bm{Y}&=(a_1\bm{e_1}+b_1\bm{e_2}+c_1\bm{e_3})\times (a_2\bm{e_1}+b_2\bm{e_2}+c_2\bm{e_3})\\ &=\begin{vmatrix} \bm{e_1} & a_1 & a_2\\ \bm{e_2} & b_1 & b_2\\ \bm{e_3} & c_1 & c_2\\ \end{vmatrix}\\ &=\bm{e_1}(b_1c_2 - b_2c_1)-\bm{e_2}(a_1c_2 - a_2c_1)+\bm{e_3}(a_1b_2-a_2b_1) \end{aligned} \]

∵S、T方向垂直
∴可将\(\bm{S}(i, j)=(1, 0, S_z), \bm{T}(i, j)=(0, 1, T_z)\)坐标代入:

\[\bm{S}(i, j)\times \bm{T}(i ,j)=\bm{e_1}(0-S_z)-\bm{e_2}(T_z-0)+\bm{e_3}(1-0)=(-S_z, -T_z, 1) \]

即得证.

可得(i,j)处单位法向量:

\[\bm{N}(i, j)=\frac{\bm{S}(i,j)\times \bm{T}(i, j)}{|\bm{S}(i,j)\times \bm{T}(i, j)|}=\frac{(-S_z, -T_z, 1)}{\sqrt{S_z^2+T_z^2+1}} \]

其中,\(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.

标签:bm,TEXTURE,纹理,图案,细节,坐标,图形,GL
From: https://www.cnblogs.com/fortunely/p/17920170.html

相关文章

  • 可视化学习:图形系统中的颜色表示
    引言说到颜色,前端的小伙伴们一定都不陌生,比如字体颜色、背景色等等,颜色是构建视觉效果的重要部分,所以也必然是可视化的关键部分,当学习到可视化中有关于颜色表示的这一部分时,我也想起了我曾经玩过的一个游戏,叫做Blendoku,这个名字和数独的Sudoku有些类似,考验的是玩家对颜色的敏锐度......
  • 3D游戏角色建模纹理贴图处理
    在线工具推荐:3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.jsAI自动纹理开发包 - YOLO虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎在本文中,我们将介绍3D纹理的基础知识,并讨论为什么它是游戏美术的关键部分。期......
  • PBR纹理贴图类型详解
    在线工具推荐:3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.jsAI自动纹理开发包 - YOLO虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎PBR纹理是一种帮助3D艺术家使他们的3D渲染看起来更逼真的技术。与其他着色......
  • java基础语法API之GUI图形化界面2
    一:概述在1中,已经对GUI图形化的基础知识做了个概述,2中主要以例子为载体说明。并且介绍时间监听机制二:具体说明<1>猜数字//创建窗体对象JFramejf=newJFrame();jf.setTitle("猜数字");jf.setSize(400,300);jf.set......
  • java基础语法API之GUI图形化界面1
    一:概述虽然现在在开发中,前后端交互,用户所看到的界面都是前端实现的,但是java自身的图形化界面,对于java学习初学者还是需要了解的。对于开发是有利的。二:具体说明<1>GUI介绍java中为GUI相关的API在java.awt包和java.swing包中。java.awtawt是这三个单词首字母的缩写,翻译过来是抽象窗......
  • 将开源免费进行到底,ThreadX开源电脑端GUIBuilder图形开发工具GUIX Studio
    上个月微软刚刚宣布将ThreadXRTOS全家桶贡献给Eclipse基金会,免费供大家商用,宽松的MIT授权方式,就差这个GUIXStudio没有开源了,而且Windows还经常检索不到,并且也不提供离线包。1、软件包有点大,700MB,直接分享到百度云了:链接:https://pan.baidu.com/s/1tS8IDWrIXGiCTbHxwxEkDA  提取......
  • Oracle-19c图形化界面安装部署
    ---Oracle数据库图形化界面安装,可以用linux桌面版来安装,也可以用linux命令行界面安装,不过需要Xmanager辅助图形化安装1、安装前期准备:#关闭防火墙,并关闭防火墙开机自启动systemctlstopfirewalldsystemctldisablefirewalld#检查防火墙状态systemctlstatusfirewalld#关闭......
  • 函数图形渐近线分析
    文章目录曲线的渐近线水平和垂直渐近线斜渐近线斜渐近线公式推导简便方法确定斜渐近线(一次多项式化方法)例曲线的渐近线渐近线综合了极限和函数图形的知识,尤其是斜渐近线水平和垂直渐近线若点沿曲线无限远离原点时,它于某条直线之间的距离将趋近于0,则称该直线为曲线的渐近线若......
  • 深度探索Linux操作系统 —— Linux图形原理探讨
    文章目录系列文章目录前言一、渲染和显示1、渲染2、显示二、显存1、动态显存技术2、BufferObject三、2D渲染1、创建前缓冲2、GPU渲染3、CPU渲染(1)映射BO到用户空间(2)使用CPU在映射到用户空间的BO上进行绘制四、3D渲染1、创建帧缓冲2、渲染Pipleline3、交换前缓冲和后缓冲(1)谁......
  • C语言一些小细节
    intvalue=1;intarr[2]={value,2};//C语言错,C++对intarr1[2];arr1[0]=1;//C语言错,C++错inta;a=10;//C语言错,C++错intmain(){intnum;staticintarr[2]={num,2};//C语言错,C++错intnum1=10;staticintarr1[2]={num,2};//C语言错,C++错retu......