1. 程式人生 > 實用技巧 >【OpenGL】學習筆記#5

【OpenGL】學習筆記#5

開學軍訓啦......留給我的時間不多了!

一、幀緩衝

什麼是幀緩衝?可以理解為GPU在渲染前預先準備的一個區域,之後將把它渲染成螢幕上的畫素。但是,幀緩衝本身並不儲存資料,僅僅儲存指向資料的指標。所以,幀緩衝需要繫結幾個緩衝區,我們特殊地稱它們為附件:顏色附件,深度緩衝附件,模板緩衝附件。需要注意的是,一個完整的幀緩衝必須包括一個顏色附件。

除了這種分類之外,附件還可以分為紋理附件和渲染緩衝物件(RBO,Render Buffer Object)。其中,紋理附件就是顏色附件,RBO分為深度緩衝附件和模板緩衝附件。讓我們來列一個樹形圖。

幀緩衝--[紋理附件]--顏色附件

     |

     [渲染緩衝附件]--深度緩衝附件

           |

           --模板緩衝附件

為什麼要講到幀緩衝呢?因為幀緩衝是後期處理相當重要的部分。例如陰影,模糊,反相等後期處理都要依靠幀緩衝來實現。打個比方,幀緩衝就好像是拍出來的一張照片,可以讓我們PS。

此處的PS便是著色器。OpenGL提供了可以讓我們編輯的幀緩衝,好偉大!我們可以把一張幀緩衝儲存的2D紋理或深度緩衝獲得為一個控制代碼,在後面直接使用,塞進著色器裡。

說了這麼多,來看看程式碼吧,讓我們以用著色器編輯一個幀緩衝為例子:

首先生成並繫結一個幀緩衝:

GLuint FramebufferName = 0;
    glGenFramebuffers(
1, &FramebufferName); glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

生成一個紋理:

GLuint renderedTexture;
    glGenTextures(1, &renderedTexture);

狀態機不解釋:

glBindTexture(GL_TEXTURE_2D, renderedTexture);

由於幀緩衝必須包含一個顏色附件,所以接下來對之前繫結的紋理填充一個空的影象(最後一個引數0代表空,empty):

glTexImage2D(GL_TEXTURE_2D, 0
,GL_RGB, windowWidth, windowHeight, 0,GL_RGB, GL_UNSIGNED_BYTE, 0);

可  憐  的  過  濾(效能upup),設定過濾模式:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

由於我們要渲染一個3D模型,所以要進行深度測試,所以來生成並繫結一個渲染緩衝區,將要作為我們的RBO:

GLuint depthrenderbuffer;
    glGenRenderbuffers(1, &depthrenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, depthrenderbuffer);

由於OpenGL只知道這是個渲染緩衝區,對其中的資料格式和大小全然不知,所以我們得告訴它,使用glRenderbufferStorage來指定RBO的資料格式和大小,此處指定為深度緩衝區:

glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, windowWidth, windowHeight);

對於此函式,第一個引數必須填GL_RENDERBUFFER,第二個指定了型別為深度緩衝,還可以是GL_RGB,GL_RGBA,剩下的兩個引數指定了資料長寬,這裡設定為螢幕大小。

接下來開始為幀緩衝繫結附件,將此深度緩衝繫結為深度附件:

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthrenderbuffer);

第二個引數指定了型別為深度附件。

然後繫結紋理附件,這個比較特殊,先看程式碼:

glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, renderedTexture, 0);

可以看到,第二個引數並不是指定的型別,而是GL_COLOR_ATTACHMENT0,這意味著什麼呢,這意味著顏色附件可以有多個,0代表的是顏色附件的位置。同時我們也發現RBO只能有一個,因為它直接指定了型別。

所以,我們就需要指定接下來渲染時需要渲染到哪些顏色附件上,這次我們只渲染到GL_COLOR_ATTACHMENT0上:

GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0};
    glDrawBuffers(1, DrawBuffers);

不需要多說了吧。

終於,幀緩衝的前置操作完成了。這時需要檢查你的幀緩衝是否完整,否則會有很可怕的錯誤:

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        return false;

經常check是好習慣呦~

由於我們要把幀緩衝的影象經過處理繪製到螢幕上,所以一會渲染的時候將要分兩個步驟:第一步,按照往常的操作渲染,但是在渲染前把渲染目標繫結到我們自定義的幀緩衝上,此為離屏渲染。然後,通過顏色附件獲得渲染得到的深度緩衝和紋理,再把它們傳到著色器裡處理,作為2D紋理渲染到一個螢幕大小的四邊形上。

那麼,下面開始定義四邊形:

static const GLfloat g_quad_vertex_buffer_data[] = { 
        -1.0f, -1.0f, 0.0f,
         1.0f, -1.0f, 0.0f,
        -1.0f,  1.0f, 0.0f,
        -1.0f,  1.0f, 0.0f,
         1.0f, -1.0f, 0.0f,
         1.0f,  1.0f, 0.0f,
    };

    GLuint quad_vertexbuffer;
    glGenBuffers(1, &quad_vertexbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data), g_quad_vertex_buffer_data, GL_STATIC_DRAW);

定義頂點緩衝,裡面包括的是一個2D充滿螢幕的四邊形的頂點,注意此時頂點依然是3維的格式,只不過Z=0,相當於2D。

好了,一切都準備好了,下面進入主迴圈:

首先把渲染目標繫結到我們定義的幀緩衝裡:

glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

這裡是渲染操作...

glUseProgram(programID);

(這裡使用通常的著色器)

這裡是渲染操作...

然後重頭戲來了,開始渲染四邊形,先把渲染目標繫結到預設幀緩衝:這個幀緩衝將會直接被GPU提取渲染:

glBindFramebuffer(GL_FRAMEBUFFER, 0);

這裡是渲染操作...

glUseProgram(quad_programID);

(使用第二個著色器,這個著色器只負責渲染一個2D紋理並稍稍處理)

這裡是渲染操作...

怎麼樣,這樣就完成了呢,小朋友們學會了嗎?(狗頭)

咳咳,說了這麼多,第二個著色器長啥樣呢,僅僅渲染一個2D紋理的著色器,來看吧:

#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;

// Output data ; will be interpolated for each fragment.
out vec2 UV;

void main(){
    gl_Position =  vec4(vertexPosition_modelspace,1);
    UV = (vertexPosition_modelspace.xy+vec2(1,1))/2.0;
}

出人意料的簡單呢......僅僅傳出一個UV座標。不過,這裡要注意,我們的頂點座標xy的範圍是[-1,1],而UV座標uv的範圍是[0,1],所以需要(xy+(1,1))/2來轉換。

看片元著色器:

#version 330 core

in vec2 UV;

out vec3 color;

uniform sampler2D renderedTexture;
uniform float time;

void main(){
    color = texture( renderedTexture, UV + 0.005*vec2( sin(time+1024.0*UV.x),cos(time+768.0*UV.y)) ).xyz ;
}

用時間來做一個偏移量,不多說了吧。

那麼看看結果吧:

畫素會隨著時間的變化慢慢移動喔!