Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 1 | //////////////////////////////////////////////////////////////////////////////// |
| 2 | // Utilities |
| 3 | //////////////////////////////////////////////////////////////////////////////// |
| 4 | var<private> rand_seed : vec2<f32>; |
| 5 | |
| 6 | fn rand() -> f32 { |
| 7 | rand_seed.x = fract(cos(dot(rand_seed, vec2<f32>(23.14077926, 232.61690225))) * 136.8168); |
| 8 | rand_seed.y = fract(cos(dot(rand_seed, vec2<f32>(54.47856553, 345.84153136))) * 534.7645); |
| 9 | return rand_seed.y; |
| 10 | } |
| 11 | |
| 12 | //////////////////////////////////////////////////////////////////////////////// |
| 13 | // Vertex shader |
| 14 | //////////////////////////////////////////////////////////////////////////////// |
| 15 | struct RenderParams { |
James Price | 3b671cb | 2022-03-28 14:31:22 +0000 | [diff] [blame] | 16 | modelViewProjectionMatrix : mat4x4<f32>, |
| 17 | right : vec3<f32>, |
| 18 | up : vec3<f32>, |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 19 | }; |
Ben Clayton | 01e4b6f | 2022-01-19 22:46:57 +0000 | [diff] [blame] | 20 | @binding(0) @group(0) var<uniform> render_params : RenderParams; |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 21 | |
| 22 | struct VertexInput { |
James Price | 3b671cb | 2022-03-28 14:31:22 +0000 | [diff] [blame] | 23 | @location(0) position : vec3<f32>, |
| 24 | @location(1) color : vec4<f32>, |
| 25 | @location(2) quad_pos : vec2<f32>, // -1..+1 |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 26 | }; |
| 27 | |
| 28 | struct VertexOutput { |
James Price | 3b671cb | 2022-03-28 14:31:22 +0000 | [diff] [blame] | 29 | @builtin(position) position : vec4<f32>, |
| 30 | @location(0) color : vec4<f32>, |
| 31 | @location(1) quad_pos : vec2<f32>, // -1..+1 |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 32 | }; |
| 33 | |
dan sinclair | b29892b | 2022-06-07 13:55:34 +0000 | [diff] [blame] | 34 | @vertex |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 35 | fn vs_main(in : VertexInput) -> VertexOutput { |
| 36 | var quad_pos = mat2x3<f32>(render_params.right, render_params.up) * in.quad_pos; |
| 37 | var position = in.position + quad_pos * 0.01; |
| 38 | var out : VertexOutput; |
| 39 | out.position = render_params.modelViewProjectionMatrix * vec4<f32>(position, 1.0); |
| 40 | out.color = in.color; |
| 41 | out.quad_pos = in.quad_pos; |
| 42 | return out; |
| 43 | } |
| 44 | |
| 45 | //////////////////////////////////////////////////////////////////////////////// |
| 46 | // Fragment shader |
| 47 | //////////////////////////////////////////////////////////////////////////////// |
dan sinclair | b29892b | 2022-06-07 13:55:34 +0000 | [diff] [blame] | 48 | @fragment |
Ben Clayton | 01e4b6f | 2022-01-19 22:46:57 +0000 | [diff] [blame] | 49 | fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> { |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 50 | var color = in.color; |
| 51 | // Apply a circular particle alpha mask |
| 52 | color.a = color.a * max(1.0 - length(in.quad_pos), 0.0); |
| 53 | return color; |
| 54 | } |
| 55 | |
| 56 | //////////////////////////////////////////////////////////////////////////////// |
| 57 | // Simulation Compute shader |
| 58 | //////////////////////////////////////////////////////////////////////////////// |
| 59 | struct SimulationParams { |
James Price | 3b671cb | 2022-03-28 14:31:22 +0000 | [diff] [blame] | 60 | deltaTime : f32, |
| 61 | seed : vec4<f32>, |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 62 | }; |
| 63 | |
| 64 | struct Particle { |
James Price | 3b671cb | 2022-03-28 14:31:22 +0000 | [diff] [blame] | 65 | position : vec3<f32>, |
| 66 | lifetime : f32, |
| 67 | color : vec4<f32>, |
| 68 | velocity : vec3<f32>, |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 69 | }; |
| 70 | |
| 71 | struct Particles { |
James Price | 3b671cb | 2022-03-28 14:31:22 +0000 | [diff] [blame] | 72 | particles : array<Particle>, |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 73 | }; |
| 74 | |
Ben Clayton | 01e4b6f | 2022-01-19 22:46:57 +0000 | [diff] [blame] | 75 | @binding(0) @group(0) var<uniform> sim_params : SimulationParams; |
| 76 | @binding(1) @group(0) var<storage, read_write> data : Particles; |
| 77 | @binding(2) @group(0) var texture : texture_2d<f32>; |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 78 | |
dan sinclair | b29892b | 2022-06-07 13:55:34 +0000 | [diff] [blame] | 79 | @compute @workgroup_size(64) |
Ben Clayton | 01e4b6f | 2022-01-19 22:46:57 +0000 | [diff] [blame] | 80 | fn simulate(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) { |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 81 | rand_seed = (sim_params.seed.xy + vec2<f32>(GlobalInvocationID.xy)) * sim_params.seed.zw; |
| 82 | |
| 83 | let idx = GlobalInvocationID.x; |
| 84 | var particle = data.particles[idx]; |
| 85 | |
| 86 | // Apply gravity |
| 87 | particle.velocity.z = particle.velocity.z - sim_params.deltaTime * 0.5; |
| 88 | |
| 89 | // Basic velocity integration |
| 90 | particle.position = particle.position + sim_params.deltaTime * particle.velocity; |
| 91 | |
| 92 | // Age each particle. Fade out before vanishing. |
| 93 | particle.lifetime = particle.lifetime - sim_params.deltaTime; |
James Price | 0384932 | 2022-04-14 19:49:10 +0000 | [diff] [blame] | 94 | particle.color.a = smoothstep(0.0, 0.5, particle.lifetime); |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 95 | |
| 96 | // If the lifetime has gone negative, then the particle is dead and should be |
| 97 | // respawned. |
| 98 | if (particle.lifetime < 0.0) { |
| 99 | // Use the probability map to find where the particle should be spawned. |
| 100 | // Starting with the 1x1 mip level. |
Ben Clayton | 13f0890 | 2022-10-26 18:36:44 +0000 | [diff] [blame] | 101 | var coord = vec2<u32>(0, 0); |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 102 | for (var level = textureNumLevels(texture) - 1; level > 0; level = level - 1) { |
| 103 | // Load the probability value from the mip-level |
| 104 | // Generate a random number and using the probabilty values, pick the |
| 105 | // next texel in the next largest mip level: |
| 106 | // |
| 107 | // 0.0 probabilites.r probabilites.g probabilites.b 1.0 |
| 108 | // | | | | | |
| 109 | // | TOP-LEFT | TOP-RIGHT | BOTTOM-LEFT | BOTTOM_RIGHT | |
| 110 | // |
| 111 | let probabilites = textureLoad(texture, coord, level); |
| 112 | let value = vec4<f32>(rand()); |
| 113 | let mask = (value >= vec4<f32>(0.0, probabilites.xyz)) & (value < probabilites); |
| 114 | coord = coord * 2; |
Ben Clayton | 13f0890 | 2022-10-26 18:36:44 +0000 | [diff] [blame] | 115 | coord.x += select(0u, 1u, any(mask.yw)); // x y |
| 116 | coord.y += select(0u, 1u, any(mask.zw)); // z w |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 117 | } |
| 118 | let uv = vec2<f32>(coord) / vec2<f32>(textureDimensions(texture)); |
| 119 | particle.position = vec3<f32>((uv - 0.5) * 3.0 * vec2<f32>(1.0, -1.0), 0.0); |
| 120 | particle.color = textureLoad(texture, coord, 0); |
| 121 | particle.velocity.x = (rand() - 0.5) * 0.1; |
| 122 | particle.velocity.y = (rand() - 0.5) * 0.1; |
| 123 | particle.velocity.z = rand() * 0.3; |
| 124 | particle.lifetime = 0.5 + rand() * 2.0; |
| 125 | } |
| 126 | |
| 127 | // Store the new particle value |
| 128 | data.particles[idx] = particle; |
| 129 | } |
| 130 | |
| 131 | struct UBO { |
James Price | 3b671cb | 2022-03-28 14:31:22 +0000 | [diff] [blame] | 132 | width : u32, |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 133 | }; |
| 134 | |
| 135 | struct Buffer { |
James Price | 3b671cb | 2022-03-28 14:31:22 +0000 | [diff] [blame] | 136 | weights : array<f32>, |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 137 | }; |
| 138 | |
Ben Clayton | 01e4b6f | 2022-01-19 22:46:57 +0000 | [diff] [blame] | 139 | @binding(3) @group(0) var<uniform> ubo : UBO; |
| 140 | @binding(4) @group(0) var<storage, read> buf_in : Buffer; |
| 141 | @binding(5) @group(0) var<storage, read_write> buf_out : Buffer; |
| 142 | @binding(6) @group(0) var tex_in : texture_2d<f32>; |
| 143 | @binding(7) @group(0) var tex_out : texture_storage_2d<rgba8unorm, write>; |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 144 | |
| 145 | //////////////////////////////////////////////////////////////////////////////// |
| 146 | // import_level |
| 147 | // |
| 148 | // Loads the alpha channel from a texel of the source image, and writes it to |
| 149 | // the buf_out.weights. |
| 150 | //////////////////////////////////////////////////////////////////////////////// |
dan sinclair | b29892b | 2022-06-07 13:55:34 +0000 | [diff] [blame] | 151 | @compute @workgroup_size(64) |
Ben Clayton | 01e4b6f | 2022-01-19 22:46:57 +0000 | [diff] [blame] | 152 | fn import_level(@builtin(global_invocation_id) coord : vec3<u32>) { |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 153 | _ = &buf_in; |
| 154 | let offset = coord.x + coord.y * ubo.width; |
| 155 | buf_out.weights[offset] = textureLoad(tex_in, vec2<i32>(coord.xy), 0).w; |
| 156 | } |
| 157 | |
| 158 | //////////////////////////////////////////////////////////////////////////////// |
| 159 | // export_level |
| 160 | // |
| 161 | // Loads 4 f32 weight values from buf_in.weights, and stores summed value into |
| 162 | // buf_out.weights, along with the calculated 'probabilty' vec4 values into the |
| 163 | // mip level of tex_out. See simulate() in particle.wgsl to understand the |
| 164 | // probability logic. |
| 165 | //////////////////////////////////////////////////////////////////////////////// |
dan sinclair | b29892b | 2022-06-07 13:55:34 +0000 | [diff] [blame] | 166 | @compute @workgroup_size(64) |
Ben Clayton | 01e4b6f | 2022-01-19 22:46:57 +0000 | [diff] [blame] | 167 | fn export_level(@builtin(global_invocation_id) coord : vec3<u32>) { |
Ben Clayton | be2362b | 2022-01-18 18:58:16 +0000 | [diff] [blame] | 168 | if (all(coord.xy < vec2<u32>(textureDimensions(tex_out)))) { |
| 169 | let dst_offset = coord.x + coord.y * ubo.width; |
| 170 | let src_offset = coord.x*2u + coord.y*2u * ubo.width; |
| 171 | |
| 172 | let a = buf_in.weights[src_offset + 0u]; |
| 173 | let b = buf_in.weights[src_offset + 1u]; |
| 174 | let c = buf_in.weights[src_offset + 0u + ubo.width]; |
| 175 | let d = buf_in.weights[src_offset + 1u + ubo.width]; |
| 176 | let sum = dot(vec4<f32>(a, b, c, d), vec4<f32>(1.0)); |
| 177 | |
| 178 | buf_out.weights[dst_offset] = sum / 4.0; |
| 179 | |
| 180 | let probabilities = vec4<f32>(a, a+b, a+b+c, sum) / max(sum, 0.0001); |
| 181 | textureStore(tex_out, vec2<i32>(coord.xy), probabilities); |
| 182 | } |
| 183 | } |