Skip to main content

runmat_plot/gpu/shaders/vertex/
point_billboard.rs

1pub const SHADER: &str = r#"// Camera-space marker shader: renders each marker as a billboard quad.
2
3struct Uniforms {
4    view_proj: mat4x4<f32>,
5    model: mat4x4<f32>,
6    normal_matrix: mat3x4<f32>,
7}
8
9struct PointStyleUniforms {
10    face_color: vec4<f32>,
11    edge_color: vec4<f32>,
12    edge_thickness_px: f32,
13    marker_shape: u32,
14    _pad: vec2<f32>,
15}
16
17struct MarkerScreenUniforms {
18    viewport_px: vec2<f32>,
19    _pad: vec2<f32>,
20}
21
22@group(0) @binding(0)
23var<uniform> uniforms: Uniforms;
24@group(1) @binding(0)
25var<uniform> styleU: PointStyleUniforms;
26@group(2) @binding(0)
27var<uniform> screenU: MarkerScreenUniforms;
28
29struct VertexInput {
30    @location(0) position: vec3<f32>,
31    @location(1) color: vec4<f32>,
32    @location(2) normal: vec3<f32>,
33    @location(3) tex_coords: vec2<f32>,
34}
35
36struct VSOut {
37    @builtin(position) clip_position: vec4<f32>,
38    @location(0) color: vec4<f32>,
39    @location(1) uv: vec2<f32>,
40    @location(2) size_px: f32,
41}
42
43@vertex
44fn vs_main(input: VertexInput) -> VSOut {
45    var out: VSOut;
46    let world_position = uniforms.model * vec4<f32>(input.position, 1.0);
47    var clip = uniforms.view_proj * world_position;
48
49    let size_px = max(2.0, input.normal.z);
50    let vp = max(screenU.viewport_px, vec2<f32>(1.0, 1.0));
51    let half_px = 0.5 * size_px;
52    let ndc_per_px = vec2<f32>(2.0 / vp.x, 2.0 / vp.y);
53    let ndc_offset = input.tex_coords * half_px * ndc_per_px;
54    let clip_xy = clip.xy + ndc_offset * clip.w;
55    clip = vec4<f32>(clip_xy, clip.z, clip.w);
56
57    out.clip_position = clip;
58    out.color = input.color;
59    out.uv = (input.tex_coords + vec2<f32>(1.0, 1.0)) * 0.5;
60    out.size_px = size_px;
61    return out;
62}
63
64fn inside_triangle(pt: vec2<f32>) -> bool {
65    let a = vec2<f32>(-1.0, -1.0);
66    let b = vec2<f32>(1.0, -1.0);
67    let c = vec2<f32>(0.0, 1.0);
68    let v0 = c - a;
69    let v1 = b - a;
70    let v2 = pt - a;
71    let d00 = dot(v0, v0);
72    let d01 = dot(v0, v1);
73    let d11 = dot(v1, v1);
74    let d20 = dot(v2, v0);
75    let d21 = dot(v2, v1);
76    let denom = d00 * d11 - d01 * d01;
77    if (denom == 0.0) { return false; }
78    let v = (d11 * d20 - d01 * d21) / denom;
79    let w = (d00 * d21 - d01 * d20) / denom;
80    let u = 1.0 - v - w;
81    return (u >= 0.0) && (v >= 0.0) && (w >= 0.0);
82}
83
84fn inside_hexagon(pt: vec2<f32>) -> bool {
85    let a = vec2<f32>(1.0, 0.0);
86    let b = vec2<f32>(0.5, 0.8660254);
87    let c = vec2<f32>(-0.5, 0.8660254);
88    let da = abs(dot(pt, a));
89    let db = abs(dot(pt, b));
90    let dc = abs(dot(pt, c));
91    return max(da, max(db, dc)) <= 1.0;
92}
93
94@fragment
95fn fs_main(input: VSOut) -> @location(0) vec4<f32> {
96    let p = input.uv * 2.0 - vec2<f32>(1.0, 1.0);
97    let r = length(p);
98
99    var inside: bool;
100    switch (styleU.marker_shape) {
101        case 0u: { inside = r <= 1.0; }
102        case 1u: { inside = true; }
103        case 2u: { inside = inside_triangle(p); }
104        case 3u: { inside = (abs(p.x) + abs(p.y)) <= 1.0; }
105        case 4u: {
106            let w = 0.35;
107            inside = (abs(p.x) <= w) || (abs(p.y) <= w);
108        }
109        case 5u: {
110            let w = 0.30;
111            inside = min(abs(p.x - p.y), abs(p.x + p.y)) <= w;
112        }
113        case 6u: {
114            let w1 = 0.30;
115            let w2 = 0.25;
116            let plus = (abs(p.x) <= w1) || (abs(p.y) <= w1);
117            let cross = min(abs(p.x - p.y), abs(p.x + p.y)) <= w2;
118            inside = plus || cross;
119        }
120        case 7u: {
121            inside = inside_hexagon(p);
122        }
123        default: {
124            inside = true;
125        }
126    }
127    if (!inside) {
128        discard;
129    }
130
131    let edge_norm = clamp(2.0 * styleU.edge_thickness_px / max(input.size_px, 1.0), 0.0, 1.0);
132    let face_rgb = mix(input.color.rgb, styleU.face_color.rgb, styleU.face_color.a);
133    let edge_mix = clamp(styleU.edge_color.a, 0.0, 1.0);
134    let edge_rgb = mix(input.color.rgb, styleU.edge_color.rgb, edge_mix);
135
136    if (styleU.marker_shape == 0u) {
137        if (edge_norm > 0.0 && r > (1.0 - edge_norm)) {
138            return vec4<f32>(edge_rgb, input.color.a);
139        }
140    }
141
142    return vec4<f32>(face_rgb, input.color.a);
143}
144"#;