struct MotionBlurParams { inv_view_proj: mat4x4, prev_view_proj: mat4x4, num_samples: u32, strength: f32, _pad: vec2, }; @group(0) @binding(0) var color_tex: texture_2d; @group(0) @binding(1) var depth_tex: texture_depth_2d; @group(0) @binding(2) var output_tex: texture_storage_2d; @group(0) @binding(3) var params: MotionBlurParams; @compute @workgroup_size(16, 16) fn main(@builtin(global_invocation_id) gid: vec3) { let dims = textureDimensions(color_tex); if (gid.x >= dims.x || gid.y >= dims.y) { return; } let pos = vec2(gid.xy); let uv = (vec2(gid.xy) + 0.5) / vec2(dims); // Reconstruct world position from depth let depth = textureLoad(depth_tex, pos, 0); let ndc = vec4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0); let world = params.inv_view_proj * ndc; let world_pos = world.xyz / world.w; // Project to previous frame let prev_clip = params.prev_view_proj * vec4(world_pos, 1.0); let prev_ndc = prev_clip.xyz / prev_clip.w; let prev_uv = vec2(prev_ndc.x * 0.5 + 0.5, 1.0 - (prev_ndc.y * 0.5 + 0.5)); // Velocity = current_uv - prev_uv let velocity = (uv - prev_uv) * params.strength; // Sample along velocity direction var color = vec4(0.0); let n = params.num_samples; for (var i = 0u; i < n; i++) { let t = f32(i) / f32(n - 1u) - 0.5; // -0.5 to 0.5 let sample_uv = uv + velocity * t; let sample_pos = vec2(sample_uv * vec2(dims)); let clamped = clamp(sample_pos, vec2(0), vec2(dims) - 1); color += textureLoad(color_tex, clamped, 0); } color /= f32(n); textureStore(output_tex, pos, color); }