抗鋸齒化(Anti Aliasing)
- 鋸齒形邊緣(jagged edges) 出現(xiàn)的原因取決于光柵化時(shí)如何將頂點(diǎn)數(shù)據(jù)轉(zhuǎn)換為實(shí)際片元沮榜。
- 最早,有一種叫做超分辨率抗鋸齒化(super sample anti-aliasing, SSAA) 的技術(shù)喻粹,它使用一個(gè)更高分辨率的渲染緩沖區(qū)來(lái)渲染場(chǎng)景蟆融,然后當(dāng)整個(gè)場(chǎng)景渲染完成時(shí)再通過(guò)降采樣將分辨率恢復(fù)正常。因?yàn)檫@項(xiàng)技術(shù)在性能上有重大缺點(diǎn)守呜,因此也只應(yīng)用一時(shí)型酥。
- 從SSAA技術(shù)發(fā)展了更現(xiàn)代的抗鋸齒化技術(shù):多重采樣抗鋸齒化(multisample anti-aliasing, MSAA)山憨,它借用了SSAA的一些概念但以更高效的方式實(shí)現(xiàn)。本章節(jié)重點(diǎn)介紹OpenGL內(nèi)置的MSAA技術(shù)冕末。
1. 多重采樣
- 想要了解多重采樣和它如何解決鋸齒化問(wèn)題萍歉,我們需要更深入的了解OpenGL的光柵化程序(rasterizer)。光柵化程序接收單個(gè)基元的頂點(diǎn)數(shù)據(jù)將其轉(zhuǎn)換為片元集合档桃。頂點(diǎn)坐標(biāo)理論上可以是任何坐標(biāo)枪孩,但是由于顯示屏幕分辨率的緣故,片元卻不能藻肄。因此頂點(diǎn)和片元之間的坐標(biāo)幾乎不可能進(jìn)行一對(duì)一轉(zhuǎn)換蔑舞,所以光柵化程序需要以某種方式將指定頂點(diǎn)坐標(biāo)轉(zhuǎn)換為最終的片元/屏幕坐標(biāo)。見(jiàn)下圖:(圖片取自書(shū)中)
光柵化 - 如上圖所示嘹屯,在一個(gè)像素網(wǎng)格中攻询,每個(gè)像素的中心都有一個(gè)采樣點(diǎn)(sample point),采樣點(diǎn)決定了該像素是否包含在三角形中州弟。如果采樣點(diǎn)包含在三角形中(圖中紅色采樣點(diǎn))則為對(duì)應(yīng)的像素生成片元钧栖。但是在三角形的邊上,雖然三角形經(jīng)過(guò)屏幕像素婆翔,可采樣點(diǎn)并不包含在三角形中拯杠,因此片元著色器并不會(huì)影響到該屏幕像素。
- 對(duì)于單個(gè)采樣點(diǎn)啃奴,上圖中的三角形最終渲染結(jié)果大致如下:(圖片取自書(shū)中)
單采樣點(diǎn)三角形渲染 - 多重采樣所做的潭陪,就是不使用單個(gè)采樣點(diǎn)決定基元是否包含某個(gè)屏幕像素,而是多個(gè)采樣點(diǎn)最蕾。例如我們以常規(guī)方式放置四個(gè)子采樣(subsamples)來(lái)決定是否包含像素依溯。從下圖我們可以看出,左側(cè)單個(gè)采樣點(diǎn)像素未包含在三角形中瘟则,因此該像素不會(huì)運(yùn)行片元著色器輸出顏色黎炉;右側(cè)四個(gè)采樣點(diǎn)中有兩個(gè)包含在三角形中,因此像素運(yùn)行片元著色器產(chǎn)生顏色醋拧。(圖片取自書(shū)中)
單采樣點(diǎn)與多采樣點(diǎn) - 注意:采樣的數(shù)量可以是任何數(shù)字拜隧,更多的采樣點(diǎn)能更精確地決定部分包含的像素的渲染結(jié)果。
- 在MSAA中趁仙,對(duì)于每個(gè)像素片元著色器只運(yùn)行一次,不管該像素中多少個(gè)子采樣點(diǎn)被基元包含垦页。片元著色器使用插值到像素中心的頂點(diǎn)數(shù)據(jù)運(yùn)行雀费,然后MSAA使用一個(gè)更大的深度/模板緩沖區(qū)來(lái)決定是否包含子采樣點(diǎn),最后被包含的子采樣點(diǎn)的數(shù)量決定了將多少三角形的顏色寫(xiě)入到幀緩沖區(qū)痊焊。例如上面的圖中盏袄,四個(gè)子采樣點(diǎn)中有兩個(gè)被三角形包含忿峻,那么像素最終的顏色就是一半三角形的顏色與一半幀緩沖區(qū)的顏色的混合。
- 最終的結(jié)果就是使用更高分辨率的顏色緩沖區(qū)(和更高分辨率的深度/模板緩沖區(qū))來(lái)產(chǎn)生光滑的邊緣辕羽。(圖片取自書(shū)中)
多采樣點(diǎn) - 最后三角形邊緣的渲染結(jié)果大致如下逛尚,注意邊緣像素的顏色:(圖片取自書(shū)中)
多采樣點(diǎn)渲染 - 注意:雖然片元著色器每個(gè)像素只運(yùn)行一次,但是顏色值刁愿、深度值和模板值則是按子采樣點(diǎn)數(shù)量存儲(chǔ)绰寞。
2. OpenGL中的MSAA
- 如果我們想要使用OpenGL的MSAA,我們需要一個(gè)能存儲(chǔ)每個(gè)像素不止一個(gè)采樣值的緩沖區(qū)——一種能存儲(chǔ)指定數(shù)量采樣的緩沖區(qū)類型铣口,多重采樣緩沖區(qū)(multisample buffer)滤钱。
- 大部分窗體系統(tǒng)都為我們提供了一個(gè)多重采樣緩沖區(qū)。在GLFW中脑题,我們只需在創(chuàng)建窗體之前調(diào)用
glfwWindowHint
函數(shù)指示GLFW我們需要N采樣的多重采樣緩沖區(qū)來(lái)代替常規(guī)緩沖區(qū)即可件缸。
glfwWindowHint(GLFW_SAMPLES, 4);
- 在OpenGL中我們則只需調(diào)用
glEnable
函數(shù)啟動(dòng)多重采用即可(不過(guò)OpenGL一般默認(rèn)啟動(dòng)多重采樣)。
glEnable(GL_MULTISAMPLE);
-
下面是單采樣和多采樣的綠色立方體渲染效果叔遂。
單采樣點(diǎn)立方體
多采樣點(diǎn)立方體
3. 離屏MSAA
- 因?yàn)镚LFW內(nèi)置了多重采樣緩沖區(qū)的創(chuàng)建他炊,因此啟動(dòng)多重采樣很容易。但是如果我們想使用自己的幀緩沖區(qū)已艰,我們必須自己生成多重采樣緩沖區(qū)痊末。有兩種方式可以生成多重采樣緩沖區(qū)來(lái)作為幀緩沖區(qū)的附件:紋理附件和渲染緩沖區(qū)附件。與我們?cè)趲彌_區(qū)章節(jié)討論的常規(guī)附件相似旗芬。
3.1 多重采樣紋理附件
- 要?jiǎng)?chuàng)建支持存儲(chǔ)多個(gè)采樣點(diǎn)的紋理我們使用
glTexImage2DMultisample
而不是glTexImage2D
舌胶。
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
// samples: 采樣點(diǎn)數(shù)
// 最后一個(gè)參數(shù)GL_TRUE表示:對(duì)紋理像素采用相同的采用位置和相同的子采樣數(shù)量
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, width, height, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
- 要將多重采樣紋理附加到幀緩沖區(qū),我們使用
glFramebufferTexture2D
函數(shù)疮丛。
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);
3.2 多重采樣渲染緩沖區(qū)對(duì)象
- 與紋理相似幔嫂,創(chuàng)建多重采樣渲染緩沖區(qū)對(duì)象并不困難,我只需將
glRenderbufferStorage
函數(shù)更換為glRenderbufferStorageMultisample
誊薄,并配置(當(dāng)前綁定)渲染緩沖區(qū)的內(nèi)存履恩。
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height);
3.3 渲染到多重采樣幀緩沖區(qū)
- 因?yàn)槎嘀夭蓸訋彌_區(qū)比較特別,對(duì)于一些操作如著色器中的采樣呢蔫,我們不能直接使用切心。解析一個(gè)多重采樣的幀緩沖區(qū)一般使用
glBlitFramebuffer
函數(shù),該函數(shù)從一個(gè)幀緩沖區(qū)中的一個(gè)區(qū)域?qū)?shù)據(jù)拷貝到另外一個(gè)緩沖區(qū)中片吊,同時(shí)對(duì)多重采樣緩沖區(qū)進(jìn)行了解析绽昏。從幀緩沖區(qū)章節(jié)我們知道有GL_READ_FRAMEBUFFER
和GL_DRAW_FRAMEBUFFER
兩個(gè)緩沖區(qū)目標(biāo)。glBlitFramebuffer
函數(shù)能夠從這兩個(gè)目標(biāo)讀取數(shù)據(jù)并自動(dòng)確定哪一個(gè)是源哪一個(gè)是目標(biāo)幀緩沖區(qū)俏脊。因此我們可以通過(guò)將圖像塊傳輸?shù)?strong>默認(rèn)幀緩沖區(qū)來(lái)將多重采樣幀緩沖區(qū)數(shù)據(jù)輸出到屏幕全谤。
glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampleFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
- 綜上所述,離屏MSAA的渲染循環(huán)中的大致過(guò)程如下:
// framebuffer:多重采樣的幀緩沖區(qū)
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
ClearScene();
DrawScene();
// 將多重采樣幀緩沖區(qū)輸出到默認(rèn)幀緩沖區(qū)(屏幕顯示的緩沖區(qū))
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
-
渲染效果如下爷贫,與直接在窗體系統(tǒng)設(shè)置多重采樣相似:
離屏MSAA渲染 - 如果我們想對(duì)多重采樣幀緩沖區(qū)進(jìn)行后處理认然,因?yàn)槲覀儾荒苤苯邮褂卯?dāng)前片元著色器中的多重采樣紋理补憾,因此我們需要再創(chuàng)建一個(gè)非多重采樣的中間幀緩沖區(qū)。我們先將多重采樣幀緩沖區(qū)的數(shù)據(jù)拷貝到中間幀緩沖區(qū)卷员,然后對(duì)中間幀緩沖區(qū)的常規(guī)2D紋理進(jìn)行后處理盈匾,再將其顯示到屏幕上。大致過(guò)程如下:
CreateSceneAndSetData();
CreateMultisampleFramebuffer();
CreateScreenAndSetData();
CreateIntermediaFramebuffer();
SetScreenTexture();
while (!glfwWindowShouldClose(window))
{
BindMultisampleFramebuffer();
DrawScene();
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
// 目標(biāo)設(shè)置為中間幀緩沖區(qū)
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO);
glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
DrawScreenTexture();
glfwSwapBuffers(window);
glfwPollEvents();
}
-
一個(gè)灰度化的立方體效果毕骡。
離屏MSAA后處理效果