本文是關于OpenGL ES的系統性學習過程,記錄了自己在學習OpenGL ES時的收獲。
這篇文章的作用是學習的OpenGL ES幀緩存FBO技術,實例是利用FBO技術對紋理進行多次疊加濾鏡。
環境是Xcode8.1+OpenGL ES 2.0
目前代碼已經放到github上面,OpenGL ES入門12-幀緩存
歡迎關注我的 OpenGL ES入門專題
實現效果
幀緩存
幀緩沖存儲器(Frame Buffer):簡稱幀緩存或顯存,它是屏幕所顯示畫面的一個直接映象,又稱為位映射圖(Bit Map)或光柵。幀緩存的每一存儲單元對應屏幕上的一個像素,整個幀緩存對應一幀圖像。
建構一個完整的幀緩存必須滿足以下條件:
我們必須往里面加入至少一個附件(顏色、深度、模板緩存)。
其中至少有一個是顏色附件。
所有的附件都應該是已經完全做好的(已經存儲在內存之中)。
每個緩存都應該有同樣數目的樣本。
- 創建一個幀緩存對象
void glGenRenderbuffers (GLsizei n, GLuint* renderbuffers)
- 綁定幀緩存
void glBindRenderbuffer (GLenum target, GLuint renderbuffer)
- 刪除幀緩存對象
void glDeleteFramebuffers (GLsizei n, const GLuint* framebuffers)
- 對當前的幀緩存對象進行檢查
glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE
- 離屏渲染
當我們希望圖像渲染到iOS視圖上的時候,我們需要申請一個幀緩存對象用來讓所有的渲染操作對iOS視圖產生影響。 如果后續我們又申請了另外的幀緩存對象,并且激活了該緩存對象,所有渲染操作將渲染到當前綁定的幀緩存的附加緩存中,由于我們的幀緩存不是默認的幀緩存,渲染命令對窗口的視頻輸出不會產生任何影響。出于這個原因,它被稱為離屏渲染(off-screen rendering),就是渲染到一個另外的緩存中,而不是默認的緩存。
紋理附件
當把一個紋理附加到幀緩存上的時候,所有渲染命令會寫入到紋理上,就像它是一個普通的顏色、深度或者模板緩存一樣。使用紋理的好處是,所有渲染操作的結果都會被儲存為一個紋理圖像,這樣我們就可以簡單的在著色器中使用了。
GLuint createTexture2D(GLenum format, int width, int height, void *data)
{
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
紋理附加到幀緩存上:
void glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)
渲染緩存
渲染緩存是OpenGL所管理的一處高效的內存區域,它可以存儲格式化的圖像數據。渲染緩存中的數據只有關聯到一個幀緩存對象之后才有意義,并且需要保證圖像緩存的格式需要與OpenGL要求的渲染格式相符,不能將顏色值渲染到深度緩存中。和紋理圖像一樣,渲染緩存對象也是一個緩存,它可以是一堆字節、整數、像素或者其他東西。渲染緩存對象的一大優點是,它以OpenGL原生渲染格式儲存它的數據,因此在離屏渲染到幀緩存的時候,這些數據就相當于被優化過的了。然而,渲染緩存對象通常是只寫的,不能修改它們(就像獲取紋理,不能寫入紋理一樣)。可以用 glReadPixels 函數去讀取,函數返回一個當前綁定的幀緩存的特定像素區域,而不是直接返回附件本身。
- 創建渲染緩存對象
void glGenRenderbuffers (GLsizei n, GLuint* renderbuffers)
- 綁定渲染緩存對象
void glBindRenderbuffer (GLenum target, GLuint renderbuffer)
- 為渲染緩存對象分配存儲空間
void glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)
- 渲染緩存對象附加到幀緩存
void glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
幀緩存附件
下表是常見的幀緩存附件
附件名稱 | 描述 |
---|---|
GL_COLOR_ATTACHMENTi | 第i個顏色緩存 |
GL_DEPTH_ATTACHMENT | 深度緩存 |
GL_STENCIL_ATTACHMENT | 模板緩存 |
GL_DEPTH_STENCIL_ATTACHMENT | 特殊的附件類型,用于保存壓縮后的深度-模板緩存 |
實現過程
- 創建著色器程序
- (void)setupGLProgram
{
NSString *vertFile1 = [[NSBundle mainBundle] pathForResource:@"vert.glsl" ofType:nil];
NSString *fragFile1 = [[NSBundle mainBundle] pathForResource:@"mosaic.glsl" ofType:nil];
_program = createGLProgramFromFile(vertFile1.UTF8String, fragFile1.UTF8String);
}
- (void)setupGLProgram1
{
NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"vert.glsl" ofType:nil];
NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"frag.glsl" ofType:nil];
_program1 = createGLProgramFromFile(vertFile.UTF8String, fragFile.UTF8String);
}
- 創建幀緩存
- (void)setupFrameAndRenderBuffer
{
// 窗口默認幀緩存
glGenRenderbuffers(1, &_colorRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
// 為 color renderbuffer 分配存儲空間
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
glGenFramebuffers(1, &_frameBuffer);
// 設置為當前 framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
// 將 _colorRenderBuffer 裝配到 GL_COLOR_ATTACHMENT0 這個裝配點上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, _colorRenderBuffer);
}
- (void)setupFrameBuffer1
{
_texture1 = createTexture2D(GL_RGBA, self.frame.size.width, self.frame.size.height, NULL);
glGenFramebuffers(1, &_frameBuffer1);
// 設置為當前 framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer1);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture1, 0);
}
- 創建VBO
- (void)setupVBO
{
_vertCount = 6;
GLfloat vertices[] = {
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, 1.0f, 1.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // 左下
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f // 左上
};
// 創建VBO
_vbo = createVBO(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(vertices), vertices);
glEnableVertexAttribArray(glGetAttribLocation(_program, "position"));
glVertexAttribPointer(glGetAttribLocation(_program, "position"), 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
glEnableVertexAttribArray(glGetAttribLocation(_program, "texcoord"));
glVertexAttribPointer(glGetAttribLocation(_program, "texcoord"), 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL+sizeof(GL_FLOAT)*3);
}
- (void)setupVBO1
{
_vertCount = 6;
GLfloat vertices[] = {
1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // 右上
1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // 右下
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // 左下
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上
};
// 創建VBO
_vbo1 = createVBO(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(vertices), vertices);
glEnableVertexAttribArray(glGetAttribLocation(_program1, "position"));
glVertexAttribPointer(glGetAttribLocation(_program1, "position"), 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
glEnableVertexAttribArray(glGetAttribLocation(_program1, "texcoord"));
glVertexAttribPointer(glGetAttribLocation(_program1, "texcoord"), 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL+sizeof(GL_FLOAT)*3);
}
- 渲染(先將馬賽克效果渲染到幀緩存,然后將幀緩存以紋理的形式上傳到另一著色器程序中進行灰度渲染)
- (void)render
{
/************************離屏渲染********************************************/
glUseProgram(_program1);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer1);
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glLineWidth(2.0);
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
[self setupVBO1];
[self setupTexure];
// 激活紋理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _texture);
glUniform1i(glGetUniformLocation(_program1, "image"), 0);
// 索引數組
unsigned int indices1[] = {0,1,2,3,2,0};
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices1);
/************************當前屏幕渲染********************************************/
glUseProgram(_program);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glLineWidth(2.0);
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
[self setupVBO];
// 激活紋理
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, _texture1);
glUniform1i(glGetUniformLocation(_program, "image"), 1);
// 索引數組
unsigned int indices[] = {0,1,2,3,2,0};
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices);
//將指定 renderbuffer 呈現在屏幕上,在這里我們指定的是前面已經綁定為當前 renderbuffer 的那個,在 renderbuffer 可以被呈現之前,必須調用renderbufferStorage:fromDrawable: 為之分配存儲空間。
[_context presentRenderbuffer:GL_RENDERBUFFER];
}