[MetalKit]16-Using-MetalKit-part-10使用MetalKit10

蘋果API搬運工發表於2017-12-14

本系列文章是對 metalkit.org 上面MetalKit內容的全面翻譯和學習.

MetalKit系統文章目錄


今天我們關注Metal function中沒用過的型別,kernel function核心函式compute shader計算著色器.你將經常聽到它們兩個的混合詞變形詞.核心是用於計算任務,也就是在GPU上進行大規模平行計算.例如:影像處理,科學模擬,等等.關於核心有一些重要特點:沒有渲染管線,函式總是返回void,並且名字總是以kernel關鍵字開頭,就像我們以前用過的前面帶有vertexvertex關鍵字的函式一樣.

讓我們從第8部分Part 8的playground處繼續.首先,刪除MathUtils.swift因為我們已經不需要了.然後,在MetalView.swift中刪除createBuffers()函式及其在初始化中的呼叫,還有兩個緩衝器.將MTLRenderPipelineState宣告替換為MTLComputePipelineState宣告.下一步,來到registerShaders()函式.下面是新舊兩個版本的不同:

chapter10_1.png

注意,我們不在使用descriptor了,而是在核心函式中直接建立MTLComputePipelineState.下一步,我們看看drawRect()函式的不同:

chapter10_2.png

注意,currentRenderPassDescriptor也不用了.命令編碼器則用computeCommandEncoder()函式來建立.顯然,我們也不再需要設定頂點緩衝器和繪製基本體了.作為替代,使用用一個設定了紋理的核心函式,建立執行緒組並指派它們幹活.我們用MTLSize來設定執行緒組的維數,及每次計算呼叫中要執行的執行緒組的數量.

最後,我們到Shaders.metal檔案中,用下面程式碼替換所有內容:

#include <metal_stdlib>

using namespace metal;

kernel void compute(texture2d<float, access::write> output [[texture(0)]],
                    uint2 gid [[thread_position_in_grid]])
{
    output.write(float4(0, 0.5, 0.5, 1), gid);
}
複製程式碼

我們只簡單地給紋理中的每個畫素/位置設定了相同的顏色.現在如果你到playground的主頁面,並顯示Assistant editor中的Timeline,你應該能看到類似的檢視:

chapter10_3.png

如果你看到了上面的輸出,就說明準備好繼續下去了.從當前開始,我們將不再關注主程式碼(MetalView.swift)了,因為我們所有的工作都將在核心著色器中完成.

好了,讓我們先從簡單的開始.用下面的程式碼替換核心函式中的程式碼:

int width = output.get_width();
int height = output.get_height();
float red = float(gid.x) / float(width);
float green = float(gid.y) / float(height);
output.write(float4(red, green, 0, 1), gid);
複製程式碼

你可能已經猜到了,我們拿到紋理的widthheight,然後根據畫素在紋理中的位置來計算redgreen的值,然後將新顏色寫入回紋理中.你將看到類似這樣的東西:

chapter10_4.png

接著,讓我們在螢幕中間畫一個黑色的圓.用下面幾行程式碼替換最後一行:

float2 uv = float2(gid) / float2(width, height);
uv = uv * 2.0 - 1.0;
bool inside = length(uv) < 0.5;
output.write(inside ? float4(0) : float4(red, green, 0, 1), gid); 
複製程式碼

你會看到類似這樣的東西:

chapter10_5.png

我們到底是怎麼做到的呢?其實,這是在著色中很常用的技術,叫做distance function.我們使用length函式來確定畫素是否在螢幕中心也就是我們圓的中心的0.5倍之內.注意,我們歸一化了uv向量來匹配視窗座標範圍 [-1,1].最後,我們判斷畫素如果在內部就是黑色,否則就像原來一樣,給它一個漸變色.

讓我們抽出這個圓內部/外部計算到一個距離函式中:

float dist(float2 point, float2 center, float radius)
{
    return length(point - center) - radius;
} 
複製程式碼

然後,用下面幾行替換我們定義內部的那行:

float distToCircle = dist(uv, float2(0), 0.5);
bool inside = distToCircle < 0;
複製程式碼

看不到任何改變,但我們現在有了一個可以輕易重用的函式.下一步,讓我們看看如何根據到圓的距離改變背景顏色,而不是僅根據畫素的絕對位置.我們通過計算畫素到圓的距離來改變透明通道的值.用下面這行替換最後一行:

output.write(inside ? float4(0) : float4(1, 0.7, 0, 1) * (1 - distToCircle), gid);
複製程式碼

你應該看到類似這樣的東西:

chapter10_6.png

很漂亮,對吧?現在我們讓它變成了日食,讓我們將它變得更真實一些.我們需要另一個圓(太陽),並且我們想要讓初始的圓向左一點,向下一點,這樣它們就都能看到了.用下面幾行替換我們定義內部的那行:

float distToCircle2 = dist(uv, float2(-0.1, 0.1), 0.5);
bool inside = distToCircle2 < 0;
複製程式碼

你會看到類似下面的東西:

chapter10_7.png

我們現在只是學會了著色技術的皮毛.在下一章節我們將學習更復雜和動態的計算任務.特別感謝Chris Wood的建議.

原始碼source code 已釋出在Github上.

下次見!

相關文章