OpenGL与GLES不同点
OpenGL和GLES最大的不同点,我觉得就是对于纹理坐标的设置上。对于OpenGL,其纹理坐标原点在纹理图片的左下角,和OpenCV的图片坐标在y轴上是相反的.OpenGLES则不遵守这一规定,GLES的纹理坐标原点在左上角,和OpenCV一致。
GLES几个关键的概念
除iOS、OSX以外,其他平台初始化OpenGLES的代码段
1 | EGLint configSpec[] = { EGL_RED_SIZE, 8, |
整个流程是:获得DISPLAY, 在DISPLAY基础上创建Surface,将DISPLAY、Surface等一系列上下文写入创建Context中去。
获取Display。
Display代表显示器,在有些系统上可以有多个显示器,也就会有多个Display。获得Display要调用EGLboolean eglGetDisplay(NativeDisplay dpy),参数一般为 EGL_DEFAULT_DISPLAY 。该参数实际的意义是平台实现相关的,在X-Window下是XDisplay ID,在MS Windows下是Window DC。初始化egl。
调用 EGLboolean eglInitialize(EGLDisplay dpy, EGLint major, EGLint minor),该函数会进行一些内部初始化工作,并传回EGL版本号(major.minor)。选择Config。
所谓Config实际指的是FrameBuffer的参数,在MS Windows下对应于PixelFormat,在X-Window下对应Visual。一般用EGLboolean eglChooseConfig(EGLDisplay dpy, const EGLint attr_list, EGLConfig config, EGLint config_size, EGLint num_config),其中attr_list是以EGL_NONE结束的参数数组,通常以id,value依次存放,对于个别标识性的属性可以只有 id,没有value。另一个办法是用EGLboolean eglGetConfigs(EGLDisplay dpy, EGLConfig config, EGLint config_size, EGLint *num_config) 来获得所有config。这两个函数都会返回不多于config_size个Config,结果保存在config[]中,系统的总Config个数保存 在num_config中。可以利用eglGetConfig()中间两个参数为0来查询系统支持的Config总个数。
Config有众多的Attribute,这些Attribute决定FrameBuffer的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改。构造Surface。
Surface实际上就是一个FrameBuffer,通过 EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig confg, NativeWindow win, EGLint *cfg_attr) 来创建一个可实际显示的Surface。系统通常还支持另外两种Surface:PixmapSurface和PBufferSurface,这两种都不 是可显示的Surface,PixmapSurface是保存在系统内存中的位图,PBuffer则是保存在显存中的帧。
Surface也有一些attribute,基本上都可以故名思意, EGL_HEIGHT EGL_WIDTH EGL_LARGEST_PBUFFER EGL_TEXTURE_FORMAT EGL_TEXTURE_TARGET EGL_MIPMAP_TEXTURE EGL_MIPMAP_LEVEL,通过eglSurfaceAttrib()设置、eglQuerySurface()读取。创建Context。
OpenGL的pipeline从程序的角度看就是一个状态机,有当前的颜色、纹理坐标、变换矩阵、绚染模式等一大堆状态,这些状态作用于程序提交的顶点 坐标等图元从而形成帧缓冲内的像素。在OpenGL的编程接口中,Context就代表这个状态机,程序的主要工作就是向Context提供图元、设置状 态,偶尔也从Context里获取一些信息。
用EGLContext eglCreateContext(EGLDisplay dpy, EGLSurface write, EGLSurface read, EGLContext * share_list)来创建一个Context。用eglMakeCurrent来将当前context设置为默认context绘制。
应用程序通过OpenGL API进行绘制,一帧完成之后,调用eglSwapBuffers(EGLDisplay dpy, EGLContext ctx)来显示。
DISPLAY、Surface、Context、Configuration
- DISPLAY可以理解为物理屏幕,对于PC就是显示器,对于手机,就是屏幕,获得这个句柄主要是用于正确放置surface,surface建立于DISPLAY以上。
- Surface可以理解为画板,可以为物理屏幕上的像素,也可以是离屏渲染的FrameBuffer
- Context就是设置好这些以后,所处的一个配置环境。
相应的,EGL中,
NativeDisplayType 平台显示数据类型,标识你所开发设备的物理屏幕
NativeWindowType 平台窗口数据类型,标识系统窗口
NativePixmapType 可以作为 Framebuffer 的系统图像(内存)数据类型,该类型只用于离屏渲染
获得DISPLAY
this->glContext->eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
如果你只是想得到一个系统默认的 Display ,你可以使用 EGL_DEFAULT_DISPLAY 参数。如果系统中没有一个可用的 native display ID 与给定的 display 参数匹配,函数将返回 EGL_NO_DISPLAY ,而没有任何 Error 状态被设置。由于设置无效的 display 值不会有任何错误状态,在你继续操作前请检测返回值。因此,上述函数还应该检查返回的eglDisp是不是 EGL_NO_DISPLAYassert(this->glContext->eglDisp != EGL_NO_DISPLAY)
查询到系统的Display之后,需要初始化这个eglDisp, 并且获得这个DISPLAY所支持GL版本。
Surface
Surface是可以绘制的画板,通常有屏幕像素、PixmapSurface(保存在系统内存中的位图),PBuffer(保存在显存中的帧)
iOS上的EGL配置
iOS和Android不一样,虽然iOS也支持EGL,但是是自己的一个版本。另外不能用纯C++来简历iOS上面的EGL上下文,必须采用部分object头来初始化。另外一点就是,EAGL不允许设置宽高等,直接获得context就好了。实际上,这个context还没有设置surface,后续才会去做这个设置。
另外,后台运行的任务,也就是没有占用屏幕的应用是无法执行OpenGL的渲染指令的。
一个iOS离屏渲染的例子
1 | EAGLNativeContext eaglContext; |
我们可以看到在readPixel之前tmp_data全是0,而在read_pixel之后就会变成0, 0, 0xff, 0,注意一点是,texture大小在iOS是有限制的,具体可以看context。注意这段要放在view加载之后,否则可能无法获得context。
注意,其中深度buffer有没有并不影响平面的画,但是会影响三维的draw。
总结
至此,离屏渲染实际上已经完成了跨平台,只需在android, windows, linux使用EGL,在iOS使用EAGL即可,只要对编译选项按平台稍加改造,就能够实现跨平台的opengles引擎的渲染逻辑的初始化。