[MetalKit]Using MetalKit part 2使用MetalKit2

本系列文章是對 http://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上.
下次見!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容