facett-core/src/render/gpu/scene3d.wgsl
// Scene3D — flat quads, screen-width line ribbons and SDF text glyphs on world
// planes, under one view-projection, depth-tested (render::gpu::scene3d).
struct Uniforms {
view_proj: mat4x4<f32>,
// xy = target size in physical px
viewport: vec4<f32>,
};
@group(0) @binding(0) var<uniform> u: Uniforms;
@group(0) @binding(1) var atlas: texture_2d<f32>;
@group(0) @binding(2) var atlas_smp: sampler;
// ---------------------------------------------------------------- solid (quads)
struct SolidOut {
@builtin(position) clip: vec4<f32>,
@location(0) color: vec4<f32>,
};
@vertex
fn solid_vs(@location(0) pos: vec3<f32>, @location(1) color: vec4<f32>) -> SolidOut {
var o: SolidOut;
o.clip = u.view_proj * vec4<f32>(pos, 1.0);
o.color = color;
return o;
}
@fragment
fn solid_fs(i: SolidOut) -> @location(0) vec4<f32> {
return i.color;
}
// ---------------------------------------------------------------- lines (ribbons)
// One ribbon corner: both ends of the segment, which end this vertex sits at
// (`end` 0 or 1), which side (`side` -1 or 1), the width in px.
@vertex
fn line_vs(
@location(0) a: vec3<f32>,
@location(1) b: vec3<f32>,
@location(2) end_side_width: vec3<f32>,
@location(3) color: vec4<f32>,
) -> SolidOut {
let ca = u.view_proj * vec4<f32>(a, 1.0);
let cb = u.view_proj * vec4<f32>(b, 1.0);
let wa = max(ca.w, 1e-5);
let wb = max(cb.w, 1e-5);
let half_vp = u.viewport.xy * 0.5;
let sa = ca.xy / wa * half_vp;
let sb = cb.xy / wb * half_vp;
var dir = sb - sa;
let len = length(dir);
if (len < 1e-6) {
dir = vec2<f32>(1.0, 0.0);
} else {
dir = dir / len;
}
let normal = vec2<f32>(-dir.y, dir.x);
var here = ca;
if (end_side_width.x > 0.5) {
here = cb;
}
let off_px = normal * end_side_width.y * end_side_width.z * 0.5;
let off_ndc = off_px / half_vp;
var o: SolidOut;
o.clip = vec4<f32>(here.xy + off_ndc * here.w, here.z, here.w);
o.color = color;
return o;
}
// ---------------------------------------------------------------- text (SDF glyphs)
struct TextOut {
@builtin(position) clip: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) color: vec4<f32>,
};
@vertex
fn text_vs(@location(0) pos: vec3<f32>, @location(1) uv: vec2<f32>, @location(2) color: vec4<f32>) -> TextOut {
var o: TextOut;
o.clip = u.view_proj * vec4<f32>(pos, 1.0);
o.uv = uv;
o.color = color;
return o;
}
@fragment
fn text_fs(i: TextOut) -> @location(0) vec4<f32> {
let d = textureSample(atlas, atlas_smp, i.uv).r;
// one screen pixel of the distance field, so the edge is ~1 px wide at any zoom
let w = max(fwidth(d), 1e-4);
let a = clamp((d - 0.5) / w + 0.5, 0.0, 1.0) * i.color.a;
if (a < 0.01) {
discard;
}
return vec4<f32>(i.color.rgb, a);
}