[MetalKit]40-Working-with-Particles-in-Metal-part2粒子系統2

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

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

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,你會看到粒子像水流一樣落下:

particles.gif

還有一個更加高效的方式來在GPU上渲染粒子.我們將在下次學習.我想要感謝Caroline的寶貴支援.

原始碼source code已釋出在Github上.

下次見!

相關文章