@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,190 @@
|
|
|
1
|
+
// TSL port of src/shaders/chunks/water-fragment-color.glsl — water body
|
|
2
|
+
// color: scene depth reads, refraction, absorption gradient, and procedural
|
|
3
|
+
// caustics. All uniforms come from the water material (`u` map, GLSL names).
|
|
4
|
+
//
|
|
5
|
+
// Scene depth on the node backends is a float COLOR texture holding linear
|
|
6
|
+
// [0,1] window depth (docs/tsl-conventions.md #5 — depth textures type
|
|
7
|
+
// differently per builder), written by the water grab pass's depth-color
|
|
8
|
+
// render. perspectiveDepthToViewZ works unchanged on both backends; world
|
|
9
|
+
// reconstruction happens through uInvViewProjMatrix, which the CPU composes
|
|
10
|
+
// with the backend's NDC adjustment (see updateWaterMaterialCamera).
|
|
11
|
+
//
|
|
12
|
+
// GLSL early returns become var + If; texture reads sample at .level(0).
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
abs,
|
|
16
|
+
clamp,
|
|
17
|
+
exp,
|
|
18
|
+
float,
|
|
19
|
+
If,
|
|
20
|
+
length,
|
|
21
|
+
max,
|
|
22
|
+
min,
|
|
23
|
+
mix,
|
|
24
|
+
perspectiveDepthToViewZ,
|
|
25
|
+
pow,
|
|
26
|
+
sign,
|
|
27
|
+
smoothstep,
|
|
28
|
+
vec2,
|
|
29
|
+
vec3,
|
|
30
|
+
vec4,
|
|
31
|
+
} from 'three/tsl';
|
|
32
|
+
|
|
33
|
+
import { waterRotate2d, waterVoronoi2 } from './water-common.js';
|
|
34
|
+
|
|
35
|
+
export function createWaterColorChunk({ u, flags }) {
|
|
36
|
+
const sceneRawDepth = (screenUv) => {
|
|
37
|
+
const raw = float(1.0).toVar();
|
|
38
|
+
If(u.uUseSceneDepth.greaterThanEqual(0.5), () => {
|
|
39
|
+
raw.assign(u.uSceneDepth.sample(screenUv).level(0).r);
|
|
40
|
+
});
|
|
41
|
+
return raw;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const viewDistanceFromDepth = (rawDepth) => {
|
|
45
|
+
// perspectiveDepthToViewZ is an Fn() in r185 — keep it out of select()
|
|
46
|
+
// operands (docs/tsl-conventions.md #2), hence the If shape.
|
|
47
|
+
const distance = float(u.uCameraFar).toVar();
|
|
48
|
+
If(rawDepth.lessThan(0.99999), () => {
|
|
49
|
+
distance.assign(perspectiveDepthToViewZ(rawDepth, u.uCameraNear, u.uCameraFar).negate());
|
|
50
|
+
});
|
|
51
|
+
return distance;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const worldFromDepth = (screenUv, rawDepth) => {
|
|
55
|
+
const clipPosition = vec4(vec3(screenUv, rawDepth).mul(2.0).sub(1.0), 1.0);
|
|
56
|
+
const worldPosition = u.uInvViewProjMatrix.mul(clipPosition).toVar();
|
|
57
|
+
return worldPosition.xyz.div(max(abs(worldPosition.w), 1e-6)).mul(sign(worldPosition.w));
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Screen-space refraction with a validity check: if the refracted sample
|
|
61
|
+
// lands on geometry in front of the water surface, fall back to the
|
|
62
|
+
// unrefracted uv so above-water objects never smear into the water.
|
|
63
|
+
const refractedUv = (screenUv, surfaceNormal, waterViewDistance, viewDepthDiff) => {
|
|
64
|
+
const result = vec2(screenUv).toVar();
|
|
65
|
+
If(u.uUseSceneColor.greaterThanEqual(0.5).and(u.uUseSceneDepth.greaterThanEqual(0.5)), () => {
|
|
66
|
+
const offset = surfaceNormal.xz.mul(u.uRefractionStrength)
|
|
67
|
+
.mul(clamp(viewDepthDiff, 0.0, 2.0))
|
|
68
|
+
.div(max(waterViewDistance.mul(0.35), 1.0))
|
|
69
|
+
.toVar();
|
|
70
|
+
// Cap the displacement in screen space: large offsets re-sample
|
|
71
|
+
// submerged objects from far away, drawing them twice as an animated
|
|
72
|
+
// ghost copy. A few-pixel cap keeps the shimmer without the double image.
|
|
73
|
+
const offsetLength = length(offset).toVar();
|
|
74
|
+
offset.mulAssign(min(offsetLength, 0.018).div(max(offsetLength, 1e-5)));
|
|
75
|
+
const candidate = clamp(screenUv.add(offset), vec2(0.001), vec2(0.999)).toVar();
|
|
76
|
+
const refractedDistance = viewDistanceFromDepth(sceneRawDepth(candidate));
|
|
77
|
+
If(refractedDistance.greaterThanEqual(waterViewDistance.sub(0.02)), () => {
|
|
78
|
+
result.assign(candidate);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
return result;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const pierceSample = (sampleUv, surfaceHeight) => {
|
|
85
|
+
const pierce = float(0.0).toVar();
|
|
86
|
+
const rawDepth = sceneRawDepth(sampleUv).toVar();
|
|
87
|
+
If(rawDepth.lessThan(0.99999), () => { // sky — nothing pierces here
|
|
88
|
+
const neighborWorld = worldFromDepth(sampleUv, rawDepth);
|
|
89
|
+
pierce.assign(smoothstep(0.01, 0.05, neighborWorld.y.sub(surfaceHeight)));
|
|
90
|
+
});
|
|
91
|
+
return pierce;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Detects geometry piercing the water surface within a small world-space
|
|
95
|
+
// radius of this pixel: shoreline sand, rocks, a wading character.
|
|
96
|
+
const pierceProximity = (screenUv, waterViewDistance, surfaceHeight) => {
|
|
97
|
+
const pierce = float(1.0).toVar();
|
|
98
|
+
If(u.uUseSceneDepth.greaterThanEqual(0.5), () => {
|
|
99
|
+
// ~0.4 m probe radius projected to screen space (approximate focal scale).
|
|
100
|
+
const radiusUv = clamp(float(0.4).div(max(waterViewDistance, 1.0)), 0.004, 0.05);
|
|
101
|
+
const radius = radiusUv.mul(vec2(u.uResolution.y.div(u.uResolution.x), 1.0)).toVar();
|
|
102
|
+
const lo = vec2(0.001);
|
|
103
|
+
const hi = vec2(0.999);
|
|
104
|
+
const accumulated = pierceSample(clamp(screenUv.add(vec2(radius.x, 0.0)), lo, hi), surfaceHeight).toVar();
|
|
105
|
+
accumulated.assign(max(accumulated, pierceSample(clamp(screenUv.sub(vec2(radius.x, 0.0)), lo, hi), surfaceHeight)));
|
|
106
|
+
accumulated.assign(max(accumulated, pierceSample(clamp(screenUv.add(vec2(0.0, radius.y)), lo, hi), surfaceHeight)));
|
|
107
|
+
accumulated.assign(max(accumulated, pierceSample(clamp(screenUv.sub(vec2(0.0, radius.y)), lo, hi), surfaceHeight)));
|
|
108
|
+
pierce.assign(accumulated);
|
|
109
|
+
});
|
|
110
|
+
return pierce;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Three-stop absorption gradient: bright shallow teal -> mid blue -> deep
|
|
114
|
+
// blue. GLSL `out float absorb` → returned alongside the tint.
|
|
115
|
+
const absorptionTint = (effectiveDepth) => {
|
|
116
|
+
const absorb = exp(effectiveDepth.div(max(u.uDepthFadeDistance, 1e-3)).negate()).oneMinus().toVar();
|
|
117
|
+
const deepMix = exp(
|
|
118
|
+
max(effectiveDepth.sub(u.uDepthFadeDistance.mul(0.5)), 0.0)
|
|
119
|
+
.div(max(u.uDeepFadeDistance, 1e-3)).negate(),
|
|
120
|
+
).oneMinus();
|
|
121
|
+
const tint = mix(u.uShallowColor, u.uMidColor, clamp(absorb.mul(1.25), 0.0, 1.0));
|
|
122
|
+
return { absorb, tint: mix(tint, u.uDeepColor, deepMix).toVar() };
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Stylized voronoi-web caustics anchored to the reconstructed underwater
|
|
126
|
+
// ground position, offset along the sun direction so they feel projected.
|
|
127
|
+
// flags.chromaticCaustics = the GLSL WATER_QUALITY >= 2 chroma-shift path.
|
|
128
|
+
const caustics = (groundWorld, columnDepth, time) => {
|
|
129
|
+
const result = vec3(0.0).toVar();
|
|
130
|
+
If(u.uCausticsStrength.greaterThan(0.001), () => {
|
|
131
|
+
const anchor = groundWorld.xz.sub(
|
|
132
|
+
u.uSunDirection.xz.div(max(u.uSunDirection.y, 0.35)).mul(columnDepth).mul(0.55),
|
|
133
|
+
).toVar();
|
|
134
|
+
const causticUv1 = anchor.mul(u.uCausticsScale)
|
|
135
|
+
.add(vec2(0.131, 0.207).mul(time).mul(u.uCausticsSpeed)).toVar();
|
|
136
|
+
const causticUv2 = waterRotate2d(0.72).mul(anchor).mul(u.uCausticsScale.mul(1.29))
|
|
137
|
+
.sub(vec2(0.173, 0.114).mul(time).mul(u.uCausticsSpeed)).toVar();
|
|
138
|
+
|
|
139
|
+
// Caustic reach is capped independently of the clarity distances: the
|
|
140
|
+
// dappling belongs to the sub-2m shallows.
|
|
141
|
+
const causticReach = min(u.uDepthFadeDistance, 1.5).toVar();
|
|
142
|
+
const mask = smoothstep(0.015, 0.12, columnDepth)
|
|
143
|
+
.mul(smoothstep(
|
|
144
|
+
causticReach.mul(0.9),
|
|
145
|
+
causticReach.mul(1.6).add(min(u.uDeepFadeDistance, 2.5)),
|
|
146
|
+
columnDepth,
|
|
147
|
+
).oneMinus())
|
|
148
|
+
.toVar();
|
|
149
|
+
If(mask.greaterThan(0.002), () => {
|
|
150
|
+
const strands = vec3(0.0).toVar();
|
|
151
|
+
if (flags.chromaticCaustics) {
|
|
152
|
+
// Kept subtle: wide shifts read as oil-slick rainbows instead of
|
|
153
|
+
// light dispersion, especially on top-down camera angles.
|
|
154
|
+
const chromaShift = vec2(0.011, 0.007);
|
|
155
|
+
const voroRed = waterVoronoi2(causticUv1.sub(chromaShift)).toVar();
|
|
156
|
+
const voroGreen = waterVoronoi2(causticUv1).toVar();
|
|
157
|
+
const voroBlue = waterVoronoi2(causticUv1.add(chromaShift)).toVar();
|
|
158
|
+
const voroLayer = waterVoronoi2(causticUv2).toVar();
|
|
159
|
+
const layer = smoothstep(0.0, 0.22, voroLayer.y.sub(voroLayer.x)).oneMinus();
|
|
160
|
+
const web = vec3(
|
|
161
|
+
smoothstep(0.0, 0.16, voroRed.y.sub(voroRed.x)).oneMinus(),
|
|
162
|
+
smoothstep(0.0, 0.16, voroGreen.y.sub(voroGreen.x)).oneMinus(),
|
|
163
|
+
smoothstep(0.0, 0.16, voroBlue.y.sub(voroBlue.x)).oneMinus(),
|
|
164
|
+
);
|
|
165
|
+
strands.assign(web.mul(mix(0.45, 1.0, layer)));
|
|
166
|
+
} else {
|
|
167
|
+
const voroGreen = waterVoronoi2(causticUv1).toVar();
|
|
168
|
+
const voroLayer = waterVoronoi2(causticUv2).toVar();
|
|
169
|
+
const layer = smoothstep(0.0, 0.22, voroLayer.y.sub(voroLayer.x)).oneMinus();
|
|
170
|
+
strands.assign(vec3(
|
|
171
|
+
smoothstep(0.0, 0.16, voroGreen.y.sub(voroGreen.x)).oneMinus().mul(mix(0.45, 1.0, layer)),
|
|
172
|
+
));
|
|
173
|
+
}
|
|
174
|
+
strands.assign(pow(strands, vec3(1.6)));
|
|
175
|
+
result.assign(u.uSunColor.mul(u.uCausticsStrength).mul(strands).mul(mask));
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
return result;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
absorptionTint,
|
|
183
|
+
caustics,
|
|
184
|
+
pierceProximity,
|
|
185
|
+
refractedUv,
|
|
186
|
+
sceneRawDepth,
|
|
187
|
+
viewDistanceFromDepth,
|
|
188
|
+
worldFromDepth,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// TSL port of src/shaders/chunks/water-common.glsl — shared procedural
|
|
2
|
+
// helpers for the stylized shaders (hashes, value noise, fbm, voronoi, toon
|
|
3
|
+
// step). Everything is generated in-shader so the systems ship with zero
|
|
4
|
+
// texture assets.
|
|
5
|
+
//
|
|
6
|
+
// Convention (docs/tsl-conventions.md): leaf helpers that map node→node are
|
|
7
|
+
// `Fn()` exports (compiled as real shader functions); helpers with
|
|
8
|
+
// compile-time-constant parameters (loop counts) are plain JS composition
|
|
9
|
+
// functions and unroll at node-build time.
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
dot,
|
|
13
|
+
float,
|
|
14
|
+
floor,
|
|
15
|
+
Fn,
|
|
16
|
+
fract,
|
|
17
|
+
If,
|
|
18
|
+
mat2,
|
|
19
|
+
mix,
|
|
20
|
+
normalize,
|
|
21
|
+
smoothstep,
|
|
22
|
+
sqrt,
|
|
23
|
+
vec2,
|
|
24
|
+
vec3,
|
|
25
|
+
vec4,
|
|
26
|
+
} from 'three/tsl';
|
|
27
|
+
|
|
28
|
+
export const waterHash11 = /*@__PURE__*/ Fn(([p]) => {
|
|
29
|
+
const x = fract(p.mul(0.1031)).toVar();
|
|
30
|
+
x.mulAssign(x.add(33.33));
|
|
31
|
+
x.mulAssign(x.add(x));
|
|
32
|
+
return fract(x);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const waterHash12 = /*@__PURE__*/ Fn(([p]) => {
|
|
36
|
+
const p3 = fract(vec3(p.x, p.y, p.x).mul(0.1031)).toVar();
|
|
37
|
+
p3.addAssign(dot(p3, p3.yzx.add(33.33)));
|
|
38
|
+
return fract(p3.x.add(p3.y).mul(p3.z));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const waterHash22 = /*@__PURE__*/ Fn(([p]) => {
|
|
42
|
+
const p3 = fract(vec3(p.x, p.y, p.x).mul(vec3(0.1031, 0.103, 0.0973))).toVar();
|
|
43
|
+
p3.addAssign(dot(p3, p3.yzx.add(33.33)));
|
|
44
|
+
return fract(p3.xx.add(p3.yz).mul(p3.zy));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const waterValueNoise = /*@__PURE__*/ Fn(([p]) => {
|
|
48
|
+
const i = floor(p);
|
|
49
|
+
const f = fract(p);
|
|
50
|
+
const u = f.mul(f).mul(f.mul(-2).add(3));
|
|
51
|
+
const a = waterHash12(i);
|
|
52
|
+
const b = waterHash12(i.add(vec2(1, 0)));
|
|
53
|
+
const c = waterHash12(i.add(vec2(0, 1)));
|
|
54
|
+
const d = waterHash12(i.add(vec2(1, 1)));
|
|
55
|
+
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// GLSL: waterFbm(vec2 p, int octaves) — every call site passes a literal, so
|
|
59
|
+
// the octave count is a JS number here and the loop unrolls at build time.
|
|
60
|
+
export function waterFbm(p, octaves) {
|
|
61
|
+
const count = Math.min(Math.max(Math.trunc(octaves), 0), 5);
|
|
62
|
+
let value = float(0);
|
|
63
|
+
let amplitude = 0.5;
|
|
64
|
+
let total = 0;
|
|
65
|
+
let q = vec2(p);
|
|
66
|
+
for (let i = 0; i < count; i += 1) {
|
|
67
|
+
value = value.add(waterValueNoise(q).mul(amplitude));
|
|
68
|
+
total += amplitude;
|
|
69
|
+
// GLSL: p = mat2(0.8, 0.6, -0.6, 0.8) * p * 2.02 + vec2(17.13, 9.77),
|
|
70
|
+
// written out component-wise (columns (0.8, 0.6) and (-0.6, 0.8)).
|
|
71
|
+
q = vec2(
|
|
72
|
+
q.x.mul(0.8).add(q.y.mul(-0.6)),
|
|
73
|
+
q.x.mul(0.6).add(q.y.mul(0.8)),
|
|
74
|
+
).mul(2.02).add(vec2(17.13, 9.77));
|
|
75
|
+
amplitude *= 0.5;
|
|
76
|
+
}
|
|
77
|
+
return value.div(Math.max(total, 1e-4));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Voronoi F1/F2 distances plus the owning cell id: vec4(F1, F2, cellId.xy).
|
|
81
|
+
// The 3×3 neighbourhood walk unrolls at build time (matches the GLSL loop).
|
|
82
|
+
export const waterVoronoi2 = /*@__PURE__*/ Fn(([p]) => {
|
|
83
|
+
const cell = floor(p).toVar();
|
|
84
|
+
const local = fract(p).toVar();
|
|
85
|
+
const f1 = float(8).toVar();
|
|
86
|
+
const f2 = float(8).toVar();
|
|
87
|
+
const bestId = vec2(0).toVar();
|
|
88
|
+
for (let y = -1; y <= 1; y += 1) {
|
|
89
|
+
for (let x = -1; x <= 1; x += 1) {
|
|
90
|
+
const offset = vec2(x, y);
|
|
91
|
+
const feature = waterHash22(cell.add(offset));
|
|
92
|
+
const delta = offset.add(feature).sub(local);
|
|
93
|
+
const distanceSq = dot(delta, delta).toVar();
|
|
94
|
+
If(distanceSq.lessThan(f1), () => {
|
|
95
|
+
f2.assign(f1);
|
|
96
|
+
f1.assign(distanceSq);
|
|
97
|
+
bestId.assign(cell.add(offset));
|
|
98
|
+
}).ElseIf(distanceSq.lessThan(f2), () => {
|
|
99
|
+
f2.assign(distanceSq);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return vec4(sqrt(f1), sqrt(f2), bestId);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// GLSL: mat2 waterRotate2d(angle) = mat2(c, -s, s, c) (column-major scalars).
|
|
107
|
+
// TSL's mat2() takes three.js ROW-major scalar order — the transpose of the
|
|
108
|
+
// GLSL constructor — so the argument order here is deliberately swapped.
|
|
109
|
+
// Verified vs WebGL cloud-pattern parity (docs/tsl-conventions.md).
|
|
110
|
+
export function waterRotate2d(angle) {
|
|
111
|
+
const s = float(angle).sin();
|
|
112
|
+
const c = float(angle).cos();
|
|
113
|
+
return mat2(c, s, s.negate(), c);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Crisp two-tone step with a controllable soft edge; the core toon primitive.
|
|
117
|
+
export const waterToonStep = /*@__PURE__*/ Fn(([edge, softness, value]) => {
|
|
118
|
+
return smoothstep(edge.sub(softness), edge.add(softness), value);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Tilts a surface normal by a heightfield gradient (dh/dx, dh/dz).
|
|
122
|
+
export const waterCombineNormal = /*@__PURE__*/ Fn(([baseNormal, gradient]) => {
|
|
123
|
+
return normalize(vec3(baseNormal.x.sub(gradient.x), baseNormal.y, baseNormal.z.sub(gradient.y)));
|
|
124
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// TSL port of src/shaders/chunks/water-fragment-foam.glsl — shoreline contact
|
|
2
|
+
// foam, offset lapping foam lines, open-water whitecaps, and the shared toon
|
|
3
|
+
// shaping that gives foam its crisp dissolve-dot edge.
|
|
4
|
+
//
|
|
5
|
+
// foamOctaves is the GLSL WATER_FOAM_OCTAVES define (compile-time; the fbm
|
|
6
|
+
// loop unrolls at graph-build time). GLSL early returns become var + If.
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
clamp,
|
|
10
|
+
float,
|
|
11
|
+
fract,
|
|
12
|
+
If,
|
|
13
|
+
max,
|
|
14
|
+
min,
|
|
15
|
+
mix,
|
|
16
|
+
smoothstep,
|
|
17
|
+
} from 'three/tsl';
|
|
18
|
+
|
|
19
|
+
import { waterFbm, waterToonStep, waterValueNoise } from './water-common.js';
|
|
20
|
+
|
|
21
|
+
export function createWaterFoamChunk({ u, foamOctaves }) {
|
|
22
|
+
// Raw shoreline foam mask. The shoreline bands are driven by the vertical
|
|
23
|
+
// water-column depth so they hug the shore identically from any camera
|
|
24
|
+
// angle; a tight view-space silhouette band handles objects standing in the
|
|
25
|
+
// water. `pierce` gates the contact bands to true waterline contacts.
|
|
26
|
+
const shoreFoam = (columnDepth, viewDepthDiff, restXZ, time, pierce) => {
|
|
27
|
+
const result = float(0.0).toVar();
|
|
28
|
+
If(u.uUseSceneDepth.greaterThanEqual(0.5), () => {
|
|
29
|
+
const flowOffset = u.uFlowDirection.mul(time).mul(u.uFlowSpeed).mul(0.35).toVar();
|
|
30
|
+
const breakup = waterFbm(restXZ.mul(u.uFoamNoiseScale).add(flowOffset), foamOctaves).toVar();
|
|
31
|
+
|
|
32
|
+
// A tight ungated core keeps a crisp waterline rim on any contact.
|
|
33
|
+
const contactTight = smoothstep(0.0, min(u.uFoamContactDistance.mul(0.5), 0.07), columnDepth).oneMinus();
|
|
34
|
+
const contactCore = max(
|
|
35
|
+
smoothstep(0.0, u.uFoamContactDistance.mul(0.5), columnDepth).oneMinus().mul(pierce),
|
|
36
|
+
contactTight,
|
|
37
|
+
).toVar();
|
|
38
|
+
const contactHalo = smoothstep(0.0, u.uFoamContactDistance.mul(1.2), columnDepth).oneMinus()
|
|
39
|
+
.mul(smoothstep(0.34, 0.66, breakup)).mul(0.4).mul(pierce).toVar();
|
|
40
|
+
// The silhouette band additionally requires deep water below this pixel
|
|
41
|
+
// so a floating body is not painted solid white from above.
|
|
42
|
+
const silhouette = smoothstep(0.0, u.uFoamContactDistance.mul(0.4), viewDepthDiff).oneMinus()
|
|
43
|
+
.mul(pierce)
|
|
44
|
+
.mul(smoothstep(u.uFoamContactDistance.mul(0.6), u.uFoamContactDistance.mul(1.3), columnDepth))
|
|
45
|
+
.toVar();
|
|
46
|
+
|
|
47
|
+
// Lapping lines march toward the shore and dissolve into dashes.
|
|
48
|
+
const lineDistance = columnDepth.div(max(u.uFoamLineSpacing, 1e-3))
|
|
49
|
+
.add(breakup.sub(0.5).mul(0.7)).sub(time.mul(0.32));
|
|
50
|
+
const lineBand = fract(lineDistance).toVar();
|
|
51
|
+
const line = smoothstep(0.8, 0.93, lineBand)
|
|
52
|
+
.mul(smoothstep(0.93, 1.0, lineBand).oneMinus()).toVar();
|
|
53
|
+
const lineFalloff = smoothstep(1.3, 2.4, columnDepth.div(max(u.uFoamLineSpacing, 1e-3))).oneMinus();
|
|
54
|
+
const dashes = waterValueNoise(restXZ.mul(u.uFoamNoiseScale).mul(2.6).add(flowOffset.mul(1.7)));
|
|
55
|
+
line.mulAssign(lineFalloff.mul(smoothstep(0.36, 0.6, breakup)).mul(smoothstep(0.3, 0.55, dashes)));
|
|
56
|
+
|
|
57
|
+
result.assign(clamp(
|
|
58
|
+
contactCore.mul(1.3).add(silhouette.mul(1.2)).add(contactHalo).add(line),
|
|
59
|
+
0.0,
|
|
60
|
+
1.0,
|
|
61
|
+
));
|
|
62
|
+
});
|
|
63
|
+
return result;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Whitecaps on open water crests: driven by the Gerstner crest factor and
|
|
67
|
+
// surface steepness, broken into patches, scaled by the storminess amount.
|
|
68
|
+
const whitecaps = (crest, gerstnerNormalY, restXZ, time) => {
|
|
69
|
+
const result = float(0.0).toVar();
|
|
70
|
+
If(u.uWhitecapAmount.greaterThan(0.002), () => {
|
|
71
|
+
const drift = u.uFlowDirection.mul(time).mul(u.uFlowSpeed.mul(0.4).add(0.35));
|
|
72
|
+
const patches = waterFbm(restXZ.mul(u.uFoamNoiseScale).mul(0.6).add(drift), foamOctaves).toVar();
|
|
73
|
+
const steepness = clamp(gerstnerNormalY.oneMinus().mul(9.0), 0.0, 1.0);
|
|
74
|
+
const energy = crest.mul(steepness.mul(0.45).add(0.55)).mul(patches.mul(0.85).add(0.45)).toVar();
|
|
75
|
+
const threshold = mix(float(0.985), float(0.32), clamp(u.uWhitecapAmount, 0.0, 1.0)).toVar();
|
|
76
|
+
result.assign(smoothstep(threshold, threshold.add(0.22), energy));
|
|
77
|
+
});
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Shared toon shaping: a solid band plus a sparse dissolve-dot fringe so
|
|
82
|
+
// foam edges read as hand-drawn dots rather than a soft alpha gradient.
|
|
83
|
+
const foamShape = (rawMask, restXZ, time) => {
|
|
84
|
+
const dotNoise = waterValueNoise(
|
|
85
|
+
restXZ.mul(u.uFoamNoiseScale).mul(5.2).add(u.uFlowDirection.mul(time).mul(u.uFlowSpeed).mul(0.6)),
|
|
86
|
+
);
|
|
87
|
+
const solid = waterToonStep(0.56, 0.12, rawMask);
|
|
88
|
+
const dots = waterToonStep(0.74, 0.06, rawMask.mul(dotNoise.mul(0.75).add(0.35)));
|
|
89
|
+
return clamp(max(solid, dots), 0.0, 1.0);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return { foamShape, shoreFoam, whitecaps };
|
|
93
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// TSL port of src/shaders/chunks/water-fragment-lighting.glsl — stylized
|
|
2
|
+
// fresnel, toon-banded sun glints, twinkling sparkles, and reflections
|
|
3
|
+
// (planar render target with a procedural sky fallback).
|
|
4
|
+
//
|
|
5
|
+
// The reflection map is a render-target texture (.level(0) samples); the
|
|
6
|
+
// projective uReflectionMatrix is CPU-composed with the node backends' RT
|
|
7
|
+
// y-flip (docs/tsl-conventions.md #6 — see WaterScenePasses), so the shader
|
|
8
|
+
// math stays the literal GLSL port. GLSL early returns become var + If, which
|
|
9
|
+
// also keeps the uniform-dependent divisions guarded (conventions #8).
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
clamp,
|
|
13
|
+
dot,
|
|
14
|
+
If,
|
|
15
|
+
fract,
|
|
16
|
+
max,
|
|
17
|
+
mix,
|
|
18
|
+
normalize,
|
|
19
|
+
pow,
|
|
20
|
+
reflect,
|
|
21
|
+
smoothstep,
|
|
22
|
+
step,
|
|
23
|
+
vec2,
|
|
24
|
+
vec3,
|
|
25
|
+
vec4,
|
|
26
|
+
} from 'three/tsl';
|
|
27
|
+
|
|
28
|
+
import { waterHash12, waterToonStep, waterVoronoi2 } from './water-common.js';
|
|
29
|
+
|
|
30
|
+
export function createWaterLightingChunk({ u }) {
|
|
31
|
+
// Schlick-style fresnel with a stylized floor: uFresnelBias keeps some sky
|
|
32
|
+
// reflection alive even looking straight down.
|
|
33
|
+
const fresnelFactor = (viewDir, surfaceNormal) => {
|
|
34
|
+
const facing = clamp(dot(viewDir, surfaceNormal), 0.0, 1.0);
|
|
35
|
+
return clamp(
|
|
36
|
+
u.uFresnelBias.add(pow(facing.oneMinus(), u.uFresnelPower).mul(u.uFresnelStrength)),
|
|
37
|
+
0.0,
|
|
38
|
+
1.0,
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Toon-banded Blinn-Phong glint. The normal is anisotropically squashed
|
|
43
|
+
// along the sun azimuth so low sun angles draw the long sparkling sun path.
|
|
44
|
+
const specular = (viewDir, surfaceNormal, shadowFactor) => {
|
|
45
|
+
const sunAzimuth = normalize(u.uSunDirection.xz.add(vec2(1e-5, 0.0))).toVar();
|
|
46
|
+
const sunPerp = vec2(sunAzimuth.y.negate(), sunAzimuth.x).toVar();
|
|
47
|
+
const stretched = sunAzimuth.mul(dot(surfaceNormal.xz, sunAzimuth))
|
|
48
|
+
.add(sunPerp.mul(dot(surfaceNormal.xz, sunPerp)).mul(clamp(u.uSpecularStretch, 0.0, 0.95).oneMinus()))
|
|
49
|
+
.toVar();
|
|
50
|
+
const glintNormal = normalize(vec3(stretched.x, surfaceNormal.y, stretched.y));
|
|
51
|
+
const halfDir = normalize(viewDir.add(u.uSunDirection));
|
|
52
|
+
const alignment = pow(max(dot(glintNormal, halfDir), 0.0), u.uSpecularShininess).toVar();
|
|
53
|
+
const core = waterToonStep(0.5, 0.1, alignment);
|
|
54
|
+
const halo = waterToonStep(0.18, 0.16, alignment).mul(0.3);
|
|
55
|
+
return u.uSunColor.mul(u.uSpecularStrength).mul(core.add(halo)).mul(shadowFactor);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Twinkling star glints: sparse voronoi feature points with per-cell phase.
|
|
59
|
+
const sparkles = (restXZ, surfaceNormal, viewDir, viewDistance, time) => {
|
|
60
|
+
const result = vec3(0.0).toVar();
|
|
61
|
+
If(u.uSparkleStrength.greaterThan(0.001), () => {
|
|
62
|
+
const sparkleUv = restXZ.mul(u.uSparkleScale)
|
|
63
|
+
.add(u.uFlowDirection.mul(time).mul(u.uFlowSpeed).mul(0.4));
|
|
64
|
+
const voro = waterVoronoi2(sparkleUv).toVar();
|
|
65
|
+
const cellRandom = waterHash12(voro.zw).toVar();
|
|
66
|
+
const activeCell = step(0.5, cellRandom);
|
|
67
|
+
const twinklePhase = fract(
|
|
68
|
+
cellRandom.mul(7.13).add(time.mul(u.uSparkleSpeed).mul(cellRandom.mul(0.3).add(0.16))),
|
|
69
|
+
).toVar();
|
|
70
|
+
const twinkle = smoothstep(0.88, 0.965, twinklePhase)
|
|
71
|
+
.mul(smoothstep(0.965, 1.0, twinklePhase).oneMinus());
|
|
72
|
+
const point = smoothstep(0.02, 0.12, voro.x).oneMinus();
|
|
73
|
+
const halfDir = normalize(viewDir.add(u.uSunDirection));
|
|
74
|
+
const alignment = smoothstep(0.4, 0.92, dot(surfaceNormal, halfDir));
|
|
75
|
+
const distanceFade = smoothstep(18.0, 55.0, viewDistance).oneMinus();
|
|
76
|
+
result.assign(
|
|
77
|
+
u.uSunColor.mul(u.uSparkleStrength).mul(point).mul(twinkle).mul(activeCell)
|
|
78
|
+
.mul(alignment).mul(distanceFade).mul(3.2),
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
return result;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Procedural sky used when no planar reflection target is bound: a vertical
|
|
85
|
+
// gradient plus a soft sun glow, cheap and asset-free.
|
|
86
|
+
const proceduralSky = (reflectDir) => {
|
|
87
|
+
const horizon = pow(clamp(reflectDir.y, 0.0, 1.0), 0.55);
|
|
88
|
+
const sky = mix(u.uSkyHorizonColor, u.uSkyZenithColor, horizon);
|
|
89
|
+
const sunGlow = pow(max(dot(reflectDir, u.uSunDirection), 0.0), 180.0);
|
|
90
|
+
return sky.add(u.uSunColor.mul(sunGlow).mul(u.uSunGlowStrength));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const reflectionColor = (worldPosition, surfaceNormal, viewDir) => {
|
|
94
|
+
const reflectDir = reflect(viewDir.negate(), surfaceNormal).toVar();
|
|
95
|
+
const fallback = proceduralSky(reflectDir).toVar();
|
|
96
|
+
const result = vec3(fallback).toVar();
|
|
97
|
+
If(u.uUseReflectionMap.greaterThanEqual(0.5), () => {
|
|
98
|
+
const projected = u.uReflectionMatrix.mul(vec4(worldPosition, 1.0)).toVar();
|
|
99
|
+
If(projected.w.greaterThan(0.0), () => {
|
|
100
|
+
const reflectionUv = projected.xy.div(projected.w)
|
|
101
|
+
.add(surfaceNormal.xz.mul(u.uReflectionDistortion)).toVar();
|
|
102
|
+
const inRange = reflectionUv.x.greaterThanEqual(0.0).and(reflectionUv.x.lessThanEqual(1.0))
|
|
103
|
+
.and(reflectionUv.y.greaterThanEqual(0.0)).and(reflectionUv.y.lessThanEqual(1.0));
|
|
104
|
+
If(inRange, () => {
|
|
105
|
+
const planar = u.uReflectionMap.sample(reflectionUv).level(0).rgb;
|
|
106
|
+
// Milky painterly reflections: soften the mirror image toward the
|
|
107
|
+
// smooth procedural sky instead of showing a sharp flipped scene.
|
|
108
|
+
result.assign(mix(planar, fallback, clamp(u.uReflectionSoftness, 0.0, 1.0)));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
return result;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return { fresnelFactor, proceduralSky, reflectionColor, sparkles, specular };
|
|
116
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// TSL port of src/shaders/chunks/water-ripple.glsl — interactive ripple
|
|
2
|
+
// heightfield sampling, shared by the water vertex and fragment stages.
|
|
3
|
+
// The simulation texture stores: R = height, G = velocity, B = foam energy.
|
|
4
|
+
// uRippleRegion = (centerX, centerZ, halfSizeX, halfSizeZ) in world space.
|
|
5
|
+
//
|
|
6
|
+
// The ripple map is a render-target texture, so every sample is `.level(0)`
|
|
7
|
+
// (docs/tsl-conventions.md: RT-fed textures have no mips and WGSL forbids
|
|
8
|
+
// implicit-derivative sampling in non-uniform control flow). The GLSL early
|
|
9
|
+
// returns become var + If — the mask guard keeps the texture reads out of
|
|
10
|
+
// the disabled path exactly like the GLSL branch.
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
float,
|
|
14
|
+
If,
|
|
15
|
+
max,
|
|
16
|
+
smoothstep,
|
|
17
|
+
vec2,
|
|
18
|
+
vec3,
|
|
19
|
+
} from 'three/tsl';
|
|
20
|
+
|
|
21
|
+
export function createWaterRippleChunk({ u }) {
|
|
22
|
+
const rippleUv = (restXZ) =>
|
|
23
|
+
restXZ.sub(u.uRippleRegion.xy).div(u.uRippleRegion.zw.mul(2.0)).add(0.5);
|
|
24
|
+
|
|
25
|
+
// Smoothly fades ripple influence to zero at the simulation region border so
|
|
26
|
+
// a moving region never shows a hard seam on large water bodies.
|
|
27
|
+
const rippleMask = (uv) => {
|
|
28
|
+
const border = smoothstep(vec2(0.0), vec2(0.06), uv)
|
|
29
|
+
.mul(smoothstep(vec2(0.94), vec2(1.0), uv).oneMinus());
|
|
30
|
+
return border.x.mul(border.y).mul(u.uUseRippleMap);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const rippleSample = (restXZ) => {
|
|
34
|
+
const result = vec3(0.0).toVar();
|
|
35
|
+
const uv = rippleUv(restXZ).toVar();
|
|
36
|
+
const mask = rippleMask(uv).toVar();
|
|
37
|
+
If(mask.greaterThan(0.0), () => {
|
|
38
|
+
const state = u.uRippleMap.sample(uv).level(0).rgb;
|
|
39
|
+
result.assign(state.mul(mask));
|
|
40
|
+
});
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Central-difference normal contribution from the ripple heightfield.
|
|
45
|
+
const rippleGradient = (restXZ) => {
|
|
46
|
+
const result = vec2(0.0).toVar();
|
|
47
|
+
const uv = rippleUv(restXZ).toVar();
|
|
48
|
+
const mask = rippleMask(uv).toVar();
|
|
49
|
+
If(mask.greaterThan(0.0), () => {
|
|
50
|
+
const left = u.uRippleMap.sample(uv.sub(vec2(u.uRippleTexel.x, 0.0))).level(0).r;
|
|
51
|
+
const right = u.uRippleMap.sample(uv.add(vec2(u.uRippleTexel.x, 0.0))).level(0).r;
|
|
52
|
+
const down = u.uRippleMap.sample(uv.sub(vec2(0.0, u.uRippleTexel.y))).level(0).r;
|
|
53
|
+
const up = u.uRippleMap.sample(uv.add(vec2(0.0, u.uRippleTexel.y))).level(0).r;
|
|
54
|
+
const worldStep = u.uRippleTexel.mul(2.0).mul(u.uRippleRegion.zw.mul(2.0)).toVar();
|
|
55
|
+
result.assign(vec2(
|
|
56
|
+
right.sub(left).div(max(worldStep.x, 1e-5)),
|
|
57
|
+
up.sub(down).div(max(worldStep.y, 1e-5)),
|
|
58
|
+
).mul(u.uRippleHeightScale).mul(mask));
|
|
59
|
+
});
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return { rippleGradient, rippleMask, rippleSample, rippleUv };
|
|
64
|
+
}
|