# 16 Particle Systems Written by Marius Horga

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

One of the many ways to create art and present science in code is by making use of particles. A particle is a tiny graphical object that carries basic information about itself such as color, position, life, speed and direction of movement.

Nothing explains a visual effect better than an image showing what you’ll be able to achieve at the end of this chapter.

Particle systems are widely used in:

• Video games and animation: hair, cloth, fur.
• Modeling of natural phenomena: fire, smoke, water, snow.
• Scientific simulations: galaxy collisions, cellular mitosis, fluid turbulence.

Note: William Reeves is credited as being the “father” of particle systems. While at Lucasfilm, Reeves created the Genesis Effect in 1982 while working on the movie Star Trek II: The Wrath of Khan. Later, he joined Pixar Animation Studios where he’s still creating amazing animations using particles.

In a moment, you’ll get your feet wet trying out one such practical application: fireworks. But first, what exactly is a particle?

## Particle

Newtonian dynamics describe the relationship between any small body — a particle — and the forces acting upon it, as well as its motion in response to those forces. Newton’s three laws of motion define the relationship between them.

The first two laws define motion as a result of either inertia or force interference upon the particle’s current state of motion (stationary or moving). You’ll be working with them in this chapter.

The third law, however, defines motion as a reaction of two or more particles interacting with each other. You’ll work with this law in Chapter 17, “Particle Behavior.”

A fourth law, if you wish, is the law of life. It’s not one of the Newtonian motion laws, but it does indeed apply to particles. Particles are born; they move and interact with the environment; and then, they die.

You need a particle system to create fireworks. First, however, you need to define a particle that has — at a minimum — a position, direction, speed, color and life.

What makes a particle system cohesive, however, are emitters.

## Emitter

An emitter is nothing more than a particle generator — in other words, a source of particles. You can make your particle system more exciting by having several emitters shooting out particles from different positions.

``````import MetalKit

struct Particle {
var position: float2
var direction: Float
var speed: Float
var color: float3
var life: Float
}
``````
``````public struct Emitter {
public let particleBuffer: MTLBuffer
}
``````
``````public init(particleCount: Int, size: CGSize,
life: Float, device: MTLDevice) {
let bufferSize = MemoryLayout<Particle>.stride * particleCount
particleBuffer = device.makeBuffer(length: bufferSize)!
}
``````
``````var pointer =
particleBuffer.contents().bindMemory(to: Particle.self,
capacity: particleCount)
let width = Float(size.width)
let height = Float(size.height)
let position = float2(Float.random(in: 0...width),
Float.random(in: 0...height))
let color = float3(Float.random(in: 0...life) / life,
Float.random(in: 0...life) / life,
Float.random(in: 0...life) / life)

for _ in 0..<particleCount {
let direction =
2 * Float.pi * Float.random(in: 0...width) / width
let speed = 3 * Float.random(in: 0...width) / width
pointer.pointee.position = position
pointer.pointee.direction = direction
pointer.pointee.speed = speed
pointer.pointee.color = color
pointer.pointee.life = life
}
``````
``````let particleCount = 10000
let maxEmitters = 8
var emitters: [Emitter] = []
let life: Float = 256
var timer: Float = 0
``````
``````func update(size: CGSize) {
timer += 1
if timer >= 50 {
timer = 0
if emitters.count > maxEmitters {
emitters.removeFirst()
}
let emitter = Emitter(particleCount: particleCount,
size: size, life: life,
device: device)
emitters.append(emitter)
}
}
``````
``````update(size: view.drawableSize)
``````

## Compute

What is compute? Simply put, compute programming is the only other way to use a GPU besides rendering. Compute is also widely known as General Purpose GPU (GPGPU) programming.

``````let pipelineState: MTLComputePipelineState!
``````
``````private static func initializeMetal() -> (
device: MTLDevice, commandQueue: MTLCommandQueue,
pipelineState: MTLComputePipelineState)? {
guard let device = MTLCreateSystemDefaultDevice(),
let commandQueue = device.makeCommandQueue(),
ofType: "metal")
else { return nil }

let pipelineState: MTLComputePipelineState

do {
let input = try String(contentsOfFile: path,
encoding: String.Encoding.utf8)
let library = try device.makeLibrary(source: input,
options: nil)
guard let function = library.makeFunction(name: "compute")
else { return nil }
pipelineState =
try device.makeComputePipelineState(function: function)
}
catch {
print(error.localizedDescription)
return nil
}
return (device, commandQueue, pipelineState)
}
``````
``````pipelineState = initialized?.pipelineState
``````

Next, you’ll create a compute pass. For that, you need to specify how many times you want the kernel function to run. To determine this, you need to know the size of the array, texture or volume you want to process. This size is known as the grid and consists of threads organized into threadgroups.

``````let width = 32
let height = 16
let gridWidth = Int(view.drawableSize.width)
let gridHeight = Int(view.drawableSize.height)
MTLSizeMake((gridWidth + width - 1) / width,
(gridHeight + height - 1) / height,
1)
``````

``````let renderEncoder = makeRenderCommandEncoder(commandBuffer,
drawable.texture)
renderEncoder.endEncoding()
``````
``````// 1
guard let computeEncoder = commandBuffer.makeComputeCommandEncoder()
else { return }
computeEncoder.setComputePipelineState(pipelineState)
computeEncoder.setTexture(drawable.texture, index: 0)
// 2
width = Int(view.drawableSize.width)
height = Int(view.drawableSize.height)
var threadsPerGrid = MTLSizeMake(width, height, 1)
// 3
computeEncoder.endEncoding()
``````
``````#include <metal_stdlib>
using namespace metal;

output [[texture(0)]],
output.write(half4(0.0, 0.0, 0.0, 1.0), id);
}
``````

## Fireworks

At the top of `Renderer`, add a new pipeline state:

``````let particlePipelineState: MTLComputePipelineState!
``````
``````private static func initializeMetal() -> (
device: MTLDevice, commandQueue: MTLCommandQueue,
pipelineState: MTLComputePipelineState,
particlePipelineState: MTLComputePipelineState)?
``````
``````let particlePipelineState: MTLComputePipelineState
``````
``````guard let function = library.makeFunction(name: "compute"),
let particleFunction =
library.makeFunction(name: "particleKernel")
else { return nil }
pipelineState =
try device.makeComputePipelineState(function: function)
particlePipelineState = try
device.makeComputePipelineState(function: particleFunction)
``````
``````return (
device, commandQueue,
pipelineState: pipelineState,
particlePipelineState: particlePipelineState)
``````
``````particlePipelineState = initialized?.particlePipelineState
``````
``````// 1
guard let particleEncoder = commandBuffer.makeComputeCommandEncoder()
else { return }
particleEncoder.setComputePipelineState(particlePipelineState)
particleEncoder.setTexture(drawable.texture, index: 0)
// 2
for emitter in emitters {
// 3
let particleBuffer = emitter.particleBuffer
particleEncoder.setBuffer(particleBuffer, offset: 0, index: 0)
}
particleEncoder.endEncoding()
``````

### Particle dynamics

Particle dynamics makes heavy use of Newton’s laws of motion. Particles are considered to be small objects approximated as point masses.

``````velocity = speed * direction
``````
``````newPosition = oldPosition * velocity
``````

``````xVelocity = speed * cos(direction)
yVelocity = speed * sin(direction)
``````
``````struct Particle {
float2 position;
float  direction;
float  speed;
float3 color;
float  life;
};
``````
``````kernel void particleKernel(texture2d<half, access::read_write>
output [[texture(0)]],
// 1
device Particle *particles [[buffer(0)]],
// 2
float xVelocity = particles[id].speed
* cos(particles[id].direction);
float yVelocity = particles[id].speed
* sin(particles[id].direction) + 3.0;
particles[id].position.x += xVelocity;
particles[id].position.y += yVelocity;
// 3
particles[id].life -= 1.0;
half4 color;
color.rgb =
half3(particles[id].color * particles[id].life / 255.0);
// 4
color.a = 1.0;
uint2 position = uint2(particles[id].position);
output.write(color, position);
output.write(color, position + uint2(0, 1));
output.write(color, position - uint2(0, 1));
output.write(color, position + uint2(1, 0));
output.write(color, position - uint2(1, 0));
}
``````

## Particle systems

Open up the Particles starter project. You’ll get a warning compile message, but that will disappear when you start adding code.

### Snow

You’ll attach a texture to each snow particle to improve the realism of your rendering. To render textured particles, as well as having a compute kernel to update the particles, you’ll also have a render pipeline with vertex and fragment functions to render them.

``````guard let computeEncoder =
commandBuffer.makeComputeCommandEncoder()
else { return }
computeEncoder.setComputePipelineState(particlesPipelineState)
let threadsPerGroup = MTLSizeMake(width, 1, 1)
for emitter in emitters {
let threadsPerGrid = MTLSizeMake(emitter.particleCount, 1, 1)
computeEncoder.setBuffer(emitter.particleBuffer,
offset: 0, index: 0)
}
computeEncoder.endEncoding()
``````
``````// 1
let renderEncoder =
commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)!
renderEncoder.setRenderPipelineState(renderPipelineState)
// 2
var size = float2(Float(view.drawableSize.width),
Float(view.drawableSize.height))
renderEncoder.setVertexBytes(&size,
length: MemoryLayout<float2>.stride,
index: 0)
// 3
for emitter in emitters {
renderEncoder.setVertexBuffer(emitter.particleBuffer,
offset: 0, index: 1)
renderEncoder.setVertexBytes(&emitter.position,
length: MemoryLayout<float2>.stride,
index: 2)
renderEncoder.setFragmentTexture(emitter.particleTexture,
index: 0)
renderEncoder.drawPrimitives(type: .point, vertexStart: 0,
vertexCount: 1,
instanceCount: emitter.currentParticles)
}
renderEncoder.endEncoding()
``````
``````// 1
kernel void compute(device Particle *particles [[buffer(0)]],
// 2
float xVelocity = particles[id].speed
* cos(particles[id].direction);
float yVelocity = particles[id].speed
* sin(particles[id].direction);
particles[id].position.x += xVelocity;
particles[id].position.y += yVelocity;
// 3
particles[id].age += 1.0;
float age = particles[id].age / particles[id].life;
particles[id].scale =  mix(particles[id].startScale,
particles[id].endScale, age);
// 4
if (particles[id].age > particles[id].life) {
particles[id].position = particles[id].startPosition;
particles[id].age = 0;
particles[id].scale = particles[id].startScale;
}
}
``````
``````struct VertexOut {
float4 position   [[position]];
float  point_size [[point_size]];
float4 color;
};
``````
``````// 1
vertex VertexOut vertex_particle(
constant float2 &size [[buffer(0)]],
const device Particle *particles [[buffer(1)]],
constant float2 &emitterPosition [[ buffer(2) ]],
uint instance [[instance_id]]) {
VertexOut out;
// 2
float2 position = particles[instance].position
+ emitterPosition;
out.position.xy = position.xy / size * 2.0 - 1.0;
out.position.z = 0;
out.position.w = 1;
// 3
out.point_size = particles[instance].size
* particles[instance].scale;
out.color = particles[instance].color;
return out;
}
``````
``````// 1
fragment float4 fragment_particle(
VertexOut in [[stage_in]],
texture2d<float> particleTexture [[texture(0)]],
float2 point [[point_coord]]) {
constexpr sampler default_sampler;
float4 color = particleTexture.sample(default_sampler, point);
if (color.a < 0.5) {
}
color = float4(color.xyz, 0.5);
color *= in.color;
return color;
}
``````
``````func snow(size: CGSize) -> Emitter {
let emitter = Emitter()

// 1
emitter.particleCount = 100
emitter.birthRate = 1
emitter.birthDelay = 20

// 2
emitter.particleTexture =

// 3
var descriptor = ParticleDescriptor()
descriptor.position.x = 0
descriptor.positionXRange = 0...Float(size.width)
descriptor.direction = -.pi / 2
descriptor.speedRange =  2...6
descriptor.pointSizeRange = 80 * 0.5...80
descriptor.startScale = 0
descriptor.startScaleRange = 0.2...1.0

// 4
descriptor.life = 500
descriptor.color = [1, 1, 1, 1]
emitter.particleDescriptor = descriptor
return emitter
}
``````
``````let snowEmitter = snow(size: metalView.drawableSize)
snowEmitter.position = [0, Float(metalView.drawableSize.height)]
emitters.append(snowEmitter)
``````
``````func mtkView(_ view: MTKView,
drawableSizeWillChange size: CGSize) {
emitters.removeAll()
let snowEmitter = snow(size: size)
snowEmitter.position = [0, Float(size.height)]
emitters.append(snowEmitter)
}
``````
``````for emitter in emitters {
emitter.emit()
}
``````

## Fire

Brrr. That snow is so cold, you need a fire. In Renderer.swift, at the end of `init(metalView)` change the snow emitter to:

``````let fireEmitter = fire(size: metalView.drawableSize)
fireEmitter.position = [0, -10]
emitters.append(fireEmitter)
``````
``````let fireEmitter = fire(size: size)
fireEmitter.position = [0, -10]
emitters.append(fireEmitter)
``````

``````descriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
``````
``````// 1
descriptor.colorAttachments[0].isBlendingEnabled = true
// 2
descriptor.colorAttachments[0].sourceRGBBlendFactor
= .sourceAlpha
descriptor.colorAttachments[0].destinationRGBBlendFactor = .one
``````

## Where to go from here?

You’ve only just begun playing with particles! There are many more particle characteristics you could include in your particle system:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below: