OpenGL ES系列 之 提高-2:材质纹理
2009-05-04 14:02
197 查看
材质纹理是增加物体表面细节的有效手段。前面我们已经可以加载任意复杂的三维模型了,但是白乎乎的团,看着一点也不酷,现在是时候让它变漂亮一些了。
第一步我们给它上点颜色。首先我们需要对光照模型有点概念,物体看上去有颜色,是它被光线照射的结果,如果光是白色的,那么呈现的就是物体本身的颜色,否则会是光色和表面本色综合的结果。物体表面被点光源照射后,会呈现三个区域:高光区、过渡色区和环境色区。高光区是镜面反射的结果,亮度特别高,光源越强越明显;过渡色区是漫反射的结果,通常面积较大;环境色区则是不受光照部份的颜色,通常较暗。
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 );
以启用光照计算,绘制结果将会是:
如果你看过Hello EGL例子,你会发现在不启用光照计算时,也可以直接用COLOR_ARRAY来给表面上色,但这并不是一般意义上的材质。
给模型表面上色之后,看上去漂亮多了,不过这样还是不足以表达复杂的表面细节,如果物体表面不是大面积的色块而是复杂的花纹,那就需要通过纹理贴图来表现了。实际上,纹理贴图是游戏类图形程序最重要的工作,针对娱乐市场开发的显示卡其优化重点也是增加显卡总线带宽以便更快速度的将纹图贴图从系统内存传递到显卡处理流水线中以及并行根据纹理计算每个像素点最终颜色。而所谓专业图形卡则更重视于线条等基本图元姆⑸砟芰Α?
在OpenGL ES中给一个面指定纹理有四步:在内存里准备好纹理数据,调用glTexImage2D将纹理数据上传给显卡并设置当前纹理,设置贴图参数,在绘制面时提供纹理坐标(uv)。
OpenGL ES限制纹理图片的长和宽都必须是2的次方,长和宽可以不一样。以下这段代码在生成一个128x128的图片并保存到数组embText里:
在实际应用中,纹理图片通常需要从特写格式的文件中读取,像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, idArray)/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两个选择。
glTexEnvx:这个比较复杂,见手册。
最常见的做法是这样的:
现在万事俱备,就差画画了。由于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的值。
以下是一个完整的例子:
运行效果如下图所示:
从上图中不难看出,左上第一格是白色而不是纹理图片左上第一格的红色,如果将纹理坐标中v的0.0,1.0互换一下,就完全一样了。这是因为OpenGL ES规定二维纹理图片数据是从下到上逐行存放的,而示例程序中生成的数据是自上而下逐行存放的。
这里介绍的只是表面材质纹理效果的基础知识,目前材质与贴图是实时应用中获得高真实感效果的主要手段,这部份还有大量更深入的技术和运用方式值得研究,例如多重纹理、多遍纹理、材质效果合并、BUMP-MAPPING、环境贴图等等。
第一步我们给它上点颜色。首先我们需要对光照模型有点概念,物体看上去有颜色,是它被光线照射的结果,如果光是白色的,那么呈现的就是物体本身的颜色,否则会是光色和表面本色综合的结果。物体表面被点光源照射后,会呈现三个区域:高光区、过渡色区和环境色区。高光区是镜面反射的结果,亮度特别高,光源越强越明显;过渡色区是漫反射的结果,通常面积较大;环境色区则是不受光照部份的颜色,通常较暗。
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 );
以启用光照计算,绘制结果将会是:
如果你看过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, idArray)/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两个选择。
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、环境贴图等等。
相关文章推荐
- OpenGL ES系列之提高-2:材质纹理
- OpenGL ES系列 之 提高-1:加载模型
- OpenGL ES教程系列_LessonY_使用2D纹理渲染文字
- 基于Cocos2d-x学习OpenGL ES 2.0系列——纹理贴图(6)
- OpenGL ES系列 之 提高-3:光照
- OpenGL ES系列 之 深入 - 1:压缩纹理
- 罗大柚OpenGL ES教程系列_LessonY_使用2D纹理渲染文字
- 基于Cocos2d-x学习OpenGL ES 2.0系列——纹理贴图(6)
- [OPENGL]纹理,材质,光照
- OpenGL ES 纹理参数设置方法 glTexParameter
- Android OpenGL ES 应用(二) 纹理
- Ogre透明材质和纹理阴影
- 【技术美术】贴图、纹理、材质的区别
- 从零开始学习OpenGL ES之五 – 材质
- Android应用开发提高系列(2)——《Practical Java 中文版》读书笔记(下)
- Android OpenGL es 纹理坐标设定与贴图规则
- OpenGL ES 从零开始系列9a:动画基础和关键帧动画
- OpenGL ES纹理详解
- EXTJS学习系列提高篇:第十二篇(转载)作者殷良胜,利用Ext实现类似Windows的操作
- EXTJS学习系列提高篇:第二十六篇(转载)作者殷良胜,ext2.2打造Ext.form.ComboBox系列--静态绑定