/ proof-of-concept / steelsim31
Sign in
proof-of-concept / steelsim31
steelsim31 / src / render / shaders.wgsl
142 lines · 5.2 KBBlameHistoryRaw
1struct RU {
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 holeX: f32,
12 holeY: f32,
13 pushX: f32,
14 pushY: f32,
15 markerR: f32,
16 lightX: f32,
17 lightY: f32,
18 lightZ: f32,
19 showShape: u32, // add the stress-free initial shape to the displayed field (both views)
20 shape: u32, // 0: rectangle, 1: ellipse inscribed in the bounding box
21 pad1: u32,
22 pad2: u32,
23};
25@group(0) @binding(0) var<uniform> R: RU;
26@group(0) @binding(1) var<storage, read> w: array<f32>;
27@group(0) @binding(2) var<storage, read> w0: array<f32>;
28@group(0) @binding(3) var<storage, read> nw: array<f32>; // node weights, 0 outside the sheet
30fn colormap(t: f32) -> vec3f {
31 let s = clamp(t, -1.0, 1.0);
32 let mid = vec3f(0.93, 0.93, 0.93);
33 let blue = vec3f(0.10, 0.35, 0.85);
34 let red = vec3f(0.85, 0.20, 0.12);
35 if (s < 0.0) { return mix(mid, blue, -s); }
36 return mix(mid, red, s);
39// Displayed field: the deviation w, plus the initial shape when requested (the total shape).
40fn wAt(i: i32, j: i32) -> f32 {
41 let ii = clamp(i, 0, i32(R.nx) - 1);
42 let jj = clamp(j, 0, i32(R.ny) - 1);
43 var z = w[u32(jj) * R.nx + u32(ii)];
44 if (R.showShape != 0u) { z += w0[u32(jj) * R.nx + u32(ii)]; }
45 return z;
47fn nwAt(i: i32, j: i32) -> f32 {
48 if (i < 0 || i >= i32(R.nx) || j < 0 || j >= i32(R.ny)) { return 0.0; }
49 return nw[u32(j) * R.nx + u32(i)];
51// Height used by the 3D view: the same field as the color.
52fn zAt(i: i32, j: i32) -> f32 { return wAt(i, j); }
53// Slope along one axis, one-sided where a neighbor lies outside the sheet.
54fn slope(i: i32, j: i32, di: i32, dj: i32, h: f32) -> f32 {
55 let hasP = nwAt(i + di, j + dj) > 0.0;
56 let hasM = nwAt(i - di, j - dj) > 0.0;
57 if (hasP && hasM) { return (zAt(i + di, j + dj) - zAt(i - di, j - dj)) / (2.0 * h); }
58 if (hasP) { return (zAt(i + di, j + dj) - zAt(i, j)) / h; }
59 if (hasM) { return (zAt(i, j) - zAt(i - di, j - dj)) / h; }
60 return 0.0;
63// ---------- 2D view: full-screen triangle, bilinear lookup in the fragment shader ----------
65struct V2 { @builtin(position) pos: vec4f, @location(0) uv: vec2f };
67@vertex
68fn vs2d(@builtin(vertex_index) vi: u32) -> V2 {
69 var p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
70 var o: V2;
71 o.pos = vec4f(p[vi], 0.0, 1.0);
72 o.uv = p[vi];
73 return o;
76@fragment
77fn fs2d(in: V2) -> @location(0) vec4f {
78 let Lx = f32(R.nx - 1u) * R.hx;
79 let Ly = f32(R.ny - 1u) * R.hy;
80 // Fit the plate into the canvas with a margin, preserving its aspect ratio.
81 let margin = 0.92;
82 var sx = margin; var sy = margin;
83 let plateAspect = Lx / Ly;
84 if (plateAspect > R.aspect) { sy = margin * R.aspect / plateAspect; } else { sx = margin * plateAspect / R.aspect; }
85 let px = in.uv.x / sx * 0.5 * Lx;
86 let py = in.uv.y / sy * 0.5 * Ly;
87 let bg = vec4f(0.11, 0.12, 0.14, 1.0);
88 if (abs(px) > 0.5 * Lx || abs(py) > 0.5 * Ly) { return bg; }
89 if (R.shape == 1u) {
90 let ex = px / (0.5 * Lx); let ey = py / (0.5 * Ly);
91 if (ex * ex + ey * ey > 1.0) { return bg; }
92 }
93 let fi = (px + 0.5 * Lx) / R.hx;
94 let fj = (py + 0.5 * Ly) / R.hy;
95 let i0 = i32(floor(fi)); let j0 = i32(floor(fj));
96 let a = fi - f32(i0); let b = fj - f32(j0);
97 // Bilinear interpolation over the nodes of the sheet only (outside nodes carry no value).
98 let c = vec4f((1.0 - a) * (1.0 - b), a * (1.0 - b), (1.0 - a) * b, a * b);
99 let m = vec4f(nwAt(i0, j0), nwAt(i0 + 1, j0), nwAt(i0, j0 + 1), nwAt(i0 + 1, j0 + 1));
100 let wgt = c * step(vec4f(1e-6), m);
101 let den = wgt.x + wgt.y + wgt.z + wgt.w;
102 if (den <= 0.0) { return bg; }
103 let val = dot(wgt, vec4f(wAt(i0, j0), wAt(i0 + 1, j0), wAt(i0, j0 + 1), wAt(i0 + 1, j0 + 1))) / den;
104 var col = colormap(val / R.colorRange);
105 // Markers: hole (ring) and pushed patch (dashed-looking ring).
106 let dh = distance(vec2f(px, py), vec2f(R.holeX, R.holeY));
107 if (abs(dh - R.markerR) < 0.35 * R.markerR) { col = vec3f(0.1, 0.1, 0.1); }
108 if (dh < 0.4 * R.markerR) { col = vec3f(0.11, 0.12, 0.14); }
109 let dp = distance(vec2f(px, py), vec2f(R.pushX, R.pushY));
110 if (abs(dp - R.markerR * 1.6) < 0.25 * R.markerR) { col = vec3f(0.95, 0.75, 0.1); }
111 return vec4f(col, 1.0);
114// ---------- 3D view: height field ----------
116struct V3 { @builtin(position) pos: vec4f, @location(0) val: f32, @location(1) normal: vec3f };
118@vertex
119fn vs3d(@builtin(vertex_index) vi: u32) -> V3 {
120 let i = i32(vi % R.nx); let j = i32(vi / R.nx);
121 let x = (f32(i) - 0.5 * f32(R.nx - 1u)) * R.hx;
122 let y = (f32(j) - 0.5 * f32(R.ny - 1u)) * R.hy;
123 let val = wAt(i, j);
124 let z = zAt(i, j) * R.wScale;
125 let dzdx = slope(i, j, 1, 0, R.hx) * R.wScale;
126 let dzdy = slope(i, j, 0, 1, R.hy) * R.wScale;
127 var o: V3;
128 o.pos = R.mvp * vec4f(x, y, z, 1.0);
129 o.val = val;
130 o.normal = normalize(vec3f(-dzdx, -dzdy, 1.0));
131 return o;
134@fragment
135fn fs3d(in: V3) -> @location(0) vec4f {
136 let l = normalize(vec3f(R.lightX, R.lightY, R.lightZ));
137 let n = normalize(in.normal);
138 let diff = abs(dot(n, l));
139 let base = colormap(in.val / R.colorRange);
140 let col = base * (0.35 + 0.65 * diff) + vec3f(0.15) * pow(diff, 24.0);
141 return vec4f(col, 1.0);
↑↓ move↵openescclose