【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 ; }
用時間來做一個偏移量,不多說了吧。
那麼看看結果吧:
畫素會隨著時間的變化慢慢移動喔!