[MetalKit]3-Using-MetalKit-part-2使用MetalKit2

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

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

MetalKit系統文章目錄


在本系列的第一部分中我們介紹了MetalKit框架.讓我們回到part1的專案中並繼續.再看一遍render() 函式,他應該看起來這樣:

func render() {
    let device = MTLCreateSystemDefaultDevice()!
    self.device = device
    let rpd = MTLRenderPassDescriptor()
    let bleen = MTLClearColor(red: 0, green: 0.5, blue: 0.5, alpha: 1)
    rpd.colorAttachments[0].texture = currentDrawable!.texture
    rpd.colorAttachments[0].clearColor = bleen
    rpd.colorAttachments[0].loadAction = .Clear
    let commandQueue = device.newCommandQueue()
    let commandBuffer = commandQueue.commandBuffer()
    let encoder = commandBuffer.renderCommandEncoderWithDescriptor(rpd)
    encoder.endEncoding()
    commandBuffer.presentDrawable(currentDrawable!)
    commandBuffer.commit()
}
複製程式碼

讓我們改進一下這段程式碼.首先,既然我們的類已經是MTKView的子類,它就已經有了自己的device,所以沒有必要再宣告一個.這就可以把頭兩行變成一行:

device = MTLCreateSystemDefaultDevice()
複製程式碼

第二步,上週我們說到我們應該確保currentDrawablecurrentRenderPassDescriptor不為空否則應用會崩潰.為了簡單點,我們在第1部分時沒做,所以現在來新增一下.這將讓我們能再減少幾行程式碼.最終版看起來會像這樣:

func render() {
    device = MTLCreateSystemDefaultDevice()
    if let rpd = currentRenderPassDescriptor, drawable = currentDrawable {
        rpd.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1.0)
        let command_buffer = device!.newCommandQueue().commandBuffer()
        let command_encoder = command_buffer.renderCommandEncoderWithDescriptor(rpd)
        command_encoder.endEncoding()
        command_buffer.presentDrawable(drawable)
        command_buffer.commit()
    }
}
複製程式碼

你可能會好奇colorAttachments[0] 是什麼.為了設定rendering pipeline state渲染管線狀態,Metal框架提供了3種型別的附件,來讓我們寫入:

  • colorAttachments顏色附件
  • depthAttachmentPixelFormat畫素格式的深度附件
  • stencilAttachmentPixelFormat畫素格式的模板附件 我們目前只關心如何儲存顏色資料,colorAttachments是一個紋理陣列,裡面包含了繪製結果並將他們展示到螢幕上.我們目前只有一個這樣的紋理-陣列中的第一個元素(陣列下標為0). OK,現在是時候執行程式了,確保你仍然能看到像上次一樣背景顏色.很棒!只用9行程式碼我們就建立了一個安全執行在GPU上的Metal程式碼.

接下來,讓我們深入研究一個新的Metal話題-在螢幕上繪製幾何體.所有的圖形學教程比如和OpenGL相關的都會以Hello,Triangle型別程式開始,因為三角形是能繪製在螢幕上幾何體中最簡單的一個.它是2D圖形學基本元素,圖形學中其他所有物件都是三角形組成的,所以它是個很好的入門切入點.想象螢幕座標系統擁有自己的貫穿螢幕中心的座標軸,中心點座標為 (0,0).相應的螢幕邊緣應該為 -11 .讓我們建立一組浮點數和一個緩衝器來儲存三角形的頂點.在初始化device後插入下面幾行程式碼:

let vertex_data:[Float] = [-1.0, -1.0, 0.0, 1.0,
                            1.0, -1.0, 0.0, 1.0,
                            0.0,  1.0, 0.0, 1.0]
let data_size = vertex_data.count * sizeof(Float)
let vertex_buffer = device!.newBufferWithBytes(vertex_data, length: data_size, options: [])
複製程式碼

上面的頂點資料是按順序排列的:左下,右下,中上.我們注意到每個頂點使用4個浮點數來表示座標.前兩個是xy軸.本次用不到的浮點數是:第三個深度(z軸)和第四個w座標用來使座標系齊次化.我們將在下一節討論他們.然後我們計算這個陣列的size大小為12個浮點數長度,最後我們用陣列及其長度來建立一個緩衝器.現在我們已經儲存好了我們的頂點,還需要找個辦法將他們傳送到GPU以便能在螢幕上顯示他們.讓我們看看繪製到螢幕的整個處理過程(即管線):

chapter03_1.png

我們目前已經完成了第1階段,儲存頂點.下一步要求我們有一個新的構件稱為shader著色器.一個shader就是程式設計師能夠用自定義函式來干預圖形管線的地方.Metal提供了幾種型別的著色器,但今天我們只看其中兩種:vertex shader頂點著色器負責點的位置,fragment shader片段著色器負責點的顏色.

Metal框架提供了一個函式,我們可以在device中呼叫,來建立一個函式(shader)組成的Library庫,如下:

let library = device!.newDefaultLibrary()!
let vertex_func = library.newFunctionWithName("vertex_func")
let frag_func = library.newFunctionWithName("fragment_func")
複製程式碼

我們建立兩個新的函式,將其指向對應的著色器(稍後會建立).下一步是建立一個Render Pipeline Descriptor渲染管線描述符來使用著色器:

let rpld = MTLRenderPipelineDescriptor()
rpld.vertexFunction = vertex_func
rpld.fragmentFunction = frag_func
rpld.colorAttachments[0].pixelFormat = .BGRA8Unorm
複製程式碼

你可能會好奇 .BGRA8Unorm是什麼意思.它設定了畫素格式,所以渲染管線中出來的所有東西的顏色元件都會是同一順序(本例中按Blue,Green,Red,Alpha順序),同時大小也會一致(本例中是8-bit的顏色值,範圍從0255).最後一步是根據上面的descriptor描述符建立一個Render Pipeline State渲染管線狀態:

let rps = try! device!.newRenderPipelineStateWithDescriptor(rpld)
複製程式碼

最後,我們只需要讓命令編碼器獲取到我們的三角形就可以了,所以新增下面幾行程式碼到建立encoder編碼器之後:

command_encoder.setRenderPipelineState(rps)
command_encoder.setVertexBuffer(vertex_buffer, offset: 0, atIndex: 0)
command_encoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1) 
複製程式碼

現在我們回到兩個shaders那裡,我們在建立Library庫時說過需要建立的.為了建立shader,我們需要建立一個Xcode中的新檔案.選擇Metal File型別,命名為Shaders.metal或者其他類似名字,單擊Create.你將看到程式碼似乎不是Swift的,因為Metal shading language著色語言其實是基於C++的.新增下面的程式碼:

#include <metal_stdlib>

using namespace metal;

struct Vertex {
    float4 position [[position]];
};

vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]], uint vid [[vertex_id]]) {
    return vertices[vid];
}

fragment float4 fragment_func(Vertex vert [[stage_in]]) {
    return float4(0.7, 1, 1, 1);
}
複製程式碼

程式碼相當直白.我們首先建立一個struct結構體命名為Vertex,裡面只有一個成員-一個包含位置陣列的陣列.我們注意到陣列是float4型別,在著色語言中該型別和我們前面建立頂點時的4個浮點數是一樣的.我們現在先不解釋 [[...]] 語法的意思.然後我們看到vertex_func著色器返回當前頂點的location位置,fragment_func著色器返回當前頂點的color顏色.我們硬編碼了一個特殊的顏色值,但是我們可以將color結構體成員新增到Vertex上,這樣將每個頂點設定不同的顏色. 如果你執行應用,將會看到像這樣的三角形:

chapter03_2.png

下一部分我們將學習Metal shading language也就是3D圖形是怎樣渲染到GPUs的.原始碼source code 已釋出在Github上.

下次見!

相關文章