本系列文章是對 http://metalkit.org 上面MetalKit內(nèi)容的全面翻譯和學(xué)習(xí).
上次我們關(guān)注的是如何操縱GPU上的Model I/O對象的頂點(diǎn).本文我們用另一種方式來通過計(jì)算線程來創(chuàng)建粒子.我們可以重用上次的playground,從修改metal視圖代理類的Particle結(jié)構(gòu)體開始,只需要包含兩個(gè)GPU更新用的成員就行了-position和velocity:
struct Particle {
var position: float2
var velocity: float2
}
我們還可以刪除timer變量, 及translate(by:)和update()方法了.改得最多的是initializeBuffers()方法:
func initializeBuffers() {
for _ in 0 ..< particleCount {
let particle = Particle(
position: float2(Float(arc4random() % UInt32(side)),
Float(arc4random() % UInt32(side))),
velocity: float2((Float(arc4random() % 10) - 5) / 10,
(Float(arc4random() % 10) - 5) / 10))
particles.append(particle)
}
let size = particles.count * MemoryLayout<Particle>.size
particleBuffer = device.makeBuffer(bytes: &particles, length: size, options: [])
}
注意:我們在整個(gè)窗口范圍內(nèi)生成隨機(jī)位置,并生成
[-5,5]范圍內(nèi)的速度值.將其除以10以讓速度慢下來.
最重要的部分則是在配置指令編碼器時(shí).設(shè)置threads per group數(shù)量為2D網(wǎng)格,一邊為thread execution width,另一邊為maximum total threads per threadgroup,這兩個(gè)值是GPU的硬件特征值,且在執(zhí)行期間不會(huì)改變.設(shè)置threads per grid為一維數(shù)組,size由粒子數(shù)量決定:
let w = pipelineState.threadExecutionWidth
let h = pipelineState.maxTotalThreadsPerThreadgroup / w
let threadsPerGroup = MTLSizeMake(w, h, 1)
let threadsPerGrid = MTLSizeMake(particleCount, 1, 1)
commandEncoder.dispatchThreads(threadsPerGrid, threadsPerThreadgroup: threadsPerGroup)
注意:在新的
Metal 2中,dispatchThreads(:)可以不指定線程組數(shù)而直接工作.與使用舊的dispatchThreadgroups(:)方法相比,新方法計(jì)算組數(shù),并當(dāng)網(wǎng)格尺寸不是組尺寸的倍數(shù)時(shí)提供nonuniform thread groups,并確保沒有未使用的線程.
回到內(nèi)核著色器中,首先匹配CPU中的粒子結(jié)構(gòu)體,然后在內(nèi)核中更新位置和速度:
Particle particle = particles[id];
float2 position = particle.position;
float2 velocity = particle.velocity;
int width = output.get_width();
int height = output.get_height();
if (position.x < 0 || position.x > width) { velocity.x *= -1; }
if (position.y < 0 || position.y > height) { velocity.y *= -1; }
position += velocity;
particle.position = position;
particle.velocity = velocity;
particles[id] = particle;
uint2 pos = uint2(position.x, position.y);
output.write(half4(1.), pos);
output.write(half4(1.), pos + uint2( 1, 0));
output.write(half4(1.), pos + uint2( 0, 1));
output.write(half4(1.), pos - uint2( 1, 0));
output.write(half4(1.), pos - uint2( 0, 1));
注意:我們做了邊界檢測,當(dāng)遇到邊界時(shí)將速度取反,使粒子不會(huì)離開屏幕.還有一個(gè)小技巧,通過渲染出相鄰的四個(gè)粒子來讓整個(gè)粒子看起來更大點(diǎn).
你可以設(shè)置particleCount為1000000,但這樣會(huì)花費(fèi)好幾秒來渲染全部粒子.我只用了10000個(gè)粒子,這樣它們在屏幕上不會(huì)顯得太擠.運(yùn)行一下app,你會(huì)看到粒子隨機(jī)來回移動(dòng):

至此,粒子渲染系統(tǒng)結(jié)束,感謝FlexMonkey分享對計(jì)算概念的見解,源代碼source code已發(fā)布在Github上.
下次見!