使用Metal繪製簡單的形狀
簡介
ShapeMetal提供一些簡單接口使用Metal去繪製一些簡單的圖形。例如,如果想繪製一個矩形傳入當前座標系下面的一個Rect區域即可,繪製圓傳Center和Radius即可。該工程使用了POP的編程風格,捨棄了OOP的方式,不是因爲OOP不好,我只是單純的認爲OOP不適合組件式開發。如果有同學對POP感興趣,可以看一下國外大神對swift使用POP的理解。
創作原因
Metal作爲蘋果推出的圖像渲染和深度學習的框架,相比於OpenGL更加貼合蘋果的硬件設備,性能有很大的提高。但是太過底層,如果當我們需要在MTKView上面單純的繪製一些簡單的圖形的時候,過程特別繁瑣。下面是我碰到的問題:
- Metal的座標系是歸一化的座標系,範圍是從[-1, 1]。所以我們屏幕上使用的座標系系統不能直接在Metal中使用,每次都需要轉換頂點,就是好麻煩。
- 如果我們使用Core Graphics框架來繪製一些簡單的圖形,例如,繪製一個矩形,我們只需要傳入一個NSRect,就可以繪製出來我們想要的形狀。但是如果是metal的話就需要生成5個頂點信息來繪製出一個矩形。(這裏爲什麼是5個頂點,後面會介紹到。)
如果我們想要繪製一些簡單的圖形就要通過兩個複雜的步驟,1.屏幕座標系轉換到Metal座標系 -> 2. 生成對應的頂點信息。所以我就在想有沒有辦法來把這些不必要的過程封裝一下,只需要傳入屏幕上一些簡單的座標就能使用metal渲染出自己想要的圖形。所以ShapeMetal工程就產生了。
ShapeMetal
Code Structure
- 因爲是繪製的操作所以首先我們需要封裝一些繪製操作,我創建了一個繪製簡單圖形的protocol
//定義一些簡單的繪製圖形的接口
protocol ShapeMetalDrawable {
func line(_ start: NSPoint, _ end: NSPoint)
func rectangle(_ rect: NSRect)
func circle(_ center: NSPoint, _ radius: CGFloat)
}
如果是使用Core Graphics來繪製我們可以這麼實現這個協議(或者說是擴展這個協議)。
extension ShapeMetalDrawable Where Self: CGContext {
func line(_ start: NSPoint, _ end: NSPoint) {
saveGState()
addLines(between: [start, end])
strokePath()
restoreGState()
}
func circle(_ center: NSPoint, _ radius: CGFloat) {
...
}
func rectangle(_ rect: NSRect) {
...
}
}
我使用這個協議在一個NSView上面繪製了一個寬度爲10的紅色線段。
下面是具體的代碼實現,比較簡單就不一一贅述
extension CGContext: ShapeMetalDrawable {
}
class ShapeView: NSView {
override func draw(_ dirtyRect: NSRect) {
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
let startPoint = dirtyRect.origin
let endPoint = NSPoint(x: dirtyRect.midX, y: dirtyRect.midY)
context.setLineWidth(10)
context.setStrokeColor(CGColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0))
context.line(startPoint, endPoint)
}
}
Draw with Metal
接下來我們進入正題,怎麼使用Metal來做簡單的圖形繪製。首先我們可以確定的是蘋果公司並沒有在Metal中提供一些簡單的接口去繪製簡單的圖形,其實想想也比較合理,如果是單純的繪製一些簡單的圖形,我們完全可以選擇比metal更高級的一些框架,例如CoreGraphics。但是有時我們可能會需要來繪製一些簡單的圖形在MTKView上面。
在MTKView上面繪製一些簡單的圖形,有以下步驟:
- 座標轉換
-
配置渲染管線和Shader
如果不太瞭解這一塊的同學,我建議先去了解一下Metal的基礎知識。 -
根據Point數據生成Vertex數據。
我們的想法就是怎麼把這些細節給隱藏掉,讓接口越簡單越好。例如如果我們想繪製一個圓形,只需在繪製目標的座標系統下把圓點的座標和半徑傳給一個繪製接口。
寫到這突然不想寫了, 自己去看代碼吧。