本系列文章是對 metalkit.org 上面MetalKit內容的全面翻譯和學習.
上次我們學瞭如何在著色器中用距離函式直接建立一個粒子物體.並可以根據時間來移動物體.但是,如果我們要使用頂點,那就需要在CPU
上定義粒子,併傳送頂點資料到GPU
.我們還是使用上次3D
渲染中用過的playground檔案,從建立一個Particle結構體開始,建立在我們metal檢視代理類中:
struct Particle {
var initialMatrix = matrix_identity_float4x4
var matrix = matrix_identity_float4x4
var color = float4()
}
複製程式碼
接著,我們建立一個粒子陣列及一個緩衝器來儲存資料.這裡我們還是給每個粒子漂亮的藍色及一個隨機的起始位置:
particles = [Particle](repeatElement(Particle(), count: 1000))
particlesBuffer = device.makeBuffer(length: particles.count * MemoryLayout<Particle>.stride, options: [])!
var pointer = particlesBuffer.contents().bindMemory(to: Particle.self, capacity: particles.count)
for _ in particles {
pointer.pointee.initialMatrix = translate(by: [Float(drand48()) / 10, Float(drand48()) * 10, 0])
pointer.pointee.color = float4(0.2, 0.6, 0.9, 1)
pointer = pointer.advanced(by: 1)
}
複製程式碼
注意:我們將
x
座標除以10
來讓粒子聚集在很窄的水平範圍內,同時我們將y
座標乘以10
來達到相反的效果--讓它們在豎直方向上散開些.
下一步,是建立一個球體來作為粒子的網格:
let allocator = MTKMeshBufferAllocator(device: device)
let sphere = MDLMesh(sphereWithExtent: [0.01, 0.01, 0.01], segments: [8, 8], inwardNormals: false, geometryType: .triangles, allocator: allocator)
do { model = try MTKMesh(mesh: sphere, device: device) }
catch let e { print(e) }
複製程式碼
接下來,我們需要一個更新函式來讓粒子在螢幕上動起來.在這個函式裡,我們讓計時器每幀增長0.01,並根據計時器的值來更新y
座標--建立一種類似下落的運動:
func update() {
timer += 0.01
var pointer = particlesBuffer.contents().bindMemory(to: Particle.self, capacity: particles.count)
for _ in particles {
pointer.pointee.matrix = translate(by: [0, -3 * timer, 0]) * pointer.pointee.initialMatrix
pointer = pointer.advanced(by: 1)
}
}
複製程式碼
此時,我們已經準備好在draw方法中來呼叫這個函式並將資料傳送到GPU
了:
update()
let submesh = model.submeshes[0]
commandEncoder.setVertexBuffer(model.vertexBuffers[0].buffer, offset: 0, index: 0)
commandEncoder.setVertexBuffer(particlesBuffer, offset: 0, index: 1)
commandEncoder.drawIndexedPrimitives(type: .triangle, indexCount: submesh.indexCount, indexType: submesh.indexType, indexBuffer: submesh.indexBuffer.buffer, indexBufferOffset: 0, instanceCount: particles.count)
複製程式碼
在Shaders.metal檔案中,我們有一個結構體對應進入和退出的頂點,還有一個給粒子例項使用:
struct VertexIn {
float4 position [[attribute(0)]];
};
struct VertexOut {
float4 position [[position]];
float4 color;
};
struct Particle {
float4x4 initial_matrix;
float4x4 matrix;
float4 color;
};
複製程式碼
這個頂點著色器使用instance_id屬性,我們可以用這個屬性來給一個球體建立許多例項,這個球體正是我們在頂點緩衝器的索引0
中傳送到GPU
的那個.我們接著給每一個例項賦值一個位置,這個位置是在緩衝器的索引1
中儲存併傳送到GPU
的.
vertex VertexOut vertex_main(const VertexIn vertex_in [[stage_in]],
constant Particle *particles [[buffer(1)]],
uint instanceid [[instance_id]]) {
VertexOut vertex_out;
Particle particle = particles[instanceid];
vertex_out.position = particle.matrix * vertex_in.position ;
vertex_out.color = particle.color;
return vertex_out;
}
複製程式碼
最後,在片段著色器中,我們返回我們在頂點著色器中傳遞過來的顏色:
fragment float4 fragment_main(VertexOut vertex_in [[stage_in]]) {
return vertex_in.color;
}
複製程式碼
如果你執行app,你會看到粒子像水流一樣落下:
還有一個更加高效的方式來在GPU
上渲染粒子.我們將在下次學習.我想要感謝Caroline的寶貴支援.
原始碼source code已釋出在Github上.
下次見!