本系列文章是對 metalkit.org 上面MetalKit內容的全面翻譯和學習.c++
今天咱們關注Metal function
中沒用過的類型,kernel function內核函數或compute shader計算着色器.你將常常聽到它們兩個的混合詞變形詞.內核是用於計算
任務,也就是在GPU
上進行大規模並行計算.例如:圖像處理,科學模擬,等等.關於內核有一些重要特色:沒有渲染管線,函數老是返回void
,而且名字老是以kernel關鍵字開頭,就像咱們之前用過的前面帶有vertex
和vertex
關鍵字的函數同樣.github
讓咱們從第8部分Part 8的playground處繼續.首先,刪除MathUtils.swift
由於咱們已經不須要了.而後,在MetalView.swift
中刪除createBuffers()
函數及其在初始化中的調用,還有兩個緩衝器.將MTLRenderPipelineState
聲明替換爲MTLComputePipelineState聲明.下一步,來到registerShaders()
函數.下面是新舊兩個版本的不一樣:swift
注意,咱們不在使用descriptor
了,而是在內核函數中直接建立MTLComputePipelineState
.下一步,咱們看看drawRect()
函數的不一樣:ide
注意,currentRenderPassDescriptor
也不用了.命令編碼器則用computeCommandEncoder()
函數來建立.顯然,咱們也再也不須要設置頂點緩衝器和繪製基本體了.做爲替代,使用用一個設置了紋理的內核函數,建立線程組並指派它們幹活.咱們用MTLSize
來設置線程組的維數,及每次計算調用中要執行的線程組的數量.函數
最後,咱們到Shaders.metal
文件中,用下面代碼替換全部內容:post
#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
,你應該能看到相似的視圖:學習
若是你看到了上面的輸出,就說明準備好繼續下去了.從當前開始,咱們將再也不關注主代碼(MetalView.swift
)了,由於咱們全部的工做都將在內核着色器中完成.ui
好了,讓咱們先從簡單的開始.用下面的代碼替換內核函數中的代碼:編碼
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);
複製代碼
你可能已經猜到了,咱們拿到紋理的width
和height
,而後根據像素在紋理中的位置來計算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].最後,咱們判斷像素若是在內部就是黑色,不然就像原來同樣,給它一個漸變色.
讓咱們抽出這個圓內部/外部計算到一個距離函數中:
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);
複製代碼
你應該看到相似這樣的東西:
很漂亮,對吧?如今咱們讓它變成了日食,讓咱們將它變得更真實一些.咱們須要另外一個圓(太陽),而且咱們想要讓初始的圓向左一點,向下一點,這樣它們就都能看到了.用下面幾行替換咱們定義內部
的那行:
float distToCircle2 = dist(uv, float2(-0.1, 0.1), 0.5);
bool inside = distToCircle2 < 0;
複製代碼
你會看到相似下面的東西:
咱們如今只是學會了着色技術的皮毛.在下一章節咱們將學習更復雜和動態的計算任務.特別感謝Chris Wood的建議.
源代碼source code 已發佈在Github上.
下次見!