@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,461 @@
|
|
|
1
|
+
// TSL port of the post-processing final composite pass and the pyramid
|
|
2
|
+
// (dual-filter mip chain) bloom passes from src/post/postProcessing.js.
|
|
3
|
+
// Consumed by postProcessing.js's createFinalCompositeMaterial /
|
|
4
|
+
// createBloomPassMaterial call sites on the TSL backend; every material here
|
|
5
|
+
// exposes `.uniforms` under the exact GLSL uniform names so the shared,
|
|
6
|
+
// backend-agnostic settings code (applyCompositeSettings, renderBloomPyramid,
|
|
7
|
+
// setCharacterMask) keeps working unchanged on both backends
|
|
8
|
+
// (docs/tsl-conventions.md, "The .uniforms compatibility surface").
|
|
9
|
+
//
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Color-space audit
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// This is the one place in the post stack where the node renderer's implicit
|
|
14
|
+
// output encode structurally differs from the classic pipeline's explicit
|
|
15
|
+
// encode, so read this before touching any texture read below.
|
|
16
|
+
//
|
|
17
|
+
// Classic: anime.frag.glsl / environment.frag.glsl / grass.frag.glsl each
|
|
18
|
+
// end with `#include <colorspace_fragment>` — but linearToOutputTexel is
|
|
19
|
+
// compiled PER RENDER TARGET: three picks the transfer function from the
|
|
20
|
+
// bound target's texture.colorSpace, not from renderer.outputColorSpace.
|
|
21
|
+
// The post pipeline's offscreen `target.texture` is explicitly NoColorSpace,
|
|
22
|
+
// so when classic renders the scene into it the baked include is an
|
|
23
|
+
// IDENTITY and the buffer holds LINEAR color; the one real sRGB encode
|
|
24
|
+
// happens in this composite's own trailing include when it draws to the
|
|
25
|
+
// sRGB canvas. Net classic behavior: encode(composite-math(linearScene)) —
|
|
26
|
+
// a SINGLE encode. (An earlier revision of this port assumed the include
|
|
27
|
+
// was unconditional and double-encoded; measured on the live pipeline,
|
|
28
|
+
// post-on classic differs from post-off classic only by the composite math
|
|
29
|
+
// itself, proving the single-encode reading.)
|
|
30
|
+
//
|
|
31
|
+
// Node renderer: color-space conversion is not baked into materials at all;
|
|
32
|
+
// it happens exactly once, automatically, as the renderer's terminal output
|
|
33
|
+
// pass when drawing to the default framebuffer (renderer/common/Renderer.js
|
|
34
|
+
// `_renderOutput`), and never when rendering into our own offscreen target.
|
|
35
|
+
// So the node chain ALREADY matches classic with no compensation: scene ->
|
|
36
|
+
// target holds linear color (same as classic's identity-encoded buffer),
|
|
37
|
+
// the composite math runs in linear (same domain as classic), and the
|
|
38
|
+
// terminal canvas pass supplies the single encode (same as classic's
|
|
39
|
+
// trailing include). Therefore: NO manual sRGBTransfer* call anywhere in
|
|
40
|
+
// this file — adding one washes the whole frame out by a spare encode
|
|
41
|
+
// (measured: +191/255 mean vs classic). renderer.toneMapping is
|
|
42
|
+
// NoToneMapping project-wide, so the tone-mapping half of the terminal pass
|
|
43
|
+
// is a no-op either way.
|
|
44
|
+
//
|
|
45
|
+
// tBloom / bloom-chain intermediate textures (downsample/upsample) are NOT
|
|
46
|
+
// re-encoded — they're already downstream of the prefilter's one manual
|
|
47
|
+
// encode, exactly like classic never re-encodes mid-chain either. tDepth is
|
|
48
|
+
// a linear window-depth value (never color-space encoded in either
|
|
49
|
+
// pipeline). tCharacterMask is a binary 0/1 mask (encode-invariant at both
|
|
50
|
+
// endpoints) so it is read raw.
|
|
51
|
+
|
|
52
|
+
import * as THREE from 'three';
|
|
53
|
+
import {
|
|
54
|
+
abs,
|
|
55
|
+
clamp,
|
|
56
|
+
distance,
|
|
57
|
+
dot,
|
|
58
|
+
float,
|
|
59
|
+
floor,
|
|
60
|
+
fract,
|
|
61
|
+
Fn,
|
|
62
|
+
If,
|
|
63
|
+
length,
|
|
64
|
+
max,
|
|
65
|
+
min,
|
|
66
|
+
mix,
|
|
67
|
+
positionLocal,
|
|
68
|
+
screenCoordinate,
|
|
69
|
+
sign,
|
|
70
|
+
smoothstep,
|
|
71
|
+
texture,
|
|
72
|
+
uniform,
|
|
73
|
+
uv,
|
|
74
|
+
vec2,
|
|
75
|
+
vec3,
|
|
76
|
+
vec4,
|
|
77
|
+
viewZToOrthographicDepth,
|
|
78
|
+
perspectiveDepthToViewZ,
|
|
79
|
+
} from 'three/tsl';
|
|
80
|
+
import { NodeMaterial } from 'three/webgpu';
|
|
81
|
+
|
|
82
|
+
// Shared 1x1 placeholder so every optional texture uniform (tLut,
|
|
83
|
+
// tCharacterMask, tBloom, and the not-yet-wired tDepth/tDiffuse at
|
|
84
|
+
// construction time) always has a real bound texture. Classic tolerates a
|
|
85
|
+
// `null` sampler uniform because it is only ever read inside an `if` guard
|
|
86
|
+
// that is false whenever the uniform is unset; the node backends bind every
|
|
87
|
+
// declared texture unconditionally regardless of runtime branching, so a
|
|
88
|
+
// `null` TextureNode.value is unsafe even inside a runtime-gated `If()`
|
|
89
|
+
// (docs/tsl-conventions.md gotcha 9 territory — same "always keep a valid
|
|
90
|
+
// fallback bound" rule as anime.js's fallbackWhiteTexture()).
|
|
91
|
+
let sharedFallbackTexture = null;
|
|
92
|
+
export function fallbackPostTexture() {
|
|
93
|
+
if (!sharedFallbackTexture) {
|
|
94
|
+
sharedFallbackTexture = new THREE.DataTexture(new Uint8Array([0, 0, 0, 255]), 1, 1);
|
|
95
|
+
sharedFallbackTexture.needsUpdate = true;
|
|
96
|
+
}
|
|
97
|
+
return sharedFallbackTexture;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function luma(color) {
|
|
101
|
+
return dot(color, vec3(0.2126, 0.7152, 0.0722));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function applySaturationHelper(color, amount) {
|
|
105
|
+
return mix(vec3(luma(color)), color, amount);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Raw NDC passthrough matching the classic bloom passes' vertex shader
|
|
109
|
+
// (`gl_Position = vec4(position.xy, 0.0, 1.0)`), which ignores the camera
|
|
110
|
+
// entirely — the bloom quad mesh's own [-1,1] geometry IS clip space.
|
|
111
|
+
// Assigning `material.vertexNode` directly (rather than `positionNode`)
|
|
112
|
+
// bypasses NodeMaterial's default MVP transform, mirroring GLSL exactly.
|
|
113
|
+
const bloomQuadVertexNode = vec4(positionLocal.xy, 0.0, 1.0);
|
|
114
|
+
|
|
115
|
+
function createBloomPassMaterialBase(name) {
|
|
116
|
+
const material = new NodeMaterial();
|
|
117
|
+
material.name = name;
|
|
118
|
+
material.depthTest = false;
|
|
119
|
+
material.depthWrite = false;
|
|
120
|
+
material.fog = false;
|
|
121
|
+
material.lights = false;
|
|
122
|
+
material.vertexNode = bloomQuadVertexNode;
|
|
123
|
+
return material;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Bloom prefilter: thresholds target.texture (character-mask aware) into the
|
|
128
|
+
* first mip of the pyramid chain. TSL port of bloomPrefilterFragmentShader.
|
|
129
|
+
*/
|
|
130
|
+
export function createBloomPrefilterNodeMaterial() {
|
|
131
|
+
const u = {
|
|
132
|
+
bloomBackgroundSuppress: uniform(1),
|
|
133
|
+
bloomCharacterBoost: uniform(1),
|
|
134
|
+
bloomThreshold: uniform(0.9),
|
|
135
|
+
tCharacterMask: texture(fallbackPostTexture()),
|
|
136
|
+
tInput: texture(fallbackPostTexture()),
|
|
137
|
+
useCharacterMask: uniform(0),
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const material = createBloomPassMaterialBase('PostProcessing.BloomPrefilterNode');
|
|
141
|
+
material.fragmentNode = Fn(() => {
|
|
142
|
+
const sampleUv = uv();
|
|
143
|
+
// Raw linear read — see the file header color-space audit: the scene
|
|
144
|
+
// buffer is linear on both pipelines, exactly like classic's prefilter
|
|
145
|
+
// input; no transfer function anywhere in the chain.
|
|
146
|
+
const color = u.tInput.sample(sampleUv).level(0).rgb;
|
|
147
|
+
const brightness = luma(color);
|
|
148
|
+
const bright = smoothstep(u.bloomThreshold, 1.0, brightness);
|
|
149
|
+
const excess = max(color.sub(u.bloomThreshold), vec3(0.0));
|
|
150
|
+
const mask = u.tCharacterMask.sample(sampleUv).level(0).r;
|
|
151
|
+
// mix()-based equivalent of `if (useCharacterMask > 0.5) { ... }`: safe
|
|
152
|
+
// because useCharacterMask is always exactly 0.0 or 1.0 (never
|
|
153
|
+
// fractional), and tCharacterMask always has a valid fallback bound, so
|
|
154
|
+
// unconditionally sampling it changes nothing observable while avoiding
|
|
155
|
+
// an extra If() purely for a cheap scalar blend.
|
|
156
|
+
const maskWeight = mix(1.0, mix(u.bloomBackgroundSuppress, u.bloomCharacterBoost, mask), u.useCharacterMask);
|
|
157
|
+
return vec4(excess.mul(bright).mul(maskWeight), 1.0);
|
|
158
|
+
})();
|
|
159
|
+
material.uniforms = u;
|
|
160
|
+
return material;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Bloom downsample: 5-tap box filter, one pyramid level narrower. TSL port
|
|
165
|
+
* of bloomDownsampleFragmentShader.
|
|
166
|
+
*/
|
|
167
|
+
export function createBloomDownsampleNodeMaterial() {
|
|
168
|
+
const u = {
|
|
169
|
+
tInput: texture(fallbackPostTexture()),
|
|
170
|
+
texelSize: uniform(new THREE.Vector2()),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const material = createBloomPassMaterialBase('PostProcessing.BloomDownsampleNode');
|
|
174
|
+
material.fragmentNode = Fn(() => {
|
|
175
|
+
const sampleUv = uv();
|
|
176
|
+
const halfTexel = u.texelSize.mul(0.5);
|
|
177
|
+
const sum = u.tInput.sample(sampleUv).level(0).rgb.mul(4.0).toVar();
|
|
178
|
+
sum.addAssign(u.tInput.sample(sampleUv.sub(halfTexel)).level(0).rgb);
|
|
179
|
+
sum.addAssign(u.tInput.sample(sampleUv.add(halfTexel)).level(0).rgb);
|
|
180
|
+
sum.addAssign(u.tInput.sample(sampleUv.add(vec2(halfTexel.x, halfTexel.y.negate()))).level(0).rgb);
|
|
181
|
+
sum.addAssign(u.tInput.sample(sampleUv.sub(vec2(halfTexel.x, halfTexel.y.negate()))).level(0).rgb);
|
|
182
|
+
return vec4(sum.div(8.0), 1.0);
|
|
183
|
+
})();
|
|
184
|
+
material.uniforms = u;
|
|
185
|
+
return material;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Bloom upsample: 8-tap tent filter, accumulating the narrower level back
|
|
190
|
+
* onto the wider one. TSL port of bloomUpsampleFragmentShader.
|
|
191
|
+
*/
|
|
192
|
+
export function createBloomUpsampleNodeMaterial() {
|
|
193
|
+
const u = {
|
|
194
|
+
bloomSpread: uniform(1),
|
|
195
|
+
tAccumulate: texture(fallbackPostTexture()),
|
|
196
|
+
tInput: texture(fallbackPostTexture()),
|
|
197
|
+
texelSize: uniform(new THREE.Vector2()),
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const material = createBloomPassMaterialBase('PostProcessing.BloomUpsampleNode');
|
|
201
|
+
material.fragmentNode = Fn(() => {
|
|
202
|
+
const sampleUv = uv();
|
|
203
|
+
const t = u.texelSize.mul(u.bloomSpread);
|
|
204
|
+
const sum = u.tInput.sample(sampleUv.add(vec2(t.x.mul(-2.0), 0.0))).level(0).rgb.toVar();
|
|
205
|
+
sum.addAssign(u.tInput.sample(sampleUv.add(vec2(t.x.negate(), t.y))).level(0).rgb.mul(2.0));
|
|
206
|
+
sum.addAssign(u.tInput.sample(sampleUv.add(vec2(0.0, t.y.mul(2.0)))).level(0).rgb);
|
|
207
|
+
sum.addAssign(u.tInput.sample(sampleUv.add(vec2(t.x, t.y))).level(0).rgb.mul(2.0));
|
|
208
|
+
sum.addAssign(u.tInput.sample(sampleUv.add(vec2(t.x.mul(2.0), 0.0))).level(0).rgb);
|
|
209
|
+
sum.addAssign(u.tInput.sample(sampleUv.add(vec2(t.x, t.y.negate()))).level(0).rgb.mul(2.0));
|
|
210
|
+
sum.addAssign(u.tInput.sample(sampleUv.add(vec2(0.0, t.y.mul(-2.0)))).level(0).rgb);
|
|
211
|
+
sum.addAssign(u.tInput.sample(sampleUv.add(vec2(t.x.negate(), t.y.negate()))).level(0).rgb.mul(2.0));
|
|
212
|
+
return vec4(sum.div(12.0).add(u.tAccumulate.sample(sampleUv).level(0).rgb), 1.0);
|
|
213
|
+
})();
|
|
214
|
+
material.uniforms = u;
|
|
215
|
+
return material;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Final composite: motion blur, single-pass or pyramid bloom add, depth cue,
|
|
220
|
+
* color grade, LUT, vertical grade, screen outline, vignette, strength
|
|
221
|
+
* blend, dither. TSL port of FinalCompositeShader. `.uniforms` exposes every
|
|
222
|
+
* GLSL uniform name so applyCompositeSettings() in postProcessing.js works
|
|
223
|
+
* unchanged.
|
|
224
|
+
*/
|
|
225
|
+
export function createPostCompositeNodeMaterial() {
|
|
226
|
+
const u = {
|
|
227
|
+
bloomBackgroundSuppress: uniform(1.0),
|
|
228
|
+
bloomCharacterBoost: uniform(1.0),
|
|
229
|
+
bloomRadius: uniform(0.28),
|
|
230
|
+
bloomStrength: uniform(0.0),
|
|
231
|
+
bloomThreshold: uniform(0.84),
|
|
232
|
+
tBloom: texture(fallbackPostTexture()),
|
|
233
|
+
useBloomTexture: uniform(0.0),
|
|
234
|
+
tCharacterMask: texture(fallbackPostTexture()),
|
|
235
|
+
useCharacterMask: uniform(0.0),
|
|
236
|
+
useMotionBlur: uniform(0.0),
|
|
237
|
+
motionBlurStrength: uniform(0.0),
|
|
238
|
+
motionBlurInverseViewProjection: uniform(new THREE.Matrix4()),
|
|
239
|
+
motionBlurPreviousViewProjection: uniform(new THREE.Matrix4()),
|
|
240
|
+
bottomDark: uniform(0.0),
|
|
241
|
+
cameraFar: uniform(100.0),
|
|
242
|
+
cameraNear: uniform(0.1),
|
|
243
|
+
contrast: uniform(1.0),
|
|
244
|
+
depthCueColor: uniform(new THREE.Color(0x9db7d8)),
|
|
245
|
+
depthCueFar: uniform(24.0),
|
|
246
|
+
depthCueNear: uniform(1.0),
|
|
247
|
+
depthCueStrength: uniform(0.0),
|
|
248
|
+
exposure: uniform(1.0),
|
|
249
|
+
tLut: texture(fallbackPostTexture()),
|
|
250
|
+
lutSize: uniform(0.0),
|
|
251
|
+
lutStrength: uniform(0.0),
|
|
252
|
+
outlineColor: uniform(new THREE.Color(0x10131a)),
|
|
253
|
+
outlineDepthStrength: uniform(0.0),
|
|
254
|
+
outlineLumaStrength: uniform(0.0),
|
|
255
|
+
outlineStrength: uniform(0.0),
|
|
256
|
+
resolution: uniform(new THREE.Vector2(1, 1)),
|
|
257
|
+
saturation: uniform(1.0),
|
|
258
|
+
strength: uniform(1.0),
|
|
259
|
+
// Float-color scene depth (docs/tsl-conventions.md gotcha 5) written by
|
|
260
|
+
// the auto-gated prepass in postProcessing.js — NOT a classic
|
|
261
|
+
// THREE.DepthTexture. Same [0,1] window-depth convention, so
|
|
262
|
+
// perspectiveDepthToViewZ below is unchanged from the classic formula.
|
|
263
|
+
tDepth: texture(fallbackPostTexture()),
|
|
264
|
+
tDiffuse: texture(fallbackPostTexture()),
|
|
265
|
+
topLight: uniform(0.0),
|
|
266
|
+
useColorGrade: uniform(0.0),
|
|
267
|
+
useDepthCue: uniform(0.0),
|
|
268
|
+
useScreenOutline: uniform(0.0),
|
|
269
|
+
useVignette: uniform(0.0),
|
|
270
|
+
useVerticalGrade: uniform(0.0),
|
|
271
|
+
vignetteRadius: uniform(0.72),
|
|
272
|
+
vignetteSoftness: uniform(0.34),
|
|
273
|
+
vignetteStrength: uniform(0.0),
|
|
274
|
+
warmth: uniform(0.0),
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// ---- Helpers (closures over u; docs/tsl-conventions.md "leaf helpers
|
|
278
|
+
// that map nodes->nodes are Fn() exports") ----
|
|
279
|
+
|
|
280
|
+
// RT-orientation gap (docs/tsl-conventions.md gotcha 6, projective-sample
|
|
281
|
+
// form): this material is the one RT->canvas pass in the chain, and the
|
|
282
|
+
// node backends store render-target rows top-down relative to the canvas
|
|
283
|
+
// quad's uv — every fetch from an offscreen target must flip v. Screen-
|
|
284
|
+
// anchored math (vignette, bottom/top gradients, NDC reconstruction) keeps
|
|
285
|
+
// the raw quad uv; RT->RT bloom passes are self-consistent and don't flip.
|
|
286
|
+
const rtUv = Fn(([sampleUv]) => vec2(sampleUv.x, sampleUv.y.oneMinus()));
|
|
287
|
+
|
|
288
|
+
// Raw linear read — see the file header color-space audit: the offscreen
|
|
289
|
+
// buffer holds linear color on BOTH pipelines (classic's baked include is
|
|
290
|
+
// identity for a NoColorSpace target), so no transfer function here.
|
|
291
|
+
const sampleSceneColor = Fn(([sampleUv]) => {
|
|
292
|
+
return u.tDiffuse.sample(rtUv(sampleUv)).level(0).rgb;
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const readLinearDepth = Fn(([sampleUv]) => {
|
|
296
|
+
const fragCoordZ = u.tDepth.sample(rtUv(sampleUv)).level(0).x;
|
|
297
|
+
const viewZ = perspectiveDepthToViewZ(fragCoordZ, u.cameraNear, u.cameraFar);
|
|
298
|
+
return viewZToOrthographicDepth(viewZ, u.cameraNear, u.cameraFar);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const sampleLutSlice = Fn(([color, slice]) => {
|
|
302
|
+
const uCoord = slice.mul(u.lutSize).add(0.5).add(color.r.mul(u.lutSize.sub(1.0))).div(u.lutSize.mul(u.lutSize));
|
|
303
|
+
const vCoord = float(0.5).add(color.g.mul(u.lutSize.sub(1.0))).div(u.lutSize);
|
|
304
|
+
return u.tLut.sample(vec2(uCoord, vCoord)).level(0).rgb;
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const applyLut = Fn(([color]) => {
|
|
308
|
+
const clamped = clamp(color, 0.0, 1.0).toVar();
|
|
309
|
+
const blue = clamped.b.mul(u.lutSize.sub(1.0));
|
|
310
|
+
const slice0 = floor(blue);
|
|
311
|
+
const slice1 = min(slice0.add(1.0), u.lutSize.sub(1.0));
|
|
312
|
+
return mix(sampleLutSlice(clamped, slice0), sampleLutSlice(clamped, slice1), blue.sub(slice0));
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const brightSample = Fn(([sampleUv]) => {
|
|
316
|
+
const color = sampleSceneColor(sampleUv);
|
|
317
|
+
const bright = smoothstep(u.bloomThreshold, 1.0, luma(color));
|
|
318
|
+
const excess = max(color.sub(u.bloomThreshold), vec3(0.0));
|
|
319
|
+
const mask = u.tCharacterMask.sample(rtUv(sampleUv)).level(0).r;
|
|
320
|
+
const maskWeight = mix(1.0, mix(u.bloomBackgroundSuppress, u.bloomCharacterBoost, mask), u.useCharacterMask);
|
|
321
|
+
return excess.mul(bright).mul(maskWeight);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const bloomSample = Fn(([texel, centerUv]) => {
|
|
325
|
+
const radius = max(u.bloomRadius, 0.001).mul(8.0);
|
|
326
|
+
const offsetX = vec2(texel.x.mul(radius), 0.0);
|
|
327
|
+
const offsetY = vec2(0.0, texel.y.mul(radius));
|
|
328
|
+
const offsetD = vec2(texel.x.mul(radius).mul(0.7071), texel.y.mul(radius).mul(0.7071));
|
|
329
|
+
|
|
330
|
+
const bloom = brightSample(centerUv).mul(0.2).toVar();
|
|
331
|
+
bloom.addAssign(brightSample(centerUv.add(offsetX)).mul(0.1));
|
|
332
|
+
bloom.addAssign(brightSample(centerUv.sub(offsetX)).mul(0.1));
|
|
333
|
+
bloom.addAssign(brightSample(centerUv.add(offsetY)).mul(0.1));
|
|
334
|
+
bloom.addAssign(brightSample(centerUv.sub(offsetY)).mul(0.1));
|
|
335
|
+
bloom.addAssign(brightSample(centerUv.add(offsetD)).mul(0.075));
|
|
336
|
+
bloom.addAssign(brightSample(centerUv.sub(offsetD)).mul(0.075));
|
|
337
|
+
bloom.addAssign(brightSample(centerUv.add(vec2(offsetD.x, offsetD.y.negate()))).mul(0.075));
|
|
338
|
+
bloom.addAssign(brightSample(centerUv.add(vec2(offsetD.x.negate(), offsetD.y))).mul(0.075));
|
|
339
|
+
return bloom;
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const lumaEdge = Fn(([texel, centerUv]) => {
|
|
343
|
+
const lumaAt = (offsetUv) => luma(sampleSceneColor(offsetUv));
|
|
344
|
+
const center = lumaAt(centerUv);
|
|
345
|
+
const horizontal = abs(lumaAt(centerUv.add(vec2(texel.x, 0.0))).sub(center))
|
|
346
|
+
.add(abs(lumaAt(centerUv.sub(vec2(texel.x, 0.0))).sub(center)));
|
|
347
|
+
const vertical = abs(lumaAt(centerUv.add(vec2(0.0, texel.y))).sub(center))
|
|
348
|
+
.add(abs(lumaAt(centerUv.sub(vec2(0.0, texel.y))).sub(center)));
|
|
349
|
+
return horizontal.add(vertical);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const depthEdge = Fn(([texel, centerUv]) => {
|
|
353
|
+
const center = readLinearDepth(centerUv);
|
|
354
|
+
const horizontal = abs(readLinearDepth(centerUv.add(vec2(texel.x, 0.0))).sub(center))
|
|
355
|
+
.add(abs(readLinearDepth(centerUv.sub(vec2(texel.x, 0.0))).sub(center)));
|
|
356
|
+
const vertical = abs(readLinearDepth(centerUv.add(vec2(0.0, texel.y))).sub(center))
|
|
357
|
+
.add(abs(readLinearDepth(centerUv.sub(vec2(0.0, texel.y))).sub(center)));
|
|
358
|
+
return horizontal.add(vertical);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const material = new NodeMaterial();
|
|
362
|
+
material.name = 'PostProcessing.FinalCompositeNode';
|
|
363
|
+
material.depthTest = false;
|
|
364
|
+
material.depthWrite = false;
|
|
365
|
+
material.fog = false;
|
|
366
|
+
material.lights = false;
|
|
367
|
+
|
|
368
|
+
material.fragmentNode = Fn(() => {
|
|
369
|
+
const vUv = uv();
|
|
370
|
+
|
|
371
|
+
const rawSource = u.tDiffuse.sample(rtUv(vUv)).level(0).toVar();
|
|
372
|
+
const sourceAlpha = rawSource.a;
|
|
373
|
+
const originalColor = rawSource.rgb.toVar();
|
|
374
|
+
|
|
375
|
+
If(u.useMotionBlur.greaterThan(0.5), () => {
|
|
376
|
+
const fragDepth = u.tDepth.sample(rtUv(vUv)).level(0).x;
|
|
377
|
+
const ndc = vec4(vUv.mul(2.0).sub(1.0), fragDepth.mul(2.0).sub(1.0), 1.0);
|
|
378
|
+
const worldPos = u.motionBlurInverseViewProjection.mul(ndc).toVar();
|
|
379
|
+
worldPos.assign(worldPos.div(worldPos.w));
|
|
380
|
+
const prevClip = u.motionBlurPreviousViewProjection.mul(worldPos);
|
|
381
|
+
const prevUv = prevClip.xy.div(max(abs(prevClip.w), 1e-6)).mul(sign(prevClip.w)).mul(0.5).add(0.5);
|
|
382
|
+
const velocity = vUv.sub(prevUv).mul(u.motionBlurStrength).toVar();
|
|
383
|
+
const speed = length(velocity);
|
|
384
|
+
If(speed.greaterThan(0.0001), () => {
|
|
385
|
+
velocity.assign(velocity.mul(min(1.0, float(0.05).div(speed))));
|
|
386
|
+
const blurSum = originalColor.toVar();
|
|
387
|
+
for (let i = 1; i < 8; i += 1) {
|
|
388
|
+
blurSum.addAssign(sampleSceneColor(vUv.add(velocity.mul(float(i).div(7.0).sub(0.5)))));
|
|
389
|
+
}
|
|
390
|
+
originalColor.assign(blurSum.div(8.0));
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const color = originalColor.toVar();
|
|
395
|
+
const texel = vec2(1.0).div(max(u.resolution, vec2(1.0)));
|
|
396
|
+
|
|
397
|
+
If(u.useBloomTexture.greaterThan(0.5), () => {
|
|
398
|
+
color.addAssign(u.tBloom.sample(rtUv(vUv)).level(0).rgb.mul(u.bloomStrength));
|
|
399
|
+
}).ElseIf(u.bloomStrength.greaterThan(0.0), () => {
|
|
400
|
+
color.addAssign(bloomSample(texel, vUv).mul(u.bloomStrength));
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
If(u.useDepthCue.greaterThan(0.5), () => {
|
|
404
|
+
const depth = readLinearDepth(vUv);
|
|
405
|
+
const cue = smoothstep(u.depthCueNear, u.depthCueFar, depth).mul(u.depthCueStrength);
|
|
406
|
+
color.assign(mix(color, u.depthCueColor, cue));
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
If(u.useColorGrade.greaterThan(0.5), () => {
|
|
410
|
+
color.mulAssign(u.exposure);
|
|
411
|
+
color.assign(color.sub(0.5).mul(u.contrast).add(0.5));
|
|
412
|
+
color.assign(applySaturationHelper(color, u.saturation));
|
|
413
|
+
color.mulAssign(vec3(
|
|
414
|
+
u.warmth.add(1.0),
|
|
415
|
+
u.warmth.mul(0.35).add(1.0),
|
|
416
|
+
u.warmth.mul(-0.55).add(1.0),
|
|
417
|
+
));
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
If(u.lutStrength.greaterThan(0.0).and(u.lutSize.greaterThan(1.5)), () => {
|
|
421
|
+
color.assign(mix(color, applyLut(color), clamp(u.lutStrength, 0.0, 1.0)));
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
If(u.useVerticalGrade.greaterThan(0.5), () => {
|
|
425
|
+
const top = smoothstep(0.36, 1.0, vUv.y).mul(u.topLight);
|
|
426
|
+
const bottom = smoothstep(0.0, 0.72, vUv.y).oneMinus().mul(u.bottomDark);
|
|
427
|
+
color.addAssign(vec3(1.0, 0.92, 0.78).mul(top));
|
|
428
|
+
color.mulAssign(bottom.oneMinus());
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
If(u.useScreenOutline.greaterThan(0.5), () => {
|
|
432
|
+
const edge = lumaEdge(texel, vUv).mul(u.outlineLumaStrength).add(depthEdge(texel, vUv).mul(u.outlineDepthStrength));
|
|
433
|
+
const edgeMask = smoothstep(0.02, 0.18, edge).mul(u.outlineStrength);
|
|
434
|
+
color.assign(mix(color, u.outlineColor, clamp(edgeMask, 0.0, 1.0)));
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
If(u.useVignette.greaterThan(0.5), () => {
|
|
438
|
+
const dist = distance(vUv, vec2(0.5));
|
|
439
|
+
const vignetteMask = smoothstep(u.vignetteRadius, u.vignetteRadius.add(u.vignetteSoftness), dist);
|
|
440
|
+
color.mulAssign(vignetteMask.mul(u.vignetteStrength).oneMinus());
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
color.assign(mix(originalColor, color, clamp(u.strength, 0.0, 1.0)));
|
|
444
|
+
|
|
445
|
+
// Interleaved-gradient dither (see FinalCompositeShader) — pixel
|
|
446
|
+
// coordinates via screenCoordinate, the TSL equivalent of gl_FragCoord.xy
|
|
447
|
+
// already used the same way in anime.js's Bayer dither.
|
|
448
|
+
const gradientNoise = fract(dot(screenCoordinate.xy, vec2(0.06711056, 0.00583715)));
|
|
449
|
+
const dither = fract(gradientNoise.mul(52.9829189));
|
|
450
|
+
color.addAssign(dither.sub(0.5).mul(1.5 / 255.0));
|
|
451
|
+
|
|
452
|
+
// No manual tone-mapping/color-space re-encode here — see the file
|
|
453
|
+
// header color-space audit. The renderer's automatic terminal-canvas
|
|
454
|
+
// output pass supplies the second encode (matching classic's own second
|
|
455
|
+
// `colorspace_fragment` include); adding one here would double-apply it.
|
|
456
|
+
return vec4(max(color, vec3(0.0)), sourceAlpha);
|
|
457
|
+
})();
|
|
458
|
+
|
|
459
|
+
material.uniforms = u;
|
|
460
|
+
return material;
|
|
461
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// TSL port of src/shaders/sky.vert.glsl + sky.frag.glsl — stylized procedural
|
|
2
|
+
// sky dome. Vertical gradient, sun disc with glow, painterly two-tone fbm
|
|
3
|
+
// clouds, and twinkling stars. Fully procedural; pairs with the water's
|
|
4
|
+
// procedural sky fallback.
|
|
5
|
+
//
|
|
6
|
+
// Parity notes vs the GLSL ShaderMaterial:
|
|
7
|
+
// - The dome renders at the far plane via a custom vertexNode
|
|
8
|
+
// (gl_Position.z = w * 0.99995 in the GLSL vertex stage).
|
|
9
|
+
// - GLSL ends with tonemapping_fragment + colorspace_fragment; the node
|
|
10
|
+
// renderer applies the same output transforms automatically.
|
|
11
|
+
// - The material exposes `.uniforms` (node objects keyed by the GLSL uniform
|
|
12
|
+
// names, each with a `.value`) so applySkySettingsToMaterial and
|
|
13
|
+
// StylizedSky.update write the same slots on both backends.
|
|
14
|
+
|
|
15
|
+
import * as THREE from 'three';
|
|
16
|
+
import {
|
|
17
|
+
acos,
|
|
18
|
+
cameraProjectionMatrix,
|
|
19
|
+
cameraViewMatrix,
|
|
20
|
+
clamp,
|
|
21
|
+
dot,
|
|
22
|
+
float,
|
|
23
|
+
Fn,
|
|
24
|
+
If,
|
|
25
|
+
max,
|
|
26
|
+
mix,
|
|
27
|
+
modelWorldMatrix,
|
|
28
|
+
normalize,
|
|
29
|
+
positionLocal,
|
|
30
|
+
pow,
|
|
31
|
+
select,
|
|
32
|
+
smoothstep,
|
|
33
|
+
step,
|
|
34
|
+
uniform,
|
|
35
|
+
vec2,
|
|
36
|
+
vec3,
|
|
37
|
+
vec4,
|
|
38
|
+
} from 'three/tsl';
|
|
39
|
+
import { MeshBasicNodeMaterial } from 'three/webgpu';
|
|
40
|
+
|
|
41
|
+
import { waterFbm, waterHash12, waterToonStep, waterVoronoi2 } from './chunks/water-common.js';
|
|
42
|
+
|
|
43
|
+
export function createSkyNodeMaterial() {
|
|
44
|
+
const uniforms = {
|
|
45
|
+
uCloudColor: uniform(new THREE.Color()),
|
|
46
|
+
uCloudCoverage: uniform(0),
|
|
47
|
+
uCloudScale: uniform(0),
|
|
48
|
+
uCloudShadeColor: uniform(new THREE.Color()),
|
|
49
|
+
uCloudSpeed: uniform(0),
|
|
50
|
+
uGroundColor: uniform(new THREE.Color()),
|
|
51
|
+
uHorizonColor: uniform(new THREE.Color()),
|
|
52
|
+
uHorizonScattering: uniform(0),
|
|
53
|
+
uStarsStrength: uniform(0),
|
|
54
|
+
uSunColor: uniform(new THREE.Color()),
|
|
55
|
+
uSunDirection: uniform(new THREE.Vector3(0, 1, 0)),
|
|
56
|
+
uSunGlowStrength: uniform(0),
|
|
57
|
+
uSunSize: uniform(0),
|
|
58
|
+
uTime: uniform(0),
|
|
59
|
+
uZenithColor: uniform(new THREE.Color()),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const material = new MeshBasicNodeMaterial({
|
|
63
|
+
depthWrite: false,
|
|
64
|
+
side: THREE.BackSide,
|
|
65
|
+
});
|
|
66
|
+
material.name = 'StylizedSky';
|
|
67
|
+
material.fog = false;
|
|
68
|
+
material.lights = false;
|
|
69
|
+
|
|
70
|
+
// Vertex stage: dome pinned to the far plane regardless of sphere radius,
|
|
71
|
+
// so it never clips scene geometry and works inside reflection passes.
|
|
72
|
+
const clipPosition = cameraProjectionMatrix
|
|
73
|
+
.mul(cameraViewMatrix)
|
|
74
|
+
.mul(modelWorldMatrix)
|
|
75
|
+
.mul(vec4(positionLocal, 1.0));
|
|
76
|
+
material.vertexNode = vec4(
|
|
77
|
+
clipPosition.x,
|
|
78
|
+
clipPosition.y,
|
|
79
|
+
clipPosition.w.mul(0.99995),
|
|
80
|
+
clipPosition.w,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
material.colorNode = Fn(() => {
|
|
84
|
+
const direction = normalize(positionLocal).toVar();
|
|
85
|
+
const up = direction.y.toVar();
|
|
86
|
+
|
|
87
|
+
// Base vertical gradient with a mirrored ground fade below the horizon.
|
|
88
|
+
const zenithMix = pow(clamp(up, 0.0, 1.0), 0.48);
|
|
89
|
+
const color = select(
|
|
90
|
+
up.greaterThanEqual(0.0),
|
|
91
|
+
mix(uniforms.uHorizonColor, uniforms.uZenithColor, zenithMix),
|
|
92
|
+
mix(uniforms.uHorizonColor, uniforms.uGroundColor, pow(clamp(up.negate(), 0.0, 1.0), 0.55)),
|
|
93
|
+
).toVar();
|
|
94
|
+
|
|
95
|
+
// Sun disc and glow.
|
|
96
|
+
const cosAngle = dot(direction, uniforms.uSunDirection).toVar();
|
|
97
|
+
const angle = acos(clamp(cosAngle, -1.0, 1.0));
|
|
98
|
+
const sunDisc = smoothstep(uniforms.uSunSize.mul(0.5), uniforms.uSunSize, angle)
|
|
99
|
+
.oneMinus()
|
|
100
|
+
.mul(smoothstep(0.55, 0.82, uniforms.uCloudCoverage).oneMinus());
|
|
101
|
+
const sunGlow = pow(max(cosAngle, 0.0), 5.0).mul(0.16)
|
|
102
|
+
.add(pow(max(cosAngle, 0.0), 60.0).mul(0.5))
|
|
103
|
+
.toVar();
|
|
104
|
+
color.addAssign(
|
|
105
|
+
uniforms.uSunColor.mul(sunDisc.mul(2.4).add(sunGlow.mul(uniforms.uSunGlowStrength))),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Atmospheric scattering hint: a warm wedge along the sun-side horizon
|
|
109
|
+
// that fades with altitude.
|
|
110
|
+
const horizonBand = smoothstep(0.0, 0.42, up.abs()).oneMinus();
|
|
111
|
+
const sunward = pow(clamp(cosAngle.mul(0.5).add(0.5), 0.0, 1.0), 5.0);
|
|
112
|
+
color.addAssign(
|
|
113
|
+
uniforms.uSunColor
|
|
114
|
+
.mul(horizonBand)
|
|
115
|
+
.mul(sunward)
|
|
116
|
+
.mul(uniforms.uHorizonScattering)
|
|
117
|
+
.mul(0.35),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Stars: sparse grid glints, hidden below the horizon and near the sun.
|
|
121
|
+
// The If guard matches the GLSL branch — it also keeps the projected UV
|
|
122
|
+
// division (up + 0.28 crosses zero below the horizon) out of reach.
|
|
123
|
+
If(uniforms.uStarsStrength.greaterThan(0.001).and(up.greaterThan(0.02)), () => {
|
|
124
|
+
const starUv = direction.xz.div(up.add(0.28)).mul(14.0);
|
|
125
|
+
const starVoro = waterVoronoi2(starUv);
|
|
126
|
+
const starRandom = waterHash12(starVoro.zw);
|
|
127
|
+
const twinkle = uniforms.uTime
|
|
128
|
+
.mul(starRandom.mul(2.4).add(1.2))
|
|
129
|
+
.add(starRandom.mul(31.0))
|
|
130
|
+
.sin()
|
|
131
|
+
.mul(0.4)
|
|
132
|
+
.add(0.6);
|
|
133
|
+
const star = smoothstep(0.0, 0.06, starVoro.x).oneMinus().mul(step(0.72, starRandom));
|
|
134
|
+
color.addAssign(
|
|
135
|
+
vec3(1.0, 0.98, 0.92)
|
|
136
|
+
.mul(star)
|
|
137
|
+
.mul(twinkle)
|
|
138
|
+
.mul(uniforms.uStarsStrength)
|
|
139
|
+
.mul(smoothstep(0.03, 0.24, up))
|
|
140
|
+
.mul(sunGlow.oneMinus()),
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Painterly clouds: fbm silhouettes with a toon-stepped shadow side and a
|
|
145
|
+
// silver lining toward the sun, projected onto a virtual cloud plane.
|
|
146
|
+
If(uniforms.uCloudCoverage.greaterThan(0.001).and(up.greaterThan(0.015)), () => {
|
|
147
|
+
const cloudUv = direction.xz
|
|
148
|
+
.div(up.add(0.22))
|
|
149
|
+
.mul(uniforms.uCloudScale)
|
|
150
|
+
.add(uniforms.uTime.mul(uniforms.uCloudSpeed).mul(vec2(0.021, 0.006)))
|
|
151
|
+
.toVar();
|
|
152
|
+
const cloudBase = waterFbm(cloudUv, 4).toVar();
|
|
153
|
+
const threshold = mix(0.74, 0.34, clamp(uniforms.uCloudCoverage, 0.0, 1.0));
|
|
154
|
+
const cloudCore = smoothstep(threshold.add(0.1), threshold.add(0.24), cloudBase);
|
|
155
|
+
const cloudEdge = smoothstep(threshold, threshold.add(0.1), cloudBase);
|
|
156
|
+
const cloudMask = max(cloudCore, cloudEdge.mul(0.65));
|
|
157
|
+
|
|
158
|
+
const litSample = waterFbm(cloudUv.sub(uniforms.uSunDirection.xz.mul(0.4)), 4);
|
|
159
|
+
const shade = waterToonStep(float(0.02), float(0.06), cloudBase.sub(litSample));
|
|
160
|
+
const cloudColor = mix(uniforms.uCloudColor, uniforms.uCloudShadeColor, shade.mul(0.85))
|
|
161
|
+
.add(uniforms.uSunColor.mul(pow(max(cosAngle, 0.0), 10.0)).mul(0.3))
|
|
162
|
+
.toVar();
|
|
163
|
+
|
|
164
|
+
const horizonFade = smoothstep(0.015, 0.16, up);
|
|
165
|
+
color.assign(mix(color, cloudColor, cloudMask.mul(horizonFade)));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return vec4(color, 1.0);
|
|
169
|
+
})();
|
|
170
|
+
|
|
171
|
+
// Same-name uniform slots as the ShaderMaterial (UniformNodes expose
|
|
172
|
+
// `.value` just like ShaderMaterial uniform entries).
|
|
173
|
+
material.uniforms = uniforms;
|
|
174
|
+
return material;
|
|
175
|
+
}
|