转 OpenGL核心技术之帧缓冲
首先給大家分享一個巨牛的人工智能教程,是我無意中發現的。教程不僅是零基礎,通俗易懂,而且非常風趣幽默,還時不時有內涵段子,像看小說一樣,哈哈~我正在學習中,覺得太牛了,所以分享給大家!點這里可以跳轉到教程
筆者介紹:姜雪偉,IT公司技術合伙人,IT高級講師,CSDN社區專家,特邀編輯,暢銷書作者,國家專利發明人;已出版書籍:《手把手教你架構3D游戲引擎》電子工業出版社和《Unity3D實戰核心技術詳解》電子工業出版社等。
CSDN視頻網址:http://edu.csdn.net/lecturer/144
本篇博文主要是給讀者解密關于游戲后處理渲染效果的原理,后處理渲染效果在Unity,UE4虛幻引擎等商業引擎 使用的非常多,
比如Bloom,Blur,SSAO,PSSM,HDR等等都屬于后處理渲染效果,它們的實現其實就是應用幀緩沖技術實現的,本篇博文主要是
圍繞幀緩沖給讀者介紹其實現原理以及應用案例。
在前面給讀者介紹了幾種不同的屏幕緩沖:用于寫入顏色值的顏色緩沖,用于寫入深度信息的深度緩沖,以及允許我們基于一些條件丟棄指定片段的模板緩沖。本篇博客主要是給讀者介紹幀緩沖,什么是幀緩沖?其實就是把前面介紹的這幾種緩沖結合起來叫做幀緩沖(Framebuffer),它被儲存于內存中。OpenGL給了我們自己定義幀緩沖的自由,我們可以選擇性的定義自己的顏色緩沖、深度和模板緩沖。本篇博客主要是給讀者介紹幀緩沖,我們前面介紹的渲染操作都是在默認的幀緩沖之上進行的,當你創建了你的窗口的時候默認幀緩沖就被創建和配置好了,通過創建我們自己的幀緩沖我們能夠獲得一種額外的渲染方式。通過幀緩沖可以將你的場景渲染到一個不同的幀緩沖中,可以使我們能夠在場景中創建鏡子這樣的效果,或者做出一些炫酷的特效。首先我們會討論它們是如何工作的,然后我們將利用幀緩沖來實現一些炫酷的效果。
我們在引擎渲染中經常會使用一些后處理效果,這些后處理效果就是在幀緩沖中進行的。下面我們就告訴讀者幀緩沖是如何工作的?
我們可以使用一個叫做glGenFramebuffers的函數來創建一個幀緩沖對象(簡稱FBO):
?
GLuint fbo;glGenFramebuffers(1, &fbo);?
首先我們要創建一個幀緩沖對象,把它綁定到當前幀緩沖,做一些操作,然后解綁幀緩沖。我們使用glBindFramebuffer來綁定幀緩沖:
glBindFramebuffer(GL_FRAMEBUFFER, fbo);?
綁定到GL_FRAMEBUFFER目標后,接下來所有的讀、寫幀緩沖的操作都會影響到當前綁定的幀緩沖。也可以把幀緩沖分開綁定到讀或寫目標上,分別使用GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER來做這件事。如果綁定到了GL_READ_FRAMEBUFFER,就能執行所有讀取操作,
像glReadPixels這樣的函數使用了,綁定到GL_DRAW_FRAMEBUFFER上,就允許進行渲染、清空和其他的寫入操作。在此給讀者總結一下構建一個完整的幀
緩沖滿足的條件:
建構一個完整的幀緩沖必須滿足以下條件:
- 我們必須往里面加入至少一個附件(顏色、深度、模板緩沖)。
- 其中至少有一個是顏色附件。
- 所有的附件都應該是已經完全做好的(已經存儲在內存之中)。
- 每個緩沖都應該有同樣數目的樣本。
上面的條件提到了樣本,如果你不知道什么是樣本也不用擔心,我們會在后面的博文中講到。
我們需要為幀緩沖創建一些附件(Attachment),還需要把這些附件附加到幀緩沖上。當我們做完所有上面提到的條件的時候我們就可以用?glCheckFramebufferStatus?帶上?GL_FRAMEBUFFER?這個參數來檢查是否真的成功做到了。然后檢查當前綁定的幀緩沖,返回了這些規范中的哪個值。如果返回的是?GL_FRAMEBUFFER_COMPLETE就對了:
?
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)? // Execute victory dance后續所有渲染操作將渲染到當前綁定的幀緩沖的附加緩沖中,由于我們的幀緩沖不是默認的幀緩沖,渲染命令對窗口的視頻輸出不會產生任何影響。出于這個原因,它被稱為離屏渲染(off-screen rendering),就是渲染到一個另外的緩沖中。為了讓所有的渲染操作對主窗口產生影響我們必須通過綁定為0來使默認幀緩沖被激活:
?
?
glBindFramebuffer(GL_FRAMEBUFFER, 0);當我們做完所有幀緩沖操作,不要忘記刪除幀緩沖對象:
?
?
?
?
glDeleteFramebuffers(1, &fbo);
現在在執行完成檢測前,我們需要把一個或更多的附件附加到幀緩沖上。一個附件就是一個內存地址,這個內存地址里面包含一個為幀緩沖準備的緩沖,它可以是個圖像。當創建一個附件的時候我們有兩種方式可以采用:紋理或渲染緩沖(renderbuffer)對象。
?
接下來介紹紋理,當把一個紋理附加到幀緩沖上的時候,所有渲染命令會寫入到紋理上,就像它是一個普通的顏色/深度或者模板緩沖一樣。使用紋理的好處是,所有渲染操作的結果都會被儲存為一個紋理圖像,這樣我們就可以簡單的在著色器中使用了。
創建一個幀緩沖的紋理和創建普通紋理差不多:
GLuint texture;glGenTextures(1, &texture);glBindTexture(GL_TEXTURE_2D, texture);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);這里主要的區別是我們把紋理的維度設置為屏幕大小(盡管不是必須的),我們還傳遞NULL作為紋理的data參數。對于這個紋理,
?
我們只分配內存,而不去填充它。紋理填充會在渲染到幀緩沖的時候去做。
?
如果你打算把整個屏幕渲染到一個或大或小的紋理上,你需要用新的紋理的尺寸作為參數再次調用glViewport(要在渲染到你的幀緩沖之前
做好),否則只有一小部分紋理或屏幕能夠繪制到紋理上。現在我們已經創建了一個紋理,最后一件要做的事情是把它附加到幀緩沖上:
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D, texture, 0);glFramebufferTexture2D函數需要傳入下列參數:
- target:我們所創建的幀緩沖類型的目標(繪制、讀取或兩者都有)。
- attachment:我們所附加的附件的類型。現在我們附加的是一個顏色附件。需要注意,最后的那個0是暗示我們可以附加1個以上顏色的附件。我們會在后面的教程中談到。
- textarget:你希望附加的紋理類型。
- texture:附加的實際紋理。
- level:Mipmap level。我們設置為0。
除顏色附件以外,我們還可以附加一個深度和一個模板紋理到幀緩沖對象上。為了附加一個深度緩沖,我們可以知道那個GL_DEPTH_ATTACHMENT作為附件類型。記住,這時紋理格式和內部格式類型(internalformat)就成了?GL_DEPTH_COMPONENT去反應深度緩沖的存儲格式。附加一個模板緩沖,你要使用?GL_STENCIL_ATTACHMENT作為第二個參數,把紋理格式指定為GL_STENCIL_INDEX。
也可以同時附加一個深度緩沖和一個模板緩沖為一個單獨的紋理,這樣紋理的每32位數值就包含了24位的深度信息和8位的模板信息。為了把一個深度和模板緩沖附加到一個單獨紋理上,我們使用GL_DEPTH_STENCIL_ATTACHMENT類型配置紋理格式以包含深度值和模板值的結合物。下面是一個附加了深度和模板緩沖為單一紋理的例子:
glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL );glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);再介紹渲染緩沖對象,OpenGL引進了渲染緩沖對象(Renderbuffer objects),所以在過去那些美好時光里紋理是附件的唯一可用的類型。和紋理圖像一樣,渲染緩沖對象也是一個緩沖,它可以是一堆字節、整數、像素或者其他東西。渲染緩沖對象的一大優點是,它以OpenGL原生渲染格式儲存它的數據,因此在離屏渲染到幀緩沖的時候,這些數據就相當于被優化過的了。
渲染緩沖對象將所有渲染數據直接儲存到它們的緩沖里,而不會進行針對特定紋理格式的任何轉換,這樣它們就成了一種快速可寫的存儲介質了。然而,渲染緩沖對象通常是只寫的,不能修改它們(就像獲取紋理,不能寫入紋理一樣)。可以用glReadPixels函數去讀取,函數返回一個當前綁定的幀緩沖的特定像素區域,而不是直接返回附件本身。
因為它們的數據已經是原生格式了,在寫入或把它們的數據簡單地到其他緩沖的時候非常快。當使用渲染緩沖對象時,像切換緩沖這種操作變得異常高速。我們在每個渲染迭代末尾使用的那個glfwSwapBuffers函數,同樣以渲染緩沖對象實現:我們簡單地寫入到一個渲染緩沖圖像,最后交換到另一個里。渲染緩沖對象對于這種操作來說很完美。
創建一個渲染緩沖對象和創建幀緩沖代碼差不多:
GLuint rbo;glGenRenderbuffers(1, &rbo);
相似地,我們打算把渲染緩沖對象綁定,這樣所有后續渲染緩沖操作都會影響到當前的渲染緩沖對象:
?
glBindRenderbuffer(GL_RENDERBUFFER, rbo);?
由于渲染緩沖對象通常是只寫的,它們經常作為深度和模板附件來使用,由于大多數時候,我們不需要從深度和模板緩沖中讀取數據,但仍關心深度和模板測試。我們就需要有深度和模板值提供給測試,但不需要對這些值進行采樣(sample),所以深度緩沖對象是完全符合的。當我們不去從這些緩沖中采樣的時候,渲染緩沖對象通常很合適,因為它們等于是被優化過的。
?
調用glRenderbufferStorage函數可以創建一個深度和模板渲染緩沖對象:
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);?
創建一個渲染緩沖對象與創建紋理對象相似,不同之處在于這個對象是專門被設計用于圖像的,而不是通用目的的數據緩沖,比如紋理。這里我們選擇GL_DEPTH24_STENCIL8作為內部格式,它同時代表24位的深度和8位的模板緩沖。
?
最后一件還要做的事情是把幀緩沖對象附加上:
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
在幀緩沖項目中,渲染緩沖對象可以提供一些優化,但更重要的是知道何時使用渲染緩沖對象,何時使用紋理。通常的規則是,如果你永遠都不需要從特定的緩沖中進行采樣,渲染緩沖對象對特定緩沖是更明智的選擇。如果哪天需要從比如顏色或深度值這樣的特定緩沖采樣數據的話,你最好還是使用紋理附件。從執行效率角度考慮,它不會對效率有太大影響。
?
?
下面通過案例的方式介紹如何使用幀緩存,我們會把場景渲染到一個顏色紋理上,這個紋理附加到一個我們創建的幀緩沖上,然后把紋
?
?
理繪制到一個簡單的四邊形上,這個四邊形鋪滿整個屏幕。輸出的圖像看似和沒用幀緩沖一樣,但是這次,它其實是直接打印到了一個單獨的
四邊形上面。為什么這很有用呢?下一部分我們會看到原因。
第一件要做的事情是創建一個幀緩沖對象,并綁定它,這比較明了:
?
GLuint framebuffer;glGenFramebuffers(1, &framebuffer);glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);第二件要做的事情是我們創建一個紋理圖像,這是我們將要附加到幀緩沖的顏色附件。我們把紋理的尺寸設置為窗口的寬度和高度,并保持數據未初始化:
?
?
?
?
// Generate textureGLuint texColorBuffer;glGenTextures(1, &texColorBuffer);glBindTexture(GL_TEXTURE_2D, texColorBuffer);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glBindTexture(GL_TEXTURE_2D, 0);// Attach it to currently bound framebuffer objectglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);我們同樣打算要讓OpenGL確定可以進行深度測試(模板測試,如果你用的話)所以我們必須還要確保向幀緩沖中添加一個深度(和模板)
?
附件。由于我們只采樣顏色緩沖,并不采樣其他緩沖,我們可以創建一個渲染緩沖對象來達到這個目的。
創建一個渲染緩沖對象不太難。唯一一件要記住的事情是,我們正在創建的是一個渲染緩沖對象的深度和模板附件。我們把它的內部給事設置
為GL_DEPTH24_STENCIL8,對于我們的目的來說這個精確度已經足夠了。
GLuint rbo;glGenRenderbuffers(1, &rbo);glBindRenderbuffer(GL_RENDERBUFFER, rbo);glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);? glBindRenderbuffer(GL_RENDERBUFFER, 0);?
我們為渲染緩沖對象分配了足夠的內存空間以后,我們可以解綁渲染緩沖。
接著,在做好幀緩沖之前,還有最后一步,我們把渲染緩沖對象附加到幀緩沖的深度和模板附件上:
?
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);?
然后我們要檢查幀緩沖是否真的做好了,如果沒有,我們就打印一個錯誤消息。
?
?
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;glBindFramebuffer(GL_FRAMEBUFFER, 0);?
還要保證解綁幀緩沖,這樣我們才不會意外渲染到錯誤的幀緩沖上。
現在幀緩沖做好了,我們要做的全部就是渲染到幀緩沖上,而不是綁定到幀緩沖對象的默認緩沖。余下所有命令會影響到當前綁定的幀緩沖上。所有深度和模板操作同樣會從當前綁定的幀緩沖的深度和模板附件中讀取,當然,得是在它們可用的情況下。如果你遺漏了比如深度緩沖,所有深度測試就不會工作,因為當前綁定的幀緩沖里沒有深度緩沖。
所以,為把場景繪制到一個單獨的紋理,我們必須以下面步驟來做:
為了繪制四邊形我們將會創建新的著色器。我們不打算引入任何花哨的變換矩陣,因為我們只提供已經是標準化設備坐標的頂點坐標,所以我們可以直接把它們作為頂點著色器的輸出。頂點著色器看起來像這樣:
?
layout (location = 0) in vec2 position;layout (location = 1) in vec2 texCoords;out vec2 TexCoords;void main(){??? gl_Position = vec4(position.x, position.y, 0.0f, 1.0f);??? TexCoords = texCoords;}
片段著色器更簡潔,因為我們做的唯一一件事是從紋理采樣:
?
?
?
in vec2 TexCoords;out vec4 color;uniform sampler2D screenTexture;void main(){??? color = texture(screenTexture, TexCoords);}?
接著需要你為屏幕上的四邊形創建和配置一個VAO。渲染迭代中幀緩沖處理會有下面的結構:
// First passglBindFramebuffer(GL_FRAMEBUFFER, framebuffer);glClearColor(0.1f, 0.1f, 0.1f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // We're not using stencil buffer nowglEnable(GL_DEPTH_TEST);DrawScene();// Second passglBindFramebuffer(GL_FRAMEBUFFER, 0); // back to defaultglClearColor(1.0f, 1.0f, 1.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);screenShader.Use();? glBindVertexArray(quadVAO);glDisable(GL_DEPTH_TEST);glBindTexture(GL_TEXTURE_2D, textureColorbuffer);glDrawArrays(GL_TRIANGLES, 0, 6);glBindVertexArray(0);?
第一,由于我們用的每個幀緩沖都有自己的一系列緩沖,我們打算使用glClear設置的合適的位(bits)來清空這些緩沖。
?
第二,當渲染四邊形的時候,我們關閉深度測試,因為我們不關系深度測試,我們繪制的是一個簡單的四邊形;當我們繪制普通場景時我們必須再次開啟深度測試。實現效果如下所示:
?
上述案例實現得出的結果是可以自由的獲取渲染場景中的任何像素,其實就是把它作為一個紋理圖像。接下來利用幀緩沖實現我們游戲中經常使用的后處理效果,比如游戲中顏色的反相處理,就是把顏色值取反。這個在片段著色器中處理即可,在片段著色器里返回這些顏色的反色(Inversion)并不難。我們得到屏幕紋理的顏色,然后用1.0減去它:
?
void main(){??? color = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);}反相是一種相對簡單的后處理特效,實現的效果如下所示:
?
?
這樣把整個場景都處理了,這也是后處理渲染實現的效果,這樣只需要在上述片段著色器中修改一行代碼即可實現。
下面再繼續深入探討,在單獨紋理圖像上進行后處理的另一個好處是我們可以從紋理的其他部分進行采樣。比如我們可以從當前紋理值的周圍采樣多個紋理值。創造性地把它們結合起來就能創造出有趣的效果了。
kernel是一個長得有點像一個小矩陣的數值數組,它中間的值中心可以映射到一個像素上,這個像素和這個像素周圍的值再乘以kernel,最后再把結果相加就能得到一個值。所以,我們基本上就是給當前紋理坐標加上一個它四周的偏移量,然后基于kernel把它們結合起來。下面是一個kernel的例子:
這個kernel表示一個像素周圍八個像素乘以2,它自己乘以-15。這個例子基本上就是把周圍像素乘上2,中間像素去乘以一個比較大的負數來進行平衡。kernel對于后處理來說非常管用,因為用起來簡單。網上能找到有很多實例,為了能用上kernel我們還得改改片段著色器。這里假設每個kernel都是3×3(實際上大多數都是3×3):
?
const float offset = 1.0 / 300;? void main(){??? vec2 offsets[9] = vec2[](??????? vec2(-offset, offset),? // top-left??????? vec2(0.0f,??? offset),? // top-center??????? vec2(offset,? offset),? // top-right??????? vec2(-offset, 0.0f),??? // center-left??????? vec2(0.0f,??? 0.0f),??? // center-center??????? vec2(offset,? 0.0f),??? // center-right??????? vec2(-offset, -offset), // bottom-left??????? vec2(0.0f,??? -offset), // bottom-center??????? vec2(offset,? -offset)? // bottom-right??? );??? float kernel[9] = float[](??????? -1, -1, -1,??????? -1,? 9, -1,??????? -1, -1, -1??? );??? vec3 sampleTex[9];??? for(int i = 0; i < 9; i++)??? {??????? sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));??? }??? vec3 col;??? for(int i = 0; i < 9; i++)??????? col += sampleTex[i] * kernel[i];??? color = vec4(col, 1.0);}?
?
在片段著色器中我們先為每個四周的紋理坐標創建一個9個vec2偏移量的數組。偏移量是一個簡單的常數,你可以設置為自己喜歡的。接著我們定義kernel,這里應該是一個銳化kernel,它通過一種有趣的方式從所有周邊的像素采樣,對每個顏色值進行銳化。最后,在采樣的時候我們把每個偏移量加到當前紋理坐標上,然后用加在一起的kernel的值乘以這些紋理值。
這個銳化的kernel看起來像這樣:
再舉個例子關于模糊(Blur)效果的Kernel定義如下:
由于所有數值加起來的總和為16,簡單返回結合起來的采樣顏色是非常亮的,所以我們必須將kernel的每個值除以16.最終的kernel數組會是這樣的:
?
float kernel[9] = float[](??? 1.0 / 16, 2.0 / 16, 1.0 / 16,??? 2.0 / 16, 4.0 / 16, 2.0 / 16,??? 1.0 / 16, 2.0 / 16, 1.0 / 16? );通過在像素著色器中改變kernel的float數組,我們就完全改變了之后的后處理效果.現在看起來會像是這樣:
?
?
這樣的模糊效果具有創建許多有趣效果的潛力,模糊效果在后處理中使用的非常多,它會結合著Bloom后處理渲染使用。模糊也能為我們在后面的教程中提供都顏色值進行平滑處理的能力。
最后把關于幀緩沖的頂點著色器和片段著色器代碼分別給讀者展示如下:
頂點著色器代碼:
?
layout (location = 0) in vec2 position;layout (location = 1) in vec2 texCoords;out vec2 TexCoords;void main(){??? gl_Position = vec4(position.x, position.y, 0.0f, 1.0f);???? TexCoords = texCoords;}
片段著色器代碼如下所示:
?
?
in vec2 TexCoords;out vec4 color;uniform sampler2D screenTexture;const float offset = 1.0 / 300;? void main(){??? vec2 offsets[9] = vec2[](??????? vec2(-offset, offset),? // top-left??????? vec2(0.0f,??? offset),? // top-center??????? vec2(offset,? offset),? // top-right??????? vec2(-offset, 0.0f),??? // center-left??????? vec2(0.0f,??? 0.0f),??? // center-center??????? vec2(offset,? 0.0f),??? // center-right??????? vec2(-offset, -offset), // bottom-left??????? vec2(0.0f,??? -offset), // bottom-center??????? vec2(offset,? -offset)? // bottom-right??? ??? );??? float kernel[9] = float[](??????? -1, -1, -1,??????? -1,? 9, -1,??????? -1, -1, -1??? );??????? vec3 sampleTex[9];??? for(int i = 0; i < 9; i++)??? {??????? sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));??? }??? vec3 col;??? for(int i = 0; i < 9; i++)??????? col += sampleTex[i] * kernel[i];??????? color = vec4(col, 1.0);}另外把在C++中關于處理幀緩存的核心代碼給讀者展示如下:
?
?
// Setup cube VAO??? GLuint cubeVAO, cubeVBO;??? glGenVertexArrays(1, &cubeVAO);??? glGenBuffers(1, &cubeVBO);??? glBindVertexArray(cubeVAO);??? glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);??? glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);??? glEnableVertexAttribArray(0);??? glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);??? glEnableVertexAttribArray(1);??? glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));??? glBindVertexArray(0);??? // Setup plane VAO??? GLuint floorVAO, floorVBO;??? glGenVertexArrays(1, &floorVAO);??? glGenBuffers(1, &floorVBO);??? glBindVertexArray(floorVAO);??? glBindBuffer(GL_ARRAY_BUFFER, floorVBO);??? glBufferData(GL_ARRAY_BUFFER, sizeof(floorVertices), &floorVertices, GL_STATIC_DRAW);??? glEnableVertexAttribArray(0);??? glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);??? glEnableVertexAttribArray(1);??? glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));??? glBindVertexArray(0);??? // Setup screen VAO??? GLuint quadVAO, quadVBO;??? glGenVertexArrays(1, &quadVAO);??? glGenBuffers(1, &quadVBO);??? glBindVertexArray(quadVAO);??? glBindBuffer(GL_ARRAY_BUFFER, quadVBO);??? glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);??? glEnableVertexAttribArray(0);??? glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);??? glEnableVertexAttribArray(1);??? glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));??? glBindVertexArray(0);??? // Load textures??? GLuint cubeTexture = loadTexture(FileSystem::getPath("resources/textures/container.jpg").c_str());??? GLuint floorTexture = loadTexture(FileSystem::getPath("resources/textures/metal.png").c_str());??? ??? // Framebuffers??? GLuint framebuffer;??? glGenFramebuffers(1, &framebuffer);??? glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);????? // Create a color attachment texture??? GLuint textureColorbuffer = generateAttachmentTexture(false, false);??? glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);??? // Create a renderbuffer object for depth and stencil attachment (we won't be sampling these)??? GLuint rbo;??? glGenRenderbuffers(1, &rbo);??? glBindRenderbuffer(GL_RENDERBUFFER, rbo);???? glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, screenWidth, screenHeight); // Use a single renderbuffer object for both a depth AND stencil buffer.??? glBindRenderbuffer(GL_RENDERBUFFER, 0);??? glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // Now actually attach it??? // Now that we actually created the framebuffer and added all attachments we want to check if it is actually complete now??? if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)??????? cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;??? glBindFramebuffer(GL_FRAMEBUFFER, 0);
每一幀處理的代碼如下所示:
?
?
??????? /??????? // Bind to framebuffer and draw to color texture ??????? // as we normally would.??????? // //??????? glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);??????? // Clear all attached buffers??????? ??????? glClearColor(0.1f, 0.1f, 0.1f, 1.0f);??????? glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // We're not using stencil buffer so why bother with clearing???????? glEnable(GL_DEPTH_TEST);??????? // Set uniforms??????? shader.Use();??????? glm::mat4 model;??????? glm::mat4 view = camera.GetViewMatrix();??????? glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth/(float)screenHeight, 0.1f, 100.0f);??????? glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));??????? glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));??????? // Floor??????? glBindVertexArray(floorVAO);??????? glBindTexture(GL_TEXTURE_2D, floorTexture);??????? model = glm::mat4();??????? glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));??????? glDrawArrays(GL_TRIANGLES, 0, 6);????????? glBindVertexArray(0);???????? // Cubes??????? glBindVertexArray(cubeVAO);??????? glBindTexture(GL_TEXTURE_2D, cubeTexture);????????? model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));??????? glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));??????? glDrawArrays(GL_TRIANGLES, 0, 36);??????? model = glm::mat4();??????? model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));??????? glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));??????? glDrawArrays(GL_TRIANGLES, 0, 36);??????? glBindVertexArray(0);?????????? /??????? // Bind to default framebuffer again and draw the ??????? // quad plane with attched screen texture.??????? // //??????? glBindFramebuffer(GL_FRAMEBUFFER, 0);??????? // Clear all relevant buffers??????? glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // Set clear color to white (not really necessery actually, since we won't be able to see behind the quad anyways)??????? glClear(GL_COLOR_BUFFER_BIT);??????? glDisable(GL_DEPTH_TEST); // We don't care about depth information when rendering a single quad??????? // Draw Screen??????? screenShader.Use();??????? glBindVertexArray(quadVAO);??????? glBindTexture(GL_TEXTURE_2D, textureColorbuffer); // Use the color attachment texture as the texture of the quad plane??????? glDrawArrays(GL_TRIANGLES, 0, 6);??????? glBindVertexArray(0);??????? // Swap the buffers??????? glfwSwapBuffers(window);?
?
總結:
以上就是關于幀緩沖的介紹,它主要的作用是可以獲取到場景像素,后處理就是對場景像素作渲染處理的,所以該技術廣泛的被應用在后處理開發中,這也是為讀者揭示后處理渲染的本質,希望對大家有所幫助。。。。。
?
?
?
?
?
???????????瀏覽人工智能教程
總結
以上是生活随笔為你收集整理的转 OpenGL核心技术之帧缓冲的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【PyTorch】6.1 正则化之wei
- 下一篇: [canvas] 万有引力