大家好,本文介紹了“GPU實現粒子效果”的基本思想,並推薦了相應的學習資料。
本文學習webgpu-samplers->computeBoids示例,它展示了如何用compute shader實現粒子效果,模擬鳥群的行為。
上一篇博文:
WebGPU學習(九):學習“fractalCube”示例
下一篇博文:
WebGPU學習(十一):學習兩個優化:“reuse render command buffer”和“dynamic uniform buffer offset”
最終渲染結果:
為什么不在CPU端實現粒子效果?
雖然在CPU端實現會更靈活和可控,但如果粒子數量很大(如上百萬),且與場景有交互,則最好在GPU端實現。
示例的實現思想
首先執行compute pass
代碼如下:
const numParticles = 1500;
...
let t = 0;
return function frame() {
...
const commandEncoder = device.createCommandEncoder({});
{
const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, particleBindGroups[t % 2]);
passEncoder.dispatch(numParticles);
passEncoder.endPass();
}
...
++t;
}
我們對這個pass進行分析:
particleBindGroups包含兩個storage buffer:ParticlesA和ParticlesB
ParticlesA存儲了上一幀所有粒子的數據。compute shader首先讀取它,然后計算出下一幀所有粒子的數據,最好寫到ParticlesB中。這樣就打了一個ping-pong操作;
注:storage buffer在shader中可被讀或寫,而uniform buffer、vertex buffer等在shader中只能被讀
dispatch到1500個instance,每個instance執行一次compute shader
compute shader計算每個粒子的數據時,需要遍歷其它的所有粒子,計算相互的交互作用。
一共有1500個粒子,共需要計算15001500次。
如果在CPU端執行,只能串行計算,一共需要計算15001500次;
如果在GPU端執行,GPU有1500個instance,每個instance並行地計算1500次,因此一共只需要計算1500次,大大提高了效率。
然后執行render pass
代碼如下:
const numParticles = 1500;
...
const renderPipeline = device.createRenderPipeline({
...
vertexState: {
vertexBuffers: [{
// instanced particles buffer
arrayStride: 4 * 4,
stepMode: "instance",
attributes: [{
// instance position
shaderLocation: 0,
offset: 0,
format: "float2"
}, {
// instance velocity
shaderLocation: 1,
offset: 2 * 4,
format: "float2"
}],
}, {
// vertex buffer
arrayStride: 2 * 4,
stepMode: "vertex",
attributes: [{
// vertex positions
shaderLocation: 2,
offset: 0,
format: "float2"
}],
}],
},
...
});
...
const vertexBufferData = new Float32Array([-0.01, -0.02, 0.01, -0.02, 0.00, 0.02]);
const verticesBuffer = device.createBuffer({
size: vertexBufferData.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
verticesBuffer.setSubData(0, vertexBufferData);
...
return function frame() {
...
const commandEncoder = device.createCommandEncoder({});
...
{
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
passEncoder.setPipeline(renderPipeline);
//ParticlesB使用“instance”的stepMode,被設置到第一個vertex buffer中
passEncoder.setVertexBuffer(0, particleBuffers[(t + 1) % 2]);
//vertices buffer(包含3個頂點數據,每個頂點數據包含x坐標和y坐標)使用“vertex”的stepMode,被設置到第二個vertex buffer中
passEncoder.setVertexBuffer(1, verticesBuffer);
//draw一次,繪制1500個實例(使用ParticlesB的數據),其中每個實例有3個頂點(使用vertices buffer的數據)
//注:每個粒子作為一個實例,由包含3個頂點的三角形組成
passEncoder.draw(3, numParticles, 0, 0);
passEncoder.endPass();
}
...
}
推薦學習資料
大家可以參考WebGPU-8,來學習示例的具體的代碼。
雖然該文對應的示例代碼的版本比較老(如它的示例中是1000個粒子,而不是1500個粒子),但與本文對應的最新版本基本上相同,而且它對示例代碼分析得比較詳細,所以推薦大家學習。
另外,大家可以通過Get started with GPU Compute on the Web,學習如何使用compute shader計算矩陣運算。