// Heatmap accumulation shader.
//
// Pass 1: Accumulates Gaussian weight contributions into an R16Float
// framebuffer using additive blending.
// The colour mapping pass uses the grid_scalar pipeline with a ramp texture.
struct Uniforms {
view_proj: mat4x4<f32>,
fog_color: vec4<f32>,
eye_pos: vec4<f32>,
fog_params: vec4<f32>,
};
struct HeatmapVertexInput {
@location(0) position: vec3<f32>,
@location(1) quad_offset: vec2<f32>,
@location(2) params: vec4<f32>, // (weight, radius, intensity, 0)
};
struct HeatmapVertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) quad_uv: vec2<f32>,
@location(1) params: vec4<f32>,
};
@group(0) @binding(0)
var<uniform> u: Uniforms;
@vertex
fn vs_main(in: HeatmapVertexInput) -> HeatmapVertexOutput {
let radius = in.params.y;
// Expand quad around centre.
let expanded = in.position + vec3<f32>(
in.quad_offset.x * radius,
in.quad_offset.y * radius,
0.0,
);
var out: HeatmapVertexOutput;
out.clip_position = u.view_proj * vec4<f32>(expanded, 1.0);
out.quad_uv = in.quad_offset;
out.params = in.params;
return out;
}
@fragment
fn fs_main(in: HeatmapVertexOutput) -> @location(0) vec4<f32> {
let weight = in.params.x;
let intensity = in.params.z;
// Gaussian kernel: exp(-d^2 / (2 * sigma^2)) where sigma = 1/3
// so that the kernel reaches ~zero at the quad edge (|uv| = 1).
let dist_sq = dot(in.quad_uv, in.quad_uv);
if dist_sq > 1.0 {
discard;
}
let sigma = 1.0 / 3.0;
let gauss = exp(-dist_sq / (2.0 * sigma * sigma));
let value = gauss * weight * intensity;
// Output accumulated weight into a single-channel framebuffer.
return vec4<f32>(value, 0.0, 0.0, 1.0);
}