@wave3d/core 0.9.0 → 0.11.0
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/dist/config/model.d.ts +209 -6
- package/dist/config/model.js +85 -3
- package/dist/config/model.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/presets.js +295 -0
- package/dist/presets.js.map +1 -1
- package/dist/renderer/WaveGeometry.js +21 -0
- package/dist/renderer/WaveGeometry.js.map +1 -1
- package/dist/renderer/WaveRenderer.d.ts +62 -0
- package/dist/renderer/WaveRenderer.js +349 -10
- package/dist/renderer/WaveRenderer.js.map +1 -1
- package/dist/renderer/WaveRendererGPU.js +32 -2
- package/dist/renderer/WaveRendererGPU.js.map +1 -1
- package/dist/renderer/interaction.js +12 -0
- package/dist/renderer/interaction.js.map +1 -1
- package/dist/renderer/particleField.js +26 -2
- package/dist/renderer/particleField.js.map +1 -1
- package/dist/renderer/particleFieldGPU.js +6 -0
- package/dist/renderer/particleFieldGPU.js.map +1 -1
- package/dist/renderer/shaders.js +748 -13
- package/dist/renderer/shaders.js.map +1 -1
- package/dist/renderer/tsl/dissolve.js +56 -0
- package/dist/renderer/tsl/dissolve.js.map +1 -0
- package/dist/renderer/tsl/particleMaterial.js +46 -8
- package/dist/renderer/tsl/particleMaterial.js.map +1 -1
- package/dist/renderer/tsl/uniforms.js +40 -1
- package/dist/renderer/tsl/uniforms.js.map +1 -1
- package/dist/renderer/tsl/waveMaterial.js +275 -26
- package/dist/renderer/tsl/waveMaterial.js.map +1 -1
- package/dist/renderer/tsl/waveShape.js +31 -10
- package/dist/renderer/tsl/waveShape.js.map +1 -1
- package/dist/renderer/wavePath.js +189 -0
- package/dist/renderer/wavePath.js.map +1 -0
- package/dist/shell/createWave.d.ts +23 -3
- package/dist/shell/createWave.js +5 -4
- package/dist/shell/createWave.js.map +1 -1
- package/dist/shell/probe.d.ts +28 -0
- package/dist/shell/probe.js +58 -11
- package/dist/shell/probe.js.map +1 -1
- package/dist/standalone/wave3d.standalone.js +3401 -2108
- package/dist/standalone/wave3d.standalone.webgpu.js +7372 -5848
- package/dist/standalone.d.ts +2 -2
- package/dist/standalone.js +2 -2
- package/dist/studio/StudioWaveRenderer.d.ts +115 -8
- package/dist/studio/StudioWaveRenderer.js +588 -14
- package/dist/studio/StudioWaveRenderer.js.map +1 -1
- package/dist/studio/index.d.ts +2 -2
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/randomize.js +0 -1
- package/dist/studio/randomize.js.map +1 -1
- package/package.json +1 -1
- package/skills/wave3d/SKILL.md +142 -5
|
@@ -2,9 +2,10 @@ import "../../config/model.js";
|
|
|
2
2
|
import { grainHash, simplexNoise } from "./noise.js";
|
|
3
3
|
import { applyTwist, waveShape } from "./waveShape.js";
|
|
4
4
|
import { pointerField } from "./pointerField.js";
|
|
5
|
+
import { dissolved } from "./dissolve.js";
|
|
5
6
|
import { applyColorGrade, hueShift, mapLinear, parabola, waveBaseColor } from "./color.js";
|
|
6
|
-
import { NodeMaterial } from "three/webgpu";
|
|
7
|
-
import { Break, Fn, If, Loop, abs, cameraPosition, cameraProjectionMatrix, cameraViewMatrix, clamp, cos, cross, dFdx, dFdy, dot, float, fwidth, max, mix, modelWorldMatrix, normalize, positionLocal, positionWorld, pow, radians, screenUV, select, sin, smoothstep, uv, varying, vec2, vec3, vec4 } from "three/tsl";
|
|
7
|
+
import { AdditiveBlending, NodeMaterial } from "three/webgpu";
|
|
8
|
+
import { Break, Discard, Fn, If, Loop, abs, asin, attribute, cameraPosition, cameraProjectionMatrix, cameraViewMatrix, clamp, cos, cross, dFdx, dFdy, dot, exp, float, fwidth, length, max, min, mix, modelWorldMatrix, normalize, positionLocal, positionWorld, pow, radians, screenUV, select, sign, sin, smoothstep, sqrt, uv, varying, vec2, vec3, vec4 } from "three/tsl";
|
|
8
9
|
//#region src/renderer/tsl/waveMaterial.ts
|
|
9
10
|
/**
|
|
10
11
|
* The wave's `NodeMaterial` — the TSL port of `vertexShader` + `fragmentShader` / `lineFragmentShader`.
|
|
@@ -12,11 +13,16 @@ import { Break, Fn, If, Loop, abs, cameraPosition, cameraProjectionMatrix, camer
|
|
|
12
13
|
* The GLSL `#ifdef` variants become JS flags, so each material builds exactly the graph it needs
|
|
13
14
|
* instead of relying on a define set and a program cache key.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* One deliberate difference from the GLSL: no output colour-space conversion. The material writes
|
|
17
|
+
* LINEAR, exactly as the GLSL does; the conversion happens once at the end of the post chain,
|
|
18
|
+
* matching `OutputPass` on the WebGL path.
|
|
19
|
+
*
|
|
20
|
+
* Premultiplied alpha, by contrast, has to be done HERE. `NodeMaterial.setupOutput()` does call
|
|
21
|
+
* `setupPremultipliedAlpha()` — but on its own `basicOutput`, which it then discards the moment a
|
|
22
|
+
* custom `outputNode` is set (`if (isCustomOutput) resultNode = this.outputNode`). So a material
|
|
23
|
+
* that writes its own output gets the premultiplied BLEND FACTORS without the premultiply, and
|
|
24
|
+
* every partly transparent pixel composites too bright. The GLSL does the same multiply under the
|
|
25
|
+
* PREMULTIPLIED_ALPHA define Three injects for it; this is that line.
|
|
20
26
|
*/
|
|
21
27
|
/** Linear + orbit time. Only the one selected by `loopMotion` is read, as in the GLSL. */
|
|
22
28
|
function timeNodes(u, flags) {
|
|
@@ -66,56 +72,299 @@ function surfaceStreaks(u, uvIn, color, crease) {
|
|
|
66
72
|
/** Build one wave's material for the given flags. */
|
|
67
73
|
function buildWaveMaterial(u, flags) {
|
|
68
74
|
const material = new NodeMaterial();
|
|
69
|
-
material.transparent =
|
|
75
|
+
material.transparent = flags.theme !== "glass";
|
|
70
76
|
material.depthTest = true;
|
|
71
77
|
material.depthWrite = true;
|
|
72
78
|
material.side = 2;
|
|
79
|
+
if (flags.layerPass) {
|
|
80
|
+
material.transparent = true;
|
|
81
|
+
material.blending = AdditiveBlending;
|
|
82
|
+
material.depthTest = false;
|
|
83
|
+
material.depthWrite = false;
|
|
84
|
+
}
|
|
73
85
|
let pointerFall = null;
|
|
86
|
+
let vertexNormal = null;
|
|
74
87
|
material.positionNode = Fn(() => {
|
|
75
88
|
const { t, loopOff } = timeNodes(u, flags);
|
|
76
89
|
const ws = waveShape(u, flags, positionLocal, uv(), t, loopOff);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
const nb = flags.vertexNormal ? (() => {
|
|
91
|
+
const aU = attribute("positionU", "vec4");
|
|
92
|
+
const aV = attribute("positionV", "vec4");
|
|
93
|
+
const stepU = aU.w;
|
|
94
|
+
const stepV = aV.w;
|
|
95
|
+
return {
|
|
96
|
+
stepU,
|
|
97
|
+
stepV,
|
|
98
|
+
wsU: waveShape(u, flags, aU.xyz, uv().add(vec2(stepU, 0)), t, loopOff),
|
|
99
|
+
wsV: waveShape(u, flags, aV.xyz, uv().add(vec2(0, stepV)), t, loopOff)
|
|
100
|
+
};
|
|
101
|
+
})() : null;
|
|
102
|
+
let pos = ws.pos;
|
|
103
|
+
let posU = nb ? nb.wsU.pos : null;
|
|
104
|
+
let posV = nb ? nb.wsV.pos : null;
|
|
105
|
+
if (flags.pointerFx) {
|
|
106
|
+
const mvp = cameraProjectionMatrix.mul(cameraViewMatrix).mul(modelWorldMatrix).toVar("mvp");
|
|
107
|
+
const bump = (p, twists) => {
|
|
108
|
+
const clip = mvp.mul(vec4(p, 1)).toVar();
|
|
109
|
+
const hit = pointerField(u, {
|
|
110
|
+
loopMotion: flags.loopMotion,
|
|
111
|
+
ripples: flags.pointerRipples
|
|
112
|
+
}, clip.xy.div(max(clip.w, 1e-6)), mvp, twists, p, t, loopOff);
|
|
113
|
+
const axis = applyTwist(applyTwist(applyTwist(vec3(0, 1, 0), twists[0]), twists[1]), twists[2]);
|
|
114
|
+
return {
|
|
115
|
+
hit,
|
|
116
|
+
pos: p.add(axis.mul(hit.disp))
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
const main = bump(ws.pos, ws.twists);
|
|
120
|
+
pointerFall = varying(main.hit.fall, "vPointerFall");
|
|
121
|
+
pos = main.pos;
|
|
122
|
+
if (nb) {
|
|
123
|
+
posU = bump(nb.wsU.pos, nb.wsU.twists).pos;
|
|
124
|
+
posV = bump(nb.wsV.pos, nb.wsV.twists).pos;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (nb && posU && posV) {
|
|
128
|
+
const tU = modelWorldMatrix.mul(vec4(posU.sub(pos).mul(sign(nb.stepU)), 0)).xyz;
|
|
129
|
+
const tV = modelWorldMatrix.mul(vec4(posV.sub(pos).mul(sign(nb.stepV)), 0)).xyz;
|
|
130
|
+
const n = cross(tU, tV).toVar("vtxNormal");
|
|
131
|
+
vertexNormal = varying(n.div(max(length(n), 1e-9)), "vNormal");
|
|
132
|
+
}
|
|
133
|
+
return pos;
|
|
87
134
|
})();
|
|
88
135
|
const rawClip = varying(cameraProjectionMatrix.mul(cameraViewMatrix).mul(modelWorldMatrix).mul(vec4(material.positionNode, 1)), "vClipPosition");
|
|
89
136
|
const clipZ = flags.webgpuClipZ ? rawClip.z.mul(2).sub(1) : rawClip.z;
|
|
90
|
-
|
|
137
|
+
const ndc = rawClip.xy.div(max(rawClip.w, 1e-6)).mul(.5).add(.5);
|
|
138
|
+
const fragment = (flags.layerPass ? buildGlassLayerFragment(u, flags, ndc) : flags.theme === "wireframe" ? buildWireframeFragment(u, flags, clipZ, () => pointerFall, ndc) : flags.theme === "glass" ? buildGlassFragment(u, flags, ndc, () => vertexNormal) : buildSolidFragment(u, flags, clipZ, () => pointerFall, ndc)).toVar("fragOut");
|
|
139
|
+
material.outputNode = flags.premultiplied ? vec4(fragment.rgb.mul(fragment.a), fragment.a) : fragment;
|
|
91
140
|
return material;
|
|
92
141
|
}
|
|
93
142
|
/** The wireframe thin-line theme: colour carved into fine lengthwise strands, faded by depth. */
|
|
94
|
-
function buildWireframeFragment(u, flags, clipZ,
|
|
143
|
+
function buildWireframeFragment(u, flags, clipZ, getPointerFall, ndc) {
|
|
95
144
|
return Fn(() => {
|
|
145
|
+
const pointerFall = getPointerFall();
|
|
146
|
+
if (flags.pointerFx && !pointerFall) throw new Error("wave: the pointer varying was not created before the fragment built");
|
|
96
147
|
const vUv = uv();
|
|
148
|
+
if (flags.dissolve) Discard(dissolved(u, vUv, ndc));
|
|
97
149
|
const color = applyColorGrade(u, waveBaseColor(u, vUv)).toVar("lineColor");
|
|
98
150
|
if (pointerFall) {
|
|
99
151
|
color.assign(hueShift(color, radians(u.uPointerHue).mul(pointerFall)));
|
|
100
152
|
color.mulAssign(float(1).add(u.uPointerLighten.mul(pointerFall)));
|
|
101
153
|
}
|
|
102
154
|
const dy = dFdy(vUv).toVar();
|
|
103
|
-
const lineThickness = u.uLineThickness.mul(pow(abs(dy.x.mul(
|
|
155
|
+
const lineThickness = u.uLineThickness.mul(pow(abs(dy.x.mul(1232)), u.uLineDerivativePower)).toVar("lineThickness");
|
|
104
156
|
if (pointerFall) lineThickness.mulAssign(clamp(float(1).sub(u.uPointerThin.mul(pointerFall)), 0, 1));
|
|
157
|
+
const lineRate = u.uLineAmount.mul(fwidth(vUv.x)).toVar("lineRate");
|
|
158
|
+
if (flags.lineLight) {
|
|
159
|
+
const vWorldPos = positionWorld;
|
|
160
|
+
const vViewDir = cameraPosition.sub(vWorldPos).toVar("lineViewDir");
|
|
161
|
+
const n = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos))).toVar("lineN");
|
|
162
|
+
const vd = normalize(vViewDir).toVar("lineV");
|
|
163
|
+
If(dot(n, vd).lessThan(0), () => {
|
|
164
|
+
n.assign(n.negate());
|
|
165
|
+
});
|
|
166
|
+
{
|
|
167
|
+
const gu = vec2(dFdx(vUv.x), dFdy(vUv.x)).toVar("lineGu");
|
|
168
|
+
const gg = dot(gu, gu).toVar("lineGg");
|
|
169
|
+
If(gg.greaterThan(1e-12), () => {
|
|
170
|
+
const across = dFdx(vWorldPos).mul(gu.x).add(dFdy(vWorldPos).mul(gu.y)).div(gg).toVar("lineAcross");
|
|
171
|
+
across.assign(normalize(across.sub(n.mul(dot(across, n)))));
|
|
172
|
+
const sAcross = clamp(sin(vUv.x.mul(u.uLineAmount)).div(max(lineThickness, 1e-4)), -1, 1);
|
|
173
|
+
n.assign(normalize(n.add(across.mul(sAcross).mul(1.2))));
|
|
174
|
+
If(dot(n, vd).lessThan(0), () => {
|
|
175
|
+
n.assign(n.negate());
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
const facing = abs(dot(n, vd)).toVar("lineFacing");
|
|
180
|
+
const lit = color.mul(mix(float(.08), float(1), facing)).toVar("lineLit");
|
|
181
|
+
Loop({
|
|
182
|
+
start: 0,
|
|
183
|
+
end: 8,
|
|
184
|
+
type: "int"
|
|
185
|
+
}, ({ i }) => {
|
|
186
|
+
If(float(i).greaterThanEqual(float(u.uNumLights)), () => {
|
|
187
|
+
Break();
|
|
188
|
+
});
|
|
189
|
+
const l = normalize(u.uLightPos.el(i).sub(vWorldPos)).toVar();
|
|
190
|
+
const lc = u.uLightColor.el(i).mul(u.uLightIntensity.el(i)).toVar();
|
|
191
|
+
lit.addAssign(color.mul(max(dot(n, l), 0)).mul(lc).mul(.5));
|
|
192
|
+
lit.addAssign(lc.mul(pow(max(dot(n, normalize(l.add(vd))), 0), 48)).mul(1.5));
|
|
193
|
+
});
|
|
194
|
+
lit.mulAssign(clamp(u.uAmbient, 0, 1).add(.55));
|
|
195
|
+
color.assign(mix(color, lit, clamp(u.uLineLight, 0, 1)));
|
|
196
|
+
}
|
|
105
197
|
const a = smoothstep(lineThickness, 0, abs(sin(vUv.x.mul(u.uLineAmount)))).toVar("lineA");
|
|
198
|
+
const rungRate = float(0).toVar("rungRate");
|
|
199
|
+
const dutyRung = float(0).toVar("dutyRung");
|
|
106
200
|
if (flags.rungs) {
|
|
107
|
-
|
|
108
|
-
|
|
201
|
+
rungRate.assign(u.uRungAmount.mul(fwidth(vUv.y)));
|
|
202
|
+
const rungT = u.uRungThickness.mul(rungRate).toVar("rungT");
|
|
203
|
+
a.assign(max(a, smoothstep(rungT, 0, abs(sin(vUv.y.mul(u.uRungAmount))))));
|
|
204
|
+
dutyRung.assign(asin(clamp(rungT.mul(.5), 0, 1)).mul(.63661977));
|
|
109
205
|
}
|
|
110
|
-
|
|
111
|
-
|
|
206
|
+
if (flags.lineSharp) {
|
|
207
|
+
const aaRate = max(lineRate, rungRate).mul(.5);
|
|
208
|
+
a.assign(clamp(a.sub(.5).div(max(float(1).sub(u.uLineSharpness), aaRate.mul(1.4))).add(.5), 0, 1));
|
|
209
|
+
}
|
|
210
|
+
a.assign(mix(a, max(a, dutyRung), smoothstep(1.2, 3, rungRate)));
|
|
211
|
+
const depthFade = clamp(clipZ.mul(6), 0, 1).mul(u.uLineDepthFade);
|
|
212
|
+
const cov = a.mul(float(1).sub(depthFade)).toVar("lineCov");
|
|
213
|
+
const feather = flags.edgeFeather ? u.uEdgeFeather : float(.1);
|
|
214
|
+
cov.mulAssign(smoothstep(0, feather, vUv.y).mul(float(1).sub(smoothstep(float(1).sub(feather), 1, vUv.y))));
|
|
215
|
+
if (flags.lineClearGaps) {
|
|
216
|
+
const gapA = float(1).sub(cov).mul(u.uLineGapOpacity).toVar("lineGapA");
|
|
217
|
+
const outA = cov.add(gapA).toVar("lineOutA");
|
|
218
|
+
Discard(outA.lessThanEqual(.002));
|
|
219
|
+
const straight = color.mul(cov).add(u.uClearColor.mul(gapA)).div(outA).toVar("lineStraight");
|
|
220
|
+
return vec4(select(u.uSquared.greaterThan(.5), straight.mul(straight), straight), u.uOpacity.mul(outA));
|
|
221
|
+
}
|
|
222
|
+
const faded = mix(u.uClearColor, color, cov).toVar("lineFaded");
|
|
112
223
|
return vec4(select(u.uSquared.greaterThan(.5), faded.mul(faded), faded), u.uOpacity);
|
|
113
224
|
})();
|
|
114
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* The glass theme's twin. Structurally the same as the GLSL: bend the backdrop along the surface
|
|
228
|
+
* normal, absorb the wave's own palette over a thickness, then fresnel / rim / specular on top.
|
|
229
|
+
*
|
|
230
|
+
* Three TSL-specific departures, all forced:
|
|
231
|
+
* - The surface normal is the interpolated vertex-stage `vNormal`, as in the GLSL, and the caustic
|
|
232
|
+
* is the screen-space Jacobian of the offset — so there is no normal pass to twin.
|
|
233
|
+
* - The frost samples are unrolled in JavaScript. A Loop carrying a toVar accumulator inside
|
|
234
|
+
* another control block does not survive node generation — the accumulator gets hoisted out of
|
|
235
|
+
* scope — and building eleven taps as straight-line graph avoids the question entirely.
|
|
236
|
+
* - Screen position comes from the clip vector (the `ndc` the dissolve already uses), NOT from
|
|
237
|
+
* `screenUV`, which is top-down here while a clip-derived UV is y-up. Mixing the two is the
|
|
238
|
+
* classic way to send a sample the wrong way and get banding no one can explain.
|
|
239
|
+
*/
|
|
240
|
+
function buildGlassFragment(u, flags, ndc, getVertexNormal) {
|
|
241
|
+
return Fn(() => {
|
|
242
|
+
const vUv = uv();
|
|
243
|
+
if (flags.dissolve) Discard(dissolved(u, vUv, ndc));
|
|
244
|
+
const vWorldPos = positionWorld;
|
|
245
|
+
const vertexNormal = getVertexNormal();
|
|
246
|
+
if (flags.vertexNormal && !vertexNormal) throw new Error("glass: the vertex normal varying was not created before the fragment built");
|
|
247
|
+
const V = normalize(u.uViewAxis).toVar("glassV");
|
|
248
|
+
const rawN = (vertexNormal ?? cross(dFdx(vWorldPos), dFdy(vWorldPos))).toVar("glassRawN");
|
|
249
|
+
const shadingNormal = (raw, pos, tag) => {
|
|
250
|
+
const len = length(raw).toVar(`glassNLen${tag}`);
|
|
251
|
+
const n0 = select(len.greaterThan(1e-6), raw.div(len), V).toVar(`glassN0${tag}`);
|
|
252
|
+
const flat = select(dot(n0, V).lessThan(0), n0.negate(), n0).toVar(`glassFlatN${tag}`);
|
|
253
|
+
const n = flat.toVar(`glassN${tag}`);
|
|
254
|
+
If(u.uGlassRipple.greaterThan(.001), () => {
|
|
255
|
+
const ph = u.uTime.mul(u.uGlassFlow);
|
|
256
|
+
const k1 = vec3(1, .62, .31);
|
|
257
|
+
const k2 = vec3(-.54, 1.13, .47);
|
|
258
|
+
const k3 = vec3(.36, -.82, 1.07);
|
|
259
|
+
const k4 = vec3(-1.18, -.33, .72);
|
|
260
|
+
const g = k1.mul(cos(dot(pos, k1).mul(u.uGlassRippleScale).add(ph))).add(k2.mul(cos(dot(pos, k2).mul(u.uGlassRippleScale).sub(ph.mul(2)).add(1.7))).mul(.65)).add(k3.mul(cos(dot(pos, k3).mul(u.uGlassRippleScale).add(ph.mul(3)).add(3.9))).mul(.42)).add(k4.mul(cos(dot(pos, k4).mul(u.uGlassRippleScale).sub(ph).add(2.6))).mul(.55));
|
|
261
|
+
n.assign(normalize(n.add(g.mul(u.uGlassRipple).mul(.16))));
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
N: n,
|
|
265
|
+
flatN: flat
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
const glassOffset = (raw, pos, tag) => {
|
|
269
|
+
const { N: n, flatN: flat } = shadingNormal(raw, pos, tag);
|
|
270
|
+
const r = pow(float(1).sub(abs(dot(n, V))), max(u.uGlassRimPower, float(.001)));
|
|
271
|
+
return n.xy.sub(flat.xy).mul(2).add(n.xy).mul(-1).mul(mix(r, float(1), u.uGlassRipple.mul(.25))).mul(u.uGlassStrength);
|
|
272
|
+
};
|
|
273
|
+
const { N, flatN } = shadingNormal(rawN, vWorldPos, "");
|
|
274
|
+
const ndv = clamp(abs(dot(N, V)), .02, 1).toVar("glassNdv");
|
|
275
|
+
const rim = pow(float(1).sub(abs(dot(N, V))), max(u.uGlassRimPower, float(.001))).toVar();
|
|
276
|
+
const sUv = ndc.toVar("glassSUv");
|
|
277
|
+
const texUv = vec2(sUv.x, float(1).sub(sUv.y)).toVar("glassTexUv");
|
|
278
|
+
const dir = N.xy.sub(flatN.xy).mul(2).add(N.xy).mul(-1).toVar("glassDir");
|
|
279
|
+
If(u.uGlassFusion.greaterThan(.001), () => {
|
|
280
|
+
const g = vec2(9, 9).div(u.uResolution);
|
|
281
|
+
const fieldAt = (p) => u.uLayers.sample(p).r;
|
|
282
|
+
const grad = vec2(fieldAt(texUv.add(vec2(g.x, 0))).sub(fieldAt(texUv.sub(vec2(g.x, 0)))), fieldAt(texUv.sub(vec2(0, g.y))).sub(fieldAt(texUv.add(vec2(0, g.y))))).toVar("glassGrad");
|
|
283
|
+
If(dot(grad, grad).greaterThan(1e-8), () => {
|
|
284
|
+
dir.assign(mix(dir, normalize(grad), clamp(u.uGlassFusion, 0, 1)));
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
const off = dir.mul(mix(rim, float(1), u.uGlassRipple.mul(.25))).mul(u.uGlassStrength).toVar("glassOffPx").div(max(u.uResolution, vec2(1, 1))).toVar("glassOff");
|
|
288
|
+
const texOff = vec2(off.x, off.y.negate()).toVar("glassTexOff");
|
|
289
|
+
const backdropAt = (p) => u.uBackdrop.sample(p).rgb;
|
|
290
|
+
const uvR = texUv.add(texOff.mul(float(1).add(u.uGlassChroma.mul(.2)))).toVar("glassUvR");
|
|
291
|
+
const uvG = texUv.add(texOff.mul(float(1).add(u.uGlassChroma.mul(.1)))).toVar("glassUvG");
|
|
292
|
+
const uvB = texUv.add(texOff).toVar("glassUvB");
|
|
293
|
+
const col = vec3(backdropAt(uvR).r, backdropAt(uvG).g, backdropAt(uvB).b).toVar("glassCol");
|
|
294
|
+
If(u.uGlassCaustic.greaterThan(.001), () => {
|
|
295
|
+
const dNx = dFdx(rawN).mul(3).toVar("glassDNx");
|
|
296
|
+
const dNy = dFdy(rawN).mul(3).toVar("glassDNy");
|
|
297
|
+
const dPx = dFdx(vWorldPos).mul(3).toVar("glassDPx");
|
|
298
|
+
const dPy = dFdy(vWorldPos).mul(3).toVar("glassDPy");
|
|
299
|
+
const dOdx = glassOffset(rawN.add(dNx), vWorldPos.add(dPx), "Cxp").sub(glassOffset(rawN.sub(dNx), vWorldPos.sub(dPx), "Cxm")).div(6).toVar("glassDOdx");
|
|
300
|
+
const dOdy = glassOffset(rawN.add(dNy), vWorldPos.add(dPy), "Cyp").sub(glassOffset(rawN.sub(dNy), vWorldPos.sub(dPy), "Cym")).div(6).toVar("glassDOdy");
|
|
301
|
+
const detJ = float(1).add(dOdx.x).mul(float(1).add(dOdy.y)).sub(dOdy.x.mul(dOdx.y));
|
|
302
|
+
const gain = clamp(float(1).div(max(abs(detJ), float(.12))), 0, 6);
|
|
303
|
+
col.mulAssign(mix(float(1), gain, clamp(u.uGlassCaustic, 0, 1)));
|
|
304
|
+
});
|
|
305
|
+
const frostRadius = u.uGlassFrost.mul(u.uGlassFrost).mul(46).toVar("glassFrostRadius");
|
|
306
|
+
If(frostRadius.greaterThan(.5), () => {
|
|
307
|
+
const r = frostRadius.div(max(u.uResolution, vec2(1, 1)));
|
|
308
|
+
const rot = sin(dot(ndc.mul(u.uResolution), vec2(12.9898, 78.233))).mul(43758.5453).fract().mul(6.2831853);
|
|
309
|
+
const tap = (i) => {
|
|
310
|
+
const t = (i + .5) / 11;
|
|
311
|
+
const a = rot.add(i * 2.399963);
|
|
312
|
+
const o = vec2(cos(a), sin(a)).mul(r).mul(Math.sqrt(t));
|
|
313
|
+
return backdropAt(uvG.add(o));
|
|
314
|
+
};
|
|
315
|
+
let sum = tap(0);
|
|
316
|
+
for (let i = 1; i < 11; i++) sum = sum.add(tap(i));
|
|
317
|
+
col.assign(mix(col, sum.div(11), clamp(u.uGlassFrost, 0, 1)));
|
|
318
|
+
});
|
|
319
|
+
const layers = max(u.uLayers.sample(texUv).r.mul(8), float(1)).toVar("glassLayers");
|
|
320
|
+
const chord = float(2).mul(u.uGlassPath).mul(pow(ndv, .4)).mul(float(1).add(u.uGlassLayerGain.mul(layers.sub(1)))).toVar("glassChord");
|
|
321
|
+
const lit = applyColorGrade(u, waveBaseColor(u, vUv)).toVar("glassLit");
|
|
322
|
+
const hue = lit.div(max(max(lit.r, max(lit.g, lit.b)), float(.001))).toVar("glassHue");
|
|
323
|
+
const sigma = u.uGlassDensity.mul(float(1).sub(hue.mul(.9)));
|
|
324
|
+
const chordRGB = chord.mul(vec3(float(1).add(u.uGlassChroma.mul(.12)), 1, float(1).sub(u.uGlassChroma.mul(.12))));
|
|
325
|
+
col.mulAssign(mix(vec3(1, 1, 1), exp(sigma.mul(chordRGB).mul(-1)), clamp(u.uGlassTint, 0, 1)));
|
|
326
|
+
const s2 = float(1).sub(ndv.mul(ndv)).div(max(u.uGlassIor.mul(u.uGlassIor), float(1e-4)));
|
|
327
|
+
const cosT = sqrt(max(float(1).sub(s2), float(0)));
|
|
328
|
+
const phase = vec3(650, 550, 440);
|
|
329
|
+
const film = mix(vec3(1, 1, 1), float(.5).add(cos(float(6.2831853).mul(float(2).mul(u.uGlassIor).mul(u.uGlassFilmNm).mul(cosT)).div(phase)).mul(.5)), clamp(u.uGlassIrid, 0, 1)).toVar("glassFilm");
|
|
330
|
+
const f0 = pow(u.uGlassIor.sub(1).div(u.uGlassIor.add(1)), float(2));
|
|
331
|
+
const F = f0.add(float(1).sub(f0).mul(pow(float(1).sub(ndv), float(5))));
|
|
332
|
+
col.assign(mix(col, mix(u.uClearColor, vec3(1, 1, 1), .35).mul(film), F.mul(float(.18).add(u.uGlassIrid.mul(.4)))));
|
|
333
|
+
col.assign(mix(col, film, float(1).sub(ndv).smoothstep(mix(float(.62), float(.42), u.uGlassIrid), float(1)).mul(u.uGlassRim)));
|
|
334
|
+
col.mulAssign(float(1).sub(float(1).sub(ndv).smoothstep(.62, .86).mul(.1)));
|
|
335
|
+
const KEY = normalize(vec3(-.3, .86, .42));
|
|
336
|
+
const KEY_FILL = normalize(vec3(.42, .16, .89));
|
|
337
|
+
const mirror = V.mul(-1).sub(N.mul(dot(V.mul(-1), N).mul(2))).toVar("glassMirror");
|
|
338
|
+
const lobe = pow(max(dot(mirror, KEY), float(0)), float(40)).add(pow(max(dot(mirror, KEY_FILL), float(0)), float(40)).mul(.55)).toVar("glassLobe");
|
|
339
|
+
const spec = lobe.add(rim.mul(.25)).mul(u.uGlassSpec).toVar("glassSpec");
|
|
340
|
+
col.addAssign(lobe.mul(u.uGlassSpec).mul(.35).mul(film));
|
|
341
|
+
const luma = dot(col, vec3(.299, .587, .114)).toVar("glassLuma");
|
|
342
|
+
const darkBlend = luma.smoothstep(.25, .7);
|
|
343
|
+
col.assign(max(mix(col.add(spec), col.mul(float(1).sub(spec)), darkBlend), vec3(0, 0, 0)));
|
|
344
|
+
col.addAssign(float(.5).sub(luma).mul(u.uGlassVibrancy));
|
|
345
|
+
const fade = clamp(u.uOpacity, 0, 1).toVar("glassFade");
|
|
346
|
+
If(u.uEdgeFeather.greaterThan(0), () => {
|
|
347
|
+
const e = min(min(vUv.x, float(1).sub(vUv.x)), min(vUv.y, float(1).sub(vUv.y)));
|
|
348
|
+
fade.mulAssign(e.smoothstep(float(0), u.uEdgeFeather));
|
|
349
|
+
});
|
|
350
|
+
col.assign(mix(backdropAt(texUv), col, fade));
|
|
351
|
+
return vec4(clamp(col, 0, 1), 1);
|
|
352
|
+
})();
|
|
353
|
+
}
|
|
354
|
+
/** The glass layer-count companion: coverage only, on the wave's own vertex stage. */
|
|
355
|
+
function buildGlassLayerFragment(u, flags, ndc) {
|
|
356
|
+
return Fn(() => {
|
|
357
|
+
if (flags.dissolve) Discard(dissolved(u, uv(), ndc));
|
|
358
|
+
return vec4(.125, .125, .125, 1);
|
|
359
|
+
})();
|
|
360
|
+
}
|
|
115
361
|
/** The solid surface theme. */
|
|
116
|
-
function buildSolidFragment(u, flags, clipZ,
|
|
362
|
+
function buildSolidFragment(u, flags, clipZ, getPointerFall, ndc) {
|
|
117
363
|
return Fn(() => {
|
|
364
|
+
const pointerFall = getPointerFall();
|
|
365
|
+
if (flags.pointerFx && !pointerFall) throw new Error("wave: the pointer varying was not created before the fragment built");
|
|
118
366
|
const vUv = uv();
|
|
367
|
+
if (flags.dissolve) Discard(dissolved(u, vUv, ndc));
|
|
119
368
|
const vWorldPos = positionWorld;
|
|
120
369
|
const vViewDir = cameraPosition.sub(vWorldPos).toVar("vViewDir");
|
|
121
370
|
const crease = float(0).toVar("crease");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"waveMaterial.js","names":["tabs"],"sources":["../../../src/renderer/tsl/waveMaterial.ts"],"sourcesContent":["/**\n * The wave's `NodeMaterial` — the TSL port of `vertexShader` + `fragmentShader` / `lineFragmentShader`.\n *\n * The GLSL `#ifdef` variants become JS flags, so each material builds exactly the graph it needs\n * instead of relying on a define set and a program cache key.\n *\n * Two deliberate differences from the GLSL, both because the node pipeline already does the work:\n * - No `PREMULTIPLIED_ALPHA` block. `NodeMaterial.setupOutput()` calls `setupPremultipliedAlpha()`\n * when `material.premultipliedAlpha` is set, so premultiplying here would double-apply it.\n * - No output colour-space conversion. The material writes LINEAR, exactly as the GLSL does; the\n * conversion happens once at the end of the post chain, matching `OutputPass` on the WebGL path.\n */\nimport { NodeMaterial } from \"three/webgpu\";\nimport {\n Fn,\n varying,\n vec2,\n vec3,\n vec4,\n float,\n uv,\n positionLocal,\n positionWorld,\n cameraPosition,\n cameraProjectionMatrix,\n cameraViewMatrix,\n modelWorldMatrix,\n screenUV,\n dFdx,\n dFdy,\n fwidth,\n abs as tabs,\n sin,\n cos,\n pow,\n max,\n clamp,\n mix,\n smoothstep,\n cross,\n dot,\n normalize,\n radians,\n Loop,\n If,\n Break,\n select,\n} from \"three/tsl\";\nimport { MAX_LIGHTS, MAX_NOISE_BANDS } from \"../../config/model\";\nimport { simplexNoise, grainHash } from \"./noise\";\nimport { waveShape, applyTwist, type WaveShapeFlags } from \"./waveShape\";\nimport { applyColorGrade, waveBaseColor, hueShift, parabola, mapLinear } from \"./color\";\nimport { pointerField } from \"./pointerField\";\nimport type { FloatNode, Vec2Node, Vec3Node } from \"./types\";\nimport type { WaveTslUniforms } from \"./uniforms\";\n\nexport interface WaveMaterialFlags extends WaveShapeFlags {\n theme: \"solid\" | \"wireframe\";\n /** Pointer field: per-wave, config-only, so input never triggers a rebuild. */\n pointerFx: boolean;\n /** Click ripples, which nest inside the pointer field. */\n pointerRipples: boolean;\n depthTint: boolean;\n edgeFeather: boolean;\n rungs: boolean;\n /**\n * True when the active backend uses [0,1] clip Z (WebGPU) rather than [-1,1] (WebGL).\n *\n * `camera.coordinateSystem` changes what `projectionMatrix` produces — for an orthographic camera\n * the two differ by exactly `z_webgl = 2 * z_webgpu - 1`. The depth fade and depth tint consume\n * clip Z directly (`clamp(z * 6, 0, 1)`), so without this remap the wireframe theme and the tint\n * would silently render differently on each backend. WebGPURenderer can run either backend, so\n * this is read from the live renderer rather than assumed.\n */\n webgpuClipZ: boolean;\n}\n\n/** Linear + orbit time. Only the one selected by `loopMotion` is read, as in the GLSL. */\nfunction timeNodes(u: WaveTslUniforms, flags: WaveShapeFlags): { t: FloatNode; loopOff: Vec2Node } {\n if (!flags.loopMotion) {\n return { t: u.uTime.mul(u.uSpeed).add(u.uSeed), loopOff: vec2(0, 0) };\n }\n // Seamless loop: sample the noise on a circle of radius loopR at angle loopTheta — exactly\n // periodic with period uLoopSeconds, and the tangential speed matches the linear drift.\n const loopTheta = u.uTime.mul(float(6.28318530718).div(u.uLoopSeconds)).add(u.uSeed).toVar();\n const loopR = u.uSpeed.mul(u.uLoopSeconds).mul(0.159154943092); // = uSpeed·uLoopSeconds / (2π)\n return { t: float(0), loopOff: vec2(cos(loopTheta), sin(loopTheta)).mul(loopR) };\n}\n\n/**\n * Striations: subtle high-frequency simplex grain ADDED to the colour — colour-matched (weaker\n * where blue is high), only near folds, and concentrated toward the ends. Blends rather than\n * reading as hard lines. Noise bands override the params inside rectangular uv regions.\n */\nfunction surfaceStreaks(\n u: WaveTslUniforms,\n uvIn: Vec2Node,\n color: Vec3Node,\n crease: FloatNode,\n): Vec3Node {\n const strength = float(u.uFiberStrength).toVar(\"streakStrength\");\n const freq = float(u.uFiberCount).toVar(\"streakFreq\");\n const colorAtten = float(0.9).toVar(\"streakAtten\");\n const paraPow = float(3.0).toVar(\"streakPara\");\n\n Loop({ start: 0, end: MAX_NOISE_BANDS, type: \"int\" }, ({ i }) => {\n If(float(i).greaterThanEqual(float(u.uNumNoiseBands)), () => {\n Break();\n });\n const b = u.uNoiseBandBounds.el(i).toVar();\n const prm = u.uNoiseBandParams.el(i).toVar();\n const feather = max(prm.x, 1.0e-4).toVar();\n const blend = smoothstep(b.x.sub(feather), b.x, uvIn.x)\n .mul(float(1).sub(smoothstep(b.y, b.y.add(feather), uvIn.x)))\n .mul(smoothstep(b.z.sub(feather), b.z, uvIn.y))\n .mul(float(1).sub(smoothstep(b.w, b.w.add(feather), uvIn.y)));\n strength.assign(mix(strength, prm.y, blend));\n freq.assign(mix(freq, prm.z, blend));\n colorAtten.assign(mix(colorAtten, prm.w, blend));\n paraPow.assign(mix(paraPow, u.uNoiseBandParaPow.el(i), blend));\n });\n\n // The high frequency runs along uv.x (the folded WIDTH), packing many thin stripes across the\n // cross-section while uv.y is barely scaled, so each stretches into a fine LENGTHWISE fiber.\n const p = float(1).sub(parabola(uvIn.x, paraPow));\n const n0 = simplexNoise(vec2(uvIn.x.mul(0.1), uvIn.y.mul(0.5))).toVar();\n const n1raw = simplexNoise(\n vec2(uvIn.x.mul(freq.add(freq.mul(0.5).mul(n0))), uvIn.y.mul(4.0).mul(n0)),\n );\n const n1 = mapLinear(n1raw, -1, 1, 0, 1);\n return color.add(\n n1\n .mul(strength)\n .mul(float(1).sub(color.b.mul(colorAtten)))\n .mul(crease)\n .mul(p),\n );\n}\n\n/** Build one wave's material for the given flags. */\nexport function buildWaveMaterial(u: WaveTslUniforms, flags: WaveMaterialFlags): NodeMaterial {\n const material = new NodeMaterial();\n material.transparent = true;\n material.depthTest = true;\n material.depthWrite = true;\n material.side = 2; // THREE.DoubleSide\n\n // ---- Vertex ----\n // The pointer falloff is computed in the vertex stage alongside the displacement and carried to\n // the fragment, matching the GLSL's `varying float vPointerFall`.\n let pointerFall: FloatNode | null = null;\n\n material.positionNode = Fn(() => {\n const { t, loopOff } = timeNodes(u, flags);\n const ws = waveShape(u, flags, positionLocal, uv(), t, loopOff);\n if (!flags.pointerFx) return ws.pos;\n\n // Displace along the wave's own (post-twist) up-axis, weighted by a screen-space falloff around\n // the smoothed cursor. Everything here is ADDITIVE, so the shared path above is untouched.\n // Shared clip transform, computed once and reused for the cursor metric and the ribbon tangent\n // (the compiler is not guaranteed to CSE the triple product).\n const mvp = cameraProjectionMatrix.mul(cameraViewMatrix).mul(modelWorldMatrix).toVar(\"mvp\");\n const preClip = mvp.mul(vec4(ws.pos, 1.0)).toVar(\"preClip\");\n const hit = pointerField(\n u,\n { loopMotion: flags.loopMotion, ripples: flags.pointerRipples },\n preClip.xy.div(max(preClip.w, 1.0e-6)),\n mvp,\n ws.twists,\n ws.pos,\n t,\n loopOff,\n );\n pointerFall = varying(hit.fall, \"vPointerFall\");\n // Rotations are linear, so displacing the post-twist axis equals displacing pre-twist Y.\n const dispAxis = applyTwist(\n applyTwist(applyTwist(vec3(0, 1, 0), ws.twists[0]), ws.twists[1]),\n ws.twists[2],\n );\n return ws.pos.add(dispAxis.mul(hit.disp));\n })();\n\n // Clip-space depth, normalised to the WebGL [-1,1] convention the GLSL was written against.\n const rawClip = varying(\n cameraProjectionMatrix\n .mul(cameraViewMatrix)\n .mul(modelWorldMatrix)\n .mul(vec4(material.positionNode as unknown as Vec3Node, 1.0)),\n \"vClipPosition\",\n );\n const clipZ: FloatNode = flags.webgpuClipZ ? rawClip.z.mul(2).sub(1) : rawClip.z;\n\n // ---- Fragment ----\n material.outputNode =\n flags.theme === \"wireframe\"\n ? buildWireframeFragment(u, flags, clipZ, pointerFall)\n : buildSolidFragment(u, flags, clipZ, pointerFall);\n\n return material;\n}\n\n/** The wireframe thin-line theme: colour carved into fine lengthwise strands, faded by depth. */\nfunction buildWireframeFragment(\n u: WaveTslUniforms,\n flags: WaveMaterialFlags,\n clipZ: FloatNode,\n pointerFall: FloatNode | null,\n) {\n return Fn(() => {\n const vUv = uv();\n const color = applyColorGrade(u, waveBaseColor(u, vUv)).toVar(\"lineColor\");\n if (pointerFall) {\n color.assign(hueShift(color, radians(u.uPointerHue).mul(pointerFall)));\n color.mulAssign(float(1).add(u.uPointerLighten.mul(pointerFall)));\n }\n\n // Carve into fine lengthwise strands; thickness from the screen-space uv derivative.\n const dy = dFdy(vUv).toVar();\n const lineThickness = u.uLineThickness\n .mul(pow(tabs(dy.x.mul(u.uMaxWidth)), u.uLineDerivativePower))\n .toVar(\"lineThickness\");\n if (pointerFall) {\n // Strands taper to hairlines near the cursor.\n lineThickness.mulAssign(clamp(float(1).sub(u.uPointerThin.mul(pointerFall)), 0, 1));\n }\n const a = smoothstep(lineThickness, 0.0, tabs(sin(vUv.x.mul(u.uLineAmount)))).toVar(\"lineA\");\n\n if (flags.rungs) {\n // The same carve at constant uv.y, so this family runs ACROSS the ribbon where the one above\n // runs along it — together they read as a ladder. Width comes from fwidth(), not the\n // lengthwise term's dFdy(vUv).x, which is the derivative of the wrong axis for this direction.\n const rung = tabs(sin(vUv.y.mul(u.uRungAmount)));\n a.assign(\n max(a, smoothstep(u.uRungThickness.mul(u.uRungAmount).mul(fwidth(vUv.y)), 0.0, rung)),\n );\n }\n\n // Depth fade: the wave recedes into the background colour with depth.\n const depthFade = clamp(clipZ.mul(6.0), 0, 1);\n const faded = mix(u.uClearColor, color, a.mul(float(1).sub(depthFade))).toVar(\"lineFaded\");\n // Deep \"squared\" look — composited, not replace-blended (see applyBlendMode).\n const out = select(u.uSquared.greaterThan(0.5), faded.mul(faded), faded);\n return vec4(out, u.uOpacity);\n })();\n}\n\n/** The solid surface theme. */\nfunction buildSolidFragment(\n u: WaveTslUniforms,\n flags: WaveMaterialFlags,\n clipZ: FloatNode,\n pointerFall: FloatNode | null,\n) {\n return Fn(() => {\n const vUv = uv();\n const vWorldPos = positionWorld;\n const vViewDir = cameraPosition.sub(vWorldPos).toVar(\"vViewDir\");\n\n // crease: a foreshortening / fold detector from the screen-space uv derivative. It drives BOTH\n // the roundness shading and where the streaks appear — this is what gives the wave its\n // thickness without any normal-based lighting.\n const crease = float(0).toVar(\"crease\");\n crease.assign(dFdy(vUv).y.mul(u.uResolution.y).mul(u.uCreaseLight));\n crease.assign(clamp(mapLinear(crease, -1, 1, 0, 1), 0, 1));\n crease.assign(pow(crease, u.uCreaseSharpness));\n crease.assign(clamp(smoothstep(0.0, u.uCreaseSoftness, crease), 0, 1));\n\n const col = waveBaseColor(u, vUv).toVar(\"col\");\n col.assign(surfaceStreaks(u, vUv, col, crease));\n col.assign(applyColorGrade(u, col));\n\n if (pointerFall) {\n // Local hue rotation + brightness lift near the cursor (both fade out with the falloff).\n col.assign(hueShift(col, radians(u.uPointerHue).mul(pointerFall)));\n col.mulAssign(float(1).add(u.uPointerLighten.mul(pointerFall)));\n }\n\n // Iridescence: a thin-film hue that shifts with view angle — grazing parts of the ribbon shift\n // most, so the colour flows as the ribbon curves.\n If(u.uIridescence.greaterThan(0.001), () => {\n const iridN = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos)));\n const iridFacing = tabs(dot(iridN, normalize(vViewDir)));\n col.assign(hueShift(col, float(1).sub(iridFacing).mul(u.uIridescence).mul(Math.PI)));\n });\n\n // Sheen: lift the flat (low-crease) areas toward white. Pose-dependent, so kept gentle.\n col.addAssign(float(1).sub(crease).mul(0.25).mul(u.uSheen));\n\n // Pose-robust roundness: shade by the camera-facing ratio of the derivative surface normal so\n // the ribbon reads as a rounded solid from any angle.\n If(u.uRoundness.greaterThan(0.001), () => {\n const volN = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos)));\n const facing = tabs(dot(volN, normalize(vViewDir))).toVar(\"facing\");\n col.mulAssign(mix(float(1).sub(u.uRoundness.mul(0.6)), 1.0, facing));\n col.addAssign(smoothstep(0.65, 1.0, facing).mul(u.uRoundness).mul(0.18));\n });\n\n // Optional positionable lights — additive and gentle, on top of the base shading.\n If(u.uNumLights.greaterThan(0), () => {\n const n = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos))).toVar(\"lightN\");\n const vd = normalize(vViewDir).toVar(\"lightV\");\n If(dot(n, vd).lessThan(0.0), () => {\n n.assign(n.negate());\n });\n Loop({ start: 0, end: MAX_LIGHTS, type: \"int\" }, ({ i }) => {\n If(float(i).greaterThanEqual(float(u.uNumLights)), () => {\n Break();\n });\n const l = normalize(u.uLightPos.el(i).sub(vWorldPos)).toVar();\n const lc = u.uLightColor.el(i).mul(u.uLightIntensity.el(i)).toVar();\n const diff = max(dot(n, l), 0.0);\n const spec = pow(max(dot(n, normalize(l.add(vd))), 0.0), 28.0);\n col.addAssign(col.mul(diff).mul(lc).mul(0.16).add(lc.mul(spec).mul(0.1)));\n });\n });\n\n col.mulAssign(clamp(u.uAmbient, 0, 1).add(0.55)); // overall level; default 0.45 => x1.0\n\n if (flags.depthTint) {\n // Fade far fragments toward a colour so a multi-wave stack gains atmospheric separation.\n col.assign(mix(col, u.uDepthTintColor, clamp(clipZ.mul(6.0), 0, 1).mul(u.uDepthTint)));\n }\n\n If(u.uTexture.greaterThan(0.001), () => {\n col.mulAssign(float(1).add(grainHash(vUv.mul(850.0)).sub(0.5).mul(u.uTexture).mul(0.25)));\n });\n\n // Soft ribbon ENDS (fades on vUv.y, the length) + optional viewport-edge fade.\n const ribEdge = flags.edgeFeather\n ? smoothstep(0.0, u.uEdgeFeather, vUv.y).mul(\n float(1).sub(smoothstep(float(1).sub(u.uEdgeFeather), 1.0, vUv.y)),\n )\n : smoothstep(0.0, 0.1, vUv.y).mul(float(1).sub(smoothstep(0.9, 1.0, vUv.y)));\n const alpha = u.uOpacity.mul(ribEdge).toVar(\"alpha\");\n if (pointerFall) {\n alpha.mulAssign(clamp(float(1).sub(u.uPointerThin.mul(pointerFall)), 0, 1)); // local translucency\n }\n\n If(u.uEdgeFade.greaterThan(0.001), () => {\n const sc = screenUV;\n const vig = smoothstep(0.0, u.uEdgeFade, sc.x)\n .mul(float(1).sub(smoothstep(float(1).sub(u.uEdgeFade), 1.0, sc.x)))\n .mul(smoothstep(0.0, u.uEdgeFade, sc.y))\n .mul(float(1).sub(smoothstep(float(1).sub(u.uEdgeFade), 1.0, sc.y)));\n alpha.mulAssign(vig);\n });\n\n // Deep \"squared\" hero colour: square colour AND alpha so the soft ribbon edges keep the crisp\n // feather of the original squared-blend look, but composited (premultiplied) rather than\n // replace-blended, so they no longer punch holes.\n col.assign(clamp(col, vec3(0), vec3(1)));\n const squared = u.uSquared.greaterThan(0.5);\n return vec4(select(squared, col.mul(col), col), select(squared, alpha.mul(alpha), alpha));\n })();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"waveMaterial.js","names":["tabs"],"sources":["../../../src/renderer/tsl/waveMaterial.ts"],"sourcesContent":["/**\n * The wave's `NodeMaterial` — the TSL port of `vertexShader` + `fragmentShader` / `lineFragmentShader`.\n *\n * The GLSL `#ifdef` variants become JS flags, so each material builds exactly the graph it needs\n * instead of relying on a define set and a program cache key.\n *\n * One deliberate difference from the GLSL: no output colour-space conversion. The material writes\n * LINEAR, exactly as the GLSL does; the conversion happens once at the end of the post chain,\n * matching `OutputPass` on the WebGL path.\n *\n * Premultiplied alpha, by contrast, has to be done HERE. `NodeMaterial.setupOutput()` does call\n * `setupPremultipliedAlpha()` — but on its own `basicOutput`, which it then discards the moment a\n * custom `outputNode` is set (`if (isCustomOutput) resultNode = this.outputNode`). So a material\n * that writes its own output gets the premultiplied BLEND FACTORS without the premultiply, and\n * every partly transparent pixel composites too bright. The GLSL does the same multiply under the\n * PREMULTIPLIED_ALPHA define Three injects for it; this is that line.\n */\nimport { AdditiveBlending, NodeMaterial } from \"three/webgpu\";\nimport {\n Fn,\n varying,\n vec2,\n vec3,\n vec4,\n float,\n uv,\n positionLocal,\n positionWorld,\n cameraPosition,\n cameraProjectionMatrix,\n cameraViewMatrix,\n modelWorldMatrix,\n screenUV,\n dFdx,\n dFdy,\n attribute,\n sign,\n length,\n fwidth,\n abs as tabs,\n sin,\n cos,\n pow,\n max,\n min,\n clamp,\n mix,\n smoothstep,\n asin,\n cross,\n dot,\n exp,\n sqrt,\n normalize,\n radians,\n Loop,\n If,\n Break,\n select,\n Discard,\n} from \"three/tsl\";\nimport { MAX_LIGHTS, MAX_NOISE_BANDS } from \"../../config/model\";\nimport { simplexNoise, grainHash } from \"./noise\";\nimport { waveShape, applyTwist, type WaveShapeFlags, type WaveShapeResult } from \"./waveShape\";\nimport { applyColorGrade, waveBaseColor, hueShift, parabola, mapLinear } from \"./color\";\nimport { pointerField } from \"./pointerField\";\nimport { dissolved } from \"./dissolve\";\nimport type { FloatNode, Vec2Node, Vec3Node, Vec4Node } from \"./types\";\nimport type { WaveTslUniforms } from \"./uniforms\";\n\nexport interface WaveMaterialFlags extends WaveShapeFlags {\n theme: \"solid\" | \"wireframe\" | \"glass\";\n /** Build the glass LAYER-COUNT companion instead of the shaded material: the same vertex stage\n * on the same uniforms, with a fragment that only marks coverage. Drawn additively, depth off. */\n layerPass?: boolean;\n /** Interpolate a vertex-stage normal (glass): finite differences of the deformation at the two\n * baked grid neighbours. Smooth where the fragment's dFdx normal is constant per triangle. */\n vertexNormal: boolean;\n /** Pointer field: per-wave, config-only, so input never triggers a rebuild. */\n pointerFx: boolean;\n /** Click ripples, which nest inside the pointer field. */\n pointerRipples: boolean;\n depthTint: boolean;\n edgeFeather: boolean;\n rungs: boolean;\n /** Stripe hardening (wireframe only): compiled only when lineSharpness > 0, as in the GLSL. */\n lineSharp: boolean;\n /** The disintegration front: compiled only when the wave has one, as in the GLSL. */\n dissolve: boolean;\n /** Clear gaps (wireframe only): compiled only when lineGapOpacity < 1, as in the GLSL. */\n lineClearGaps: boolean;\n /** Lit / round strands (wireframe only): compiled only when lineLight > 0, as in the GLSL. */\n lineLight: boolean;\n /**\n * Premultiply the output, for the blend modes that ask for premultiplied factors (\"squared\" —\n * the default — and \"multiply\"). Mirrors `applyBlendMode`'s own rule; see the note at the top of\n * this file for why the node pipeline cannot do it for us.\n */\n premultiplied: boolean;\n /**\n * True when the active backend uses [0,1] clip Z (WebGPU) rather than [-1,1] (WebGL).\n *\n * `camera.coordinateSystem` changes what `projectionMatrix` produces — for an orthographic camera\n * the two differ by exactly `z_webgl = 2 * z_webgpu - 1`. The depth fade and depth tint consume\n * clip Z directly (`clamp(z * 6, 0, 1)`), so without this remap the wireframe theme and the tint\n * would silently render differently on each backend. WebGPURenderer can run either backend, so\n * this is read from the live renderer rather than assumed.\n */\n webgpuClipZ: boolean;\n}\n\n/** Linear + orbit time. Only the one selected by `loopMotion` is read, as in the GLSL. */\nfunction timeNodes(u: WaveTslUniforms, flags: WaveShapeFlags): { t: FloatNode; loopOff: Vec2Node } {\n if (!flags.loopMotion) {\n return { t: u.uTime.mul(u.uSpeed).add(u.uSeed), loopOff: vec2(0, 0) };\n }\n // Seamless loop: sample the noise on a circle of radius loopR at angle loopTheta — exactly\n // periodic with period uLoopSeconds, and the tangential speed matches the linear drift.\n const loopTheta = u.uTime.mul(float(6.28318530718).div(u.uLoopSeconds)).add(u.uSeed).toVar();\n const loopR = u.uSpeed.mul(u.uLoopSeconds).mul(0.159154943092); // = uSpeed·uLoopSeconds / (2π)\n return { t: float(0), loopOff: vec2(cos(loopTheta), sin(loopTheta)).mul(loopR) };\n}\n\n/**\n * Striations: subtle high-frequency simplex grain ADDED to the colour — colour-matched (weaker\n * where blue is high), only near folds, and concentrated toward the ends. Blends rather than\n * reading as hard lines. Noise bands override the params inside rectangular uv regions.\n */\nfunction surfaceStreaks(\n u: WaveTslUniforms,\n uvIn: Vec2Node,\n color: Vec3Node,\n crease: FloatNode,\n): Vec3Node {\n const strength = float(u.uFiberStrength).toVar(\"streakStrength\");\n const freq = float(u.uFiberCount).toVar(\"streakFreq\");\n const colorAtten = float(0.9).toVar(\"streakAtten\");\n const paraPow = float(3.0).toVar(\"streakPara\");\n\n Loop({ start: 0, end: MAX_NOISE_BANDS, type: \"int\" }, ({ i }) => {\n If(float(i).greaterThanEqual(float(u.uNumNoiseBands)), () => {\n Break();\n });\n const b = u.uNoiseBandBounds.el(i).toVar();\n const prm = u.uNoiseBandParams.el(i).toVar();\n const feather = max(prm.x, 1.0e-4).toVar();\n const blend = smoothstep(b.x.sub(feather), b.x, uvIn.x)\n .mul(float(1).sub(smoothstep(b.y, b.y.add(feather), uvIn.x)))\n .mul(smoothstep(b.z.sub(feather), b.z, uvIn.y))\n .mul(float(1).sub(smoothstep(b.w, b.w.add(feather), uvIn.y)));\n strength.assign(mix(strength, prm.y, blend));\n freq.assign(mix(freq, prm.z, blend));\n colorAtten.assign(mix(colorAtten, prm.w, blend));\n paraPow.assign(mix(paraPow, u.uNoiseBandParaPow.el(i), blend));\n });\n\n // The high frequency runs along uv.x (the folded WIDTH), packing many thin stripes across the\n // cross-section while uv.y is barely scaled, so each stretches into a fine LENGTHWISE fiber.\n const p = float(1).sub(parabola(uvIn.x, paraPow));\n const n0 = simplexNoise(vec2(uvIn.x.mul(0.1), uvIn.y.mul(0.5))).toVar();\n const n1raw = simplexNoise(\n vec2(uvIn.x.mul(freq.add(freq.mul(0.5).mul(n0))), uvIn.y.mul(4.0).mul(n0)),\n );\n const n1 = mapLinear(n1raw, -1, 1, 0, 1);\n return color.add(\n n1\n .mul(strength)\n .mul(float(1).sub(color.b.mul(colorAtten)))\n .mul(crease)\n .mul(p),\n );\n}\n\n/** Build one wave's material for the given flags. */\nexport function buildWaveMaterial(u: WaveTslUniforms, flags: WaveMaterialFlags): NodeMaterial {\n const material = new NodeMaterial();\n // Glass draws OPAQUE with depth write on, exactly as the GLSL twin does: its see-through is\n // sampled from the backdrop rather than blended, which is what keeps overlapping sheets free of\n // any sorting. Leaving it transparent here made the two backends disagree on alpha across the\n // whole sheet — the parity case caught it as a 0.43 alpha bias.\n material.transparent = flags.theme !== \"glass\";\n material.depthTest = true;\n material.depthWrite = true;\n material.side = 2; // THREE.DoubleSide\n if (flags.layerPass) {\n // Every layer over a pixel has to land and add up: no depth test, additive.\n material.transparent = true;\n material.blending = AdditiveBlending;\n material.depthTest = false;\n material.depthWrite = false;\n }\n\n // ---- Vertex ----\n // The pointer falloff is computed in the vertex stage alongside the displacement and carried to\n // the fragment, matching the GLSL's `varying float vPointerFall`.\n let pointerFall: FloatNode | null = null;\n // The vertex normal (glass): finite differences of the SAME deformation at the two baked grid\n // neighbours, so it follows every twist, path and pointer bump exactly and interpolates smoothly\n // across the mesh — where dFdx of the position is constant per triangle. See the GLSL.\n let vertexNormal: Vec3Node | null = null;\n\n material.positionNode = Fn(() => {\n const { t, loopOff } = timeNodes(u, flags);\n const ws = waveShape(u, flags, positionLocal, uv(), t, loopOff);\n const nb = flags.vertexNormal\n ? (() => {\n const aU = attribute(\"positionU\", \"vec4\") as unknown as Vec4Node;\n const aV = attribute(\"positionV\", \"vec4\") as unknown as Vec4Node;\n const stepU = aU.w;\n const stepV = aV.w;\n return {\n stepU,\n stepV,\n wsU: waveShape(u, flags, aU.xyz, uv().add(vec2(stepU, 0)), t, loopOff),\n wsV: waveShape(u, flags, aV.xyz, uv().add(vec2(0, stepV)), t, loopOff),\n };\n })()\n : null;\n let pos: Vec3Node = ws.pos;\n let posU: Vec3Node | null = nb ? nb.wsU.pos : null;\n let posV: Vec3Node | null = nb ? nb.wsV.pos : null;\n\n if (flags.pointerFx) {\n // Displace along the wave's own (post-twist) up-axis, weighted by a screen-space falloff\n // around the smoothed cursor. Everything here is ADDITIVE, so the shared path above is\n // untouched. Shared clip transform, computed once and reused for the cursor metric and the\n // ribbon tangent (the compiler is not guaranteed to CSE the triple product).\n const mvp = cameraProjectionMatrix.mul(cameraViewMatrix).mul(modelWorldMatrix).toVar(\"mvp\");\n const bump = (p: Vec3Node, twists: WaveShapeResult[\"twists\"]) => {\n const clip = mvp.mul(vec4(p, 1.0)).toVar();\n const hit = pointerField(\n u,\n { loopMotion: flags.loopMotion, ripples: flags.pointerRipples },\n clip.xy.div(max(clip.w, 1.0e-6)),\n mvp,\n twists,\n p,\n t,\n loopOff,\n );\n // Rotations are linear, so displacing the post-twist axis equals displacing pre-twist Y.\n const axis = applyTwist(\n applyTwist(applyTwist(vec3(0, 1, 0), twists[0]), twists[1]),\n twists[2],\n );\n return { hit, pos: p.add(axis.mul(hit.disp)) as Vec3Node };\n };\n const main = bump(ws.pos, ws.twists);\n pointerFall = varying(main.hit.fall, \"vPointerFall\");\n pos = main.pos;\n // The neighbours ride the same bump, each from its own clip position and twist frame, so the\n // normal follows the pointer's displacement rather than ignoring it.\n if (nb) {\n posU = bump(nb.wsU.pos, nb.wsU.twists).pos;\n posV = bump(nb.wsV.pos, nb.wsV.twists).pos;\n }\n }\n\n if (nb && posU && posV) {\n // Tangents transform covariantly, so the model matrix is right for any scale (a normal would\n // need its inverse transpose); sign(w) undoes the backward step the last row and column take.\n const tU = modelWorldMatrix.mul(vec4(posU.sub(pos).mul(sign(nb.stepU)), 0)).xyz;\n const tV = modelWorldMatrix.mul(vec4(posV.sub(pos).mul(sign(nb.stepV)), 0)).xyz;\n const n = cross(tU, tV).toVar(\"vtxNormal\");\n vertexNormal = varying(n.div(max(length(n), 1.0e-9)), \"vNormal\") as unknown as Vec3Node;\n }\n return pos;\n })();\n\n // Clip-space depth, normalised to the WebGL [-1,1] convention the GLSL was written against.\n const rawClip = varying(\n cameraProjectionMatrix\n .mul(cameraViewMatrix)\n .mul(modelWorldMatrix)\n .mul(vec4(material.positionNode as unknown as Vec3Node, 1.0)),\n \"vClipPosition\",\n );\n const clipZ: FloatNode = flags.webgpuClipZ ? rawClip.z.mul(2).sub(1) : rawClip.z;\n\n // ---- Fragment ----\n // This fragment's 0..1 screen position — read only by a screen-axis dissolve front. Taken from\n // the same clip vector the depth fade uses, so the two never disagree about where a fragment is.\n const ndc = rawClip.xy.div(max(rawClip.w, 1.0e-6)).mul(0.5).add(0.5);\n const fragment = (\n flags.layerPass\n ? buildGlassLayerFragment(u, flags, ndc)\n : flags.theme === \"wireframe\"\n ? buildWireframeFragment(u, flags, clipZ, () => pointerFall, ndc)\n : flags.theme === \"glass\"\n ? buildGlassFragment(u, flags, ndc, () => vertexNormal)\n : buildSolidFragment(u, flags, clipZ, () => pointerFall, ndc)\n ).toVar(\"fragOut\");\n material.outputNode = flags.premultiplied\n ? vec4(fragment.rgb.mul(fragment.a), fragment.a)\n : fragment;\n\n return material;\n}\n\n/** The wireframe thin-line theme: colour carved into fine lengthwise strands, faded by depth. */\nfunction buildWireframeFragment(\n u: WaveTslUniforms,\n flags: WaveMaterialFlags,\n clipZ: FloatNode,\n getPointerFall: () => FloatNode | null,\n ndc: Vec2Node,\n) {\n return Fn(() => {\n // Read at build time, not at the call: the positionNode closure that creates this varying\n // runs only when the vertex stage is built, after buildWaveMaterial has returned.\n const pointerFall = getPointerFall();\n if (flags.pointerFx && !pointerFall) {\n throw new Error(\"wave: the pointer varying was not created before the fragment built\");\n }\n const vUv = uv();\n // The disintegration front: drop the chunks it has already eaten, before any shading work.\n if (flags.dissolve) Discard(dissolved(u, vUv, ndc));\n const color = applyColorGrade(u, waveBaseColor(u, vUv)).toVar(\"lineColor\");\n if (pointerFall) {\n color.assign(hueShift(color, radians(u.uPointerHue).mul(pointerFall)));\n color.mulAssign(float(1).add(u.uPointerLighten.mul(pointerFall)));\n }\n\n // Carve into fine lengthwise strands; thickness from the screen-space uv derivative.\n const dy = dFdy(vUv).toVar();\n const lineThickness = u.uLineThickness\n .mul(pow(tabs(dy.x.mul(1232.0)), u.uLineDerivativePower)) // fixed reference width; see the GLSL\n .toVar(\"lineThickness\");\n if (pointerFall) {\n // Strands taper to hairlines near the cursor.\n lineThickness.mulAssign(clamp(float(1).sub(u.uPointerThin.mul(pointerFall)), 0, 1));\n }\n // Each family's per-pixel rate and the duty cycle it averages to — see the GLSL for why a\n // sub-pixel period has to fall back to a tone rather than be point-sampled.\n const lineRate = u.uLineAmount.mul(fwidth(vUv.x)).toVar(\"lineRate\");\n if (flags.lineLight) {\n // The line theme is otherwise UNLIT — a strand's colour comes from its uv alone, so it holds\n // one tone wherever the surface turns. This shades it with the same derivative normal and\n // lights the solid theme uses, and ROUNDS each strand so a highlight can run along one and not\n // its neighbour. See the LINE_LIGHT block in the GLSL for the full reasoning.\n const vWorldPos = positionWorld;\n const vViewDir = cameraPosition.sub(vWorldPos).toVar(\"lineViewDir\");\n const n = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos))).toVar(\"lineN\");\n const vd = normalize(vViewDir).toVar(\"lineV\");\n If(dot(n, vd).lessThan(0.0), () => {\n n.assign(n.negate());\n });\n {\n // World direction of increasing uv.x, by least squares from the screen derivatives — the\n // axis to tilt each strand about.\n const gu = vec2(dFdx(vUv.x), dFdy(vUv.x)).toVar(\"lineGu\");\n const gg = dot(gu, gu).toVar(\"lineGg\");\n If(gg.greaterThan(1.0e-12), () => {\n const across = dFdx(vWorldPos)\n .mul(gu.x)\n .add(dFdy(vWorldPos).mul(gu.y))\n .div(gg)\n .toVar(\"lineAcross\");\n across.assign(normalize(across.sub(n.mul(dot(across, n)))));\n const sAcross = clamp(\n sin(vUv.x.mul(u.uLineAmount)).div(max(lineThickness, 1.0e-4)),\n -1,\n 1,\n );\n n.assign(normalize(n.add(across.mul(sAcross).mul(1.2)))); // LINE_ROUND\n If(dot(n, vd).lessThan(0.0), () => {\n n.assign(n.negate());\n });\n });\n }\n const facing = tabs(dot(n, vd)).toVar(\"lineFacing\");\n const lit = color.mul(mix(float(0.08), float(1), facing)).toVar(\"lineLit\");\n Loop({ start: 0, end: MAX_LIGHTS, type: \"int\" }, ({ i }) => {\n If(float(i).greaterThanEqual(float(u.uNumLights)), () => {\n Break();\n });\n const l = normalize(u.uLightPos.el(i).sub(vWorldPos)).toVar();\n const lc = u.uLightColor.el(i).mul(u.uLightIntensity.el(i)).toVar();\n lit.addAssign(\n color\n .mul(max(dot(n, l), 0.0))\n .mul(lc)\n .mul(0.5),\n );\n lit.addAssign(\n lc.mul(pow(max(dot(n, normalize(l.add(vd))), 0.0), 48.0)).mul(1.5), // LINE_GLINT\n );\n });\n lit.mulAssign(clamp(u.uAmbient, 0, 1).add(0.55));\n color.assign(mix(color, lit, clamp(u.uLineLight, 0, 1)));\n }\n const a = smoothstep(lineThickness, 0.0, tabs(sin(vUv.x.mul(u.uLineAmount)))).toVar(\"lineA\");\n // No duty-cycle fallback for the LENGTHWISE family — see the GLSL: its threshold is itself\n // derivative-built, so keying one on it makes cross-backend agreement worse.\n const rungRate = float(0).toVar(\"rungRate\");\n const dutyRung = float(0).toVar(\"dutyRung\");\n if (flags.rungs) {\n // The same carve at constant uv.y, so this family runs ACROSS the ribbon where the one above\n // runs along it — together they read as a ladder. Width comes from fwidth(), not the\n // lengthwise term's dFdy(vUv).x, which is the derivative of the wrong axis for this direction.\n rungRate.assign(u.uRungAmount.mul(fwidth(vUv.y)));\n const rungT = u.uRungThickness.mul(rungRate).toVar(\"rungT\");\n a.assign(max(a, smoothstep(rungT, 0.0, tabs(sin(vUv.y.mul(u.uRungAmount))))));\n dutyRung.assign(asin(clamp(rungT.mul(0.5), 0, 1)).mul(0.63661977));\n }\n if (flags.lineSharp) {\n // Steepen the stripe about its own midpoint, which turns uLineThickness into a DUTY CYCLE and\n // this into the edge — see the LINE_SHARP block in the GLSL for why the soft ramp alone cannot\n // reach dense ink, why it is applied to the MERGED coverage, and why the floor is the analytic\n // stripe rate rather than fwidth() of that merged value.\n const aaRate = max(lineRate, rungRate).mul(0.5);\n a.assign(\n clamp(\n a\n .sub(0.5)\n .div(max(float(1).sub(u.uLineSharpness), aaRate.mul(1.4)))\n .add(0.5),\n 0,\n 1,\n ),\n );\n }\n // Sub-pixel rungs: fade to the tone those strands average to.\n a.assign(mix(a, max(a, dutyRung), smoothstep(1.2, 3.0, rungRate)));\n\n // Depth fade: the wave recedes into the background colour with depth.\n const depthFade = clamp(clipZ.mul(6.0), 0, 1).mul(u.uLineDepthFade);\n const cov = a.mul(float(1).sub(depthFade)).toVar(\"lineCov\");\n // Soft ribbon ENDS, as the solid theme fades them — see the GLSL for why a wireframe needs it.\n const feather = flags.edgeFeather ? u.uEdgeFeather : float(0.1);\n cov.mulAssign(\n smoothstep(0.0, feather, vUv.y).mul(\n float(1).sub(smoothstep(float(1).sub(feather), 1.0, vUv.y)),\n ),\n );\n if (flags.lineClearGaps) {\n // CLEAR GAPS: the gaps carry the page colour only as far as uLineGapOpacity and are otherwise\n // transparent, so the strands composite over whatever is really behind them rather than over a\n // flat card of page colour. See the LINE_CLEAR_GAPS block in the GLSL.\n const gapA = float(1).sub(cov).mul(u.uLineGapOpacity).toVar(\"lineGapA\");\n const outA = cov.add(gapA).toVar(\"lineOutA\");\n // A fully clear gap must not reach the depth buffer, or it occludes the wave behind it.\n Discard(outA.lessThanEqual(0.002));\n const straight = color.mul(cov).add(u.uClearColor.mul(gapA)).div(outA).toVar(\"lineStraight\");\n const outC = select(u.uSquared.greaterThan(0.5), straight.mul(straight), straight);\n return vec4(outC, u.uOpacity.mul(outA));\n }\n const faded = mix(u.uClearColor, color, cov).toVar(\"lineFaded\");\n // Deep \"squared\" look — composited, not replace-blended (see applyBlendMode).\n const out = select(u.uSquared.greaterThan(0.5), faded.mul(faded), faded);\n return vec4(out, u.uOpacity);\n })();\n}\n\n/**\n * The glass theme's twin. Structurally the same as the GLSL: bend the backdrop along the surface\n * normal, absorb the wave's own palette over a thickness, then fresnel / rim / specular on top.\n *\n * Three TSL-specific departures, all forced:\n * - The surface normal is the interpolated vertex-stage `vNormal`, as in the GLSL, and the caustic\n * is the screen-space Jacobian of the offset — so there is no normal pass to twin.\n * - The frost samples are unrolled in JavaScript. A Loop carrying a toVar accumulator inside\n * another control block does not survive node generation — the accumulator gets hoisted out of\n * scope — and building eleven taps as straight-line graph avoids the question entirely.\n * - Screen position comes from the clip vector (the `ndc` the dissolve already uses), NOT from\n * `screenUV`, which is top-down here while a clip-derived UV is y-up. Mixing the two is the\n * classic way to send a sample the wrong way and get banding no one can explain.\n */\nfunction buildGlassFragment(\n u: WaveTslUniforms,\n flags: WaveMaterialFlags,\n ndc: Vec2Node,\n getVertexNormal: () => Vec3Node | null,\n) {\n return Fn(() => {\n const vUv = uv();\n if (flags.dissolve) Discard(dissolved(u, vUv, ndc));\n const vWorldPos = positionWorld;\n // Read here, not at the call: TSL runs the positionNode closure that creates the varying only\n // when the vertex stage is BUILT, which is after buildWaveMaterial has returned. A value taken\n // at the call is always null — and the fallback below then compiled silently, faceted.\n const vertexNormal = getVertexNormal();\n if (flags.vertexNormal && !vertexNormal) {\n throw new Error(\"glass: the vertex normal varying was not created before the fragment built\");\n }\n\n // ORTHOGRAPHIC view axis, not a per-fragment ray to the eye — column 2 of the view matrix.\n const V = normalize(u.uViewAxis).toVar(\"glassV\");\n // The interpolated vertex normal, or the per-triangle one where none is compiled.\n const rawN = (vertexNormal ?? cross(dFdx(vWorldPos), dFdy(vWorldPos))).toVar(\"glassRawN\");\n // The shading normal: unit length, faced toward the viewer (the orientation the screen-derivative\n // normal always had, so a thin sheet bends the same way whichever face is in front), then\n // rippled. Shared with the caustic's finite differences below — see the GLSL twin. `tag` keeps\n // the declarations of the three evaluations apart.\n const shadingNormal = (raw: Vec3Node, pos: Vec3Node, tag: string) => {\n const len = length(raw).toVar(`glassNLen${tag}`);\n const n0 = select(len.greaterThan(1.0e-6), raw.div(len), V).toVar(`glassN0${tag}`);\n const flat = select(dot(n0, V).lessThan(0), n0.negate(), n0).toVar(`glassFlatN${tag}`);\n const n = flat.toVar(`glassN${tag}`);\n If(u.uGlassRipple.greaterThan(0.001), () => {\n // Four travelling waves added to the normal as a gradient — see the GLSL for why trig rather\n // than scrolled noise, and why the temporal frequencies are integer multiples.\n const ph = u.uTime.mul(u.uGlassFlow);\n const k1 = vec3(1.0, 0.62, 0.31);\n const k2 = vec3(-0.54, 1.13, 0.47);\n const k3 = vec3(0.36, -0.82, 1.07);\n const k4 = vec3(-1.18, -0.33, 0.72);\n const g = k1\n .mul(cos(dot(pos, k1).mul(u.uGlassRippleScale).add(ph)))\n .add(k2.mul(cos(dot(pos, k2).mul(u.uGlassRippleScale).sub(ph.mul(2)).add(1.7))).mul(0.65))\n .add(k3.mul(cos(dot(pos, k3).mul(u.uGlassRippleScale).add(ph.mul(3)).add(3.9))).mul(0.42))\n .add(k4.mul(cos(dot(pos, k4).mul(u.uGlassRippleScale).sub(ph).add(2.6))).mul(0.55));\n n.assign(normalize(n.add(g.mul(u.uGlassRipple).mul(0.16))));\n });\n return { N: n, flatN: flat };\n };\n // The refraction offset, in pixels, of the surface at (raw, pos) — before droplet fusion.\n const glassOffset = (raw: Vec3Node, pos: Vec3Node, tag: string): Vec2Node => {\n const { N: n, flatN: flat } = shadingNormal(raw, pos, tag);\n const r = pow(float(1).sub(tabs(dot(n, V))), max(u.uGlassRimPower, float(0.001)));\n const d = n.xy.sub(flat.xy).mul(2).add(n.xy).mul(-1);\n return d.mul(mix(r, float(1), u.uGlassRipple.mul(0.25))).mul(u.uGlassStrength);\n };\n const { N, flatN } = shadingNormal(rawN, vWorldPos, \"\");\n\n const ndv = clamp(tabs(dot(N, V)), 0.02, 1.0).toVar(\"glassNdv\");\n const rim = pow(float(1).sub(tabs(dot(N, V))), max(u.uGlassRimPower, float(0.001))).toVar();\n\n const sUv = ndc.toVar(\"glassSUv\");\n // The captures are sampled TOP-DOWN here while a clip-derived UV is y-up, so every texture\n // coordinate and every offset that rides one has its y flipped. These are not two flips that\n // cancel: getting it wrong does not mirror the image, it sends each sample to the wrong side of\n // the surface, which looked like the whole refraction had been rotated.\n const texUv = vec2(sUv.x, float(1).sub(sUv.y)).toVar(\"glassTexUv\");\n const dir = N.xy.sub(flatN.xy).mul(2).add(N.xy).mul(-1).toVar(\"glassDir\");\n If(u.uGlassFusion.greaterThan(0.001), () => {\n const g = vec2(9.0, 9.0).div(u.uResolution);\n const fieldAt = (p: Vec2Node): FloatNode => u.uLayers.sample(p).r;\n const grad = vec2(\n fieldAt(texUv.add(vec2(g.x, 0))).sub(fieldAt(texUv.sub(vec2(g.x, 0)))),\n // y runs the other way in the texture, so the difference is negated to stay a gradient in\n // the same space as the normal it is blended with.\n fieldAt(texUv.sub(vec2(0, g.y))).sub(fieldAt(texUv.add(vec2(0, g.y)))),\n ).toVar(\"glassGrad\");\n If(dot(grad, grad).greaterThan(1.0e-8), () => {\n dir.assign(mix(dir, normalize(grad), clamp(u.uGlassFusion, 0, 1)));\n });\n });\n const offPx = dir\n .mul(mix(rim, float(1), u.uGlassRipple.mul(0.25)))\n .mul(u.uGlassStrength)\n .toVar(\"glassOffPx\");\n const off = offPx.div(max(u.uResolution, vec2(1, 1))).toVar(\"glassOff\");\n const texOff = vec2(off.x, off.y.negate()).toVar(\"glassTexOff\");\n\n // Opaque capture: the sample IS the colour behind the glass, no alpha composite.\n const backdropAt = (p: Vec2Node): Vec3Node => u.uBackdrop.sample(p).rgb;\n\n const uvR = texUv.add(texOff.mul(float(1).add(u.uGlassChroma.mul(0.2)))).toVar(\"glassUvR\");\n const uvG = texUv.add(texOff.mul(float(1).add(u.uGlassChroma.mul(0.1)))).toVar(\"glassUvG\");\n const uvB = texUv.add(texOff).toVar(\"glassUvB\");\n const col = vec3(backdropAt(uvR).r, backdropAt(uvG).g, backdropAt(uvB).b).toVar(\"glassCol\");\n\n If(u.uGlassCaustic.greaterThan(0.001), () => {\n // One-pixel forward differences of the offset from dFdx of the INTERPOLATED normal and\n // position — linear across a triangle, so the two backends agree on them to the bit where\n // dFdx of the offset itself (coarse or fine, implementation-defined) split them at every\n // steep fold. See the GLSL twin.\n // A ±3 px central difference, the baseline the normal-buffer stencil had — see the GLSL.\n const dNx = dFdx(rawN).mul(3).toVar(\"glassDNx\");\n const dNy = dFdy(rawN).mul(3).toVar(\"glassDNy\");\n const dPx = dFdx(vWorldPos).mul(3).toVar(\"glassDPx\");\n const dPy = dFdy(vWorldPos).mul(3).toVar(\"glassDPy\");\n const dOdx = glassOffset(rawN.add(dNx), vWorldPos.add(dPx), \"Cxp\")\n .sub(glassOffset(rawN.sub(dNx), vWorldPos.sub(dPx), \"Cxm\"))\n .div(6)\n .toVar(\"glassDOdx\");\n const dOdy = glassOffset(rawN.add(dNy), vWorldPos.add(dPy), \"Cyp\")\n .sub(glassOffset(rawN.sub(dNy), vWorldPos.sub(dPy), \"Cym\"))\n .div(6)\n .toVar(\"glassDOdy\");\n const detJ = float(1).add(dOdx.x).mul(float(1).add(dOdy.y)).sub(dOdy.x.mul(dOdx.y));\n const gain = clamp(float(1).div(max(tabs(detJ), float(0.12))), 0, 6);\n col.mulAssign(mix(float(1), gain, clamp(u.uGlassCaustic, 0, 1)));\n });\n\n // Gated on the radius in PIXELS, as the GLSL is: under half a pixel the taps average back to\n // the sample they surround, and the default knob value sat there.\n const frostRadius = u.uGlassFrost.mul(u.uGlassFrost).mul(46.0).toVar(\"glassFrostRadius\");\n If(frostRadius.greaterThan(0.5), () => {\n const r = frostRadius.div(max(u.uResolution, vec2(1, 1)));\n // The SAME hash the GLSL uses. A different per-pixel rotation is not \"equivalent noise\": the\n // two backends then pick different sample sets and the frosted area disagrees everywhere at\n // once, which is pure mae with no bias to point at it.\n const p = ndc.mul(u.uResolution);\n const rot = sin(dot(p, vec2(12.9898, 78.233)))\n .mul(43758.5453)\n .fract()\n .mul(6.2831853);\n // One RGB gather at the green offset — see the GLSL for why not one per channel.\n const tap = (i: number): Vec3Node => {\n const t = (i + 0.5) / 11;\n const a = rot.add(i * 2.399963);\n const o = vec2(cos(a), sin(a)).mul(r).mul(Math.sqrt(t));\n return backdropAt(uvG.add(o));\n };\n let sum: Vec3Node = tap(0);\n for (let i = 1; i < 11; i++) sum = sum.add(tap(i));\n col.assign(mix(col, sum.div(11), clamp(u.uGlassFrost, 0, 1)));\n });\n\n // The material itself: the palette as transmitted light, absorbed over the sheet's thickness.\n const layers = max(u.uLayers.sample(texUv).r.mul(8), float(1)).toVar(\"glassLayers\");\n const chord = float(2)\n .mul(u.uGlassPath)\n .mul(pow(ndv, 0.4))\n .mul(float(1).add(u.uGlassLayerGain.mul(layers.sub(1))))\n .toVar(\"glassChord\");\n const lit = applyColorGrade(u, waveBaseColor(u, vUv)).toVar(\"glassLit\");\n const hue = lit.div(max(max(lit.r, max(lit.g, lit.b)), float(0.001))).toVar(\"glassHue\");\n const sigma = u.uGlassDensity.mul(float(1).sub(hue.mul(0.9)));\n const chordRGB = chord.mul(\n vec3(float(1).add(u.uGlassChroma.mul(0.12)), 1.0, float(1).sub(u.uGlassChroma.mul(0.12))),\n );\n col.mulAssign(mix(vec3(1, 1, 1), exp(sigma.mul(chordRGB).mul(-1)), clamp(u.uGlassTint, 0, 1)));\n\n // Thin film tints only what BOUNCES.\n const s2 = float(1)\n .sub(ndv.mul(ndv))\n .div(max(u.uGlassIor.mul(u.uGlassIor), float(1.0e-4)));\n const cosT = sqrt(max(float(1).sub(s2), float(0)));\n const phase = vec3(650.0, 550.0, 440.0);\n const film = mix(\n vec3(1, 1, 1),\n float(0.5).add(\n cos(\n float(6.2831853).mul(float(2).mul(u.uGlassIor).mul(u.uGlassFilmNm).mul(cosT)).div(phase),\n ).mul(0.5),\n ),\n clamp(u.uGlassIrid, 0, 1),\n ).toVar(\"glassFilm\");\n\n const f0 = pow(u.uGlassIor.sub(1).div(u.uGlassIor.add(1)), float(2));\n const F = f0.add(\n float(1)\n .sub(f0)\n .mul(pow(float(1).sub(ndv), float(5))),\n );\n col.assign(\n mix(\n col,\n mix(u.uClearColor, vec3(1, 1, 1), 0.35).mul(film),\n F.mul(float(0.18).add(u.uGlassIrid.mul(0.4))),\n ),\n );\n\n // Rim, with a deliberately WIDE window — see the GLSL.\n col.assign(\n mix(\n col,\n film,\n float(1)\n .sub(ndv)\n .smoothstep(mix(float(0.62), float(0.42), u.uGlassIrid), float(1))\n .mul(u.uGlassRim),\n ),\n );\n col.mulAssign(float(1).sub(float(1).sub(ndv).smoothstep(0.62, 0.86).mul(0.1)));\n\n // Two keys, wide lobe: one overhead light never reaches horizontal normals.\n const KEY = normalize(vec3(-0.3, 0.86, 0.42));\n const KEY_FILL = normalize(vec3(0.42, 0.16, 0.89));\n // reflect(-V, N) = -V - 2*dot(-V,N)*N. Spelled out because there is no reflect() helper here;\n // negating the whole expression, as this first did, points the mirror direction backwards and\n // puts the highlight on the wrong face.\n const mirror = V.mul(-1)\n .sub(N.mul(dot(V.mul(-1), N).mul(2)))\n .toVar(\"glassMirror\");\n const lobe = pow(max(dot(mirror, KEY), float(0)), float(40))\n .add(pow(max(dot(mirror, KEY_FILL), float(0)), float(40)).mul(0.55))\n .toVar(\"glassLobe\");\n const spec = lobe.add(rim.mul(0.25)).mul(u.uGlassSpec).toVar(\"glassSpec\");\n col.addAssign(lobe.mul(u.uGlassSpec).mul(0.35).mul(film));\n\n const luma = dot(col, vec3(0.299, 0.587, 0.114)).toVar(\"glassLuma\");\n const darkBlend = luma.smoothstep(0.25, 0.7);\n col.assign(max(mix(col.add(spec), col.mul(float(1).sub(spec)), darkBlend), vec3(0, 0, 0)));\n col.addAssign(float(0.5).sub(luma).mul(u.uGlassVibrancy));\n\n // Opaque draw, so the feather (and opacity) fade toward the UNBENT backdrop rather than\n // scaling the colour — see the GLSL twin for the dark line that scaling drew.\n const fade = clamp(u.uOpacity, 0, 1).toVar(\"glassFade\");\n If(u.uEdgeFeather.greaterThan(0), () => {\n const e = min(min(vUv.x, float(1).sub(vUv.x)), min(vUv.y, float(1).sub(vUv.y)));\n fade.mulAssign(e.smoothstep(float(0), u.uEdgeFeather));\n });\n col.assign(mix(backdropAt(texUv), col, fade));\n return vec4(clamp(col, 0, 1), 1.0);\n })();\n}\n\n/** The glass layer-count companion: coverage only, on the wave's own vertex stage. */\nfunction buildGlassLayerFragment(u: WaveTslUniforms, flags: WaveMaterialFlags, ndc: Vec2Node) {\n return Fn(() => {\n if (flags.dissolve) Discard(dissolved(u, uv(), ndc));\n return vec4(0.125, 0.125, 0.125, 1.0);\n })();\n}\n\n/** The solid surface theme. */\nfunction buildSolidFragment(\n u: WaveTslUniforms,\n flags: WaveMaterialFlags,\n clipZ: FloatNode,\n getPointerFall: () => FloatNode | null,\n ndc: Vec2Node,\n) {\n return Fn(() => {\n // Read at build time, not at the call: the positionNode closure that creates this varying\n // runs only when the vertex stage is built, after buildWaveMaterial has returned.\n const pointerFall = getPointerFall();\n if (flags.pointerFx && !pointerFall) {\n throw new Error(\"wave: the pointer varying was not created before the fragment built\");\n }\n const vUv = uv();\n // The disintegration front: drop the chunks it has already eaten, before any shading work.\n if (flags.dissolve) Discard(dissolved(u, vUv, ndc));\n const vWorldPos = positionWorld;\n const vViewDir = cameraPosition.sub(vWorldPos).toVar(\"vViewDir\");\n\n // crease: a foreshortening / fold detector from the screen-space uv derivative. It drives BOTH\n // the roundness shading and where the streaks appear — this is what gives the wave its\n // thickness without any normal-based lighting.\n const crease = float(0).toVar(\"crease\");\n crease.assign(dFdy(vUv).y.mul(u.uResolution.y).mul(u.uCreaseLight));\n crease.assign(clamp(mapLinear(crease, -1, 1, 0, 1), 0, 1));\n crease.assign(pow(crease, u.uCreaseSharpness));\n crease.assign(clamp(smoothstep(0.0, u.uCreaseSoftness, crease), 0, 1));\n\n const col = waveBaseColor(u, vUv).toVar(\"col\");\n col.assign(surfaceStreaks(u, vUv, col, crease));\n col.assign(applyColorGrade(u, col));\n\n if (pointerFall) {\n // Local hue rotation + brightness lift near the cursor (both fade out with the falloff).\n col.assign(hueShift(col, radians(u.uPointerHue).mul(pointerFall)));\n col.mulAssign(float(1).add(u.uPointerLighten.mul(pointerFall)));\n }\n\n // Iridescence: a thin-film hue that shifts with view angle — grazing parts of the ribbon shift\n // most, so the colour flows as the ribbon curves.\n If(u.uIridescence.greaterThan(0.001), () => {\n const iridN = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos)));\n const iridFacing = tabs(dot(iridN, normalize(vViewDir)));\n col.assign(hueShift(col, float(1).sub(iridFacing).mul(u.uIridescence).mul(Math.PI)));\n });\n\n // Sheen: lift the flat (low-crease) areas toward white. Pose-dependent, so kept gentle.\n col.addAssign(float(1).sub(crease).mul(0.25).mul(u.uSheen));\n\n // Pose-robust roundness: shade by the camera-facing ratio of the derivative surface normal so\n // the ribbon reads as a rounded solid from any angle.\n If(u.uRoundness.greaterThan(0.001), () => {\n const volN = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos)));\n const facing = tabs(dot(volN, normalize(vViewDir))).toVar(\"facing\");\n col.mulAssign(mix(float(1).sub(u.uRoundness.mul(0.6)), 1.0, facing));\n col.addAssign(smoothstep(0.65, 1.0, facing).mul(u.uRoundness).mul(0.18));\n });\n\n // Optional positionable lights — additive and gentle, on top of the base shading.\n If(u.uNumLights.greaterThan(0), () => {\n const n = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos))).toVar(\"lightN\");\n const vd = normalize(vViewDir).toVar(\"lightV\");\n If(dot(n, vd).lessThan(0.0), () => {\n n.assign(n.negate());\n });\n Loop({ start: 0, end: MAX_LIGHTS, type: \"int\" }, ({ i }) => {\n If(float(i).greaterThanEqual(float(u.uNumLights)), () => {\n Break();\n });\n const l = normalize(u.uLightPos.el(i).sub(vWorldPos)).toVar();\n const lc = u.uLightColor.el(i).mul(u.uLightIntensity.el(i)).toVar();\n const diff = max(dot(n, l), 0.0);\n const spec = pow(max(dot(n, normalize(l.add(vd))), 0.0), 28.0);\n col.addAssign(col.mul(diff).mul(lc).mul(0.16).add(lc.mul(spec).mul(0.1)));\n });\n });\n\n col.mulAssign(clamp(u.uAmbient, 0, 1).add(0.55)); // overall level; default 0.45 => x1.0\n\n if (flags.depthTint) {\n // Fade far fragments toward a colour so a multi-wave stack gains atmospheric separation.\n col.assign(mix(col, u.uDepthTintColor, clamp(clipZ.mul(6.0), 0, 1).mul(u.uDepthTint)));\n }\n\n If(u.uTexture.greaterThan(0.001), () => {\n col.mulAssign(float(1).add(grainHash(vUv.mul(850.0)).sub(0.5).mul(u.uTexture).mul(0.25)));\n });\n\n // Soft ribbon ENDS (fades on vUv.y, the length) + optional viewport-edge fade.\n const ribEdge = flags.edgeFeather\n ? smoothstep(0.0, u.uEdgeFeather, vUv.y).mul(\n float(1).sub(smoothstep(float(1).sub(u.uEdgeFeather), 1.0, vUv.y)),\n )\n : smoothstep(0.0, 0.1, vUv.y).mul(float(1).sub(smoothstep(0.9, 1.0, vUv.y)));\n const alpha = u.uOpacity.mul(ribEdge).toVar(\"alpha\");\n if (pointerFall) {\n alpha.mulAssign(clamp(float(1).sub(u.uPointerThin.mul(pointerFall)), 0, 1)); // local translucency\n }\n\n If(u.uEdgeFade.greaterThan(0.001), () => {\n const sc = screenUV;\n const vig = smoothstep(0.0, u.uEdgeFade, sc.x)\n .mul(float(1).sub(smoothstep(float(1).sub(u.uEdgeFade), 1.0, sc.x)))\n .mul(smoothstep(0.0, u.uEdgeFade, sc.y))\n .mul(float(1).sub(smoothstep(float(1).sub(u.uEdgeFade), 1.0, sc.y)));\n alpha.mulAssign(vig);\n });\n\n // Deep \"squared\" hero colour: square colour AND alpha so the soft ribbon edges keep the crisp\n // feather of the original squared-blend look, but composited (premultiplied) rather than\n // replace-blended, so they no longer punch holes.\n col.assign(clamp(col, vec3(0), vec3(1)));\n const squared = u.uSquared.greaterThan(0.5);\n return vec4(select(squared, col.mul(col), col), select(squared, alpha.mul(alpha), alpha));\n })();\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { simplexNoise } from "./noise.js";
|
|
2
|
-
import { Fn, clamp, cos, cross, dot, exp2, float, max, mix, normalize, pow, radians, sin, vec2, vec3 } from "three/tsl";
|
|
2
|
+
import { Fn, clamp, cos, cross, dot, exp2, float, floor, fract, max, min, mix, normalize, pow, radians, select, sin, vec2, vec3 } from "three/tsl";
|
|
3
3
|
//#region src/renderer/tsl/waveShape.ts
|
|
4
4
|
/**
|
|
5
5
|
* The wave SHAPE deform in TSL — the port of the `waveShapeChunk` GLSL in `../shaders.ts`.
|
|
@@ -43,12 +43,12 @@ const applyTwist = (v, t) => {
|
|
|
43
43
|
* so a constant-uv.x combed fiber turns into a constant-angle radial spoke. `amount` 0 is the
|
|
44
44
|
* identity mix. Exported so `parity:math` can probe it against the GLSL.
|
|
45
45
|
*/
|
|
46
|
-
function applyRadial(pos, uv, amount, arc, spread, radius, center) {
|
|
47
|
-
const rAng = radians(center).add(clamp(uv.x, 0, 1).sub(.5).mul(radians(arc))).toVar("rAng");
|
|
46
|
+
function applyRadial(pos, uv, amount, arc, spread, radius, center, cone, swirl) {
|
|
47
|
+
const rAng = radians(center).add(clamp(uv.x, 0, 1).sub(.5).mul(radians(arc))).add(uv.y.mul(radians(swirl))).toVar("rAng");
|
|
48
48
|
const rRho = radius.add(uv.y.mul(400).mul(spread));
|
|
49
49
|
const rEr = vec3(cos(rAng), sin(rAng), 0);
|
|
50
50
|
const rEt = vec3(sin(rAng).negate(), cos(rAng), 0);
|
|
51
|
-
return mix(pos, rEr.mul(rRho).add(rEt.mul(pos.z.sub(-8)).mul(.5)).add(vec3(0, 0, pos.y)), clamp(amount, 0, 1));
|
|
51
|
+
return mix(pos, rEr.mul(rRho).add(rEt.mul(pos.z.sub(-8)).mul(.5)).add(vec3(0, 0, pos.y.add(uv.y.mul(400).mul(cone)))), clamp(amount, 0, 1));
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
54
|
* Deform one vertex. `t` is linear time and `loopOff` the orbit offset; only the one selected by
|
|
@@ -93,16 +93,37 @@ function waveShape(u, flags, position, uv, t, loopOff) {
|
|
|
93
93
|
}
|
|
94
94
|
];
|
|
95
95
|
const twisted = applyTwist(applyTwist(applyTwist(pos, twists[0]), twists[1]), twists[2]).toVar();
|
|
96
|
-
|
|
97
|
-
pos: twisted,
|
|
98
|
-
twists
|
|
99
|
-
};
|
|
96
|
+
const fanned = flags.radial ? applyRadial(twisted, uv, u.uRadialAmount, u.uRadialArc, u.uRadialSpread, u.uRadialRadius, u.uRadialCenter, u.uRadialCone, u.uRadialSwirl) : twisted;
|
|
100
97
|
return {
|
|
101
|
-
pos:
|
|
98
|
+
pos: flags.path ? applyPath(fanned, u.uPathTex) : fanned,
|
|
102
99
|
twists
|
|
103
100
|
};
|
|
104
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Sweep the deformed ribbon onto its authored centreline. The TSL twin of the GLSL `PATH` block —
|
|
104
|
+
* see that for the four details that make a straight path exactly the identity.
|
|
105
|
+
*/
|
|
106
|
+
/** V coordinate of a LUT row's texel centre. */
|
|
107
|
+
const pathRow = (r) => (r + .5) / 3;
|
|
108
|
+
function applyPath(pos, tex) {
|
|
109
|
+
const at = (x, r) => tex.sample(vec2(x, pathRow(r))).level(float(0));
|
|
110
|
+
const sRaw = pos.x.add(200).div(400).toVar();
|
|
111
|
+
const closed = at(float(.5 / 128), 2).w.greaterThan(.5).toVar();
|
|
112
|
+
const s = select(closed, fract(sRaw), clamp(sRaw, 0, 1)).toVar();
|
|
113
|
+
const i = s.mul(127).toVar();
|
|
114
|
+
const i0 = floor(i).toVar();
|
|
115
|
+
const f = i.sub(i0).toVar();
|
|
116
|
+
const u0 = i0.add(.5).div(128).toVar();
|
|
117
|
+
const u1 = min(i0.add(1), 127).add(.5).div(128).toVar();
|
|
118
|
+
const lerpRow = (r) => mix(at(u0, r), at(u1, r), f);
|
|
119
|
+
const pP = lerpRow(0).toVar();
|
|
120
|
+
const pNL = lerpRow(1).toVar();
|
|
121
|
+
const pN = normalize(pNL.xyz).toVar();
|
|
122
|
+
const pB = normalize(lerpRow(2).xyz).toVar();
|
|
123
|
+
const past = select(closed, float(0), sRaw.sub(s).mul(pNL.w));
|
|
124
|
+
return pP.xyz.add(cross(pN, pB).mul(past)).add(pN.mul(pos.y)).add(pB.mul(pos.z.sub(-8).mul(pP.w)));
|
|
125
|
+
}
|
|
105
126
|
//#endregion
|
|
106
|
-
export { applyRadial, applyTwist, expStep, waveShape };
|
|
127
|
+
export { applyPath, applyRadial, applyTwist, expStep, waveShape };
|
|
107
128
|
|
|
108
129
|
//# sourceMappingURL=waveShape.js.map
|