@call-me-sensei/toonlab 0.2.0 → 0.3.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.
Files changed (205) hide show
  1. package/AGENTS.md +127 -4
  2. package/ATTRIBUTION.md +41 -3
  3. package/README.md +234 -34
  4. package/docs/characters.md +144 -0
  5. package/docs/debug-panel.md +126 -0
  6. package/docs/docs.css +589 -0
  7. package/docs/environment.md +186 -0
  8. package/docs/getting-started.md +180 -0
  9. package/docs/index.html +14 -0
  10. package/docs/lab-architecture.md +100 -0
  11. package/docs/lighting.md +825 -0
  12. package/docs/main.jsx +739 -0
  13. package/docs/mcp.md +97 -0
  14. package/docs/post-processing.md +98 -0
  15. package/docs/settings-reference.md +1878 -0
  16. package/docs/shader-constants.md +77 -0
  17. package/docs/sky.md +182 -0
  18. package/docs/style-labs.md +309 -0
  19. package/docs/texture-lab.md +135 -0
  20. package/docs/toon-shading.md +183 -0
  21. package/docs/tsl-conventions.md +167 -0
  22. package/docs/vegetation-sky.md +275 -0
  23. package/docs/water.md +430 -0
  24. package/docs/weather.md +200 -0
  25. package/docs/world-scale.md +111 -0
  26. package/mcp/server.mjs +610 -0
  27. package/mcp/style-lab-tools.mjs +371 -0
  28. package/mcp/vite-plugin.mjs +174 -0
  29. package/mcp/workspace.mjs +397 -0
  30. package/package.json +64 -5
  31. package/src/ambientfx/INTEGRATION.md +164 -0
  32. package/src/ambientfx/ambientFxPresets.js +83 -0
  33. package/src/ambientfx/ambientFxSettings.js +368 -0
  34. package/src/ambientfx/emitters.js +169 -0
  35. package/src/ambientfx/index.js +5 -0
  36. package/src/ambientfx/particleBackbone.js +493 -0
  37. package/src/ambientfx/stylizedAmbientFx.js +450 -0
  38. package/src/assetlib/ambientcg.js +117 -0
  39. package/src/assetlib/assetRef.js +91 -0
  40. package/src/assetlib/importedEntry.js +40 -0
  41. package/src/assetlib/index.js +23 -0
  42. package/src/assetlib/kaykit.js +192 -0
  43. package/src/assetlib/kaykitStaticIndex.js +700 -0
  44. package/src/assetlib/loadImported.js +143 -0
  45. package/src/assetlib/opensource3d.js +113 -0
  46. package/src/assetlib/polyhaven.js +182 -0
  47. package/src/assetlib/polypizza.js +115 -0
  48. package/src/assetlib/smithsonian.js +214 -0
  49. package/src/assetlib/sources.js +279 -0
  50. package/src/assetlib/zip.js +58 -0
  51. package/src/biome/biomeGenerator.js +385 -0
  52. package/src/biome/biomeRuntime.js +299 -0
  53. package/src/biome/index.js +2 -0
  54. package/src/buildinggen/buildingAsset.js +44 -0
  55. package/src/buildinggen/buildingGrammar.js +311 -0
  56. package/src/buildinggen/buildingMesh.js +451 -0
  57. package/src/buildinggen/buildingPresets.js +46 -0
  58. package/src/buildinggen/buildingRecipe.js +100 -0
  59. package/src/buildinggen/buildingSettings.js +238 -0
  60. package/src/buildinggen/index.js +6 -0
  61. package/src/camera/cameraDirector.js +157 -0
  62. package/src/camera/cameraGenerator.js +367 -0
  63. package/src/camera/cameraRig.js +570 -0
  64. package/src/camera/cameraSettings.js +236 -0
  65. package/src/camera/index.js +7 -0
  66. package/src/catalog/builtinEntries.js +245 -0
  67. package/src/catalog/catalog.js +210 -0
  68. package/src/catalog/index.js +3 -0
  69. package/src/catalog/manifest.js +84 -0
  70. package/src/core/generation.js +529 -0
  71. package/src/environment/environmentRigs.js +21 -1
  72. package/src/environment/environmentSettings.js +4 -0
  73. package/src/environment/environmentSunShadowPass.js +8 -0
  74. package/src/fauna/INTEGRATION.md +174 -0
  75. package/src/fauna/boids.js +861 -0
  76. package/src/fauna/faunaBodies.js +492 -0
  77. package/src/fauna/faunaPresets.js +52 -0
  78. package/src/fauna/faunaSettings.js +525 -0
  79. package/src/fauna/index.js +5 -0
  80. package/src/fauna/stylizedFauna.js +395 -0
  81. package/src/game-feel/gameFeelGenerator.js +402 -0
  82. package/src/game-feel/gameFeelRuntime.js +549 -0
  83. package/src/game-feel/index.js +2 -0
  84. package/src/index.js +17 -6
  85. package/src/lighting/colorIntensity.js +177 -0
  86. package/src/lighting/index.js +161 -0
  87. package/src/lighting/lightDescriptors.js +249 -0
  88. package/src/lighting/lightingCapabilities.js +79 -0
  89. package/src/lighting/lightingDocuments.js +247 -0
  90. package/src/lighting/lightingFixtures.js +446 -0
  91. package/src/lighting/lightingGenerator.js +449 -0
  92. package/src/lighting/lightingPresets.js +319 -0
  93. package/src/lighting/lightingRuntime.js +723 -0
  94. package/src/lighting/lightingStyle.js +386 -0
  95. package/src/lighting/lightingSystem.js +774 -0
  96. package/src/lighting/unrealExport.js +186 -0
  97. package/src/lighting/utils.js +87 -0
  98. package/src/motion/index.js +5 -0
  99. package/src/motion/motionClip.js +441 -0
  100. package/src/motion/motionController.js +628 -0
  101. package/src/motion/motionDocuments.js +225 -0
  102. package/src/motion/motionGraph.js +307 -0
  103. package/src/motion/motionSettings.js +222 -0
  104. package/src/pathgen/index.js +7 -0
  105. package/src/pathgen/pathBridge.js +232 -0
  106. package/src/pathgen/pathPresets.js +35 -0
  107. package/src/pathgen/pathRibbon.js +410 -0
  108. package/src/pathgen/pathRouter.js +380 -0
  109. package/src/pathgen/pathSettings.js +335 -0
  110. package/src/pathgen/pathTextures.js +123 -0
  111. package/src/pathgen/stylizedPaths.js +453 -0
  112. package/src/post/index.js +1 -0
  113. package/src/post/postGenerator.js +177 -0
  114. package/src/post/postProcessing.js +41 -0
  115. package/src/propgen/generatorsWave1.js +379 -0
  116. package/src/propgen/generatorsWave2.js +462 -0
  117. package/src/propgen/index.js +5 -0
  118. package/src/propgen/propAsset.js +323 -0
  119. package/src/propgen/propParts.js +170 -0
  120. package/src/propgen/propPlacement.js +459 -0
  121. package/src/propgen/propPresets.js +82 -0
  122. package/src/propgen/propSettings.js +395 -0
  123. package/src/shaders-tsl/chunks/projected-water-caustics.js +242 -0
  124. package/src/shaders-tsl/chunks/vegetation-style.js +360 -0
  125. package/src/shaders-tsl/chunks/water-shore-state.js +31 -0
  126. package/src/shaders-tsl/chunks/water-waves.js +90 -11
  127. package/src/shaders-tsl/environment.js +15 -1
  128. package/src/shaders-tsl/flower.js +279 -30
  129. package/src/shaders-tsl/grass.js +60 -33
  130. package/src/shaders-tsl/sky.js +125 -31
  131. package/src/shaders-tsl/tree-leaf.js +61 -25
  132. package/src/shaders-tsl/water-breaker.js +7 -4
  133. package/src/shaders-tsl/water-shore-state-simulation.js +523 -0
  134. package/src/shaders-tsl/water.js +439 -49
  135. package/src/shaders-tsl/woody-surface.js +154 -0
  136. package/src/sky/sceneOverrideLayers.js +10 -0
  137. package/src/sky/skyQuality.js +26 -0
  138. package/src/sky/stylizedSky.js +753 -45
  139. package/src/soundscape/index.js +4 -0
  140. package/src/soundscape/soundscapeGenerator.js +179 -0
  141. package/src/soundscape/soundscapeRuntime.js +806 -0
  142. package/src/soundscape/soundscapeSettings.js +292 -0
  143. package/src/styles/index.js +13 -0
  144. package/src/styles/styleBundle.js +325 -0
  145. package/src/stylizedTerrain.js +32 -2
  146. package/src/stylizedWorld.js +423 -20
  147. package/src/texgen/evaluateTexture.js +675 -0
  148. package/src/texgen/index.js +60 -0
  149. package/src/texgen/noise2.js +210 -0
  150. package/src/texgen/textureAi.js +436 -0
  151. package/src/texgen/textureGenerators.js +516 -0
  152. package/src/texgen/texturePresets.js +490 -0
  153. package/src/texgen/textureSettings.js +342 -0
  154. package/src/texgen/textureThree.js +59 -0
  155. package/src/vegetation/flowerSpecies.js +15 -3
  156. package/src/vegetation/grassPalettes.js +153 -0
  157. package/src/vegetation/index.js +6 -0
  158. package/src/vegetation/stylizedBush.js +2 -0
  159. package/src/vegetation/stylizedFlower.js +82 -0
  160. package/src/vegetation/stylizedFlowers.js +48 -7
  161. package/src/vegetation/stylizedForest.js +29 -1
  162. package/src/vegetation/stylizedGrass.js +291 -56
  163. package/src/vegetation/stylizedTree.js +38 -2
  164. package/src/vegetation/stylizedTreeFoliage.js +2 -1
  165. package/src/vegetation/vegetationShaders.js +1110 -0
  166. package/src/vfxgen/INTEGRATION.md +145 -0
  167. package/src/vfxgen/core/burstBackbone.js +380 -0
  168. package/src/vfxgen/core/projectileCore.js +92 -0
  169. package/src/vfxgen/core/spriteShapes.js +98 -0
  170. package/src/vfxgen/core/trailRibbon.js +272 -0
  171. package/src/vfxgen/core/vfxRandom.js +29 -0
  172. package/src/vfxgen/effects/emitHelpers.js +37 -0
  173. package/src/vfxgen/effects/magicEffects.js +162 -0
  174. package/src/vfxgen/effects/movementEffects.js +87 -0
  175. package/src/vfxgen/effects/weaponEffects.js +118 -0
  176. package/src/vfxgen/index.js +18 -0
  177. package/src/vfxgen/moves/moveController.js +146 -0
  178. package/src/vfxgen/moves/moveLibrary.js +335 -0
  179. package/src/vfxgen/vfxPresets.js +98 -0
  180. package/src/vfxgen/vfxSettings.js +384 -0
  181. package/src/vfxgen/vfxSystem.js +449 -0
  182. package/src/vfxgen/weapons/stylizedWeapons.js +137 -0
  183. package/src/villagegen/index.js +4 -0
  184. package/src/villagegen/stylizedVillage.js +490 -0
  185. package/src/villagegen/villageArchetypes.js +160 -0
  186. package/src/villagegen/villageNames.js +40 -0
  187. package/src/villagegen/villageSites.js +105 -0
  188. package/src/water/sceneOverrideLayers.js +23 -0
  189. package/src/water/water.js +5 -0
  190. package/src/water/waterBreakerSystem.js +15 -1
  191. package/src/water/waterCurrentField.js +447 -0
  192. package/src/water/waterMaterial.js +12 -0
  193. package/src/water/waterNearshorePhase.js +320 -0
  194. package/src/water/waterScenePasses.js +83 -30
  195. package/src/water/waterSettings.js +325 -13
  196. package/src/water/waterShoreMaterial.js +322 -0
  197. package/src/water/waterShoreStateField.js +605 -0
  198. package/src/water/waterSurface.js +797 -28
  199. package/src/weather/index.js +6 -0
  200. package/src/weather/weatherPrecipitation.js +221 -0
  201. package/src/weather/weatherPresets.js +258 -0
  202. package/src/weather/weatherSettings.js +269 -0
  203. package/src/weather/weatherSystem.js +871 -0
  204. package/src/worldMinimap.js +62 -0
  205. package/src/worldPresets.js +4 -1
@@ -33,30 +33,63 @@ import {
33
33
  step,
34
34
  uniform,
35
35
  vec2,
36
- vec3,
37
36
  vec4,
38
37
  } from 'three/tsl';
39
38
  import { MeshBasicNodeMaterial } from 'three/webgpu';
40
39
 
41
40
  import { waterFbm, waterHash12, waterToonStep, waterVoronoi2 } from './chunks/water-common.js';
41
+ import { resolveSkyQuality } from '../sky/skyQuality.js';
42
42
 
43
- export function createSkyNodeMaterial() {
43
+ export function createSkyNodeMaterial({ quality = 'high' } = {}) {
44
+ const qualitySettings = resolveSkyQuality(quality);
44
45
  const uniforms = {
45
46
  uCloudColor: uniform(new THREE.Color()),
46
47
  uCloudCoverage: uniform(0),
48
+ uCloudDirection: uniform(new THREE.Vector2(1, 0.2857142857)),
49
+ uCloudEdgeOpacity: uniform(0),
50
+ uCloudHorizonFade: uniform(0),
51
+ uCloudLightOffset: uniform(0),
52
+ uCloudOpacity: uniform(0),
53
+ uCloudProjection: uniform(0),
47
54
  uCloudScale: uniform(0),
55
+ uCloudSeed: uniform(0),
48
56
  uCloudShadeColor: uniform(new THREE.Color()),
57
+ uCloudShadeSoftness: uniform(0),
58
+ uCloudShadeStrength: uniform(0),
59
+ uCloudShadeThreshold: uniform(0),
60
+ uCloudSilverLiningStrength: uniform(0),
61
+ uCloudSoftness: uniform(0),
49
62
  uCloudSpeed: uniform(0),
63
+ uCloudSunPower: uniform(0),
50
64
  uGroundColor: uniform(new THREE.Color()),
65
+ uGroundExponent: uniform(0),
66
+ uHorizonBandSize: uniform(0),
51
67
  uHorizonColor: uniform(new THREE.Color()),
52
68
  uHorizonScattering: uniform(0),
69
+ uHorizonSunPower: uniform(0),
70
+ uStarsColor: uniform(new THREE.Color()),
71
+ uStarsDensity: uniform(0),
72
+ uStarsHorizonFade: uniform(0),
73
+ uStarsScale: uniform(0),
74
+ uStarsSeed: uniform(0),
75
+ uStarsSize: uniform(0),
53
76
  uStarsStrength: uniform(0),
77
+ uStarsTwinkleSpeed: uniform(0),
78
+ uStarsTwinkleStrength: uniform(0),
79
+ uSunCloudOcclusionStrength: uniform(0),
54
80
  uSunColor: uniform(new THREE.Color()),
81
+ uSunDiscSoftness: uniform(0),
82
+ uSunDiscIntensity: uniform(0),
55
83
  uSunDirection: uniform(new THREE.Vector3(0, 1, 0)),
84
+ uSunGlowBroadStrength: uniform(0),
85
+ uSunGlowCoreSharpness: uniform(0),
86
+ uSunGlowCoreStrength: uniform(0),
87
+ uSunGlowSpread: uniform(0),
56
88
  uSunGlowStrength: uniform(0),
57
89
  uSunSize: uniform(0),
58
90
  uTime: uniform(0),
59
91
  uZenithColor: uniform(new THREE.Color()),
92
+ uZenithExponent: uniform(0),
60
93
  };
61
94
 
62
95
  const material = new MeshBasicNodeMaterial({
@@ -66,6 +99,7 @@ export function createSkyNodeMaterial() {
66
99
  material.name = 'StylizedSky';
67
100
  material.fog = false;
68
101
  material.lights = false;
102
+ material.userData.skyQuality = { ...qualitySettings };
69
103
 
70
104
  // Vertex stage: dome pinned to the far plane regardless of sphere radius,
71
105
  // so it never clips scene geometry and works inside reflection passes.
@@ -85,30 +119,52 @@ export function createSkyNodeMaterial() {
85
119
  const up = direction.y.toVar();
86
120
 
87
121
  // Base vertical gradient with a mirrored ground fade below the horizon.
88
- const zenithMix = pow(clamp(up, 0.0, 1.0), 0.48);
122
+ const zenithMix = pow(clamp(up, 0.0, 1.0), uniforms.uZenithExponent);
89
123
  const color = select(
90
124
  up.greaterThanEqual(0.0),
91
125
  mix(uniforms.uHorizonColor, uniforms.uZenithColor, zenithMix),
92
- mix(uniforms.uHorizonColor, uniforms.uGroundColor, pow(clamp(up.negate(), 0.0, 1.0), 0.55)),
126
+ mix(
127
+ uniforms.uHorizonColor,
128
+ uniforms.uGroundColor,
129
+ pow(clamp(up.negate(), 0.0, 1.0), uniforms.uGroundExponent),
130
+ ),
93
131
  ).toVar();
94
132
 
95
133
  // Sun disc and glow.
96
134
  const cosAngle = dot(direction, uniforms.uSunDirection).toVar();
97
135
  const angle = acos(clamp(cosAngle, -1.0, 1.0));
98
- const sunDisc = smoothstep(uniforms.uSunSize.mul(0.5), uniforms.uSunSize, angle)
136
+ const sunCloudVisibility = smoothstep(0.55, 0.82, uniforms.uCloudCoverage).oneMinus();
137
+ const sunVisibility = mix(1.0, sunCloudVisibility, uniforms.uSunCloudOcclusionStrength);
138
+ const safeSunSize = max(uniforms.uSunSize, 0.00001);
139
+ const sunDisc = smoothstep(
140
+ safeSunSize.mul(uniforms.uSunDiscSoftness.oneMinus()),
141
+ safeSunSize,
142
+ angle,
143
+ )
99
144
  .oneMinus()
100
- .mul(smoothstep(0.55, 0.82, uniforms.uCloudCoverage).oneMinus());
101
- const sunGlow = pow(max(cosAngle, 0.0), 5.0).mul(0.16)
102
- .add(pow(max(cosAngle, 0.0), 60.0).mul(0.5))
145
+ .mul(sunVisibility)
146
+ .mul(step(0.00001, uniforms.uSunSize));
147
+ const sunGlow = pow(max(cosAngle, 0.0), uniforms.uSunGlowSpread)
148
+ .mul(uniforms.uSunGlowBroadStrength)
149
+ .add(
150
+ pow(max(cosAngle, 0.0), uniforms.uSunGlowCoreSharpness)
151
+ .mul(uniforms.uSunGlowCoreStrength),
152
+ )
103
153
  .toVar();
104
154
  color.addAssign(
105
- uniforms.uSunColor.mul(sunDisc.mul(2.4).add(sunGlow.mul(uniforms.uSunGlowStrength))),
155
+ uniforms.uSunColor.mul(
156
+ sunDisc.mul(uniforms.uSunDiscIntensity)
157
+ .add(sunGlow.mul(uniforms.uSunGlowStrength)),
158
+ ),
106
159
  );
107
160
 
108
161
  // Atmospheric scattering hint: a warm wedge along the sun-side horizon
109
162
  // that fades with altitude.
110
- const horizonBand = smoothstep(0.0, 0.42, up.abs()).oneMinus();
111
- const sunward = pow(clamp(cosAngle.mul(0.5).add(0.5), 0.0, 1.0), 5.0);
163
+ const horizonBand = smoothstep(0.0, uniforms.uHorizonBandSize, up.abs()).oneMinus();
164
+ const sunward = pow(
165
+ clamp(cosAngle.mul(0.5).add(0.5), 0.0, 1.0),
166
+ uniforms.uHorizonSunPower,
167
+ );
112
168
  color.addAssign(
113
169
  uniforms.uSunColor
114
170
  .mul(horizonBand)
@@ -121,23 +177,29 @@ export function createSkyNodeMaterial() {
121
177
  // The If guard matches the GLSL branch — it also keeps the projected UV
122
178
  // division (up + 0.28 crosses zero below the horizon) out of reach.
123
179
  If(uniforms.uStarsStrength.greaterThan(0.001).and(up.greaterThan(0.02)), () => {
124
- const starUv = direction.xz.div(up.add(0.28)).mul(14.0);
180
+ const starUv = direction.xz
181
+ .div(up.add(0.28))
182
+ .mul(uniforms.uStarsScale)
183
+ .add(vec2(uniforms.uStarsSeed.mul(1.37), uniforms.uStarsSeed.mul(2.11)));
125
184
  const starVoro = waterVoronoi2(starUv);
126
185
  const starRandom = waterHash12(starVoro.zw);
127
- const twinkle = uniforms.uTime
186
+ const twinkleWave = uniforms.uTime
187
+ .mul(uniforms.uStarsTwinkleSpeed)
128
188
  .mul(starRandom.mul(2.4).add(1.2))
129
189
  .add(starRandom.mul(31.0))
130
- .sin()
131
- .mul(0.4)
132
- .add(0.6);
133
- const star = smoothstep(0.0, 0.06, starVoro.x).oneMinus().mul(step(0.72, starRandom));
190
+ .sin();
191
+ const twinkleAmount = uniforms.uStarsTwinkleStrength.mul(0.5);
192
+ const twinkle = twinkleWave.mul(twinkleAmount).add(float(1.0).sub(twinkleAmount));
193
+ const star = smoothstep(0.0, uniforms.uStarsSize, starVoro.x)
194
+ .oneMinus()
195
+ .mul(step(uniforms.uStarsDensity.oneMinus(), starRandom));
134
196
  color.addAssign(
135
- vec3(1.0, 0.98, 0.92)
197
+ uniforms.uStarsColor
136
198
  .mul(star)
137
199
  .mul(twinkle)
138
200
  .mul(uniforms.uStarsStrength)
139
- .mul(smoothstep(0.03, 0.24, up))
140
- .mul(sunGlow.oneMinus()),
201
+ .mul(smoothstep(0.03, uniforms.uStarsHorizonFade, up))
202
+ .mul(clamp(sunGlow, 0.0, 1.0).oneMinus()),
141
203
  );
142
204
  });
143
205
 
@@ -145,23 +207,55 @@ export function createSkyNodeMaterial() {
145
207
  // silver lining toward the sun, projected onto a virtual cloud plane.
146
208
  If(uniforms.uCloudCoverage.greaterThan(0.001).and(up.greaterThan(0.015)), () => {
147
209
  const cloudUv = direction.xz
148
- .div(up.add(0.22))
210
+ .div(up.add(uniforms.uCloudProjection))
149
211
  .mul(uniforms.uCloudScale)
150
- .add(uniforms.uTime.mul(uniforms.uCloudSpeed).mul(vec2(0.021, 0.006)))
212
+ .add(
213
+ uniforms.uTime
214
+ .mul(uniforms.uCloudSpeed)
215
+ .mul(uniforms.uCloudDirection)
216
+ .mul(0.0218403297),
217
+ )
218
+ .add(vec2(uniforms.uCloudSeed.mul(1.37), uniforms.uCloudSeed.mul(2.11)))
151
219
  .toVar();
152
- const cloudBase = waterFbm(cloudUv, 4).toVar();
220
+ const cloudBase = waterFbm(cloudUv, qualitySettings.cloudOctaves).toVar();
153
221
  const threshold = mix(0.74, 0.34, clamp(uniforms.uCloudCoverage, 0.0, 1.0));
154
- const cloudCore = smoothstep(threshold.add(0.1), threshold.add(0.24), cloudBase);
155
- const cloudEdge = smoothstep(threshold, threshold.add(0.1), cloudBase);
156
- const cloudMask = max(cloudCore, cloudEdge.mul(0.65));
222
+ const cloudCore = smoothstep(
223
+ threshold.add(uniforms.uCloudSoftness),
224
+ threshold.add(uniforms.uCloudSoftness.mul(2.4)),
225
+ cloudBase,
226
+ );
227
+ const cloudEdge = smoothstep(
228
+ threshold,
229
+ threshold.add(uniforms.uCloudSoftness),
230
+ cloudBase,
231
+ );
232
+ const cloudMask = max(
233
+ cloudCore,
234
+ cloudEdge.mul(uniforms.uCloudEdgeOpacity),
235
+ ).mul(uniforms.uCloudOpacity);
157
236
 
158
- const litSample = waterFbm(cloudUv.sub(uniforms.uSunDirection.xz.mul(0.4)), 4);
159
- const shade = waterToonStep(float(0.02), float(0.06), cloudBase.sub(litSample));
160
- const cloudColor = mix(uniforms.uCloudColor, uniforms.uCloudShadeColor, shade.mul(0.85))
161
- .add(uniforms.uSunColor.mul(pow(max(cosAngle, 0.0), 10.0)).mul(0.3))
237
+ const litSample = waterFbm(
238
+ cloudUv.sub(uniforms.uSunDirection.xz.mul(uniforms.uCloudLightOffset)),
239
+ qualitySettings.cloudOctaves,
240
+ );
241
+ const shade = waterToonStep(
242
+ uniforms.uCloudShadeThreshold,
243
+ uniforms.uCloudShadeSoftness,
244
+ cloudBase.sub(litSample),
245
+ );
246
+ const cloudColor = mix(
247
+ uniforms.uCloudColor,
248
+ uniforms.uCloudShadeColor,
249
+ shade.mul(uniforms.uCloudShadeStrength),
250
+ )
251
+ .add(
252
+ uniforms.uSunColor
253
+ .mul(pow(max(cosAngle, 0.0), uniforms.uCloudSunPower))
254
+ .mul(uniforms.uCloudSilverLiningStrength),
255
+ )
162
256
  .toVar();
163
257
 
164
- const horizonFade = smoothstep(0.015, 0.16, up);
258
+ const horizonFade = smoothstep(0.015, uniforms.uCloudHorizonFade, up);
165
259
  color.assign(mix(color, cloudColor, cloudMask.mul(horizonFade)));
166
260
  });
167
261
 
@@ -48,6 +48,13 @@ import { NodeMaterial } from 'three/webgpu';
48
48
  import { sampleEnvironmentSunShadow } from './chunks/environment-sun-shadow.js';
49
49
  import { stylizedCloudShadow } from './chunks/stylized-cloud-shadow.js';
50
50
  import { applyFoliageFog, createFoliageFogUniforms } from './chunks/foliage-fog.js';
51
+ import {
52
+ createVegetationStyleUniforms,
53
+ shadeVegetationSurface,
54
+ tagVegetationRole,
55
+ vegetationBand,
56
+ vegetationVisibility,
57
+ } from './chunks/vegetation-style.js';
51
58
 
52
59
  // Shared card-placement vertex logic (color + depth variants). Returns the
53
60
  // clip position; assigns the provided varyings when given.
@@ -101,10 +108,10 @@ function buildLeafVertexNode(u, { vUv, vWorldNormal, vWorldPosition, vTint, vHei
101
108
  })();
102
109
  }
103
110
 
104
- export function createTreeLeafNodeMaterial(settings) {
111
+ export function createTreeLeafNodeMaterial(settings, vegetationShader = null) {
112
+ const styleUniforms = createVegetationStyleUniforms(vegetationShader, 'foliageCard');
105
113
  const u = {
106
114
  uAlphaCutoff: uniform(settings.alphaCutoff ?? 0.5),
107
- uBacklitStrength: uniform(settings.backlitStrength ?? 0),
108
115
  uCloudShadowCoverage: uniform(settings.cloudShadowCoverage ?? 0),
109
116
  uCloudShadowScale: uniform(settings.cloudShadowScale ?? 1),
110
117
  uCloudShadowStrength: uniform(settings.cloudShadowStrength ?? 0),
@@ -122,7 +129,12 @@ export function createTreeLeafNodeMaterial(settings) {
122
129
  uWindSpeed: uniform(settings.windSpeed ?? 1),
123
130
  uWindStrength: uniform(settings.windStrength ?? 0.06),
124
131
  ...createFoliageFogUniforms(),
132
+ ...styleUniforms,
125
133
  };
134
+ if (!vegetationShader) {
135
+ u.uStyleFoliageBacklitStrength.value = settings.backlitStrength ?? 0;
136
+ }
137
+ u.uBacklitStrength = u.uStyleFoliageBacklitStrength;
126
138
 
127
139
  const material = new NodeMaterial();
128
140
  material.name = 'StylizedTreeFoliage';
@@ -151,41 +163,65 @@ export function createTreeLeafNodeMaterial(settings) {
151
163
  u.uCloudShadowStrength, u.uCloudShadowCoverage, u.uCloudShadowScale, u.uCloudShadowVelocity,
152
164
  );
153
165
  const sceneShadow = mix(1.0, sampleEnvironmentSunShadow(vWorldPosition), u.uSceneShadowStrength).toVar();
154
- const sunVisibility = cloudShadow.mul(sceneShadow).toVar();
166
+ const sunVisibility = vegetationVisibility(
167
+ sceneShadow,
168
+ cloudShadow,
169
+ u.uStyleFoliageSceneShadowResponse,
170
+ u.uStyleFoliageCloudShadowResponse,
171
+ ).toVar();
155
172
 
156
- const litBand = smoothstep(0.38, 0.56, wrap).mul(sunVisibility).toVar();
157
- const crestBand = smoothstep(0.66, 0.78, wrap).mul(sunVisibility)
173
+ const litBand = vegetationBand(
174
+ wrap,
175
+ u.uStyleFoliageBandThreshold,
176
+ u.uStyleFoliageBandSoftness,
177
+ ).mul(sunVisibility).toVar();
178
+ const crestBand = vegetationBand(
179
+ wrap,
180
+ u.uStyleFoliageCrestThreshold,
181
+ u.uStyleFoliageCrestSoftness,
182
+ ).mul(sunVisibility)
158
183
  .mul(smoothstep(0.3, 0.8, vHeightT.mul(normal.y.mul(0.5).add(0.5).mul(0.3).add(0.7))));
159
184
 
160
185
  const color = mix(u.uShadowColor, u.uLitColor, litBand).toVar();
161
186
  color.assign(mix(color, u.uCrownColor, crestBand));
162
- color.mulAssign(mix(vec3(1.0), u.uSunColor, litBand.mul(0.35)));
163
187
 
164
188
  // Baked per-leaf luminance + per-card jitter.
165
- color.mulAssign(sprite.r.mul(0.36).add(0.78));
166
- color.mulAssign(vTint.mul(0.16).add(0.92));
167
-
168
- // Sky fill + silhouette rim.
169
- color.addAssign(u.uSkyColor.mul(normal.y.mul(0.5).add(0.5)).mul(litBand.oneMinus()).mul(0.10));
170
- const viewDirection = normalize(cameraPosition.sub(vWorldPosition));
171
- const rim = pow(abs(dot(normal, viewDirection)).oneMinus(), 3.0);
172
- color.addAssign(u.uSkyColor.mul(rim).mul(0.12).mul(vHeightT.mul(0.6).add(0.4)));
189
+ color.mulAssign(sprite.r.mul(u.uStyleFoliageSpriteLuminanceStrength)
190
+ .add(u.uStyleFoliageSpriteLuminanceStrength.mul(0.611111).oneMinus()));
191
+ color.mulAssign(vTint.sub(0.5).mul(u.uStyleFoliageCardVariationStrength).add(1.0));
173
192
 
174
- // Backlit translucency.
175
- const backlit = pow(clamp(dot(viewDirection, sunDirection.negate()), 0.0, 1.0), 4.0);
176
- color.addAssign(
177
- u.uSunColor.mul(backlit).mul(u.uBacklitStrength)
178
- .mul(sprite.r.mul(0.7).add(0.3))
179
- .mul(sunVisibility.mul(0.65).add(0.35)),
180
- );
193
+ const shaded = shadeVegetationSurface({
194
+ baseColor: color,
195
+ bandSoftness: u.uStyleFoliageBandSoftness,
196
+ bandThreshold: u.uStyleFoliageBandThreshold,
197
+ cloudShadow,
198
+ cloudShadowResponse: u.uStyleFoliageCloudShadowResponse,
199
+ normal,
200
+ sceneShadow,
201
+ sceneShadowResponse: u.uStyleFoliageSceneShadowResponse,
202
+ shadowFloor: 1,
203
+ skyColor: u.uSkyColor,
204
+ sunColor: u.uSunColor,
205
+ sunDirection,
206
+ transmissionMultiplier: u.uStyleFoliageBacklitStrength.div(0.35)
207
+ .mul(sprite.r.mul(0.7).add(0.3)),
208
+ transmissionPower: u.uStyleThinSurfaceTransmissionPower
209
+ .mul(u.uStyleFoliageTransmissionPowerMultiplier),
210
+ u,
211
+ worldPosition: vWorldPosition,
212
+ });
181
213
 
182
214
  // Occluded crowns read clearly darker.
183
- color.mulAssign(mix(0.8, 1.0, sceneShadow));
215
+ shaded.color.mulAssign(mix(
216
+ u.uStyleFoliageCrownOcclusionStrength.oneMinus(),
217
+ 1.0,
218
+ sceneShadow,
219
+ ));
184
220
 
185
221
  // Manual linear scene fog on the true billboarded depth (see foliage-fog).
186
- color.assign(applyFoliageFog(color, vViewZ, u));
222
+ shaded.color.assign(applyFoliageFog(shaded.color, vViewZ, u));
187
223
 
188
- return vec4(color, 1.0);
224
+ return vec4(shaded.color, 1.0);
189
225
  })();
190
226
 
191
227
  material.uniforms = u;
@@ -260,5 +296,5 @@ export function createTreeLeafNodeMaterial(settings) {
260
296
  return depthMaterial;
261
297
  };
262
298
 
263
- return material;
299
+ return tagVegetationRole(material, 'foliageCard', 'cutout');
264
300
  }
@@ -257,7 +257,10 @@ export function createWaterBreakerNodeMaterial({
257
257
  // --- Shell shaping ----------------------------------------------------
258
258
  const amount = clamp(u.uBreakerAmount, 0.0, 1.0).toVar();
259
259
  const capDepth = min(lineDepth, localDepth);
260
- const breakHeight = capDepth.mul(0.9).mul(u.uBreakerScale).mul(amount).mul(endFade).mul(setEnvelope).toVar();
260
+ // Crest elevation is roughly half the depth-limited wave height. Using
261
+ // 0.9*d here treated the entire breaker height as elevation above rest
262
+ // water and made Ocean-on-Beach shells hover like layered ice shelves.
263
+ const breakHeight = capDepth.mul(0.48).mul(u.uBreakerScale).mul(amount).mul(endFade).mul(setEnvelope).toVar();
261
264
  const H = breakHeight.mul(mix(0.12, 1.0, pulse)).mul(post.mul(0.55).oneMinus()).toVar();
262
265
  const R = H.mul(0.52).toVar();
263
266
 
@@ -386,10 +389,10 @@ export function createWaterBreakerNodeMaterial({
386
389
  const faceNoise = waterFbm(
387
390
  vec2(vAlong.mul(1.7), vProfile.mul(4.0).sub(u.uTime.mul(1.6))).mul(noiseScale), 3,
388
391
  ).toVar();
389
- const lipFoam = smoothstep(faceNoise.mul(-0.12).add(0.68), 0.9, vProfile.add(faceNoise.mul(0.08)))
390
- .mul(vPulse).mul(1.4).toVar();
392
+ const lipFoam = smoothstep(faceNoise.mul(-0.08).add(0.82), 0.96, vProfile.add(faceNoise.mul(0.04)))
393
+ .mul(vPulse).mul(1.1).toVar();
391
394
  const streaks = smoothstep(0.62, 0.85, faceNoise)
392
- .mul(smoothstep(0.3, 0.7, vProfile)).mul(vPulse).mul(0.7);
395
+ .mul(smoothstep(0.38, 0.76, vProfile)).mul(vPulse).mul(0.45);
393
396
  const whitewater = vPost.mul(
394
397
  waterFbm(vec2(vAlong.mul(2.3), vProfile.mul(3.0).add(u.uTime.mul(2.2))), 3).mul(0.6).add(0.7),
395
398
  );