WebGPU學習(三):MSAA


大家好,本文學習MSAA以及在WebGPU中的實現。

上一篇博文
WebGPU學習(二): 學習“繪制一個三角形”示例

下一篇博文
WebGPU學習(四):Alpha To Coverage

學習MSAA

介紹

MSAA(多重采樣抗鋸齒),是硬件實現的抗鋸齒技術

動機

參考深入剖析MSAA

具體到實時渲染領域中,走樣有以下三種:
1.幾何體走樣(幾何物體的邊緣有鋸齒),幾何走樣由於對幾何邊緣采樣不足導致。
2.着色走樣,由於對着色器中着色公式(渲染方程)采樣不足導致。比較明顯的現象就是高光閃爍。
3.時間走樣,主要是對高速運動的物體采樣不足導致。比如游戲中播放的動畫發生跳變等。

這里討論幾何體走樣。
anti_aliasing_rasterization.png-27.2kB

如上圖所示,我們要繪制一個三角形。它由三個頂點組成,紅線范圍內的三角形是片元primitive覆蓋的區域。
primitive會被光柵化為fragment,而一個fragment最終對應屏幕上的一個像素,如圖中的小方塊所示。

gpu會根據像素中心的采樣點是否被pritimive覆蓋來判斷是否生成該fragment和執行對應的fragment shader。

圖中紅色的點是被覆蓋的采樣點,它所在的像素會被渲染。

下圖是最終渲染的結果,我們看到三角形邊緣產生了鋸齒:
anti_aliasing_rasterization_filled.png-14.2kB

原理

MSAA通過增加采樣點來減輕幾何體走樣。
它包括4個步驟:
1.針對采樣點進行覆蓋檢測
2.每個被覆蓋的fragment執行一次fragment shader
3.針對采樣點進行深度檢測和模版檢測
4.解析(resolve)

下面以4X MSAA為例(每個像素有4個采樣點),說明每個步驟:

1.針對采樣點進行覆蓋檢測

gpu會計算每個fragment的coverage(覆蓋率),從而得知對應像素的每個采樣點是否被覆蓋的信息。

coverage相關知識可以參考WebGPU學習(四):Alpha To Coverage -> 學習Alpha To Coverage -> 原理

這里為了簡化,我們只考慮通過“檢測每個像素有哪些采樣點被primitive覆蓋”來計算coverager:

anti_aliasing_rasterization_samples.png-38.9kB

如上圖所示,藍色的采樣點是在三角形中,是被覆蓋的采樣點。

2.每個被覆蓋的fragment執行一次fragment shader

如果一個像素至少有一個采樣點被覆蓋,那么會執行一次它對應的fragment(注意,只執行一次哈,不是執行4次)(它所有的輸入varying變量都是針對其像素中心點而言的,所以計算的輸出的顏色始終是針對該柵格化出的像素中心點而言的),輸出的顏色保存在color buffer中(覆蓋的采樣點都要保存同一個輸出的顏色)

3.針對采樣點進行深度檢測和模版檢測

所有采樣點的深度值和模版值都要存到depth buffer和stencil buffer中(無論是否被覆蓋)。

被覆蓋的采樣點會進行深度檢測和模版檢測,通過了的采樣點會進入“解析”步驟。

那為什么要保存所有采樣點的深度和模版值了(包括沒有被覆蓋的)?因為它們在深度檢測和模版檢測階段決定所在的fragment是否被丟棄:

那是因為之后需要每個sample(采樣點)都執行一下depth-test,以確定整個fragment是否要流向(通往緩沖區輸出的)流水線下一階段——只有當全部fragment-sample的Depth-Test都Fail掉的時候,才決定拋棄掉這個fragment(蒙版值stencil也是這樣的,每個sample都得進行Stencil-Test)。

4.解析

什么是解析?

根據深入剖析MSAA 的說法:

像超采樣一樣,過采樣的信號必須重新采樣到指定的分辨率,這樣我們才可以顯示它。
這個過程叫解析(resolving)。

根據亂彈紀錄II:Alpha To Coverage 的說法:

在把所有像素輸出到渲染緩沖區前執行Resolve以生成單一像素值。
。。。。。。
也該是時候談到一直說的“計算輸出的顏色”是怎么一回事了。MultiSample的Resolve階段,如果是屏幕輸出的話這個階段會發生在設備的屏幕輸出直前;如果是FBO輸出,則是發生在把這個Multisample-FBO映射到非multisample的FBO(或屏幕)的時候(見[多重采樣(MultiSample)下的FBO反鋸齒] )。Resolve,說白了就是把MultiSample的存儲區域的數據,根據一定法則映射到可以用於顯示的Buffer上了(這里的輸出緩沖區包括了Color、Depth或還有Stencil這幾個)。Depth和Stencil前面已經提及了法則了,Color方面其實也簡單,一般的顯卡的默認處理就是把sample的color取平均了。注意,因為depth-test等Test以及Coverage mask的影響下,有些sample是不參與計算的(被摒棄),例如4XMSAA下上面的0101,就只有兩個sample,又已知各sample都對應的只是同一個顏色值,所以輸出的顏色 = 2 * fragment color / 4 = 0.5 * fragment color。也就是是說該fragemnt最終顯示到屏幕(或Non-MS-FBO)上是fragment shader計算出的color值的一半——這不僅是顏色亮度減半還包括真·透明度值的減半。

我的理解:
“解析”是把每個像素經過上述步驟得到的采樣點的顏色值,取平均值,得到這個像素的顏色值。

anti_aliasing_sample_points.png-6.7kB
如上圖右邊所示,像素的2個采樣點進入了“解析”,最終該像素的顏色值為 0.5(2/4) * 原始顏色值

經過上述所有步驟后,最終的渲染結果如下:
anti_aliasing_rasterization_samples_filled.png-50.4kB

總結

MSAA能減輕幾何體走樣,但會增加color buffer、depth buffer、stencil buffer開銷。

參考資料

深入剖析MSAA
亂彈紀錄II:Alpha To Coverage
Anti Aliasing

WebGPU實現MSAA

有下面幾個要點:

  • 能夠查詢最大的采樣個數sample count

目前我沒找到查詢的方法,但至少支持4個采樣點

參考 Investigation: Multisampled Render Targets and Resolve Operations

We can say that 4xMSAA is guaranteed on all WebGPU implementations, and we need to provide APIs for queries on whether we can create a multisampled texture with given format and sample count.

  • 設置sample count
dictionary GPURenderPipelineDescriptor : GPUPipelineDescriptorBase {
...
    unsigned long sampleCount = 1;
...
};
dictionary GPUTextureDescriptor : GPUObjectDescriptorBase {
...
    unsigned long sampleCount = 1;
...
};

我們在WebGPU 規范中看到render pipeline descriptor和texture descriptor可以設置sampleCount。

  • 設置resolveTarget

在“解析”步驟中,需要重新采樣到指定的分辨率:

過采樣的信號必須重新采樣到指定的分辨率,這樣我們才可以顯示它

所以我們應該設置color的resolveTarget(depth、stencil不支持resolveTarget),它包含“分辨率”的信息。

我們來看下WebGPU 規范:

dictionary GPURenderPassColorAttachmentDescriptor {
    required GPUTextureView attachment;
    GPUTextureView resolveTarget;

    required (GPULoadOp or GPUColor) loadValue;
    GPUStoreOp storeOp = "store";
};

resolveTarget在render pass colorAttachment descriptor中設置,它的類型是GPUTextureView。

而GPUTextureView是從GPUTexture得來的,我們來看下GPUTexture的descriptor的定義:

dictionary GPUExtent3DDict {
    required unsigned long width;
    required unsigned long height;
    required unsigned long depth;
};
typedef (sequence<unsigned long> or GPUExtent3DDict) GPUExtent3D;

dictionary GPUTextureDescriptor : GPUObjectDescriptorBase {
...
  required GPUExtent3D size;
...
};

GPUTextureDescriptor的size屬性有width和height屬性,只要texture對應了屏幕大小,我們就能獲得屏幕“分辨率”的信息(depth設為1,因為分辨率只有寬、高,沒有深度)。

實現sample

我們對應到sample來看下。

打開webgpu-samplers->helloTriangleMSAA.ts文件。

代碼基本上與我們上篇文章學習的webgpu-samplers->helloTriangle.ts差不多,

我們看下創建render pipeline代碼

    const sampleCount = 4;

    const pipeline = device.createRenderPipeline({
    ...
      sampleCount,
    });

這里設置了sample count為4

我們看下frame函數->render pass descrptor代碼

      const renderPassDescriptor: GPURenderPassDescriptor = {
        colorAttachments: [{
          attachment: attachment,
          resolveTarget: swapChain.getCurrentTexture().createView(),
          ...
        }],
      };
  • 設置attachment為多重采樣的texture的view

該texture的創建代碼為:

    const texture = device.createTexture({
      size: {
        width: canvas.width,
        height: canvas.height,
        depth: 1,
      },
      sampleCount,
      format: swapChainFormat,
      usage: GPUTextureUsage.OUTPUT_ATTACHMENT,
    });
    const attachment = texture.createView();

注意:texture的sampleCount應該與render pipeline的sampleCount一樣,都是4

  • 設置resolveTarget為swapChain對應的view

swapChain.getCurrentTexture()獲得的texture的大小是與屏幕相同,所以它包含了屏幕分辨率的信息。

參考資料

helloTriangleMSAA.ts
Investigation: Multisampled Render Targets and Resolve Operations


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM