由于《OpenGL ES 2.0 Programming Guide》原书第12章并没有提供相关的示例,为了加深理解,遂自己实现了一份C语言版本作为练习,希望能够帮助到同样喜欢OpenGL ES 2.0的同学。
在实现的时候遇到的问题——FBO的 Ping Pong技术:
文章提到有三种可能的方法
由于ES 2.0的 Frame Buffer 只支持一个Color Attachement,所以不能使用OpenGL 的 Multi-Attachment的方式(速度最快的方法3),然后尝试方法2,通过实时切换FBO 绑定的Color Attachement,但是没有成功(如果有人成功的话,希望指点一下),所以只好用了“最慢”的多FBO的方式(方法1)。
废话不多说,直接上代码(本例使用了2个Pass):
#include <stdlib.h> #include <stdio.h> #include "esUtil.h" #include "utils.h" #include "userData.h" #define SIZE 512 GLint InitFbo(ESContext *esContext, GLint width, GLint height, GLuint *pFrameBuffer, GLuint *pTextureId, GLuint *pDepthRenderBuffer) { GLenum status; GLint maxRenderbufferSize; UserData *userData = (UserData *)esContext->userData; glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxRenderbufferSize); // check if GL_MAX_RENDERBUFFER_SIZE is >= texWidth and texHeight if ((maxRenderbufferSize <= width) || (maxRenderbufferSize <= height)) { // cannot use framebuffer objects as we need to create // a depth buffer as a renderbuffer object printf("Cannot use framebuffer objects!\n"); exit(EXIT_FAILURE); return FALSE; } // generate the framebuffer, renderbuffer names glGenFramebuffers(1, pFrameBuffer); glGenRenderbuffers(1, pDepthRenderBuffer); // bind renderbuffer and create a 16-bit depth buffer // width and height of renderbuffer = width and height of // the texture glBindRenderbuffer(GL_RENDERBUFFER, *pDepthRenderBuffer); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height); // Dst Texture glGenTextures(1, pTextureId); glBindTexture(GL_TEXTURE_2D, *pTextureId); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); // bind the framebuffer glBindFramebuffer(GL_FRAMEBUFFER, *pFrameBuffer); // ☆ specify texture as color attachment ☆ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *pTextureId, 0); // specify depth_renderbufer as depth attachment glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, *pDepthRenderBuffer); // check for framebuffer complete status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { printf("Framebuffer object is not complete!\n"); exit(EXIT_FAILURE); return FALSE; } return TRUE; } GLint InitFboShader(ESContext *esContext, const char* vFboShader, const char* fFboShader, GLuint* pProgramObj, GLint* pPositionLoc, GLint* pTexcoordLoc, GLint* pSamplerLoc) { UserData *userData = (UserData *)esContext->userData; const char *vShaderStr = NULL, *fShaderStr = NULL; vShaderStr = (const char *)ReadShader(vFboShader); fShaderStr = (const char *)ReadShader(fFboShader); *pProgramObj = esLoadProgram(vShaderStr, fShaderStr); *pPositionLoc = glGetAttribLocation (*pProgramObj, "a_position" ); *pTexcoordLoc = glGetAttribLocation(*pProgramObj, "a_texCoord"); *pSamplerLoc = glGetUniformLocation(*pProgramObj, "s_texture"); return TRUE; } GLint InitFboVbo(ESContext *esContext, GLuint* pVboFboIds) { UserData *userData = (UserData *)esContext->userData; // VBO Id pVboFboIds[0] = 0; pVboFboIds[1] = 0; // Quad const GLfloat vVertices[] = { -1.f, 1.f, 0.0f, // Position 0 0.0f, 0.0f, // TexCoord 0 -1.f, -1.f, 0.0f, // Position 1 0.0f, 1.0f, // TexCoord 1 1.f, -1.f, 0.0f, // Position 2 1.0f, 1.0f, // TexCoord 2 1.f, 1.f, 0.0f, // Position 3 1.0f, 0.0f // TexCoord 3 }; const GLushort indices[] = { 0, 1, 2, 0, 2, 3 }; const int numIndices = 6; const int numVertices = 4; // Setup VBO glGenBuffers ( 2, pVboFboIds ); glBindBuffer ( GL_ARRAY_BUFFER, pVboFboIds[0] ); glBufferData ( GL_ARRAY_BUFFER, (3 + 2) * sizeof(GLfloat) * numVertices, vVertices, GL_STATIC_DRAW ); glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, pVboFboIds[1] ); glBufferData ( GL_ELEMENT_ARRAY_BUFFER, numIndices * sizeof(GLushort), indices, GL_STATIC_DRAW ); return TRUE; } GLint InitShader(ESContext *esContext) { UserData *userData = (UserData *)esContext->userData; const char *vShaderStr = NULL, *fShaderStr = NULL; vShaderStr = (const char *)ReadShader("shaders/vertex_shader.glsl"); fShaderStr = (const char *)ReadShader("shaders/fragment_shader.glsl"); // Load the shaders and get a linked program object userData->programObject = esLoadProgram ( vShaderStr, fShaderStr ); // Get the attribute locations userData->positionLoc = glGetAttribLocation ( userData->programObject, "a_position" ); // Get the uniform locations userData->mvpLoc = glGetUniformLocation( userData->programObject, "u_mvpMatrix" ); // Get the texture attribute locations userData->texcoordLoc = glGetAttribLocation(userData->programObject, "a_texCoord"); userData->samplerLoc = glGetUniformLocation(userData->programObject, "s_texture"); return TRUE; } GLint InitVbo(ESContext *esContext) { UserData *userData = (UserData *)esContext->userData; // VBO Id userData->vboIds[0] = 0; userData->vboIds[1] = 0; userData->vboIds[2] = 0; const int numVertices = 24; // Setup VBO glGenBuffers ( 3, userData->vboIds ); glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[0] ); glBufferData ( GL_ARRAY_BUFFER, 3 * sizeof(GLfloat) * numVertices, userData->vertices, GL_STATIC_DRAW ); glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[1] ); glBufferData ( GL_ARRAY_BUFFER, 2 * sizeof(GLfloat) * numVertices, userData->texcoords, GL_STATIC_DRAW ); glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[2] ); glBufferData ( GL_ELEMENT_ARRAY_BUFFER, userData->numIndices * sizeof(GLuint), userData->indices, GL_STATIC_DRAW ); return TRUE; } GLint Init(ESContext *esContext) { UserData *userData = (UserData *)esContext->userData; // Load the texture ☆ userData->textureFBOId = LoadTexture("../beard.tga"); // Pass 0 if (!InitFboShader(esContext, "shaders/vertex_fbo_shader.glsl", "shaders/fragment_fbo_shader.glsl", &userData->programFBOObject, &userData->positionFBOLoc, &userData->texcoordFBOLoc, &userData->samplerFBOLoc)) { printf("InitFboShader exception ! \n"); return FALSE; } // Pass 1 if (!InitFboShader(esContext, "shaders/vertex_fbo_shader_.glsl", "shaders/fragment_fbo_shader_.glsl", &userData->programFBOObject_, &userData->positionFBOLoc_, &userData->texcoordFBOLoc_, &userData->samplerFBOLoc_)) { printf("InitFboShader exception ! \n"); return FALSE; } // -------- if (!InitShader(esContext)) { printf("InitShader exception ! \n"); return FALSE; } // -------- // Pass 0 InitFbo(esContext, SIZE, SIZE, &userData->frameBuffer, &userData->textureFBOId_, // dst texture 0 &userData->depthRenderBuffer); InitFboVbo(esContext, userData->vboFBOIds); // Pass 1 InitFbo(esContext, SIZE, SIZE, &userData->frameBuffer_, &userData->textureId, // dst texture 1 &userData->depthRenderBuffer_); InitFboVbo(esContext, userData->vboFBOIds_); // Generate the vertex data userData->numIndices = esGenCube( 1.0, &userData->vertices, NULL, &userData->texcoords, &userData->indices ); InitVbo(esContext); // Starting rotation angle for the cube userData->angle = 45.0f; glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f ); glClearDepthf( 1.0f ); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); //glEnable(GL_BLEND); //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); return TRUE; } // Update the mvp matrix void Update(ESContext *esContext, float deltaTime) { UserData *userData = (UserData *) esContext->userData; ESMatrix perspective; ESMatrix modelview; float aspect; // Compute a rotation angle based on time to rotate the cube userData->angle += ( deltaTime * 40.0f ); if( userData->angle >= 360.0f ) userData->angle -= 360.0f; // Compute the window aspect ratio aspect = (GLfloat) esContext->width / (GLfloat) esContext->height; // Generate a perspective matrix with a 60 degree FOV esMatrixLoadIdentity( &perspective ); esPerspective( &perspective, 60.0f, aspect, 1.0f, 20.0f ); // Generate a model view matrix to rotate/translate the cube esMatrixLoadIdentity( &modelview ); // Translate away from the viewer esTranslate( &modelview, 0.0, 0.0, -2.0 ); // Rotate the cube esRotate( &modelview, userData->angle, 1.0, 0.0, 1.0 ); // Compute the final MVP by multiplying the // modevleiw and perspective matrices together esMatrixMultiply( &userData->mvpMatrix, &modelview, &perspective ); } void DrawToFbo(ESContext *esContext, GLuint frameBuffer, GLuint programObj, GLuint texId, GLuint* pVboIds, GLint positionLoc, GLint texcoordLoc, GLint samplerLoc) { UserData *userData = (UserData *)esContext->userData; const int numIndices = 6; glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); // ---------------------------------------------------------- PrepareForDraw(esContext, programObj); // ----------------------------------------------------------- // Load the vertex position glBindBuffer ( GL_ARRAY_BUFFER, pVboIds[0] ); glVertexAttribPointer ( positionLoc, 3, GL_FLOAT, GL_FALSE, (3 + 2) * sizeof(GLfloat), 0 ); glEnableVertexAttribArray ( positionLoc ); // ----------------------------------------------------------- // Load the texture coordinate glBindBuffer ( GL_ARRAY_BUFFER, pVboIds[0] ); glVertexAttribPointer ( texcoordLoc, 2, GL_FLOAT, GL_FALSE, (3 + 2) * sizeof(GLfloat), (void *)(3 * sizeof(GLfloat))); glEnableVertexAttribArray(texcoordLoc); // ----------------------------------------------------------- // Bind the FBO texture // Set the sampler texture unit to 0 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texId); glUniform1i(samplerLoc, 0); // ----------------------------------------------------------- // Draw the quad glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, pVboIds[1] ); glDrawElements ( GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, 0 ); // ----------------------------------------------------------- // Clean glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer ( GL_ARRAY_BUFFER, 0 ); glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, 0 ); glDisableVertexAttribArray ( positionLoc ); glDisableVertexAttribArray ( texcoordLoc ); } void Draw(ESContext *esContext) { UserData *userData = (UserData *)esContext->userData; // ---------------------------------------------------------- // Pass 0 DrawToFbo(esContext, userData->frameBuffer, userData->programFBOObject, userData->textureFBOId, userData->vboFBOIds, userData->positionFBOLoc, userData->texcoordFBOLoc, userData->samplerFBOLoc); // Pass 1 DrawToFbo(esContext, userData->frameBuffer_, userData->programFBOObject_, userData->textureFBOId_, userData->vboFBOIds_, userData->positionFBOLoc_, userData->texcoordFBOLoc_, userData->samplerFBOLoc_); // ---------------------------------------------------------- glBindFramebuffer(GL_FRAMEBUFFER, 0); // ---------------------------------------------------------- PrepareForDraw(esContext, userData->programObject); // ---------------------------------------------------------- // Load the vertex position glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[0] ); glVertexAttribPointer ( userData->positionLoc, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), 0 ); glEnableVertexAttribArray ( userData->positionLoc ); // ----------------------------------------------------------- // Load the texture coordinate glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[1] ); glVertexAttribPointer(userData->texcoordLoc, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0); glEnableVertexAttribArray(userData->texcoordLoc); // ----------------------------------------------------------- // Load the MVP matrix glUniformMatrix4fv( userData->mvpLoc, 1, GL_FALSE, (GLfloat *) &userData->mvpMatrix.m[0][0] ); // ----------------------------------------------------------- // Bind the new texture // Set the sampler texture unit to 0 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, userData->textureId); glUniform1i(userData->samplerLoc, 0); // ----------------------------------------------------------- // Draw the cube glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[2] ); glDrawElements ( GL_TRIANGLES, userData->numIndices, GL_UNSIGNED_INT, 0 ); eglSwapBuffers ( esContext->eglDisplay, esContext->eglSurface ); // ----------------------------------------------------------- // Clean glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer ( GL_ARRAY_BUFFER, 0 ); glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, 0 ); glDisableVertexAttribArray ( userData->positionLoc ); glDisableVertexAttribArray ( userData->texcoordLoc ); } void ShutDown(ESContext *esContext) { UserData *userData = (UserData *)esContext->userData; //glDisable(GL_BLEND); if ( userData->vertices != NULL ) { free ( userData->vertices ); } if ( userData->texcoords != NULL ) { free ( userData->texcoords ); } if ( userData->indices != NULL ) { free ( userData->indices ); } // Delete program object glDeleteProgram (userData->programObject); glDeleteProgram(userData->programFBOObject); // Delete texture glDeleteTextures(1, &userData->textureId); glDeleteTextures(1, &userData->textureFBOId); glDeleteRenderbuffers(1, &userData->depthRenderBuffer); glDeleteFramebuffers(1, &userData->frameBuffer); // Delete VBO glDeleteBuffers ( 2, userData->vboFBOIds ); glDeleteBuffers ( 3, userData->vboIds ); } int main ( int argc, char *argv[] ) { ESContext esContext; UserData userData; esInitContext ( &esContext ); esContext.userData = &userData; esCreateWindow ( &esContext, "Simple Multi Pass", SIZE, SIZE, ES_WINDOW_RGB | ES_WINDOW_MULTISAMPLE ); if ( !Init ( &esContext ) ) return 0; esRegisterDrawFunc ( &esContext, Draw ); esRegisterUpdateFunc ( &esContext, Update ); esMainLoop ( &esContext ); ShutDown ( &esContext ); }
效果图: