深入理解多重采样(Multisampling)
轉載請標明出處:http://blog.csdn.net/yunchao_he/article/details/78354528
Multi-sampling或者說Multi-sample Anti-Alias (簡稱MSAA)是一種抗鋸齒的技術,它通過在一個像素上進行多次采樣多次計算并最終匯總(Resolve to single-sample),可使繪制的圖像邊緣更加平滑。通過這種方式繪制出來的圖片質量更高,顯得更真實。但同時,它對繪制的性能也會產生負面影響。所以,是否使用這項技術,需要開發者在圖片質量(Quality)和性能(Performance)之間進行權衡。那么,MSAA到底對整個繪制流水線(Rendering Pipeline)產生了什么影響?本文將進行深入分析,從而幫助自己及讀者在相關問題上有更深的理解,從而做出正確合理的決策:比如在開發過程中是否使用MSAA。
MSAA簡介
首先簡單講一下什么是MSAA。MSAA就是把每個pixel(或者說fragment)細分為多個sub-pixel,比如分為4個、8個、16個甚至32個sub-pixel,分別對應MSAA4, MSAA8, MSAA16, MSAA32。我們知道,圖形學里,每個piexl占據屏幕上的一小塊矩形網格。比如對于1920*1280的顯示器,就有1920*1280個小的矩形網格,每個網格都是一個pixel。而MSAA則把每個小的矩形網格再進行細分。比如MSAA4/MSAA8分別把每個piexl再分為4個或者8個sub-pixel,其中每一個sub-pixel稱為一個sample。而正常pipeline里的所有per-pixel(per-fragment)的操作,打開MSAA后,理論上都可以per-sample來處理。這樣,每個pixel里的多個sample,?都可以獨立進行插值、獨立執行fragment shader,計算出獨立的顏色值、深度值。然后求出同一個pixel的所有sample的算術平均值(也就是resolve to single sample),就得出這個pixel的最終顏色。通過這種方式,圖形邊緣的繪制會更精細更平滑。當然,對于1920*1280的網格,MSAA4相當于在處理1920*1280*4個網格,計算量(以及顯存里某些變量的存儲空間)也是成倍增加。
MSAA分析
MSAA在Rendering Pipeline的過程中,可能的影響有以下幾方面。
1)光柵化階段(Rasterization)
光柵化階段一個重要的工作就是插值計算(Interpolation),所以多重采樣作用到這個階段主要是多重插值。?
這個階段的multisampling可以分為幾種,一種是重量級的多重插值,一種是定制化的多重插值,還有一種是輕量級的多重插值。當然,這只是我個人的簡單分類,具體區別詳見下文。
先講重量級、重負載的多重插值。我們知道,在Rasterization階段,需要對fragment shader階段的所有inputs進行插值計算。可能的插值計算變量包括但不限于顏色,法線,紋理坐標等等。比如上圖手繪的圖片里,對三角形的繪制,開發者通常只設置頂點信息。這里以顏色為例,三個頂點A/B/C的顏色分別為藍色、黑色、紅色。三角形覆蓋的區域(網格區),都是GPU在Rasterization階段根據各個像素所在位置,進行插值計算,得出各個pixel/fragment的顏色值。這個顏色值顯然是三個頂點顏色值的混合。理論上,凡是需要per-pixel插值的變量,也可以進行per-sample插值,也就是多重插值。注意,這里的“多重”,其實是站在每個pixel(或者說fragment)的角度。因為同一個pixel有多個sample,每個sample都是對這個pixel進行了細分,是它的sub-pixel(可理解為多個"子網格")。根據具體硬件的布局實現,同一個pixel內的多個sample之間,位置有差異,從而可計算出per-sample的更精細的值。這樣就相當于對這個pixel進行了多次插值計算。實際上對于每個sample,?當然還是只進行一次插值計算。
如果對所有需要插值的變量,比如fragment shader的所有inputs,都進行這種多重插值,這就是重量級的多重插值。
比如對于顏色,如果是繪制一個很大的矩形,比如1920*1280的矩形, 對顏色變量進行插值計算時則需要1920*1280次計算,也需要在顯存(video memory)里占用這么多的存儲空間。這是光柵化階段不可避免的計算消耗和存儲消耗。如果打開4倍的MSAA(MSAA4),則需要1980*1280*4次計算,同時也需要相應規模的顯存空間。所以計算消耗和存儲消耗一下子擴大了4倍。當然,對于其它需要進行插值的變量也是如此。所以,如果對所有需要插值的變量都做多重插值,顯然消耗很大。但GL確實可以這么做,調用
?
?
就可以對所有變量都進行多重插值。注意,進行多重插值,不僅計算量顯著增加,顯存消耗量也會顯著增加。
第二種是定制化的多重插值。可以在vertex shader的某個或者某些outputs以及fragment shader里相應的inputs變量前加'sample'關鍵字。這樣,插值計算時只會對你指定的變量進行多重插值。比如以下的一段簡單的fragment shader代碼,對fragment shader里的部分input變量添加了'sample'關鍵字(這里是color),指定對它(們)進行多重插值,而其它變量則沒有多重插值:
#version 450 coresample in vec4 color; in vec4 normal;out vec4 fColor;void main() {// do something }這兩種多重插值,實際上都需要和per-sample shading相結合,才有意義。也即是說,需要fragment shader的執行是per sample,而不是per pixel。關于這一點,詳見后面的片段著色階段如何enable多重采樣。
最后一種,是輕量級的多重插值。它對需要插值的變量本身不進行多重插值。只針對color變量,附加coverage計算。而這個計算是per-sample的的。也就是說,對于MSAA4,它會對每個pixel申請4個bits的gl_coverage變量。記錄rasterization過程中這個sample有沒有被覆蓋。如果某個sample被覆蓋,則在gl_coverage里相應的bit位設置為1。如果某個sample沒有被覆蓋,則相應的bit位設置為0。這樣,可以根據coverage來調整最終顏色。比如處于圖像邊緣的像素,如果4個sample里有1個sample被覆蓋,而可以使用1/4來調和這個像素的顏色。從而達到MSAA的效果。但本身并不需要針對每個color進行4次插值計算,也不需要4倍的顯存空間存儲color的值。
當然,對于depth/stencil的值,又有一些區別。通常情況下,如果draw framebuffer里有depth/stencil?的多重采樣緩沖區,則會對depth/stencil的值做多重插值,并且在per-fragment operation階段,會進行per-sample的depth/stencil test.?
如果申請的render target是支持多重采樣的,則會自動enable輕量級的插值計算。這包括兩種情況,第一種情況是繪制到離屏的fbo里,這時需要使用multisample renderbuffer或者multisample texture作為color buffer,必要的話,可以使用multisample renderbuffer或者multisample texture作為depth_stencil buffer,然后進行離屏渲染,繪制到這個fbo里。而且,繪制完成后,需要開發者主動blitting到single-sample的framebuffer去顯示。Chromium里WebGL的實現,會默認打開anti-alias,用的正是這種方式。相應的示例代碼如下:
?
// 創建multisample texture glGenTextures( 1, &tex ); glBindTexture( GL_TEXTURE_2D_MULTISAMPLE, tex ); glTexStorage2DMultisample( GL_TEXTURE_2D_MULTISAMPLE, num_samples, GL_RGBA8, width, height, false );// 把multisample texture 作為fbo的color bufferglGenFramebuffers( 1, &fbo );glBindFramebuffer( GL_FRAMEBUFFER, fbo );glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0 );rendering(); // 離屏渲染,繪制到fbo里。開發者需要根據自己的業務邏輯,自行實現// blitting glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glDrawBuffer(GL_BACK); glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);另一種情況是直接繪制到窗口的默認緩存(default framebuffer),?這需要在窗口創建的時候聲明為Multisample的窗口, glut可以幫助你完成這個操作,而不需要對各種窗口系統進行處理。其代碼如下(這段代碼將創建一個RGBA顏色格式的multisample default framebuffer,?而且是雙緩沖):
?
這種情況下,系統也會自動幫你resolve到single sample的buffer并顯示,不需要開發者做更多操作。實際上,如果創建窗口默認緩存(default framebuffer)時就enable了MSAA,窗口系統會申請一個msaa的color buffer,?同時也會申請single-sample?的color buffer。但default framebuffer的depth/stencil buffer,?則只有一個msaa的depth/stencil buffer,?沒有single-sample的depth/stencil buffer.?
2)采樣階段
綁定多重采樣的texture,?然后使用texelFetch之類的采樣函數,則可從multisample texture里進行逐sample的紋理采樣(per-sample texture fetching)。當然,per-sample texture fetching可以結合gl_sampleID來進行,也就是同一個pixel里的每個sample,有自己的sampleID,然后通過gl_sampleID為每個sample獲取不同的值。以下是一個簡單的fragment shader的例子,將插值得到的color和multisample里的多重采樣值,進行融混。
#version 450uniform sample2DMS tex; in ivec2 texCoord; in color;out fColor;void main() {fColor = 0.5 * color + 0.5 * texelFetch(tex, texCoord, gl_SampleID); }當然,per-sample texture fetching也是在per-sample shading的時候才有意義。
另外需要指出的是,開發者無法初始化一個multisample texture的數據。也即是不存在類似于TexImage2D這樣的接口,去上傳初始化multisample的原始圖片。所以,multisample texture里進行采樣,像素內容必定是用戶自主render出來的。
3)片段著色階段
片段著色階段進行多重采樣,就是指fragment shader的執行不是per pixel/fragment,?而是per sample。也是說,對于1920*1280的render target,?普通情況下,需要調用1920*1280次fragment shader,?如果使用了MSAA4,則需要調用1920*1280*4次fragment shader!
所以,如果fragment shader很復雜,4倍的計算量將會嚴重影響性能。
當然,per-sample shading并不會自動打開,需要開發者主動調用上文提到的glMinSampleShading(1.0)。當然,MinSampleShading里的參數可以選擇其它數據,比如0.5。則對MSAA4,它會選擇4個sample里的2個sample進行per-sample shading,計算量為2倍。另外,前面已經講到,per-samper shading還會針對需要插值的變量,進行per-sample interpolation。這樣,同一個pixel/fragment內的每個sample的插值的結果都會不一樣。從而使計算結果更精細。當然代價也很大,既成倍增加插值計算量,也成倍增加顯存的存儲空間。同樣地,也可以在fragment shader里進行per-sample texture fetching,?為同一個pixel/fragment里的不用sample獲取不同的紋理采樣值。
4)Blitting
blitting通常是把multisample framebuffer里的像素內容,resolve到single-sample framebuffer里。如果繪制的時候使用了multisample fbo進行離屏渲染,則需要開發者自行調用blitFramebuffer。上文也有使用blitFramebuffer從離屏的msaa fbo渲染到single-sample framebuffer的例子。
小結
最后,大家可以發現,MSAA對Rasterization之前的階段都沒有直接影響。比如CPU的操作(主要是clident driver的validation等)不會受到影響,比如也不增加從CPU上傳到GPU的數據(比如頂點數據,紋理數據),也不會增加vertex shader、tessellation shader、geometry shader的執行次數。這些階段,都不會受MSAA的直接影響。如果這些階段是繪制程序的性能瓶頸,即使開啟MSAA,性能的損失可能也不會明顯。
而MSAA對性能的影響主要體現在所有per pixel/fragment的操作,比如rasterization階段的插值計算以及存儲開銷, fragment shader的執行等。不同類型的MSAA操作,會對這些階段帶來顯著影響。如果性能瓶頸在這些階段,使用MSAA后,很可能導致性能顯著下降。
一般來講,使用輕量級的multisampling技術(也就是創建multisample的framebuffer),就可以達到較好的渲染質量,性能損失也不太大。但是,如果需要處理alpha-tested transparency問題,輕量級的multisampling技術則根本不起作用。當你使用一張較大的規則圖片去表達不規則的貼圖,多余部分則需要通過alpha值(比如alpha值為0)來剔除,這時就需要使用alpha-tested transparency技術。比如一棵樹的圖片,可能是規則長方形,但樹木本身并不規整,原始圖片里樹木本身之外的像素,texture mapping時需要根據alpha值剔除,以免遮擋場景里的其它物體。這時候如果需要使用multisampling技術,則輕量級的MSAA會出問題。關于alpha-tested transparency問題,詳見參考文獻[7]。所以重量級的多重采樣,也有其應用場景和價值。
參考文檔:
[1] OpenGL, OpenGL ES specification,?主要是OpenGL 4.5/4.6, OpenGL ES 3.1/3.2?的specification.
[2] OpenGL Shading Language和OpenGL ES Shading Language,?主要是GLSL 450/460以及GLSL ES 310/320
[3] OpenGL Programming Guide 8th edition
[4] ARB_sample_shading:https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_sample_shading.txt (The Q&A part is also interesting)?
[5] OES_sample_shading:https://www.khronos.org/registry/OpenGL/extensions/OES/OES_sample_shading.txt
[6] Multisampling:https://www.khronos.org/opengl/wiki/Multisampling
[7]Transparency and alpha-tested transparency:https://www.khronos.org/opengl/wiki/Transparency_Sorting
————————————————
?
原文鏈接:https://blog.csdn.net/yunchao_he/article/details/78354528
總結
以上是生活随笔為你收集整理的深入理解多重采样(Multisampling)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: win10系统一键备份的方法是什么
- 下一篇: HTML如何制作网页动态时钟