InstancedMesh
12,400 pillars that move with a wave, drawn from one mesh and one draw call (drawMesh(_:instances:); flip the parameter to see the per-copy cost)
- Run
swift run --package-path Examples Example-Rendering-InstancedMesh- Source
- Examples/Rendering/InstancedMesh/Sketch.swift
import Ollin
/// A field of 12,400 pillars from ONE mesh and ONE draw call. Each copy is a
/// `MeshInstance`: a position, a rotation, a scale, and a tint. The mesh's
/// vertices upload once; the GPU places every copy, so the whole field costs
/// about what a single box costs the CPU. The pillars move with a traveling wave
/// (the instance list is rebuilt each frame, which is just 12,400 small
/// structs), they shade like any solid mesh, and they drop real shadows onto
/// the floor.
///
/// Flip the **instanced** parameter off to draw the same field the ordinary way,
/// one `drawMesh` per pillar, and watch the frame time in the inspector: that
/// gap is the per-copy CPU cost instancing removes.
@main
final class InstancedMesh: Sketch {
@Param var instanced = true // off = one drawMesh call per pillar
private let pillar = Mesh.box(width: 0.11, height: 1, depth: 0.11)
private var seats: [(position: Vector3, radius: Double, angle: Double)] = []
override func setup() {
seed(90_210)
// A polar field: rings of seats from the center out, jittered a little
// so the grid reads organic. Built once; only heights change per frame.
var r = 1.4
while r < 10.5 {
let count = Int(r * 50)
for i in 0 ..< count {
let a = Double(i) / Double(count) * .tau + random(-0.02, 0.02)
let rr = r + random(-0.06, 0.06)
seats.append((Vector3(cos(a) * rr, 0, sin(a) * rr), rr, a))
}
r += 0.22
}
}
override func draw() {
background(Color(hex: 0x0E1016))
cameraShowcase(.turntable(period: .tau / 0.12), target: Vector3(0, 0.9, 0),
radius: 15, elevation: 0.55, fieldOfView: .pi / 4.2)
ambientLight(Color(white: 0.14))
directionalLight(Color(hue: 0.09, saturation: 0.22, brightness: 1.0),
direction: Vector3(-0.5, -0.85, -0.35), intensity: 1.0)
directionalLight(Color(white: 0.5), direction: Vector3(0.55, 0.35, 0.5), intensity: 0.25,
castsShadow: false) // a fill: one shadow pass, not two
castShadows()
// The floor that catches the field's shadows.
withState {
fill(Color(white: 0.78))
specular(0.05)
drawPlane(width: 26, depth: 26)
}
specular(0.25)
specularSharpness(36)
// One traveling wave over the polar field: each pillar's height follows
// a crest moving outward, and its tint follows its height.
let low = Color(hex: 0x27435F)
let high = Color(hex: 0xF2B75C)
func height(_ seat: (position: Vector3, radius: Double, angle: Double)) -> Double {
let crest = unipolar(sin(seat.radius * 1.15 - time * 1.6))
let swirl = unipolar(sin(seat.angle * 3 + time * 0.7))
return 0.25 + crest * (1.6 + swirl * 1.2)
}
if instanced {
var instances: [MeshInstance] = []
instances.reserveCapacity(seats.count)
for seat in seats {
let h = height(seat)
instances.append(MeshInstance(
position: Vector3(seat.position.x, h / 2, seat.position.z),
scale: Vector3(1, h, 1),
color: Color.mix(low, high, (h - 0.25) / 2.8)))
}
drawMesh(pillar, instances: instances)
} else {
for seat in seats {
let h = height(seat)
withState {
translate(seat.position.x, h / 2, seat.position.z)
scale(1, h, 1)
fill(Color.mix(low, high, (h - 0.25) / 2.8))
drawMesh(pillar)
}
}
}
drawCaption("Instanced meshes: 12,400 pillars, one draw call")
}
}