转 OpenGL核心技术之帧缓冲
首先給大家分享一個(gè)巨牛的人工智能教程,是我無意中發(fā)現(xiàn)的。教程不僅是零基礎(chǔ),通俗易懂,而且非常風(fēng)趣幽默,還時(shí)不時(shí)有內(nèi)涵段子,像看小說一樣,哈哈~我正在學(xué)習(xí)中,覺得太牛了,所以分享給大家!點(diǎn)這里可以跳轉(zhuǎn)到教程
筆者介紹:姜雪偉,IT公司技術(shù)合伙人,IT高級(jí)講師,CSDN社區(qū)專家,特邀編輯,暢銷書作者,國(guó)家專利發(fā)明人;已出版書籍:《手把手教你架構(gòu)3D游戲引擎》電子工業(yè)出版社和《Unity3D實(shí)戰(zhàn)核心技術(shù)詳解》電子工業(yè)出版社等。
CSDN視頻網(wǎng)址:http://edu.csdn.net/lecturer/144
本篇博文主要是給讀者解密關(guān)于游戲后處理渲染效果的原理,后處理渲染效果在Unity,UE4虛幻引擎等商業(yè)引擎 使用的非常多,
比如Bloom,Blur,SSAO,PSSM,HDR等等都屬于后處理渲染效果,它們的實(shí)現(xiàn)其實(shí)就是應(yīng)用幀緩沖技術(shù)實(shí)現(xiàn)的,本篇博文主要是
圍繞幀緩沖給讀者介紹其實(shí)現(xiàn)原理以及應(yīng)用案例。
在前面給讀者介紹了幾種不同的屏幕緩沖:用于寫入顏色值的顏色緩沖,用于寫入深度信息的深度緩沖,以及允許我們基于一些條件丟棄指定片段的模板緩沖。本篇博客主要是給讀者介紹幀緩沖,什么是幀緩沖?其實(shí)就是把前面介紹的這幾種緩沖結(jié)合起來叫做幀緩沖(Framebuffer),它被儲(chǔ)存于內(nèi)存中。OpenGL給了我們自己定義幀緩沖的自由,我們可以選擇性的定義自己的顏色緩沖、深度和模板緩沖。本篇博客主要是給讀者介紹幀緩沖,我們前面介紹的渲染操作都是在默認(rèn)的幀緩沖之上進(jìn)行的,當(dāng)你創(chuàng)建了你的窗口的時(shí)候默認(rèn)幀緩沖就被創(chuàng)建和配置好了,通過創(chuàng)建我們自己的幀緩沖我們能夠獲得一種額外的渲染方式。通過幀緩沖可以將你的場(chǎng)景渲染到一個(gè)不同的幀緩沖中,可以使我們能夠在場(chǎng)景中創(chuàng)建鏡子這樣的效果,或者做出一些炫酷的特效。首先我們會(huì)討論它們是如何工作的,然后我們將利用幀緩沖來實(shí)現(xiàn)一些炫酷的效果。
我們?cè)谝驿秩局薪?jīng)常會(huì)使用一些后處理效果,這些后處理效果就是在幀緩沖中進(jìn)行的。下面我們就告訴讀者幀緩沖是如何工作的?
我們可以使用一個(gè)叫做glGenFramebuffers的函數(shù)來創(chuàng)建一個(gè)幀緩沖對(duì)象(簡(jiǎn)稱FBO):
?
GLuint fbo;glGenFramebuffers(1, &fbo);?
首先我們要?jiǎng)?chuàng)建一個(gè)幀緩沖對(duì)象,把它綁定到當(dāng)前幀緩沖,做一些操作,然后解綁幀緩沖。我們使用glBindFramebuffer來綁定幀緩沖:
glBindFramebuffer(GL_FRAMEBUFFER, fbo);?
綁定到GL_FRAMEBUFFER目標(biāo)后,接下來所有的讀、寫幀緩沖的操作都會(huì)影響到當(dāng)前綁定的幀緩沖。也可以把幀緩沖分開綁定到讀或?qū)懩繕?biāo)上,分別使用GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER來做這件事。如果綁定到了GL_READ_FRAMEBUFFER,就能執(zhí)行所有讀取操作,
像glReadPixels這樣的函數(shù)使用了,綁定到GL_DRAW_FRAMEBUFFER上,就允許進(jìn)行渲染、清空和其他的寫入操作。在此給讀者總結(jié)一下構(gòu)建一個(gè)完整的幀
緩沖滿足的條件:
建構(gòu)一個(gè)完整的幀緩沖必須滿足以下條件:
- 我們必須往里面加入至少一個(gè)附件(顏色、深度、模板緩沖)。
- 其中至少有一個(gè)是顏色附件。
- 所有的附件都應(yīng)該是已經(jīng)完全做好的(已經(jīng)存儲(chǔ)在內(nèi)存之中)。
- 每個(gè)緩沖都應(yīng)該有同樣數(shù)目的樣本。
上面的條件提到了樣本,如果你不知道什么是樣本也不用擔(dān)心,我們會(huì)在后面的博文中講到。
我們需要為幀緩沖創(chuàng)建一些附件(Attachment),還需要把這些附件附加到幀緩沖上。當(dāng)我們做完所有上面提到的條件的時(shí)候我們就可以用?glCheckFramebufferStatus?帶上?GL_FRAMEBUFFER?這個(gè)參數(shù)來檢查是否真的成功做到了。然后檢查當(dāng)前綁定的幀緩沖,返回了這些規(guī)范中的哪個(gè)值。如果返回的是?GL_FRAMEBUFFER_COMPLETE就對(duì)了:
?
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)? // Execute victory dance后續(xù)所有渲染操作將渲染到當(dāng)前綁定的幀緩沖的附加緩沖中,由于我們的幀緩沖不是默認(rèn)的幀緩沖,渲染命令對(duì)窗口的視頻輸出不會(huì)產(chǎn)生任何影響。出于這個(gè)原因,它被稱為離屏渲染(off-screen rendering),就是渲染到一個(gè)另外的緩沖中。為了讓所有的渲染操作對(duì)主窗口產(chǎn)生影響我們必須通過綁定為0來使默認(rèn)幀緩沖被激活:
?
?
glBindFramebuffer(GL_FRAMEBUFFER, 0);當(dāng)我們做完所有幀緩沖操作,不要忘記刪除幀緩沖對(duì)象:
?
?
?
?
glDeleteFramebuffers(1, &fbo);
現(xiàn)在在執(zhí)行完成檢測(cè)前,我們需要把一個(gè)或更多的附件附加到幀緩沖上。一個(gè)附件就是一個(gè)內(nèi)存地址,這個(gè)內(nèi)存地址里面包含一個(gè)為幀緩沖準(zhǔn)備的緩沖,它可以是個(gè)圖像。當(dāng)創(chuàng)建一個(gè)附件的時(shí)候我們有兩種方式可以采用:紋理或渲染緩沖(renderbuffer)對(duì)象。
?
接下來介紹紋理,當(dāng)把一個(gè)紋理附加到幀緩沖上的時(shí)候,所有渲染命令會(huì)寫入到紋理上,就像它是一個(gè)普通的顏色/深度或者模板緩沖一樣。使用紋理的好處是,所有渲染操作的結(jié)果都會(huì)被儲(chǔ)存為一個(gè)紋理圖像,這樣我們就可以簡(jiǎn)單的在著色器中使用了。
創(chuàng)建一個(gè)幀緩沖的紋理和創(chuàng)建普通紋理差不多:
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);這里主要的區(qū)別是我們把紋理的維度設(shè)置為屏幕大小(盡管不是必須的),我們還傳遞NULL作為紋理的data參數(shù)。對(duì)于這個(gè)紋理,
?
我們只分配內(nèi)存,而不去填充它。紋理填充會(huì)在渲染到幀緩沖的時(shí)候去做。
?
如果你打算把整個(gè)屏幕渲染到一個(gè)或大或小的紋理上,你需要用新的紋理的尺寸作為參數(shù)再次調(diào)用glViewport(要在渲染到你的幀緩沖之前
做好),否則只有一小部分紋理或屏幕能夠繪制到紋理上。現(xiàn)在我們已經(jīng)創(chuàng)建了一個(gè)紋理,最后一件要做的事情是把它附加到幀緩沖上:
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D, texture, 0);glFramebufferTexture2D函數(shù)需要傳入下列參數(shù):
- target:我們所創(chuàng)建的幀緩沖類型的目標(biāo)(繪制、讀取或兩者都有)。
- attachment:我們所附加的附件的類型。現(xiàn)在我們附加的是一個(gè)顏色附件。需要注意,最后的那個(gè)0是暗示我們可以附加1個(gè)以上顏色的附件。我們會(huì)在后面的教程中談到。
- textarget:你希望附加的紋理類型。
- texture:附加的實(shí)際紋理。
- level:Mipmap level。我們?cè)O(shè)置為0。
除顏色附件以外,我們還可以附加一個(gè)深度和一個(gè)模板紋理到幀緩沖對(duì)象上。為了附加一個(gè)深度緩沖,我們可以知道那個(gè)GL_DEPTH_ATTACHMENT作為附件類型。記住,這時(shí)紋理格式和內(nèi)部格式類型(internalformat)就成了?GL_DEPTH_COMPONENT去反應(yīng)深度緩沖的存儲(chǔ)格式。附加一個(gè)模板緩沖,你要使用?GL_STENCIL_ATTACHMENT作為第二個(gè)參數(shù),把紋理格式指定為GL_STENCIL_INDEX。
也可以同時(shí)附加一個(gè)深度緩沖和一個(gè)模板緩沖為一個(gè)單獨(dú)的紋理,這樣紋理的每32位數(shù)值就包含了24位的深度信息和8位的模板信息。為了把一個(gè)深度和模板緩沖附加到一個(gè)單獨(dú)紋理上,我們使用GL_DEPTH_STENCIL_ATTACHMENT類型配置紋理格式以包含深度值和模板值的結(jié)合物。下面是一個(gè)附加了深度和模板緩沖為單一紋理的例子:
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);再介紹渲染緩沖對(duì)象,OpenGL引進(jìn)了渲染緩沖對(duì)象(Renderbuffer objects),所以在過去那些美好時(shí)光里紋理是附件的唯一可用的類型。和紋理圖像一樣,渲染緩沖對(duì)象也是一個(gè)緩沖,它可以是一堆字節(jié)、整數(shù)、像素或者其他東西。渲染緩沖對(duì)象的一大優(yōu)點(diǎn)是,它以O(shè)penGL原生渲染格式儲(chǔ)存它的數(shù)據(jù),因此在離屏渲染到幀緩沖的時(shí)候,這些數(shù)據(jù)就相當(dāng)于被優(yōu)化過的了。
渲染緩沖對(duì)象將所有渲染數(shù)據(jù)直接儲(chǔ)存到它們的緩沖里,而不會(huì)進(jìn)行針對(duì)特定紋理格式的任何轉(zhuǎn)換,這樣它們就成了一種快速可寫的存儲(chǔ)介質(zhì)了。然而,渲染緩沖對(duì)象通常是只寫的,不能修改它們(就像獲取紋理,不能寫入紋理一樣)??梢杂胓lReadPixels函數(shù)去讀取,函數(shù)返回一個(gè)當(dāng)前綁定的幀緩沖的特定像素區(qū)域,而不是直接返回附件本身。
因?yàn)樗鼈兊臄?shù)據(jù)已經(jīng)是原生格式了,在寫入或把它們的數(shù)據(jù)簡(jiǎn)單地到其他緩沖的時(shí)候非???。當(dāng)使用渲染緩沖對(duì)象時(shí),像切換緩沖這種操作變得異常高速。我們?cè)诿總€(gè)渲染迭代末尾使用的那個(gè)glfwSwapBuffers函數(shù),同樣以渲染緩沖對(duì)象實(shí)現(xiàn):我們簡(jiǎn)單地寫入到一個(gè)渲染緩沖圖像,最后交換到另一個(gè)里。渲染緩沖對(duì)象對(duì)于這種操作來說很完美。
創(chuàng)建一個(gè)渲染緩沖對(duì)象和創(chuàng)建幀緩沖代碼差不多:
GLuint rbo;glGenRenderbuffers(1, &rbo);
相似地,我們打算把渲染緩沖對(duì)象綁定,這樣所有后續(xù)渲染緩沖操作都會(huì)影響到當(dāng)前的渲染緩沖對(duì)象:
?
glBindRenderbuffer(GL_RENDERBUFFER, rbo);?
由于渲染緩沖對(duì)象通常是只寫的,它們經(jīng)常作為深度和模板附件來使用,由于大多數(shù)時(shí)候,我們不需要從深度和模板緩沖中讀取數(shù)據(jù),但仍關(guān)心深度和模板測(cè)試。我們就需要有深度和模板值提供給測(cè)試,但不需要對(duì)這些值進(jìn)行采樣(sample),所以深度緩沖對(duì)象是完全符合的。當(dāng)我們不去從這些緩沖中采樣的時(shí)候,渲染緩沖對(duì)象通常很合適,因?yàn)樗鼈兊扔谑潜粌?yōu)化過的。
?
調(diào)用glRenderbufferStorage函數(shù)可以創(chuàng)建一個(gè)深度和模板渲染緩沖對(duì)象:
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);?
創(chuàng)建一個(gè)渲染緩沖對(duì)象與創(chuàng)建紋理對(duì)象相似,不同之處在于這個(gè)對(duì)象是專門被設(shè)計(jì)用于圖像的,而不是通用目的的數(shù)據(jù)緩沖,比如紋理。這里我們選擇GL_DEPTH24_STENCIL8作為內(nèi)部格式,它同時(shí)代表24位的深度和8位的模板緩沖。
?
最后一件還要做的事情是把幀緩沖對(duì)象附加上:
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
在幀緩沖項(xiàng)目中,渲染緩沖對(duì)象可以提供一些優(yōu)化,但更重要的是知道何時(shí)使用渲染緩沖對(duì)象,何時(shí)使用紋理。通常的規(guī)則是,如果你永遠(yuǎn)都不需要從特定的緩沖中進(jìn)行采樣,渲染緩沖對(duì)象對(duì)特定緩沖是更明智的選擇。如果哪天需要從比如顏色或深度值這樣的特定緩沖采樣數(shù)據(jù)的話,你最好還是使用紋理附件。從執(zhí)行效率角度考慮,它不會(huì)對(duì)效率有太大影響。
?
?
下面通過案例的方式介紹如何使用幀緩存,我們會(huì)把場(chǎng)景渲染到一個(gè)顏色紋理上,這個(gè)紋理附加到一個(gè)我們創(chuàng)建的幀緩沖上,然后把紋
?
?
理繪制到一個(gè)簡(jiǎn)單的四邊形上,這個(gè)四邊形鋪滿整個(gè)屏幕。輸出的圖像看似和沒用幀緩沖一樣,但是這次,它其實(shí)是直接打印到了一個(gè)單獨(dú)的
四邊形上面。為什么這很有用呢?下一部分我們會(huì)看到原因。
第一件要做的事情是創(chuàng)建一個(gè)幀緩沖對(duì)象,并綁定它,這比較明了:
?
GLuint framebuffer;glGenFramebuffers(1, &framebuffer);glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);第二件要做的事情是我們創(chuàng)建一個(gè)紋理圖像,這是我們將要附加到幀緩沖的顏色附件。我們把紋理的尺寸設(shè)置為窗口的寬度和高度,并保持?jǐn)?shù)據(jù)未初始化:
?
?
?
?
// 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確定可以進(jìn)行深度測(cè)試(模板測(cè)試,如果你用的話)所以我們必須還要確保向幀緩沖中添加一個(gè)深度(和模板)
?
附件。由于我們只采樣顏色緩沖,并不采樣其他緩沖,我們可以創(chuàng)建一個(gè)渲染緩沖對(duì)象來達(dá)到這個(gè)目的。
創(chuàng)建一個(gè)渲染緩沖對(duì)象不太難。唯一一件要記住的事情是,我們正在創(chuàng)建的是一個(gè)渲染緩沖對(duì)象的深度和模板附件。我們把它的內(nèi)部給事設(shè)置
為GL_DEPTH24_STENCIL8,對(duì)于我們的目的來說這個(gè)精確度已經(jīng)足夠了。
GLuint rbo;glGenRenderbuffers(1, &rbo);glBindRenderbuffer(GL_RENDERBUFFER, rbo);glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);? glBindRenderbuffer(GL_RENDERBUFFER, 0);?
我們?yōu)殇秩揪彌_對(duì)象分配了足夠的內(nèi)存空間以后,我們可以解綁渲染緩沖。
接著,在做好幀緩沖之前,還有最后一步,我們把渲染緩沖對(duì)象附加到幀緩沖的深度和模板附件上:
?
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);?
然后我們要檢查幀緩沖是否真的做好了,如果沒有,我們就打印一個(gè)錯(cuò)誤消息。
?
?
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << endl;glBindFramebuffer(GL_FRAMEBUFFER, 0);?
還要保證解綁幀緩沖,這樣我們才不會(huì)意外渲染到錯(cuò)誤的幀緩沖上。
現(xiàn)在幀緩沖做好了,我們要做的全部就是渲染到幀緩沖上,而不是綁定到幀緩沖對(duì)象的默認(rèn)緩沖。余下所有命令會(huì)影響到當(dāng)前綁定的幀緩沖上。所有深度和模板操作同樣會(huì)從當(dāng)前綁定的幀緩沖的深度和模板附件中讀取,當(dāng)然,得是在它們可用的情況下。如果你遺漏了比如深度緩沖,所有深度測(cè)試就不會(huì)工作,因?yàn)楫?dāng)前綁定的幀緩沖里沒有深度緩沖。
所以,為把場(chǎng)景繪制到一個(gè)單獨(dú)的紋理,我們必須以下面步驟來做:
為了繪制四邊形我們將會(huì)創(chuàng)建新的著色器。我們不打算引入任何花哨的變換矩陣,因?yàn)槲覀冎惶峁┮呀?jīng)是標(biāo)準(zhǔn)化設(shè)備坐標(biāo)的頂點(diǎn)坐標(biāo),所以我們可以直接把它們作為頂點(diǎn)著色器的輸出。頂點(diǎn)著色器看起來像這樣:
?
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;}
片段著色器更簡(jiǎn)潔,因?yàn)槲覀冏龅奈ㄒ灰患率菑募y理采樣:
?
?
?
in vec2 TexCoords;out vec4 color;uniform sampler2D screenTexture;void main(){??? color = texture(screenTexture, TexCoords);}?
接著需要你為屏幕上的四邊形創(chuàng)建和配置一個(gè)VAO。渲染迭代中幀緩沖處理會(huì)有下面的結(jié)構(gòu):
// 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);?
第一,由于我們用的每個(gè)幀緩沖都有自己的一系列緩沖,我們打算使用glClear設(shè)置的合適的位(bits)來清空這些緩沖。
?
第二,當(dāng)渲染四邊形的時(shí)候,我們關(guān)閉深度測(cè)試,因?yàn)槲覀儾魂P(guān)系深度測(cè)試,我們繪制的是一個(gè)簡(jiǎn)單的四邊形;當(dāng)我們繪制普通場(chǎng)景時(shí)我們必須再次開啟深度測(cè)試。實(shí)現(xiàn)效果如下所示:
?
上述案例實(shí)現(xiàn)得出的結(jié)果是可以自由的獲取渲染場(chǎng)景中的任何像素,其實(shí)就是把它作為一個(gè)紋理圖像。接下來利用幀緩沖實(shí)現(xiàn)我們游戲中經(jīng)常使用的后處理效果,比如游戲中顏色的反相處理,就是把顏色值取反。這個(gè)在片段著色器中處理即可,在片段著色器里返回這些顏色的反色(Inversion)并不難。我們得到屏幕紋理的顏色,然后用1.0減去它:
?
void main(){??? color = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);}反相是一種相對(duì)簡(jiǎn)單的后處理特效,實(shí)現(xiàn)的效果如下所示:
?
?
這樣把整個(gè)場(chǎng)景都處理了,這也是后處理渲染實(shí)現(xiàn)的效果,這樣只需要在上述片段著色器中修改一行代碼即可實(shí)現(xiàn)。
下面再繼續(xù)深入探討,在單獨(dú)紋理圖像上進(jìn)行后處理的另一個(gè)好處是我們可以從紋理的其他部分進(jìn)行采樣。比如我們可以從當(dāng)前紋理值的周圍采樣多個(gè)紋理值。創(chuàng)造性地把它們結(jié)合起來就能創(chuàng)造出有趣的效果了。
kernel是一個(gè)長(zhǎng)得有點(diǎn)像一個(gè)小矩陣的數(shù)值數(shù)組,它中間的值中心可以映射到一個(gè)像素上,這個(gè)像素和這個(gè)像素周圍的值再乘以kernel,最后再把結(jié)果相加就能得到一個(gè)值。所以,我們基本上就是給當(dāng)前紋理坐標(biāo)加上一個(gè)它四周的偏移量,然后基于kernel把它們結(jié)合起來。下面是一個(gè)kernel的例子:
這個(gè)kernel表示一個(gè)像素周圍八個(gè)像素乘以2,它自己乘以-15。這個(gè)例子基本上就是把周圍像素乘上2,中間像素去乘以一個(gè)比較大的負(fù)數(shù)來進(jìn)行平衡。kernel對(duì)于后處理來說非常管用,因?yàn)橛闷饋砗?jiǎn)單。網(wǎng)上能找到有很多實(shí)例,為了能用上kernel我們還得改改片段著色器。這里假設(shè)每個(gè)kernel都是3×3(實(shí)際上大多數(shù)都是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);}?
?
在片段著色器中我們先為每個(gè)四周的紋理坐標(biāo)創(chuàng)建一個(gè)9個(gè)vec2偏移量的數(shù)組。偏移量是一個(gè)簡(jiǎn)單的常數(shù),你可以設(shè)置為自己喜歡的。接著我們定義kernel,這里應(yīng)該是一個(gè)銳化kernel,它通過一種有趣的方式從所有周邊的像素采樣,對(duì)每個(gè)顏色值進(jìn)行銳化。最后,在采樣的時(shí)候我們把每個(gè)偏移量加到當(dāng)前紋理坐標(biāo)上,然后用加在一起的kernel的值乘以這些紋理值。
這個(gè)銳化的kernel看起來像這樣:
再舉個(gè)例子關(guān)于模糊(Blur)效果的Kernel定義如下:
由于所有數(shù)值加起來的總和為16,簡(jiǎn)單返回結(jié)合起來的采樣顏色是非常亮的,所以我們必須將kernel的每個(gè)值除以16.最終的kernel數(shù)組會(huì)是這樣的:
?
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數(shù)組,我們就完全改變了之后的后處理效果.現(xiàn)在看起來會(huì)像是這樣:
?
?
這樣的模糊效果具有創(chuàng)建許多有趣效果的潛力,模糊效果在后處理中使用的非常多,它會(huì)結(jié)合著Bloom后處理渲染使用。模糊也能為我們?cè)诤竺娴慕坛讨刑峁┒碱伾颠M(jìn)行平滑處理的能力。
最后把關(guān)于幀緩沖的頂點(diǎn)著色器和片段著色器代碼分別給讀者展示如下:
頂點(diǎn)著色器代碼:
?
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++中關(guān)于處理幀緩存的核心代碼給讀者展示如下:
?
?
// 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);?
?
總結(jié):
以上就是關(guān)于幀緩沖的介紹,它主要的作用是可以獲取到場(chǎng)景像素,后處理就是對(duì)場(chǎng)景像素作渲染處理的,所以該技術(shù)廣泛的被應(yīng)用在后處理開發(fā)中,這也是為讀者揭示后處理渲染的本質(zhì),希望對(duì)大家有所幫助。。。。。
?
?
?
?
?
???????????瀏覽人工智能教程
總結(jié)
以上是生活随笔為你收集整理的转 OpenGL核心技术之帧缓冲的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【PyTorch】6.1 正则化之wei
- 下一篇: [canvas] 万有引力