欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【OpenGL ES】Hello Triangle

程序员文章站 2023-12-25 21:21:15
...

像Hello World一样,Hello Triangle是OpenGL ES的一个入门级例子,OpenGL ES 3.0完全基于着色器,如果没有绑定和加载合适的着色器,就无法绘制任何几何形状。下面介绍Hello Triangle的一般步骤,如何用OpenGL ES绘制一个三角形,需要做哪些事情,而每个步骤的详细原理则不作介绍,具体源码可参考https://github.com/geminy/aidear/tree/master/graphics/mu/examples/opengles3

1、main

首先从main函数开始,main函数在esUtil.c文件中定义,代码如下所示:

// esUtil.c
int main ( int argc, char *argv[] )
{
   // 1
   // ESContext是一个很重要的struct
   // 用于OpenGL ES的上下文管理
   // 贯穿程序始终
   // 稍后详细介绍ESContext 
   ESContext esContext;
   // 2
   // 初始化ESContext 
   memset ( &esContext, 0, sizeof( esContext ) );
   // 3
   // 通过esMain函数设置ESContext
   // esMain函数还作了其它的许多工作
   // 稍后详细介绍esMain 
   if ( esMain ( &esContext ) != GL_TRUE )
      return 1;   
   // 4
   // 进入程序主循环WinLoop函数
   // 稍后详细介绍WinLoop
   WinLoop ( &esContext );
   // 5
   // shutdown callback
   if ( esContext.shutdownFunc != NULL )
       esContext.shutdownFunc ( &esContext );
   // 6
   // 释放内存
   if ( esContext.userData != NULL )
       free ( esContext.userData );
   // 7
   // 程序结束
   return 0;
}

程序运行起来时,初始界面如下:

【OpenGL ES】Hello Triangle

2、ESContext

// esUtil.h
struct ESContext
{
   void       *platformData; // 平台数据
   void       *userData; // 用户数据
   GLint       width; // 窗口宽度
   GLint       height; // 窗口高度
   EGLNativeDisplayType eglNativeDisplay; // egl display handle
   EGLNativeWindowType  eglNativeWindow; // egl window handle
   EGLDisplay  eglDisplay; // egl display
   EGLContext  eglContext; // egl context
   EGLSurface  eglSurface; // egl surface
   void ( *drawFunc ) ( ESContext * ); // draw callback
   void ( *shutdownFunc ) ( ESContext * ); // shutdown callback
   void ( *keyFunc ) ( ESContext *, unsigned char, int, int ); // key callback
   void ( *updateFunc ) ( ESContext *, float deltaTime ); // update callback
};

ESContext的几个callback函数通过如下函数进行注册:

// esUtil.c
// 注册draw callback
void esRegisterDrawFunc ( ESContext *esContext, void ( *drawFunc ) ( ESContext * ) )
{
   esContext->drawFunc = drawFunc;
}
// 注册shutdown callback
void esRegisterShutdownFunc ( ESContext *esContext, void ( *shutdownFunc ) ( ESContext * ) )
{
   esContext->shutdownFunc = shutdownFunc;
}
// 注册update callback
void esRegisterUpdateFunc ( ESContext *esContext, void ( *updateFunc ) ( ESContext *, float ) )
{
   esContext->updateFunc = updateFunc;
}
// 注册key callback
void ESUTIL_API esRegisterKeyFunc ( ESContext *esContext,
                                    void ( *keyFunc ) ( ESContext *, unsigned char, int, int ) )
{
   esContext->keyFunc = keyFunc;
}

3、HelloTriangle

esMain——
HelloTriangle从上面提到的esMain函数开始,代码如下所示:

// Hello_Triangle.c
typedef struct
{
   GLuint programObject; // opengl es program object handle
} UserData;

int esMain ( ESContext *esContext )
{
    // 1
   // 给用户数据分配内存
   esContext->userData = malloc ( sizeof ( UserData ) );
   // 2
   // 创建窗口
   // 窗口标题为Hello Triangle
   // 窗口宽x高为320x240
   // 窗口颜色缓冲区使用RGB通道
   // 同时还更新了ESContext
   // 稍后详细介绍esCreateWindow函数
   esCreateWindow ( esContext, "Hello Triangle", 320, 240, ES_WINDOW_RGB );
   // 3
   // 初始化绘制三角形所需的opengl es shader和program
   if ( !Init ( esContext ) )
   {
      return GL_FALSE;
   }
   // 4 注册shutdown和draw callback
   esRegisterShutdownFunc ( esContext, Shutdown );
   esRegisterDrawFunc ( esContext, Draw );

   return GL_TRUE;
}

Init——
初始化绘制三角形所需的opengl es shader和program的Init函数主要包括四个步骤,LoadShader、glCreateProgram、glAttachShader和glLinkProgram,如下:

// Hello_Triangle.c
int Init ( ESContext *esContext )
{
   // 在此之前已经给用户数据分配了内存
   UserData *userData = esContext->userData;
   // 顶点着色器
   char vShaderStr[] =
      "#version 300 es                          \n"
      "layout(location = 0) in vec4 vPosition;  \n"
      "void main()                              \n"
      "{                                        \n"
      "   gl_Position = vPosition;              \n"
      "}                                        \n";
   // 片段着色器
   // fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );表示三角形颜色为黄色
   char fShaderStr[] =
      "#version 300 es                              \n"
      "precision mediump float;                     \n"
      "out vec4 fragColor;                          \n"
      "void main()                                  \n"
      "{                                            \n"
      "   fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );  \n"
      "}                                            \n";

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

   // 加载顶点着色器和片段着色器
   // 稍后详细介绍LoadShader函数
   vertexShader = LoadShader ( GL_VERTEX_SHADER, vShaderStr );
   fragmentShader = LoadShader ( GL_FRAGMENT_SHADER, fShaderStr );
   // 创建程序对象
   programObject = glCreateProgram ( );
   if ( programObject == 0 )
   {
      return 0;
   }
   // 把加载好的顶点着色器和片段着色器与刚创建的程序对象绑定起来
   glAttachShader ( programObject, vertexShader );
   glAttachShader ( programObject, fragmentShader );
   // 链接程序对象
   glLinkProgram ( programObject );
   // 检查程序对象链接状态
   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;
   }
   // 存储程序对象
   userData->programObject = programObject;
   // 设置背景颜色为蓝色
   glClearColor ( 0.0f, 1.0f, 0.0f, 0.0f );

   return TRUE;
}

LoadShader——
LoadShader函数用于加载指定的着色器,主要包括三个步骤,glCreateShader、glShaderSource和glCompileShader,如下:

// Hello_Triangle.c
GLuint LoadShader ( GLenum type, const char *shaderSrc )
{
   GLuint shader;
   GLint compiled;

   // 创建指定类型的着色器
   // type为GL_VERTEX_SHADER或GL_FRAGMENT_SHADER
   shader = glCreateShader ( type );
   if ( shader == 0 )
   {
      return 0;
   }
   // 加载着色器源码
   glShaderSource ( shader, 1, &shaderSrc, NULL );
   // 编译着色器
   glCompileShader ( shader );
   // 检查着色器编译状态
   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;
}

Draw——
在Draw回调函数中,glViewPort设置一个矩形观察区域,glClear用之前在片段着色器中设置的黄色清除三角形的颜色缓冲区(这是必需的),glUseProgram使用之前在Init函数中创建的程序对象,vVertices定义一个顶点数组,用于设置三角形的三个顶点的坐标,坐标原点在屏幕*,水平X轴正方向向右(可视坐标从-1.0到正1.0),竖直Y轴正方向向上(可视坐标从-1.0到正1.0),glVertexAttribPointer和glEnableVertexAttribArray对顶点数组进行处理,最后通过glDrawArrays绘制三角形,代码如下所示:

// Hello_Triangle.c
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
                         };
   glViewport ( 0, 0, esContext->width, esContext->height );
   glClear ( GL_COLOR_BUFFER_BIT );
   glUseProgram ( userData->programObject );
   glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
   glEnableVertexAttribArray ( 0 );
   glDrawArrays ( GL_TRIANGLES, 0, 3 );
}

Shutdown——
Shutdown回调只是通过glDeleteProgram删除之前创建的程序对象,避免内存泄漏,代码如下所示:

// Hello_Triangle.c
void Shutdown ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   glDeleteProgram ( userData->programObject );
}

4、CreateWindow

esCreateWindow——
下面介绍上面esMain函数中使用的esCreateWindow函数,主要就是通过libEGL和libX11中的API创建窗口,代码如下:

// esUtil.c
GLboolean esCreateWindow ( ESContext *esContext, const char *title, GLint width, GLint height, GLuint flags )
{
   EGLConfig config;
   EGLint majorVersion;
   EGLint minorVersion;
   // eglCreateContext函数用到的属性
   // 通过EGL_CONTEXT_CLIENT_VERSION指定了opengl es版本为3
   EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };
   if ( esContext == NULL )
   {
      return GL_FALSE;
   }
   // 设置宽和高
   esContext->width = width;
   esContext->height = height;
   // 通过WinCreate函数创建窗口
   // 稍后详细介绍WinCreate
   if ( !WinCreate ( esContext, title ) )
   {
      return GL_FALSE;
   }
   // 获取egl display
   // egl native display在上面的WinCreate函数中设置
   esContext->eglDisplay = eglGetDisplay( esContext->eglNativeDisplay );
   if ( esContext->eglDisplay == EGL_NO_DISPLAY )
   {
      return GL_FALSE;
   }
   // 初始化egl
   if ( !eglInitialize ( esContext->eglDisplay, &majorVersion, &minorVersion ) )
   {
      return GL_FALSE;
   }

   {
      // 配置egl
      EGLint numConfigs = 0;
      EGLint attribList[] =
      {
         EGL_RED_SIZE,       5,
         EGL_GREEN_SIZE,     6,
         EGL_BLUE_SIZE,      5,
         EGL_ALPHA_SIZE,     ( flags & ES_WINDOW_ALPHA ) ? 8 : EGL_DONT_CARE,
         EGL_DEPTH_SIZE,     ( flags & ES_WINDOW_DEPTH ) ? 8 : EGL_DONT_CARE,
         EGL_STENCIL_SIZE,   ( flags & ES_WINDOW_STENCIL ) ? 8 : EGL_DONT_CARE,
         EGL_SAMPLE_BUFFERS, ( flags & ES_WINDOW_MULTISAMPLE ) ? 1 : 0,
         EGL_RENDERABLE_TYPE, GetContextRenderableType ( esContext->eglDisplay ),
         EGL_NONE
      };
      if ( !eglChooseConfig ( esContext->eglDisplay, attribList, &config, 1, &numConfigs ) )
      {
         return GL_FALSE;
      }
      if ( numConfigs < 1 )
      {
         return GL_FALSE;
      }
   }
   // 创建egl window surface
   // egl native window在上面的WinCreate函数中设置
   esContext->eglSurface = eglCreateWindowSurface ( esContext->eglDisplay, config, 
                                                    esContext->eglNativeWindow, NULL );
   if ( esContext->eglSurface == EGL_NO_SURFACE )
   {
      return GL_FALSE;
   }
   // 创建egl context
   esContext->eglContext = eglCreateContext ( esContext->eglDisplay, config, 
                                              EGL_NO_CONTEXT, contextAttribs );

   if ( esContext->eglContext == EGL_NO_CONTEXT )
   {
      return GL_FALSE;
   }
   // make current
   if ( !eglMakeCurrent ( esContext->eglDisplay, esContext->eglSurface, 
                          esContext->eglSurface, esContext->eglContext ) )
   {
      return GL_FALSE;
   }

   return GL_TRUE;
}

WinCreate——
WinCreate函数创建X11窗口,同时设置egl native display和window,代码如下:

// esUtil_X11.c
static Display *x_display = NULL;
static Atom s_wmDeleteMessage;

EGLBoolean WinCreate(ESContext *esContext, const char *title)
{
    Window root;
    XSetWindowAttributes swa;
    XSetWindowAttributes  xattr;
    Atom wm_state;
    XWMHints hints;
    XEvent xev;
    Window win;

    /*
     * X11 native display initialization
     */
    x_display = XOpenDisplay(NULL);
    if ( x_display == NULL )
    {
        return EGL_FALSE;
    }

    root = DefaultRootWindow(x_display);

    swa.event_mask  =  ExposureMask | PointerMotionMask | KeyPressMask;
    win = XCreateWindow(
               x_display, root,
               0, 0, esContext->width, esContext->height, 0,
               CopyFromParent, InputOutput,
               CopyFromParent, CWEventMask,
               &swa );
    s_wmDeleteMessage = XInternAtom(x_display, "WM_DELETE_WINDOW", False);
    XSetWMProtocols(x_display, win, &s_wmDeleteMessage, 1);

    xattr.override_redirect = FALSE;
    XChangeWindowAttributes ( x_display, win, CWOverrideRedirect, &xattr );

    hints.input = TRUE;
    hints.flags = InputHint;
    XSetWMHints(x_display, win, &hints);

    // make the window visible on the screen
    XMapWindow (x_display, win);
    XStoreName (x_display, win, title);

    // get identifiers for the provided atom name strings
    wm_state = XInternAtom (x_display, "_NET_WM_STATE", FALSE);

    memset ( &xev, 0, sizeof(xev) );
    xev.type                 = ClientMessage;
    xev.xclient.window       = win;
    xev.xclient.message_type = wm_state;
    xev.xclient.format       = 32;
    xev.xclient.data.l[0]    = 1;
    xev.xclient.data.l[1]    = FALSE;
    XSendEvent (
       x_display,
       DefaultRootWindow ( x_display ),
       FALSE,
       SubstructureNotifyMask,
       &xev );

    esContext->eglNativeWindow = (EGLNativeWindowType) win;
    esContext->eglNativeDisplay = (EGLNativeDisplayType) x_display;
    return EGL_TRUE;
}

GetContextRenderableType——

EGLint GetContextRenderableType ( EGLDisplay eglDisplay )
{
#ifdef EGL_KHR_create_context
   const char *extensions = eglQueryString ( eglDisplay, EGL_EXTENSIONS );
   if ( extensions != NULL && strstr( extensions, "EGL_KHR_create_context" ) )
   {
      return EGL_OPENGL_ES3_BIT_KHR;
   }
#endif
   return EGL_OPENGL_ES2_BIT;
}

5、WinLoop

前面在main函数中提到了程序主循环WinLoop函数,在这个函数中,循环实现基于libX11的事件系统,在循环中执行我们注册的回调函数和swap buffer,代码如下:

// esUtil_X11.c
void WinLoop ( ESContext *esContext )
{
    struct timeval t1, t2;
    struct timezone tz;
    float deltatime;

    gettimeofday ( &t1 , &tz );

    while(userInterrupt(esContext) == GL_FALSE)
    {
        gettimeofday(&t2, &tz);
        deltatime = (float)(t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec) * 1e-6);
        t1 = t2;

        if (esContext->updateFunc != NULL)
            esContext->updateFunc(esContext, deltatime);
        if (esContext->drawFunc != NULL)
            esContext->drawFunc(esContext);

        eglSwapBuffers(esContext->eglDisplay, esContext->eglSurface);        
    }
}

GLboolean userInterrupt(ESContext *esContext)
{
    XEvent xev;
    KeySym key;
    GLboolean userinterrupt = GL_FALSE;
    char text;

    // Pump all messages from X server. Keypresses are directed to keyfunc (if defined)
    while ( XPending ( x_display ) )
    {
        XNextEvent( x_display, &xev );
        if ( xev.type == KeyPress )
        {
            if (XLookupString(&xev.xkey,&text,1,&key,0)==1)
            {
                if (esContext->keyFunc != NULL)
                    esContext->keyFunc(esContext, text, 0, 0);
            }
        }
        if (xev.type == ClientMessage) {
            if (xev.xclient.data.l[0] == s_wmDeleteMessage) {
                userinterrupt = GL_TRUE;
            }
        }
        if ( xev.type == DestroyNotify )
            userinterrupt = GL_TRUE;
    }
    return userinterrupt;
}
相关标签: opengl es

上一篇:

下一篇: