├── .gitignore ├── .idea └── .gitignore ├── shaders ├── vertex_shader.glsl ├── fragment_shader.glsl ├── geometry_shader.glsl ├── compute_shader_second.glsl └── compute_shader.glsl └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /shaders/vertex_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec4 in_vertex; 4 | in vec4 in_color; 5 | 6 | out vec2 vertex_pos; 7 | out float vertex_radius; 8 | out vec4 vertex_color; 9 | out float v_strength; 10 | 11 | void main() 12 | { 13 | vertex_pos = in_vertex.xy; 14 | v_strength = smoothstep(-600, 600, in_vertex.z) + .2; 15 | vertex_radius = in_vertex.w; 16 | vertex_color = in_color; 17 | } -------------------------------------------------------------------------------- /shaders/fragment_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec2 g_uv; 4 | in vec3 g_color; 5 | in float g_strength; 6 | 7 | out vec4 out_color; 8 | 9 | void main() 10 | { 11 | float l = length(vec2(0.5, 0.5) - g_uv.xy); 12 | if ( l > 0.5) 13 | { 14 | discard; 15 | } 16 | float alpha; 17 | if (l == 0.0) 18 | alpha = g_strength; 19 | else { 20 | float linfade = g_strength - (l * 2); 21 | alpha = min(1.0, linfade*linfade); 22 | //alpha = 1.0-(l*2.0)*(l*2.0); 23 | } 24 | //alpha =1; 25 | //alpha = 0.5; 26 | vec3 c = g_color.rgb; 27 | // c.xy += v_uv.xy * 0.05; 28 | // c.xy += v_pos.xy * 0.75; 29 | out_color = vec4(c, alpha); 30 | } 31 | -------------------------------------------------------------------------------- /shaders/geometry_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout (points) in; 4 | layout (triangle_strip, max_vertices = 4) out; 5 | 6 | // Use arcade's global projection UBO 7 | uniform Projection { 8 | uniform mat4 matrix; 9 | } proj; 10 | 11 | in vec2 vertex_pos[]; 12 | in vec4 vertex_color[]; 13 | in float vertex_radius[]; 14 | in float v_strength[]; 15 | 16 | out vec2 g_uv; 17 | out vec3 g_color; 18 | out float g_strength; 19 | 20 | void main() { 21 | vec2 center = vertex_pos[0]; 22 | vec2 hsize = vec2(vertex_radius[0]*3.0); 23 | g_strength = v_strength[0]; 24 | 25 | g_color = vertex_color[0].rgb; 26 | 27 | gl_Position = proj.matrix * vec4(vec2(-hsize.x, hsize.y) + center, 0.0, 1.0); 28 | g_uv = vec2(0, 1); 29 | EmitVertex(); 30 | 31 | gl_Position = proj.matrix * vec4(vec2(-hsize.x, -hsize.y) + center, 0.0, 1.0); 32 | g_uv = vec2(0, 0); 33 | EmitVertex(); 34 | 35 | gl_Position = proj.matrix * vec4(vec2(hsize.x, hsize.y) + center, 0.0, 1.0); 36 | g_uv = vec2(1, 1); 37 | EmitVertex(); 38 | 39 | gl_Position = proj.matrix * vec4(vec2(hsize.x, -hsize.y) + center, 0.0, 1.0); 40 | g_uv = vec2(1, 0); 41 | EmitVertex(); 42 | 43 | EndPrimitive(); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /shaders/compute_shader_second.glsl: -------------------------------------------------------------------------------- 1 | #version 430 2 | 3 | // Set up our compute groups 4 | layout(local_size_x=COMPUTE_SIZE_X, local_size_y=COMPUTE_SIZE_Y) in; 5 | 6 | // Input uniforms go here if you need them. 7 | // Some examples: 8 | uniform vec2 screen_size; 9 | //uniform vec2 force; 10 | //uniform float frame_time; 11 | 12 | uniform vec2 mouse_pos; 13 | uniform float lmb; 14 | uniform float rmb; 15 | uniform float mmb; 16 | 17 | // Structure of the ball data 18 | struct Ball 19 | { 20 | vec4 pos; 21 | vec4 vel; 22 | vec4 veldup; 23 | vec4 color; 24 | }; 25 | 26 | // Input buffer 27 | layout(std430, binding=0) buffer balls_in 28 | { 29 | Ball balls[]; 30 | } In; 31 | 32 | // Output buffer 33 | layout(std430, binding=1) buffer balls_out 34 | { 35 | Ball balls[]; 36 | } Out; 37 | 38 | void main() 39 | { 40 | int curBallIndex = int(gl_GlobalInvocationID); 41 | 42 | Ball in_ball = In.balls[curBallIndex]; 43 | 44 | Ball out_ball; 45 | out_ball.pos.xyz = in_ball.pos.xyz + (in_ball.veldup.xyz); 46 | out_ball.pos.w = in_ball.pos.w; 47 | out_ball.vel.xyzw = in_ball.veldup.xyzw; 48 | out_ball.veldup.xyzw = in_ball.veldup.xyzw; 49 | 50 | vec4 c = in_ball.color.xyzw; 51 | out_ball.color.xyzw = c.xyzw; 52 | 53 | Out.balls[curBallIndex] = out_ball; 54 | } 55 | -------------------------------------------------------------------------------- /shaders/compute_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 430 2 | 3 | // Set up our compute groups 4 | layout(local_size_x=COMPUTE_SIZE_X, local_size_y=COMPUTE_SIZE_Y) in; 5 | 6 | // Input uniforms go here if you need them. 7 | // Some examples: 8 | uniform vec2 screen_size; 9 | //uniform vec2 force; 10 | //uniform float frame_time; 11 | 12 | uniform vec2 mouse_pos; 13 | uniform float lmb; 14 | uniform float rmb; 15 | uniform float mmb; 16 | 17 | // Structure of the ball data 18 | struct Ball 19 | { 20 | vec4 pos; 21 | vec4 vel; 22 | vec4 veldup; 23 | vec4 color; 24 | }; 25 | 26 | // Input buffer 27 | layout(std430, binding=0) buffer balls_in 28 | { 29 | Ball balls[]; 30 | } In; 31 | 32 | // Output buffer 33 | layout(std430, binding=1) buffer balls_out 34 | { 35 | Ball balls[]; 36 | } Out; 37 | 38 | void main() 39 | { 40 | int curBallIndex = int(gl_GlobalInvocationID); 41 | 42 | Ball in_ball = In.balls[curBallIndex]; 43 | 44 | vec4 p = in_ball.pos.xyzw; 45 | vec4 v = in_ball.vel.xyzw; 46 | 47 | float pressure = 1.0; 48 | 49 | // Move the ball according to the current force 50 | //p.xyz += v.xyz; 51 | 52 | int ballnum = In.balls.length(); 53 | float ballnumf = ballnum; 54 | // Calculate the new force based on all the other bodies 55 | for (int i = 0; i < ballnum; i++) { 56 | // If enabled, this will keep the star from calculating gravity on itself 57 | // However, it does slow down the calcluations do do this check. 58 | if (i == curBallIndex) 59 | continue; 60 | 61 | 62 | 63 | 64 | vec3 diff = p.xyz - In.balls[i].pos.xyz; 65 | float distsqr = (diff.x * diff.x + diff.y * diff.y + diff.z * diff.z); 66 | float dist = sqrt(distsqr); 67 | float rad = p.w*2.0; 68 | /*if (dist == 0.0){ 69 | v.x += i * 0.001; 70 | 71 | } 72 | else*/ 73 | if(dist < rad*2.0) { 74 | 75 | vec3 direction = diff / (dist + curBallIndex * 0.0000001); 76 | float diffdist = rad*2.0 - dist; 77 | pressure += diffdist; 78 | if (dist < rad * 1.0) { 79 | diffdist = rad - dist; 80 | 81 | //distsqr = dist * dist; 82 | //vec3 rediff = direction * ((distsqr * 0.01) - 0.0); 83 | vec3 force = direction * (diffdist); 84 | 85 | //vec3 force = rediff / (ballnum); 86 | float pressurediff = In.balls[i].vel.w / v.w; 87 | 88 | v.xyz += force * .5 * pressurediff; 89 | //if(dist < 30) 90 | 91 | float pressuresum = In.balls[i].vel.w + v.w; 92 | float selfmul = (v.w / pressuresum); 93 | float othermul = 1.0 - selfmul;//0.01;//In.balls[i].vel.w/pressuresum; 94 | v.xyz = v.xyz * selfmul + In.balls[i].vel.xyz * othermul; 95 | } 96 | } 97 | 98 | 99 | } 100 | 101 | 102 | if (p.x < 0) { 103 | p.x = 0; 104 | v.x = abs(v.x); 105 | } 106 | if (p.y < 0) { 107 | p.y = 0; 108 | v.y = abs(v.y); 109 | } 110 | if (p.z < 0)p.z = 0; 111 | 112 | 113 | if (p.x > screen_size.x) { 114 | p.x = screen_size.x; 115 | v.x = abs(v.x) * -1; 116 | } 117 | if (p.y > screen_size.y) { 118 | p.y = screen_size.y; 119 | v.y = abs(v.y) * -1; 120 | 121 | } 122 | if (p.z > 1000)p.z = 999; 123 | 124 | vec3 other = vec3(mouse_pos.x, mouse_pos.y, 0); 125 | 126 | vec3 diff = p.xyz - other;//In.balls[i].pos.xyz; 127 | float distsqr = (diff.x * diff.x + diff.y * diff.y + diff.z * diff.z); 128 | float dist = sqrt(distsqr); 129 | if (dist < 500) { 130 | 131 | vec3 direction = diff / (dist ); 132 | 133 | vec3 rediff = direction * ((dist) - 500.0 * rmb); 134 | 135 | vec3 force = rediff; 136 | 137 | force *= lmb; 138 | 139 | vec3 force2 = vec3(force.y*-0.1, force.x, force.z); 140 | force += force2; 141 | 142 | v.xyz += force * -0.001; 143 | } 144 | 145 | 146 | 147 | 148 | v.xyz*= 0.999-(0.10*mmb); 149 | 150 | v.y+=-0.01; 151 | v.w = pressure; 152 | 153 | float speed = sqrt(v.x*v.x+v.y*v.y); 154 | // if (speed > 4){ 155 | // vec3 direc = v.xyz/speed; 156 | // v.xyz = direc * 4.0; 157 | // } 158 | 159 | 160 | Ball out_ball; 161 | out_ball.pos.xyzw = p.xyzw; 162 | out_ball.vel.xyzw = in_ball.vel.xyzw; 163 | out_ball.veldup.xyzw = v.xyzw; 164 | 165 | vec4 c = in_ball.color.xyzw; 166 | out_ball.color.xyzw = c.xyzw; 167 | out_ball.color.x = 0.001 * pressure; 168 | out_ball.color.y = 0.1 * speed; 169 | out_ball.color.z = 0.5; 170 | 171 | 172 | Out.balls[curBallIndex] = out_ball; 173 | } 174 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Compute shader with buffers 3 | """ 4 | import random 5 | from array import array 6 | 7 | import arcade 8 | from arcade.gl import BufferDescription 9 | 10 | # Window dimensions 11 | WINDOW_WIDTH = 1800 12 | WINDOW_HEIGHT = 1000 13 | 14 | # Size of performance graphs 15 | GRAPH_WIDTH = 200 16 | GRAPH_HEIGHT = 120 17 | GRAPH_MARGIN = 5 18 | 19 | RADIUS = 8 20 | BALLNUM = 10000 21 | 22 | 23 | class MyWindow(arcade.Window): 24 | 25 | def __init__(self): 26 | # Call parent constructor 27 | # Ask for OpenGL 4.3 context, as we need that for compute shader support. 28 | super().__init__(WINDOW_WIDTH, WINDOW_HEIGHT, 29 | "Compute Shader", 30 | gl_version=(4, 3), 31 | resizable=True) 32 | self.center_window() 33 | 34 | self.mx = 0 35 | self.my = 0 36 | 37 | self.lmb = 0 38 | self.rmb = 0 39 | self.mmb = 0 40 | 41 | # --- Class instance variables 42 | 43 | # Number of balls to move 44 | self.num_stars = BALLNUM 45 | 46 | # This has something to do with how we break the calculations up 47 | # and parallelize them. 48 | self.group_x = 256 49 | self.group_y = 1 50 | 51 | # --- Create buffers 52 | 53 | # Format of the buffer data. 54 | # 4f = position and size -> x, y, z, radius 55 | # 4x4 = Four floats used for calculating velocity. Not needed for visualization. 56 | # 4f = color -> rgba 57 | buffer_format = "4f 4x4 4x4 4f" 58 | # Generate the initial data that we will put in buffer 1. 59 | # Pick one of these or make your own function 60 | initial_data = self.gen_random_space() 61 | # initial_data = self.gen_galaxies_colliding() 62 | 63 | # Create data buffers for the compute shader 64 | # We ping-pong render between these two buffers 65 | # ssbo = shader storage buffer object 66 | self.ssbo_1 = self.ctx.buffer(data=array('f', initial_data)) 67 | self.ssbo_2 = self.ctx.buffer(reserve=self.ssbo_1.size) 68 | 69 | # Attribute variable names for the vertex shader 70 | attributes = ["in_vertex", "in_color"] 71 | self.vao_1 = self.ctx.geometry( 72 | [BufferDescription(self.ssbo_1, buffer_format, attributes)], 73 | mode=self.ctx.POINTS, 74 | ) 75 | self.vao_2 = self.ctx.geometry( 76 | [BufferDescription(self.ssbo_2, buffer_format, attributes)], 77 | mode=self.ctx.POINTS, 78 | ) 79 | 80 | # --- Create shaders 81 | 82 | # Load in the shader source code 83 | file = open("shaders/compute_shader.glsl") 84 | compute_shader_source = file.read() 85 | file = open("shaders/compute_shader_second.glsl") 86 | compute_shader_second_source = file.read() 87 | file = open("shaders/vertex_shader.glsl") 88 | vertex_shader_source = file.read() 89 | file = open("shaders/fragment_shader.glsl") 90 | fragment_shader_source = file.read() 91 | file = open("shaders/geometry_shader.glsl") 92 | geometry_shader_source = file.read() 93 | 94 | # Create our compute shader. 95 | # Search/replace to set up our compute groups 96 | compute_shader_source = compute_shader_source.replace("COMPUTE_SIZE_X", 97 | str(self.group_x)) 98 | compute_shader_source = compute_shader_source.replace("COMPUTE_SIZE_Y", 99 | str(self.group_y)) 100 | self.compute_shader = self.ctx.compute_shader(source=compute_shader_source) 101 | 102 | 103 | compute_shader_second_source = compute_shader_second_source.replace("COMPUTE_SIZE_X", 104 | str(self.group_x)) 105 | compute_shader_second_source = compute_shader_second_source.replace("COMPUTE_SIZE_Y", 106 | str(self.group_y)) 107 | self.compute_shader_second = self.ctx.compute_shader(source=compute_shader_second_source) 108 | 109 | # Program for visualizing the balls 110 | self.program = self.ctx.program( 111 | vertex_shader=vertex_shader_source, 112 | geometry_shader=geometry_shader_source, 113 | fragment_shader=fragment_shader_source, 114 | ) 115 | 116 | # --- Create FPS graph 117 | 118 | # Enable timings for the performance graph 119 | arcade.enable_timings() 120 | 121 | # Create a sprite list to put the performance graph into 122 | self.perf_graph_list = arcade.SpriteList() 123 | 124 | # Create the FPS performance graph 125 | graph = arcade.PerfGraph(GRAPH_WIDTH, GRAPH_HEIGHT, graph_data="FPS") 126 | graph.center_x = GRAPH_WIDTH / 2 127 | graph.center_y = self.height - GRAPH_HEIGHT / 2 128 | self.perf_graph_list.append(graph) 129 | 130 | def on_mouse_motion(self, x, y, dx, dy): 131 | """ Handle Mouse Motion """ 132 | 133 | # Move the center of the player sprite to match the mouse x, y 134 | self.mx = x 135 | self.my = y 136 | 137 | def on_mouse_press(self, x, y, button, modifiers): 138 | if button == 1: self.lmb = 1 139 | if button == 4: self.rmb = 1 140 | if button == 2: self.mmb = 1 141 | 142 | def on_mouse_release(self, x, y, button, modifiers): 143 | if button == 1: self.lmb = 0 144 | if button == 4: self.rmb = 0 145 | if button == 2: self.mmb = 0 146 | 147 | def on_draw(self): 148 | # Clear the screen 149 | self.clear() 150 | # Enable blending so our alpha channel works 151 | self.ctx.enable(self.ctx.BLEND) 152 | 153 | # Bind buffers 154 | self.ssbo_1.bind_to_storage_buffer(binding=0) 155 | self.ssbo_2.bind_to_storage_buffer(binding=1) 156 | 157 | # Set input variables for compute shader 158 | # These are examples, although this example doesn't use them 159 | self.compute_shader["screen_size"] = self.get_size() 160 | # self.compute_shader["force"] = force 161 | # self.compute_shader["frame_(time"] = self.run_time 162 | self.compute_shader["mouse_pos"] = (self.mx, self.my) 163 | self.compute_shader["lmb"] = self.lmb 164 | self.compute_shader["rmb"] = self.rmb 165 | self.compute_shader["mmb"] = self.mmb 166 | 167 | # Run compute shader 168 | self.compute_shader.run(group_x=self.group_x, group_y=self.group_y) 169 | 170 | try: 171 | self.compute_shader_second["screen_size"] = self.get_size() 172 | except: 173 | pass 174 | # self.compute_shader_second["force"] = force 175 | # self.compute_shader_second["frame_(time"] = self.run_time 176 | # self.compute_shader_second["mouse_pos"] = (self.mx, self.my) 177 | # self.compute_shader_second["lmb"] = self.lmb 178 | # self.compute_shader_second["rmb"] = self.rmb 179 | # self.compute_shader_second["mmb"] = self.mmb 180 | 181 | self.ssbo_1, self.ssbo_2 = self.ssbo_2, self.ssbo_1 182 | self.ssbo_1.bind_to_storage_buffer(binding=0) 183 | self.ssbo_2.bind_to_storage_buffer(binding=1) 184 | # Run compute shader 185 | #self.ssbo_1, self.ssbo_2 = self.ssbo_2, self.ssbo_1 186 | #self.vao_1, self.vao_2 = self.vao_2, self.vao_1 187 | self.compute_shader_second.run(group_x=self.group_x, group_y=self.group_y) 188 | 189 | 190 | 191 | # Draw the balls 192 | self.vao_1.render(self.program) 193 | 194 | # Swap the buffers around (we are ping-ping rendering between two buffers) 195 | self.ssbo_1, self.ssbo_2 = self.ssbo_2, self.ssbo_1 196 | # Swap what geometry we draw 197 | self.vao_1, self.vao_2 = self.vao_2, self.vao_1 198 | 199 | # Draw the graphs 200 | self.perf_graph_list.draw() 201 | 202 | def gen_random_space(self): 203 | radius = RADIUS 204 | 205 | for i in range(self.num_stars): 206 | # Position/radius 207 | 208 | yield random.random() * WINDOW_WIDTH 209 | yield random.random() * WINDOW_HEIGHT 210 | yield 0 # random.random() * WINDOW_HEIGHT 211 | yield radius 212 | 213 | # # pressure 214 | # yield 0.0 215 | # yield 0.0 216 | # yield 0.0 217 | # yield 0.0 218 | 219 | # Velocity 220 | yield 0.0 # random.randrange(-2.0, 2.0) 221 | yield 0.0 # random.randrange(-2.0, 2.0) 222 | yield 0.0 223 | yield 1.0 # pressure 224 | 225 | # Velocity dupe 226 | yield 0.0 # random.randrange(-2.0, 2.0) 227 | yield 0.0 # random.randrange(-2.0, 2.0) 228 | yield 0.0 229 | yield 1.0 # pressure 230 | 231 | # Color 232 | yield 0# random.random() # r 233 | yield 0.5+random.random()*0.5 # g 234 | yield 0# random.random() # b 235 | yield 1.0 # a 236 | 237 | 238 | app = MyWindow() 239 | arcade.run() 240 | --------------------------------------------------------------------------------