// Two vertex paths share one fragment shader: the plate mesh (static x, y // per node plus a per-frame record of height, slope and color) and the // marker geometry (explicit position, normal and color). The plate frame is // used directly as the world frame, with z up, so a cymbal lies flat. The 2D // map is the same mesh under an orthographic camera with the heights zeroed // (zScale = 0) and the lighting off (lit = 0). struct Uniforms { mvp: mat4x4f, eye: vec3f, zScale: f32, light: vec3f, lit: f32, }; @group(0) @binding(0) var U: Uniforms; struct VOut { @builtin(position) pos: vec4f, @location(0) world: vec3f, @location(1) normal: vec3f, @location(2) color: vec3f, }; struct MeshIn { @location(0) xy: vec2f, @location(1) z: f32, @location(2) slope: vec2f, @location(3) color: vec4f, }; @vertex fn vsMesh(in: MeshIn) -> VOut { let p = vec3f(in.xy, in.z * U.zScale); var o: VOut; o.pos = U.mvp * vec4f(p, 1.0); o.world = p; o.normal = vec3f(-in.slope * U.zScale, 1.0); o.color = in.color.rgb; return o; } struct MarkerIn { @location(0) pos: vec3f, @location(1) normal: vec3f, @location(2) color: vec4f, }; @vertex fn vsMarker(in: MarkerIn) -> VOut { var o: VOut; o.pos = U.mvp * vec4f(in.pos, 1.0); o.world = in.pos; o.normal = in.normal; o.color = in.color.rgb; return o; } // Lambert with a little Blinn-Phong specular; two-sided, so the underside of // the plate is lit when the camera goes below it. fn shade(color: vec3f, normal: vec3f, world: vec3f) -> vec3f { if (U.lit < 0.5) { return color; } let v = normalize(U.eye - world); var n = normalize(normal); if (dot(n, v) < 0.0) { n = -n; } let l = normalize(U.light); let diff = max(dot(n, l), 0.0); let h = normalize(l + v); let spec = pow(max(dot(n, h), 0.0), 40.0); return color * (0.28 + 0.72 * diff) + vec3f(0.14) * spec; } @fragment fn fsMain(in: VOut) -> @location(0) vec4f { return vec4f(shade(in.color, in.normal, in.world), 1.0); }