由于《OpenGL ES 2.0 Programming Guide》原书第12章并没有提供相关的示例,为了加深理解,遂自己实现了一份C语言版本作为练习,希望能够帮助到同样喜欢OpenGL ES 2.0的同学。
在实现的时候遇到的问题——FBO的 Ping Pong技术:
文章提到有三种可能的方法
1. Use one FBO with one attachment per texture that is rendered to, and bind a different FBO in each rendering pass using glBindFramebufferEXT().
2. Use one FBO, reattach the render target texture in each pass using glFramebufferTexture2DEXT().
3. Use one FBO and multiple attachment points, switch using glDrawBuffer().
由于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 );
}
效果图: