@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,267 @@
1
+ // TSL port of src/shaders/flower.vert.glsl + flower.frag.glsl — procedural
2
+ // daisy heads riding the grass canopy as camera-facing billboards. Also home
3
+ // to the tree lab's flower-patch materials (textured species heads + toon
4
+ // stems), so every flower path renders through node materials.
5
+
6
+ import * as THREE from 'three';
7
+ import {
8
+ abs,
9
+ atan,
10
+ cameraProjectionMatrix,
11
+ cameraViewMatrix,
12
+ clamp,
13
+ cos,
14
+ Discard,
15
+ dot,
16
+ float,
17
+ Fn,
18
+ instanceIndex,
19
+ length,
20
+ mix,
21
+ modelWorldMatrix,
22
+ normalize,
23
+ positionLocal,
24
+ pow,
25
+ sin,
26
+ smoothstep,
27
+ texture,
28
+ time,
29
+ uniform,
30
+ uv,
31
+ varying,
32
+ vertexColor,
33
+ vec2,
34
+ vec3,
35
+ vec4,
36
+ attribute,
37
+ } from 'three/tsl';
38
+ import { MeshBasicNodeMaterial, MeshToonNodeMaterial, NodeMaterial } from 'three/webgpu';
39
+
40
+ import { sampleEnvironmentSunShadow } from './chunks/environment-sun-shadow.js';
41
+
42
+ export function createFlowerNodeMaterial(settings) {
43
+ const u = {
44
+ uCenterColor: uniform(new THREE.Color()),
45
+ uPetalColor: uniform(new THREE.Color()),
46
+ uShadowStrength: uniform(settings.shadowStrength),
47
+ uTime: uniform(0),
48
+ uWindDirection: uniform(new THREE.Vector2(settings.windDirection[0], settings.windDirection[1])),
49
+ uWindSpeed: uniform(settings.windSpeed),
50
+ uWindStrength: uniform(settings.windStrength),
51
+ };
52
+
53
+ const material = new NodeMaterial();
54
+ material.name = 'StylizedFlowers';
55
+ material.side = THREE.DoubleSide;
56
+ material.fog = true;
57
+
58
+ const vPetal = varying(vec2(), 'vFlowerPetal');
59
+ const vPhase = varying(float(), 'vFlowerPhase');
60
+ const vWorldPosition = varying(vec3(), 'vFlowerWorldPosition');
61
+
62
+ material.vertexNode = Fn(() => {
63
+ const iOrigin = attribute('iOrigin', 'vec3');
64
+ const iInfo = attribute('iInfo', 'vec4');
65
+
66
+ vPetal.assign(positionLocal.xy.mul(2.0));
67
+ vPhase.assign(iInfo.y);
68
+
69
+ const windDirection = normalize(u.uWindDirection.add(vec2(1e-4, 0.0)));
70
+ const phase = u.uTime.mul(u.uWindSpeed).add(iInfo.y.mul(6.2831))
71
+ .add(dot(iOrigin.xz, vec2(0.35, 0.28)));
72
+ const flutter = sin(phase).mul(0.5).add(0.5).add(sin(phase.mul(2.33).add(1.7)).mul(0.3));
73
+ const sway = windDirection.mul(flutter).mul(u.uWindStrength).mul(0.55).mul(iInfo.z);
74
+
75
+ const center = iOrigin.add(vec3(sway.x, iInfo.z, sway.y));
76
+ // viewMatrix rows 0/1 = camera right/up (GLSL viewMatrix[c][r] indexing).
77
+ const cameraRight = vec3(
78
+ cameraViewMatrix.element(0).x,
79
+ cameraViewMatrix.element(1).x,
80
+ cameraViewMatrix.element(2).x,
81
+ );
82
+ const cameraUp = vec3(
83
+ cameraViewMatrix.element(0).y,
84
+ cameraViewMatrix.element(1).y,
85
+ cameraViewMatrix.element(2).y,
86
+ );
87
+ const flowerPosition = center.add(
88
+ cameraRight.mul(positionLocal.x).add(cameraUp.mul(positionLocal.y)).mul(iInfo.x),
89
+ );
90
+
91
+ const worldPosition = modelWorldMatrix.mul(vec4(flowerPosition, 1.0));
92
+ vWorldPosition.assign(worldPosition.xyz);
93
+ return cameraProjectionMatrix.mul(cameraViewMatrix).mul(worldPosition);
94
+ })();
95
+
96
+ material.fragmentNode = Fn(() => {
97
+ const radius = length(vPetal);
98
+ const angle = atan(vPetal.y, vPetal.x);
99
+ // Eight wide petals with narrow notches between them.
100
+ const petalEdge = pow(abs(cos(angle.mul(4.0).add(vPhase.mul(6.2831)))), 0.35)
101
+ .mul(0.58).add(0.42);
102
+ Discard(radius.greaterThan(petalEdge));
103
+
104
+ const centerMask = smoothstep(0.34, 0.2, radius);
105
+ const color = mix(u.uPetalColor, u.uCenterColor, centerMask).toVar();
106
+ // Petals cup slightly: darken toward their outer edge.
107
+ color.mulAssign(radius.div(petalEdge).oneMinus().mul(0.12).add(0.9));
108
+
109
+ // Scene shadows: cool dark tint matched to the grass field's response.
110
+ const sceneShadow = mix(1.0, sampleEnvironmentSunShadow(vWorldPosition), u.uShadowStrength);
111
+ color.mulAssign(mix(vec3(0.44, 0.48, 0.62), vec3(1.0), sceneShadow));
112
+
113
+ return vec4(color, 1.0);
114
+ })();
115
+
116
+ material.uniforms = u;
117
+ return material;
118
+ }
119
+
120
+ // Shared instance-phased sway for the tree lab's flower patch. Heads use the
121
+ // full amplitude; stems scale it by height fraction so a head stays glued to
122
+ // its stem tip (head and stem instances share the same index order, so the
123
+ // phases line up).
124
+ function flowerSway(u) {
125
+ const phase = time.mul(u.uWindSpeed).add(float(instanceIndex).mul(1.618));
126
+ return vec3(sin(phase), 0.0, cos(phase.mul(0.7)).mul(0.5)).mul(u.uWindStrength);
127
+ }
128
+
129
+ /**
130
+ * Unlit cutout material for textured flower heads (species sprites drawn on
131
+ * a canvas, including hand-drawn petals). TSL replacement for the flower
132
+ * patch's old MeshBasicMaterial billboards, plus a gentle wind bob the
133
+ * classic material never had.
134
+ */
135
+ export function createFlowerHeadNodeMaterial({ map, alphaCutoff = 0.4, windSpeed = 1, windStrength = 0 } = {}) {
136
+ const u = {
137
+ uAlphaCutoff: uniform(alphaCutoff),
138
+ uMap: texture(map),
139
+ uWindSpeed: uniform(windSpeed),
140
+ uWindStrength: uniform(windStrength),
141
+ };
142
+
143
+ const material = new MeshBasicNodeMaterial();
144
+ material.name = 'FlowerPatchHead';
145
+ material.side = THREE.DoubleSide;
146
+ material.transparent = true;
147
+
148
+ material.positionNode = positionLocal.add(flowerSway(u));
149
+ material.colorNode = u.uMap; // TextureNode samples at uv() by default
150
+ material.alphaTestNode = u.uAlphaCutoff;
151
+
152
+ material.uniforms = u;
153
+ return material;
154
+ }
155
+
156
+ /**
157
+ * Camera-facing textured bloom material for StylizedFlower plants: instanced
158
+ * billboard quads (iOrigin vec3 + iInfo vec4 = size / roll / sway amplitude)
159
+ * sampling a species head sprite, with the daisy field's wind bob. A bloom
160
+ * never renders edge-on, from any camera.
161
+ */
162
+ export function createFlowerHeadBillboardNodeMaterial({
163
+ map, alphaCutoff = 0.4, windDirection = [1, 0.3], windSpeed = 1, windStrength = 0,
164
+ } = {}) {
165
+ const u = {
166
+ uAlphaCutoff: uniform(alphaCutoff),
167
+ uMap: texture(map),
168
+ uWindDirection: uniform(new THREE.Vector2(windDirection[0], windDirection[1])),
169
+ uWindSpeed: uniform(windSpeed),
170
+ uWindStrength: uniform(windStrength),
171
+ };
172
+
173
+ const material = new NodeMaterial();
174
+ material.name = 'StylizedFlowerHeads';
175
+ material.side = THREE.DoubleSide;
176
+
177
+ const vUv = varying(vec2(), 'vFlowerHeadUv');
178
+
179
+ material.vertexNode = Fn(() => {
180
+ const iOrigin = attribute('iOrigin', 'vec3');
181
+ const iInfo = attribute('iInfo', 'vec4');
182
+ vUv.assign(uv());
183
+
184
+ const windDir = normalize(u.uWindDirection.add(vec2(1e-4, 0.0)));
185
+ const phase = time.mul(u.uWindSpeed).add(iInfo.y.mul(6.2831));
186
+ const flutter = sin(phase).mul(0.5).add(0.5).add(sin(phase.mul(2.33).add(1.7)).mul(0.3));
187
+ const sway = windDir.mul(flutter).mul(u.uWindStrength).mul(iInfo.z);
188
+ const center = iOrigin.add(vec3(sway.x, 0.0, sway.y));
189
+
190
+ // Static per-head roll so repeated petal sprites don't align.
191
+ const roll = iInfo.y.mul(6.2831);
192
+ const rollCos = cos(roll);
193
+ const rollSin = sin(roll);
194
+ const corner = vec2(
195
+ positionLocal.x.mul(rollCos).sub(positionLocal.y.mul(rollSin)),
196
+ positionLocal.x.mul(rollSin).add(positionLocal.y.mul(rollCos)),
197
+ );
198
+
199
+ // viewMatrix rows 0/1 = camera right/up (GLSL viewMatrix[c][r] indexing).
200
+ const cameraRight = vec3(
201
+ cameraViewMatrix.element(0).x,
202
+ cameraViewMatrix.element(1).x,
203
+ cameraViewMatrix.element(2).x,
204
+ );
205
+ const cameraUp = vec3(
206
+ cameraViewMatrix.element(0).y,
207
+ cameraViewMatrix.element(1).y,
208
+ cameraViewMatrix.element(2).y,
209
+ );
210
+ const headPosition = center.add(
211
+ cameraRight.mul(corner.x).add(cameraUp.mul(corner.y)).mul(iInfo.x),
212
+ );
213
+ const worldPosition = modelWorldMatrix.mul(vec4(headPosition, 1.0));
214
+ return cameraProjectionMatrix.mul(cameraViewMatrix).mul(worldPosition);
215
+ })();
216
+
217
+ material.fragmentNode = Fn(() => {
218
+ const sprite = u.uMap.sample(vUv).toVar();
219
+ Discard(sprite.a.lessThan(u.uAlphaCutoff));
220
+ return vec4(sprite.rgb, 1.0);
221
+ })();
222
+
223
+ material.uniforms = u;
224
+ return material;
225
+ }
226
+
227
+ /**
228
+ * Toon material for 3D bloom-head meshes (vertex-colored petals + center
229
+ * from createFlowerHeadGeometry), with the shared instance-phased wind bob.
230
+ */
231
+ export function createFlowerBloomNodeMaterial({ windSpeed = 1, windStrength = 0 } = {}) {
232
+ const u = {
233
+ uWindSpeed: uniform(windSpeed),
234
+ uWindStrength: uniform(windStrength),
235
+ };
236
+
237
+ const material = new MeshToonNodeMaterial({ side: THREE.DoubleSide, vertexColors: true });
238
+ material.name = 'StylizedFlowerBloom';
239
+ material.positionNode = positionLocal.add(flowerSway(u));
240
+ // Petal-tinted floor for unlit faces: cup interiors and shaded petals
241
+ // read as darker petal color instead of toon-band black.
242
+ material.emissiveNode = vertexColor().rgb.mul(0.35);
243
+
244
+ material.uniforms = u;
245
+ return material;
246
+ }
247
+
248
+ /**
249
+ * Toon stem material for ground flowers. Bends with the same sway as the
250
+ * head, scaled by height fraction (root stays planted, tip follows the head).
251
+ */
252
+ export function createFlowerStemNodeMaterial({ color = 0x4d8a3f, height = 1, windSpeed = 1, windStrength = 0 } = {}) {
253
+ const u = {
254
+ uHeight: uniform(Math.max(height, 1e-3)),
255
+ uWindSpeed: uniform(windSpeed),
256
+ uWindStrength: uniform(windStrength),
257
+ };
258
+
259
+ const material = new MeshToonNodeMaterial({ color });
260
+ material.name = 'FlowerPatchStem';
261
+
262
+ const bend = clamp(positionLocal.y.div(u.uHeight), 0.0, 1.0);
263
+ material.positionNode = positionLocal.add(flowerSway(u).mul(bend));
264
+
265
+ material.uniforms = u;
266
+ return material;
267
+ }
@@ -0,0 +1,193 @@
1
+ // TSL port of src/shaders/grass.vert.glsl + grass.frag.glsl — instanced,
2
+ // wind-swayed, character-aware stylized grass blades.
3
+ //
4
+ // Scene-shadow reception (getShadowMask) comes from the shared sun-shadow
5
+ // pass; cloud shadows from the stylized-cloud-shadow chunk. Uniforms keep
6
+ // their GLSL names on `.uniforms` (StylizedGrassField.applySettings writes
7
+ // them by name on both backends).
8
+
9
+ import * as THREE from 'three';
10
+ import {
11
+ abs,
12
+ attribute,
13
+ cameraPosition,
14
+ cameraProjectionMatrix,
15
+ cameraViewMatrix,
16
+ clamp,
17
+ cos,
18
+ Discard,
19
+ distance,
20
+ dot,
21
+ float,
22
+ Fn,
23
+ fract,
24
+ length,
25
+ mix,
26
+ modelWorldMatrix,
27
+ normalize,
28
+ positionLocal,
29
+ pow,
30
+ sin,
31
+ smoothstep,
32
+ step,
33
+ uniform,
34
+ uv,
35
+ varying,
36
+ vec2,
37
+ vec3,
38
+ vec4,
39
+ } from 'three/tsl';
40
+ import { NodeMaterial } from 'three/webgpu';
41
+
42
+ import { sampleEnvironmentSunShadow } from './chunks/environment-sun-shadow.js';
43
+ import { stylizedCloudShadow } from './chunks/stylized-cloud-shadow.js';
44
+
45
+ export function createGrassNodeMaterial(settings) {
46
+ const u = {
47
+ uBacklitStrength: uniform(settings.backlitStrength),
48
+ uBaseColor: uniform(new THREE.Color()),
49
+ uCloudShadowCoverage: uniform(settings.cloudShadowCoverage),
50
+ uCloudShadowScale: uniform(settings.cloudShadowScale),
51
+ uCloudShadowStrength: uniform(settings.cloudShadowStrength),
52
+ uCloudShadowVelocity: uniform(new THREE.Vector2(settings.cloudShadowVelocity[0], settings.cloudShadowVelocity[1])),
53
+ uFadeEnd: uniform(1e6 + 1),
54
+ uFadeStart: uniform(1e6),
55
+ uGustFrequency: uniform(settings.gustFrequency),
56
+ uGustSpeed: uniform(settings.gustSpeed),
57
+ uPushPosition: uniform(new THREE.Vector3(0, -1e5, 0)),
58
+ uPushRadius: uniform(settings.pushRadius),
59
+ uShadowStrength: uniform(settings.shadowStrength),
60
+ uShadowTint: uniform(new THREE.Color()),
61
+ uSkyColor: uniform(new THREE.Color()),
62
+ uSunColor: uniform(new THREE.Color()),
63
+ uSunDirection: uniform(new THREE.Vector3(...settings.sunDirection).normalize()),
64
+ uTime: uniform(0),
65
+ uTipColor: uniform(new THREE.Color()),
66
+ uWindDirection: uniform(new THREE.Vector2(settings.windDirection[0], settings.windDirection[1])),
67
+ uWindSpeed: uniform(settings.windSpeed),
68
+ uWindStrength: uniform(settings.windStrength),
69
+ };
70
+
71
+ const material = new NodeMaterial();
72
+ material.name = 'StylizedGrass';
73
+ material.side = THREE.DoubleSide;
74
+ material.fog = true;
75
+
76
+ const vUv = uv();
77
+ const vJitter = varying(float(), 'vGrassJitter');
78
+ const vGust = varying(float(), 'vGrassGust');
79
+ const vNormal = varying(vec3(), 'vGrassNormal');
80
+ const vWorldPosition = varying(vec3(), 'vGrassWorldPosition');
81
+
82
+ material.vertexNode = Fn(() => {
83
+ const iOrigin = attribute('iOrigin', 'vec3');
84
+ const iInfo = attribute('iInfo', 'vec4');
85
+
86
+ vJitter.assign(fract(iInfo.y.mul(13.73)));
87
+ const heightFraction = vUv.y;
88
+ const facing = vec2(cos(iInfo.w), sin(iInfo.w)).toVar();
89
+ const windDirection = normalize(u.uWindDirection.add(vec2(1e-4, 0.0))).toVar();
90
+
91
+ const bladePosition = vec3(iOrigin).toVar();
92
+ bladePosition.xz.addAssign(facing.mul(positionLocal.x.mul(iInfo.z)));
93
+ bladePosition.y.addAssign(heightFraction.mul(iInfo.x));
94
+
95
+ const bendCurve = heightFraction.mul(heightFraction).toVar();
96
+
97
+ // Static per-blade lean (see grass.vert.glsl).
98
+ const bowDirection = vec2(facing.y.negate(), facing.x);
99
+ const leanDirection = normalize(
100
+ bowDirection.mul(vJitter.sub(0.5))
101
+ .add(facing.mul(fract(iInfo.y.mul(7.31)).sub(0.5)))
102
+ .add(vec2(1e-4, 0.0)),
103
+ ).toVar();
104
+ const leanAmount = fract(iInfo.y.mul(3.17)).mul(0.5).add(0.18).toVar();
105
+ bladePosition.xz.addAssign(leanDirection.mul(leanAmount).mul(bendCurve).mul(iInfo.x));
106
+ bladePosition.y.subAssign(leanAmount.mul(leanAmount).mul(0.4).mul(bendCurve).mul(iInfo.x));
107
+
108
+ // Traveling gust wave.
109
+ const gustPhase = dot(iOrigin.xz, windDirection).mul(u.uGustFrequency)
110
+ .sub(u.uTime.mul(u.uGustSpeed));
111
+ const gust = sin(gustPhase).mul(0.5)
112
+ .add(sin(gustPhase.mul(0.43).add(1.7)).mul(0.3))
113
+ .add(sin(gustPhase.mul(2.3).add(iInfo.y.mul(4.0))).mul(0.2));
114
+ vGust.assign(clamp(gust.mul(0.5).add(0.5), 0.0, 1.0));
115
+
116
+ const phase = u.uTime.mul(u.uWindSpeed).add(iInfo.y.mul(6.2831))
117
+ .add(dot(iOrigin.xz, vec2(0.35, 0.28)));
118
+ const flutter = sin(phase).mul(0.5).add(0.5).add(sin(phase.mul(2.33).add(1.7)).mul(0.3));
119
+ const wind = windDirection.mul(flutter.mul(0.6).add(vGust.mul(1.1))).toVar();
120
+ bladePosition.xz.addAssign(wind.mul(u.uWindStrength).mul(bendCurve).mul(iInfo.x));
121
+
122
+ // Character push.
123
+ const fromPush = bladePosition.xz.sub(u.uPushPosition.xz).toVar();
124
+ const pushDistance = length(fromPush);
125
+ const push = smoothstep(0.0, u.uPushRadius, pushDistance).oneMinus()
126
+ .mul(step(abs(iOrigin.y.sub(u.uPushPosition.y)), 1.8));
127
+ bladePosition.xz.addAssign(
128
+ normalize(fromPush.add(vec2(1e-4, 0.0))).mul(push).mul(0.42).mul(bendCurve).mul(iInfo.x),
129
+ );
130
+ bladePosition.y.subAssign(push.mul(0.14).mul(bendCurve).mul(iInfo.x));
131
+
132
+ // Shared field normal, tilted by the current bend.
133
+ const tilt = wind.mul(u.uWindStrength).add(leanDirection.mul(leanAmount).mul(0.35)).mul(heightFraction);
134
+ vNormal.assign(normalize(vec3(tilt.x.mul(1.4), 1.0, tilt.y.mul(1.4))));
135
+
136
+ // Distance fade to a degenerate point.
137
+ const fadeDistance = distance(modelWorldMatrix.mul(vec4(iOrigin, 1.0)).xz, cameraPosition.xz);
138
+ const fade = smoothstep(u.uFadeStart, u.uFadeEnd, fadeDistance).oneMinus();
139
+ bladePosition.assign(mix(iOrigin, bladePosition, fade));
140
+
141
+ const worldPosition = modelWorldMatrix.mul(vec4(bladePosition, 1.0));
142
+ vWorldPosition.assign(worldPosition.xyz);
143
+ return cameraProjectionMatrix.mul(cameraViewMatrix).mul(worldPosition);
144
+ })();
145
+
146
+ material.fragmentNode = Fn(() => {
147
+ // Rounded taper silhouette.
148
+ const taper = pow(vUv.y, 1.6).mul(0.96).oneMinus();
149
+ const halfWidth = taper.mul(0.5);
150
+ Discard(abs(vUv.x.sub(0.5)).greaterThan(halfWidth));
151
+
152
+ const tipMix = smoothstep(0.1, 0.95, vUv.y.mul(vJitter.mul(0.3).add(0.85))).toVar();
153
+ const color = mix(u.uBaseColor, u.uTipColor, tipMix).toVar();
154
+ color.mulAssign(vJitter.mul(0.2).add(0.9));
155
+
156
+ // Dense-field AO toward the roots.
157
+ color.mulAssign(mix(0.64, 1.0, smoothstep(0.0, 0.62, vUv.y)));
158
+
159
+ const normal = normalize(vNormal);
160
+ const sunDirection = normalize(u.uSunDirection);
161
+ const cloudShadow = stylizedCloudShadow(
162
+ vWorldPosition.xz, u.uTime,
163
+ u.uCloudShadowStrength, u.uCloudShadowCoverage, u.uCloudShadowScale, u.uCloudShadowVelocity,
164
+ );
165
+ const sceneShadow = mix(1.0, sampleEnvironmentSunShadow(vWorldPosition), u.uShadowStrength);
166
+ const sunVisibility = cloudShadow.mul(sceneShadow).toVar();
167
+ const wrap = dot(normal, sunDirection).mul(0.5).add(0.5);
168
+ const litBand = smoothstep(0.44, 0.54, wrap).mul(sunVisibility).toVar();
169
+ color.mulAssign(mix(0.86, 1.06, litBand));
170
+ color.mulAssign(mix(vec3(1.0), u.uSunColor, litBand.mul(0.25)));
171
+
172
+ // Occlusion darkening shared with the terrain response.
173
+ color.mulAssign(mix(u.uShadowTint, vec3(1.0), sunVisibility));
174
+
175
+ // Sky fill + gust sheen.
176
+ color.addAssign(u.uSkyColor.mul(litBand.oneMinus()).mul(0.07));
177
+ const sheen = smoothstep(0.78, 1.0, vGust).mul(tipMix);
178
+ color.addAssign(u.uSunColor.mul(sheen).mul(0.22).mul(litBand));
179
+
180
+ // Backlit translucency.
181
+ const viewDirection = normalize(cameraPosition.sub(vWorldPosition));
182
+ const backlit = pow(clamp(dot(viewDirection, sunDirection.negate()), 0.0, 1.0), 3.0);
183
+ color.addAssign(
184
+ u.uSunColor.mul(backlit).mul(tipMix).mul(u.uBacklitStrength)
185
+ .mul(sunVisibility.mul(0.65).add(0.35)),
186
+ );
187
+
188
+ return vec4(color, 1.0);
189
+ })();
190
+
191
+ material.uniforms = u;
192
+ return material;
193
+ }