寫(xiě)在前面:
對(duì)Metal技術(shù)感興趣的同學(xué),可以關(guān)注我的專題:Metal專輯
也可以關(guān)注我個(gè)人的簡(jiǎn)書(shū)賬號(hào):張芳濤
所有的代碼存儲(chǔ)的Github地址是:Metal
正文
上次我們描述了graphics pipeline(圖形管道)和Metal pipeline(Metal管道)。 現(xiàn)在是時(shí)候我們更深入地觀察管道,并了解頂點(diǎn)如何在較低的層次上真正處理。 為此,我們需要學(xué)習(xí)一些比如transformations的3D math(3D)數(shù)學(xué)概念。
在3D graphics(3D圖形)的世界中,我們通常根據(jù)3或4維來(lái)考慮我們的數(shù)據(jù)。 正如你從上一集中記得的,location(位置)和color(顏色)都是vector_float4(4維)類型。 為了在屏幕上繪制3D幾何圖形,頂點(diǎn)會(huì)遭受一系列變換 - 從object space(物體空間)到world space(世界空間),然后到camera/eye space(相機(jī)/眼睛空間),然后到clipping space(裁剪空間),然后到normalized device coordinates(標(biāo)準(zhǔn)化的設(shè)備坐標(biāo))空間,最后到screen space(屏幕空間)。 我們只看這個(gè)劇集的第一階段。
我們triangle(三角形)的頂點(diǎn)用object space(物體空間)(局部坐標(biāo))表示。 他們目前指定了位于屏幕中心的三角形原點(diǎn)。 為了在更大的場(chǎng)景(世界空間)中定位和移動(dòng)三角形,我們需要對(duì)這些頂點(diǎn)應(yīng)用transformations(變換)。 我們將看到的transformations(轉(zhuǎn)換)是:scaling(縮放),translation(平移)和rotation(旋轉(zhuǎn))。
translation matrix(平移矩陣)類似于identity matrix(單位矩陣)(其主對(duì)角線上的值為1)以及位置[12],[13]和[14](column-major order(按照列排序),它們等同于[3],[ 7]和[11]位置)填充了一個(gè)D向量的值,該D向量表示頂點(diǎn)將移動(dòng)到各個(gè)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]用表示頂點(diǎn)將被放大/縮小的比例的S向量的值填充。x,y,z向量值通常是相同的浮點(diǎn)值,因?yàn)樵谒休S上按比例進(jìn)行縮放。
| Sx 0 0 0 |
| 0 Sy 0 0 |
| 0 0 Sz 0 |
| 0 0 0 1 |
rotation matrix(旋轉(zhuǎn)矩陣)也類似于identity matrix(單位矩陣),其中取決于我們正在旋轉(zhuǎn)哪個(gè)軸,不同的位置正在用我們旋轉(zhuǎn)的角度的sinus(正弦)或cosinus(余弦)填充。 如果我們圍繞x軸旋轉(zhuǎn),則會(huì)填充位置[5],[6],[9]和[10]。 如果我們圍繞y軸旋轉(zhuǎn),則填充位置[0],[2],[8]和[10]。 最后,如果我們圍繞z軸旋轉(zhuǎn),則填充位置[0],[1],[4]和[5]。 請(qǐng)記住,這些職位需要轉(zhuǎn)換為按列排序。
| 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 |
我們有足夠的數(shù)學(xué)知識(shí)----得需要一周的時(shí)間來(lái)消化掉,所以讓我們把這些矩陣加入到代碼中。 我們將繼續(xù)第3部分之后的代碼。它適用于我們創(chuàng)建一個(gè)名為Matrix的結(jié)構(gòu)體,它將包含這些transformations(轉(zhuǎn)換):
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
}
}
我們來(lái)看看這段代碼。 我們首先創(chuàng)建一個(gè)struct(結(jié)構(gòu)體)并聲明一個(gè)浮點(diǎn)類型的數(shù)組。 然后我們?yōu)樗峁┮粋€(gè)初始化器,它是identity matrix(單位矩陣)(對(duì)角線上的所有的值都是1)。 接下來(lái),我們創(chuàng)建變換矩陣。 最后,我們創(chuàng)建一個(gè)modelMatrix,它將所有轉(zhuǎn)換組合到一個(gè)輸出矩陣中。
為了進(jìn)行這些轉(zhuǎn)換工作,我們需要通過(guò)shader(著色器)將它們發(fā)送到GPU。 為了做到這一點(diǎn),我們首先需要?jiǎng)?chuàng)建一個(gè)新的緩沖區(qū)。 我們把它命名為uniform_buffer。 當(dāng)我們想要將數(shù)據(jù)發(fā)送到整個(gè)模型而不是每個(gè)頂點(diǎn)時(shí),Uniforms就是我們可以使用的構(gòu)造。 只有通過(guò)使用Uniforms來(lái)節(jié)省空間并發(fā)送包含所有轉(zhuǎn)換的最終model matrix才有意義。 所以在我們的MetalView類的一開(kāi)始,創(chuàng)建新的緩沖區(qū):
var uniform_buffer: MTLBuffer!
在createBuffers()函數(shù)內(nèi)部,為緩沖區(qū)分配內(nèi)存,足以容納4x4矩陣:
uniform_buffer = device!.newBufferWithLength(sizeof(Float) * 16, options: [])
let bufferPointer = uniform_buffer.contents()
memcpy(bufferPointer, Matrix().modelMatrix(Matrix()).m, sizeof(Float) * 16)
在endToGPU()函數(shù)內(nèi)部,在命令編碼器中設(shè)置vertex_buffer之后,還要設(shè)置uniform_buffer:
command_encoder.setVertexBuffer(uniform_buffer, offset: 0, atIndex: 1)
最后,讓我們轉(zhuǎn)到Shaders.metal進(jìn)行配置的最后部分。 在Vertex結(jié)構(gòu)體下面,創(chuàng)建一個(gè)名為Uniforms的結(jié)構(gòu),它將保存我們的模型矩陣:
struct Uniforms {
float4x4 modelMatrix;
};
修改vertex shader(頂點(diǎn)著色器)以包含我們從CPU傳來(lái)的轉(zhuǎn)換:
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;
}
我們?cè)谶@里所做的只是將uniforms作為第二個(gè)參數(shù)(緩沖區(qū)),然后將模型矩陣與頂點(diǎn)相乘。 如果您現(xiàn)在運(yùn)行該應(yīng)用程序,您將看到一個(gè)三角形,占據(jù)整個(gè)視圖的空間。

我們將其縮小至原始尺寸的四分之一。 將此行添加到modelMatrix函數(shù)中:
matrix = scalingMatrix(matrix, 0.25)
再次運(yùn)行應(yīng)用程序,注意現(xiàn)在三角形變小了:

接下來(lái),讓我們通過(guò)將它向上移動(dòng)一半的屏幕大小來(lái)在y軸上平移三角形:
matrix = translationMatrix(matrix, float3(0.0, 0.5, 0.0))
再次運(yùn)行應(yīng)用程序,注意三角現(xiàn)在比以前更高:

最后,讓我們圍繞z軸旋轉(zhuǎn)三角形:
matrix = rotationMatrix(matrix, float3(0.0, 0.0, 0.1))
再次運(yùn)行應(yīng)用程序,注意三角形現(xiàn)在也旋轉(zhuǎn)了:
