[MetalKit]4-Using-MetalKit-part-3使用MetalKit3

本系列文章是對 metalkit.org 上面MetalKit內容的全面翻譯和學習.c++

MetalKit系統文章目錄git


上一節我說咱們將學習Metal shading language.在學以前,咱們先作一些代碼清理和重構.從下載前一節的源代碼 source code開始.咱們將從重構render() 函數開始.因此讓咱們取出vertex bufferrender pipeline state,並建立3個新的函數放進去,這樣咱們的舊函數就減小到這樣:github

var vertex_buffer: MTLBuffer!
var rps: MTLRenderPipelineState! = nil

func render() {
    device = MTLCreateSystemDefaultDevice()
    createBuffer()
    registerShaders()
    sendToGPU()
}
複製代碼

咱們先對createBuffer() 函數作一些改變.回憶上一節vertex dataFloat類型的數組,像這樣:swift

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]
複製代碼

讓咱們把它轉換成更好的格式,一個帶有兩個vector_float4類型成員的結構體,一個position另外一個是color:數組

struct Vertex {
    var position: vector_float4
    var color: vector_float4
}
複製代碼

你可能會好奇vector_float4究竟是什麼樣的數據類型.從蘋果官方文檔中咱們發現,這種向量類型是一種clang基礎類型,比傳統的SIMD類型更適合向量-向量向量-標量的算術運算.它能夠經過相似數組下標來訪問向量的成員份量,具體做法是用 . 操做符和組件名稱訪問(x,y,z,w,或它們的組合).除了 .xyzw組件名外,下面的子向量也能經過:.lo / .hi(向量的前半部分和後半部分)來輕鬆訪問,還有奇偶位的 .even / .odd子向量:函數

vector_float4 x = 1.0f;         // x = { 1, 1, 1, 1 }.

vector_float3 y = { 1, 2, 3 };  // y = { 1, 2, 3 }.

x.xyz = y.zyx;                  // x = { 1/3, 1/2, 1, 1 }.

x.w = 0;                        // x = { 1/4, 1/3, 1/2, 0 }.
複製代碼

讓咱們返回到createBuffer()用新的結構體來替換vertex-data:post

func createBuffer() {
    let vertex_data = [Vertex(position: [-1.0, -1.0, 0.0, 1.0], color: [1, 0, 0, 1]),
                       Vertex(position: [ 1.0, -1.0, 0.0, 1.0], color: [0, 1, 0, 1]),
                       Vertex(position: [ 0.0,  1.0, 0.0, 1.0], color: [0, 0, 1, 1])]
    vertex_buffer = device!.newBufferWithBytes(vertex_data, length: sizeof(Vertex) * 3, options:[])
}
複製代碼

你看,經過簡單地將它轉成結構體數組,咱們能夠輕易建立頂點數據.學習

同時,咱們保持頂點位置仍在上次的位置上,而且咱們爲每一個頂點添加單獨的顏色(紅,綠,藍).接下來,是registerShaders() 函數.咱們無需改變舊代碼,只須要將它移動到新的地方:ui

func registerShaders() {
    let library = device!.newDefaultLibrary()!
    let vertex_func = library.newFunctionWithName("vertex_func")
    let frag_func = library.newFunctionWithName("fragment_func")
    let rpld = MTLRenderPipelineDescriptor()
    rpld.vertexFunction = vertex_func
    rpld.fragmentFunction = frag_func
    rpld.colorAttachments[0].pixelFormat = .BGRA8Unorm
    do {
        try rps = device!.newRenderPipelineStateWithDescriptor(rpld)
    } catch let error {
        self.print("\(error)")
    }
}
複製代碼

最後,咱們對sendToGPU() 函數也作一樣的操做,不改變舊代碼只移動到新地方:編碼

func sendToGPU() {
    if let rpd = currentRenderPassDescriptor, drawable = currentDrawable {
        rpd.colorAttachments[0].clearColor = MTLClearColorMake(0.5, 0.5, 0.5, 1.0)
        let command_buffer = device!.newCommandQueue().commandBuffer()
        let command_encoder = command_buffer.renderCommandEncoderWithDescriptor(rpd)
        command_encoder.setRenderPipelineState(rps)
        command_encoder.setVertexBuffer(vertex_buffer, offset: 0, atIndex: 0)
        command_encoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
        command_encoder.endEncoding()
        command_buffer.presentDrawable(drawable)
        command_buffer.commit()
    }
}
複製代碼

接下來讓咱們轉移到Shaders.metal文件.這時咱們作兩處修改.首先,給咱們的Vertex結構體添加一個color成員,這樣咱們就能夠在CPUGPU之間來回傳遞數據:

struct Vertex {
    float4 position [[position]];
    float4 color; 
};
複製代碼

其次,咱們替換上次在fragment着色器中使用的硬編碼的顏色:

fragment float4 fragment_func(Vertex vert [[stage_in]]) {
    return float4(0.7, 1, 1, 1);
}
複製代碼

替換爲每一個頂點自帶的實際顏色(經過vertex_buffer傳遞到GPU):

fragment float4 fragment_func(Vertex vert [[stage_in]]) {
    return vert.color;
}
複製代碼

若是你運行程序,你看到一個更漂亮的彩色三角形:

chapter04.png

你也許會奇怪,爲何咱們只傳遞給三個頂點對應顏色,但頂點之間的顏色倒是漸變的?要理解這些,就必須先理解兩種着色器的不一樣及它們在圖形管線中角色的不一樣.讓咱們看看任一個着色器的語法(這裏先頂點着色器做例子):

vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]], uint vid [[vertex_id]])
複製代碼

第一個關鍵詞,是函數限定符只能使用vertex, fragmentkernel.下一個關鍵詞是返回值類型.接下來是帶有圓括號參數的函數name名稱.Metal shading language限定了指針的使用,必須用device,threadgroupconstant修飾符來聲明,這些修飾符指定了函數變量或參數分配到的內存區域.[[...]] 語法是用來聲明屬性,例如資源位置,着色器輸入,以及在着色器與CPU之間來回傳遞的內置變量.

Metal使用 [[ buffer(index)]] 屬性來標識出位置,讓deviceconstant buffer的參數類型可以區分.內置的輸入變量和輸出變量被用來在圖形函數(頂點和片斷)與固定圖形管線流程之間傳遞數據.在咱們例子中 [[vertex_id]] 是傳遞過程當中每一個頂點的標識符.Metal接收頂點函數和光柵產生的片斷的輸出,來產生輸入到片斷函數的各個片斷.每一個片斷輸入依靠 [[stage_in]] 屬性修飾符來標識.

vertex shader用指向頂點列表的指針做爲第一個參數.咱們能夠用第2個參數vid來索引vertices頂點,其中的vid被賦值成vertex_id,它告訴Metal插入當前正在被處理的頂點的索引做爲第2個參數.而後只需傳遞每一個頂點(包括位置和顏色)給fragment shader片斷着色器去處理.fragment shader片斷着色器所做的操做是,取出從vertex shader頂點着色器中傳過來的頂點,直接傳給每一個像素而無需改變輸入數據.頂點着色器運行頻率不高(本例中只需3次-每一個頂點1次),但fragment shader片斷着色器運行幾千次-每一個須要繪製的像素一次.

因此你可能仍然會問:"ok,可是顏色漸變到底怎麼回事呢?" 如今你理解了每一個着色器的做用及運行頻率,你能夠認爲任一個像素點的顏色都是它的附近像素顏色的平均值.例如,在red紅green綠顏色像素正中間的像素顏色將會是yellow黃,只是由於fragment shader片斷着色器用平均數來產生顏色插值:0.5 * red + 0.5 * green.一樣的,在red紅blue藍正中間的顏色會是magenta品紅,在blue藍green綠正中間的顏色會是cyan青.就這樣,剩餘部分像素都是用初始顏色的插值,最終結果就是你看到的漸變範圍.

源代碼source code 已發佈在Github上.

下次見!

相關文章
相關標籤/搜索