WebGPU學習(三):MSAA

你們好,本文學習MSAA以及在WebGPU中的實現。html

上一篇博文
WebGPU學習(二): 學習「繪製一個三角形」示例git

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

學習MSAA

介紹

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

動機

參考深刻剖析MSAAcanvas

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

這裏討論幾何體走樣。
anti_aliasing_rasterization.png-27.2kB函數

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

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

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

下圖是最終渲染的結果,咱們看到三角形邊緣產生了鋸齒:
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

相關文章
相關標籤/搜索