Implement compute-shader simulation of particles!
[lovr-particles] / main.lua
1 local mat4 = lovr.math.mat4
2
3 local nParticles = 2 ^ 14
4 local batchSize = 128
5 local batches = nParticles / batchSize
6
7 local features
8 local shaders = {}
9 local blocks = {}
10 local meshes = {}
11 local compute = {}
12
13 function lovr.load()
14 features = lovr.graphics.getFeatures()
15 shaders.particles = lovr.graphics.newShader("shaders/particles.vert", "shaders/particles.frag")
16
17 blocks.particles = lovr.graphics.newShaderBlock("compute", {
18 -- Reading a value seems to work around whatever the rendering glitch
19 -- was. So we include this flag so we only have to ask for a single
20 -- value, instead of having to copy a large array. Still, annoying :(
21 -- For reference, see issue comment at:
22 -- https://github.com/bjornbytes/lovr/issues/211#issuecomment-580985010
23 reloadFlag = "float",
24 particles = {"mat4", nParticles},
25 }, { readable = true })
26 shaders.particles:sendBlock('particleData', blocks.particles)
27 shaders.particles:send("batchSize", batchSize)
28
29 meshes.particle = makeParticleMesh(batchSize)
30
31 compute.particles = lovr.graphics.newComputeShader("shaders/particles.comp")
32 compute.particles:sendBlock('particleData', blocks.particles)
33 compute.particles:send("dt", 0.01)
34
35 local xr, yr, zr = range(-1, 1), range(1, 3), range(-2, 0)
36 local sr, vr = range(0.005, 0.01), range(-0.05, 0.05)
37 local initial = initialParticleData(nParticles, xr, yr, zr, sr, vr)
38 blocks.particles:send("particles", initial)
39 end
40
41 function lovr.draw()
42 lovr.graphics.clear()
43 local x, y, z = lovr.headset.getPose("head")
44 lovr.graphics.compute(compute.particles, batches)
45 -- Read the buffer to force SSBO synch of some sort.
46 blocks.particles:read("reloadFlag")
47 shaders.particles:send("headPos", {x, y, z})
48
49 lovr.graphics.setColor(0.6, 0.1, 0)
50 lovr.graphics.setShader(nil)
51 for _, hand in ipairs(lovr.headset.getHands()) do
52 local x, y, z, a, ax, ay, az = lovr.headset.getPose(hand)
53 lovr.graphics.cube("fill", x, y, z, 0.1, a, ax, ay, az)
54 end
55
56 withDepthTest("lequal", false, function()
57 lovr.graphics.setColor(1, 1, 1)
58 lovr.graphics.setShader(shaders.particles)
59 meshes.particle:draw(mat4(), batches)
60 end)
61 end
62
63 function makeParticleMesh(size)
64 local format = { {'vertPosition', 'float', 2}, }
65 local verts = {}
66 local vertMap = {}
67 local insert = table.insert
68 for chunk = 1, size do
69 insert(verts, { 1, 1})
70 insert(verts, { 1, -1})
71 insert(verts, {-1, -1})
72 insert(verts, {-1, 1})
73 local base = 4 * (chunk - 1)
74 insert(vertMap, 3 + base)
75 insert(vertMap, 2 + base)
76 insert(vertMap, 1 + base)
77 insert(vertMap, 4 + base)
78 insert(vertMap, 3 + base)
79 insert(vertMap, 1 + base)
80 end
81 local mesh = lovr.graphics.newMesh(format, verts, "triangles", "static")
82 mesh:setVertexMap(vertMap)
83 return mesh
84 end
85
86 function range(min, max)
87 local scale = max - min
88 return function()
89 return min + lovr.math.random() * scale
90 end
91 end
92
93 function initialParticleData(size, xRange, yRange, zRange, sRange, vRange)
94 local particles = {}
95 local insert = table.insert
96 for i = 1, size do
97 insert(particles, {
98 xRange(), yRange(), zRange(), sRange(),
99 vRange(), vRange(), vRange(), 1,
100 0, 0, 0, 0,
101 0, 0, 0, 0,
102 })
103 end
104 return particles
105 end
106
107 function withDepthTest(compareMode, write, callback)
108 local oldComp, oldWrite = lovr.graphics.getDepthTest()
109 lovr.graphics.setDepthTest(compareMode, write)
110 callback()
111 lovr.graphics.setDepthTest(oldComp, oldWrite)
112 end