本文是關(guān)于OpenGL ES的系統(tǒng)性學習過程瓶籽,記錄了自己在學習OpenGL ES時的收獲恃鞋。
這篇文章的作用是學習的OpenGL ES幀緩存FBO技術(shù)崖媚,實例是利用FBO技術(shù)對紋理進行多次疊加濾鏡。
環(huán)境是Xcode8.1+OpenGL ES 2.0
目前代碼已經(jīng)放到github上面恤浪,OpenGL ES入門12-幀緩存
歡迎關(guān)注我的 OpenGL ES入門專題
實現(xiàn)效果
幀緩存
幀緩沖存儲器(Frame Buffer):簡稱幀緩存或顯存畅哑,它是屏幕所顯示畫面的一個直接映象,又稱為位映射圖(Bit Map)或光柵水由。幀緩存的每一存儲單元對應屏幕上的一個像素荠呐,整個幀緩存對應一幀圖像。
建構(gòu)一個完整的幀緩存必須滿足以下條件:
我們必須往里面加入至少一個附件(顏色绷杜、深度、模板緩存)濒募。
其中至少有一個是顏色附件鞭盟。
所有的附件都應該是已經(jīng)完全做好的(已經(jīng)存儲在內(nèi)存之中)。
每個緩存都應該有同樣數(shù)目的樣本瑰剃。
- 創(chuàng)建一個幀緩存對象
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視圖產(chǎn)生影響。 如果后續(xù)我們又申請了另外的幀緩存對象晌姚,并且激活了該緩存對象粤剧,所有渲染操作將渲染到當前綁定的幀緩存的附加緩存中,由于我們的幀緩存不是默認的幀緩存挥唠,渲染命令對窗口的視頻輸出不會產(chǎn)生任何影響抵恋。出于這個原因,它被稱為離屏渲染(off-screen rendering)宝磨,就是渲染到一個另外的緩存中弧关,而不是默認的緩存。
紋理附件
當把一個紋理附加到幀緩存上的時候唤锉,所有渲染命令會寫入到紋理上世囊,就像它是一個普通的顏色、深度或者模板緩存一樣窿祥。使用紋理的好處是株憾,所有渲染操作的結(jié)果都會被儲存為一個紋理圖像,這樣我們就可以簡單的在著色器中使用了晒衩。
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所管理的一處高效的內(nèi)存區(qū)域嗤瞎,它可以存儲格式化的圖像數(shù)據(jù)墙歪。渲染緩存中的數(shù)據(jù)只有關(guān)聯(lián)到一個幀緩存對象之后才有意義,并且需要保證圖像緩存的格式需要與OpenGL要求的渲染格式相符猫胁,不能將顏色值渲染到深度緩存中箱亿。和紋理圖像一樣,渲染緩存對象也是一個緩存弃秆,它可以是一堆字節(jié)届惋、整數(shù)、像素或者其他東西菠赚。渲染緩存對象的一大優(yōu)點是脑豹,它以O(shè)penGL原生渲染格式儲存它的數(shù)據(jù),因此在離屏渲染到幀緩存的時候衡查,這些數(shù)據(jù)就相當于被優(yōu)化過的了瘩欺。然而,渲染緩存對象通常是只寫的拌牲,不能修改它們(就像獲取紋理俱饿,不能寫入紋理一樣)∷觯可以用 glReadPixels 函數(shù)去讀取拍埠,函數(shù)返回一個當前綁定的幀緩存的特定像素區(qū)域,而不是直接返回附件本身土居。
- 創(chuàng)建渲染緩存對象
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 | 特殊的附件類型枣购,用于保存壓縮后的深度-模板緩存 |
實現(xiàn)過程
- 創(chuàng)建著色器程序
- (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);
}
- 創(chuàng)建幀緩存
- (void)setupFrameAndRenderBuffer
{
// 窗口默認幀緩存
glGenRenderbuffers(1, &_colorRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
// 為 color renderbuffer 分配存儲空間
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
glGenFramebuffers(1, &_frameBuffer);
// 設(shè)置為當前 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);
// 設(shè)置為當前 framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer1);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture1, 0);
}
- 創(chuàng)建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 // 左上
};
// 創(chuàng)建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 // 左上
};
// 創(chuàng)建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);
// 索引數(shù)組
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);
// 索引數(shù)組
unsigned int indices[] = {0,1,2,3,2,0};
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices);
//將指定 renderbuffer 呈現(xiàn)在屏幕上擦耀,在這里我們指定的是前面已經(jīng)綁定為當前 renderbuffer 的那個棉圈,在 renderbuffer 可以被呈現(xiàn)之前,必須調(diào)用renderbufferStorage:fromDrawable: 為之分配存儲空間眷蜓。
[_context presentRenderbuffer:GL_RENDERBUFFER];
}