@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.
- package/ATTRIBUTION.md +40 -0
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/package.json +97 -0
- package/src/character/characterRig.js +353 -0
- package/src/character/freestyleSwimClip.js +541 -0
- package/src/character/index.js +4 -0
- package/src/character/modelLoader.js +433 -0
- package/src/core/materialRoles.js +438 -0
- package/src/core/presetDocuments.js +110 -0
- package/src/core/shaderBackend.js +24 -0
- package/src/debrisgen/debrisFields.js +544 -0
- package/src/debrisgen/debrisGenerator.js +1929 -0
- package/src/debrisgen/debrisPalettes.js +71 -0
- package/src/debrisgen/debrisPhysics.js +198 -0
- package/src/debrisgen/debrisPresets.js +155 -0
- package/src/debrisgen/debrisSettings.js +333 -0
- package/src/debrisgen/debrisTextures.js +380 -0
- package/src/debrisgen/index.js +5 -0
- package/src/debug/fieldValues.js +95 -0
- package/src/debug/index.js +7 -0
- package/src/debug/settingsPanel.js +170 -0
- package/src/environment/environmentAmbientProbe.js +160 -0
- package/src/environment/environmentMaterialAdapter.js +336 -0
- package/src/environment/environmentMaterialClassifier.js +178 -0
- package/src/environment/environmentPlanarReflection.js +201 -0
- package/src/environment/environmentPresets.js +443 -0
- package/src/environment/environmentRigs.js +684 -0
- package/src/environment/environmentSettings.js +504 -0
- package/src/environment/environmentShaderMaterials.js +318 -0
- package/src/environment/environmentSunShadowPass.js +268 -0
- package/src/environment/environmentTextureResolver.js +161 -0
- package/src/environment/environmentTimeOfDay.js +167 -0
- package/src/environment/environmentVertexAo.js +188 -0
- package/src/environment/index.js +8 -0
- package/src/environment/scanAssetStylize.js +103 -0
- package/src/index.js +21 -0
- package/src/loaders/index.js +1 -0
- package/src/post/index.js +2 -0
- package/src/post/postProcessing.js +1087 -0
- package/src/rockgen/export/glbExport.js +177 -0
- package/src/rockgen/heightfield/heightfieldErosion.js +7 -0
- package/src/rockgen/heightfield/heightfieldPatch.js +183 -0
- package/src/rockgen/heightfield/stylizedErosionSim.js +356 -0
- package/src/rockgen/index.js +14 -0
- package/src/rockgen/mesh/meshAttributes.js +374 -0
- package/src/rockgen/mesh/meshDocument.js +158 -0
- package/src/rockgen/mesh/surfaceNets.js +329 -0
- package/src/rockgen/noise/cellularNoise3.js +92 -0
- package/src/rockgen/noise/prng.js +62 -0
- package/src/rockgen/noise/simplexNoise3.js +89 -0
- package/src/rockgen/noise/valueNoise3.js +87 -0
- package/src/rockgen/rockDocument.js +255 -0
- package/src/rockgen/rockHelpers.js +8 -0
- package/src/rockgen/rockgenPresets.js +559 -0
- package/src/rockgen/rockgenSettings.js +756 -0
- package/src/rockgen/sdf/fieldCompiler.js +544 -0
- package/src/rockgen/sdf/sculptEdits.js +66 -0
- package/src/rockgen/sdf/sdfModifiers.js +45 -0
- package/src/rockgen/sdf/sdfOps.js +37 -0
- package/src/rockgen/sdf/sdfPrimitives.js +88 -0
- package/src/shaders-tsl/anime.js +963 -0
- package/src/shaders-tsl/chunks/character-color.js +64 -0
- package/src/shaders-tsl/chunks/character-highlights.js +174 -0
- package/src/shaders-tsl/chunks/character-lighting.js +309 -0
- package/src/shaders-tsl/chunks/character-material-maps.js +146 -0
- package/src/shaders-tsl/chunks/character-roles.js +52 -0
- package/src/shaders-tsl/chunks/character-scene-lights.js +270 -0
- package/src/shaders-tsl/chunks/character-shadow-color.js +61 -0
- package/src/shaders-tsl/chunks/character-skinning.js +140 -0
- package/src/shaders-tsl/chunks/environment-color.js +47 -0
- package/src/shaders-tsl/chunks/environment-debug.js +79 -0
- package/src/shaders-tsl/chunks/environment-lighting.js +260 -0
- package/src/shaders-tsl/chunks/environment-sun-shadow.js +87 -0
- package/src/shaders-tsl/chunks/foliage-fog.js +60 -0
- package/src/shaders-tsl/chunks/pass-depth-color.js +89 -0
- package/src/shaders-tsl/chunks/stylized-cloud-shadow.js +70 -0
- package/src/shaders-tsl/chunks/water-color.js +190 -0
- package/src/shaders-tsl/chunks/water-common.js +124 -0
- package/src/shaders-tsl/chunks/water-foam.js +93 -0
- package/src/shaders-tsl/chunks/water-lighting.js +116 -0
- package/src/shaders-tsl/chunks/water-ripple.js +64 -0
- package/src/shaders-tsl/chunks/water-waves.js +100 -0
- package/src/shaders-tsl/environment-ao-overlay.js +60 -0
- package/src/shaders-tsl/environment.js +680 -0
- package/src/shaders-tsl/flower.js +267 -0
- package/src/shaders-tsl/grass.js +193 -0
- package/src/shaders-tsl/post-composite.js +461 -0
- package/src/shaders-tsl/sky.js +175 -0
- package/src/shaders-tsl/tree-leaf.js +264 -0
- package/src/shaders-tsl/water-breaker.js +423 -0
- package/src/shaders-tsl/water-kelp.js +171 -0
- package/src/shaders-tsl/water-rain.js +107 -0
- package/src/shaders-tsl/water-simulation.js +140 -0
- package/src/shaders-tsl/water-splash.js +298 -0
- package/src/shaders-tsl/water.js +509 -0
- package/src/sky/index.js +2 -0
- package/src/sky/stylizedSky.js +341 -0
- package/src/toon/characterRenderPasses.js +646 -0
- package/src/toon/index.js +4 -0
- package/src/toon/settings/alphaSettings.js +170 -0
- package/src/toon/settings/averageShadowSettings.js +181 -0
- package/src/toon/settings/baseTextureSettings.js +133 -0
- package/src/toon/settings/celShadeSettings.js +96 -0
- package/src/toon/settings/contactShadowSettings.js +114 -0
- package/src/toon/settings/eyeHighlightSettings.js +187 -0
- package/src/toon/settings/faceLightingSettings.js +135 -0
- package/src/toon/settings/furSettings.js +112 -0
- package/src/toon/settings/glitterSettings.js +102 -0
- package/src/toon/settings/hairHighlightSettings.js +325 -0
- package/src/toon/settings/indirectLightSettings.js +244 -0
- package/src/toon/settings/localLightSettings.js +176 -0
- package/src/toon/settings/materialMapSettings.js +299 -0
- package/src/toon/settings/outlineSettings.js +263 -0
- package/src/toon/settings/perspectiveRemovalSettings.js +60 -0
- package/src/toon/settings/rimLightSettings.js +268 -0
- package/src/toon/settings/sceneShadowSettings.js +147 -0
- package/src/toon/settings/selfShadowSettings.js +221 -0
- package/src/toon/settings/shadowColorSettings.js +238 -0
- package/src/toon/settings/skinToneSettings.js +150 -0
- package/src/toon/settings/specularSettings.js +314 -0
- package/src/toon/settings/stickerSettings.js +102 -0
- package/src/toon/toonMaterialAdapter.js +2235 -0
- package/src/toon/toonSettings.js +860 -0
- package/src/vegetation/flowerSpecies.js +260 -0
- package/src/vegetation/index.js +10 -0
- package/src/vegetation/stylizedBush.js +136 -0
- package/src/vegetation/stylizedFlower.js +213 -0
- package/src/vegetation/stylizedFlowers.js +297 -0
- package/src/vegetation/stylizedGrass.js +491 -0
- package/src/vegetation/stylizedTree.js +2449 -0
- package/src/vegetation/stylizedTreeFoliage.js +691 -0
- package/src/vegetation/treeExport.js +300 -0
- package/src/vegetation/treeRecipe.js +647 -0
- package/src/water/index.js +2 -0
- package/src/water/water.js +20 -0
- package/src/water/waterBreakerSystem.js +591 -0
- package/src/water/waterInteraction.js +193 -0
- package/src/water/waterMaterial.js +263 -0
- package/src/water/waterRain.js +136 -0
- package/src/water/waterRippleSimulation.js +227 -0
- package/src/water/waterScenePasses.js +415 -0
- package/src/water/waterSettings.js +1138 -0
- package/src/water/waterSplashSystem.js +251 -0
- package/src/water/waterSurface.js +475 -0
- 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
|
+
}
|
package/src/sky/index.js
ADDED