摘 要:
本篇文档从零开始,深入简出,跟大家介绍一下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的原理和开发。

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 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分析。
 

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)来显示。

 OpenGL ES的HelloWorld---Hello********

 
  1. // 头文件包含
  2. #include "TG3.h"
  3. #include
  4. #include
  5. #include "OGLES2Hello********.h"
  6.   
  7. // 宏定义
  8. #define VERTEX_ARRAY 0
  9.   
  10. // 定义Display、config、surface、context
  11. EGLDisplay eglDisplay = 0;
  12. EGLConfig eglConfig = 0;
  13. EGLSurface eglSurface = 0;
  14. EGLContext eglContext = 0;
  15. EGLNativeWindowType eglWindow = 0;
  16.   
  17. // 沃Phone窗口指针
  18. extern TWindow *g_pThis;
  19.   
  20. bool TestEGLError()
  21. {
  22.     //eglGetError返回上一个egl中的错误,用户在每个egl函数调用结束都需要调用这个函数。
  23.     EGLint iErr = eglGetError();
  24.     if (iErr != EGL_SUCCESS)
  25.     {
  26.         return false;
  27.     }
  28.   
  29.     return true;
  30. }
  31.   
  32. bool CreateEGLContext()
  33. {
  34.     // 第一步:获得或者创建一个可以用于OpenGL ES输出的EGLNativeWindowType
  35.     eglWindow = (EGLNativeWindowType)g_pThis;
  36.   
  37.     //第二步:获得默认的Display。通常我们只有一块屏幕,参数传EGL_DEFAULT_DISPLAY就可以了。
  38.     eglDisplay = eglGetDisplay((EGLNativeDisplayType) EGL_DEFAULT_DISPLAY);
  39.   
  40.     //第三步:初始化EGL,如果我们不想要版本号,后两个参数也可以传NULL进去。
  41.     EGLint iMajorVersion, iMinorVersion;
  42.     if (!eglInitialize(eglDisplay, &iMajorVersion, &iMinorVersion))
  43.     {
  44.         return false;
  45.     }
  46.   
  47.     //第四步:指定需要的配置属性,一个EGL的配置描述了Surfaces上像素的格式信息。当前我们要的是Windows的surface,在屏幕上是可见的,以EGL_NONE结尾。
  48.     const EGLint pi32ConfigAttribs[] =
  49.     {
  50.         EGL_LEVEL, 0,
  51.         EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
  52.         EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
  53.         EGL_NATIVE_RENDERABLE, EGL_FALSE,
  54.         EGL_DEPTH_SIZE, EGL_DONT_CARE,
  55.         EGL_NONE
  56.     };
  57.   
  58.     //第五步:寻找一个符合所有要求的配置,我们需要的只是其中一个,所以可以限制config的个数为1。
  59.     int iConfigs;
  60.     if (!eglChooseConfig(eglDisplay, pi32ConfigAttribs, &eglConfig, 1, &iConfigs) || (iConfigs != 1))
  61.     {
  62.         return false;
  63.     }
  64.   
  65.     //第六步:创建一个可画的surface。这里创建时可见的windows surface。Pixmaps和pbuffers都是不可见的。
  66.     eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, eglWindow, NULL);
  67.   
  68.     if(eglSurface == EGL_NO_SURFACE)
  69.     {
  70.         eglGetError(); // Clear error
  71.         eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, NULL, NULL);
  72.     }
  73.   
  74.     if (!TestEGLError())
  75.     {
  76.         return false;
  77.     }
  78.   
  79.     //第七步:创建Context。我们OpenGL ES的资源,比如纹理只有在这个context里是可见的。
  80.     // 绑定API (可以是OpenGLES或者 OpenVG)
  81.     eglBindAPI(EGL_OPENGL_ES_API);
  82.     EGLint ai32ContextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
  83.     eglContext = eglCreateContext(eglDisplay, eglConfig, NULL, ai32ContextAttribs);
  84.   
  85.     if (!TestEGLError())
  86.     {
  87.         return false;
  88.     }
  89.   
  90.     //第八步:将创建的context绑定到当前的线程,使用我们创建的surface进行读和写。Context绑定到线程上,你就可以不用担心其他的进程影响你的OpenGL ES应用程序。
  91.     eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
  92.     if (!TestEGLError())
  93.     {
  94.         return false;
  95.     }
  96.     return true;
  97. }
  98.   
  99. bool Render()
  100. {
  101.     //第九步:使用OpenGL ES API画一些东西。到这里,所有的东西都已准备就绪,我们做好了往屏幕上画东西的准备。
  102.     bool bRet = false;
  103.   
  104.     //单元矩阵,用于投影和模型变换
  105.     float pfIdentity[] =
  106.     {
  107.         1.0f,0.0f,0.0f,0.0f,
  108.         0.0f,1.0f,0.0f,0.0f,
  109.         0.0f,0.0f,1.0f,0.0f,
  110.         0.0f,0.0f,0.0f,1.0f
  111.     };
  112.   
  113.     // Vertex和Fragment的shader
  114.     // gl_FragColor指定了最终的像素颜色。
  115.     // gl_position则指定了最终的点在人眼坐标中的位置。
  116.     char szFragShaderSrc[] = {"\
  117.                               void main (void)\
  118.                               {\
  119.                               gl_FragColor = vec4(1.0, 1.0, 0.66 ,1.0);\
  120.                               }"};
  121.     char szVertShaderSrc[] = {"\
  122.                               attribute highp vec4 myVertex;\
  123.                               uniform mediump mat4 myPMVMatrix;\
  124.                               void main(void)\
  125.                               {\
  126.                               gl_Position = myPMVMatrix * myVertex;\
  127.                               }"};
  128.   
  129.     char * pszFragShader = (char *)szFragShaderSrc;
  130.     char * pszVertShader = (char *)szVertShaderSrc;
  131.   
  132.     GLuint uiFragShader = 0;
  133.     GLuint uiVertShader = 0; /* 用来存放Vertex和Fragment shader的句柄 */ 
  134.     GLuint uiProgramObject = 0; /* 用来存放创建的program的句柄*/ 
  135.   
  136.     GLint bShaderCompiled;
  137.     GLint bLinked;
  138.   
  139.     // 我们要画一个三角形,所以,我们先创建一个顶点缓冲区
  140.     GLuint ui32Vbo = 0; // 顶点缓冲区对象句柄
  141.   
  142.     //顶点数据 这9个数据分别为3个点的X、Y、Z坐标
  143.     GLfloat afVertices[] = { -0.4f,-0.4f,0.0f, // Position
  144.         0.4f ,-0.4f,0.0f,
  145.         0.0f ,0.4f ,0.0f};
  146.   
  147.     int i32InfoLogLength, i32CharsWritten;
  148.     char* pszInfoLog = NULL;
  149.     int i32Location = 0;
  150.     unsigned int uiSize = 0;
  151.   
  152.     //创建Fragment着色器对象
  153.     uiFragShader = glCreateShader(GL_FRAGMENT_SHADER);
  154.   
  155.     // 将代码加载进来
  156.     glShaderSource(uiFragShader, 1, (const char**)&pszFragShader, NULL);
  157.   
  158.     //编译代码
  159.     glCompileShader(uiFragShader);
  160.   
  161.     //看编译是否成功进行
  162.     glGetShaderiv(uiFragShader, GL_COMPILE_STATUS, &bShaderCompiled);
  163.   
  164.     if (!bShaderCompiled)
  165.     {
  166.         // 错误发生,首先获取错误的长度
  167.         glGetShaderiv(uiFragShader, GL_INFO_LOG_LENGTH, &i32InfoLogLength);
  168.         //开辟足够的空间来存储错误信息
  169.         pszInfoLog = new char[i32InfoLogLength];
  170.         glGetShaderInfoLog(uiFragShader, i32InfoLogLength, &i32CharsWritten, pszInfoLog);
  171.         delete[] pszInfoLog;
  172.         goto cleanup;
  173.     }
  174.   
  175.     // 使用同样的方法加载Vertex Shader
  176.     uiVertShader = glCreateShader(GL_VERTEX_SHADER);
  177.     glShaderSource(uiVertShader, 1, (const char**)&pszVertShader, NULL);
  178.     glCompileShader(uiVertShader);
  179.     glGetShaderiv(uiVertShader, GL_COMPILE_STATUS, &bShaderCompiled);
  180.     if (!bShaderCompiled)
  181.     {
  182.         glGetShaderiv(uiVertShader, GL_INFO_LOG_LENGTH, &i32InfoLogLength);
  183.         pszInfoLog = new char[i32InfoLogLength];
  184.         glGetShaderInfoLog(uiVertShader, i32InfoLogLength, &i32CharsWritten, pszInfoLog);
  185.         delete[] pszInfoLog;
  186.         goto cleanup;
  187.     }
  188.   
  189.     // 创建着色器程序
  190.     uiProgramObject = glCreateProgram();
  191.   
  192.     // 将Vertex和Fragment Shader绑定进去。
  193.     glAttachShader(uiProgramObject, uiFragShader);
  194.     glAttachShader(uiProgramObject, uiVertShader);
  195.   
  196.     //将用户自定义的顶点属性myVertex绑定到VERTEX_ARRAY。
  197.     glBindAttribLocation(uiProgramObject, VERTEX_ARRAY, "myVertex");
  198.   
  199.     // 链接着色器程序
  200.     glLinkProgram(uiProgramObject);
  201.   
  202.     // 判断链接是否成功的操作
  203.     glGetProgramiv(uiProgramObject, GL_LINK_STATUS, &bLinked);
  204.     if (!bLinked)
  205.     {
  206.         glGetProgramiv(uiProgramObject, GL_INFO_LOG_LENGTH, &i32InfoLogLength);
  207.         pszInfoLog = new char[i32InfoLogLength];
  208.         glGetProgramInfoLog(uiProgramObject, i32InfoLogLength, &i32CharsWritten, pszInfoLog);
  209.         delete[] pszInfoLog;
  210.         goto cleanup;
  211.     }
  212.   
  213.     // 使用着色器程序
  214.     glUseProgram(uiProgramObject);
  215.   
  216.     // 设置清除颜色,以RGBA的模式,每个分量的值从0.0到1.0
  217.     glClearColor(0.6f, 0.8f, 1.0f, 1.0f);
  218.   
  219.     //生成一个顶点缓冲区对象
  220.     glGenBuffers(1, &ui32Vbo);
  221.   
  222.     //绑定生成的缓冲区对象到GL_ARRAY_BUFFER
  223.     glBindBuffer(GL_ARRAY_BUFFER, ui32Vbo);
  224.   
  225.     // 加载顶点数据
  226.     uiSize = 3 * (sizeof(GLfloat) * 3); // Calc afVertices size (3 vertices * stride (3 GLfloats per vertex))
  227.     glBufferData(GL_ARRAY_BUFFER, uiSize, afVertices, GL_STATIC_DRAW);
  228.   
  229.     // 画三角形
  230.     {
  231.         //清除颜色缓冲区。glClear同样也能清除深度缓冲区(GL_DEPTH_BUFFER)和模板缓冲区(GL_STENCIL_BUFFER_BIT)
  232.         glClear(GL_COLOR_BUFFER_BIT);
  233.   
  234.   
  235.         //获取myPMVMatrix在shader中的位置
  236.         i32Location = glGetUniformLocation(uiProgramObject, "myPMVMatrix");
  237.   
  238.         //传值给获取到的位置,也就是将pfIdentity传给myPMVMatrix
  239.         glUniformMatrix4fv( i32Location, 1, GL_FALSE, pfIdentity);
  240.   
  241.         //将VERTEX_ARRAY置为有效。
  242.         glEnableVertexAttribArray(VERTEX_ARRAY);
  243.   
  244.         // 将定点数据传到VERTEX_ARRAY
  245.         glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, 0, 0);
  246.   
  247.         //画一个三角形。
  248.         glDrawArrays(GL_TRIANGLES, 0, 3);
  249.   
  250.         //SwapBuffers。就可以将三角形显示出来
  251.         eglSwapBuffers(eglDisplay, eglSurface);
  252.     }
  253.     bRet = true;
  254.   
  255. cleanup:
  256.     // 释放资源
  257.     if (uiProgramObject)
  258.         glDeleteProgram(uiProgramObject);
  259.     if (uiFragShader)
  260.         glDeleteShader(uiFragShader);
  261.     if (uiVertShader)
  262.         glDeleteShader(uiVertShader);
  263.   
  264.     // Delete the VBO as it is no longer needed
  265.     if (ui32Vbo)
  266.         glDeleteBuffers(1, &ui32Vbo);
  267.   
  268.     return bRet;
  269. }
  270.   
  271. bool DestroyEGLContext()
  272. {
  273.     //第十步:结束 OpenGL ES并删除创建的Windows(如果存在的话).eglminate已经负责清除context和surface。所以调用了eglTerminate 后就无需再调用eglDestroySurface和eglDestroyContext了。
  274.     if (eglDisplay != EGL_NO_DISPLAY)
  275.     {
  276.         eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
  277.         eglDestroyContext(eglDisplay, eglContext );
  278.         eglDestroySurface(eglDisplay, eglSurface );
  279.         eglTerminate(eglDisplay);
  280.         eglDisplay = EGL_NO_DISPLAY;
  281.     }
  282.   
  283.     //第十一步:删除已经创建的eglWindow。这个是跟具体平台有关的。
  284.     eglWindow = NULL;
  285.     return true;
  286. }
  287.   
  288. //这样,有关OpenGL ES画一个三角形的步骤就走完了,剩下的,只是在沃Phone的平台上进行相关额外的操作,比如窗口的创建,消息循环的处理等。
  289. //根据工程向导新建一个新工程,工程新建完,在**MainForm.cpp中
  290. //新增定义:
  291. TWindow *g_pThis = NULL;
  292.   
  293. //在TMainForm::TMainForm(TApplication * pApp):TWindow(pApp)中加入
  294.     m_bGLESInit = FALSE;
  295.     g_pThis = this;
  296.   
  297. //在case EVENT_WinInit:中加入:
  298.             if (CreateEGLContext())
  299.             {
  300.                 m_bGLESInit = TRUE;
  301.                 Sys_PostMessage(MESSAGE_PRIOR_LOWEST, 0, &PID_SELF, EVENT_FirstUser, GetWindowHwndId(), 0, NULL, 0);
  302.             }
  303.             bHandled = TRUE;
  304.   
  305. //新增:
  306.     case EVENT_FirstUser:
  307.         {
  308.             if(m_bGLESInit)
  309.             {
  310.                 Render();
  311.                 Sys_PostMessage(MESSAGE_PRIOR_LOWEST, 0, &PID_SELF, EVENT_FirstUser, GetWindowHwndId(), 0, NULL, 0);
  312.             }
  313.             bHandled = TRUE;
  314.         }
  315.         break;
  316.     case EVENT_GlesUpdateNotify:
  317.         {
  318.             if(m_bGLESInit)
  319.             {
  320.                 TRectangle rt;
  321.                 GetClientBounds(&rt);
  322.                 MarkUpdateRectangle(&rt);
  323.             }
  324.             bHandled = TRUE;
  325.         }
  326.         break;
  327.   
  328. //在case EVENT_WinClose:中加入:
  329.             if (m_bGLESInit)
  330.             {
  331.                 DestroyEGLContext();
  332.                 m_bGLESInit = FALSE;
  333.             }
  334.             // Stop the application since the main form has been closed
  335.             pApp->SendStopEvent();
 
这个是在模拟器上跑的时候的代码,如果是在真机下跑,同在模拟器上跑,有以下区别:
1.      模拟器上使用的是自己创建的 windows 窗口,使用 ms window 的消息机制。但在真机上传的是 沃Phone 的窗口句柄。
2.      在调用 eglGetDisplay 的时候,模拟器上传入的是 hdc ,真机上传入的是 (EGLNativeDisplayType)EGL_DEFAULT_DISPLAY)
3.      调用 eglCreateWindowSurface 的时候,模拟器上使用的是 hWnd ,但是真机上传入的是 TWindows
4.      模拟器上的消息循环使用 ms 的机制,需要自己控制。真机上则需要加入 EVENT_FirstUser EVENT_GlesUpdateNotify 等消息。具体可参见范例。