本系列文章是對 metalkit.org 上面MetalKit內容的全面翻譯和學習.
上一次我們瞭解了graphics pipeline圖形管線
和Metal pipelineMetal管線
.這次我們更深入地學習管線,並理解頂點是如何在底層被處理.為此,我們需要學習一點兒3D math 3D數學
概念比如transformations變換.
在3D graphics 3D圖形
世界中,我們經常以3維或4維來考慮我們的資料.上一節中,location
和color
都是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_buffer
到command 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個引數(緩衝器),並將模型矩陣乘以頂點.如果現在你執行應用,你將看到我們的三角形還是老樣子,佔據了檢視的整個空間.
讓我們縮放它到原始尺寸的四分之一.在modelMatrix函式新增一行:
matrix = scalingMatrix(matrix, 0.25)
複製程式碼
再執行應用,注意到三角形現在變小了:
接下來,讓我們沿y軸正方向平移三角形,向上移動半個螢幕的高度:
matrix = translationMatrix(matrix, float3(0.0, 0.5, 0.0))
複製程式碼
再執行應用,注意到三角形現在比以前高了:
最後,讓我們繞z軸旋轉三角形:
matrix = rotationMatrix(matrix, float3(0.0, 0.0, 0.1))
複製程式碼
再執行應用,可以看到三角形也旋轉了:
下週我們終於可以開始繪製3D物體(例如立方體或球體)了,原始碼source code 已釋出在Github上.
下次見!