[MetalKit]15-Using-MetalKit-part-9使用MetalKit9

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

MetalKit系統文章目錄github


我打賭不少讀者很相信MetalKit系列,因此今天我重回這個系列,咱們將學習如何在Metal中繪製3D內容.讓咱們繼續咱們在playground中的工做,繼續本系列的第8部分 part 8.swift

在本章結束時,咱們將渲染一個3D立方體,可是首先讓咱們繪製一個2D矩形並複用這個矩形的邏輯來建議立方體的全部面.讓咱們修改vertex_data數組來保存4個頂點而不是原來三角形的3個頂點:數組

let vertex_data = [
    Vertex(pos: [-1.0, -1.0, 0.0,  1.0], col: [1, 0, 0, 1]),
    Vertex(pos: [ 1.0, -1.0, 0.0,  1.0], col: [0, 1, 0, 1]),
    Vertex(pos: [ 1.0,  1.0, 0.0,  1.0], col: [0, 0, 1, 1]),
    Vertex(pos: [-1.0,  1.0, 0.0,  1.0], col: [1, 1, 1, 1])
]
複製代碼

最有意思的部分來了.矩形和其它複雜幾何體都是由三角形組成,而且大部分頂點屬於2個或更多三角形,就不須要給這些頂點建立複本了,由於咱們有一種辦法經過index buffer索引緩衝器來複用它們,這種方法能夠從vertex buffer頂點緩衝器中保存頂點索引到列表中,來追蹤那些將要用到的頂點的順序.因此讓咱們建立這樣一份索引列表:數據結構

let index_data: [UInt16] = [
    0, 1, 2, 2, 3, 0
]
複製代碼

爲了理解這些索引是如何被保存的,讓咱們看下面這幅圖:框架

chapter09_1.jpg

對於前方的面(矩形)來講,咱們用到的頂點儲存在vertex buffer頂點緩衝器03號位.稍後咱們將添加另外4個頂點.前面是由兩個三角形構成.咱們先用頂點0,12繪製一個三角形,而後用頂點2,30再繪製一個三角形.請注意,正如期待的那樣,有兩個頂點被重用了.還要注意的是繪製是以clockwise順時針完成的.這是Metal中默認的正面繞序,可是也能被設置爲counterclockwise逆時針的.函數

而後咱們須要建立index_buffer:post

var index_buffer: MTLBuffer!
複製代碼

下一步,咱們須要在createBuffers()函數中把index_data賦值給index buffer:學習

index_buffer = device!.newBufferWithBytes(index_data, length: sizeof(UInt16) * index_data.count , options: [])
複製代碼

最後,在drawRect(:)函數中咱們須要將drawPrimitives調用:ui

command_encoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
複製代碼

替換爲drawIndexedPrimitives調用:

command_encoder.drawIndexedPrimitives(.Triangle, indexCount: index_buffer.length / sizeof(UInt16), indexType: MTLIndexType.UInt16, indexBuffer: index_buffer, indexBufferOffset: 0)
複製代碼

在playground主頁面中,看看新產生的圖像:

chapter09_2.png

如今咱們知道如何繪製一個矩形了,讓咱們看看如何繪製更多矩形!

let vertex_data = [
    Vertex(pos: [-1.0, -1.0,  1.0, 1.0], col: [1, 0, 0, 1]),
    Vertex(pos: [ 1.0, -1.0,  1.0, 1.0], col: [0, 1, 0, 1]),
    Vertex(pos: [ 1.0,  1.0,  1.0, 1.0], col: [0, 0, 1, 1]),
    Vertex(pos: [-1.0,  1.0,  1.0, 1.0], col: [1, 1, 1, 1]),
    Vertex(pos: [-1.0, -1.0, -1.0, 1.0], col: [0, 0, 1, 1]),
    Vertex(pos: [ 1.0, -1.0, -1.0, 1.0], col: [1, 1, 1, 1]),
    Vertex(pos: [ 1.0,  1.0, -1.0, 1.0], col: [1, 0, 0, 1]),
    Vertex(pos: [-1.0,  1.0, -1.0, 1.0], col: [0, 1, 0, 1])
]
let index_data: [UInt16] = [
    0, 1, 2, 2, 3, 0,   // front

    1, 5, 6, 6, 2, 1,   // right

    3, 2, 6, 6, 7, 3,   // top

    4, 5, 1, 1, 0, 4,   // bottom

    4, 0, 3, 3, 7, 4,   // left

    7, 6, 5, 5, 4, 7,   // back

]
複製代碼

如今咱們有了準備渲染的整個立方體,讓咱們來到MathUtils.swift中,在modelMatrix()中註釋掉rotationtranslation調用,只保留縮放倍數爲0.5.你將極可能看到一個像這樣的圖像:

chapter09_3.png

呃,可是仍然是一個矩形!是的,由於咱們仍沒有depth深度概念因此立方體看起來只是個平的.是時候來點數學魔法了.咱們不須要使用Matrix矩陣結構體由於simd框架給咱們提供了相似的數據結構和數學函數,咱們能夠直接使用.咱們能輕易用matrix_float4x4代替自定義的Matrix結構體來重寫咱們的轉換函數.

可是你可能會問,如何在咱們2D屏幕上顯示3D物體.這個過程將每一個像素通過一系列變換.首先,modelMatrix() 將像素從物體空間轉換到世界空間.這個矩陣是咱們已經知道的,負責平移,旋轉和縮放的那個.添加新的重寫過的函數後,modelMatrix應該看起來像這樣:

func modelMatrix() -> matrix_float4x4 {
    let scaled = scalingMatrix(0.5)
    let rotatedY = rotationMatrix(Float(M_PI)/4, float3(0, 1, 0))
    let rotatedX = rotationMatrix(Float(M_PI)/4, float3(1, 0, 0))
    return matrix_multiply(matrix_multiply(rotatedX, rotatedY), scaled)
}
複製代碼

你注意到用到的matrix_multiply函數由於Matrix結構體已經不能用了.同時,由於全部像素將經歷一樣的變換,咱們想要把矩陣儲存爲一個Uniform並傳遞到vertex shader.爲此,讓咱們建立一個新的結構體:

struct Uniforms {
    var modelViewProjectionMatrix: matrix_float4x4
}
複製代碼

回到createBuffers()函數,讓咱們用傳遞modelMatrix時用到的緩衝器指針來傳遞全局變量到着色器:

let modelViewProjectionMatrix = modelMatrix()
var uniforms = Uniforms(modelViewProjectionMatrix: modelViewProjectionMatrix)
memcpy(bufferPointer, &uniforms, sizeof(Uniforms))
複製代碼

在playground主頁面中,看看新產生的圖像:

chapter09_4.png

呃...立方體看上去差很少了,但是有些地方沒了.下一步變換,像素將從世界空間攝像機空間.咱們在屏幕上看到的全部東西都是被一個虛擬攝像機觀察到的,它經過帶有near最近far最遠平面的frustum平頭截體(金字塔形)來限制觀察(攝像機)空間:

chapter09_5.png

回到MathUtils.swift讓咱們建立viewMatrix():

func viewMatrix() -> matrix_float4x4 {
    let cameraPosition = vector_float3(0, 0, -3)
    return translationMatrix(cameraPosition)
}
複製代碼

下一步的變換,像素將從camera space攝像機空間變換到clip space裁剪空間.這裏,全部不在clip space裁剪空間裏面的頂點將被判斷,看三角形被culled剔除(全部頂點都在裁剪空間外)或clipped to bounds截斷(某些頂點在外面某些在內部).projectionMatrix() 會幫咱們計算邊界並判斷頂點在哪裏:

func projectionMatrix(near: Float, far: Float, aspect: Float, fovy: Float) -> matrix_float4x4 {
    let scaleY = 1 / tan(fovy * 0.5)
    let scaleX = scaleY / aspect
    let scaleZ = -(far + near) / (far - near)
    let scaleW = -2 * far * near / (far - near)
    let X = vector_float4(scaleX, 0, 0, 0)
    let Y = vector_float4(0, scaleY, 0, 0)
    let Z = vector_float4(0, 0, scaleZ, -1)
    let W = vector_float4(0, 0, scaleW, 0)
    return matrix_float4x4(columns:(X, Y, Z, W))
}
複製代碼

最後兩個變換是從clip space裁剪空間normalized device coordinates (NDC)規格化設備座標,還有從NDCscreen space屏幕空間.這兩步是由Metal框架爲咱們處理的.

下一步,回到createBuffers()函數,讓咱們修改modelViewProjectionMatrix,咱們以前爲了適應modelMatrix來設置的:

let aspect = Float(drawableSize.width / drawableSize.height)
let projMatrix = projectionMatrix(1, far: 100, aspect: aspect, fovy: 1.1)
let modelViewProjectionMatrix = matrix_multiply(projMatrix, matrix_multiply(viewMatrix(), modelMatrix()))
複製代碼

drawRect(:)中咱們須要設置裁剪模式,正面模式,來避免出現奇怪的現象好比立方體透明瞭:

command_encoder.setFrontFacingWinding(.CounterClockwise)
command_encoder.setCullMode(.Back)
複製代碼

在playground主頁面中,看看新產生的圖像:

chapter09_6.png

這就是咱們一直想看到的最終版3D立方體! 還要作一件事來讓它更真實:讓它旋轉起來.首先,讓咱們建立一個全局變量命名爲rotation,咱們想要隨着時間流逝來刷新它:

var rotation: Float = 0
複製代碼

下一步,從createBuffers()函數中取出矩陣,並建立一個新的命名爲update().下面是咱們每幀更新rotation來建立平滑滾動效果的地方:

func update() {
    let scaled = scalingMatrix(0.5)
    rotation += 1 / 100 * Float(M_PI) / 4
    let rotatedY = rotationMatrix(rotation, float3(0, 1, 0))
    let rotatedX = rotationMatrix(Float(M_PI) / 4, float3(1, 0, 0))
    let modelMatrix = matrix_multiply(matrix_multiply(rotatedX, rotatedY), scaled)
    let cameraPosition = vector_float3(0, 0, -3)
    let viewMatrix = translationMatrix(cameraPosition)
    let aspect = Float(drawableSize.width / drawableSize.height)
    let projMatrix = projectionMatrix(0, far: 10, aspect: aspect, fovy: 1)
    let modelViewProjectionMatrix = matrix_multiply(projMatrix, matrix_multiply(viewMatrix, modelMatrix))
    let bufferPointer = uniform_buffer.contents()
    var uniforms = Uniforms(modelViewProjectionMatrix: modelViewProjectionMatrix)
    memcpy(bufferPointer, &uniforms, sizeof(Uniforms))
}
複製代碼

drawRect(:)中調用update函數:

update()
複製代碼

在playground主頁面中,看看新產生的圖像:

chapter09_7.gif

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

下次見!

相關文章
相關標籤/搜索