摘 要:
本篇文档从零开始,深入简出,跟大家介绍一下OpenGL ES的原理和开发。
前言
OpenGL ES是Khronos Group创建的一系列API中的一种(官方组织是:http://www.khronos.org/)。在桌面计算机上有两套标准的 3DAPI:Direct3D和OpenGL。Direct3D实际上是运行在windows操作系统上的标准3DAPI,而OpenGL则是跨平台的, 适用于Linux、多种UNIX、MAC OS X和windows。由于OpenGL得到了广范围的认可,所以,基于嵌入式的3DAPI---OpenGL ES也就应运而生。
沃Phone使用的芯片高通7227,它能很好的提供对OpenGL ES的支持,了解OpenGL ES的种种特性,不仅能开发出很好的适用于沃Phone的3D游戏、3D应用等。借助于OpenGL ES的平台无关性,只要稍微修改EGL,理论上就可以将开发的3D游戏、3D应用移植到任何支持OpenGL ES的平台上去。
本篇文档就从零开始,深入简出,跟大家介绍一下OpenGL ES的原理和开发。
沃Phone使用的芯片高通7227,它能很好的提供对OpenGL ES的支持,了解OpenGL ES的种种特性,不仅能开发出很好的适用于沃Phone的3D游戏、3D应用等。借助于OpenGL ES的平台无关性,只要稍微修改EGL,理论上就可以将开发的3D游戏、3D应用移植到任何支持OpenGL ES的平台上去。
本篇文档就从零开始,深入简出,跟大家介绍一下OpenGL ES的原理和开发。
OpenGL ES简介
什么是OpenGL ES
OpenGL ES是一套适用于手持嵌入式设备的3DAPI。比如手机、PDA、汽车、航空等等上面都可以使用到OpenGL ES。OpenGL ES是免授权费的、跨平台的、功能完善的2D和3D图形应用程序接口API,它是桌面OpenGL的子集,是从OpenGL裁剪定制而来的。由于手持设备 的相关局限性,OpenGL ES相对于OpenGL不可避免的进行了相关的精简。去除了OpenGL中比如glBegin/glEnd,四边形(GL_QUADS)、多边形 (GL_POLYGONS)等复杂图元等许多非绝对必要的特性。但是OpenGL方面的很多知识,OpenGL ES都是可以借鉴的。
OpenGL ES其实是一个状态机(State machine),它保存一种状态直至其改变。每个状态都有本身默认的缺省值,可以通过相关的查询和设置函数进行相关的查询和设置。
大多数的OpenGL ES的应用都采用的是相同的操作顺序,这一系列的处理阶段被称作OpenGL ES的渲染管线(pipeline)。
OpenGL ES其实是一个状态机(State machine),它保存一种状态直至其改变。每个状态都有本身默认的缺省值,可以通过相关的查询和设置函数进行相关的查询和设置。
大多数的OpenGL ES的应用都采用的是相同的操作顺序,这一系列的处理阶段被称作OpenGL ES的渲染管线(pipeline)。
OpenGL ES 2.0
看一下OpenGL ES的发展历程,如下图:
从图中我们可见,现在常用的OpenGL ES有两个版本,OpenGL ES1.1和OpenGL ES2.0。其中OpenGL ES1.1是固定渲染管线的,而OpenGL ES2.0是可编程渲染管线的。1.1版本现在多用于低成本的设备中,而2.0的版本的设备通常是兼容1.1的。这里说的渲染,就是根据模型创建图像的一 个过程。而模型是根据几何图元创建的,图元包括点、直线、多边形,他们是通过顶点(Vertex)定义的。
OpenGL ES的处理流程
1. 固定渲染管线流程:
上图为固定渲染管线流程图,这个渲染管线是OpenGL ES1.1使用的。所处理的流程如下
1) Primitive Processing:
这一步是图元运算过程,所谓图元,其实就是一个点集。在OpenGL ES中,所有的物体,几何元素最终都是以顶点的形式表述的。一般来说,这些顶点将会产生三角形、直线或点。它做的工作就是将顶点提供给顶点处理器进行处 理。顶点的数据包括顶点的位置(空间坐标)、大小、颜色、顶点的法向量(用于光照计算)、纹理坐标(可能有多个)等等。
2) Transform and Lighting:
这一步是转换和光照过程。其中Transform是通过模型、视图、投影变换矩阵,将所有的顶点坐标变换成人眼坐标系下的一致坐标。变换矩阵同样会改变物 体的顶点法向量。如果激活了纹理,还可以进行纹理坐标转换,以及自动纹理坐标的生产。Lighting处理的就是光照部分,它会利用光源、材质、转换后的 顶点位置和法向量计算每个顶点的颜色值。
3) Primitive Assembly:
图元装配过程。管线中这个流程是对所有的点数据进行点线面等基础图元的组装。这个过程会对所有的图元进行剪切和筛选。对于不在视区空间中的部分进行剪切,对于不可见的面进行筛选。
4) Resterizar:
光栅化。光栅化的过程就是对所有的经过Primitive Assembly图元转换成屏幕上可以显示的二维Fragment(片元)。片元和将要显示的像素一一对应。
5) Texture Environment:
纹理处理。利用纹理坐标来进行纹理的相关处理。
6) Colour Sum:
颜色叠加。根据纹理颜色等相关属性确定最终的顶点颜色。
7) Fog:
雾。雾化处理。
8) Alpha Test:
Alpha测试。判断某些片元是否抛弃。比如可以规定Alpha小于0.2的片元就需要抛弃。
9) Depth Stencil:
深度测试和模板测试。深度测试需要一个深度缓冲区,是在后面的会被在前的遮盖,需要抛弃。模板测试需要一个模板缓冲区,也就是模板缓冲区中为每个像素保存 了一个“模板值”,当像素需要进行模板测试时,将设定的模板参考值与该像素的“模板值”进行比较,符合条件的通过测试,不符合条件的则被丢弃,不进行绘 制。条件的设置与Alpha测试中的条件设置相似。但注意Alpha测试中是用浮点数来进行比较,而模板测试则是用整数来进行比较。
10) Color Buffer blend:
跟颜色缓冲区进行混合。最终生成的片元颜色需要跟颜色缓冲区中本来的进行混合(也可以理解成为跟背景混合),以生成最终的颜色。
11) Dither:
抖动。在可用颜色数量较少的系统中,可能需要对颜色值进行抖动,在适当损失颜色质量的情况下增加可使用的颜色数量。
12) Frame Buffer:
最终结果就写进了Frame Buffer。一个流程就算结束了。
2. 可编程渲染管线流程:
对比固定渲染管线流程图和可编程渲染管线流程图,可以看出来,大部分的流程都是一样的,只是可编程将固定中的几个功能合并了,新增了两个新的流程,Vertex Shader和Fragment Shader。也就是着色器(shader)
作为OpenGL ES2.0的一部分,着色器允许应用程序显式地指定处理顶点和片断时候所执行的操作。
1) Shader language简介:
编写OpenGL ES程序使用的着色器类似于使用基于编译器的语言(比如C语言)编写程序。我们需要用编译器来分析程序,检查它所存在的错误,并把它转换成为目标代码。接着,在链接阶段,链接器把一组目标文件组合在一起,形成一个可执行的程序。着色器的创建流程如下:
对于每个着色器对象:
1. 创建一个着色器对象。
2. 把着色器源代码编译为目标代码。
3. 验证这个着色器已经成功通过编译。
然后,为了把多个着色器对象链接到一个着色器程序中,需要:
1. 创建一个着色器程序。
2. 把适当的着色器对象连接到这个着色器程序中。
3. 链接着色器程序。
4. 验证着色器程序链接成功。
5. 使用着色器进行顶点或片段处理。
1) Vertex Shader:
包含了固定渲染管线中的Transform and lighting的所有操作。
2) Fragment Shader:
包含了固定渲染管线中的纹理处理、颜色叠加、雾、alpha测试等内容。
具体着色器语言和两个着色器的使用,可见后续的sample分析。
1) Primitive Processing:
这一步是图元运算过程,所谓图元,其实就是一个点集。在OpenGL ES中,所有的物体,几何元素最终都是以顶点的形式表述的。一般来说,这些顶点将会产生三角形、直线或点。它做的工作就是将顶点提供给顶点处理器进行处 理。顶点的数据包括顶点的位置(空间坐标)、大小、颜色、顶点的法向量(用于光照计算)、纹理坐标(可能有多个)等等。
2) Transform and Lighting:
这一步是转换和光照过程。其中Transform是通过模型、视图、投影变换矩阵,将所有的顶点坐标变换成人眼坐标系下的一致坐标。变换矩阵同样会改变物 体的顶点法向量。如果激活了纹理,还可以进行纹理坐标转换,以及自动纹理坐标的生产。Lighting处理的就是光照部分,它会利用光源、材质、转换后的 顶点位置和法向量计算每个顶点的颜色值。
3) Primitive Assembly:
图元装配过程。管线中这个流程是对所有的点数据进行点线面等基础图元的组装。这个过程会对所有的图元进行剪切和筛选。对于不在视区空间中的部分进行剪切,对于不可见的面进行筛选。
4) Resterizar:
光栅化。光栅化的过程就是对所有的经过Primitive Assembly图元转换成屏幕上可以显示的二维Fragment(片元)。片元和将要显示的像素一一对应。
5) Texture Environment:
纹理处理。利用纹理坐标来进行纹理的相关处理。
6) Colour Sum:
颜色叠加。根据纹理颜色等相关属性确定最终的顶点颜色。
7) Fog:
雾。雾化处理。
8) Alpha Test:
Alpha测试。判断某些片元是否抛弃。比如可以规定Alpha小于0.2的片元就需要抛弃。
9) Depth Stencil:
深度测试和模板测试。深度测试需要一个深度缓冲区,是在后面的会被在前的遮盖,需要抛弃。模板测试需要一个模板缓冲区,也就是模板缓冲区中为每个像素保存 了一个“模板值”,当像素需要进行模板测试时,将设定的模板参考值与该像素的“模板值”进行比较,符合条件的通过测试,不符合条件的则被丢弃,不进行绘 制。条件的设置与Alpha测试中的条件设置相似。但注意Alpha测试中是用浮点数来进行比较,而模板测试则是用整数来进行比较。
10) Color Buffer blend:
跟颜色缓冲区进行混合。最终生成的片元颜色需要跟颜色缓冲区中本来的进行混合(也可以理解成为跟背景混合),以生成最终的颜色。
11) Dither:
抖动。在可用颜色数量较少的系统中,可能需要对颜色值进行抖动,在适当损失颜色质量的情况下增加可使用的颜色数量。
12) Frame Buffer:
最终结果就写进了Frame Buffer。一个流程就算结束了。
2. 可编程渲染管线流程:
对比固定渲染管线流程图和可编程渲染管线流程图,可以看出来,大部分的流程都是一样的,只是可编程将固定中的几个功能合并了,新增了两个新的流程,Vertex Shader和Fragment Shader。也就是着色器(shader)
作为OpenGL ES2.0的一部分,着色器允许应用程序显式地指定处理顶点和片断时候所执行的操作。
1) Shader language简介:
编写OpenGL ES程序使用的着色器类似于使用基于编译器的语言(比如C语言)编写程序。我们需要用编译器来分析程序,检查它所存在的错误,并把它转换成为目标代码。接着,在链接阶段,链接器把一组目标文件组合在一起,形成一个可执行的程序。着色器的创建流程如下:
对于每个着色器对象:
1. 创建一个着色器对象。
2. 把着色器源代码编译为目标代码。
3. 验证这个着色器已经成功通过编译。
然后,为了把多个着色器对象链接到一个着色器程序中,需要:
1. 创建一个着色器程序。
2. 把适当的着色器对象连接到这个着色器程序中。
3. 链接着色器程序。
4. 验证着色器程序链接成功。
5. 使用着色器进行顶点或片段处理。
1) Vertex Shader:
包含了固定渲染管线中的Transform and lighting的所有操作。
2) Fragment Shader:
包含了固定渲染管线中的纹理处理、颜色叠加、雾、alpha测试等内容。
具体着色器语言和两个着色器的使用,可见后续的sample分析。
EGL简介
OpenGL实现跨平台的功能,在不同的操作系统上需要不同的类似适配层的内容,比如在Windows操作系统上需要WGL。同样的,OpenGL ES是一个平台中立的图形库,在它能够工作前,需要与一个实际的窗口关联起来,但是,与OpenGL不一样的是,OpenGL是每个窗口系统需要一个与之 对应的适配层,Windows需要WGL,X-Window需要xgl,Mac OS需要agl。而OpenGL ES的这层,是统一的一个标准。这个标准就是EGL。
(一) 初识EGL
EGL是介于RenderAPI(比如OpenGL ES和OpenVG)和本地基础系统的一套接口。里面涉及了OpenGL ES和OpenVG的一些相关描述,所以需要和OpenGL ES和OpenVG文档一起阅读。EGL使用OpenGLES的命名习惯来命名函数入口和宏定义。具体的接口和相关宏定义可以参见egl.h。
(二) EGL的使用
1. 获取Display:
Display代表的是显示器,有的系统上有多个显示器,也就会有多个display。获得Display需要调用EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id);,参数一般为EGL_DEFAULT_DISPLAY。该参数的实际意义是平台相关的,比如在windows平台上,一般返回的 就是DC。沃Phone上就是TDC。
2. 初始化egl:
获得了Display后,调用EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor);该函数会进行一些相关的内部初始化工作。我们可以通过这个函数获得egl的版本号。
3. 选择Config:
Config实际就是FrameBuffer的参数,在Windows下对应于PixelFormat,在X-Window下对应Visual。可以用函 数EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);,其中attrib_list 是以EGL_NONE结束的参数数组,通常以id,value依次存放,对于个别标识性的属性可以只有id,没有value。另一个办法是用 EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); 来获得所有config。这两个函数都会返回不多于config_size个Config,结果保存在configs[]中,系统的总Config个数保 存在num_config中。可以利用eglGetConfig()中间两个参数为0来查询系统支持的Config总个数。Config有众多的 Attribute,这些Attribute决定FrameBuffer的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改。
4. 构造Surface:
有了Config,就可以开始构造Surface了。Surface实际上就是一个FrameBuffer。通过函数EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config,EGLNativeWindowType win, const EGLint *attrib_list)可以创建一个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()读取。
5. 创建Context:
OpenGL ES的pipeline从程序的角度看就是一个状态机,有当前的颜色、纹理坐标、变换矩阵、渲染模式等一大堆状态,这些状态作用于程序提交的顶点坐标等图 元从而形成帧缓冲内的像素。在OpenGL ES的编程接口中,Context就代表这个状态机,程序的主要工作就是向Context提供图元、设置状态,偶尔也从Context里获取一些信息。用 EGLContext eglCreateContext(EGLDisplay dpy, EGLSurface write, EGLSurface read, EGLContext * share_list)来创建一个Context。
6. 绘制:
应用程序通过OpenGL API进行绘制,一帧完成之后,调用eglSwapBuffers(EGLDisplay dpy, EGLContext ctx)来显示。
(一) 初识EGL
EGL是介于RenderAPI(比如OpenGL ES和OpenVG)和本地基础系统的一套接口。里面涉及了OpenGL ES和OpenVG的一些相关描述,所以需要和OpenGL ES和OpenVG文档一起阅读。EGL使用OpenGLES的命名习惯来命名函数入口和宏定义。具体的接口和相关宏定义可以参见egl.h。
(二) EGL的使用
1. 获取Display:
Display代表的是显示器,有的系统上有多个显示器,也就会有多个display。获得Display需要调用EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id);,参数一般为EGL_DEFAULT_DISPLAY。该参数的实际意义是平台相关的,比如在windows平台上,一般返回的 就是DC。沃Phone上就是TDC。
2. 初始化egl:
获得了Display后,调用EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor);该函数会进行一些相关的内部初始化工作。我们可以通过这个函数获得egl的版本号。
3. 选择Config:
Config实际就是FrameBuffer的参数,在Windows下对应于PixelFormat,在X-Window下对应Visual。可以用函 数EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);,其中attrib_list 是以EGL_NONE结束的参数数组,通常以id,value依次存放,对于个别标识性的属性可以只有id,没有value。另一个办法是用 EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); 来获得所有config。这两个函数都会返回不多于config_size个Config,结果保存在configs[]中,系统的总Config个数保 存在num_config中。可以利用eglGetConfig()中间两个参数为0来查询系统支持的Config总个数。Config有众多的 Attribute,这些Attribute决定FrameBuffer的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改。
4. 构造Surface:
有了Config,就可以开始构造Surface了。Surface实际上就是一个FrameBuffer。通过函数EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config,EGLNativeWindowType win, const EGLint *attrib_list)可以创建一个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()读取。
5. 创建Context:
OpenGL ES的pipeline从程序的角度看就是一个状态机,有当前的颜色、纹理坐标、变换矩阵、渲染模式等一大堆状态,这些状态作用于程序提交的顶点坐标等图 元从而形成帧缓冲内的像素。在OpenGL ES的编程接口中,Context就代表这个状态机,程序的主要工作就是向Context提供图元、设置状态,偶尔也从Context里获取一些信息。用 EGLContext eglCreateContext(EGLDisplay dpy, EGLSurface write, EGLSurface read, EGLContext * share_list)来创建一个Context。
6. 绘制:
应用程序通过OpenGL API进行绘制,一帧完成之后,调用eglSwapBuffers(EGLDisplay dpy, EGLContext ctx)来显示。
OpenGL ES的HelloWorld---Hello********
- // 头文件包含
- #include "TG3.h"
- #include
- #include
- #include "OGLES2Hello********.h"
- // 宏定义
- #define VERTEX_ARRAY 0
- // 定义Display、config、surface、context
- EGLDisplay eglDisplay = 0;
- EGLConfig eglConfig = 0;
- EGLSurface eglSurface = 0;
- EGLContext eglContext = 0;
- EGLNativeWindowType eglWindow = 0;
- // 沃Phone窗口指针
- extern TWindow *g_pThis;
- bool TestEGLError()
- {
- //eglGetError返回上一个egl中的错误,用户在每个egl函数调用结束都需要调用这个函数。
- EGLint iErr = eglGetError();
- if (iErr != EGL_SUCCESS)
- {
- return false;
- }
- return true;
- }
- bool CreateEGLContext()
- {
- // 第一步:获得或者创建一个可以用于OpenGL ES输出的EGLNativeWindowType
- eglWindow = (EGLNativeWindowType)g_pThis;
- //第二步:获得默认的Display。通常我们只有一块屏幕,参数传EGL_DEFAULT_DISPLAY就可以了。
- eglDisplay = eglGetDisplay((EGLNativeDisplayType) EGL_DEFAULT_DISPLAY);
- //第三步:初始化EGL,如果我们不想要版本号,后两个参数也可以传NULL进去。
- EGLint iMajorVersion, iMinorVersion;
- if (!eglInitialize(eglDisplay, &iMajorVersion, &iMinorVersion))
- {
- return false;
- }
- //第四步:指定需要的配置属性,一个EGL的配置描述了Surfaces上像素的格式信息。当前我们要的是Windows的surface,在屏幕上是可见的,以EGL_NONE结尾。
- const EGLint pi32ConfigAttribs[] =
- {
- EGL_LEVEL, 0,
- EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL_NATIVE_RENDERABLE, EGL_FALSE,
- EGL_DEPTH_SIZE, EGL_DONT_CARE,
- EGL_NONE
- };
- //第五步:寻找一个符合所有要求的配置,我们需要的只是其中一个,所以可以限制config的个数为1。
- int iConfigs;
- if (!eglChooseConfig(eglDisplay, pi32ConfigAttribs, &eglConfig, 1, &iConfigs) || (iConfigs != 1))
- {
- return false;
- }
- //第六步:创建一个可画的surface。这里创建时可见的windows surface。Pixmaps和pbuffers都是不可见的。
- eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, eglWindow, NULL);
- if(eglSurface == EGL_NO_SURFACE)
- {
- eglGetError(); // Clear error
- eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, NULL, NULL);
- }
- if (!TestEGLError())
- {
- return false;
- }
- //第七步:创建Context。我们OpenGL ES的资源,比如纹理只有在这个context里是可见的。
- // 绑定API (可以是OpenGLES或者 OpenVG)
- eglBindAPI(EGL_OPENGL_ES_API);
- EGLint ai32ContextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
- eglContext = eglCreateContext(eglDisplay, eglConfig, NULL, ai32ContextAttribs);
- if (!TestEGLError())
- {
- return false;
- }
- //第八步:将创建的context绑定到当前的线程,使用我们创建的surface进行读和写。Context绑定到线程上,你就可以不用担心其他的进程影响你的OpenGL ES应用程序。
- eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
- if (!TestEGLError())
- {
- return false;
- }
- return true;
- }
- bool Render()
- {
- //第九步:使用OpenGL ES API画一些东西。到这里,所有的东西都已准备就绪,我们做好了往屏幕上画东西的准备。
- bool bRet = false;
- //单元矩阵,用于投影和模型变换
- float pfIdentity[] =
- {
- 1.0f,0.0f,0.0f,0.0f,
- 0.0f,1.0f,0.0f,0.0f,
- 0.0f,0.0f,1.0f,0.0f,
- 0.0f,0.0f,0.0f,1.0f
- };
- // Vertex和Fragment的shader
- // gl_FragColor指定了最终的像素颜色。
- // gl_position则指定了最终的点在人眼坐标中的位置。
- char szFragShaderSrc[] = {"\
- void main (void)\
- {\
- gl_FragColor = vec4(1.0, 1.0, 0.66 ,1.0);\
- }"};
- char szVertShaderSrc[] = {"\
- attribute highp vec4 myVertex;\
- uniform mediump mat4 myPMVMatrix;\
- void main(void)\
- {\
- gl_Position = myPMVMatrix * myVertex;\
- }"};
- char * pszFragShader = (char *)szFragShaderSrc;
- char * pszVertShader = (char *)szVertShaderSrc;
- GLuint uiFragShader = 0;
- GLuint uiVertShader = 0; /* 用来存放Vertex和Fragment shader的句柄 */
- GLuint uiProgramObject = 0; /* 用来存放创建的program的句柄*/
- GLint bShaderCompiled;
- GLint bLinked;
- // 我们要画一个三角形,所以,我们先创建一个顶点缓冲区
- GLuint ui32Vbo = 0; // 顶点缓冲区对象句柄
- //顶点数据 这9个数据分别为3个点的X、Y、Z坐标
- GLfloat afVertices[] = { -0.4f,-0.4f,0.0f, // Position
- 0.4f ,-0.4f,0.0f,
- 0.0f ,0.4f ,0.0f};
- int i32InfoLogLength, i32CharsWritten;
- char* pszInfoLog = NULL;
- int i32Location = 0;
- unsigned int uiSize = 0;
- //创建Fragment着色器对象
- uiFragShader = glCreateShader(GL_FRAGMENT_SHADER);
- // 将代码加载进来
- glShaderSource(uiFragShader, 1, (const char**)&pszFragShader, NULL);
- //编译代码
- glCompileShader(uiFragShader);
- //看编译是否成功进行
- glGetShaderiv(uiFragShader, GL_COMPILE_STATUS, &bShaderCompiled);
- if (!bShaderCompiled)
- {
- // 错误发生,首先获取错误的长度
- glGetShaderiv(uiFragShader, GL_INFO_LOG_LENGTH, &i32InfoLogLength);
- //开辟足够的空间来存储错误信息
- pszInfoLog = new char[i32InfoLogLength];
- glGetShaderInfoLog(uiFragShader, i32InfoLogLength, &i32CharsWritten, pszInfoLog);
- delete[] pszInfoLog;
- goto cleanup;
- }
- // 使用同样的方法加载Vertex Shader
- uiVertShader = glCreateShader(GL_VERTEX_SHADER);
- glShaderSource(uiVertShader, 1, (const char**)&pszVertShader, NULL);
- glCompileShader(uiVertShader);
- glGetShaderiv(uiVertShader, GL_COMPILE_STATUS, &bShaderCompiled);
- if (!bShaderCompiled)
- {
- glGetShaderiv(uiVertShader, GL_INFO_LOG_LENGTH, &i32InfoLogLength);
- pszInfoLog = new char[i32InfoLogLength];
- glGetShaderInfoLog(uiVertShader, i32InfoLogLength, &i32CharsWritten, pszInfoLog);
- delete[] pszInfoLog;
- goto cleanup;
- }
- // 创建着色器程序
- uiProgramObject = glCreateProgram();
- // 将Vertex和Fragment Shader绑定进去。
- glAttachShader(uiProgramObject, uiFragShader);
- glAttachShader(uiProgramObject, uiVertShader);
- //将用户自定义的顶点属性myVertex绑定到VERTEX_ARRAY。
- glBindAttribLocation(uiProgramObject, VERTEX_ARRAY, "myVertex");
- // 链接着色器程序
- glLinkProgram(uiProgramObject);
- // 判断链接是否成功的操作
- glGetProgramiv(uiProgramObject, GL_LINK_STATUS, &bLinked);
- if (!bLinked)
- {
- glGetProgramiv(uiProgramObject, GL_INFO_LOG_LENGTH, &i32InfoLogLength);
- pszInfoLog = new char[i32InfoLogLength];
- glGetProgramInfoLog(uiProgramObject, i32InfoLogLength, &i32CharsWritten, pszInfoLog);
- delete[] pszInfoLog;
- goto cleanup;
- }
- // 使用着色器程序
- glUseProgram(uiProgramObject);
- // 设置清除颜色,以RGBA的模式,每个分量的值从0.0到1.0
- glClearColor(0.6f, 0.8f, 1.0f, 1.0f);
- //生成一个顶点缓冲区对象
- glGenBuffers(1, &ui32Vbo);
- //绑定生成的缓冲区对象到GL_ARRAY_BUFFER
- glBindBuffer(GL_ARRAY_BUFFER, ui32Vbo);
- // 加载顶点数据
- uiSize = 3 * (sizeof(GLfloat) * 3); // Calc afVertices size (3 vertices * stride (3 GLfloats per vertex))
- glBufferData(GL_ARRAY_BUFFER, uiSize, afVertices, GL_STATIC_DRAW);
- // 画三角形
- {
- //清除颜色缓冲区。glClear同样也能清除深度缓冲区(GL_DEPTH_BUFFER)和模板缓冲区(GL_STENCIL_BUFFER_BIT)
- glClear(GL_COLOR_BUFFER_BIT);
- //获取myPMVMatrix在shader中的位置
- i32Location = glGetUniformLocation(uiProgramObject, "myPMVMatrix");
- //传值给获取到的位置,也就是将pfIdentity传给myPMVMatrix
- glUniformMatrix4fv( i32Location, 1, GL_FALSE, pfIdentity);
- //将VERTEX_ARRAY置为有效。
- glEnableVertexAttribArray(VERTEX_ARRAY);
- // 将定点数据传到VERTEX_ARRAY
- glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, 0, 0);
- //画一个三角形。
- glDrawArrays(GL_TRIANGLES, 0, 3);
- //SwapBuffers。就可以将三角形显示出来
- eglSwapBuffers(eglDisplay, eglSurface);
- }
- bRet = true;
- cleanup:
- // 释放资源
- if (uiProgramObject)
- glDeleteProgram(uiProgramObject);
- if (uiFragShader)
- glDeleteShader(uiFragShader);
- if (uiVertShader)
- glDeleteShader(uiVertShader);
- // Delete the VBO as it is no longer needed
- if (ui32Vbo)
- glDeleteBuffers(1, &ui32Vbo);
- return bRet;
- }
- bool DestroyEGLContext()
- {
- //第十步:结束 OpenGL ES并删除创建的Windows(如果存在的话).eglminate已经负责清除context和surface。所以调用了eglTerminate 后就无需再调用eglDestroySurface和eglDestroyContext了。
- if (eglDisplay != EGL_NO_DISPLAY)
- {
- eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- eglDestroyContext(eglDisplay, eglContext );
- eglDestroySurface(eglDisplay, eglSurface );
- eglTerminate(eglDisplay);
- eglDisplay = EGL_NO_DISPLAY;
- }
- //第十一步:删除已经创建的eglWindow。这个是跟具体平台有关的。
- eglWindow = NULL;
- return true;
- }
- //这样,有关OpenGL ES画一个三角形的步骤就走完了,剩下的,只是在沃Phone的平台上进行相关额外的操作,比如窗口的创建,消息循环的处理等。
- //根据工程向导新建一个新工程,工程新建完,在**MainForm.cpp中
- //新增定义:
- TWindow *g_pThis = NULL;
- //在TMainForm::TMainForm(TApplication * pApp):TWindow(pApp)中加入
- m_bGLESInit = FALSE;
- g_pThis = this;
- //在case EVENT_WinInit:中加入:
- if (CreateEGLContext())
- {
- m_bGLESInit = TRUE;
- Sys_PostMessage(MESSAGE_PRIOR_LOWEST, 0, &PID_SELF, EVENT_FirstUser, GetWindowHwndId(), 0, NULL, 0);
- }
- bHandled = TRUE;
- //新增:
- case EVENT_FirstUser:
- {
- if(m_bGLESInit)
- {
- Render();
- Sys_PostMessage(MESSAGE_PRIOR_LOWEST, 0, &PID_SELF, EVENT_FirstUser, GetWindowHwndId(), 0, NULL, 0);
- }
- bHandled = TRUE;
- }
- break;
- case EVENT_GlesUpdateNotify:
- {
- if(m_bGLESInit)
- {
- TRectangle rt;
- GetClientBounds(&rt);
- MarkUpdateRectangle(&rt);
- }
- bHandled = TRUE;
- }
- break;
- //在case EVENT_WinClose:中加入:
- if (m_bGLESInit)
- {
- DestroyEGLContext();
- m_bGLESInit = FALSE;
- }
- // Stop the application since the main form has been closed
- pApp->SendStopEvent();
这个是在模拟器上跑的时候的代码,如果是在真机下跑,同在模拟器上跑,有以下区别:
1.
模拟器上使用的是自己创建的
windows
窗口,使用
ms window
的消息机制。但在真机上传的是
沃Phone
的窗口句柄。
2.
在调用
eglGetDisplay
的时候,模拟器上传入的是
hdc
,真机上传入的是
(EGLNativeDisplayType)EGL_DEFAULT_DISPLAY)
。
3.
调用
eglCreateWindowSurface
的时候,模拟器上使用的是
hWnd
,但是真机上传入的是
TWindows
4.
模拟器上的消息循环使用
ms
的机制,需要自己控制。真机上则需要加入
EVENT_FirstUser
、
EVENT_GlesUpdateNotify
等消息。具体可参见范例。