材质纹理是增加物体表面细节的有效手段。前面我们已经可以加载任意复杂的三 维模型了,但是白乎乎的团,看着一点也不酷,现在是时候让它变漂亮一些了。
第一步我们给它上点颜色。首先我们需要对光照模型有点概念,物体看上去有颜 色,是它被光线照射的结果,如果光是白色的,那么呈现的就是物体本身的颜色, 否则会是光色和表面本色综合的结果。物体表面被点光源照射后,会呈现三个区 域:高光区、过渡色区和环境色区。高光区是镜面反射的结果,亮度特别高,光 源越强越明显;过渡色区是漫反射的结果,通常面积较大;环境色区则是不受光 照部份的颜色,通常较暗。
OpenGL ES 提供了 API 供我们为物体表面指定这三个区域的颜色:
void glMaterialx(GLenum face, GLenum pname, GLfixed * param);
该接口的使用如下所示:
GLfixed specmat[4] = { 1<<16, 0, 0, 0 }; GLfixed diffmat[4] = { 0, 1<<16, 0, 0 }; GLfixed ambmat [4] = { 0, 0, 1<<16, 0 }; glMaterialxv( GL_FRONT, GL_SPECULAR, specmat ); glMaterialxv( GL_FRONT, GL_DIFFUSE, diffmat ); glMaterialxv( GL_FRONT, GL_AMBIENT, ambmat ); ... draw code ...
在之前的“加载模型”示例程序中使用以上材质后的效果:
图
注意,既然颜色与光照有如此密切的关系,如果不调用以下两行代码:
glEnable( GL_LIGHTING ); glEnable( GL_LIGHT0 );
以启用光照计算,绘制结果将会是:
图:draw model with material but no lighting
如果你看过“Hello EGL”例子,你会发现在不启用光照计算时,也可以直接用 COLOR_ARRAY 来给表面上色,但这并不是一般意义上的材质。
给模型表面上色之后,看上去漂亮多了,不过这样还是不足以表达复杂的表面细 节,如果物体表面不是大面积的色块而是复杂的花纹,那就需要通过纹理贴图来 表现了。实际上,纹理贴图是游戏类图形程序最重要的工作,针对娱乐市场开发 的显示卡其优化重点也是增加显卡总线带宽以便更快速度的将纹图贴图从系统内 存传递到显卡处理流水线中以及并行根据纹理计算每个像素点最终颜色。而所谓 专业图形卡则更重视于线条等基本图元。
在 OpenGL ES 中给一个面指定纹理有四步:
- 在内存里准备好纹理数据,
- 调用 glTexImage2D 将纹理数据上传给显卡并设置当前纹理,
- 设置贴图参数,
- 在绘制面时提供纹理坐标(uv)。
OpenGL ES 限制纹理图片的长和宽都必须是 2 的次方,长和宽可以不一样。以 下这段代码在生成一个 128x128 的图片并保存到数组 embText 里:
1 unsigned short embTex[128*128]; 2 unsigned short color[4]={ 0xF800, 0x7E0, 0x1F, 0xffff }; 3 for ( int i = 0; i < 128; ++i ) 4 { 5 for ( int j = 0; j < 128; ++j ) 6 { 7 embTex[i*128+j] = color[(i/32 + j/32)%4]; 8 } 9 } 10
在实际应用中,纹理图片通常需要从特写格式的文件中读取,像 tga 之类的文 件格式比较简单,而 jpg/gif 则相当复杂但压缩率高,多数游戏会采用自定义 格式的文件,这样不仅可以按需要对格式进行优化,也可以提供简单的资源保护。
纹理数据放进内存之后,就可以调用 glTexImage2D 将其上传到显存里并通知流 水线将其作为“当前”纹理:
11 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, 128, 128 , 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, embTex);
参数的意义是相当直接的:
- target: GL_TEXTURE_2D 表示定义一个二维纹理,实际上这是目前 OpenGL ES 支持的唯一一个值;
- level: LOD 级数,每一级 LOD 纹理是上级纹理长宽各缩小一半的图片。如果使用这个纹理的面由于距离或角度,实际可见面积很小,流水线会选用适当级别的纹理来绘制它,这样可以避免实时缩小图片而需要的大量计算。
- internalFormat:内部格式,指定纹理中的颜色组分数量,用象 GL_RGB 这样的符号常量。可以想象 OpenGL ES 只支持几种最常用的格式。简单而言之,这个参数定义纹理在显存中的格式。后面还有一个 format,那是说明内存中的格式的。
- width, height:无边界时必须是2n,有边界时必须是2n+2
- border:边界宽度,必须是 0 或者 1。使用带边界的纹理对解决拼接时的 缝隙是很有帮助的,不过 OpenGL ES 1.0 不支持。OpenGL 支持的纹理大小是非常有限的,你很可能要用多个纹理拼成一个很大图案,由于 Linear 之类的贴图算法需要将拼缝上的像素与相邻像素平均,将被切到下一个贴图上的像素放到边界里,可以保证获得的效果和整张图时是一样的。否则,可能会出现一条明显的拼缝。
- format: 待上传的像素数据格式,GL_RGB等都可以故名思义的。同样,OpenGL ES 1.0 只支持最常用的 RGB,RGBA,LUMINANCE_ALPHA,ALPHA。
- type:待上传像素的数据类型。GL_UNSIGNED_BYTE 表示纹理是一组 8bit 字节序列; GL_UNSIGNED_SHORT_5_6_5 表示是 16bit 字序列,其中头 5 位记录 Red 分量,中 6 位记录 Green 分量,末 5 位记录 Blue 分量。
- pixel:实际的纹理数据。
这里有几点值得关注一下:
- embTex 在这个命令执行完之后就没用了,如果是动态分配的内存,可以 free/delete 之。数据已经上传到显存里了。
- 流水线只会有一个“当前”纹理,如果在绘制之前调用多个 glTexImage3D ,只有最后一个起作用。
- glTexImage 如果一个纹理反复被用到,那么可以调用
glGenTextures(count, &id); glBindTexture(GL_TEXTURE_2D, id);
为其指定一个ID,需要时
glActiveTexture(id);
就可再次将其设为“当前”纹理。这样可以省掉重复上传纹理纹理数据的开销。
控制贴图过程的参数主要有:
- glEnable(GL_TEXTURE_2D):如果disable,那么三维图形流水线会完全忽略纹理贴图,默认是 disable的。
- glTexParameterx(GL_TEXTURE_2D, param, value):当面生成的光栅与纹理像素点不能一一对应时如何处理。
- GL_TEXTURE_WRAP_T/S:在纵横方向上大小不一致时,如何调整纹理坐标(uv)。
- 默认的 GL_REPEAT 为重复使用纹理,
- GL_CLAMP 为将 uv 坐标收缩到[0,1.0]之内,
- GL_CLAMP_TO_EDGE 为将 uv 坐标收缩到 [1/(2*size),1-1/(2*size)] 之间。
- 只有 GL_CLAMP 时边界上的纹理才可以访问。
- GL_TEXTURE_MIN_FILTER:纹理缩小算法。当面所生成的像素在纹理化过程 中,映射到纹理上的大于一个像素的区域时,该参数选择如何合并多个纹理像素 产生最终应用到面上的颜色。GL_NEAREST 表示用离得最近的那个像素, GL_LINEAR 表示周围最近四个纹理像素加权平均,GL_*A*_MIPMAP_*B*表示根据*B*选择适当的MIPMAP级,然后再用*A*选择最近的或计算。
- GL_TEXTURE_MAG_FILTER:纹理放大算法。与MIN相反的情况。只有 GL_NEAREST/GL_LINEAR两个选择。
- GL_TEXTURE_WRAP_T/S:在纵横方向上大小不一致时,如何调整纹理坐标(uv)。
- glTexEnvx:这个比较复杂,见手册
最常见的做法是这样的:
glEnable(GL_TEXTURE_2D); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
现在万事俱备,就差画画了。由于 OpenGL ES 只支持 vetex array 方式发送模 型数据,这一步只有一件事要做,给模型加上 uv 坐标:
glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glTexCoordPointer( components, GL_FIXED, stride, uvPointer );
注意:纹理坐标(uv)与顶点坐标系的习惯略有不同,其原点在图片的左下角,左 至右为正u方向,底至顶为正v方向。
完整形式的纹理坐标可以表示为(s,t,r,q),其中(s,t)对应一般三维建模软件中的uv也就是平面纹理图片的(x,y);r在使用三维纹理时使用,OpenGL ES目前明确不支持三维纹理,应设为0;q为齐次坐标,通常为1,在坐标变换计算过程中可能使用非1的值。
以下是一个完整的例子:
1 void testTexture() 2 { 3 GLfixed rect[] = { Float2Fixed(-1.0f), Float2Fixed(-1.0),0, 4 Float2Fixed(1.0f), Float2Fixed(-1.0), 0, 5 Float2Fixed(1.0f), Float2Fixed(1.0), 0, 6 7 Float2Fixed(-1.0f), Float2Fixed(-1.0),0, 8 Float2Fixed(1.0f), Float2Fixed(1.0), 0, 9 Float2Fixed(-1.0f), Float2Fixed(1.0f) , 0 10 }; 11 GLfixed rnormal[] = { 12 0, Float2Fixed(1.0f), 0, 13 0, Float2Fixed(1.0f), 0, 14 0, Float2Fixed(1.0f), 0, 15 16 0, Float2Fixed(1.0f), 0, 17 0, Float2Fixed(1.0f), 0, 18 0, Float2Fixed(1.0f), 0 }; 19 GLfixed rectuv[] = { Float2Fixed(0.0f), Float2Fixed(0.0), 20 Float2Fixed(1.0f), Float2Fixed(0.0), 21 Float2Fixed(1.0f), Float2Fixed(1.0), 22 23 Float2Fixed(0.0f), Float2Fixed(0.0), 24 Float2Fixed(1.0f), Float2Fixed(1.0), 25 Float2Fixed(0.0f), Float2Fixed(1.0f) }; 26 27 unsigned short color[4]={ 0xF800, 0x7E0, 0x1F, 0xffff }; 28 29 for ( int i = 0; i < 128; ++i ) 30 { 31 for ( int j = 0; j < 128; ++j ) 32 { 33 embTex[i*128+j] = color[(i/32 + j/32)%4]; 34 } 35 } 36 37 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 38 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 39 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 40 glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 41 42 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, 128, 128 , 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, embTex); 43 44 glEnableClientState( GL_VERTEX_ARRAY ); 45 glEnableClientState( GL_NORMAL_ARRAY ); 46 glEnableClientState( GL_TEXTURE_COORD_ARRAY ); 47 glVertexPointer( 3, GL_FIXED, 0, rect ); 48 glNormalPointer( GL_FIXED, 0, rnormal); 49 glTexCoordPointer( 2, GL_FIXED, 0, rectuv ); 50 51 glDrawArrays( GL_TRIANGLES, 0, 6); 52 } 53
运行效果如下图所示:
从上图中不难看出,左上第一格是白色而不是纹理图片左上第一格的红色,如果 将纹理坐标中v的0.0,1.0互换一下,就完全一样了。这是因为OpenGL ES规定二 维纹理图片数据是从下到上逐行存放的,而示例程序中生成的数据是自上而下逐 行存放的。
这里介绍的只是表面材质纹理效果的基础知识,目前材质与贴图是实时应用中获 得高真实感效果的主要手段,这部份还有大量更深入的技术和运用方式值得研究, 例如多重纹理、多遍纹理、材质效果合并、BUMP-MAPPING、环境贴图等等。