@call-me-sensei/toonlab 0.1.1

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.
Files changed (146) hide show
  1. package/ATTRIBUTION.md +40 -0
  2. package/LICENSE +21 -0
  3. package/README.md +125 -0
  4. package/package.json +97 -0
  5. package/src/character/characterRig.js +353 -0
  6. package/src/character/freestyleSwimClip.js +541 -0
  7. package/src/character/index.js +4 -0
  8. package/src/character/modelLoader.js +433 -0
  9. package/src/core/materialRoles.js +438 -0
  10. package/src/core/presetDocuments.js +110 -0
  11. package/src/core/shaderBackend.js +24 -0
  12. package/src/debrisgen/debrisFields.js +544 -0
  13. package/src/debrisgen/debrisGenerator.js +1929 -0
  14. package/src/debrisgen/debrisPalettes.js +71 -0
  15. package/src/debrisgen/debrisPhysics.js +198 -0
  16. package/src/debrisgen/debrisPresets.js +155 -0
  17. package/src/debrisgen/debrisSettings.js +333 -0
  18. package/src/debrisgen/debrisTextures.js +380 -0
  19. package/src/debrisgen/index.js +5 -0
  20. package/src/debug/fieldValues.js +95 -0
  21. package/src/debug/index.js +7 -0
  22. package/src/debug/settingsPanel.js +170 -0
  23. package/src/environment/environmentAmbientProbe.js +160 -0
  24. package/src/environment/environmentMaterialAdapter.js +336 -0
  25. package/src/environment/environmentMaterialClassifier.js +178 -0
  26. package/src/environment/environmentPlanarReflection.js +201 -0
  27. package/src/environment/environmentPresets.js +443 -0
  28. package/src/environment/environmentRigs.js +684 -0
  29. package/src/environment/environmentSettings.js +504 -0
  30. package/src/environment/environmentShaderMaterials.js +318 -0
  31. package/src/environment/environmentSunShadowPass.js +268 -0
  32. package/src/environment/environmentTextureResolver.js +161 -0
  33. package/src/environment/environmentTimeOfDay.js +167 -0
  34. package/src/environment/environmentVertexAo.js +188 -0
  35. package/src/environment/index.js +8 -0
  36. package/src/environment/scanAssetStylize.js +103 -0
  37. package/src/index.js +21 -0
  38. package/src/loaders/index.js +1 -0
  39. package/src/post/index.js +2 -0
  40. package/src/post/postProcessing.js +1087 -0
  41. package/src/rockgen/export/glbExport.js +177 -0
  42. package/src/rockgen/heightfield/heightfieldErosion.js +7 -0
  43. package/src/rockgen/heightfield/heightfieldPatch.js +183 -0
  44. package/src/rockgen/heightfield/stylizedErosionSim.js +356 -0
  45. package/src/rockgen/index.js +14 -0
  46. package/src/rockgen/mesh/meshAttributes.js +374 -0
  47. package/src/rockgen/mesh/meshDocument.js +158 -0
  48. package/src/rockgen/mesh/surfaceNets.js +329 -0
  49. package/src/rockgen/noise/cellularNoise3.js +92 -0
  50. package/src/rockgen/noise/prng.js +62 -0
  51. package/src/rockgen/noise/simplexNoise3.js +89 -0
  52. package/src/rockgen/noise/valueNoise3.js +87 -0
  53. package/src/rockgen/rockDocument.js +255 -0
  54. package/src/rockgen/rockHelpers.js +8 -0
  55. package/src/rockgen/rockgenPresets.js +559 -0
  56. package/src/rockgen/rockgenSettings.js +756 -0
  57. package/src/rockgen/sdf/fieldCompiler.js +544 -0
  58. package/src/rockgen/sdf/sculptEdits.js +66 -0
  59. package/src/rockgen/sdf/sdfModifiers.js +45 -0
  60. package/src/rockgen/sdf/sdfOps.js +37 -0
  61. package/src/rockgen/sdf/sdfPrimitives.js +88 -0
  62. package/src/shaders-tsl/anime.js +963 -0
  63. package/src/shaders-tsl/chunks/character-color.js +64 -0
  64. package/src/shaders-tsl/chunks/character-highlights.js +174 -0
  65. package/src/shaders-tsl/chunks/character-lighting.js +309 -0
  66. package/src/shaders-tsl/chunks/character-material-maps.js +146 -0
  67. package/src/shaders-tsl/chunks/character-roles.js +52 -0
  68. package/src/shaders-tsl/chunks/character-scene-lights.js +270 -0
  69. package/src/shaders-tsl/chunks/character-shadow-color.js +61 -0
  70. package/src/shaders-tsl/chunks/character-skinning.js +140 -0
  71. package/src/shaders-tsl/chunks/environment-color.js +47 -0
  72. package/src/shaders-tsl/chunks/environment-debug.js +79 -0
  73. package/src/shaders-tsl/chunks/environment-lighting.js +260 -0
  74. package/src/shaders-tsl/chunks/environment-sun-shadow.js +87 -0
  75. package/src/shaders-tsl/chunks/foliage-fog.js +60 -0
  76. package/src/shaders-tsl/chunks/pass-depth-color.js +89 -0
  77. package/src/shaders-tsl/chunks/stylized-cloud-shadow.js +70 -0
  78. package/src/shaders-tsl/chunks/water-color.js +190 -0
  79. package/src/shaders-tsl/chunks/water-common.js +124 -0
  80. package/src/shaders-tsl/chunks/water-foam.js +93 -0
  81. package/src/shaders-tsl/chunks/water-lighting.js +116 -0
  82. package/src/shaders-tsl/chunks/water-ripple.js +64 -0
  83. package/src/shaders-tsl/chunks/water-waves.js +100 -0
  84. package/src/shaders-tsl/environment-ao-overlay.js +60 -0
  85. package/src/shaders-tsl/environment.js +680 -0
  86. package/src/shaders-tsl/flower.js +267 -0
  87. package/src/shaders-tsl/grass.js +193 -0
  88. package/src/shaders-tsl/post-composite.js +461 -0
  89. package/src/shaders-tsl/sky.js +175 -0
  90. package/src/shaders-tsl/tree-leaf.js +264 -0
  91. package/src/shaders-tsl/water-breaker.js +423 -0
  92. package/src/shaders-tsl/water-kelp.js +171 -0
  93. package/src/shaders-tsl/water-rain.js +107 -0
  94. package/src/shaders-tsl/water-simulation.js +140 -0
  95. package/src/shaders-tsl/water-splash.js +298 -0
  96. package/src/shaders-tsl/water.js +509 -0
  97. package/src/sky/index.js +2 -0
  98. package/src/sky/stylizedSky.js +341 -0
  99. package/src/toon/characterRenderPasses.js +646 -0
  100. package/src/toon/index.js +4 -0
  101. package/src/toon/settings/alphaSettings.js +170 -0
  102. package/src/toon/settings/averageShadowSettings.js +181 -0
  103. package/src/toon/settings/baseTextureSettings.js +133 -0
  104. package/src/toon/settings/celShadeSettings.js +96 -0
  105. package/src/toon/settings/contactShadowSettings.js +114 -0
  106. package/src/toon/settings/eyeHighlightSettings.js +187 -0
  107. package/src/toon/settings/faceLightingSettings.js +135 -0
  108. package/src/toon/settings/furSettings.js +112 -0
  109. package/src/toon/settings/glitterSettings.js +102 -0
  110. package/src/toon/settings/hairHighlightSettings.js +325 -0
  111. package/src/toon/settings/indirectLightSettings.js +244 -0
  112. package/src/toon/settings/localLightSettings.js +176 -0
  113. package/src/toon/settings/materialMapSettings.js +299 -0
  114. package/src/toon/settings/outlineSettings.js +263 -0
  115. package/src/toon/settings/perspectiveRemovalSettings.js +60 -0
  116. package/src/toon/settings/rimLightSettings.js +268 -0
  117. package/src/toon/settings/sceneShadowSettings.js +147 -0
  118. package/src/toon/settings/selfShadowSettings.js +221 -0
  119. package/src/toon/settings/shadowColorSettings.js +238 -0
  120. package/src/toon/settings/skinToneSettings.js +150 -0
  121. package/src/toon/settings/specularSettings.js +314 -0
  122. package/src/toon/settings/stickerSettings.js +102 -0
  123. package/src/toon/toonMaterialAdapter.js +2235 -0
  124. package/src/toon/toonSettings.js +860 -0
  125. package/src/vegetation/flowerSpecies.js +260 -0
  126. package/src/vegetation/index.js +10 -0
  127. package/src/vegetation/stylizedBush.js +136 -0
  128. package/src/vegetation/stylizedFlower.js +213 -0
  129. package/src/vegetation/stylizedFlowers.js +297 -0
  130. package/src/vegetation/stylizedGrass.js +491 -0
  131. package/src/vegetation/stylizedTree.js +2449 -0
  132. package/src/vegetation/stylizedTreeFoliage.js +691 -0
  133. package/src/vegetation/treeExport.js +300 -0
  134. package/src/vegetation/treeRecipe.js +647 -0
  135. package/src/water/index.js +2 -0
  136. package/src/water/water.js +20 -0
  137. package/src/water/waterBreakerSystem.js +591 -0
  138. package/src/water/waterInteraction.js +193 -0
  139. package/src/water/waterMaterial.js +263 -0
  140. package/src/water/waterRain.js +136 -0
  141. package/src/water/waterRippleSimulation.js +227 -0
  142. package/src/water/waterScenePasses.js +415 -0
  143. package/src/water/waterSettings.js +1138 -0
  144. package/src/water/waterSplashSystem.js +251 -0
  145. package/src/water/waterSurface.js +475 -0
  146. package/src/water/waterVegetation.js +83 -0
@@ -0,0 +1,509 @@
1
+ // TSL port of src/shaders/water.vert.glsl + water.frag.glsl — the stylized
2
+ // water surface. Consumed by createWaterMaterial on the TSL backend; exposes
3
+ // `.uniforms` under the exact GLSL uniform names so applyWaterSettingsToMaterial,
4
+ // updateWaterMaterialCamera, WaterScenePasses.bindToMaterial, setCloudShadow,
5
+ // and setWaterDebugMode keep working unchanged on both backends.
6
+ //
7
+ // Porting notes (docs/tsl-conventions.md):
8
+ // - Quality defines (WATER_QUALITY / WATER_DETAIL_OCTAVES / WATER_FOAM_OCTAVES)
9
+ // and WATER_SHOALING become graph-build flags. Changing quality after
10
+ // creation therefore needs a new material on this backend (the classic path
11
+ // recompiles via defines); the lab HUD never does this live.
12
+ // - getShadowMask() → sampleEnvironmentSunShadow(worldPos): inert (1.0) until
13
+ // the shared sun-shadow pass runs, like a scene with no shadow-casting sun.
14
+ // - Scene color/depth/reflection/ripple textures are render-target-fed:
15
+ // every sample is .level(0); scene depth is a float COLOR texture with
16
+ // linear window depth (see WaterScenePasses' node-backend depth render).
17
+ // - screenUV replaces gl_FragCoord/uResolution for the grab-pass uv; the
18
+ // world reconstruction stays GL-convention in-shader because
19
+ // updateWaterMaterialCamera composes the backend NDC adjustment into
20
+ // uInvViewProjMatrix on the CPU (conventions #6).
21
+ // - Debug views are uniform-driven (masked sum, no deep select chains).
22
+ //
23
+ // TODO(tsl Phase 7): fog ordering — the GLSL applies scene fog AFTER the
24
+ // colorspace conversion (fog_fragment follows colorspace_fragment); the node
25
+ // pipeline would apply it before. Scene fog is disabled here until the fog
26
+ // phase resolves the ordering.
27
+
28
+ import * as THREE from 'three';
29
+ import {
30
+ abs,
31
+ attribute,
32
+ cameraPosition,
33
+ cameraProjectionMatrix,
34
+ cameraViewMatrix,
35
+ clamp,
36
+ dot,
37
+ float,
38
+ Fn,
39
+ If,
40
+ length,
41
+ max,
42
+ min,
43
+ mix,
44
+ modelWorldMatrix,
45
+ normalize,
46
+ positionGeometry,
47
+ screenUV,
48
+ select,
49
+ smoothstep,
50
+ texture,
51
+ uniform,
52
+ uniformArray,
53
+ varying,
54
+ vec2,
55
+ vec3,
56
+ vec4,
57
+ } from 'three/tsl';
58
+ import { NodeMaterial } from 'three/webgpu';
59
+
60
+ import { sampleEnvironmentSunShadow } from './chunks/environment-sun-shadow.js';
61
+ import { stylizedCloudShadow } from './chunks/stylized-cloud-shadow.js';
62
+ import { waterCombineNormal, waterFbm, waterRotate2d } from './chunks/water-common.js';
63
+ import { createWaterColorChunk } from './chunks/water-color.js';
64
+ import { createWaterFoamChunk } from './chunks/water-foam.js';
65
+ import { createWaterLightingChunk } from './chunks/water-lighting.js';
66
+ import { createWaterRippleChunk } from './chunks/water-ripple.js';
67
+ import { createWaterWavesChunk } from './chunks/water-waves.js';
68
+
69
+ // Node-backend placeholders: same roles as waterMaterial.js's placeholders,
70
+ // but linear (NoColorSpace) — node-backend render targets hold working-space
71
+ // values and TextureNode bakes the colorSpace conversion at graph build, so
72
+ // placeholder and live texture must agree (both linear).
73
+ function createLinearPlaceholder(rgba) {
74
+ const tex = new THREE.DataTexture(new Uint8Array(rgba), 1, 1, THREE.RGBAFormat);
75
+ tex.needsUpdate = true;
76
+ return tex;
77
+ }
78
+
79
+ let placeholders = null;
80
+ function getPlaceholders() {
81
+ if (!placeholders) {
82
+ placeholders = {
83
+ depth: createLinearPlaceholder([255, 255, 255, 255]),
84
+ reflection: createLinearPlaceholder([126, 190, 218, 255]),
85
+ ripple: createLinearPlaceholder([128, 128, 128, 255]),
86
+ sceneColor: createLinearPlaceholder([80, 150, 190, 255]),
87
+ };
88
+ }
89
+ return placeholders;
90
+ }
91
+
92
+ // UniformArrayNode keeps the authoring array on `.array` (its `.value` is the
93
+ // packed GPU buffer created at setup), so the `.uniforms` compatibility
94
+ // surface exposes array uniforms through a classic-style wrapper whose
95
+ // `.value` is that array — writeWaveUniforms's `uniforms.uWavesA.value[i]`
96
+ // writes hit the exact slots the node repacks each render. `.node` carries
97
+ // the UniformArrayNode for graph adoption (attachWaveUniforms).
98
+ export function waterArrayUniformEntry(node) {
99
+ return { node, value: node.array };
100
+ }
101
+
102
+ // Accepts a UniformArrayNode or a wrapper made by waterArrayUniformEntry.
103
+ export function resolveWaterArrayUniformNode(entry) {
104
+ return entry?.isNode ? entry : entry?.node ?? null;
105
+ }
106
+
107
+ /**
108
+ * Creates the water surface NodeMaterial.
109
+ *
110
+ * flags:
111
+ * - waveCount WATER_WAVE_COUNT
112
+ * - qualityLevel WATER_QUALITY (0/1/2)
113
+ * - detailOctaves WATER_DETAIL_OCTAVES
114
+ * - foamOctaves WATER_FOAM_OCTAVES
115
+ * - shoaling WATER_SHOALING (aBedHeight attribute present)
116
+ */
117
+ export function createWaterNodeMaterial({
118
+ waveCount,
119
+ qualityLevel = 2,
120
+ detailOctaves = 4,
121
+ foamOctaves = 3,
122
+ shoaling = false,
123
+ } = {}) {
124
+ const flags = {
125
+ caustics: qualityLevel >= 1,
126
+ chromaticCaustics: qualityLevel >= 2,
127
+ detailOctaves,
128
+ foamOctaves,
129
+ shoaling: Boolean(shoaling),
130
+ sparkles: qualityLevel >= 1,
131
+ waveCount,
132
+ };
133
+ const holders = getPlaceholders();
134
+
135
+ const wavesANode = uniformArray(Array.from({ length: waveCount }, () => new THREE.Vector4()), 'vec4');
136
+ const wavesBNode = uniformArray(Array.from({ length: waveCount }, () => new THREE.Vector4()), 'vec4');
137
+
138
+ // ---- Uniforms (exact GLSL names; UniformNodes/TextureNodes expose
139
+ // `.value` like ShaderMaterial uniform entries; array uniforms are
140
+ // wrapped — see waterArrayUniformEntry) ----
141
+ const u = {
142
+ uTime: uniform(0),
143
+ uResolution: uniform(new THREE.Vector2(1, 1)),
144
+ uCameraNear: uniform(0.05),
145
+ uCameraFar: uniform(200),
146
+ uInvViewProjMatrix: uniform(new THREE.Matrix4()),
147
+
148
+ uSceneColor: texture(holders.sceneColor),
149
+ uSceneDepth: texture(holders.depth),
150
+ uUseSceneColor: uniform(0),
151
+ uUseSceneDepth: uniform(0),
152
+
153
+ uReflectionMap: texture(holders.reflection),
154
+ uReflectionMatrix: uniform(new THREE.Matrix4()),
155
+ uUseReflectionMap: uniform(0),
156
+ uReflectionStrength: uniform(0.55),
157
+ uReflectionDistortion: uniform(0.04),
158
+
159
+ uRippleMap: texture(holders.ripple),
160
+ uUseRippleMap: uniform(0),
161
+ uRippleRegion: uniform(new THREE.Vector4(0, 0, 10, 10)),
162
+ uRippleTexel: uniform(new THREE.Vector2(1 / 256, 1 / 256)),
163
+ uRippleHeightScale: uniform(1),
164
+
165
+ uShoalingDepth: uniform(1.4),
166
+ uShorelineWaves: uniform(0.35),
167
+ uShorelineRunup: uniform(0.6),
168
+ uWaveEnergy: uniform(0.3),
169
+
170
+ uWavesA: waterArrayUniformEntry(wavesANode),
171
+ uWavesB: waterArrayUniformEntry(wavesBNode),
172
+
173
+ uDetailNormalStrength: uniform(0.5),
174
+ uDetailScale: uniform(0.5),
175
+ uFlowDirection: uniform(new THREE.Vector2(1, 0)),
176
+ uFlowSpeed: uniform(0.35),
177
+
178
+ uShallowColor: uniform(new THREE.Color()),
179
+ uMidColor: uniform(new THREE.Color()),
180
+ uDeepColor: uniform(new THREE.Color()),
181
+ uDepthFadeDistance: uniform(1.2),
182
+ uDeepFadeDistance: uniform(3),
183
+ uOpacity: uniform(0.9),
184
+ uRefractionStrength: uniform(0.6),
185
+ uCausticsStrength: uniform(0.6),
186
+ uCausticsScale: uniform(1),
187
+ uCausticsSpeed: uniform(1),
188
+
189
+ uFoamColor: uniform(new THREE.Color()),
190
+ uFoamAmount: uniform(1),
191
+ uFoamContactDistance: uniform(1),
192
+ uFoamLineSpacing: uniform(1.4),
193
+ uFoamNoiseScale: uniform(0.6),
194
+ uWhitecapAmount: uniform(0.3),
195
+ uRippleFoamStrength: uniform(1),
196
+
197
+ uSunDirection: uniform(new THREE.Vector3(0, 1, 0)),
198
+ uSunColor: uniform(new THREE.Color()),
199
+ uSpecularStrength: uniform(0.8),
200
+ uSpecularShininess: uniform(120),
201
+ uSpecularStretch: uniform(0.5),
202
+ uSparkleStrength: uniform(0.5),
203
+ uSparkleScale: uniform(1.2),
204
+ uSparkleSpeed: uniform(1),
205
+ uSunGlowStrength: uniform(0.6),
206
+ uFresnelStrength: uniform(0.6),
207
+ uFresnelPower: uniform(3),
208
+ uFresnelBias: uniform(0.12),
209
+ uReflectionSoftness: uniform(0.4),
210
+ uFresnelColor: uniform(new THREE.Color(1, 1, 1)),
211
+ uSkyZenithColor: uniform(new THREE.Color(0.35, 0.58, 0.85)),
212
+ uSkyHorizonColor: uniform(new THREE.Color(0.85, 0.92, 1.0)),
213
+
214
+ uCloudShadowStrength: uniform(0),
215
+ uCloudShadowCoverage: uniform(0.45),
216
+ uCloudShadowScale: uniform(0.012),
217
+ uCloudShadowVelocity: uniform(new THREE.Vector2(0.02, 0.006)),
218
+ uSceneShadowStrength: uniform(0.5),
219
+
220
+ uDebugMode: uniform(0, 'int'),
221
+ uCameraBelow: uniform(0),
222
+ };
223
+
224
+ const waves = createWaterWavesChunk({ wavesA: wavesANode, wavesB: wavesBNode, waveCount });
225
+ const ripple = createWaterRippleChunk({ u });
226
+ const color = createWaterColorChunk({ u, flags });
227
+ const foam = createWaterFoamChunk({ u, foamOctaves });
228
+ const lighting = createWaterLightingChunk({ u });
229
+
230
+ const material = new NodeMaterial();
231
+ material.name = 'StylizedWater';
232
+ material.lights = false;
233
+ // TODO(tsl Phase 7): fog ordering (see module header).
234
+ material.fog = false;
235
+ material.side = THREE.DoubleSide;
236
+ material.transparent = true;
237
+ material.depthWrite = true;
238
+
239
+ // ---- Vertex stage (water.vert.glsl) ----
240
+ const vWorldPosition = varying(vec3(), 'vWaterWorldPosition');
241
+ const vRestWorldXZ = varying(vec2(), 'vWaterRestWorldXZ');
242
+ const vCrest = varying(float(), 'vWaterCrest');
243
+ const vShoal = varying(float(), 'vWaterShoal');
244
+ const vBreaker = varying(float(), 'vWaterBreaker');
245
+ const vSwash = varying(float(), 'vWaterSwash');
246
+ const vChop = varying(float(), 'vWaterChop');
247
+
248
+ material.vertexNode = Fn(() => {
249
+ const worldPosition = modelWorldMatrix.mul(vec4(positionGeometry, 1.0)).toVar();
250
+ const restXZ = worldPosition.xz.toVar();
251
+ vRestWorldXZ.assign(restXZ);
252
+
253
+ // Shallow water filters the spectrum: the short cross chop dies out toward
254
+ // the surf zone, leaving clean parallel lines of the dominant swell.
255
+ const chopWeight = float(1.0).toVar();
256
+ const shoal = float(1.0).toVar();
257
+ const rippleShoal = float(1.0).toVar();
258
+ const breakFoam = float(0.0).toVar();
259
+ const swash = float(0.0).toVar();
260
+
261
+ let rawWave;
262
+ const waveDisplacement = vec3(0.0).toVar();
263
+ if (flags.shoaling) {
264
+ const aBedHeight = attribute('aBedHeight', 'float');
265
+ const restDepth = worldPosition.y.sub(aBedHeight).toVar();
266
+ // Big swells feel the bottom much deeper than small ones.
267
+ const shoalRange = max(max(u.uShoalingDepth, u.uWaveEnergy.mul(2.2)), 1e-3).toVar();
268
+ chopWeight.assign(mix(0.15, 1.0, smoothstep(shoalRange.mul(0.3), shoalRange.mul(1.4), restDepth)));
269
+
270
+ rawWave = waves.gerstnerDisplacementFiltered(restXZ, u.uTime, chopWeight).toVar();
271
+
272
+ // Stylized breaking-wave shoaling (see water.vert.glsl for the model).
273
+ const deepFactor = smoothstep(0.0, shoalRange, restDepth);
274
+ const rearUp = smoothstep(shoalRange.mul(0.45), shoalRange.mul(1.5), restDepth).oneMinus()
275
+ .mul(smoothstep(0.05, 0.35, restDepth));
276
+ shoal.assign(mix(u.uShorelineWaves, 1.0, deepFactor).mul(rearUp.mul(0.3).add(1.0)));
277
+
278
+ const targetY = rawWave.y.mul(shoal).toVar();
279
+ const capY = max(restDepth, 0.0).mul(0.72).toVar();
280
+ // Soft cap: 10% of the excess bleeds through so sustained over-cap swell
281
+ // reads as rounded rollers instead of flat-topped plateaus.
282
+ const capExcess = max(targetY.sub(capY), 0.0).toVar();
283
+ const brokenY = min(targetY, capY).add(capExcess.mul(0.1)).toVar();
284
+ // A trough can never dip below the seabed.
285
+ brokenY.assign(max(brokenY, max(restDepth, 0.0).negate().add(0.04)));
286
+ breakFoam.assign(clamp(capExcess.mul(0.9).div(max(u.uWaveEnergy.mul(0.3), 1e-3)), 0.0, 1.0));
287
+
288
+ const wavePhase = clamp(rawWave.y.div(max(u.uWaveEnergy, 1e-3)), -1.0, 1.0).toVar();
289
+ const runup = u.uShorelineRunup.mul(u.uWaveEnergy).mul(max(wavePhase, 0.0));
290
+ // Dry land tucks the film 20cm under the terrain.
291
+ const film = clamp(restDepth.add(runup), -0.2, 0.05).toVar();
292
+ const filmY = aBedHeight.sub(worldPosition.y).add(film);
293
+ const beach = smoothstep(-0.02, 0.06, restDepth).oneMinus().toVar();
294
+
295
+ rippleShoal.assign(smoothstep(0.0, 0.12, restDepth));
296
+ swash.assign(beach.mul(smoothstep(0.004, 0.02, film)).mul(wavePhase.mul(0.45).add(0.55)));
297
+ shoal.mulAssign(beach.oneMinus());
298
+ waveDisplacement.assign(vec3(
299
+ rawWave.x.mul(shoal),
300
+ mix(brokenY, filmY, beach),
301
+ rawWave.z.mul(shoal),
302
+ ));
303
+ } else {
304
+ rawWave = waves.gerstnerDisplacementFiltered(restXZ, u.uTime, chopWeight).toVar();
305
+ waveDisplacement.assign(rawWave);
306
+ }
307
+
308
+ const rippleState = ripple.rippleSample(restXZ);
309
+ const rippleHeight = rippleState.r.mul(u.uRippleHeightScale).mul(rippleShoal).toVar();
310
+
311
+ worldPosition.xyz.addAssign(waveDisplacement);
312
+ worldPosition.y.addAssign(rippleHeight);
313
+
314
+ const { crest } = waves.gerstnerNormalFiltered(restXZ, u.uTime, chopWeight);
315
+ vCrest.assign(crest.mul(clamp(shoal, 0.0, 1.0)));
316
+ vChop.assign(chopWeight);
317
+ vShoal.assign(shoal);
318
+ vBreaker.assign(breakFoam);
319
+ vSwash.assign(swash);
320
+ vWorldPosition.assign(worldPosition.xyz);
321
+
322
+ return cameraProjectionMatrix.mul(cameraViewMatrix).mul(vec4(worldPosition.xyz, 1.0));
323
+ })();
324
+
325
+ // ---- Fragment stage (water.frag.glsl) ----
326
+ material.fragmentNode = Fn(() => {
327
+ const screenUv = screenUV.toVar();
328
+ const toCamera = cameraPosition.sub(vWorldPosition).toVar();
329
+ const viewDir = normalize(toCamera).toVar();
330
+ const viewDistance = length(toCamera).toVar();
331
+
332
+ // --- surface normal: Gerstner (analytic, per-pixel) + fbm detail + ripples ---
333
+ const gerstner = waves.gerstnerNormalFiltered(vRestWorldXZ, u.uTime, vChop);
334
+ const gerstnerNormal = vec3(gerstner.normal).toVar();
335
+ const crest = float(gerstner.crest).toVar();
336
+ const shoalBlend = clamp(vShoal, 0.0, 1.0).toVar();
337
+ gerstnerNormal.assign(normalize(mix(vec3(0.0, 1.0, 0.0), gerstnerNormal, shoalBlend)));
338
+ crest.assign(clamp(crest.mul(shoalBlend).add(vBreaker.mul(crest.mul(0.55).add(0.45))), 0.0, 1.0));
339
+ const gerstnerNormalY = gerstnerNormal.y.toVar();
340
+
341
+ const detailFade = smoothstep(16.0, 60.0, viewDistance).oneMinus();
342
+ const detailStrength = u.uDetailNormalStrength.mul(mix(0.2, 1.0, detailFade)).toVar();
343
+ const flowOffset = u.uFlowDirection.mul(u.uTime).mul(u.uFlowSpeed).toVar();
344
+ const detailUv1 = vRestWorldXZ.mul(u.uDetailScale).add(flowOffset.mul(0.55));
345
+ const detailUv2 = waterRotate2d(1.9).mul(vRestWorldXZ).mul(u.uDetailScale.mul(1.63)).sub(flowOffset.mul(0.34));
346
+ const waterDetailGradient = (detailUv) => {
347
+ const uv = vec2(detailUv).toVar();
348
+ const center = waterFbm(uv, flags.detailOctaves).toVar();
349
+ const dx = waterFbm(uv.add(vec2(0.11, 0.0)), flags.detailOctaves);
350
+ const dy = waterFbm(uv.add(vec2(0.0, 0.11)), flags.detailOctaves);
351
+ return vec2(dx.sub(center), dy.sub(center)).div(0.11);
352
+ };
353
+ const detailGradient = waterDetailGradient(detailUv1).mul(detailStrength)
354
+ .add(waterDetailGradient(detailUv2).mul(detailStrength).mul(0.55)).toVar();
355
+ const rippleGradient = ripple.rippleGradient(vRestWorldXZ);
356
+ const rippleState = ripple.rippleSample(vRestWorldXZ).toVar();
357
+ const waveNormal = waterCombineNormal(gerstnerNormal, rippleGradient).toVar();
358
+ const surfaceNormal = waterCombineNormal(waveNormal, detailGradient).toVar();
359
+ // Refraction reads with most of the micro detail removed.
360
+ const refractionNormal = waterCombineNormal(waveNormal, detailGradient.mul(0.3)).toVar();
361
+
362
+ const output = vec4(0.0).toVar();
363
+ If(u.uCameraBelow.greaterThan(0.5), () => {
364
+ // --- underside: camera below the surface looking up ---
365
+ // A stylized Snell-window: bright sky punches through overhead, the
366
+ // surface darkens to the deep tint toward grazing angles.
367
+ const throughDir = normalize(
368
+ viewDir.negate().add(vec3(surfaceNormal.x, 0.0, surfaceNormal.z).mul(0.7)),
369
+ ).toVar();
370
+ const skyThrough = lighting.proceduralSky(throughDir);
371
+ const window = smoothstep(0.15, 0.8, throughDir.y).toVar();
372
+ const underColor = mix(u.uDeepColor.mul(0.55), skyThrough.mul(1.15), window).toVar();
373
+ underColor.addAssign(
374
+ lighting.sparkles(vRestWorldXZ, surfaceNormal, viewDir, viewDistance, u.uTime).mul(0.5),
375
+ );
376
+ output.assign(vec4(underColor, mix(0.95, 0.62, window)));
377
+ }).Else(() => {
378
+ // --- scene depth, refraction, and the water body color ---
379
+ const waterViewDistance = cameraViewMatrix.mul(vec4(vWorldPosition, 1.0)).z.negate().toVar();
380
+ const sceneRawDepth = color.sceneRawDepth(screenUv).toVar();
381
+ const sceneViewDistance = color.viewDistanceFromDepth(sceneRawDepth).toVar();
382
+ const viewDepthDiff = max(sceneViewDistance.sub(waterViewDistance), 0.0).toVar();
383
+
384
+ const refractedUv = color.refractedUv(screenUv, refractionNormal, waterViewDistance, viewDepthDiff).toVar();
385
+ const refractedRawDepth = color.sceneRawDepth(refractedUv).toVar();
386
+ const groundWorld = color.worldFromDepth(refractedUv, refractedRawDepth).toVar();
387
+ const columnDepth = select(
388
+ u.uUseSceneDepth.greaterThan(0.5),
389
+ max(vWorldPosition.y.sub(groundWorld.y), 0.0),
390
+ u.uDepthFadeDistance.add(u.uDeepFadeDistance),
391
+ ).toVar();
392
+ const effectiveDepth = columnDepth.add(viewDepthDiff.mul(0.18));
393
+
394
+ const { absorb, tint: absorptionTint } = color.absorptionTint(effectiveDepth);
395
+
396
+ const bodyColor = vec3(0.0).toVar();
397
+ const alpha = float(1.0).toVar();
398
+ If(u.uUseSceneColor.greaterThan(0.5), () => {
399
+ const sceneColor = u.uSceneColor.sample(refractedUv).level(0).rgb;
400
+ const tintedScene = sceneColor.mul(mix(vec3(1.0), u.uShallowColor.mul(1.25), clamp(absorb.mul(0.6), 0.0, 1.0)));
401
+ bodyColor.assign(mix(tintedScene, absorptionTint, absorb));
402
+ alpha.assign(1.0);
403
+ }).Else(() => {
404
+ bodyColor.assign(absorptionTint);
405
+ alpha.assign(clamp(u.uOpacity.add(absorb.mul(0.3)), 0.0, 1.0));
406
+ });
407
+
408
+ // Steep wave faces act like windows, not lenses: drop caustics there,
409
+ // and fade the dappling with view distance (WATER_QUALITY >= 1).
410
+ const caustics = flags.caustics
411
+ ? color.caustics(groundWorld, columnDepth, u.uTime)
412
+ .mul(smoothstep(0.62, 0.85, gerstnerNormalY))
413
+ .mul(smoothstep(14.0, 38.0, viewDistance).oneMinus())
414
+ .toVar()
415
+ : vec3(0.0);
416
+
417
+ // --- foam ---
418
+ const pierce = color.pierceProximity(screenUv, waterViewDistance, vWorldPosition.y).toVar();
419
+ const shoreFoam = foam.shoreFoam(columnDepth, viewDepthDiff, vRestWorldXZ, u.uTime, pierce).toVar();
420
+ const whitecaps = foam.whitecaps(crest, gerstnerNormalY, vRestWorldXZ, u.uTime).toVar();
421
+ const rippleFoamRaw = clamp(
422
+ rippleState.b.mul(u.uRippleFoamStrength).add(abs(rippleState.r).mul(u.uRippleFoamStrength).mul(4.0)),
423
+ 0.0,
424
+ 1.0,
425
+ );
426
+ const foamRaw = clamp(shoreFoam.add(whitecaps).add(rippleFoamRaw).add(vSwash), 0.0, 1.0)
427
+ .mul(clamp(u.uFoamAmount, 0.0, 2.0));
428
+ const foamValue = foam.foamShape(foamRaw, vRestWorldXZ, u.uTime).toVar();
429
+
430
+ // Drifting cloud shadows plus scene shadows (shared sun-shadow pass).
431
+ const cloudShadow = stylizedCloudShadow(
432
+ vRestWorldXZ, u.uTime,
433
+ u.uCloudShadowStrength, u.uCloudShadowCoverage, u.uCloudShadowScale, u.uCloudShadowVelocity,
434
+ );
435
+ const sceneShadow = mix(1.0, sampleEnvironmentSunShadow(vWorldPosition), u.uSceneShadowStrength);
436
+ const sunVisibility = cloudShadow.mul(sceneShadow).toVar();
437
+ bodyColor.addAssign(caustics.mul(foamValue.oneMinus()).mul(sunVisibility));
438
+
439
+ // --- reflection, fresnel, glints ---
440
+ const fresnel = lighting.fresnelFactor(viewDir, surfaceNormal).toVar();
441
+ const reflection = lighting.reflectionColor(vWorldPosition, surfaceNormal, viewDir).toVar();
442
+ const reflectionMix = clamp(fresnel.mul(u.uReflectionStrength), 0.0, 0.92).toVar();
443
+ const litColor = mix(bodyColor, reflection, reflectionMix).toVar();
444
+ litColor.addAssign(u.uFresnelColor.mul(fresnel).mul(fresnel).mul(0.35));
445
+ litColor.addAssign(lighting.specular(viewDir, surfaceNormal, foamValue.mul(0.75).oneMinus()).mul(sunVisibility));
446
+ if (flags.sparkles) {
447
+ litColor.addAssign(
448
+ lighting.sparkles(vRestWorldXZ, surfaceNormal, viewDir, viewDistance, u.uTime)
449
+ .mul(foamValue.oneMinus()).mul(sunVisibility),
450
+ );
451
+ }
452
+ litColor.mulAssign(mix(vec3(0.8, 0.85, 0.94), vec3(1.0), sunVisibility));
453
+
454
+ // --- foam overlay, slightly shaded by the sun for a two-tone toon read ---
455
+ const foamLight = clamp(dot(surfaceNormal, u.uSunDirection), 0.0, 1.0).mul(0.18).add(0.82)
456
+ .mul(sunVisibility.mul(0.18).add(0.82));
457
+ litColor.assign(mix(litColor, u.uFoamColor.mul(foamLight), foamValue));
458
+ alpha.assign(max(alpha, foamValue.mul(0.92)));
459
+ // The swash film is only centimeters deep; give it body.
460
+ alpha.assign(max(alpha, vSwash.mul(0.55)));
461
+ alpha.assign(max(alpha, clamp(reflectionMix.add(fresnel.mul(0.4)), 0.0, 1.0).mul(0.85)));
462
+
463
+ // --- debug visualizations (?waterDebug=...) ---
464
+ // Masked sum instead of an if/else chain (docs/tsl-conventions.md #3);
465
+ // always compiled — every input already lives in the graph.
466
+ const debugEntries = [
467
+ [1, vec4(clamp(viewDepthDiff.div(3.0), 0.0, 1.0), clamp(columnDepth.div(3.0), 0.0, 1.0), absorb, 1.0)],
468
+ [2, vec4(vec3(foamValue), 1.0)],
469
+ [3, vec4(surfaceNormal.mul(0.5).add(0.5), 1.0)],
470
+ [4, vec4(rippleState.r.mul(2.0).add(0.5), rippleState.b, float(0.5).sub(rippleState.r.mul(2.0)), 1.0)],
471
+ [5, vec4(reflection, 1.0)],
472
+ [6, vec4(caustics, 1.0)],
473
+ [7, vec4(
474
+ lighting.specular(viewDir, surfaceNormal, float(1.0))
475
+ .add(lighting.sparkles(vRestWorldXZ, surfaceNormal, viewDir, viewDistance, u.uTime)),
476
+ 1.0,
477
+ )],
478
+ [8, vec4(vec3(fresnel), 1.0)],
479
+ [9, vec4(crest, vCrest, whitecaps, 1.0)],
480
+ ];
481
+ let debugMask = float(0.0);
482
+ let debugSum = vec4(0.0);
483
+ for (const [mode, value] of debugEntries) {
484
+ const entryMask = select(u.uDebugMode.equal(mode), float(1.0), float(0.0));
485
+ debugMask = debugMask.add(entryMask);
486
+ debugSum = debugSum.add(value.mul(entryMask));
487
+ }
488
+ debugMask = debugMask.min(1.0);
489
+ output.assign(vec4(litColor, alpha).mul(debugMask.oneMinus()).add(debugSum));
490
+ });
491
+
492
+ return output;
493
+ })();
494
+
495
+ material.uniforms = u;
496
+ // The classic material's define block, mirrored so shared bookkeeping
497
+ // (applyWaterSettingsToMaterial's quality diff, attachWaveUniforms' wave
498
+ // count check) reads identically on both backends.
499
+ material.defines = {
500
+ WATER_WAVE_COUNT: waveCount,
501
+ WATER_QUALITY: qualityLevel,
502
+ WATER_DETAIL_OCTAVES: detailOctaves,
503
+ WATER_FOAM_OCTAVES: foamOctaves,
504
+ ...(flags.shoaling ? { WATER_SHOALING: 1 } : {}),
505
+ };
506
+ material.userData.isToonNodeMaterial = true;
507
+ material.userData.waterFlags = flags;
508
+ return material;
509
+ }
@@ -0,0 +1,2 @@
1
+ // Stylized sky barrel. Import from '@call-me-sensei/toonlab/sky'.
2
+ export * from './stylizedSky.js';