您的位置:首页 > 运维架构

使用opengl es编写2d游戏的一些说明和技巧

2012-07-01 13:05 501 查看
即使是制作2d游戏,在移动设备上也往往使用opengl es绘图接口,来达到较高的绘制效率。这里记录一下我学习opengl es 2d绘图过程中逐渐明白的一些原理与技巧。

opengl 3d贴图的基本流程是:

a.使用3d坐标指定一些描述物体空间位置的顶点(例如一个立方体,或者平面上的一个三角形,指定它们的顶点)

b.给每一个位置坐标指定一个纹理坐标,表示纹理图上的哪个点对应这个位置坐标

c.位置坐标和纹理坐标都提交后,opengl根据这些信息,使用插值、缩放等方式,将纹理图贴到物体上

如果是2d游戏贴图,上述流程不变,只是3d位置坐标变为2d位置坐标,并且2d位置坐标描述的平面图像往往是两个三角形拼成的正方形。这个正方形的四个顶点,再对应到一张矩形的2d游戏素材图片的四个角上。

基本的流程清楚了,剩下的就是一些细节,以及一些2d图形操作技巧在opengl上的对应实现方法。目前我想到的有以下几点

1、坐标系转换 2、贴图问题 3、颜色表 4、缓冲区操作

1、坐标系转换

这个坐标系转换指的是如何从opengl坐标系转换到2d游戏常用的左上角为原点的坐标系
opengl从提交位置顶点到在窗口上渲染出像素,使用的坐标系有:物体坐标、视点坐标、裁剪坐标、正规化设备坐标、窗口坐标

a.提交顶点时,使用的是物体坐标。
b.使用glTranslate\glRotate等函数,可以对物体进行平移和旋转,从物体坐标转换为视点坐标。(2d游戏不涉及旋转的部分,常常直接给物体设置世界坐标,可以说跳过了这一步)。
c.使用glOrtho\glFrustum等投影变换函数时,从坐标系变换上来说,先根据这两个函数规定的剪切区域及透视方法,进行了剪切透视变换,将3d世界变换到一个矩形管道观察体内。然后再进行正规化,使坐标在3个轴内的范围是(-1,1)
d.使用glViewPort函数进行视口变换。这步变换中,上一步的正规化坐标(Xn、Yn、Zn),其中Xn、Yn根据glViewPort的参数,可以得到一个相应的屏幕坐标(Xw、Yw)(这个屏幕坐标,opengl规定原点在左下角,向上向右为正方向),Zn坐标则用来进行深度检测相关操作。

详细来看一下glViewPort函数

void glViewport(
GLint x,
GLint y,
GLsizei width,
GLsizei height
)
;
这个函数的数学表达式为

Xw = (Xn + 1) * width/2 + x
Yw = (Yn + 1) * height 2 + y
从这个表达上可以看出,如果x、y取为0,width、height取为窗口宽高,规范化设备坐标(-1,-1)会被转换到(0,0)点,即窗口左下角。规范化坐标(1,1)会被转换到(width,height),即窗口右上角(注意原点为左下角,y向上为正)。换句话说,这样的设置,就是规范化矩形被平行的映射到窗口上去了。

这里还有一个细节,就是规范化坐标和我们提交顶点时使用的坐标是什么关系。如何保证我们惯用的2d绘图坐标(原点在左上角),在使用opengl绘图接口时保持正常?

那就需要详细看一下投影变换函数glOrtho
void glOrtho(
GLdouble left, GLdouble
right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal
)
;

我们只要把这个函数的参数bottom设为窗口高度height,参数top设为0,即可正常使用惯用的2d坐标系了(原点在左上角)。从bottom和top的字面意义上来讲,要想使绘图坐标的y轴向下为正,确实应该这样设置(反之,设置bottom=0,top=height,就是opengl窗口坐标那样的y轴向上坐标系了)。从原理上讲,这个函数只是设置了一个坐标变换矩形,这个变换矩形的各个系数是根据我们调用glOrtho函数时设置的参数算出的。感兴趣的同学可以去看看opengl关于这个函数的官方文档,自己计算一下。
http://www.opengl.org/sdk/docs/man/xhtml/glOrtho.xml

关于坐标变换,opengl官网网站的一个faq网页解释的比较清楚
http://www.opengl.org/archives/resources/faq/technical/transformations.htm

2、纹理贴图细节

a.图像出现白边或者图像移动时产生闪烁

如果使用缩放操作的话,一定要注意纹理环绕的设置。因为缩放操作导致屏幕上的像素点和纹理图片不是一一对应,就需要opengl来进行过滤采样。在纹理过滤不是临近点过滤时,对纹理边缘的过滤就可能包含纹理图像外的点。纹理环绕决定了图像外部点如何取值,像GL_CLAMP就会采用一种可设置的固定边界颜色。GL_CLAMP_TO_EDGE则忽略图像外部点。如果采用CL_CLAMP,在拼接纹理时,容易产生黑边。详细的解释可以见这篇文章http://www.linuxgraphics.cn/opengl/texture_fix_seam.html
但因为2d游戏纹理坐标经常和像素一一对应,如果不进行放大和缩放的话,就不会出现这种现象。
另外一种贴图时产生缝隙的原因,是opengl习惯使用宽高是2的幂的纹理图片,如果引擎对不符合要求的图片进行了自动补齐的话,补足的像素如果没有正确初始化,也会在放大缩小的边界线性过滤时产生缝隙,而且这时是与纹理环绕模式无关的。

我还碰到过图像闪烁的问题。那是在移动一副图片并对图片进行缩放时发生的。我猜测原因是图片位置发生变化后,因为位置变化量和缩放倍数不一致(例如放大两倍,但是一个像素一个像素的移动图片),缩放过程中采样点也相应变化。图像上某些像素点一会采样到纹理图上的亮点,一会采样到暗点,导致闪烁。

b.纹理压缩

同样规模的2d游戏,使用的图片量要远大于3d游戏(想象一个滚动的球体,2d的话需要球体滚动过程中每一帧的图片,3d的话给一张球体整体纹理图就好了)。一个效果出众的移动设备游戏,内存往往容易拙荆见肘。这时可以采用低色深图片,例如从RGBA8888转为RGBA4444,图片内存使用量轻松减了一半,效果上其实看不太出来区别。
如果想进一步榨取内存,使用opengl的硬件纹理压缩是个好方法。这样不仅可以减低内存使用,还可以加快渲染速度。如果是opengl完全版,只要提交纹理时,glTexImage2D的第三个参数internalformat使用GL_COMPRESSED_RGBA即可,opengl会负责在载入时压缩纹理。这样可以立刻看到压缩的效果(在我的一个已使用RGBA4444图片的项目中,100m的内存消耗,使用纹理压缩后变为40m)。可惜的是,opengl
es不支持载入时压缩。关于预压缩为PVR等纹理格式,Apple网站上有详细介绍。

3.颜色表

2d游戏中经常使用索引色图片,运行时动态改变调色版,来达到一些色彩变幻的效果。在opengl里,这些效果应该如何实现?

a.glColor*方法
glColor实际设置的是顶点的颜色,而顶点颜色决定几何图形颜色。一般采取的opengl纹理设置,是纹理颜色会与几何图形颜色相调制(即相乘)。用这种方法,可以达到动态减弱纹理中某个颜色通道(R\G\B\A)的效果。

b.颜色映射
颜色映射,可以将某个颜色映射为另外一种颜色。用于颜色缓冲区和外部图像数据转移时使用,即在glDrawPixels/glReadPixels/glTexImage等API调用时发生。可以采用表格的方式,也可以采用color*scale+bias的计算公式。但是这种方式在进行映射时似乎只能是通道独立的,,并不能将R\G\B结合起来映射

c.颜色矩阵和颜色表
属于图像子集(opengl es不支持)。用于图像处理管线的各个过程中。颜色矩阵(glMatrixMode(GL_COLOR))即是RGBA色彩空间的矩阵运算,颜色表(glColorTable)类似上面的颜色映射

d.索引色纹理图
Paletted textures,属于一个扩展,这个应该就是指让opengl在非索引色模式下支持索引色纹理图。新硬件一般已经不支持。可以通过shaders达到同样的功能。

4.缓冲区操作

未完待续
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: