材质纹理是增加物体表面细节的有效手段。前面我们已经可以加载任意复杂的三 维模型了,但是白乎乎的团,看着一点也不酷,现在是时候让它变漂亮一些了。
第一步我们给它上点颜色。首先我们需要对光照模型有点概念,物体看上去有颜 色,是它被光线照射的结果,如果光是白色的,那么呈现的就是物体本身的颜色, 否则会是光色和表面本色综合的结果。物体表面被点光源照射后,会呈现三个区 域:高光区、过渡色区和环境色区。高光区是镜面反射的结果,亮度特别高,光 源越强越明显;过渡色区是漫反射的结果,通常面积较大;环境色区则是不受光 照部份的颜色,通常较暗。
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 中给一个面指定纹理有四步:
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);
参数的意义是相当直接的:
这里有几点值得关注一下:
glGenTextures(count, &id); glBindTexture(GL_TEXTURE_2D, id);
为其指定一个ID,需要时
glActiveTexture(id);
就可再次将其设为“当前”纹理。这样可以省掉重复上传纹理纹理数据的开销。
控制贴图过程的参数主要有:
最常见的做法是这样的:
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、环境贴图等等。