[MetalKit]6-Using-MetalKit-part-5使用MetalKit5

蘋果API搬運工發表於2017-12-14

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

MetalKit系統文章目錄


上一次我們瞭解了graphics pipeline圖形管線Metal pipelineMetal管線.這次我們更深入地學習管線,並理解頂點是如何在底層被處理.為此,我們需要學習一點兒3D math 3D數學概念比如transformations變換.

3D graphics 3D圖形世界中,我們經常以3維或4維來考慮我們的資料.上一節中,locationcolor都是vector_float4(4維)型別.為了在螢幕上繪製3D幾何體,頂點經歷了一系列變換-從object space物體空間world space世界空間,接著到camera/eye space攝像機/視點空間, 再到clipping space裁剪空間, 到 normalized device coordinates space歸一化裝置座標空間, 最終到了screen space螢幕空間. 本節我們只關注第一個階段.

我們三角形的頂點是以object space物體空間(本地座標系)表示的.它們指定的三角形原點在螢幕的中心.為了在更大的空間(世界座標系)放置和移動三角形,我們需要transformations變換這些頂點.我們關注的transformations變換是: scaling縮放, translation平移rotation旋轉.

translation matrix平移矩陣類似於identity matrix單位矩陣(主對角線上是1的矩陣),但在 [12], [13][14] 位置(在列主序矩陣中它們相當於[3],[7][11])上存放著向量D,這個向量代表著頂點相對於x,y,z軸被移動的距離.

| 1     0     0    Dx |
| 0     1     0    Dy |
| 0     0     1    Dz |
| 0     0     0     1 |
複製程式碼

scaling matrix縮放矩陣也類似於identity matrix單位矩陣但在 [0], [5][10] 位置上存放著向量S,這個向量代表著頂點被縮放到的比例.x,y,z向量值通常能是一樣的浮點數,因為這樣各個軸上都按比例縮放.

| Sx    0     0     0 |
| 0     Sy    0     0 |
| 0     0     Sz    0 |
| 0     0     0     1 |
複製程式碼

rotation matrix旋轉矩陣也類似於identity matrix單位矩陣但根據旋轉軸不同,旋轉角度正弦餘弦存放的位置也會不同.如果是繞x軸旋轉,則存放在 [5],[6], [9][10] 位置上.如果是繞y軸旋轉,則存放在 [0] , [2] , [8][10] 位置上.是繞z軸旋轉,則存放在 [0] , [1] , [4][5] 位置上.請牢記,這些位置需要被轉置為column-major order列主序.

| 1     0     0     0 |
| 0    cos  -sin    0 |
| 0    sin   cos    0 |
| 0     0     0     1 |

| cos   0    sin    0 |
| 0     1     0     0 |
| -sin  0    cos    0 |
| 0     0     0     1 |

| cos  -sin   0     0 |
| sin  cos    0     0 |
| 0     0     1     0 |
| 0     0     0     1 |
複製程式碼

好了,我已經有了足夠的數學知識來應付本週的內容了,讓我們把這些數學公式用到程式碼裡面吧.我們從第三部分part 3的程式碼繼續下去.方便起見,我們建立一個名為Matrix結構體,其中包含這些transformations變換:

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(var matrix: Matrix, _ position: float3) -> Matrix {
        matrix.m[12] = position.x
        matrix.m[13] = position.y
        matrix.m[14] = position.z
        return matrix
    }
    
    func scalingMatrix(var matrix: Matrix, _ scale: Float) -> Matrix {
        matrix.m[0] = scale
        matrix.m[5] = scale
        matrix.m[10] = scale
        matrix.m[15] = 1.0
        return matrix
    }
    
    func rotationMatrix(var matrix: Matrix, _ rot: float3) -> Matrix {
        matrix.m[0] = cos(rot.y) * cos(rot.z)
        matrix.m[4] = cos(rot.z) * sin(rot.x) * sin(rot.y) - cos(rot.x) * sin(rot.z)
        matrix.m[8] = cos(rot.x) * cos(rot.z) * sin(rot.y) + sin(rot.x) * sin(rot.z)
        matrix.m[1] = cos(rot.y) * sin(rot.z)
        matrix.m[5] = cos(rot.x) * cos(rot.z) + sin(rot.x) * sin(rot.y) * sin(rot.z)
        matrix.m[9] = -cos(rot.z) * sin(rot.x) + cos(rot.x) * sin(rot.y) * sin(rot.z)
        matrix.m[2] = -sin(rot.y)
        matrix.m[6] = cos(rot.y) * sin(rot.x)
        matrix.m[10] = cos(rot.x) * cos(rot.y)
        matrix.m[15] = 1.0
        return matrix
    }
    
    func modelMatrix(var matrix: Matrix) -> Matrix {
        return matrix
    }
}
複製程式碼

讓我們講解一下這個程式碼.我們首先建立一個結構體並宣告一個陣列存放浮點數.然後提供一個初始化方法,返回單位矩陣(對角線上都是1).接下來,我們建立變換矩陣.最後,我們建立一個modelMatrix模型矩陣,它組合了所有的變換並輸出到一個矩陣中.

為了讓這些變換起作用,我們需要通過shader把它們送到GPU.為此,我們首先應該建立一個新的緩衝器.讓我們命名為uniform_buffer. Uniforms是一個結構體,我們可以用它來傳送資料到整個模型,而不是各個頂點.用uniforms替換及只傳送一個包含所有變換的最終model matrix模型矩陣,只是為了節約空間.所以在MetalView類開頭處建立新的緩衝器:

var uniform_buffer: MTLBuffer!
複製程式碼

createBuffers() 函式裡,給緩衝器分配記憶體,足夠容納4x4的矩陣:

uniform_buffer = device!.newBufferWithLength(sizeof(Float) * 16, options: [])
let bufferPointer = uniform_buffer.contents()
memcpy(bufferPointer, Matrix().modelMatrix(Matrix()).m, sizeof(Float) * 16)
複製程式碼

sendToGPU() 函式裡,在設定vertex_buffercommand encoder命令編碼器後,也設定一下uniform_buffer:

command_encoder.setVertexBuffer(uniform_buffer, offset: 0, atIndex: 1)
複製程式碼

最後,讓我們轉到Shaders.metal中進行最後一部分的配置.在Vertex結構體下面,建立一個新的結構體命名為Uniforms,用來儲存我們的模型矩陣:

struct Uniforms {
    float4x4 modelMatrix;
};
複製程式碼

修改vertex shader頂點著色器,來接收我們從CPU傳過來的變換:

vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]],
                          constant Uniforms &uniforms [[buffer(1)]],
                          uint vid [[vertex_id]])
{
    float4x4 matrix = uniforms.modelMatrix;
    Vertex in = vertices[vid];
    Vertex out;
    out.position = matrix * float4(in.position);
    out.color = in.color;
    return out;
}
複製程式碼

這裡我們做的就是傳遞uniforms作為第2個引數(緩衝器),並將模型矩陣乘以頂點.如果現在你執行應用,你將看到我們的三角形還是老樣子,佔據了檢視的整個空間.

chapter05_1.png

讓我們縮放它到原始尺寸的四分之一.在modelMatrix函式新增一行:

matrix = scalingMatrix(matrix, 0.25)
複製程式碼

再執行應用,注意到三角形現在變小了:

chapter05_2.png

接下來,讓我們沿y軸正方向平移三角形,向上移動半個螢幕的高度:

matrix = translationMatrix(matrix, float3(0.0, 0.5, 0.0))
複製程式碼

再執行應用,注意到三角形現在比以前高了:

chapter05_3.png

最後,讓我們繞z旋轉三角形:

matrix = rotationMatrix(matrix, float3(0.0, 0.0, 0.1))
複製程式碼

再執行應用,可以看到三角形也旋轉了:

chapter05_4.png

下週我們終於可以開始繪製3D物體(例如立方體或球體)了,原始碼source code 已釋出在Github上.

下次見!

相關文章