解读OpenGL ES 2.0绘制一个三角形的步骤

OpenGL ES2.0 专栏收录该内容
4 篇文章 0 订阅

前言:

        从上一篇文章中,我们在win7下面搭建OpenGL ES 2.0开发环境的时候,成功运行了官方编程指导中提供的Hello_Triangle这个例子,最后得到的结果就是在窗口中绘制出一个红色的三角形,接下来我们就开始来解读这个demo的代码和实现过程。

        在此之前,我们先大致了解整个绘制过程的基本步骤:

1. 使用EGL创建屏幕上渲染的表面Surface
2. 加载顶点和片元着色器
3. 创建program对象,连接顶点和片元着色器,链接program对象。
4. 设置视口(viewport)。
5. 清除颜色缓冲区。
6. 绘制一个简单的图元。
7. 使颜色缓冲区的内容显示到EGL窗口表面(屏幕)。

        这么看来绘制一个简单的三角形也是一件挺复杂的事情,因为OpenGL ES 2.0是完全基于着色器的,这也就是说我们必须加载和绑定合适的着色器才能进行图形画面的绘制,这相比于固定管线的OpenGL ES 1.x要复杂得多。

        

        从工程的结构树我们可以看到esUtil是一个API工程,其中Common目录下包含了框架源码和OpenGL ES 2.0所需的头文件,关于这部分源码的具体内容我们在后面的学习中再做了解。这里我们要分析的是demo工程的源码,其实源码就只有Hello_Triangle.c这个文件。


一、main函数:

        阅读源码的习惯当然是从main函数入口开始,那么我们来看看main函数:

int main ( int argc, char *argv[] )
{
   ESContext esContext;
   UserData  userData;

   esInitContext ( &esContext );
   esContext.userData = &userData;

   esCreateWindow ( &esContext, "Hello Triangle", 320, 240, ES_WINDOW_RGB );
   
   if ( !Init ( &esContext ) )
      return 0;

   esRegisterDrawFunc ( &esContext, Draw );
   
   esMainLoop ( &esContext );
}

        这里我们首先声明了一个ESContext结构,这个是框架提供的一个结构体,用于存储一些公用的数据,这样就可以不自行去定义全局变量,当然这对于多平台的可移植性来说是必须的,因为在某些平台中是不支持全局变量的,例如:Symbian。这里我们可以直接在Common/esUtil.c中找到ESContext的定义:

typedef struct
{
   /// Put your user data here...
   void*       userData;

   /// Window width
   GLint       width;

   /// Window height
   GLint       height;

   /// Window handle
   EGLNativeWindowType  hWnd;

   /// EGL display
   EGLDisplay  eglDisplay;
      
   /// EGL context
   EGLContext  eglContext;

   /// EGL surface
   EGLSurface  eglSurface;

   /// Callbacks
   void (ESCALLBACK *drawFunc) ( void* );
   void (ESCALLBACK *keyFunc) ( void*, unsigned char, int, int );
   void (ESCALLBACK *updateFunc) ( void*, float deltaTime );
} ESContext;

        然后main中又声明了一个UserData结构,这个其实是我们在当前脚本中自行定义的结构体,用于存放一些用户自行定义的数据,这里我们可以看到结构体中只是封装了一个GLunit数据,这个其实就是OpenGL中的无符号整型(正整型),相当于C语言中的unsigned int。

typedef struct
{
   // Handle to a program object
   GLuint programObject;
} UserData;

        这里以es为前缀的函数都是ES框架封装的一些方法:

         esInitContext: 也是框架封装好的一个方法,用于对最开始声明的 ESContext结构进行初始化;
        esCreateWindow: 此方法用于创建一个窗口并指定其宽度和高度,同时也设置了窗口左上角的标题。此处,是使用EGL在屏幕上创建一个渲染表面并附加到一个窗口上,EGL是跨平台的,事OpenGL ES与本地窗口系统的中介,可以用它来创建渲染表面和上下文;

        Init:我们自行定义的初始化函数,主要用于初始化着色器、programObject等;

        Draw:控制初始化后加载进来的顶点着色器片元着色器,绘制最后窗口真正要显示出来的内容;

       esRegisterDrawFunc:指定绘制回调的方法为此脚本中我们自行定义的Draw函数,如此系统每次绘制都会调用这个函数,渲染帧数据;

       esMainLoop:进入主消息循环,此处是无限循环,直到窗口关闭为止。


二、Init函数:
        在看完入口函数之后,我们接下来再看看入口函数中调用到的我们自定义的初始化函数Init:

///
// Initialize the shader and program object
//
int Init ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   GLbyte vShaderStr[] =  
      "attribute vec4 vPosition;    \n"
      "void main()                  \n"
      "{                            \n"
      "   gl_Position = vPosition;  \n"
      "}                            \n";
   
   GLbyte fShaderStr[] =  
      "precision mediump float;\n"\
      "void main()                                  \n"
      "{                                            \n"
      "  gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );\n"
      "}                                            \n";

   GLuint vertexShader;
   GLuint fragmentShader;
   GLuint programObject;
   GLint linked;

   // Load the vertex/fragment shaders
   vertexShader = LoadShader ( GL_VERTEX_SHADER, vShaderStr );
   fragmentShader = LoadShader ( GL_FRAGMENT_SHADER, fShaderStr );

   // Create the program object
   programObject = glCreateProgram ( );
   
   if ( programObject == 0 )
      return 0;

   glAttachShader ( programObject, vertexShader );
   glAttachShader ( programObject, fragmentShader );

   // Bind vPosition to attribute 0   
   glBindAttribLocation ( programObject, 0, "vPosition" );

   // Link the program
   glLinkProgram ( programObject );

   // Check the link status
   glGetProgramiv ( programObject, GL_LINK_STATUS, &linked );

   if ( !linked ) 
   {
      GLint infoLen = 0;

      glGetProgramiv ( programObject, GL_INFO_LOG_LENGTH, &infoLen );
      
      if ( infoLen > 1 )
      {
         char* infoLog = malloc (sizeof(char) * infoLen );

         glGetProgramInfoLog ( programObject, infoLen, NULL, infoLog );
         esLogMessage ( "Error linking program:\n%s\n", infoLog );            
         
         free ( infoLog );
      }

      glDeleteProgram ( programObject );
      return FALSE;
   }

   // Store the program object
   userData->programObject = programObject;

   glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );
   return TRUE;
}

        在这个函数中,首先我们定义了顶点着色器片元着色器源码,这是OpenGL ES 2.0进行绘制必不可少的两个对象。

vShaderStr:顶点着色器源码——声明一个vec4类型(包含四个float的向量)的输入属性vPosition;设置着色器的入口函数main函数并且在main函数中把vPosition赋值给内置输出变量gl_Position,这个变量会传给图元装配阶段。

fShaderStr:片元着色器源码——为float类型变量指定一个默认的精度为mediump;在main函数中给内置输出变量gl_FragColor复制为vec4(1.0,0.0,0.0,1.0),这里就是用于指定片元输出的颜色为红色。

注:虽然在这里我们将着色器源码以字符串变量的形式放在代码中,但在实际开发中,我们通常将着色器源码放在文件中,那样更方便代码管理。)

        用LoadShader创建了顶点着色器和片元着色器之后,需要创建programObject对象(着色器对象跟programObject的关系就相当于编译器和连接器)。需要把着色器对象连接到programObject对象,一个programObject对象必须连接一个顶点着色器和一个片元着色器。

        glCreateProgram:创建一个programObject对象

        glAttachShader:着色器与programObject对象连接

        glBindAttribLocation:设置顶点着色器属性vPosition的位置

        glGetProgramiv:获取连接状态

       glClearColor:设置glClear时的背景颜色

连接正常后把programObject对象存放到UserData中。


三、LoadShader函数:

///
// Create a shader object, load the shader source, and
// compile the shader.
//
GLuint LoadShader ( GLenum type, const char *shaderSrc )
{
   GLuint shader;
   GLint compiled;
   
   // Create the shader object
   shader = glCreateShader ( type );

   if ( shader == 0 )
   	return 0;

   // Load the shader source
   glShaderSource ( shader, 1, &shaderSrc, NULL );
   
   // Compile the shader
   glCompileShader ( shader );

   // Check the compile status
   glGetShaderiv ( shader, GL_COMPILE_STATUS, &compiled );
   if ( !compiled ) 
   {
      GLint infoLen = 0;
      glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, &infoLen );
      if ( infoLen > 1 )
      {
         char* infoLog = malloc (sizeof(char) * infoLen );

         glGetShaderInfoLog ( shader, infoLen, NULL, infoLog );
         esLogMessage ( "Error compiling shader:\n%s\n", infoLog );            
         
         free ( infoLog );
      }
      glDeleteShader ( shader );
      return 0;
   }
   return shader;
}

       这是着色器的加载函数,加载步骤:

1.glCreateShader:创建指定类型(顶点或片元)的着色器对象

2.glShaderSource:加载着色器的源码

3.glCompileShader:编译着色器

4.glGetShaderiv:获得着色器编译状态(用于判断编译是否成功,若成功则返回着色器对象)


四、Draw函数:

///
// Draw a triangle using the shader pair created in Init()
//
void Draw ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   GLfloat vVertices[] = {  0.0f,  0.5f, 0.0f, 
                           -0.5f, -0.5f, 0.0f,
                            0.5f, -0.5f, 0.0f };
      
   // Set the viewport
   glViewport ( 0, 0, esContext->width, esContext->height );
   
   // Clear the color buffer
   glClear ( GL_COLOR_BUFFER_BIT );

   // Use the program object
   glUseProgram ( userData->programObject );

   // Load the vertex data
   glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
   glEnableVertexAttribArray ( 0 );

   glDrawArrays ( GL_TRIANGLES, 0, 3 );

   eglSwapBuffers ( esContext->eglDisplay, esContext->eglSurface );
}
        Draw实现了绘制帧数据,具体步骤:

1.定义顶点数据坐标

2.glViewport:通过OpenGL ES设置绘制的2D渲染表面的起点(x,y)、宽和高,也就是最终的绘制区域(可视区域)

3.glClear:以指定的颜色清除颜色缓冲区,也就是清屏,这里使用的颜色就是Init初始化函数中glClearColor设置的颜色(故glClearColor需要在glClear之前执行)

4.glUseProgram:获取渲染所要用到的program对象,这样接下来的所有渲染都会使用连接到program对象的着色器

5.glVertexAttribPointer:加载几何图形和绘制图元,此demo中我们指定的图形是三角形,vVertices中定义了三角形的顶点坐标组,然后把顶点位置加载到OpenGL ES,并连接顶点着色器定义的属性vPosition。顶点着色器中的每个属性都有一个唯一的位置(或者说是索引,是一个unsigned int值)。所以glVertexAttribPointer的作用就是把顶点数据加载到着色器中指定索引(例如索引我为0的属性为vPosition)的属性
6.glEnableVertexAttribArray:启动通用顶点数组属性

7.glDrawArrays:告诉OpenGL ES要绘制的图元是三角形

8.eglSwapBuffers:把缓存中的数据显示到绑定的屏幕上(双缓冲技术:前台缓冲区(屏幕可见)和后台缓存区(不可见),等到后台缓冲区渲染完成后再与前台缓冲区交换,如此便可实现平滑过渡)。

        eglSwapBuffers(esContext->eglDisplay, esContext->eglSurface);这是一个EGL函数,其输入参数为EGL显示区和窗口,这两个参数代表了实际的物理显示区和渲染区。


附加:

        关于EGL,EGL 是 Khronos 组织创造的渲染 API(OpenGL ES)和操作系统窗口之间的接口应用程序中引入EGL需要添加链接库:libEGL.lib任何OpenGL ES在使用EGL渲染前需要做的事情:

1.查询设备上可用的显示初始化它;

2.创建一个渲染屏幕(隐藏平面或显示屏平面);

3.创建渲染上下文。


  • 2
    点赞
  • 1
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值