|
| 1 | +""" |
| 2 | +Example showing how er can emit particles with the gpu. |
| 3 | +We work with transform shaders to do all the logic. |
| 4 | +
|
| 5 | +This example was created on a Raspberry PI 4 |
| 6 | +""" |
| 7 | +import struct |
| 8 | +import random |
| 9 | +from array import array |
| 10 | +import arcade |
| 11 | +from arcade.gl import BufferDescription |
| 12 | + |
| 13 | + |
| 14 | + |
| 15 | +class TransformEmit(arcade.Window): |
| 16 | + |
| 17 | + def __init__(self): |
| 18 | + super().__init__(800, 600, "Transform Emit") |
| 19 | + self.time = 0 |
| 20 | + |
| 21 | + # Progam to simply draw the points |
| 22 | + self.visualize_points_program = self.ctx.program( |
| 23 | + vertex_shader=""" |
| 24 | + #version 330 |
| 25 | +
|
| 26 | + in vec2 in_pos; |
| 27 | + in vec3 in_col; |
| 28 | +
|
| 29 | + out vec3 color; |
| 30 | +
|
| 31 | + void main() { |
| 32 | + gl_Position = vec4(in_pos, 0.0, 1.0); |
| 33 | + color = in_col; |
| 34 | + } |
| 35 | + """, |
| 36 | + fragment_shader=""" |
| 37 | + #version 330 |
| 38 | +
|
| 39 | + out vec4 fragColor; |
| 40 | + in vec3 color; |
| 41 | +
|
| 42 | + void main() { |
| 43 | + fragColor = vec4(color, 1.0); |
| 44 | + } |
| 45 | + """ |
| 46 | + ) |
| 47 | + |
| 48 | + # Program to emit points into a buffer |
| 49 | + self.emit_program = self.ctx.program( |
| 50 | + vertex_shader=""" |
| 51 | + #version 330 |
| 52 | + #define M_PI 3.1415926535897932384626433832795 |
| 53 | + uniform float time; |
| 54 | + |
| 55 | + out vec2 out_pos; |
| 56 | + out vec2 out_vel; |
| 57 | +
|
| 58 | + float rand(float n) { return fract(sin(n) * 43758.5453123); } |
| 59 | +
|
| 60 | + void main() { |
| 61 | + float a = mod(time * float(gl_VertexID) + rand(time), M_PI * 2.0); |
| 62 | + float r = clamp(rand(time + float(gl_VertexID)), 0.1, 0.9); |
| 63 | +
|
| 64 | + out_pos = vec2(0.0); |
| 65 | + out_vel = vec2(sin(a), cos(a)) * r; |
| 66 | + } |
| 67 | + """, |
| 68 | + varyings=["out_pos", "out_vel"], |
| 69 | + varyings_capture_mode="separate", |
| 70 | + ) |
| 71 | + |
| 72 | + |
| 73 | + self.move_program = self.ctx.program( |
| 74 | + vertex_shader=""" |
| 75 | + #version 330 |
| 76 | +
|
| 77 | + in vec2 in_pos; |
| 78 | + in vec2 in_vel; |
| 79 | +
|
| 80 | + out vec2 v_pos; |
| 81 | + out vec2 v_vel; |
| 82 | +
|
| 83 | + void main() { |
| 84 | + v_pos = in_pos; |
| 85 | + v_vel = in_vel; |
| 86 | + } |
| 87 | + """, |
| 88 | + geometry_shader=""" |
| 89 | + #version 330 |
| 90 | +
|
| 91 | + layout(points) in; |
| 92 | + layout(points, max_vertices = 1) out; |
| 93 | +
|
| 94 | + uniform float dt; |
| 95 | +
|
| 96 | + in vec2 v_pos[]; |
| 97 | + in vec2 v_vel[]; |
| 98 | +
|
| 99 | + out vec2 out_pos; |
| 100 | + out vec2 out_vel; |
| 101 | +
|
| 102 | + void main() { |
| 103 | + vec2 pos = v_pos[0]; |
| 104 | + vec2 vel = v_vel[0]; |
| 105 | +
|
| 106 | + if (pos.y > -1.0) { |
| 107 | + out_pos = pos + vel * dt; |
| 108 | + out_vel = vel - vec2(0.0, dt); |
| 109 | + EmitVertex(); |
| 110 | + } |
| 111 | +
|
| 112 | + } |
| 113 | + """, |
| 114 | + varyings=["out_pos", "out_vel"], |
| 115 | + varyings_capture_mode="separate", |
| 116 | + ) |
| 117 | + |
| 118 | + # Configuration |
| 119 | + self.num_points = 10_000 |
| 120 | + self.active_points = 0 |
| 121 | + self.emit_max_points = 100 |
| 122 | + |
| 123 | + # First set of positons and velocities |
| 124 | + self.buffer_pos_1 = self.ctx.buffer(reserve=self.num_points * 8) |
| 125 | + self.buffer_vel_1 = self.ctx.buffer(reserve=self.num_points * 8) |
| 126 | + |
| 127 | + # Second set of positons and velocities |
| 128 | + self.buffer_pos_2 = self.ctx.buffer(reserve=self.num_points * 8) |
| 129 | + self.buffer_vel_2 = self.ctx.buffer(reserve=self.num_points * 8) |
| 130 | + |
| 131 | + self.buffer_colors = self.ctx.buffer(data=array('f', [random.random() for _ in range(self.num_points * 3)])) |
| 132 | + |
| 133 | + # Geomtry definition for drawing the two sets of positions |
| 134 | + self.draw_geometry_1 = self.ctx.geometry( |
| 135 | + [ |
| 136 | + BufferDescription(self.buffer_pos_1, "2f", ("in_pos",)), |
| 137 | + BufferDescription(self.buffer_colors, "3f", ("in_col",)), |
| 138 | + ], |
| 139 | + mode=self.ctx.POINTS, |
| 140 | + ) |
| 141 | + self.draw_geometry_2 = self.ctx.geometry( |
| 142 | + [ |
| 143 | + BufferDescription(self.buffer_pos_2, "2f", ("in_pos",)), |
| 144 | + BufferDescription(self.buffer_colors, "3f", ("in_col",)), |
| 145 | + ], |
| 146 | + mode=self.ctx.POINTS, |
| 147 | + ) |
| 148 | + |
| 149 | + # Geometry we use to move he points |
| 150 | + self.move_geometry_1 = self.ctx.geometry( |
| 151 | + [ |
| 152 | + BufferDescription(self.buffer_pos_1, "2f", ("in_pos",)), |
| 153 | + BufferDescription(self.buffer_vel_1, "2f", ("in_vel",)) |
| 154 | + ], |
| 155 | + mode=self.ctx.POINTS, |
| 156 | + ) |
| 157 | + self.move_geometry_2 = self.ctx.geometry( |
| 158 | + [ |
| 159 | + BufferDescription(self.buffer_pos_2, "2f", ("in_pos",)), |
| 160 | + BufferDescription(self.buffer_vel_2, "2f", ("in_vel",)) |
| 161 | + ], |
| 162 | + mode=self.ctx.POINTS, |
| 163 | + ) |
| 164 | + |
| 165 | + self.emit_geometry = self.ctx.geometry() |
| 166 | + self.query = self.ctx.query(primitives=True) |
| 167 | + |
| 168 | + def on_draw(self): |
| 169 | + self.clear() |
| 170 | + self.ctx.enable_only() |
| 171 | + |
| 172 | + self.draw_geometry_2.render(self.visualize_points_program) |
| 173 | + |
| 174 | + # Swap things around for the next frame |
| 175 | + self.draw_geometry_1, self.draw_geometry_2 = self.draw_geometry_2, self.draw_geometry_1 |
| 176 | + self.buffer_pos_1, self.buffer_pos_2 = self.buffer_pos_2, self.buffer_pos_1 |
| 177 | + self.buffer_vel_1, self.buffer_vel_2 = self.buffer_vel_2, self.buffer_vel_1 |
| 178 | + |
| 179 | + def on_update(self, delta_time): |
| 180 | + self.time += delta_time |
| 181 | + # print("active points", self.active_points) |
| 182 | + |
| 183 | + # Do we have poins to emit? |
| 184 | + if self.active_points < self.num_points: |
| 185 | + |
| 186 | + emit_count = min(self.num_points - self.active_points, self.emit_max_points) |
| 187 | + # print("Emitting", emit_count) |
| 188 | + |
| 189 | + # Emit some particles |
| 190 | + self.emit_program["time"] = self.time |
| 191 | + self.emit_geometry.transform( |
| 192 | + self.emit_program, |
| 193 | + [self.buffer_pos_1, self.buffer_vel_1], |
| 194 | + vertices=emit_count, |
| 195 | + buffer_offset=self.active_points * 8, |
| 196 | + ) |
| 197 | + self.active_points += emit_count |
| 198 | + |
| 199 | + # Move the poins. This will also purge dead particles |
| 200 | + self.move_program["dt"] = delta_time |
| 201 | + with self.query: |
| 202 | + self.move_geometry_1.transform( |
| 203 | + self.move_program, |
| 204 | + [self.buffer_pos_2, self.buffer_vel_2], |
| 205 | + vertices=self.active_points, |
| 206 | + ) |
| 207 | + self.active_points = self.query.primitives_generated |
| 208 | + # print("after moving", self.active_points) |
| 209 | + |
| 210 | + |
| 211 | +TransformEmit().run() |
0 commit comments