【OpenGL ES】Hello Triangle

来源:互联网 发布:tgp优化dnf 编辑:程序博客网 时间:2024/06/18 13:38

像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.cint 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;}

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

这里写图片描述

2、ESContext

// esUtil.hstruct 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 callbackvoid esRegisterDrawFunc ( ESContext *esContext, void ( *drawFunc ) ( ESContext * ) ){   esContext->drawFunc = drawFunc;}// 注册shutdown callbackvoid esRegisterShutdownFunc ( ESContext *esContext, void ( *shutdownFunc ) ( ESContext * ) ){   esContext->shutdownFunc = shutdownFunc;}// 注册update callbackvoid esRegisterUpdateFunc ( ESContext *esContext, void ( *updateFunc ) ( ESContext *, float ) ){   esContext->updateFunc = updateFunc;}// 注册key callbackvoid ESUTIL_API esRegisterKeyFunc ( ESContext *esContext,                                    void ( *keyFunc ) ( ESContext *, unsigned char, int, int ) ){   esContext->keyFunc = keyFunc;}

3、HelloTriangle

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

// Hello_Triangle.ctypedef 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.cint 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.cGLuint 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.cvoid 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.cvoid Shutdown ( ESContext *esContext ){   UserData *userData = esContext->userData;   glDeleteProgram ( userData->programObject );}

4、CreateWindow

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

// esUtil.cGLboolean 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.cstatic 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.cvoid 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;}
1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 手机不支持微信运动怎么办 淘宝虚拟商品买家退货退款怎么办 虚拟品申请啦退货退款怎么办 淘宝充值话费没到账怎么办 淘宝全球购买到假货怎么办 车跑路上没油了怎么办 摩托车跑路上没油了怎么办 话费充了不到帐怎么办 网上代充被骗了怎么办 天猫买东西没积分怎么办 购物时不要天猫积分怎么办 618没有天猫积分怎么办 话费充错了号码怎么办? 微信被骗充话费怎么办 微信话费充多了怎么办 睫毛烫的太卷了怎么办 烫完睫毛太卷了怎么办 烫睫毛太卷了怎么办 用微信充话费充错了怎么办 微信给空号充话费了怎么办 微信充话费充错号码是空号怎么办 淘宝充流量不到账怎么办 微信退货不退款怎么办 京东话费交错号怎么办? 微信缴费错了怎么办 给手机充话费被退款怎么办 买到假货淘宝商家已关店怎么办 手机刷错系统了怎么办 苹果手机成砖了怎么办 苹果6p变砖头怎么办 苹果刷成石头了怎么办 苹果手机更新成了砖头怎么办 京东售后好慢怎么办 京东商品超过售后期怎么办 京东过了售后期怎么办 京东售后不处理怎么办 京东售后不让退货怎么办 天猫盒子遥控器丢了怎么办 淘宝店铺的客服不理人怎么办 淘宝假货下架了怎么办 淘宝不让发布本地生活服务了怎么办