寫在前面:
對Metal技術感興趣的同學,可以關注我的專題:Metal專輯
也可以關注我個人的簡書賬號:張芳濤
所有的代碼存儲的Github地址是:Metal
正文
今天我們將看看我們之前沒有使用的唯一其他類型的Metal function
,kernel function
(內核函數)或compute shader
(計算著色器)。 可能我們經常會聽到來自兩者的混合詞的變化。 內核用于compute
(計算)任務,即在GPU
上完成的大規模并行計算。 一些例子包括:圖像處理,科學模擬等。 關于內核要記住的幾個重要事實:沒有渲染管道,函數總是返回void
,它的名字總是以kernel
關鍵字開頭,就像我們之前使用的其他函數前面有vertex
和fragment
關鍵字一樣。
讓我們首先拆除我們在第8部分中使用的playground。首先,刪除MathUtils.swift
,因為我們不再需要它了。 然后,在MetalView.swift
中刪除createBuffers()
函數,以及它在初始化程序內的調用,以及兩個緩沖區。 將MTLRenderPipelineState
聲明替換為MTLComputePipelineState聲明。 接下來,關于registerShaders()
函數。 以下是舊版本和新版本之間的差異:
請注意,我們不再使用descriptor
(描述符),而是直接使用內核函數創建我們的MTLComputePipelineState
。 接下來,讓我們看一下drawRect()
函數的差異:
請注意,不再使用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);
}
我們基本上只為紋理中的每個像素/位置設置相同的顏色。 現在,如果您轉到操場的主頁,如果您在Assistant editor
中顯示Timeline
(時間軸),則應該具有類似的視圖:
如果你看到上面的輸出,現在就可以繼續了。 從現在開始,我們將不再查看主機代碼(MetalView.swift
),因為我們所有的工作都將在kernel shader
(內核著色器)中。
用以下代碼替換內核函數的內容:
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);
我們得到紋理的寬度和高度,然后根據紋理中的像素位置計算red
(紅色)和green
(綠色)的值,然后我們將新顏色寫回紋理。 你應該看到這樣的東西:
接下來,讓我們在屏幕中間畫一個黑色圓圈。 用這些行替換最后一行:
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);
你應該看到這樣的東西:
我們究竟是怎么做到的? 嗯,這是用于著色的常用技術,并且被命名為distance function
(距離函數)。 我們使用length
(長度)函數來確定像素是否距離我們剛剛用作圓心的屏幕中心0.5
距離。 請注意,我們將uv向量標準化以匹配窗口坐標[-1,1]
的范圍。 最后,我們看看像素是否在內部并將其著色為black
(黑色),或者使用漸變對其進行著色,就像我們之前所做的那樣。
讓我們將內/外圓計算抽象為有用的距離函數:
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;
在視覺上,沒有任何改變,但我們現在有一個我們可以在以后輕松重用的功能。 接下來,讓我們看看我們如何根據與圓的距離而不僅僅是絕對像素位置來修改背景顏色。 我們通過計算從像素到圓的距離來更改alpha通道的值。 只需用這一個替換最后一個:
output.write(inside ? float4(0) : float4(1, 0.7, 0, 1) * (1 - distToCircle), gid);
你應該看到這樣的東西:
美麗吧? 既然我們剛剛得到了太陽全蝕的想法,那就讓它看起來更逼真。 我們還需要一個圓圈(太陽),我們想要將初始圓圈稍微向左移動一點,這樣它們都可見。 用這些替換我們定義內部的行:
float distToCircle2 = dist(uv, float2(-0.1, 0.1), 0.5);
bool inside = distToCircle2 < 0;
你應該看到這樣的東西:
滿意了不?代碼地址:代碼地址