forked from ThePathfindersCodex/Godot-Boids-Compute-Shader
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompute_boids.glsl
More file actions
158 lines (130 loc) · 4.42 KB
/
compute_boids.glsl
File metadata and controls
158 lines (130 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#[compute]
#version 450
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
// Image buffers with encoded particle data
layout(rgba32f, set = 0, binding = 0) uniform restrict image2D input_particles; // R=pos.x, G=pos.y, B=vel.x, A=vel.y
layout(rgba32f, set = 1, binding = 0) uniform restrict image2D output_particles;
// Parameters
layout(push_constant, std430) uniform Params {
float dt;
float boids_count;
float compute_texture_size;
float vision_radius;
float alignment_force;
float cohesion_force;
float separation_force;
float steering_force;
float min_speed;
float max_speed;
float drag;
float movement_randomness;
float movement_scaling;
float image_size;
float zone_size_mult;
} params;
// Utility
vec2 limit(vec2 v, float max_val) {
float mag = length(v);
if (mag > max_val) return normalize(v) * max_val;
return v;
}
// Random vec2
vec2 random_dir(uint id, float scale) {
uint seed = id * 1664525u + 1013904223u;
float ang = float(seed % 6283u) * 0.001f;
return vec2(cos(ang), sin(ang)) * scale;
}
// Border clamp/wrap
void apply_border(inout vec2 pos, inout vec2 vel) {
float zone_size = params.image_size * params.zone_size_mult;
float half_size = zone_size * 0.5;
if (pos.x < -half_size) pos.x = half_size;
if (pos.x > half_size) pos.x = -half_size;
if (pos.y < -half_size) pos.y = half_size;
if (pos.y > half_size) pos.y = -half_size;
}
// Distance check with border wrapping
vec2 toroidal_diff(vec2 a, vec2 b, vec2 world_size) {
vec2 d = b - a;
// shift into [-0.5*world_size, 0.5*world_size]
d -= world_size * round(d / world_size);
return d;
}
// Prevent problems with normalize on zero vectors
vec2 safe_normalize(vec2 v) {
float len = length(v);
return len > 0.0001 ? v / len : vec2(0.0);
}
// Core Boids sim
void run_sim() {
ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
int id = int(uv.y * params.compute_texture_size + uv.x);
if (id >= params.boids_count || uv.x >= params.compute_texture_size || uv.y >= params.compute_texture_size) {
return;
}
vec4 pixel = imageLoad(input_particles, uv);
vec2 pos = pixel.rg;
vec2 vel = pixel.ba;
// Accumulators
vec2 align = vec2(0.0);
vec2 coh = vec2(0.0);
vec2 sep = vec2(0.0);
int neighbor_count = 0;
// World size
float zone_size = params.image_size * params.zone_size_mult;
// Neighbor checks with wrapping
for (uint i = 0; i < uint(params.boids_count); i++) {
if (i == id) continue;
// Map particle index to 2D texel coordinates
ivec2 other_uv = ivec2(i % int(params.compute_texture_size), i / params.compute_texture_size);
vec4 other_pixel = imageLoad(input_particles, other_uv);
// Get particle position
vec2 other_pos = other_pixel.rg;
vec2 other_vel = other_pixel.ba;
// Distance between
vec2 diff = toroidal_diff(pos, other_pos, vec2(zone_size)); // wrapped around
float dist = length(diff);
if (dist < params.vision_radius && dist > 0.0001) {
neighbor_count++;
align += other_vel;
//coh += other_pos;
coh += pos + diff; // accumulate the wrapped "other_pos"
sep -= diff / (dist * dist); // stronger when closer
}
}
// Normalize and adjust primary simulation forces
if (neighbor_count > 0) {
align = safe_normalize(align / neighbor_count) * params.alignment_force;
coh = safe_normalize((coh / neighbor_count - pos)) * params.cohesion_force;
sep = safe_normalize(sep) * params.separation_force;
} else {
align = vec2(0.0);
coh = vec2(0.0);
sep = vec2(0.0);
}
// Combine forces
vec2 accel = align + coh + sep;
// Add randomness
accel += random_dir(id + uint(gl_WorkGroupID.x), params.movement_randomness);
// Scale by global movement scaling
accel *= params.movement_scaling;
// Limit steering
accel = limit(accel, params.steering_force);
// Apply acceleration
vel += accel * params.dt;
// Apply damping
vel *= params.drag;
// Clamp speeds
float speed = length(vel);
if (speed < params.min_speed) vel = normalize(vel) * params.min_speed;
if (speed > params.max_speed) vel = normalize(vel) * params.max_speed;
// Integrate position using timestep
pos += vel * params.dt;
// Wraparound border
apply_border(pos, vel);
// Write back
imageStore(output_particles, uv, vec4(pos, vel));
}
void main() {
run_sim();
}