Metal入門資料003-MetalKit使用(下)

寫在前面:

對Metal技術感興趣的同學,可以關注我的專題:Metal專輯
也可以關注我個人的簡書賬號:張芳濤
所有的代碼存儲的Github地址是:Metal

OSX平臺相關技術實現

在本系列的第一部分中,我們介紹了MetalKit框架。 讓我們重新使用第1部分中的項目,并從我們離開的地方拿起。 如果再看一下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 = MTLCreateSystemDefaultDevice()

上一篇博客我們說過,我們應該確保currentDrawablecurrentRenderPassDescriptor不是零,否則應用程序會崩潰。 為了簡單起見,我們在第一部分中沒有這樣做,現在就讓我們來做。 這也將幫助我們擺脫更多行代碼。 該函數的最終版本如下所示:

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處)。 好的,現在是運行應用程序的好時機,并且確保您仍然看到上次看到的相同顏色的背景。 好多了! 只需9行代碼,我們就可以在我們的GPU上運行安全的Metal代碼。

接下來,讓我們深入一個新的Metal主題 - 在屏幕上繪制幾何圖形。 所有關于OpenGL的圖形教程都以Hello,Triangle類型的程序開始,因為三角形是可以在屏幕上繪制的幾何形狀的最簡單形式。 它是一個2D graphics基本元素,圖形世界中的所有其他對象都由三角形組成,因此這是一個很好的開始。 想象一下屏幕坐標系統的軸線穿過屏幕的中心,坐標系(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軸。 我們這次沒有使用的是:第三個是depth(Z軸),第四個是W coordinate(W坐標),使得我們的坐標是homogeneous(均勻)的。 我們將在下一篇博客中談論它們。 然后我們計算這個數組的大小,簡單地說就是12個浮點數的大小,最后我們根據數組和它的大小創建緩沖區。 現在我們已經存儲了頂點,我們需要一種方法將它們發送到GPU,以便它們可以顯示在屏幕上。 讓我們看看有助于在屏幕上繪制圖形的整個過程(pipeline):

到目前為止,我們已經完成了第一階段,存儲頂點。 您注意到下一個階段需要我們有一個名為shader(著色器)的新構造。 shader(著色器)是允許程序員用自定義函數干涉圖形管線的地方。 Metal提供了幾種著色器,但是,今天我們只看其中的兩種:負責點的location(位置)的vertex shader(頂點著色器)和負責點的color(顏色)的fragment shader(片段著色器)。

Metal框架提供了一個函數,我們可以調用該函數來創建一個函數庫(著色器),所以我們來創建它:

let library = device!.newDefaultLibrary()!
let vertex_func = library.newFunctionWithName("vertex_func")
let frag_func = library.newFunctionWithName("fragment_func")

我們創建了兩個新的Functions(函數),并將它們指向它們相應的著色器(我們將在后面創建它們)。 下一步是創建一個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(8位)顏色值變為 從0255)。 最后一步是根據上述descriptor(描述符)創建Render Pipeline State(渲染管線狀態):

let rps = try! device!.newRenderPipelineStateWithDescriptor(rpld)

最后,我們只需要讓命令encoder(編碼器)知道我們的三角形,因此在創建encoder(編碼器)后立即添加以下幾行:

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

現在讓我們回到我們在創建Library(庫)時承諾創建的兩個shaders(著色器)。 為此,我們需要在Xcode中創建一個新文件。 選擇Metal File類型,將其命名為Shaders.metal或類似的東西,然后單擊Create(創建)。 您將立即注意到代碼與Swift不太相似,這是因為Metal shading language(Metal著色語言)基于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);
}

代碼非常簡單。 我們首先創建一個名為Vertex的結構體,它只有一個成員 - 一個位置數組數組。 我們注意到數組是float4,它在著色語言中與我們前面創建的頂點相同,每個頂點使用4個浮點數。 我們在下一次留下[[...]]語法的解釋。 然后我們有返回當前頂點location(位置)的vertex_func著色器和返回當前頂點顏色的fragment_func著色器。 我們對特定的顏色值進行了硬編碼,但是我們可以將color(顏色)結構成員添加到Vertex(頂點)并為每個頂點分別設置顏色。

如果你運行該應用程序,你應該看到一個這樣的三角形:

代碼地址:Ch03-OSX

iOS平臺相關技術實現

Shader.metal相關代碼:

#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);
}

MetalView.swift相關代碼實現:

import MetalKit
class MetalView: MTKView {
var commandQueue: MTLCommandQueue?
var rps: MTLRenderPipelineState?
var vertexData: [Float]?
var vertexBuffer: MTLBuffer?
required init(coder: NSCoder) {
    super.init(coder: coder)
    render()
}

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

override func draw(_ rect: CGRect) {
    if let drawable = currentDrawable, let rpd = currentRenderPassDescriptor {
        rpd.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1.0)
        let commandBuffer = commandQueue!.makeCommandBuffer()
        let commandEncode = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd)
        commandEncode?.setRenderPipelineState(rps!)
        commandEncode?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
        commandEncode?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
        commandEncode?.endEncoding()
        commandBuffer?.present(drawable)
        commandBuffer?.commit()
    }
 }
}

執行效果:

需要在真機上執行

代碼地址: Ch03-iOS

TvOS平臺相關技術實現

Shader.metal相關代碼:

#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);
}

MetalView.swift相關代碼實現:

import MetalKit
class MetalView: MTKView {
var commandQueue: MTLCommandQueue?
var rps: MTLRenderPipelineState?
var vertexData: [Float]?
var vertexBuffer: MTLBuffer?
required init(coder: NSCoder) {
    super.init(coder: coder)
    render()
}

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

override func draw(_ rect: CGRect) {
    if let drawable = currentDrawable, let rpd = currentRenderPassDescriptor {
        rpd.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1.0)
        let commandBuffer = commandQueue!.makeCommandBuffer()
        let commandEncode = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd)
        commandEncode?.setRenderPipelineState(rps!)
        commandEncode?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
        commandEncode?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
        commandEncode?.endEncoding()
        commandBuffer?.present(drawable)
        commandBuffer?.commit()
    }
 }
}

執行效果:

這個也需要在真機上測試

代碼地址: Ch03-TvOS

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容

  • <轉>我也忘了轉自哪里,抱歉,感謝原作者 什么是Shader Shader(著色器)是一段能夠針對3D對象進行操作...
    星易乾川閱讀 5,621評論 1 16
  • 1 著色器和程序(Shaders and Programs) 1.1 著色器語言(Language Overvie...
    RichardJieChen閱讀 9,607評論 3 12
  • 轉載注明出處:點擊打開鏈接 Shader(著色器)是一段能夠針對3D對象進行操作、并被GPU所執行的程序。Shad...
    游戲開發小Y閱讀 3,408評論 0 4
  • 圖元處理(Primitive Processing) 如何在場景中使用曲面細分來添加幾何細節 如何使用幾何著色器處...
    RichardJieChen閱讀 6,954評論 2 4
  • 高山中國紅(新韻) 林忠順 遠山寺宇鳥朦朧,雨步長階落葉松。 未見僧尼行禮意,先得玫瑰點迎恭。 環林壁海全乏趣,背...
    林忠順閱讀 411評論 4 7