/ proof-of-concept / steelsim41
Sign in
proof-of-concept / steelsim41
steelsim41 / src / render / shaders.wgsl
168 lines · 6.1 KBCodeBlameHistory
2 mvp: mat4x4f,
3 nx: u32,
4 ny: u32,
5 hx: f32,
6 hy: f32,
7 wScale: f32, // vertical exaggeration in the 3D view
8 colorRange: f32, // |w| mapped to full color [m]
9 aspect: f32, // canvas width / height
10 mode: u32, // 0: 2D, 1: 3D
11 strikeX: f32,
12 strikeY: f32,
13 strikeR: f32,
14 mountX: f32,
15 mountY: f32,
16 mountR: f32,
17 lightX: f32,
18 lightY: f32,
19 lightZ: f32,
20 showShape: u32, // add the stress-free initial shape to the displayed field (both views)
21 colorMode: u32, // 0: color by the displayed field, 1: color by the thickness
22 mountOn: u32, // draw the mount marker
23 hMin: f32, // thickness range for colorMode 1 [m]
24 hMax: f32,
25 pad1: u32,
26 pad2: u32,
27};
29@group(0) @binding(0) var<uniform> R: RU;
30@group(0) @binding(1) var<storage, read> w: array<f32>;
31@group(0) @binding(2) var<storage, read> w0: array<f32>;
32@group(0) @binding(3) var<storage, read> nw: array<f32>; // node weights, 0 outside the plate
33@group(0) @binding(4) var<storage, read> hn: array<f32>; // thickness per node
34@group(0) @binding(5) var<storage, read> cm: array<f32>; // cell membership, (nx - 1) * (ny - 1)
36const BG = vec3f(0.11, 0.12, 0.14);
38// Diverging map for the displacement: blue, light gray, red.
39fn colormap(t: f32) -> vec3f {
40 let s = clamp(t, -1.0, 1.0);
41 let mid = vec3f(0.93, 0.93, 0.93);
42 let blue = vec3f(0.10, 0.35, 0.85);
43 let red = vec3f(0.85, 0.20, 0.12);
44 if (s < 0.0) { return mix(mid, blue, -s); }
45 return mix(mid, red, s);
47// Sequential map for the thickness: dark teal through green to pale yellow.
48fn thickmap(t: f32) -> vec3f {
49 let s = clamp(t, 0.0, 1.0);
50 let a = vec3f(0.13, 0.22, 0.40);
51 let b = vec3f(0.20, 0.60, 0.55);
52 let c = vec3f(0.98, 0.93, 0.55);
53 if (s < 0.5) { return mix(a, b, 2.0 * s); }
54 return mix(b, c, 2.0 * s - 1.0);
57fn idx(i: i32, j: i32) -> u32 {
58 let ii = clamp(i, 0, i32(R.nx) - 1);
59 let jj = clamp(j, 0, i32(R.ny) - 1);
60 return u32(jj) * R.nx + u32(ii);
62// Displayed field: the deviation w, plus the initial shape when requested (the total shape).
63fn wAt(i: i32, j: i32) -> f32 {
64 let k = idx(i, j);
65 var z = w[k];
66 if (R.showShape != 0u) { z += w0[k]; }
67 return z;
69fn hAt(i: i32, j: i32) -> f32 { return hn[idx(i, j)]; }
70fn nwAt(i: i32, j: i32) -> f32 {
71 if (i < 0 || i >= i32(R.nx) || j < 0 || j >= i32(R.ny)) { return 0.0; }
72 return nw[u32(j) * R.nx + u32(i)];
74fn cellAt(ci: i32, cj: i32) -> f32 {
75 if (ci < 0 || ci >= i32(R.nx) - 1 || cj < 0 || cj >= i32(R.ny) - 1) { return 0.0; }
76 return cm[u32(cj) * (R.nx - 1u) + u32(ci)];
78// Color of a node from the displayed field value and the thickness.
79fn colorOf(val: f32, h: f32) -> vec3f {
80 if (R.colorMode == 1u) { return thickmap((h - R.hMin) / max(R.hMax - R.hMin, 1e-9)); }
81 return colormap(val / R.colorRange);
83// Height used by the 3D view: the same field as the color.
84fn zAt(i: i32, j: i32) -> f32 { return wAt(i, j); }
85// Slope along one axis, one-sided where a neighbor lies outside the plate.
86fn slope(i: i32, j: i32, di: i32, dj: i32, h: f32) -> f32 {
87 let hasP = nwAt(i + di, j + dj) > 0.0;
88 let hasM = nwAt(i - di, j - dj) > 0.0;
89 if (hasP && hasM) { return (zAt(i + di, j + dj) - zAt(i - di, j - dj)) / (2.0 * h); }
90 if (hasP) { return (zAt(i + di, j + dj) - zAt(i, j)) / h; }
91 if (hasM) { return (zAt(i, j) - zAt(i - di, j - dj)) / h; }
92 return 0.0;
95// ---------- 2D view: full-screen triangle, bilinear lookup in the fragment shader ----------
97struct V2 { @builtin(position) pos: vec4f, @location(0) uv: vec2f };
99@vertex
100fn vs2d(@builtin(vertex_index) vi: u32) -> V2 {
101 var p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
102 var o: V2;
103 o.pos = vec4f(p[vi], 0.0, 1.0);
104 o.uv = p[vi];
105 return o;
108@fragment
109fn fs2d(in: V2) -> @location(0) vec4f {
110 let Lx = f32(R.nx - 1u) * R.hx;
111 let Ly = f32(R.ny - 1u) * R.hy;
112 // Fit the plate into the canvas with a margin, preserving its aspect ratio.
113 let margin = 0.92;
114 var sx = margin; var sy = margin;
115 let plateAspect = Lx / Ly;
116 if (plateAspect > R.aspect) { sy = margin * R.aspect / plateAspect; } else { sx = margin * plateAspect / R.aspect; }
117 let px = in.uv.x / sx * 0.5 * Lx;
118 let py = in.uv.y / sy * 0.5 * Ly;
119 let bg = vec4f(BG, 1.0);
120 if (abs(px) > 0.5 * Lx || abs(py) > 0.5 * Ly) { return bg; }
121 let fi = (px + 0.5 * Lx) / R.hx;
122 let fj = (py + 0.5 * Ly) / R.hy;
123 let i0 = min(i32(floor(fi)), i32(R.nx) - 2); let j0 = min(i32(floor(fj)), i32(R.ny) - 2);
124 // Only the cells of the plate are drawn (staircase edge, hole included).
125 if (cellAt(i0, j0) == 0.0) { return bg; }
126 let a = fi - f32(i0); let b = fj - f32(j0);
127 let c = vec4f((1.0 - a) * (1.0 - b), a * (1.0 - b), (1.0 - a) * b, a * b);
128 let val = dot(c, vec4f(wAt(i0, j0), wAt(i0 + 1, j0), wAt(i0, j0 + 1), wAt(i0 + 1, j0 + 1)));
129 let h = dot(c, vec4f(hAt(i0, j0), hAt(i0 + 1, j0), hAt(i0, j0 + 1), hAt(i0 + 1, j0 + 1)));
130 var col = colorOf(val, h);
131 // Markers: the mount (dark ring) and the strike point (yellow ring).
132 if (R.mountOn != 0u) {
133 let dm = distance(vec2f(px, py), vec2f(R.mountX, R.mountY));
134 if (abs(dm - R.mountR) < 0.12 * R.mountR + 0.0015) { col = vec3f(0.1, 0.1, 0.1); }
135 }
136 let ds = distance(vec2f(px, py), vec2f(R.strikeX, R.strikeY));
137 if (abs(ds - R.strikeR * 1.6) < 0.25 * R.strikeR) { col = vec3f(0.95, 0.75, 0.1); }
138 return vec4f(col, 1.0);
141// ---------- 3D view: height field ----------
143struct V3 { @builtin(position) pos: vec4f, @location(0) color: vec3f, @location(1) normal: vec3f };
145@vertex
146fn vs3d(@builtin(vertex_index) vi: u32) -> V3 {
147 let i = i32(vi % R.nx); let j = i32(vi / R.nx);
148 let x = (f32(i) - 0.5 * f32(R.nx - 1u)) * R.hx;
149 let y = (f32(j) - 0.5 * f32(R.ny - 1u)) * R.hy;
150 let val = wAt(i, j);
151 let z = zAt(i, j) * R.wScale;
152 let dzdx = slope(i, j, 1, 0, R.hx) * R.wScale;
153 let dzdy = slope(i, j, 0, 1, R.hy) * R.wScale;
154 var o: V3;
155 o.pos = R.mvp * vec4f(x, y, z, 1.0);
156 o.color = colorOf(val, hAt(i, j));
157 o.normal = normalize(vec3f(-dzdx, -dzdy, 1.0));
158 return o;
161@fragment
162fn fs3d(in: V3) -> @location(0) vec4f {
163 let l = normalize(vec3f(R.lightX, R.lightY, R.lightZ));
164 let n = normalize(in.normal);
165 let diff = abs(dot(n, l));
166 let col = in.color * (0.35 + 0.65 * diff) + vec3f(0.15) * pow(diff, 24.0);
167 return vec4f(col, 1.0);
↑↓ move↵openescclose