@weft-js/plugin-surface-keel 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +312 -3
- package/package.json +12 -3
- package/src/index.d.ts +5 -1
- package/src/index.mjs +20 -5
- package/src/runtime.mjs +6 -2
- package/vendor/README.md +7 -4
- package/vendor/keel-render/core-primitives.mjs +478 -0
- package/vendor/keel-render/effects-world.mjs +439 -0
- package/vendor/keel-render/indexed-render.mjs +516 -0
- package/vendor/keel-render/pixel-raster.mjs +512 -0
- package/vendor/keel-render/render-passes.mjs +530 -0
- package/vendor/keel-render/renderer-api.mjs +121 -0
- package/vendor/keel-render.mjs +1 -2268
- package/vendor/keel-theme/codec-runtime.mjs +549 -0
- package/vendor/keel-theme/collection-codecs.mjs +360 -0
- package/vendor/keel-theme/color-font.mjs +471 -0
- package/vendor/keel-theme/glyph-theme.mjs +529 -0
- package/vendor/keel-theme/icon-schema.mjs +540 -0
- package/vendor/keel-theme/scalar-codecs.mjs +535 -0
- package/vendor/keel-theme/schema-codec.mjs +515 -0
- package/vendor/keel-theme/schema-theme.mjs +533 -0
- package/vendor/keel-theme.mjs +1 -3571
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import './effects-world.mjs';
|
|
2
|
+
import {
|
|
3
|
+
FAR,
|
|
4
|
+
PALETTE_WIDTH,
|
|
5
|
+
SCREEN_TILE,
|
|
6
|
+
WATER_MAT,
|
|
7
|
+
WORLD_FS,
|
|
8
|
+
} from './effects-world.mjs';
|
|
9
|
+
|
|
10
|
+
var PIXEL_FS = `#version 300 es
|
|
11
|
+
precision highp float;
|
|
12
|
+
precision highp sampler2D;
|
|
13
|
+
uniform sampler2D uData;
|
|
14
|
+
uniform sampler2D uData2;
|
|
15
|
+
uniform sampler2D uDepth;
|
|
16
|
+
uniform sampler2D uPalette; // colours, ${PALETTE_WIDTH} to a row
|
|
17
|
+
uniform sampler2D uRamps; // row 0 per ramp: base, length, cycle from (entry), cycle speed (entries/s); row 1: graded ramp (-1 itself)
|
|
18
|
+
uniform sampler2D uScreenTex; // a core screen's thresholds, ${SCREEN_TILE} px square
|
|
19
|
+
uniform int uScreen; // 0 none, 2 / 4 / 8: Bayer size, -1: the screen texture
|
|
20
|
+
uniform float uDither; // how far the screen reaches between two entries (0..1)
|
|
21
|
+
uniform float uTime;
|
|
22
|
+
// Outline: on, steps darker, the depth gap behind that makes an edge (metres / FAR: 0.56 m the classic, more for outer-only)
|
|
23
|
+
uniform vec3 uOutline;
|
|
24
|
+
uniform int uOutlineInk; // palette index to ink with (< 0: darken the pixel's own ramp)
|
|
25
|
+
uniform vec4 uFog; // on, near, far, amount
|
|
26
|
+
uniform vec2 uFogLook; // ramp, lightness it tends to
|
|
27
|
+
uniform vec4 uGlow; // on, halo radius (px), halo steps, self steps
|
|
28
|
+
uniform vec2 uGlowK; // threshold, tint (1: the halo wears the glowing thing's ramp)
|
|
29
|
+
uniform vec4 uVig; // on, inner, outer, steps
|
|
30
|
+
uniform vec4 uScan; // on, period (px), dark rows per period, steps
|
|
31
|
+
uniform vec4 uCrt; // on, curvature, border palette index, 0
|
|
32
|
+
uniform vec4 uRim; // on, width (px), steps, 0
|
|
33
|
+
uniform vec2 uRimDir; // screen direction the light comes from
|
|
34
|
+
uniform vec4 uFlash; // amount, ramp (-1: its own top), id from, id to
|
|
35
|
+
uniform ivec4 uFlashMats[2];// up to 8 materials (-1 none)
|
|
36
|
+
uniform vec2 uGrade; // on, shift (entries)
|
|
37
|
+
uniform int uCycle; // on
|
|
38
|
+
out vec4 outColor;
|
|
39
|
+
|
|
40
|
+
float bayer(ivec2 p, int n) {
|
|
41
|
+
if (n == 2) { int m[4] = int[4](0, 2, 3, 1); return (float(m[(p.y & 1) * 2 + (p.x & 1)]) + 0.5) / 4.0; }
|
|
42
|
+
if (n == 4) { int m[16] = int[16](0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5); return (float(m[(p.y & 3) * 4 + (p.x & 3)]) + 0.5) / 16.0; }
|
|
43
|
+
// 8x8 from 4x4
|
|
44
|
+
ivec2 q = p & 7;
|
|
45
|
+
int m4[16] = int[16](0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5);
|
|
46
|
+
int a = m4[(q.y & 3) * 4 + (q.x & 3)];
|
|
47
|
+
int b = m4[(q.y >> 2) * 4 + (q.x >> 2)];
|
|
48
|
+
return (float(a * 4 + (b & 3)) + 0.5) / 64.0;
|
|
49
|
+
}
|
|
50
|
+
float screenAt(ivec2 p) {
|
|
51
|
+
if (uScreen > 0) return bayer(p, uScreen);
|
|
52
|
+
if (uScreen < 0) return texelFetch(uScreenTex, p % ${SCREEN_TILE}, 0).r;
|
|
53
|
+
return 0.5;
|
|
54
|
+
}
|
|
55
|
+
vec4 pal(int i) { return texelFetch(uPalette, ivec2(i % ${PALETTE_WIDTH}, i / ${PALETTE_WIDTH}), 0); }
|
|
56
|
+
vec4 rampOf(int r) { return texelFetch(uRamps, ivec2(r, 0), 0); }
|
|
57
|
+
bool flashed(int mat, int id) {
|
|
58
|
+
if (uFlash.x <= 0.0) return false;
|
|
59
|
+
if (float(id) >= uFlash.z && float(id) <= uFlash.w) return true;
|
|
60
|
+
for (int k = 0; k < 2; k++) { ivec4 m = uFlashMats[k]; if (m.x == mat || m.y == mat || m.z == mat || m.w == mat) return true; }
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void main() {
|
|
65
|
+
ivec2 size = textureSize(uData, 0);
|
|
66
|
+
ivec2 px = ivec2(gl_FragCoord.xy);
|
|
67
|
+
vec2 res = vec2(size);
|
|
68
|
+
// CRT: bend the picture as a tube does -- whole pixels moved, none invented.
|
|
69
|
+
if (uCrt.x > 0.5) {
|
|
70
|
+
vec2 uv = (vec2(px) + 0.5) / res * 2.0 - 1.0;
|
|
71
|
+
uv *= 1.0 + uCrt.y * dot(uv, uv);
|
|
72
|
+
if (abs(uv.x) > 1.0 || abs(uv.y) > 1.0) { outColor = pal(int(uCrt.z)); return; }
|
|
73
|
+
px = clamp(ivec2(floor((uv * 0.5 + 0.5) * res)), ivec2(0), size - 1);
|
|
74
|
+
}
|
|
75
|
+
vec4 d = texelFetch(uData, px, 0);
|
|
76
|
+
vec4 d2 = texelFetch(uData2, px, 0);
|
|
77
|
+
float z = texelFetch(uDepth, px, 0).r;
|
|
78
|
+
int ramp = int(d.g * 255.0 + 0.5);
|
|
79
|
+
int mat = int(d.b * 255.0 + 0.5);
|
|
80
|
+
int id = int(d.a * 255.0 + 0.5);
|
|
81
|
+
float s = screenAt(ivec2(gl_FragCoord.xy)); // (the screen stays put on the glass, under a CRT's bend too)
|
|
82
|
+
float L = d.r;
|
|
83
|
+
|
|
84
|
+
// Colour grading: each ramp swapped for its graded one (day / dusk / night), and shifted along it.
|
|
85
|
+
if (uGrade.x > 0.5) { float g = texelFetch(uRamps, ivec2(ramp, 1), 0).x; if (g >= 0.0) ramp = int(g); }
|
|
86
|
+
// Fog: past its near, pixels go over to the fog's ramp -- the screen decides which, so the edge is dithered.
|
|
87
|
+
if (uFog.x > 0.5 && id != 255) {
|
|
88
|
+
float dist = z * ${FAR.toFixed(1)}; // (depth is the ray's length over FAR)
|
|
89
|
+
float f = clamp((dist - uFog.y) / max(uFog.z - uFog.y, 1e-3), 0.0, 1.0) * uFog.w;
|
|
90
|
+
if (f > s) { ramp = int(uFogLook.x); L = mix(L, uFogLook.y, f); }
|
|
91
|
+
}
|
|
92
|
+
vec4 R = rampOf(ramp);
|
|
93
|
+
int base = int(R.x);
|
|
94
|
+
int len = max(int(R.y), 1);
|
|
95
|
+
float top = float(len - 1);
|
|
96
|
+
float x = L * top;
|
|
97
|
+
if (uGrade.x > 0.5) x += uGrade.y;
|
|
98
|
+
|
|
99
|
+
// Glow: what glows climbs its ramp; round it, a halo climbs the ramps of what's near (or wears the glow's).
|
|
100
|
+
if (uGlow.x > 0.5) {
|
|
101
|
+
if (d2.r >= uGlowK.x) x += uGlow.w * d2.r;
|
|
102
|
+
else {
|
|
103
|
+
float best = 0.0;
|
|
104
|
+
int bestRamp = -1;
|
|
105
|
+
float r = uGlow.y;
|
|
106
|
+
// (A golden-angle spiral of taps covers the disc evenly: about one tap per 1.5 px square at the largest radius.)
|
|
107
|
+
int taps = int(clamp(r * r * 1.4, 8.0, 48.0));
|
|
108
|
+
for (int k = 0; k < 48; k++) {
|
|
109
|
+
if (k >= taps) break;
|
|
110
|
+
float a = float(k) * 2.3999632;
|
|
111
|
+
float rr = max(1.0, r * sqrt((float(k) + 0.5) / float(taps)));
|
|
112
|
+
ivec2 q = clamp(px + ivec2(round(vec2(cos(a), sin(a)) * rr)), ivec2(0), size - 1);
|
|
113
|
+
vec4 o2 = texelFetch(uData2, q, 0);
|
|
114
|
+
if (o2.r >= uGlowK.x) {
|
|
115
|
+
float f = rr / (r + 1.0);
|
|
116
|
+
float w = smoothstep(uGlowK.x, uGlowK.x + 0.3, o2.r) * (1.0 - f * f); // (strong glows reach full halo; it falls off outward)
|
|
117
|
+
if (w > best) { best = w; bestRamp = int(texelFetch(uData, q, 0).g * 255.0 + 0.5); }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (best > 0.0) {
|
|
121
|
+
if (uGlowK.y > 0.5 && best * 1.6 > s) {
|
|
122
|
+
ramp = bestRamp; R = rampOf(ramp); base = int(R.x); len = max(int(R.y), 1); top = float(len - 1);
|
|
123
|
+
x = (0.35 + 0.4 * best) * top;
|
|
124
|
+
} else x += uGlow.z * best;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Rim light: a thing's edge on the light's side, where what's beyond it is far behind, climbs its ramp.
|
|
129
|
+
if (uRim.x > 0.5 && id < 253) {
|
|
130
|
+
ivec2 q = clamp(px + ivec2(round(uRimDir * uRim.y)), ivec2(0), size - 1);
|
|
131
|
+
float oz = texelFetch(uDepth, q, 0).r;
|
|
132
|
+
// (Only a silhouette: another thing, well behind -- a floor seen edge-on is one thing, and gets none.)
|
|
133
|
+
if (abs(texelFetch(uData, q, 0).a - d.a) > 0.5 / 255.0 && oz > z + 0.004) x += uRim.z;
|
|
134
|
+
}
|
|
135
|
+
// Hit flash: the struck thing (by material, or by id) goes up toward white (or onto a flash ramp).
|
|
136
|
+
if (flashed(mat, id)) {
|
|
137
|
+
if (uFlash.y >= 0.0) { ramp = int(uFlash.y); R = rampOf(ramp); base = int(R.x); len = max(int(R.y), 1); top = float(len - 1); x = mix(L * top, top, uFlash.x); }
|
|
138
|
+
else x = mix(x, top + 0.49, uFlash.x);
|
|
139
|
+
}
|
|
140
|
+
// Vignette: the corners step down their ramps.
|
|
141
|
+
if (uVig.x > 0.5) {
|
|
142
|
+
vec2 uv = gl_FragCoord.xy / res * 2.0 - 1.0;
|
|
143
|
+
float r = length(uv * vec2(res.x / res.y, 1.0)) / length(vec2(res.x / res.y, 1.0));
|
|
144
|
+
x -= smoothstep(uVig.y, uVig.z, r) * uVig.w;
|
|
145
|
+
}
|
|
146
|
+
// Scanlines: every so many rows, a step down.
|
|
147
|
+
if (uScan.x > 0.5 && float(int(gl_FragCoord.y) % int(uScan.y)) < uScan.z) x -= uScan.w;
|
|
148
|
+
|
|
149
|
+
// The outline: a thing's edge against what's behind it (mode 1), or only across a gap (2: outer only).
|
|
150
|
+
bool edge = false;
|
|
151
|
+
if (uOutline.x > 0.5 && d.a < 0.99) {
|
|
152
|
+
float gap = uOutline.z; // (in depth: metres / FAR)
|
|
153
|
+
for (int k = 0; k < 4; k++) {
|
|
154
|
+
ivec2 o = k == 0 ? ivec2(1, 0) : k == 1 ? ivec2(-1, 0) : k == 2 ? ivec2(0, 1) : ivec2(0, -1);
|
|
155
|
+
ivec2 q = clamp(px + o, ivec2(0), size - 1);
|
|
156
|
+
float other = texelFetch(uData, q, 0).a;
|
|
157
|
+
float oz = texelFetch(uDepth, q, 0).r;
|
|
158
|
+
if (abs(other - d.a) > 0.5 / 255.0 && oz > z + gap) edge = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (edge && uOutlineInk >= 0) { outColor = pal(uOutlineInk); return; }
|
|
162
|
+
|
|
163
|
+
// To an entry: the screen breaks the step between two.
|
|
164
|
+
float th = uScreen == 0 ? 0.0 : s - 0.5;
|
|
165
|
+
x = x + th * uDither;
|
|
166
|
+
int idx = clamp(int(floor(x + 0.5)), 0, len - 1);
|
|
167
|
+
if (edge) idx = max(0, idx - int(uOutline.y));
|
|
168
|
+
// Palette cycling: a ramp's upper entries turn over, so water shimmers and neon runs.
|
|
169
|
+
else if (uCycle == 1 && R.w > 0.0 && idx >= int(R.z)) { int from = int(R.z); idx = from + (idx - from + int(floor(uTime * R.w))) % (len - from); }
|
|
170
|
+
outColor = pal(base + idx);
|
|
171
|
+
}`;
|
|
172
|
+
|
|
173
|
+
// packages/render/src/indexed.ts
|
|
174
|
+
function edit(src, find, replace) {
|
|
175
|
+
if (!src.includes(find)) throw new Error(`BAKE_WORLD_FS: WORLD_FS no longer contains ${JSON.stringify(find.slice(0, 60))}`);
|
|
176
|
+
return src.replace(find, replace);
|
|
177
|
+
}
|
|
178
|
+
var SURFACE_UV = `// Where on its part a point is, 0..1 each way: a capsule's (round it, along it -- caps included); a ball's
|
|
179
|
+
// (longitude round y, latitude up it); a box's or a wedge's (across the face it's on).
|
|
180
|
+
vec2 surfaceUv(vec3 p, int k) {
|
|
181
|
+
if (k >= 100 && k < 200) {
|
|
182
|
+
int i = k - 100;
|
|
183
|
+
vec4 A = uCap[2 * i]; vec4 B = uCap[2 * i + 1];
|
|
184
|
+
vec3 ba = B.xyz - A.xyz;
|
|
185
|
+
vec3 pa = p - A.xyz;
|
|
186
|
+
float L2 = dot(ba, ba);
|
|
187
|
+
if (L2 < 1e-8) { vec3 d = normalize(pa + vec3(0.0, 0.0, 1e-6)); return vec2(atan(d.x, d.z) / 6.2831853 + 0.5, d.y * 0.5 + 0.5); }
|
|
188
|
+
float len = sqrt(L2);
|
|
189
|
+
vec3 ax = ba / len;
|
|
190
|
+
float h = dot(pa, ax);
|
|
191
|
+
vec3 rad = pa - ax * clamp(h, 0.0, len);
|
|
192
|
+
vec3 e1 = normalize(abs(ax.y) < 0.9 ? cross(ax, vec3(0.0, 1.0, 0.0)) : cross(ax, vec3(1.0, 0.0, 0.0)));
|
|
193
|
+
vec3 e2 = cross(ax, e1);
|
|
194
|
+
return vec2(atan(dot(rad, e2), dot(rad, e1)) / 6.2831853 + 0.5, clamp((h + A.w) / (len + 2.0 * A.w), 0.0, 1.0));
|
|
195
|
+
}
|
|
196
|
+
vec4 A; vec4 B;
|
|
197
|
+
if (k < 100) { A = uBox[2 * k]; B = uBox[2 * k + 1]; } else { A = uWedge[3 * (k - 200)]; B = uWedge[3 * (k - 200) + 1]; }
|
|
198
|
+
vec3 q = p - A.xyz;
|
|
199
|
+
float c = cos(B.w), s = sin(B.w);
|
|
200
|
+
q.xz = mat2(c, s, -s, c) * q.xz;
|
|
201
|
+
vec3 r = q / max(B.xyz, vec3(1e-4));
|
|
202
|
+
vec3 a = abs(r);
|
|
203
|
+
vec2 f = a.x >= a.y && a.x >= a.z ? r.zy : a.y >= a.z ? r.xz : r.xy;
|
|
204
|
+
return clamp(f * 0.5 + 0.5, 0.0, 1.0);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
void main() {`;
|
|
208
|
+
var BAKE_WORLD_FS = [
|
|
209
|
+
["void main() {", `uniform vec4 uSplit; // a point (xyz) the hits are split at, seen from the camera; w 1 on, 0 off
|
|
210
|
+
uniform float uOrthoH; // > 0: an ORTHOGRAPHIC bake -- parallel rays over a picture this many metres half-high (the game's own view: nothing nearer the camera drawn bigger)
|
|
211
|
+
${SURFACE_UV}`],
|
|
212
|
+
["vec3 rd = normalize(uFwd + uv.x * uTan * aspect * uRight + uv.y * uTan * uUp);\n vec3 ro = uEye;", "vec3 rd = uOrthoH > 0.0 ? uFwd : normalize(uFwd + uv.x * uTan * aspect * uRight + uv.y * uTan * uUp);\n vec3 ro = uOrthoH > 0.0 ? uEye + (uv.x * aspect * uRight + uv.y * uUp) * uOrthoH : uEye;"],
|
|
213
|
+
["float L; float ramp; float id; float depth; float mat; float glow = 0.0; float facing = 1.0;", "float L; float ramp; float id; float depth; float mat; float glow = 0.0; float facing = 1.0; vec2 surf = vec2(0.0);"],
|
|
214
|
+
["glow = clamp(mr.w, 0.0, 1.0); facing = clamp(dot(n, -rd), 0.0, 1.0);", "glow = clamp(mr.w, 0.0, 1.0); facing = clamp(dot(n, -rd), 0.0, 1.0); surf = surfaceUv(p, int(hit.z + 0.5)); ramp = uSplit.w > 0.5 && dot(p - uSplit.xyz, uFwd) > 0.0 ? 1.0 : 0.0;"],
|
|
215
|
+
["outData2 = vec4(glow, facing, 0.0, 0.0);", "outData2 = vec4(glow, facing, surf);"]
|
|
216
|
+
].reduce((src, [find, replace]) => edit(src, find, replace), WORLD_FS);
|
|
217
|
+
var INDEX_FS = `#version 300 es
|
|
218
|
+
precision highp float;
|
|
219
|
+
precision highp sampler2D;
|
|
220
|
+
uniform sampler2D uData;
|
|
221
|
+
uniform sampler2D uData2;
|
|
222
|
+
uniform sampler2D uDepth;
|
|
223
|
+
uniform float uGap; // the outline's depth gap (metres / FAR), as the pixel pass's uOutline.z
|
|
224
|
+
out vec4 outColor;
|
|
225
|
+
void main() {
|
|
226
|
+
ivec2 size = textureSize(uData, 0);
|
|
227
|
+
ivec2 px = ivec2(gl_FragCoord.xy);
|
|
228
|
+
vec4 d = texelFetch(uData, px, 0);
|
|
229
|
+
int id = int(d.a * 255.0 + 0.5);
|
|
230
|
+
if (id >= 253) { outColor = vec4(0.0); return; }
|
|
231
|
+
vec4 d2 = texelFetch(uData2, px, 0);
|
|
232
|
+
float z = texelFetch(uDepth, px, 0).r;
|
|
233
|
+
bool edge = false;
|
|
234
|
+
for (int k = 0; k < 4; k++) {
|
|
235
|
+
ivec2 o = k == 0 ? ivec2(1, 0) : k == 1 ? ivec2(-1, 0) : k == 2 ? ivec2(0, 1) : ivec2(0, -1);
|
|
236
|
+
ivec2 q = clamp(px + o, ivec2(0), size - 1);
|
|
237
|
+
float other = texelFetch(uData, q, 0).a;
|
|
238
|
+
float oz = texelFetch(uDepth, q, 0).r;
|
|
239
|
+
if (abs(other - d.a) > 0.5 / 255.0 && oz > z + uGap) edge = true;
|
|
240
|
+
}
|
|
241
|
+
int mat = min(int(d.b * 255.0 + 0.5), 31);
|
|
242
|
+
bool behind = d.g > 0.5 / 255.0;
|
|
243
|
+
outColor = vec4(float(128 + (edge ? 64 : 0) + (behind ? 32 : 0) + mat) / 255.0, d.r, d2.b, d2.a);
|
|
244
|
+
}`;
|
|
245
|
+
var HEIGHT_FS = `${WORLD_FS.slice(0, WORLD_FS.indexOf("void main() {"))}
|
|
246
|
+
uniform sampler2D uData;
|
|
247
|
+
uniform sampler2D uDepth;
|
|
248
|
+
uniform float uScale; // texels a metre (the bake's pixels per metre)
|
|
249
|
+
uniform float uEps; // how far the bake thinned each solid (metres)
|
|
250
|
+
uniform float uOrthoH; // > 0: an orthographic bake (as pass 1's)
|
|
251
|
+
void main() {
|
|
252
|
+
ivec2 px = ivec2(gl_FragCoord.xy);
|
|
253
|
+
int id = int(texelFetch(uData, px, 0).a * 255.0 + 0.5);
|
|
254
|
+
if (id >= 253) { outData = vec4(0.0); outData2 = vec4(0.0); return; }
|
|
255
|
+
float t = texelFetch(uDepth, px, 0).r * FAR;
|
|
256
|
+
vec2 uv = (gl_FragCoord.xy / uRes) * 2.0 - 1.0;
|
|
257
|
+
vec3 rd = uOrthoH > 0.0 ? uFwd : normalize(uFwd + uv.x * uTan * (uRes.x / uRes.y) * uRight + uv.y * uTan * uUp);
|
|
258
|
+
vec3 ro = uOrthoH > 0.0 ? uEye + (uv.x * (uRes.x / uRes.y) * uRight + uv.y * uUp) * uOrthoH : uEye;
|
|
259
|
+
// (Back off past where the march could have overshot, then march onto the shell pass 1 calls a hit -- the thinned
|
|
260
|
+
// field within its hit tolerance, 0.0015 x the ray's length: at the bake's distance, uEps, the design's own
|
|
261
|
+
// surface -- the first point along the ray the colours were drawn from, never past it.)
|
|
262
|
+
float s = max(0.0, t - 3.0 * max(uEps, 0.0015 * t) - 0.002);
|
|
263
|
+
for (int i = 0; i < 48; i++) {
|
|
264
|
+
float d = map(ro + rd * s).x - 0.0015 * s;
|
|
265
|
+
if (d < 0.0002) break;
|
|
266
|
+
s += d;
|
|
267
|
+
if (s > t) { s = t; break; }
|
|
268
|
+
}
|
|
269
|
+
float y = max(ro.y + rd.y * s, 0.0);
|
|
270
|
+
uint v = uint(clamp(floor(y * uScale * 16.0 + 1.5), 1.0, 65535.0));
|
|
271
|
+
outData = vec4(float(v >> 8u) / 255.0, float(v & 255u) / 255.0, 0.0, 1.0);
|
|
272
|
+
outData2 = vec4(0.0);
|
|
273
|
+
}`;
|
|
274
|
+
var DEPTH_FS = `#version 300 es
|
|
275
|
+
precision highp float;
|
|
276
|
+
precision highp sampler2D;
|
|
277
|
+
uniform sampler2D uDepth;
|
|
278
|
+
out vec4 outColor;
|
|
279
|
+
void main() {
|
|
280
|
+
float z = texelFetch(uDepth, ivec2(gl_FragCoord.xy), 0).r;
|
|
281
|
+
uint v = uint(clamp(z, 0.0, 1.0) * 16777215.0 + 0.5);
|
|
282
|
+
outColor = vec4(float(v >> 16u) / 255.0, float((v >> 8u) & 255u) / 255.0, float(v & 255u) / 255.0, 1.0);
|
|
283
|
+
}`;
|
|
284
|
+
var unpackDepth = (r, g, b) => (r << 16 | g << 8 | b) / 16777215;
|
|
285
|
+
|
|
286
|
+
// packages/render/src/raster.ts
|
|
287
|
+
var RASTER_FLOATS = 16;
|
|
288
|
+
var DIRECT_MAT = 254;
|
|
289
|
+
function boxTemplate() {
|
|
290
|
+
const faces = [
|
|
291
|
+
// normal, u axis, v axis (u x v = normal: counter-clockwise from outside)
|
|
292
|
+
[[1, 0, 0], [0, 0, -1], [0, 1, 0]],
|
|
293
|
+
[[-1, 0, 0], [0, 0, 1], [0, 1, 0]],
|
|
294
|
+
[[0, 1, 0], [1, 0, 0], [0, 0, -1]],
|
|
295
|
+
[[0, -1, 0], [1, 0, 0], [0, 0, 1]],
|
|
296
|
+
[[0, 0, 1], [1, 0, 0], [0, 1, 0]],
|
|
297
|
+
[[0, 0, -1], [-1, 0, 0], [0, 1, 0]]
|
|
298
|
+
];
|
|
299
|
+
const verts = [], norms = [], index = [];
|
|
300
|
+
faces.forEach(([n, u, v], f) => {
|
|
301
|
+
for (const [a, b] of [[-1, -1], [1, -1], [1, 1], [-1, 1]]) {
|
|
302
|
+
verts.push(n[0] + u[0] * a + v[0] * b, n[1] + u[1] * a + v[1] * b, n[2] + u[2] * a + v[2] * b);
|
|
303
|
+
norms.push(n[0], n[1], n[2]);
|
|
304
|
+
}
|
|
305
|
+
index.push(f * 4, f * 4 + 1, f * 4 + 2, f * 4, f * 4 + 2, f * 4 + 3);
|
|
306
|
+
});
|
|
307
|
+
return { verts: Float32Array.from(verts), norms: Float32Array.from(norms), index: Uint16Array.from(index) };
|
|
308
|
+
}
|
|
309
|
+
function capsuleTemplate(around = 10, rings = 3) {
|
|
310
|
+
const verts = [], norms = [], index = [];
|
|
311
|
+
const row = around + 1;
|
|
312
|
+
const rows = [];
|
|
313
|
+
for (let k = 0; k <= rings; k += 1) rows.push([-Math.PI / 2 + k / rings * (Math.PI / 2), 0]);
|
|
314
|
+
for (let k = 0; k <= rings; k += 1) rows.push([k / rings * (Math.PI / 2), 1]);
|
|
315
|
+
for (const [phi, end] of rows) for (let i = 0; i <= around; i += 1) {
|
|
316
|
+
const th = i / around * Math.PI * 2;
|
|
317
|
+
verts.push(Math.cos(th), Math.sin(th), Math.sin(phi));
|
|
318
|
+
norms.push(Math.cos(phi), end, 0);
|
|
319
|
+
}
|
|
320
|
+
for (let r = 0; r < rows.length - 1; r += 1) for (let i = 0; i < around; i += 1) {
|
|
321
|
+
const a = r * row + i, b = a + 1, c = a + row, d = c + 1;
|
|
322
|
+
index.push(a, c, b, b, c, d);
|
|
323
|
+
}
|
|
324
|
+
return { verts: Float32Array.from(verts), norms: Float32Array.from(norms), index: Uint16Array.from(index) };
|
|
325
|
+
}
|
|
326
|
+
var FARS = FAR.toFixed(1);
|
|
327
|
+
var RASTER_VS = `#version 300 es
|
|
328
|
+
layout(location=0) in vec3 aVert;
|
|
329
|
+
layout(location=1) in vec3 aNorm;
|
|
330
|
+
layout(location=2) in vec4 aI0;
|
|
331
|
+
layout(location=3) in vec4 aI1;
|
|
332
|
+
layout(location=4) in vec4 aI2;
|
|
333
|
+
layout(location=5) in vec4 aI3;
|
|
334
|
+
uniform int uKind;
|
|
335
|
+
uniform vec3 uEye, uFwd, uRight, uUp;
|
|
336
|
+
uniform float uTan, uAspect;
|
|
337
|
+
out vec3 vPos;
|
|
338
|
+
out vec3 vNorm;
|
|
339
|
+
flat out vec4 vLook; // material, ramp (-1: the material's), light scale, id
|
|
340
|
+
flat out vec4 vFx; // fade, glow, 0, 0
|
|
341
|
+
out float vShade; // a mesh vertex's lightness offset
|
|
342
|
+
// (Own frame -> world, the inverse of WORLD_FS's world -> box turn: x = c x' + s z', z = -s x' + c z'.)
|
|
343
|
+
vec3 turn(vec3 q, float yaw) { float c = cos(yaw), s = sin(yaw); return vec3(c * q.x + s * q.z, q.y, -s * q.x + c * q.z); }
|
|
344
|
+
void main() {
|
|
345
|
+
vec3 p; vec3 n;
|
|
346
|
+
vShade = 0.0;
|
|
347
|
+
if (uKind == 0) {
|
|
348
|
+
p = aVert; n = aNorm;
|
|
349
|
+
vLook = vec4(aI0.x, aI0.y, 1.0, aI0.w); vFx = vec4(1.0, 0.0, 0.0, 0.0); vShade = aI0.z;
|
|
350
|
+
} else if (uKind == 3) {
|
|
351
|
+
vec3 a = aI0.xyz, b = aI1.xyz;
|
|
352
|
+
float r = aI1.w;
|
|
353
|
+
vec3 ax = b - a;
|
|
354
|
+
float L = length(ax);
|
|
355
|
+
ax = L > 1e-6 ? ax / L : vec3(0.0, 1.0, 0.0);
|
|
356
|
+
vec3 u = normalize(abs(ax.y) < 0.9 ? cross(ax, vec3(0.0, 1.0, 0.0)) : cross(ax, vec3(1.0, 0.0, 0.0)));
|
|
357
|
+
vec3 v = cross(ax, u);
|
|
358
|
+
float sp = aVert.z, cp = aNorm.x;
|
|
359
|
+
bool atB = aNorm.y > 0.5;
|
|
360
|
+
// (phi runs from a's pole -- back along the axis, sin phi = -1 -- through the two equators to b's.)
|
|
361
|
+
vec3 d = cp * (aVert.x * u + aVert.y * v) + sp * ax;
|
|
362
|
+
p = (atB ? b : a) + r * d;
|
|
363
|
+
n = d;
|
|
364
|
+
vLook = vec4(aI0.w, aI2.y, aI2.z, aI2.w); vFx = aI3;
|
|
365
|
+
} else {
|
|
366
|
+
vec3 h = aI1.xyz;
|
|
367
|
+
vec3 q = aVert;
|
|
368
|
+
vec3 nq = aNorm;
|
|
369
|
+
if (uKind == 2) {
|
|
370
|
+
float lo = aI2.x;
|
|
371
|
+
// (The +z edge of the top comes down to the foot: lo x its height.)
|
|
372
|
+
if (q.y > 0.5 && q.z > 0.5) q.y = -1.0 + 2.0 * lo;
|
|
373
|
+
if (nq.y > 0.5) nq = normalize(vec3(0.0, 2.0 * h.z, 2.0 * h.y * (1.0 - lo)));
|
|
374
|
+
}
|
|
375
|
+
p = aI0.xyz + turn(q * h, aI1.w);
|
|
376
|
+
n = turn(uKind == 2 ? nq : aNorm, aI1.w);
|
|
377
|
+
vLook = vec4(aI0.w, aI2.y, aI2.z, aI2.w); vFx = aI3;
|
|
378
|
+
}
|
|
379
|
+
vPos = p; vNorm = n;
|
|
380
|
+
vec3 e = p - uEye;
|
|
381
|
+
float z = dot(e, uFwd);
|
|
382
|
+
const float NEAR = 0.05, FARZ = ${FARS} * 1.5;
|
|
383
|
+
gl_Position = vec4(dot(e, uRight) / (uTan * uAspect), dot(e, uUp) / uTan, z * (FARZ + NEAR) / (FARZ - NEAR) - 2.0 * FARZ * NEAR / (FARZ - NEAR), z);
|
|
384
|
+
}`;
|
|
385
|
+
var RASTER_FS = `#version 300 es
|
|
386
|
+
precision highp float;
|
|
387
|
+
precision highp sampler2D;
|
|
388
|
+
uniform sampler2D uMats;
|
|
389
|
+
uniform vec3 uEye, uSun;
|
|
390
|
+
uniform float uWaterY, uFogNear, uFogFar, uTan, uTime;
|
|
391
|
+
uniform vec2 uRes;
|
|
392
|
+
in vec3 vPos;
|
|
393
|
+
in vec3 vNorm;
|
|
394
|
+
flat in vec4 vLook;
|
|
395
|
+
flat in vec4 vFx;
|
|
396
|
+
in float vShade;
|
|
397
|
+
layout(location = 0) out vec4 outData;
|
|
398
|
+
layout(location = 1) out vec4 outData2;
|
|
399
|
+
float bayer4(ivec2 p) { int m[16] = int[16](0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5); return (float(m[(p.y & 3) * 4 + (p.x & 3)]) + 0.5) / 16.0; }
|
|
400
|
+
vec4 matOf(int i) { return texelFetch(uMats, ivec2(i, 0), 0); }
|
|
401
|
+
float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
|
|
402
|
+
float vnoise(vec2 p) { vec2 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f); return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); }
|
|
403
|
+
// (Engine patterns past the checker: 2 grain -- a ground's speckle, blades and pebbles at a pixel or two -- and 3
|
|
404
|
+
// strata -- a cliff's bands. Both fade out where a cell would be under a pixel, as the checker does.)
|
|
405
|
+
float grain(vec3 p, vec3 n, float px) {
|
|
406
|
+
vec2 uv = abs(n.y) > 0.6 ? p.xz : abs(n.x) > abs(n.z) ? p.zy : p.xy;
|
|
407
|
+
float fine = hash(floor(uv * 7.0)) - 0.5, coarse = vnoise(uv * 0.9) - 0.5;
|
|
408
|
+
return (fine * 0.12 * smoothstep(1.2, 3.0, px / 7.0) + coarse * 0.14);
|
|
409
|
+
}
|
|
410
|
+
float strata(vec3 p, float px) { return (vnoise(vec2(p.x * 0.7 + p.z * 0.7, p.y * 3.1)) - 0.5) * 0.2 + (hash(floor(vec2(p.x + p.z, p.y) * 5.0)) - 0.5) * 0.07 * smoothstep(1.2, 3.0, px / 5.0); }
|
|
411
|
+
float tiles(vec3 p, vec3 n) {
|
|
412
|
+
vec2 uv = abs(n.y) > 0.6 ? p.xz : abs(n.x) > abs(n.z) ? p.zy : p.xy;
|
|
413
|
+
vec2 f = fract(uv);
|
|
414
|
+
float joint = step(0.95, max(f.x, f.y)) * 0.06;
|
|
415
|
+
float chk = mod(floor(uv.x) + floor(uv.y), 2.0);
|
|
416
|
+
return 0.16 * chk - 0.08 - joint;
|
|
417
|
+
}
|
|
418
|
+
void main() {
|
|
419
|
+
// (A dissolve: the solid keeps the screen's lower thresholds, a sprite fading in keeps the rest.)
|
|
420
|
+
if (vFx.x < 0.999 && bayer4(ivec2(gl_FragCoord.xy)) >= vFx.x) discard;
|
|
421
|
+
vec3 n = normalize(vNorm);
|
|
422
|
+
vec3 e = vPos - uEye;
|
|
423
|
+
float dist = length(e);
|
|
424
|
+
vec3 rd = e / max(dist, 1e-6);
|
|
425
|
+
if (dot(n, rd) > 0.0) n = -n; // (a face seen from behind: light it as its front)
|
|
426
|
+
int mi = int(vLook.x + 0.5);
|
|
427
|
+
vec4 mr = matOf(mi);
|
|
428
|
+
float ramp = vLook.y >= 0.0 ? vLook.y : mr.x;
|
|
429
|
+
float L;
|
|
430
|
+
float tilePx = uRes.y / max(dist * 2.0 * uTan, 1e-3); // (picture pixels a metre here)
|
|
431
|
+
if (mi == ${WATER_MAT}) {
|
|
432
|
+
// (Water as a surface: dark and deep, rippled, brighter toward the eye's grazing angle, a glint here and there.)
|
|
433
|
+
float ripple = vnoise(vPos.xz * 1.3 + vec2(uTime * 0.35, uTime * 0.22)) * vnoise(vPos.xz * 0.6 - vec2(uTime * 0.1, 0.0));
|
|
434
|
+
float glint = step(0.8, vnoise(vPos.xz * 4.0 + uTime * 0.7)) * 0.25 * clamp(1.0 - dist / 40.0, 0.0, 1.0);
|
|
435
|
+
L = 0.14 + 0.22 * ripple + 0.2 * pow(1.0 - clamp(-rd.y, 0.0, 1.0), 3.0) + glint + vShade;
|
|
436
|
+
} else {
|
|
437
|
+
float diff = max(dot(n, uSun), 0.0);
|
|
438
|
+
float wglow = clamp(1.0 - (vPos.y - uWaterY) / 1.6, 0.0, 1.0) * max(0.0, -n.y * 0.2 + 0.8) * 0.35;
|
|
439
|
+
L = (0.16 + 0.1 * n.y + 0.5 * diff) * mr.y * vLook.z + wglow + mr.w + vFx.y + vShade;
|
|
440
|
+
float patternK = smoothstep(2.5, 7.0, tilePx) * (0.35 + 0.65 * smoothstep(28.0, 96.0, uRes.y));
|
|
441
|
+
if (mr.z > 0.5 && mr.z < 1.5) L += tiles(vPos, n) * patternK;
|
|
442
|
+
else if (mr.z > 1.5 && mr.z < 2.5) L += grain(vPos, n, tilePx);
|
|
443
|
+
else if (mr.z > 2.5 && mr.z < 3.5) L += strata(vPos, tilePx);
|
|
444
|
+
}
|
|
445
|
+
L = mix(L, 0.12, clamp((dist - uFogNear) / (uFogFar - uFogNear), 0.0, 1.0));
|
|
446
|
+
outData = vec4(clamp(L, 0.0, 1.0), ramp / 255.0, float(mi) / 255.0, mod(vLook.w, 250.0) / 255.0);
|
|
447
|
+
outData2 = vec4(clamp(mr.w + vFx.y, 0.0, 1.0), clamp(dot(n, -rd), 0.0, 1.0), 0.0, 0.0);
|
|
448
|
+
gl_FragDepth = clamp(dist / ${FARS}, 0.0, 1.0);
|
|
449
|
+
}`;
|
|
450
|
+
var DEPTH_OUT_FS = `#version 300 es
|
|
451
|
+
precision highp float;
|
|
452
|
+
precision highp sampler2D;
|
|
453
|
+
uniform sampler2D uDepth;
|
|
454
|
+
uniform vec2 uMap; // scale, bias
|
|
455
|
+
out vec4 outColor;
|
|
456
|
+
void main() {
|
|
457
|
+
gl_FragDepth = clamp(texelFetch(uDepth, ivec2(gl_FragCoord.xy), 0).r * uMap.x + uMap.y, 0.0, 1.0);
|
|
458
|
+
outColor = vec4(0.0);
|
|
459
|
+
}`;
|
|
460
|
+
var DIRECT_PIXEL_FS = PIXEL_FS.replace(" float L = d.r;\n", ` float L = d.r;
|
|
461
|
+
// (Engine, DIRECT_MAT: a palette index straight -- its ramp's base, length and place ride along for the outline and fog.)
|
|
462
|
+
int directBase = -1, directLen = 1, directPos = 0;
|
|
463
|
+
if (mat == ${DIRECT_MAT} && d2.a > 0.0) {
|
|
464
|
+
int pl = int(d2.a * 255.0 + 0.5);
|
|
465
|
+
directPos = pl & 15;
|
|
466
|
+
directLen = max(1, pl >> 4);
|
|
467
|
+
directBase = int(d.r * 255.0 + 0.5) + int(d.g * 255.0 + 0.5) * 256 - directPos;
|
|
468
|
+
L = d2.b;
|
|
469
|
+
}
|
|
470
|
+
`).replace(" if (uGrade.x > 0.5) { float g", " if (uGrade.x > 0.5 && directBase < 0) { float g").replace(" if (f > s) { ramp = int(uFogLook.x); L = mix(L, uFogLook.y, f); }\n }\n vec4 R = rampOf(ramp);", " if (f > s) { ramp = int(uFogLook.x); L = mix(L, uFogLook.y, f); directBase = -1; }\n }\n vec4 R = directBase >= 0 ? vec4(float(directBase), float(directLen), 0.0, 0.0) : rampOf(ramp);").replace(" float x = L * top;\n", " float x = directBase >= 0 ? float(directPos) : L * top;\n");
|
|
471
|
+
if (!DIRECT_PIXEL_FS.includes("directBase = -1; }") || !DIRECT_PIXEL_FS.includes("float(directPos) : L * top") || !DIRECT_PIXEL_FS.includes("directBase < 0) { float g") || !DIRECT_PIXEL_FS.includes("directPos = pl & 15")) throw new Error("DIRECT_PIXEL_FS: PIXEL_FS changed under its edits.");
|
|
472
|
+
|
|
473
|
+
// packages/render/src/smooth.ts
|
|
474
|
+
function smoothPixelFs(fs) {
|
|
475
|
+
const edits = [
|
|
476
|
+
["uniform int uCycle; // on\n", "uniform int uCycle; // on\nuniform int uSS; // samples to an art pixel, each way (smooth edges)\n"],
|
|
477
|
+
["float s = screenAt(ivec2(gl_FragCoord.xy));", "float s = screenAt(ivec2(gl_FragCoord.xy) / uSS);"],
|
|
478
|
+
["ivec2(round(vec2(cos(a), sin(a)) * rr))", "ivec2(round(vec2(cos(a), sin(a)) * rr * float(uSS)))"],
|
|
479
|
+
["ivec2(round(uRimDir * uRim.y))", "ivec2(round(uRimDir * uRim.y * float(uSS)))"],
|
|
480
|
+
["ivec2 q = clamp(px + o, ivec2(0), size - 1);", "ivec2 q = clamp(px + o * uSS, ivec2(0), size - 1);"],
|
|
481
|
+
["float(int(gl_FragCoord.y) % int(uScan.y))", "float((int(gl_FragCoord.y) / uSS) % int(uScan.y))"]
|
|
482
|
+
];
|
|
483
|
+
let out = fs;
|
|
484
|
+
for (const [from, to] of edits) {
|
|
485
|
+
if (!out.includes(from)) throw new Error(`smoothPixelFs: the pixel pass changed under its edit (${from.trim().slice(0, 40)})`);
|
|
486
|
+
out = out.replace(from, to);
|
|
487
|
+
}
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
function smoothDepthOutFs(fs) {
|
|
491
|
+
const a = "uniform vec2 uMap; // scale, bias\n", b = "texelFetch(uDepth, ivec2(gl_FragCoord.xy), 0)";
|
|
492
|
+
if (!fs.includes(a) || !fs.includes(b)) throw new Error("smoothDepthOutFs: the depth pass changed under its edit");
|
|
493
|
+
return fs.replace(a, a + "uniform int uSS;\n").replace(b, "texelFetch(uDepth, ivec2(gl_FragCoord.xy) * uSS + uSS / 2, 0)");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export {
|
|
497
|
+
BAKE_WORLD_FS,
|
|
498
|
+
DEPTH_FS,
|
|
499
|
+
DEPTH_OUT_FS,
|
|
500
|
+
DIRECT_PIXEL_FS,
|
|
501
|
+
HEIGHT_FS,
|
|
502
|
+
INDEX_FS,
|
|
503
|
+
PIXEL_FS,
|
|
504
|
+
RASTER_FLOATS,
|
|
505
|
+
RASTER_FS,
|
|
506
|
+
RASTER_VS,
|
|
507
|
+
boxTemplate,
|
|
508
|
+
capsuleTemplate,
|
|
509
|
+
smoothDepthOutFs,
|
|
510
|
+
smoothPixelFs,
|
|
511
|
+
unpackDepth,
|
|
512
|
+
};
|