[MetalKit]22-Using-MetalKit-part-15使用MetalKit15

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

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

MetalKit系統文章目錄


第13部分末尾,我們說過讓我們的行星看起來更真實有兩種方法:新增紋理,或新增一些噪聲到plante顏色中.我們在第14部分已經展示了新增噪聲.這周我們看看紋理取樣.紋理非常有用,因為比起為每個頂點計算顏色,它可以給表面提供更好的細節.

讓我們比第13部分Part 13開始,因為我們不再需要噪聲程式碼了.首先,在MetalView.swift中移除mouseDown函式,我們已經不再需要它了.同時移除mouseBufferpos變數,同時移除程式碼中對它們的引用.然後,建立一個新的紋理物件:

var texture: MTLTexture!
複製程式碼

下一步,將這行(可能你已經在前面清理中移除過了):

commandEncoder.setBuffer(mouseBuffer, offset: 0, atIndex: 2)
複製程式碼

替換為:

commandEncoder.setTexture(texture, atIndex: 1)
複製程式碼

同時改變timer的緩衝器索引,從1改為0:

commandEncoder.setBuffer(timerBuffer, offset: 0, atIndex: 0)
複製程式碼

我在Resources資料夾新增一張圖片名為texture.jpg,你可以用自己的圖片代替.讓我們建立一個函式來載入並使用這張圖片作為紋理:

func setUpTexture() {
    let path = NSBundle.mainBundle().pathForResource("texture", ofType: "jpg")
    let textureLoader = MTKTextureLoader(device: device!)
    texture = try! textureLoader.newTextureWithContentsOfURL(NSURL(fileURLWithPath: path!), options: nil)
}
複製程式碼

下一步,在我們的init函式呼叫這個函式:

override public init(frame frameRect: CGRect, device: MTLDevice?) {
    super.init(frame: frameRect, device: device)
    registerShaders()
    setUpTexture()
}
複製程式碼

現在,清理我們Shaders.metal中的核心,只保留下面幾行:

kernel void compute(texture2d<float, access::write> output [[texture(0)]],
                    texture2d<float, access::read> input [[texture(1)]],
                    constant float &timer [[buffer(1)]],
                    uint2 gid [[thread_position_in_grid]])
{
    float4 color = input.read(gid);
    gid.y = input.get_height() - gid.y;
    output.write(color, gid);
}
複製程式碼

你會首次注意到,我們從 [[texture(1)]] 屬性拿到了input紋理,因為這個屬性就是我們設定到命令編碼器裡時的索引.同時,訪問許可權設定為read.然後我們將其讀取到color變數,然而,它卻是上下顛倒的.為了修復這個問題,在下一行我們為每個畫素反轉Y軸.輸出圖片看起來應該像這樣:

chapter15_1.png

如果你開啟圖片並與我們的輸出比較,你會發現它已經旋轉到正確朝向了.下一步,我們要找回我們的行星及周圍的黑暗天空.用下面一大塊程式碼替換output行:

int width = input.get_width();
int height = input.get_height();
float2 uv = float2(gid) / float2(width, height);
uv = uv * 2.0 - 1.0;
float radius = 0.5;
float distance = length(uv) - radius;
output.write(distance < 0 ? color : float4(0), gid);
複製程式碼

這段程式碼看起來很熟悉,因為前些章節我們已經討論過如何建立行星及周圍的黑色空間.輸出圖片看起來應該像這樣:

chapter15_2.png
現在很好!下一步我們讓行星轉動起來.用下面一大塊程式碼替換output行:

uv = fmod(float2(gid) + float2(timer * 100, 0), float2(width, height));
color = input.read(uint2(uv));
output.write(distance < 0 ? color : float4(0), gid);
複製程式碼

這段程式碼看起來又很熟悉,因為前些章節我們已經討論過如何使用timer讓行星動起來.輸出圖片看起來應該像這樣:

chapter15_3.gif

這看起來就比較傻了!輸出的影像看起來像一個人舉著火把緊貼牆壁走在黑暗的洞穴裡.用下面的程式碼替換最後三行:

uv = uv * 2;
radius = 1;
constexpr sampler textureSampler(coord::normalized,
                                 address::repeat,
                                 min_filter::linear,
                                 mag_filter::linear,
                                 mip_filter::linear );
float3 norm = float3(uv, sqrt(1.0 - dot(uv, uv)));
float pi = 3.14;
float s = atan2( norm.z, norm.x ) / (2 * pi);
float t = asin( norm.y ) / (2 * pi);
t += 0.5;
color = input.sample(textureSampler, float2(s + timer * 0.1, t));
output.write(distance < 0 ? color : float4(0), gid);
複製程式碼

首先,我們縮小紋理尺寸到原來的一半,並設定半徑為1來匹配行星物件的尺寸和紋理尺寸.然後,神奇的地方來了.讓我們引入sampler取樣器.sampler取樣器是一個包含了各種渲染狀態的物件,讓紋理來配置:座標,定址方式(這裡設定為repeat)和過濾方法(設定為linear).下一步,計算球面上每個點的normal法線.最後,我們用取樣來計算color而不是像前面一樣直接讀取.還有一件事要做,在核心引數列表中,讓我們也把紋理訪問許可權由read改為sample.將這一行:

texture2d<float, access::read> input [[texture(1)]],
複製程式碼

替換為:

texture2d<float, access::sample> input [[texture(1)]],
複製程式碼

輸出影像看起來應該像這樣:

chapter15_4.gif

這就是我們所說的真實的行星表面!還要感謝 Chris的幫助. 原始碼source code 已釋出在Github上.

下次見!

相關文章