本系列文章是對 metalkit.org 上面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()
複製程式碼
第二步,上週我們說到我們應該確保currentDrawable和currentRenderPassDescriptor不為空否則應用會崩潰.為了簡單點,我們在第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).相應的螢幕邊緣應該為 -1 和 1 .讓我們建立一組浮點數和一個緩衝器來儲存三角形的頂點.在初始化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個浮點數來表示座標.前兩個是x和y軸.本次用不到的浮點數是:第三個深度(z軸)
和第四個w座標
用來使座標系齊次化.我們將在下一節討論他們.然後我們計算這個陣列的size大小為12個浮點數長度,最後我們用陣列及其長度來建立一個緩衝器.現在我們已經儲存好了我們的頂點,還需要找個辦法將他們傳送到GPU
以便能在螢幕上顯示他們.讓我們看看繪製到螢幕的整個處理過程(即管線
):
我們目前已經完成了第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
的顏色值,範圍從0
到255
).最後一步是根據上面的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
上,這樣將每個頂點設定不同的顏色.
如果你執行應用,將會看到像這樣的三角形:
下一部分我們將學習Metal shading language
也就是3D圖形
是怎樣渲染到GPUs
的.原始碼source code 已釋出在Github上.
下次見!