metal初探

前言

metal是iOS底層圖形渲染技術(shù),它是利用GPU進(jìn)行渲染,它允許我們程序員直接操作GPU繪制焕梅,所以相比UIKit層面,它更底層卦洽,效率更高贞言。它跟OpenGL是一個層面的,OpenGL是跨平臺的阀蒂,metal雖說只支持iOS平臺该窗,但是它的效率確是OpenGL的十倍,作為iOS開發(fā)者蚤霞,有必要學(xué)習(xí)一下酗失。
metal、OpenGL昧绣、UiKit规肴、CGGraphites之間的關(guān)系如圖:
[圖片上傳中...(image.png-bc22ca-1573183714631-0)]

讓我們來看一下metal框架結(jié)構(gòu)圖
[圖片上傳中...(image.png-f20d8b-1573183280420-0)]

理論基礎(chǔ)學(xué)習(xí)

我們先來了解一下metal中幾個屬性:

  • MTLDevice:獲取渲染的GPU硬件設(shè)備,這個是metal渲染的基礎(chǔ)夜畴,必須要有GPU拖刃,所以該屬性不能為空,模擬器獲取不到該設(shè)備贪绘,所以metal不支持模擬器(iOS13開始兑牡,metal開始支持模擬器)。
    iOS 可以通過下面方法獲取GPU:

MTLCreateSystemDefaultDevice()

  • MTLCommandQueue:命令隊列兔簇,負(fù)責(zé)管理所有提交給GPU渲染的Buffer的順序发绢,該對象有MTLDevice生成硬耍,是個單例,可以通過以下方式獲得:

device?.makeCommandQueue()

  • MTLBuffer:每一個由程序員提交的每一個指令塊边酒,他是metal編程的基本單元经柴,每一個指令塊應(yīng)該明確告知二進(jìn)制數(shù)據(jù)bytes,內(nèi)存長度墩朦。創(chuàng)建API:

device?.makeBuffer(bytes: vertex_data, length: data_size, options: [])

  • MTLRenderPassDescriptor:渲染描述符坯认。渲染描述符是描述本次GPU要渲染的頂點函數(shù)和片段函數(shù)。
    什么是頂點函數(shù)氓涣?
    比如說我們用metal繪制一個三角形牛哺,那么三角形的三個頂點的坐標(biāo)就是頂點函數(shù)所要描述的。
    片段函數(shù):
    三角形3個頂點之間如何過渡劳吠,過渡的顏色引润,由片段函數(shù)負(fù)責(zé)。
    需要了解的是:頂點函數(shù)和片段函數(shù)都是由Shader語言編寫
Shader語言

Shader語言是metal的著色器語言痒玩,著色器語言不同于一般的編程語言淳附,它不擅長邏輯運算,它只擅長數(shù)學(xué)計算蠢古,比如矩陣操作奴曙。因為Shader語言是面向GPU的,GPU內(nèi)存很小草讶,所以Shader語言不支持邏輯運算符語法洽糟,比如if for循環(huán)統(tǒng)統(tǒng)不支持,支持矩陣運算堕战,基本內(nèi)置函數(shù)等坤溃。
了解了Shader后,我們繼續(xù)
我們剛才說了践啄,渲染描述符對象怎么有兩個重要屬性必須要賦值浇雹,還有一個屬性也要賦值:colorAttachments沉御,這個是設(shè)置過渡顏色的屿讽。所以渲染描述符生成和重要屬性賦值的代碼如下:

let library = device?.makeDefaultLibrary()
let vertext_func = library?.makeFunction(name: "vertex_func")
let frag_func = library?.makeFunction(name: "fragment_func")
let rpld = MTLRenderPipelineDescriptor()
rpld.vertexFunction = vertext_func
rpld.fragmentFunction = frag_func
rpld.colorAttachments[0].pixelFormat = .bgra8Unorm

  • MTLRenderPipelineState:渲染管道狀態(tài)。這個即是上一個渲染描述符得到吠裆,代碼如下:

try device?.makeRenderPipelineState(descriptor: rpld)

該對象伐谈,應(yīng)該全局保存,因為它需要每次draw函數(shù)獲取渲染數(shù)據(jù)就需要該對象试疙,可以看出诵棵,該對象里面包含了所以GPU要渲染的東西了。

  • MTLRenderCommandEncoder:渲染命令編碼器祝旷。這個是將渲染管道狀態(tài)和頂點函數(shù)提交給draw函數(shù)履澳,是最后階段嘶窄。

commandEncoder?.setRenderPipelineState(rps!)
commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
commandEncoder?.setVertexBuffer(uniform_buffer, offset: 0, index: 1)

實戰(zhàn)演練

我們要繪制一個顏色漸變的三角形,最后讓這個三角形沿著z軸旋轉(zhuǎn)距贷。好了柄冲,不多說了,直接看演示效果圖:
[圖片上傳中...(image.png-9ce581-1573196630965-0)]

我們新建一個view,繼承MTKView忠蝗。
首先现横,我們獲取device對象:

required init(coder: NSCoder) {
        super.init(coder: coder)
        device = MTLCreateSystemDefaultDevice()
        render()
    }

init(frame : CGRect) {
        super.init(frame: frame, device: MTLCreateSystemDefaultDevice())
        render()
    }

然后,我們要繪制一個三角形阁最,頂點函數(shù)應(yīng)該是這樣:

func createBuffer() {
        let vertex_data = [Vertex(position: [-1.0, -1.0, 0, 1.0], color: [1, 0, 0, 1]),
                           Vertex(position: [ 1.0, -1.0, 0, 1.0], color: [0, 1, 0, 1]),
                           Vertex(position: [   0,  0.5, 0, 1.0], color: [0, 0, 1, 1])]
        let data_size = vertex_data.count * MemoryLayout<Vertex>.size
        vertexBuffer = device?.makeBuffer(bytes: vertex_data, length: data_size, options: [])
    }

頂點規(guī)則是戒祠,建立以屏幕中心為左邊原定,這里我們每個點是4維結(jié)構(gòu)速种,便于和3D模型數(shù)據(jù)統(tǒng)一哈姜盈,當(dāng)然,我們這個例子是二維的配阵,所以只要設(shè)置前2個數(shù)據(jù)贩据,后面兩個可以固定為0和1.

color,也是4維,分別對應(yīng)著r闸餐、g饱亮、b、alpha

data_size標(biāo)識每個頂點數(shù)據(jù)結(jié)構(gòu)的內(nèi)存大小舍沙。

這樣近上,我們就創(chuàng)建了三角形的三個頂點buffer了。

然后我們看完整的render函數(shù):

func render() {
        commandQueue = device?.makeCommandQueue()
//        vertexData = [-1.0, -1.0, 0, 1.0,
//                       1.0, -1.0, 0, 1.0,
//                         0,  0.5, 0, 1.0]
//        let data_size = vertexData!.count * MemoryLayout<Float>.size
//        vertexBuffer = device?.makeBuffer(bytes: vertexData!, length: data_size, options: [])
        createBuffer()
        let library = device?.makeDefaultLibrary()
        let vertext_func = library?.makeFunction(name: "vertex_func")
        let frag_func = library?.makeFunction(name: "fragment_func")
        let rpld = MTLRenderPipelineDescriptor()
        rpld.vertexFunction = vertext_func
        rpld.fragmentFunction = frag_func
        rpld.colorAttachments[0].pixelFormat = .bgra8Unorm
        do{
            rps = try device?.makeRenderPipelineState(descriptor: rpld)
        }catch let error {
            fatalError("\(error)")
        }
    }

我們首先得到bufferQueue,然后創(chuàng)建buffer數(shù)據(jù)拂铡,
然后設(shè)置頂點著色器和片段著色器壹无,合并得到渲染管道描述符,最后包裝成渲染管道狀態(tài)對象rps感帅。

我們保持了這個rps, 給誰用呢斗锭?

對了,就是失球,都是給draw函數(shù)做準(zhǔn)備的

我們知道岖是,draw()每一幀運行一次,每次運行实苞,我們把rps傳進(jìn)去豺撑,這樣cpu就會把渲染狀態(tài)對象提交到GPU去渲染。

所以黔牵,下面聪轿,我們看draw()函數(shù):

override func draw(_ rect: CGRect) {
        if let drawable = currentDrawable ,
           let rpd = currentRenderPassDescriptor {
            rpd.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0.5, blue: 0.5, alpha: 1)
            rpd.colorAttachments[0].loadAction = .clear
            let commandBuffer = commandQueue?.makeCommandBuffer()
            let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd)
            commandEncoder?.setRenderPipelineState(rps!)
            commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
            commandEncoder?.setVertexBuffer(uniform_buffer, offset: 0, index: 1)
            update()
            commandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
            commandEncoder?.endEncoding()
            commandBuffer?.present(drawable)
            commandBuffer?.commit()
        }
    }

這里主要是將上一步包裝的rps交給encoder, 然后encoder提交給GPU。這樣猾浦,一個簡單的metal編程過程結(jié)束了陆错。
還有個問題灯抛,如何進(jìn)行縮放、平移呢音瓷?
這就要了解數(shù)學(xué)矩陣基礎(chǔ)知識了牧愁。在數(shù)學(xué)矩陣中,平移矩陣是這樣的:
[圖片上傳中...(image.png-398fc6-1573198339721-0)]

縮放矩陣:
[圖片上傳中...(image.png-a44394-1573198366436-0)]

旋轉(zhuǎn)矩陣:
[圖片上傳中...(image.png-4511e8-1573198487096-0)]

所以外莲,我們新建一個Matrix結(jié)構(gòu)體:

struct Matrix {
    
    var m: [Float]

    init() {
        m = [1, 0, 0, 0,
             0, 1, 0, 0,
             0, 0, 1, 0,
             0, 0, 0, 1
        ]
    }

那么猪半,上面三種變換,用矩陣表示:

func translationMatrix(_ matrix: Matrix, _ position: SIMD3<Float>) -> Matrix {
        var mutate = matrix
        mutate.m[12] = position.x
        mutate.m[13] = position.y
        mutate.m[14] = position.z
        return mutate
    }

    func scalingMatrix(_ matrix: Matrix, _ scale: Float) -> Matrix {
        var mutate = matrix
        mutate.m[0] = scale
        mutate.m[5] = scale
        mutate.m[10] = scale
        mutate.m[15] = 1.0
        return mutate
    }

    func rotationMatrix(_ matrix: Matrix, _ rot: SIMD3<Float>) -> Matrix {
        var mutate = matrix
        mutate.m[0] = cos(rot.y) * cos(rot.z)
        mutate.m[4] = cos(rot.z) * sin(rot.x) * sin(rot.y) - cos(rot.x) * sin(rot.z)
        mutate.m[8] = cos(rot.x) * cos(rot.z) * sin(rot.y) + sin(rot.x) * sin(rot.z)
        mutate.m[1] = cos(rot.y) * sin(rot.z)
        mutate.m[5] = cos(rot.x) * cos(rot.z) + sin(rot.x) * sin(rot.y) * sin(rot.z)
        mutate.m[9] = -cos(rot.z) * sin(rot.x) + cos(rot.x) * sin(rot.y) * sin(rot.z)
        mutate.m[2] = -sin(rot.y)
        mutate.m[6] = cos(rot.y) * sin(rot.x)
        mutate.m[10] = cos(rot.x) * cos(rot.y)
        mutate.m[15] = 1.0
        return mutate
    }

好偷线,現(xiàn)在矩陣變換寫好了磨确,如何在metal中使用呢?
那么声邦,我們上面矩陣是4x4的矩陣乏奥,所以,我們要創(chuàng)建buffer ,大小應(yīng)該是16個頂點的大泻ゲ堋:

func createUniformBuffer() {
        let data_length = 16 * MemoryLayout<Float>.size
        uniform_buffer = device?.makeBuffer(length: data_length, options: [])
        let bufferPointer = uniform_buffer?.contents()
        memcpy(bufferPointer, Matrix().modelMatrix(Matrix()).m, data_length)
    }

我們應(yīng)該把這個方法插入到上面的render()中邓了。

最后我們在draw()方法中,設(shè)置變化buffer:

commandEncoder?.setVertexBuffer(uniform_buffer, offset: 0, index: 1)

這里index為1媳瞪,因為之前0的位置是三角形頂點buffer.

最后骗炉,為了讓圖形轉(zhuǎn)起來,我們只要設(shè)置一個定時器蛇受,每一幀增加0.01的角度:

func update() {
        time += 0.01
        let length = 16 * MemoryLayout<Float>.size
        let bufferPointer = uniform_buffer?.contents()
        memcpy(bufferPointer, Matrix().rotateMatrixDlta(Matrix(), time).m, length)
    }

最后句葵,應(yīng)該把這個update()函數(shù)放到draw()方法里面,因為draw()是沒一幀執(zhí)行一次兢仰,這樣圖形就轉(zhuǎn)起來了乍丈。

以上,就是我這兩天metal學(xué)習(xí)過程和體會心得把将,還有很多理解不透徹的地方轻专,希望多多加油!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末察蹲,一起剝皮案震驚了整個濱河市请垛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌递览,老刑警劉巖叼屠,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞳腌,死亡現(xiàn)場離奇詭異绞铃,居然都是意外死亡,警方通過查閱死者的電腦和手機嫂侍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門儿捧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荚坞,“玉大人,你說我怎么就攤上這事菲盾⊥怯埃” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵懒鉴,是天一觀的道長诡挂。 經(jīng)常有香客問我,道長临谱,這世上最難降的妖魔是什么璃俗? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮悉默,結(jié)果婚禮上城豁,老公的妹妹穿的比我還像新娘。我一直安慰自己抄课,他們只是感情好唱星,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著跟磨,像睡著了一般间聊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抵拘,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天甸饱,我揣著相機與錄音,去河邊找鬼仑濒。 笑死叹话,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的墩瞳。 我是一名探鬼主播驼壶,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼喉酌!你這毒婦竟也來了热凹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤泪电,失蹤者是張志新(化名)和其女友劉穎般妙,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體相速,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡碟渺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了突诬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苫拍。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡芜繁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绒极,到底是詐尸還是另有隱情骏令,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布垄提,位于F島的核電站榔袋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铡俐。R本人自食惡果不足惜摘昌,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望高蜂。 院中可真熱鬧聪黎,春花似錦、人聲如沸备恤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽露泊。三九已至喉镰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惭笑,已是汗流浹背侣姆。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沉噩,地道東北人捺宗。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像川蒙,于是被迫代替她去往敵國和親蚜厉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容