版本記錄
| 版本號(hào) | 時(shí)間 |
|---|---|
| V1.0 | 2018.01.14 |
前言
OpenGL 圖形庫項(xiàng)目中一直也沒用過,最近也想學(xué)著使用這個(gè)圖形庫,感覺還是很有意思,也就自然想著好好的總結(jié)一下,希望對(duì)大家能有所幫助。下面內(nèi)容來自歡迎來到OpenGL的世界。
1. OpenGL 圖形庫使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫使用(二) —— 渲染模式、對(duì)象、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫使用(三) —— 著色器、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫使用(五) —— 紋理
6. OpenGL 圖形庫使用(六) —— 變換
7. OpenGL 圖形庫的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫的使用(九)—— 攝像機(jī)(一)
10. OpenGL 圖形庫的使用(十)—— 攝像機(jī)(二)
11. OpenGL 圖形庫的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫的使用(十七)—— 光照之復(fù)習(xí)總結(jié)
18. OpenGL 圖形庫的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫的使用(十九)—— 模型加載之網(wǎng)格
20. OpenGL 圖形庫的使用(二十)—— 模型加載之模型
21. OpenGL 圖形庫的使用(二十一)—— 高級(jí)OpenGL之深度測(cè)試
22. OpenGL 圖形庫的使用(二十二)—— 高級(jí)OpenGL之模板測(cè)試Stencil testing
23. OpenGL 圖形庫的使用(二十三)—— 高級(jí)OpenGL之混合Blending
24. OpenGL 圖形庫的使用(二十四)—— 高級(jí)OpenGL之面剔除Face culling
幀緩沖
到目前為止,我們已經(jīng)使用了很多屏幕緩沖了:用于寫入顏色值的顏色緩沖、用于寫入深度信息的深度緩沖和允許我們根據(jù)一些條件丟棄特定片段的模板緩沖。這些緩沖結(jié)合起來叫做幀緩沖(Framebuffer),它被儲(chǔ)存在內(nèi)存中。OpenGL允許我們定義我們自己的幀緩沖,也就是說我們能夠定義我們自己的顏色緩沖,甚至是深度緩沖和模板緩沖。
我們目前所做的所有操作都是在默認(rèn)幀緩沖的渲染緩沖上進(jìn)行的。默認(rèn)的幀緩沖是在你創(chuàng)建窗口的時(shí)候生成和配置的(GLFW幫我們做了這些)。有了我們自己的幀緩沖,我們就能夠有更多方式來渲染了。
你可能不能很快理解幀緩沖的應(yīng)用,但渲染你的場(chǎng)景到不同的幀緩沖能夠讓我們?cè)趫?chǎng)景中加入類似鏡子的東西,或者做出很酷的后期處理效果。首先我們會(huì)討論它是如何工作的,之后我們將來實(shí)現(xiàn)這些炫酷的后期處理效果。
創(chuàng)建一個(gè)幀緩沖
和OpenGL中的其它對(duì)象一樣,我們會(huì)使用一個(gè)叫做glGenFramebuffers的函數(shù)來創(chuàng)建一個(gè)幀緩沖對(duì)象(Framebuffer Object, FBO):
unsigned int fbo;
glGenFramebuffers(1, &fbo);
這種創(chuàng)建和使用對(duì)象的方式我們已經(jīng)見過很多次了,所以它的使用函數(shù)也和其它的對(duì)象類似。首先我們創(chuàng)建一個(gè)幀緩沖對(duì)象,將它綁定為激活的(Active)幀緩沖,做一些操作,之后解綁幀緩沖。我們使用glBindFramebuffer來綁定幀緩沖。
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
在綁定到GL_FRAMEBUFFER目標(biāo)之后,所有的讀取和寫入幀緩沖的操作將會(huì)影響當(dāng)前綁定的幀緩沖。我們也可以使用GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER,將一個(gè)幀緩沖分別綁定到讀取目標(biāo)或?qū)懭肽繕?biāo)。綁定到GL_READ_FRAMEBUFFER的幀緩沖將會(huì)使用在所有像是glReadPixels的讀取操作中,而綁定到GL_DRAW_FRAMEBUFFER的幀緩沖將會(huì)被用作渲染、清除等寫入操作的目標(biāo)。大部分情況你都不需要區(qū)分它們,通常都會(huì)使用GL_FRAMEBUFFER,綁定到兩個(gè)上。
不幸的是,我們現(xiàn)在還不能使用我們的幀緩沖,因?yàn)樗€不完整(Complete),一個(gè)完整的幀緩沖需要滿足以下的條件:
- 附加至少一個(gè)緩沖(顏色、深度或模板緩沖)。
- 至少有一個(gè)顏色附件(Attachment)。
- 所有的附件都必須是完整的(保留了內(nèi)存)。
- 每個(gè)緩沖都應(yīng)該有相同的樣本數(shù)。
如果你不知道什么是樣本,不要擔(dān)心,我們將在之后的教程中講到。
從上面的條件中可以知道,我們需要為幀緩沖創(chuàng)建一些附件,并將附件附加到幀緩沖上。在完成所有的條件之后,我們可以以GL_FRAMEBUFFER為參數(shù)調(diào)用glCheckFramebufferStatus,檢查幀緩沖是否完整。它將會(huì)檢測(cè)當(dāng)前綁定的幀緩沖,并返回規(guī)范中這些值的其中之一。如果它返回的是GL_FRAMEBUFFER_COMPLETE,幀緩沖就是完整的了。
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
// 執(zhí)行勝利的舞蹈
之后所有的渲染操作將會(huì)渲染到當(dāng)前綁定幀緩沖的附件中。由于我們的幀緩沖不是默認(rèn)幀緩沖,渲染指令將不會(huì)對(duì)窗口的視覺輸出有任何影響。出于這個(gè)原因,渲染到一個(gè)不同的幀緩沖被叫做離屏渲染(Off-screen Rendering)。要保證所有的渲染操作在主窗口中有視覺效果,我們需要再次激活默認(rèn)幀緩沖,將它綁定到0。
glBindFramebuffer(GL_FRAMEBUFFER, 0);
在完成所有的幀緩沖操作之后,不要忘記刪除這個(gè)幀緩沖對(duì)象:
glDeleteFramebuffers(1, &fbo);
在完整性檢查執(zhí)行之前,我們需要給幀緩沖附加一個(gè)附件。附件是一個(gè)內(nèi)存位置,它能夠作為幀緩沖的一個(gè)緩沖,可以將它想象為一個(gè)圖像。當(dāng)創(chuàng)建一個(gè)附件的時(shí)候我們有兩個(gè)選項(xiàng):紋理或渲染緩沖對(duì)象(Renderbuffer Object)。
1. 紋理附件
當(dāng)把一個(gè)紋理附加到幀緩沖的時(shí)候,所有的渲染指令將會(huì)寫入到這個(gè)紋理中,就想它是一個(gè)普通的顏色/深度或模板緩沖一樣。使用紋理的優(yōu)點(diǎn)是,所有渲染操作的結(jié)果將會(huì)被儲(chǔ)存在一個(gè)紋理圖像中,我們之后可以在著色器中很方便地使用它。
為幀緩沖創(chuàng)建一個(gè)紋理和創(chuàng)建一個(gè)普通的紋理差不多:
unsigned int 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è)置為了屏幕大?。ūM管這不是必須的),并且我們給紋理的data參數(shù)傳遞了NULL。對(duì)于這個(gè)紋理,我們僅僅分配了內(nèi)存而沒有填充它。填充這個(gè)紋理將會(huì)在我們渲染到幀緩沖之后來進(jìn)行。同樣注意我們并不關(guān)心環(huán)繞方式或多級(jí)漸遠(yuǎn)紋理,我們?cè)诖蠖鄶?shù)情況下都不會(huì)需要它們。
如果你想將你的屏幕渲染到一個(gè)更小或更大的紋理上,你需要(在渲染到你的幀緩沖之前)再次調(diào)用
glViewport,使用紋理的新維度作為參數(shù),否則只有一小部分的紋理或屏幕會(huì)被渲染到這個(gè)紋理上。
現(xiàn)在我們已經(jīng)創(chuàng)建好一個(gè)紋理了,要做的最后一件事就是將它附加到幀緩沖上了:
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
glFrameBufferTexture2D有以下的參數(shù):
-
target:幀緩沖的目標(biāo)(繪制、讀取或者兩者皆有) -
attachment:我們想要附加的附件類型。當(dāng)前我們正在附加一個(gè)顏色附件。注意最后的0意味著我們可以附加多個(gè)顏色附件。我們將在之后的教程中提到。 -
textarget:你希望附加的紋理類型 -
texture:要附加的紋理本身 -
level:多級(jí)漸遠(yuǎn)紋理的級(jí)別。我們將它保留為0。
除了顏色附件之外,我們還可以附加一個(gè)深度和模板緩沖紋理到幀緩沖對(duì)象中。要附加深度緩沖的話,我們將附件類型設(shè)置為GL_DEPTH_ATTACHMENT。注意紋理的格式(Format)和內(nèi)部格式(Internalformat)類型將變?yōu)?code>GL_DEPTH_COMPONENT,來反映深度緩沖的儲(chǔ)存格式。要附加模板緩沖的話,你要將第二個(gè)參數(shù)設(shè)置為GL_STENCIL_ATTACHMENT,并將紋理的格式設(shè)定為GL_STENCIL_INDEX。
也可以將深度緩沖和模板緩沖附加為一個(gè)單獨(dú)的紋理。紋理的每32位數(shù)值將包含24位的深度信息和8位的模板信息。要將深度和模板緩沖附加為一個(gè)紋理的話,我們使用GL_DEPTH_STENCIL_ATTACHMENT類型,并配置紋理的格式,讓它包含合并的深度和模板值。將一個(gè)深度和模板緩沖附加為一個(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);
2. 渲染緩沖對(duì)象附件
渲染緩沖對(duì)象(Renderbuffer Object)是在紋理之后引入到OpenGL中,作為一個(gè)可用的幀緩沖附件類型的,所以在過去紋理是唯一可用的附件。和紋理圖像一樣,渲染緩沖對(duì)象是一個(gè)真正的緩沖,即一系列的字節(jié)、整數(shù)、像素等。渲染緩沖對(duì)象附加的好處是,它會(huì)將數(shù)據(jù)儲(chǔ)存為OpenGL原生的渲染格式,它是為離屏渲染到幀緩沖優(yōu)化過的。
渲染緩沖對(duì)象直接將所有的渲染數(shù)據(jù)儲(chǔ)存到它的緩沖中,不會(huì)做任何針對(duì)紋理格式的轉(zhuǎn)換,讓它變?yōu)橐粋€(gè)更快的可寫儲(chǔ)存介質(zhì)。然而,渲染緩沖對(duì)象通常都是只寫的,所以你不能讀取它們(比如使用紋理訪問)。當(dāng)然你仍然還是能夠使用glReadPixels來讀取它,這會(huì)從當(dāng)前綁定的幀緩沖,而不是附件本身,中返回特定區(qū)域的像素。
因?yàn)樗臄?shù)據(jù)已經(jīng)是原生的格式了,當(dāng)寫入或者復(fù)制它的數(shù)據(jù)到其它緩沖中時(shí)是非??斓摹K?,交換緩沖這樣的操作在使用渲染緩沖對(duì)象時(shí)會(huì)非??臁N覀?cè)诿總€(gè)渲染迭代最后使用的glfwSwapBuffers,也可以通過渲染緩沖對(duì)象實(shí)現(xiàn):只需要寫入一個(gè)渲染緩沖圖像,并在最后交換到另外一個(gè)渲染緩沖就可以了。渲染緩沖對(duì)象對(duì)這種操作非常完美。
創(chuàng)建一個(gè)渲染緩沖對(duì)象的代碼和幀緩沖的代碼很類似:
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
類似,我們需要綁定這個(gè)渲染緩沖對(duì)象,讓之后所有的渲染緩沖操作影響當(dāng)前的rbo:
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
由于渲染緩沖對(duì)象通常都是只寫的,它們會(huì)經(jīng)常用于深度和模板附件,因?yàn)榇蟛糠謺r(shí)間我們都不需要從深度和模板緩沖中讀取值,只關(guān)心深度和模板測(cè)試。我們需要深度和模板值用于測(cè)試,但不需要對(duì)它們進(jìn)行采樣,所以渲染緩沖對(duì)象非常適合它們。當(dāng)我們不需要從這些緩沖中采樣的時(shí)候,通常都會(huì)選擇渲染緩沖對(duì)象,因?yàn)樗鼤?huì)更優(yōu)化一點(diǎn)。
創(chuàng)建一個(gè)深度和模板渲染緩沖對(duì)象可以通過調(diào)用glRenderbufferStorage函數(shù)來完成:
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
創(chuàng)建一個(gè)渲染緩沖對(duì)象和紋理對(duì)象類似,不同的是這個(gè)對(duì)象是專門被設(shè)計(jì)作為圖像使用的,而不是紋理那樣的通用數(shù)據(jù)緩沖(General Purpose Data Buffer)。這里我們選擇GL_DEPTH24_STENCIL8作為內(nèi)部格式,它封裝了24位的深度和8位的模板緩沖。
最后一件事就是附加這個(gè)渲染緩沖對(duì)象:
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
渲染緩沖對(duì)象能為你的幀緩沖對(duì)象提供一些優(yōu)化,但知道什么時(shí)候使用渲染緩沖對(duì)象,什么時(shí)候使用紋理是很重要的。通常的規(guī)則是,如果你不需要從一個(gè)緩沖中采樣數(shù)據(jù),那么對(duì)這個(gè)緩沖使用渲染緩沖對(duì)象會(huì)是明智的選擇。如果你需要從緩沖中采樣顏色或深度值等數(shù)據(jù),那么你應(yīng)該選擇紋理附件。性能方面它不會(huì)產(chǎn)生非常大的影響的。
渲染到紋理
既然我們已經(jīng)知道幀緩沖(大概)是怎么工作的了,是時(shí)候?qū)嵺`它們了。我們將會(huì)將場(chǎng)景渲染到一個(gè)附加到幀緩沖對(duì)象上的顏色紋理中,之后將在一個(gè)橫跨整個(gè)屏幕的四邊形上繪制這個(gè)紋理。這樣視覺輸出和沒使用幀緩沖時(shí)是完全一樣的,但這次是打印到了一個(gè)四邊形上。這為什么很有用呢?我們會(huì)在下一部分中知道原因。
首先要?jiǎng)?chuàng)建一個(gè)幀緩沖對(duì)象,并綁定它,這些都很直觀:
unsigned int framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
接下來我們需要?jiǎng)?chuàng)建一個(gè)紋理圖像,我們將它作為一個(gè)顏色附件附加到幀緩沖上。我們將紋理的維度設(shè)置為窗口的寬度和高度,并且不初始化它的數(shù)據(jù):
// 生成紋理
unsigned int 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);
// 將它附加到當(dāng)前綁定的幀緩沖對(duì)象
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);
我們還希望OpenGL能夠進(jìn)行深度測(cè)試(如果你需要的話還有模板測(cè)試),所以我們還需要添加一個(gè)深度(和模板)附件到幀緩沖中。由于我們只希望采樣顏色緩沖,而不是其它的緩沖,我們可以為它們創(chuàng)建一個(gè)渲染緩沖對(duì)象。還記得當(dāng)我們不需要采樣緩沖的時(shí)候,渲染緩沖對(duì)象是更好的選擇嗎?
創(chuàng)建一個(gè)渲染緩沖對(duì)象不是非常復(fù)雜。我們需要記住的唯一事情是,我們將它創(chuàng)建為一個(gè)深度和模板附件渲染緩沖對(duì)象。我們將它的內(nèi)部格式設(shè)置為GL_DEPTH24_STENCIL8,對(duì)我們來說這個(gè)精度已經(jīng)足夠了。
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
當(dāng)我們?yōu)殇秩揪彌_對(duì)象分配了足夠的內(nèi)存之后,我們可以解綁這個(gè)渲染緩沖。
接下來,作為完成幀緩沖之前的最后一步,我們將渲染緩沖對(duì)象附加到幀緩沖的深度和模板附件上:
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
最后,我們希望檢查幀緩沖是否是完整的,如果不是,我們將打印錯(cuò)誤信息。
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
記得要解綁幀緩沖,保證我們不會(huì)不小心渲染到錯(cuò)誤的幀緩沖上。
現(xiàn)在這個(gè)幀緩沖就完整了,我們只需要綁定這個(gè)幀緩沖對(duì)象,讓渲染到幀緩沖的緩沖中而不是默認(rèn)的幀緩沖中。之后的渲染指令將會(huì)影響當(dāng)前綁定的幀緩沖。所有的深度和模板操作都會(huì)從當(dāng)前綁定的幀緩沖的深度和模板附件中(如果有的話)讀取。如果你忽略了深度緩沖,那么所有的深度測(cè)試操作將不再工作,因?yàn)楫?dāng)前綁定的幀緩沖中不存在深度緩沖。
所以,要想繪制場(chǎng)景到一個(gè)紋理上,我們需要采取以下的步驟:
- 將新的幀緩沖綁定為激活的幀緩沖,和往常一樣渲染場(chǎng)景
- 綁定默認(rèn)的幀緩沖
- 繪制一個(gè)橫跨整個(gè)屏幕的四邊形,將幀緩沖的顏色緩沖作為它的紋理。
我們將會(huì)繪制深度測(cè)試小節(jié)中的場(chǎng)景,但這次使用的是舊的箱子紋理。
為了繪制這個(gè)四邊形,我們將會(huì)新創(chuàng)建一套簡(jiǎn)單的著色器。我們將不會(huì)包含任何花哨的矩陣變換,因?yàn)槲覀兲峁┑氖菢?biāo)準(zhǔn)化設(shè)備坐標(biāo)的頂點(diǎn)坐標(biāo),所以我們可以直接將它們?cè)O(shè)定為頂點(diǎn)著色器的輸出。頂點(diǎn)著色器是這樣的:
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
TexCoords = aTexCoords;
}
并沒有太復(fù)雜的東西。片段著色器會(huì)更加基礎(chǔ),我們做的唯一一件事就是從紋理中采樣:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D screenTexture;
void main()
{
FragColor = texture(screenTexture, TexCoords);
}
接著就靠你來為屏幕四邊形創(chuàng)建并配置一個(gè)VAO了。幀緩沖的一個(gè)渲染迭代將會(huì)有以下的結(jié)構(gòu):
// 第一處理階段(Pass)
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 我們現(xiàn)在不使用模板緩沖
glEnable(GL_DEPTH_TEST);
DrawScene();
// 第二處理階段
glBindFramebuffer(GL_FRAMEBUFFER, 0); // 返回默認(rèn)
glClearColor(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);
要注意一些事情。第一,由于我們使用的每個(gè)幀緩沖都有它自己一套緩沖,我們希望設(shè)置合適的位,調(diào)用glClear,清除這些緩沖。第二,當(dāng)繪制四邊形時(shí),我們將禁用深度測(cè)試,因?yàn)槲覀兪窃诶L制一個(gè)簡(jiǎn)單的四邊形,并不需要關(guān)系深度測(cè)試。在繪制普通場(chǎng)景的時(shí)候我們將會(huì)重新啟用深度測(cè)試。
有很多步驟都可能會(huì)出錯(cuò),所以如果你沒有得到輸出的話,嘗試調(diào)試程序,并重新閱讀本節(jié)的相關(guān)部分。如果所有的東西都能夠正常工作,你將會(huì)得到下面這樣的視覺輸出:

左邊展示的是視覺輸出,它和深度測(cè)試中是完全一樣的,但這次是渲染在一個(gè)簡(jiǎn)單的四邊形上。如果我們使用線框模式渲染場(chǎng)景,就會(huì)變得很明顯,我們?cè)谀J(rèn)的幀緩沖中只繪制了一個(gè)簡(jiǎn)單的四邊形。
你可以在這里找到程序的源代碼。
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <learnopengl/shader_m.h>
#include <learnopengl/camera.h>
#include <learnopengl/model.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
unsigned int loadTexture(const char *path);
// settings
const unsigned int SCR_WIDTH = 1280;
const unsigned int SCR_HEIGHT = 720;
// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = (float)SCR_WIDTH / 2.0;
float lastY = (float)SCR_HEIGHT / 2.0;
bool firstMouse = true;
// timing
float deltaTime = 0.0f;
float lastFrame = 0.0f;
int main()
{
// glfw: initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif
// glfw window creation
// --------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// tell GLFW to capture our mouse
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// configure global opengl state
// ----------------------------- glEnable(GL_DEPTH_TEST);
// build and compile shaders
// -------------------------
Shader shader("5.1.framebuffers.vs", "5.1.framebuffers.fs");
Shader screenShader("5.1.framebuffers_screen.vs", "5.1.framebuffers_screen.fs");
// set up vertex data (and buffer(s)) and configure vertex attributes
// ------------------------------------------------------------------
float cubeVertices[] = {
// positions // texture Coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
float planeVertices[] = {
// positions // texture Coords
5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
-5.0f, -0.5f, 5.0f, 0.0f, 0.0f,
-5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
-5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
5.0f, -0.5f, -5.0f, 2.0f, 2.0f
};
float quadVertices[] = { // vertex attributes for a quad that fills the entire screen in Normalized Device Coordinates.
// positions // texCoords
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
// cube VAO
unsigned int 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(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
// plane VAO
unsigned int planeVAO, planeVBO;
glGenVertexArrays(1, &planeVAO);
glGenBuffers(1, &planeVBO);
glBindVertexArray(planeVAO);
glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
// screen quad VAO
unsigned int 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(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
// load textures
// -------------
unsigned int cubeTexture = loadTexture(FileSystem::getPath("resources/textures/marble.jpg").c_str());
unsigned int floorTexture = loadTexture(FileSystem::getPath("resources/textures/metal.png").c_str());
// shader configuration
// --------------------
shader.use();
shader.setInt("texture1", 0);
screenShader.use();
screenShader.setInt("screenTexture", 0);
// framebuffer configuration
// -------------------------
unsigned int framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
// create a color attachment texture
unsigned int textureColorbuffer;
glGenTextures(1, &textureColorbuffer);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 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);
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)
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT); // use a single renderbuffer object for both a depth AND stencil buffer.
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);
// draw as wireframe
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// render loop
// -----------
while (!glfwWindowShouldClose(window))
{
// per-frame time logic
// --------------------
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// input
// -----
processInput(window);
// render
// ------
// bind to framebuffer and draw scene as we normally would to color texture
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glEnable(GL_DEPTH_TEST); // enable depth testing (is disabled for rendering screen-space quad)
// make sure we clear the framebuffer's content
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader.use();
glm::mat4 model;
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
shader.setMat4("view", view);
shader.setMat4("projection", projection);
// cubes
glBindVertexArray(cubeVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, cubeTexture);
model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
model = glm::mat4();
model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
// floor
glBindVertexArray(planeVAO);
glBindTexture(GL_TEXTURE_2D, floorTexture);
shader.setMat4("model", glm::mat4());
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
// now bind back to default framebuffer and draw a quad plane with the attached framebuffer color texture
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST); // disable depth test so screen-space quad isn't discarded due to depth test.
// 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);
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);
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
// optional: de-allocate all resources once they've outlived their purpose:
// ------------------------------------------------------------------------
glDeleteVertexArrays(1, &cubeVAO);
glDeleteVertexArrays(1, &planeVAO);
glDeleteVertexArrays(1, &quadVAO);
glDeleteBuffers(1, &cubeVBO);
glDeleteBuffers(1, &planeVBO);
glDeleteBuffers(1, &quadVBO);
glfwTerminate();
return 0;
}
// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera.ProcessKeyboard(FORWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera.ProcessKeyboard(LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera.ProcessKeyboard(RIGHT, deltaTime);
}
// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// make sure the viewport matches the new window dimensions; note that width and
// height will be significantly larger than specified on retina displays.
glViewport(0, 0, width, height);
}
// glfw: whenever the mouse moves, this callback is called
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
// glfw: whenever the mouse scroll wheel scrolls, this callback is called
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
// utility function for loading a 2D texture from file
// ---------------------------------------------------
unsigned int loadTexture(char const * path)
{
unsigned int textureID;
glGenTextures(1, &textureID);
int width, height, nrComponents;
unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0);
if (data)
{
GLenum format;
if (nrComponents == 1)
format = GL_RED;
else if (nrComponents == 3)
format = GL_RGB;
else if (nrComponents == 4)
format = GL_RGBA;
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
stbi_image_free(data);
}
else
{
std::cout << "Texture failed to load at path: " << path << std::endl;
stbi_image_free(data);
}
return textureID;
}
所以這個(gè)有什么用處呢?因?yàn)槲覀兡軌蛞砸粋€(gè)紋理圖像的方式訪問已渲染場(chǎng)景中的每個(gè)像素,我們可以在片段著色器中創(chuàng)建出非常有趣的效果。這些有趣效果統(tǒng)稱為后期處理Post-processing效果。
后期處理
既然整個(gè)場(chǎng)景都被渲染到了一個(gè)紋理上,我們可以簡(jiǎn)單地通過修改紋理數(shù)據(jù)創(chuàng)建出一些非常有意思的效果。在這一部分中,我們將會(huì)向你展示一些流行的后期處理效果,并告訴你改如何使用創(chuàng)造力創(chuàng)建你自己的效果。
讓我們先從最簡(jiǎn)單的后期處理效果開始。
1. 反相
我們現(xiàn)在能夠訪問渲染輸出的每個(gè)顏色,所以在(譯注:屏幕的)片段著色器中返回這些顏色的反相(Inversion)并不是很難。我們將會(huì)從屏幕紋理中取顏色值,然后用1.0減去它,對(duì)它進(jìn)行反相:
void main()
{
FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
}
盡管反相是一個(gè)相對(duì)簡(jiǎn)單的后期處理效果,它已經(jīng)能創(chuàng)造一些奇怪的效果了:

在片段著色器中僅僅使用一行代碼,就能讓整個(gè)場(chǎng)景的顏色都反相了。很酷吧?
2. 灰度
另外一個(gè)很有趣的效果是,移除場(chǎng)景中除了黑白灰以外所有的顏色,讓整個(gè)圖像灰度化(Grayscale)。很簡(jiǎn)單的實(shí)現(xiàn)方式是,取所有的顏色分量,將它們平均化:
void main()
{
FragColor = texture(screenTexture, TexCoords);
float average = (FragColor.r + FragColor.g + FragColor.b) / 3.0;
FragColor = vec4(average, average, average, 1.0);
}
這已經(jīng)能創(chuàng)造很好的結(jié)果了,但人眼會(huì)對(duì)綠色更加敏感一些,而對(duì)藍(lán)色不那么敏感,所以為了獲取物理上更精確的效果,我們需要使用加權(quán)的(Weighted)通道:
void main()
{
FragColor = texture(screenTexture, TexCoords);
float average = 0.2126 * FragColor.r + 0.7152 * FragColor.g + 0.0722 * FragColor.b;
FragColor = vec4(average, average, average, 1.0);
}

你可能不會(huì)立刻發(fā)現(xiàn)有什么差別,但在更復(fù)雜的場(chǎng)景中,這樣的加權(quán)灰度效果會(huì)更真實(shí)一點(diǎn)。
核效果
在一個(gè)紋理圖像上做后期處理的另外一個(gè)好處是,我們可以從紋理的其它地方采樣顏色值。比如說我們可以在當(dāng)前紋理坐標(biāo)的周圍取一小塊區(qū)域,對(duì)當(dāng)前紋理值周圍的多個(gè)紋理值進(jìn)行采樣。我們可以結(jié)合它們創(chuàng)建出很有意思的效果。
核(Kernel)(或卷積矩陣(Convolution Matrix))是一個(gè)類矩陣的數(shù)值數(shù)組,它的中心為當(dāng)前的像素,它會(huì)用它的核值乘以周圍的像素值,并將結(jié)果相加變成一個(gè)值。所以,基本上我們是在對(duì)當(dāng)前像素周圍的紋理坐標(biāo)添加一個(gè)小的偏移量,并根據(jù)核將結(jié)果合并。下面是核的一個(gè)例子:

這個(gè)核取了8個(gè)周圍像素值,將它們乘以2,而把當(dāng)前的像素乘以-15。這個(gè)核的例子將周圍的像素乘上了一個(gè)權(quán)重,并將當(dāng)前像素乘以一個(gè)比較大的負(fù)權(quán)重來平衡結(jié)果。
你在網(wǎng)上找到的大部分核將所有的權(quán)重加起來之后都應(yīng)該會(huì)等于1,如果它們加起來不等于1,這就意味著最終的紋理顏色將會(huì)比原紋理值更亮或者更暗了。
核是后期處理一個(gè)非常有用的工具,它們使用和實(shí)驗(yàn)起來都很簡(jiǎn)單,網(wǎng)上也能找到很多例子。我們需要稍微修改一下片段著色器,讓它能夠支持核。我們假設(shè)使用的核都是3x3核(實(shí)際上大部分核都是):
const float offset = 1.0 / 300.0;
void main()
{
vec2 offsets[9] = vec2[](
vec2(-offset, offset), // 左上
vec2( 0.0f, offset), // 正上
vec2( offset, offset), // 右上
vec2(-offset, 0.0f), // 左
vec2( 0.0f, 0.0f), // 中
vec2( offset, 0.0f), // 右
vec2(-offset, -offset), // 左下
vec2( 0.0f, -offset), // 正下
vec2( offset, -offset) // 右下
);
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 = vec3(0.0);
for(int i = 0; i < 9; i++)
col += sampleTex[i] * kernel[i];
FragColor = vec4(col, 1.0);
}
在片段著色器中,我們首先為周圍的紋理坐標(biāo)創(chuàng)建了一個(gè)9個(gè)vec2偏移量的數(shù)組。偏移量是一個(gè)常量,你可以按照你的喜好自定義它。之后我們定義一個(gè)核,在這個(gè)例子中是一個(gè)銳化(Sharpen)核,它會(huì)采樣周圍的所有像素,銳化每個(gè)顏色值。最后,在采樣時(shí)我們將每個(gè)偏移量加到當(dāng)前紋理坐標(biāo)上,獲取需要采樣的紋理,之后將這些紋理值乘以加權(quán)的核值,并將它們加到一起。
這個(gè)銳化核看起來是這樣的:

這能創(chuàng)建一些很有趣的效果,比如說你的玩家打了麻醉劑所感受到的效果。
1. 模糊
創(chuàng)建模糊(Blur)效果的核是這樣的:

由于所有值的和是16,所以直接返回合并的采樣顏色將產(chǎn)生非常亮的顏色,所以我們需要將核的每個(gè)值都除以16。最終的核數(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
);
通過在片段著色器中改變核的float數(shù)組,我們完全改變了后期處理效果。它現(xiàn)在看起來是這樣子的:

這樣的模糊效果創(chuàng)造了很多的可能性。我們可以隨著時(shí)間修改模糊的量,創(chuàng)造出玩家醉酒時(shí)的效果,或者在主角沒帶眼鏡的時(shí)候增加模糊。模糊也能夠讓我們來平滑顏色值,我們將在之后教程中使用到。
你可以看到,只要我們有了這個(gè)核的實(shí)現(xiàn),創(chuàng)建炫酷的后期處理特效是非常容易的事。我們?cè)賮砜醋詈笠粋€(gè)很流行的效果來結(jié)束本節(jié)的討論。
2. 邊緣檢測(cè)
下面的邊緣檢測(cè)(Edge-detection)核和銳化核非常相似:

這個(gè)核高亮了所有的邊緣,而暗化了其它部分,在我們只關(guān)心圖像的邊角的時(shí)候是非常有用的。

你可能不會(huì)奇怪,像是Photoshop這樣的圖像修改工具/濾鏡使用的也是這樣的核。因?yàn)轱@卡處理片段的時(shí)候有著極強(qiáng)的并行處理能力,我們可以很輕松地在實(shí)時(shí)的情況下逐像素對(duì)圖像進(jìn)行處理。所以圖像編輯工具在圖像處理的時(shí)候會(huì)更傾向于使用顯卡。
注意,核在對(duì)屏幕紋理的邊緣進(jìn)行采樣的時(shí)候,由于還會(huì)對(duì)中心像素周圍的8個(gè)像素進(jìn)行采樣,其實(shí)會(huì)取到紋理之外的像素。由于環(huán)繞方式默認(rèn)是
GL_REPEAT,所以在沒有設(shè)置的情況下取到的是屏幕另一邊的像素,而另一邊的像素本不應(yīng)該對(duì)中心像素產(chǎn)生影響,這就可能會(huì)在屏幕邊緣產(chǎn)生很奇怪的條紋。為了消除這一問題,我們可以將屏幕紋理的環(huán)繞方式都設(shè)置為GL_CLAMP_TO_EDGE。這樣子在取到紋理外的像素時(shí),就能夠重復(fù)邊緣的像素來更精確地估計(jì)最終的值了。
后記
未完,待續(xù)~~~
