@call-me-sensei/toonlab 0.4.21 → 0.4.23
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/NPM-LIBRARY.md +87 -27
- package/README.md +11 -10
- package/agents/PROMPTS.md +17 -3
- package/agents/README.md +13 -3
- package/agents/claude/CLAUDE.md +3 -0
- package/agents/codex/AGENTS.md +112 -159
- package/agents/cursor/toonlab.mdc +7 -3
- package/agents/examples/game-foundation.mjs +104 -0
- package/agents/references/game-lifecycle.md +57 -0
- package/agents/references/runtime-entry-points.md +26 -0
- package/agents/references/style-bundles.md +4 -1
- package/agents/skills/claude/asset-sourcing/SKILL.md +7 -4
- package/agents/skills/claude/environment/SKILL.md +4 -0
- package/agents/skills/claude/game-dev/SKILL.md +63 -87
- package/agents/skills/claude/lighting/SKILL.md +36 -0
- package/agents/skills/claude/post-processing/SKILL.md +8 -3
- package/agents/skills/claude/rock-ground-shaders/SKILL.md +10 -15
- package/agents/skills/claude/rockgen/SKILL.md +52 -21
- package/agents/skills/claude/scene-style-application/SKILL.md +4 -0
- package/agents/skills/claude/style-presets/SKILL.md +11 -8
- package/agents/skills/claude/toon-shading/SKILL.md +4 -0
- package/agents/skills/claude/vegetation-sky/SKILL.md +13 -7
- package/agents/skills/claude/visual-verification/SKILL.md +45 -119
- package/agents/skills/claude/water/SKILL.md +6 -2
- package/agents/skills/codex/asset-sourcing/SKILL.md +7 -4
- package/agents/skills/codex/environment/SKILL.md +4 -0
- package/agents/skills/codex/game-dev/SKILL.md +63 -87
- package/agents/skills/codex/lighting/SKILL.md +36 -0
- package/agents/skills/codex/post-processing/SKILL.md +8 -3
- package/agents/skills/codex/rock-ground-shaders/SKILL.md +10 -15
- package/agents/skills/codex/rockgen/SKILL.md +52 -21
- package/agents/skills/codex/scene-style-application/SKILL.md +4 -0
- package/agents/skills/codex/style-presets/SKILL.md +11 -8
- package/agents/skills/codex/toon-shading/SKILL.md +4 -0
- package/agents/skills/codex/vegetation-sky/SKILL.md +13 -7
- package/agents/skills/codex/visual-verification/SKILL.md +45 -119
- package/agents/skills/codex/water/SKILL.md +6 -2
- package/database/apply-one-catalog-seed.mjs +63 -0
- package/database/providers.mjs +1 -0
- package/database/seeds/catalog/0005_2026-08-c8-first12.sql +1101 -0
- package/database/seeds/catalog/0006_2026-09-c8-first100.sql +14245 -0
- package/database/seeds/catalog/0007_2026-09-c8-first100-primary-model.sql +14845 -0
- package/mcp/lab-management.mjs +11 -8
- package/mcp/public-catalog.mjs +0 -16
- package/mcp/tree-lab-contract.mjs +2381 -0
- package/mcp/vite-plugin.mjs +5 -1
- package/package.json +29 -18
- package/src/catalog/officialCatalog.js +1 -0
- package/src/catalog/officialCatalogAssetRuntime.js +98 -1
- package/src/catalog/officialCatalogLod.js +160 -16
- package/src/catalog/officialCatalogPlacement.js +52 -12
- package/src/catalog/officialCatalogProvider.js +79 -8
- package/src/catalog/officialCatalogRockPackage.js +172 -0
- package/src/cloud/cloudReprojection.js +40 -7
- package/src/cloud/cloudVolume.js +44 -7
- package/src/cloud/noise/baseShapeVolume.js +8 -6
- package/src/cloud/noise/cirrusMap.js +2 -1
- package/src/cloud/noise/curlNoise.js +2 -1
- package/src/cloud/noise/erosionVolume.js +2 -1
- package/src/cloud/noise/textureCache.js +77 -0
- package/src/cloud/noise/weatherMap.js +2 -1
- package/src/core/sha256.js +83 -0
- package/src/ground-shader/groundShaderMaterial.js +12 -3
- package/src/rock-shader/rockMaterial.js +205 -28
- package/src/rock-shader/rockShaderRuntime.js +17 -1
- package/src/rock-shader/rockShaderSettings.js +16 -0
- package/src/rockgen/index.js +9 -0
- package/src/rockgen/lod/index.js +1 -0
- package/src/rockgen/lod/rockDenseFieldPolicy.js +143 -0
- package/src/rockgen/rockDocument.js +899 -35
- package/src/rockgen/surface/c7GeologySurface.js +39 -9
- package/src/rockgen/surface/naturalRockSurface.js +1948 -0
- package/src/shaders-tsl/chunks/projected-water-caustics.js +14 -2
- package/src/shaders-tsl/chunks/water-foam.js +6 -6
- package/src/shaders-tsl/chunks/water-lighting.js +28 -5
- package/src/shaders-tsl/chunks/water-shore-state.js +5 -2
- package/src/shaders-tsl/chunks/water-waves.js +10 -5
- package/src/shaders-tsl/flower.js +7 -4
- package/src/shaders-tsl/grass.js +4 -3
- package/src/shaders-tsl/water-shore-state-simulation.js +3 -2
- package/src/shaders-tsl/water.js +209 -41
- package/src/sky/skySystem.js +30 -7
- package/src/toon/settings/perspectiveRemovalSettings.js +1 -1
- package/src/toon/toonSettings.js +74 -0
- package/src/vegetation/stylizedFlower.js +6 -0
- package/src/version.js +1 -1
- package/src/water/waterDetailSpectrum.js +91 -0
- package/src/water/waterDynamics.js +422 -0
- package/src/water/waterFoamParticles.js +196 -0
- package/src/water/waterFoamTexture.js +57 -0
- package/src/water/waterHydrodynamics.js +209 -0
- package/src/water/waterMaterial.js +1 -0
- package/src/water/waterRenderExtension.js +107 -0
- package/src/water/waterScenePasses.js +14 -6
- package/src/water/waterSettings.js +53 -26
- package/src/water/waterShoreMaterial.js +38 -14
- package/src/water/waterSpectralOcean.js +137 -0
- package/src/water/waterStageSettings.js +19 -1
- package/src/water/waterSurface.js +62 -18
- package/src/water/waterUnderwaterAtmosphere.js +30 -3
- package/types/catalog/officialCatalog.d.ts +1 -0
- package/types/catalog/officialCatalogPlacement.d.ts +4 -0
- package/types/catalog/officialCatalogRockPackage.d.ts +68 -0
- package/types/cloud/noise/baseShapeVolume.d.ts +1 -1
- package/types/cloud/noise/textureCache.d.ts +12 -0
- package/types/core/sha256.d.ts +2 -0
- package/types/index.d.ts +30 -0
- package/types/rock-shader/rockMaterial.d.ts +3 -0
- package/types/rock-shader/rockShaderSettings.d.ts +2 -0
- package/types/rockgen/index.d.ts +1 -0
- package/types/rockgen/lod/index.d.ts +1 -0
- package/types/rockgen/lod/rockDenseFieldPolicy.d.ts +181 -0
- package/types/rockgen/rockDocument.d.ts +654 -71
- package/types/rockgen/surface/naturalRockSurface.d.ts +100 -0
- package/types/shaders-tsl/chunks/projected-water-caustics.d.ts +7 -1
- package/types/shaders-tsl/chunks/water-lighting.d.ts +4 -3
- package/types/shaders-tsl/water.d.ts +2 -1
- package/types/version.d.ts +1 -1
- package/types/water/waterDetailSpectrum.d.ts +13 -0
- package/types/water/waterDynamics.d.ts +89 -0
- package/types/water/waterFoamParticles.d.ts +32 -0
- package/types/water/waterFoamTexture.d.ts +1 -0
- package/types/water/waterHydrodynamics.d.ts +49 -0
- package/types/water/waterRenderExtension.d.ts +16 -0
- package/types/water/waterScenePasses.d.ts +1 -1
- package/types/water/waterSettings.d.ts +1 -0
- package/types/water/waterSpectralOcean.d.ts +17 -0
- package/types/water/waterStageSettings.d.ts +1 -1
- package/types/water/waterUnderwaterAtmosphere.d.ts +19 -4
package/src/shaders-tsl/water.js
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
// phase resolves the ordering.
|
|
27
27
|
|
|
28
28
|
import * as THREE from 'three';
|
|
29
|
+
import { getWaterFoamTexture } from '../water/waterFoamTexture.js';
|
|
29
30
|
import {
|
|
30
31
|
abs,
|
|
31
32
|
attribute,
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
cameraProjectionMatrix,
|
|
34
35
|
cameraViewMatrix,
|
|
35
36
|
clamp,
|
|
37
|
+
cos,
|
|
36
38
|
Discard,
|
|
37
39
|
dot,
|
|
38
40
|
exp,
|
|
@@ -75,6 +77,7 @@ import { createWaterFoamChunk } from './chunks/water-foam.js';
|
|
|
75
77
|
import { createWaterLightingChunk } from './chunks/water-lighting.js';
|
|
76
78
|
import { createWaterRippleChunk } from './chunks/water-ripple.js';
|
|
77
79
|
import { createWaterShoreStateChunk } from './chunks/water-shore-state.js';
|
|
80
|
+
import { projectedWaterCaustics } from './chunks/projected-water-caustics.js';
|
|
78
81
|
import { createWaterWavesChunk } from './chunks/water-waves.js';
|
|
79
82
|
|
|
80
83
|
// Node-backend placeholders: same roles as waterMaterial.js's placeholders,
|
|
@@ -132,8 +135,10 @@ export function createWaterNodeMaterial({
|
|
|
132
135
|
detailOctaves = 4,
|
|
133
136
|
foamOctaves = 3,
|
|
134
137
|
shoaling = false,
|
|
138
|
+
dynamics = false,
|
|
135
139
|
} = {}) {
|
|
136
140
|
const flags = {
|
|
141
|
+
dynamics: Boolean(dynamics),
|
|
137
142
|
caustics: qualityLevel >= 1,
|
|
138
143
|
chromaticCaustics: qualityLevel >= 2,
|
|
139
144
|
detailOctaves,
|
|
@@ -168,6 +173,23 @@ export function createWaterNodeMaterial({
|
|
|
168
173
|
uDistanceFogColor: uniform(new THREE.Color(0.66, 0.8, 0.94)),
|
|
169
174
|
uDistanceFogDensity: uniform(0),
|
|
170
175
|
uTime: uniform(0),
|
|
176
|
+
uReferenceView: uniform(0),
|
|
177
|
+
uUseShoreContour: uniform(0),
|
|
178
|
+
uFoamCoordinates: texture(holders.ripple),
|
|
179
|
+
uFoamBubbles: texture(getWaterFoamTexture()),
|
|
180
|
+
uFoamParticleRegion: uniform(new THREE.Vector4(0,0,1,1)),
|
|
181
|
+
uFoamParticleCoverage: uniform(0),
|
|
182
|
+
uFoamFrontState: texture(holders.ripple),
|
|
183
|
+
uUseFoamFrontState: uniform(0),
|
|
184
|
+
uContinueShoreAlongX: uniform(0),
|
|
185
|
+
uUseSpectralNormals: uniform(0),
|
|
186
|
+
uSpectralNormal0: texture(holders.ripple),
|
|
187
|
+
uSpectralNormal1: texture(holders.ripple),
|
|
188
|
+
uSpectralNormal2: texture(holders.ripple),
|
|
189
|
+
uSpectralLengths: uniform(new THREE.Vector3(1,1,1)),
|
|
190
|
+
uDetailNormal0: texture(holders.ripple),
|
|
191
|
+
uDetailNormal1: texture(holders.ripple),
|
|
192
|
+
uDetailLengths: uniform(new THREE.Vector2(1,1)),
|
|
171
193
|
uResolution: uniform(new THREE.Vector2(1, 1)),
|
|
172
194
|
uCameraNear: uniform(0.05),
|
|
173
195
|
uCameraFar: uniform(200),
|
|
@@ -279,7 +301,7 @@ export function createWaterNodeMaterial({
|
|
|
279
301
|
const shoreState = createWaterShoreStateChunk({ u });
|
|
280
302
|
const color = createWaterColorChunk({ u, flags });
|
|
281
303
|
const foam = createWaterFoamChunk({ u, foamOctaves });
|
|
282
|
-
const lighting = createWaterLightingChunk({ u });
|
|
304
|
+
const lighting = createWaterLightingChunk({ u, physicalSurface: flags.dynamics });
|
|
283
305
|
|
|
284
306
|
const material = new NodeMaterial();
|
|
285
307
|
material.name = 'StylizedWater';
|
|
@@ -309,12 +331,34 @@ export function createWaterNodeMaterial({
|
|
|
309
331
|
// is under dry land; the fragment shader discards those fragments so
|
|
310
332
|
// triangles spanning a cliff face can't poke through the terrain.
|
|
311
333
|
const vRestDepth = varying(float(), 'vWaterRestDepth');
|
|
334
|
+
const vDynamicsFoam = varying(float(), 'vDynamicsFoam');
|
|
335
|
+
const vDynamicsFlow = varying(vec2(), 'vDynamicsFlow');
|
|
336
|
+
const vWetDistance = varying(float(), 'vWaterWetDistance');
|
|
312
337
|
|
|
313
338
|
material.vertexNode = Fn(() => {
|
|
314
339
|
const worldPosition = modelWorldMatrix.mul(vec4(positionGeometry, 1.0)).toVar();
|
|
315
340
|
const restXZ = worldPosition.xz.toVar();
|
|
316
341
|
vRestWorldXZ.assign(restXZ);
|
|
317
342
|
|
|
343
|
+
if (flags.dynamics) {
|
|
344
|
+
const state = attribute('aWaterState', 'vec4');
|
|
345
|
+
vWetDistance.assign(attribute('aWaterWetDistance','float'));
|
|
346
|
+
worldPosition.y.assign(state.x);
|
|
347
|
+
vRestDepth.assign(state.y);
|
|
348
|
+
vSwashZone.assign(smoothstep(0.02, 0.25, state.y).oneMinus());
|
|
349
|
+
vSwashFoamData.assign(vec2(state.y.sub(0.002), 0.02));
|
|
350
|
+
vSwashSurge.assign(vec2(0.0));
|
|
351
|
+
vGerstnerNormal.assign(attribute('aWaterNormal', 'vec3'));
|
|
352
|
+
vCrest.assign(float(0.0));
|
|
353
|
+
vShoal.assign(float(1.0));
|
|
354
|
+
vBreaker.assign(float(0.0));
|
|
355
|
+
vDynamicsFlow.assign(state.zw);
|
|
356
|
+
vDynamicsFoam.assign(shoreState.shoreStateSample(restXZ).b);
|
|
357
|
+
vWorldPosition.assign(worldPosition.xyz);
|
|
358
|
+
return cameraProjectionMatrix.mul(cameraViewMatrix).mul(vec4(worldPosition.xyz, 1.0));
|
|
359
|
+
}
|
|
360
|
+
vDynamicsFoam.assign(float(0.0));
|
|
361
|
+
vDynamicsFlow.assign(vec2(0.0));
|
|
318
362
|
// Shallow water filters the spectrum: the short cross chop dies out toward
|
|
319
363
|
// the surf zone, leaving clean parallel lines of the dominant swell.
|
|
320
364
|
const chopWeight = float(1.0).toVar();
|
|
@@ -454,10 +498,13 @@ export function createWaterNodeMaterial({
|
|
|
454
498
|
.mul(u.uSwashEdgeShape.z)
|
|
455
499
|
.mul(macroWave.mul(macroWave))
|
|
456
500
|
.toVar();
|
|
501
|
+
const swashActivity = smoothstep(0.0, 0.05, u.uWaveEnergy)
|
|
502
|
+
.mul(select(max(u.uRunupDistance, u.uShorelineRunup).greaterThan(0.0), 1.0, 0.0)).toVar();
|
|
457
503
|
const edgeHead = edgeDistance.mul(bedSlope)
|
|
458
504
|
.add(
|
|
459
505
|
edgeTilt.add(edgeScallop).mul(edgeEnvelope)
|
|
460
506
|
.add(edgeMacro)
|
|
507
|
+
.mul(swashActivity)
|
|
461
508
|
.mul(bedSlope),
|
|
462
509
|
).toVar();
|
|
463
510
|
const filmHead = restDepth.add(edgeHead).toVar();
|
|
@@ -493,7 +540,7 @@ export function createWaterNodeMaterial({
|
|
|
493
540
|
const surgeDistance = min(runupDistance.mul(0.32), 3.2).toVar();
|
|
494
541
|
const swashDrift = shoreDirection.mul(swashProgress).mul(surgeDistance)
|
|
495
542
|
.add(alongDirection.mul(sin(u.uTime.mul(0.37))).mul(swashProgress).mul(0.55));
|
|
496
|
-
vSwashSurge.assign(swashDrift.mul(beach));
|
|
543
|
+
vSwashSurge.assign(swashDrift.mul(beach).mul(swashActivity));
|
|
497
544
|
waveDisplacement.assign(vec3(
|
|
498
545
|
rawWave.x.mul(shoal),
|
|
499
546
|
mix(brokenY, filmY, beach),
|
|
@@ -539,7 +586,9 @@ export function createWaterNodeMaterial({
|
|
|
539
586
|
// Apply the nonlinear wet threshold after the signed film head has been
|
|
540
587
|
// interpolated. Evaluating smoothstep at sparse vertices first exposed
|
|
541
588
|
// individual triangle rows as the water advanced at a grazing angle.
|
|
542
|
-
if (flags.
|
|
589
|
+
if (flags.dynamics) {
|
|
590
|
+
Discard(mix(vRestDepth.sub(0.002),vWetDistance,u.uUseShoreContour).lessThan(0.0));
|
|
591
|
+
} else if (flags.shoaling) {
|
|
543
592
|
const fragmentWet = mix(
|
|
544
593
|
1.0,
|
|
545
594
|
smoothstep(-0.004, 0.012, vSwashFoamData.x),
|
|
@@ -571,13 +620,16 @@ export function createWaterNodeMaterial({
|
|
|
571
620
|
// A centimetres-thin film is optically smooth at this camera scale. Full
|
|
572
621
|
// open-water normal detail turned every texel into a bright grazing-angle
|
|
573
622
|
// mirror and made the swash look like an opaque sheet of snow.
|
|
574
|
-
const detailStrength = u.uDetailNormalStrength
|
|
623
|
+
const detailStrength = u.uDetailNormalStrength.mul(u.uReferenceView.oneMinus())
|
|
575
624
|
.mul(mix(0.2, 1.0, detailFade))
|
|
576
625
|
.mul(mix(0.1, 1.0, horizonFade))
|
|
577
626
|
.mul(mix(1.0, 0.18, swashOptics)).toVar();
|
|
578
627
|
const flowOffset = u.uFlowDirection.mul(u.uTime).mul(u.uFlowSpeed).toVar();
|
|
579
|
-
|
|
580
|
-
|
|
628
|
+
// Advect in world space BEFORE frequency/rotation: changing detail scale
|
|
629
|
+
// must not reverse or rotate the current (f(x - U*t) travels with U).
|
|
630
|
+
const advectedXZ = vRestWorldXZ.sub(flowOffset).toVar();
|
|
631
|
+
const detailUv1 = advectedXZ.mul(u.uDetailScale);
|
|
632
|
+
const detailUv2 = waterRotate2d(1.9).mul(advectedXZ).mul(u.uDetailScale.mul(1.63));
|
|
581
633
|
const waterDetailGradient = (detailUv) => {
|
|
582
634
|
const uv = vec2(detailUv).toVar();
|
|
583
635
|
const center = waterFbm(uv, flags.detailOctaves).toVar();
|
|
@@ -585,11 +637,31 @@ export function createWaterNodeMaterial({
|
|
|
585
637
|
const dy = waterFbm(uv.add(vec2(0.0, 0.11)), flags.detailOctaves);
|
|
586
638
|
return vec2(dx.sub(center), dy.sub(center)).div(0.11);
|
|
587
639
|
};
|
|
588
|
-
const detailGradient =
|
|
589
|
-
|
|
640
|
+
const detailGradient = vec2(0.0).toVar();
|
|
641
|
+
if (flags.dynamics) {
|
|
642
|
+
// Broadband short waves with seeded phases and dispersion. Two tiles
|
|
643
|
+
// of different extents avoid the eight visible, endlessly repeated
|
|
644
|
+
// ridges from the former plane-wave sum. Mips filter distant glints.
|
|
645
|
+
const activity = clamp(u.uWaveEnergy.div(0.35), 0.0, 1.0);
|
|
646
|
+
const styleGain = mix(u.uDetailNormalStrength.mul(0.45).add(1.0), 1.0, u.uReferenceView);
|
|
647
|
+
const shortSlope = u.uDetailNormal0.sample(advectedXZ.div(u.uDetailLengths.x).add(0.5/64)).rg
|
|
648
|
+
.add(u.uDetailNormal1.sample(advectedXZ.div(u.uDetailLengths.y).add(0.5/64)).rg);
|
|
649
|
+
detailGradient.assign(shortSlope.mul(activity).mul(styleGain).mul(detailFade).mul(smoothstep(0.025,0.22,vRestDepth)));
|
|
650
|
+
} else {
|
|
651
|
+
detailGradient.assign(waterDetailGradient(detailUv1).mul(detailStrength)
|
|
652
|
+
.add(waterDetailGradient(detailUv2).mul(detailStrength).mul(0.55)));
|
|
653
|
+
}
|
|
590
654
|
const rippleGradient = ripple.rippleGradient(vRestWorldXZ);
|
|
591
655
|
const rippleState = ripple.rippleSample(vRestWorldXZ).toVar();
|
|
592
656
|
const waveNormal = waterCombineNormal(gerstnerNormal, rippleGradient).toVar();
|
|
657
|
+
if (flags.dynamics) {
|
|
658
|
+
const spectralSlope = u.uSpectralNormal0.sample(advectedXZ.div(u.uSpectralLengths.x)).level(0).rg
|
|
659
|
+
.add(u.uSpectralNormal1.sample(advectedXZ.div(u.uSpectralLengths.y)).level(0).rg)
|
|
660
|
+
.add(u.uSpectralNormal2.sample(advectedXZ.div(u.uSpectralLengths.z)).level(0).rg);
|
|
661
|
+
const spectralNormal = normalize(vec3(spectralSlope.x.negate(), 1.0, spectralSlope.y.negate()));
|
|
662
|
+
const depthBlend = smoothstep(0.2, 1.5, vRestDepth).mul(u.uUseSpectralNormals);
|
|
663
|
+
waveNormal.assign(normalize(mix(waveNormal, spectralNormal, depthBlend)));
|
|
664
|
+
}
|
|
593
665
|
const surfaceNormal = waterCombineNormal(waveNormal, detailGradient).toVar();
|
|
594
666
|
// Refraction reads with most of the micro detail removed.
|
|
595
667
|
const refractionNormal = waterCombineNormal(waveNormal, detailGradient.mul(0.3)).toVar();
|
|
@@ -609,23 +681,21 @@ export function createWaterNodeMaterial({
|
|
|
609
681
|
float(1.0).sub(float(1.0).div(eta.mul(eta))),
|
|
610
682
|
0.0,
|
|
611
683
|
).sqrt().toVar();
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
cosIncident,
|
|
616
|
-
).toVar();
|
|
684
|
+
// No transmitted ray exists beyond the critical angle. Fresnel itself
|
|
685
|
+
// approaches full reflection continuously from the transmissive side.
|
|
686
|
+
const snellWindow = select(cosIncident.greaterThan(criticalCos), 1.0, 0.0).toVar();
|
|
617
687
|
const refractedDirection = refract(
|
|
618
688
|
incident,
|
|
619
689
|
surfaceNormal.negate(),
|
|
620
690
|
eta,
|
|
621
691
|
).toVar();
|
|
622
|
-
// refract() returns zero under total internal reflection.
|
|
623
|
-
//
|
|
692
|
+
// refract() returns zero under total internal reflection. Use the
|
|
693
|
+
// incident ray only for a finite unused projection in that branch;
|
|
624
694
|
// snellWindow prevents that fallback ray from becoming transmission.
|
|
625
695
|
const transmissionDirection = normalize(mix(
|
|
626
696
|
incident,
|
|
627
697
|
refractedDirection,
|
|
628
|
-
|
|
698
|
+
snellWindow,
|
|
629
699
|
)).toVar();
|
|
630
700
|
const transmissionWorld = vWorldPosition.add(
|
|
631
701
|
transmissionDirection.mul(min(u.uCameraFar.mul(0.75), 400.0)),
|
|
@@ -653,10 +723,10 @@ export function createWaterNodeMaterial({
|
|
|
653
723
|
clamp(insideCapture.mul(u.uUseSceneColor), 0.0, 1.0),
|
|
654
724
|
).toVar();
|
|
655
725
|
|
|
656
|
-
// Compact Beer-Lambert-inspired attenuation
|
|
657
|
-
//
|
|
726
|
+
// Compact Beer-Lambert-inspired attenuation along the camera's water
|
|
727
|
+
// path. This uses ToonLab's authored palette, keeping the result
|
|
658
728
|
// graphic and readable instead of pursuing the reference's realism.
|
|
659
|
-
const cameraDepth =
|
|
729
|
+
const cameraDepth = viewDistance; // optical path to this surface point, not vertical depth
|
|
660
730
|
const volumeAbsorb = exp(
|
|
661
731
|
cameraDepth.div(max(
|
|
662
732
|
u.uDepthFadeDistance.add(u.uDeepFadeDistance.mul(0.5)),
|
|
@@ -668,6 +738,10 @@ export function createWaterNodeMaterial({
|
|
|
668
738
|
u.uMidColor,
|
|
669
739
|
clamp(volumeAbsorb.mul(1.35), 0.0, 1.0),
|
|
670
740
|
).toVar();
|
|
741
|
+
if (flags.dynamics) {
|
|
742
|
+
const transmittance = exp(vec3(0.20, 0.055, 0.035).mul(viewDistance).negate());
|
|
743
|
+
transmitted.assign(transmitted.mul(transmittance).add(u.uMidColor.mul(transmittance.oneMinus()).mul(0.35)));
|
|
744
|
+
} else {
|
|
671
745
|
transmitted.mulAssign(mix(
|
|
672
746
|
vec3(1.0),
|
|
673
747
|
waterTint.mul(1.12),
|
|
@@ -679,10 +753,16 @@ export function createWaterNodeMaterial({
|
|
|
679
753
|
volumeAbsorb.mul(u.uUnderwaterTintStrength),
|
|
680
754
|
));
|
|
681
755
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const cosTransmitted = max(float(1.0).sub(
|
|
759
|
+
eta.mul(eta).mul(float(1.0).sub(cosIncident.mul(cosIncident))),
|
|
760
|
+
), 0.0).sqrt().toVar();
|
|
761
|
+
const parallel = cosIncident.sub(eta.mul(cosTransmitted))
|
|
762
|
+
.div(max(cosIncident.add(eta.mul(cosTransmitted)), 1e-5));
|
|
763
|
+
const perpendicular = eta.mul(cosIncident).sub(cosTransmitted)
|
|
764
|
+
.div(max(eta.mul(cosIncident).add(cosTransmitted), 1e-5));
|
|
765
|
+
const fresnel = parallel.mul(parallel).add(perpendicular.mul(perpendicular)).mul(0.5).toVar();
|
|
686
766
|
const transmissionWeight = fresnel.oneMinus()
|
|
687
767
|
.mul(snellWindow)
|
|
688
768
|
.mul(u.uUnderwaterTransmission)
|
|
@@ -691,7 +771,15 @@ export function createWaterNodeMaterial({
|
|
|
691
771
|
u.uDeepColor.mul(0.48),
|
|
692
772
|
u.uMidColor.mul(0.72),
|
|
693
773
|
cosIncident.mul(0.3),
|
|
694
|
-
);
|
|
774
|
+
).toVar();
|
|
775
|
+
If(u.uUseReflectionMap.greaterThan(0.5), () => {
|
|
776
|
+
const reflectedClip = u.uReflectionMatrix.mul(vec4(vWorldPosition, 1.0));
|
|
777
|
+
const reflectedUv = reflectedClip.xy.div(max(abs(reflectedClip.w), 1e-5))
|
|
778
|
+
.add(surfaceNormal.xz.mul(u.uReflectionDistortion));
|
|
779
|
+
const reflectedScene = u.uReflectionMap.sample(clamp(reflectedUv, vec2(0.0), vec2(1.0))).level(0).rgb;
|
|
780
|
+
const attenuation = exp(viewDistance.mul(0.06).negate());
|
|
781
|
+
tirColor.assign(mix(u.uMidColor, reflectedScene, attenuation));
|
|
782
|
+
});
|
|
695
783
|
const underColor = mix(
|
|
696
784
|
tirColor,
|
|
697
785
|
transmitted,
|
|
@@ -706,10 +794,10 @@ export function createWaterNodeMaterial({
|
|
|
706
794
|
criticalCos.add(0.1),
|
|
707
795
|
cosIncident,
|
|
708
796
|
).oneMinus());
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
lighting.sparkles(vRestWorldXZ, surfaceNormal, viewDir, viewDistance, u.uTime).mul(0.5)
|
|
712
|
-
|
|
797
|
+
if (!flags.dynamics) {
|
|
798
|
+
underColor.addAssign(u.uFresnelColor.mul(criticalRim).mul(0.12).mul(u.uReferenceView.oneMinus()));
|
|
799
|
+
underColor.addAssign(lighting.sparkles(vRestWorldXZ, surfaceNormal, viewDir, viewDistance, u.uTime).mul(0.5));
|
|
800
|
+
}
|
|
713
801
|
// The captured air-side scene is already composited; render the
|
|
714
802
|
// underside opaque to avoid double-blending the main scene behind it.
|
|
715
803
|
output.assign(vec4(underColor, 1.0));
|
|
@@ -728,7 +816,9 @@ export function createWaterNodeMaterial({
|
|
|
728
816
|
max(vWorldPosition.y.sub(groundWorld.y), 0.0),
|
|
729
817
|
u.uDepthFadeDistance.add(u.uDeepFadeDistance),
|
|
730
818
|
).toVar();
|
|
731
|
-
const effectiveDepth =
|
|
819
|
+
const effectiveDepth = flags.dynamics
|
|
820
|
+
? length(groundWorld.sub(vWorldPosition))
|
|
821
|
+
: columnDepth.add(viewDepthDiff.mul(0.18));
|
|
732
822
|
|
|
733
823
|
const { absorb, tint: absorptionTint } = color.absorptionTint(effectiveDepth);
|
|
734
824
|
|
|
@@ -738,6 +828,26 @@ export function createWaterNodeMaterial({
|
|
|
738
828
|
const sceneColor = u.uSceneColor.sample(refractedUv).level(0).rgb;
|
|
739
829
|
const tintedScene = sceneColor.mul(mix(vec3(1.0), u.uShallowColor.mul(1.25), clamp(absorb.mul(0.6), 0.0, 1.0)));
|
|
740
830
|
bodyColor.assign(mix(tintedScene, absorptionTint, absorb));
|
|
831
|
+
if (flags.dynamics) {
|
|
832
|
+
const transmission = exp(vec3(0.20,0.055,0.035).mul(effectiveDepth).negate());
|
|
833
|
+
const physicalBody = sceneColor.mul(transmission).add(u.uDeepColor.mul(transmission.oneMinus()));
|
|
834
|
+
// Preserve optical depth in both treatments. Style colours the
|
|
835
|
+
// scattering, rather than replacing clear swash with opaque tint.
|
|
836
|
+
const shallowDistance = max(u.uDepthFadeDistance, 0.05);
|
|
837
|
+
const deepDistance = max(u.uDeepFadeDistance, 0.05);
|
|
838
|
+
const styledScatter = mix(
|
|
839
|
+
mix(u.uShallowColor, u.uMidColor, smoothstep(0.0, shallowDistance, effectiveDepth)),
|
|
840
|
+
u.uDeepColor,
|
|
841
|
+
smoothstep(shallowDistance, shallowDistance.add(deepDistance), effectiveDepth),
|
|
842
|
+
);
|
|
843
|
+
// Style controls optical density as well as hue. At centimetre
|
|
844
|
+
// depths this still transmits the sand; metre-deep water now
|
|
845
|
+
// visibly consumes the authored shallow, mid and deep palette.
|
|
846
|
+
const styledTransmission = transmission.mul(exp(effectiveDepth.div(shallowDistance).mul(-0.65)));
|
|
847
|
+
const styledBody = sceneColor.mul(styledTransmission)
|
|
848
|
+
.add(styledScatter.mul(styledTransmission.oneMinus()));
|
|
849
|
+
bodyColor.assign(mix(styledBody, physicalBody, u.uReferenceView));
|
|
850
|
+
}
|
|
741
851
|
alpha.assign(1.0);
|
|
742
852
|
}).Else(() => {
|
|
743
853
|
bodyColor.assign(absorptionTint);
|
|
@@ -749,12 +859,14 @@ export function createWaterNodeMaterial({
|
|
|
749
859
|
const clearScene = u.uSceneColor.sample(screenUv).level(0).rgb;
|
|
750
860
|
const wetScene = clearScene.mul(0.85);
|
|
751
861
|
const clearSceneWeight = select(u.uUseSceneColor.greaterThan(0.5), 0.48, 0.0);
|
|
752
|
-
|
|
753
|
-
|
|
862
|
+
if (!flags.dynamics) {
|
|
863
|
+
bodyColor.assign(mix(bodyColor, wetScene, swashOptics.mul(clearSceneWeight)));
|
|
864
|
+
bodyColor.assign(mix(bodyColor, u.uShallowColor, swashOptics.mul(0.18)));
|
|
865
|
+
}
|
|
754
866
|
|
|
755
867
|
// Steep wave faces act like windows, not lenses: drop caustics there,
|
|
756
868
|
// and fade the dappling with view distance (WATER_QUALITY >= 1).
|
|
757
|
-
const caustics = flags.caustics
|
|
869
|
+
const caustics = flags.caustics && !flags.dynamics
|
|
758
870
|
? color.caustics(groundWorld, columnDepth, u.uTime)
|
|
759
871
|
.mul(smoothstep(0.62, 0.85, gerstnerNormalY))
|
|
760
872
|
.mul(smoothstep(14.0, 38.0, viewDistance).oneMinus())
|
|
@@ -767,13 +879,19 @@ export function createWaterNodeMaterial({
|
|
|
767
879
|
// staying glued to rest space while only the envelope moves.
|
|
768
880
|
const foamXZ = vRestWorldXZ.sub(vSwashSurge).toVar();
|
|
769
881
|
const pierce = color.pierceProximity(screenUv, waterViewDistance, vWorldPosition.y).toVar();
|
|
770
|
-
const shoreFoam = foam.shoreFoam(columnDepth, viewDepthDiff, foamXZ, u.uTime, pierce)
|
|
882
|
+
const shoreFoam = (flags.dynamics ? float(0.0) : foam.shoreFoam(columnDepth, viewDepthDiff, foamXZ, u.uTime, pierce))
|
|
771
883
|
// The legacy contact mask assumes a water body deeper than the foam
|
|
772
884
|
// distance. On a centimetre-thin swash film it is almost 1 everywhere,
|
|
773
885
|
// so keep its familiar offshore/rock character but let the dedicated
|
|
774
886
|
// turbulent edge above own the beach foam.
|
|
775
887
|
.mul(mix(1.0, 0.05, clamp(vSwashZone, 0.0, 1.0))).toVar();
|
|
776
|
-
|
|
888
|
+
// Solver foam is an aeration concentration, not already opaque coverage.
|
|
889
|
+
// Map optical thickness to coverage before the toon threshold; passing
|
|
890
|
+
// a dilute swash tracer straight into it erased virtually all the lip.
|
|
891
|
+
const solvedFoamCoverage = exp(vDynamicsFoam.mul(-4.0)).oneMinus()
|
|
892
|
+
.mul(mix(1.0, u.uWhitecapAmount, u.uUseSpectralNormals))
|
|
893
|
+
.mul(mix(1.0, u.uSwashFoamAmount, swashOptics));
|
|
894
|
+
const whitecaps = (flags.dynamics ? solvedFoamCoverage : foam.whitecaps(crest, gerstnerNormalY, foamXZ, u.uTime)).toVar();
|
|
777
895
|
const rippleFoamRaw = clamp(
|
|
778
896
|
rippleState.b.mul(u.uRippleFoamStrength).add(abs(rippleState.r).mul(u.uRippleFoamStrength).mul(4.0)),
|
|
779
897
|
0.0,
|
|
@@ -786,7 +904,7 @@ export function createWaterNodeMaterial({
|
|
|
786
904
|
const persistentShoreCoverage = flags.shoaling
|
|
787
905
|
? shoreState.shoreStateCoverage(vRestWorldXZ)
|
|
788
906
|
: float(0.0);
|
|
789
|
-
if (flags.shoaling) {
|
|
907
|
+
if (flags.shoaling && !flags.dynamics) {
|
|
790
908
|
// The temporal shore field owns transport, decay, breakup, and
|
|
791
909
|
// event-to-event variation. Keep this main material deliberately
|
|
792
910
|
// small: duplicating procedural noise here can exceed WebGPU's hard
|
|
@@ -835,7 +953,56 @@ export function createWaterNodeMaterial({
|
|
|
835
953
|
// together with offshore/contact foam; never max an unshaped milky mask
|
|
836
954
|
// over the final result.
|
|
837
955
|
const foamRaw = max(baseFoamRaw, swashFoamValue).toVar();
|
|
838
|
-
|
|
956
|
+
let dynamicsFoam = foamRaw;
|
|
957
|
+
let bubbleShading = float(1.0);
|
|
958
|
+
if (flags.dynamics) {
|
|
959
|
+
// Transport texture coordinates with the water once per update.
|
|
960
|
+
// Multiplying the local velocity gradient by a long animation phase
|
|
961
|
+
// stretched the former breakup into sharp, shifting white ribbons.
|
|
962
|
+
const fieldUv=foamXZ.sub(u.uShoreStateRegion.xy).div(u.uShoreStateRegion.zw.mul(2.0)).add(0.5);
|
|
963
|
+
const boundedUv=clamp(fieldUv,vec2(0.0),vec2(1.0));
|
|
964
|
+
const outsideOffset=fieldUv.sub(boundedUv).mul(u.uShoreStateRegion.zw).mul(2.0);
|
|
965
|
+
const coordinates=u.uFoamCoordinates.sample(boundedUv).level(0).rg.add(outsideOffset);
|
|
966
|
+
const bubbles=u.uFoamBubbles.sample(coordinates.div(0.72));
|
|
967
|
+
const clusters=u.uFoamBubbles.sample(coordinates.mul(u.uFoamNoiseScale).mul(0.65)).b;
|
|
968
|
+
const coverage=smoothstep(0.32,0.68,clusters);
|
|
969
|
+
const thinFringe=smoothstep(0.012,0.06,vRestDepth).oneMinus();
|
|
970
|
+
const rafts=max(coverage,thinFringe.mul(0.5));
|
|
971
|
+
// Dense rafts expose packed bubble membranes. Dilute fringes break
|
|
972
|
+
// into individual bubbles, never an opaque cut-paper silhouette.
|
|
973
|
+
const packed=smoothstep(0.15,0.65,foamRaw.mul(rafts));
|
|
974
|
+
const pores=mix(bubbles.g,1.0,packed.mul(0.82));
|
|
975
|
+
const particleLocal=abs(foamXZ.sub(u.uFoamParticleRegion.xy)).div(u.uFoamParticleRegion.zw);
|
|
976
|
+
const particleBlend=smoothstep(vec2(.65),vec2(1.0),particleLocal).oneMinus();
|
|
977
|
+
const particleCoverage=particleBlend.x.mul(particleBlend.y).mul(u.uFoamParticleCoverage);
|
|
978
|
+
dynamicsFoam=clamp(foamRaw,0.0,1.0).mul(rafts).mul(pores).mul(mix(1.0,.28,particleCoverage));
|
|
979
|
+
// Coastal particles and the rim own the foam shape. The broad UV
|
|
980
|
+
// mask retained here for open-water whitecaps stretched into large
|
|
981
|
+
// flame-like ribbons on the thin beach sheet, even after advection.
|
|
982
|
+
const front=u.uFoamFrontState.sample(vec2(boundedUv.x,.5)).level(0);
|
|
983
|
+
// Thin cellular strands, not a thresholded broad mask. Cross-shore
|
|
984
|
+
// coordinates translate with local front flow without stretching
|
|
985
|
+
// every cell into a long solid ribbon as the old UV field did.
|
|
986
|
+
const laceUv=vec2(foamXZ.x,foamXZ.y.sub(front.a.mul(u.uUseFoamFrontState))).div(2.4).add(.5/512);
|
|
987
|
+
const lace=u.uFoamBubbles.sample(laceUv).a;
|
|
988
|
+
const trail=exp(max(vWetDistance.sub(.25),0).mul(-.65));
|
|
989
|
+
const laceFoam=clamp(foamRaw,0,1).mul(lace.mul(trail).mul(.65).add(.02)).mul(mix(.65,1,bubbles.g));
|
|
990
|
+
dynamicsFoam=mix(dynamicsFoam,laceFoam,u.uUseShoreContour);
|
|
991
|
+
// Connect the densely packed front rather than rendering individual
|
|
992
|
+
// rafts uniformly over the swash. Actual transported aeration still
|
|
993
|
+
// gates this rim: a calm, foam-free edge remains transparent.
|
|
994
|
+
const retreat=smoothstep(.02,.18,front.g.negate()).mul(u.uUseFoamFrontState);
|
|
995
|
+
const rimWidth=mix(.05,.13,clamp(front.b.mul(2),0,1)).mul(mix(1.0,.55,retreat));
|
|
996
|
+
const rim=smoothstep(.012,rimWidth,vWetDistance).oneMinus().mul(u.uUseShoreContour);
|
|
997
|
+
const rimAeration=max(vDynamicsFoam,front.b.mul(retreat));
|
|
998
|
+
const rimFoam=exp(rimAeration.mul(-12.0)).oneMinus()
|
|
999
|
+
.mul(clamp(u.uSwashFoamAmount,0.0,2.0)).mul(foamAmount)
|
|
1000
|
+
.mul(rim).mul(mix(.75,1.0,bubbles.g)).mul(mix(.32,.14,retreat));
|
|
1001
|
+
dynamicsFoam=max(dynamicsFoam,clamp(rimFoam,0.0,1.0));
|
|
1002
|
+
bubbleShading=bubbles.r;
|
|
1003
|
+
}
|
|
1004
|
+
const foamValue = (flags.dynamics ? dynamicsFoam
|
|
1005
|
+
: mix(foam.foamShape(foamRaw, foamXZ, u.uTime), foamRaw, u.uReferenceView)).toVar();
|
|
839
1006
|
|
|
840
1007
|
// Drifting cloud shadows plus scene shadows (shared sun-shadow pass).
|
|
841
1008
|
const proceduralCloudShadow = stylizedCloudShadow(
|
|
@@ -860,20 +1027,20 @@ export function createWaterNodeMaterial({
|
|
|
860
1027
|
// Thin swash still catches the sky at its lip, but the interior should
|
|
861
1028
|
// mostly reveal wet sand. Attenuate (rather than remove) its Fresnel,
|
|
862
1029
|
// reflection and glints while keeping offshore water unchanged.
|
|
863
|
-
const swashLight = mix(1.0, 0.36, swashOptics).toVar();
|
|
1030
|
+
const swashLight = (flags.dynamics ? float(1.0) : mix(1.0, 0.36, swashOptics.mul(u.uReferenceView.oneMinus()))).toVar();
|
|
864
1031
|
const surfaceFresnel = fresnel.mul(swashLight).toVar();
|
|
865
1032
|
const reflectionMix = clamp(
|
|
866
|
-
surfaceFresnel.mul(u.uReflectionStrength), 0.0, 0
|
|
1033
|
+
surfaceFresnel.mul(flags.dynamics ? 1.0 : mix(u.uReflectionStrength, 1.0, u.uReferenceView)), 0.0, 1.0,
|
|
867
1034
|
).toVar();
|
|
868
1035
|
const litColor = mix(bodyColor, reflection, reflectionMix).toVar();
|
|
869
1036
|
litColor.addAssign(
|
|
870
|
-
u.uFresnelColor.mul(surfaceFresnel).mul(surfaceFresnel).mul(0.35),
|
|
1037
|
+
u.uFresnelColor.mul(surfaceFresnel).mul(surfaceFresnel).mul(flags.dynamics ? 0.0 : 0.35).mul(u.uReferenceView.oneMinus()),
|
|
871
1038
|
);
|
|
872
1039
|
litColor.addAssign(
|
|
873
1040
|
lighting.specular(viewDir, surfaceNormal, foamValue.mul(0.75).oneMinus())
|
|
874
1041
|
.mul(sunVisibility).mul(swashLight),
|
|
875
1042
|
);
|
|
876
|
-
if (flags.sparkles) {
|
|
1043
|
+
if (flags.sparkles && !flags.dynamics) {
|
|
877
1044
|
litColor.addAssign(
|
|
878
1045
|
lighting.sparkles(vRestWorldXZ, surfaceNormal, viewDir, viewDistance, u.uTime)
|
|
879
1046
|
.mul(foamValue.oneMinus()).mul(sunVisibility).mul(swashLight),
|
|
@@ -897,6 +1064,7 @@ export function createWaterNodeMaterial({
|
|
|
897
1064
|
u.uFoamColor.mul(foamLight),
|
|
898
1065
|
smoothstep(0.5, 0.82, foamRaw),
|
|
899
1066
|
).toVar();
|
|
1067
|
+
foamTint.mulAssign(bubbleShading);
|
|
900
1068
|
// Foam is sky-lit and remains readable in shade, but it cannot remain a
|
|
901
1069
|
// pure-white post-lighting overlay. Consume the same shared cast-shadow
|
|
902
1070
|
// visibility as the water body so its white line cools and dims while
|
|
@@ -954,7 +1122,7 @@ export function createWaterNodeMaterial({
|
|
|
954
1122
|
[3, vec4(surfaceNormal.mul(0.5).add(0.5), 1.0)],
|
|
955
1123
|
[4, vec4(rippleState.r.mul(2.0).add(0.5), rippleState.b, float(0.5).sub(rippleState.r.mul(2.0)), 1.0)],
|
|
956
1124
|
[5, vec4(reflection, 1.0)],
|
|
957
|
-
[6, vec4(caustics, 1.0)],
|
|
1125
|
+
[6, vec4(flags.dynamics ? projectedWaterCaustics(groundWorld, vec3(0.0,1.0,0.0)) : caustics, 1.0)],
|
|
958
1126
|
[7, vec4(
|
|
959
1127
|
lighting.specular(viewDir, surfaceNormal, float(1.0))
|
|
960
1128
|
.add(lighting.sparkles(vRestWorldXZ, surfaceNormal, viewDir, viewDistance, u.uTime)),
|
package/src/sky/skySystem.js
CHANGED
|
@@ -343,6 +343,8 @@ function createGodRaysGroup(params = {}) {
|
|
|
343
343
|
* volumes before the first frame can be rendered.
|
|
344
344
|
*/
|
|
345
345
|
export class SkySystem {
|
|
346
|
+
/** Host cost policy. Disable only when no receivers need cloud shadows. */
|
|
347
|
+
cloudShadowsEnabled = true;
|
|
346
348
|
#renderer;
|
|
347
349
|
#camera;
|
|
348
350
|
#scene;
|
|
@@ -711,6 +713,21 @@ export class SkySystem {
|
|
|
711
713
|
return this.#cloudShadow.setLightDirection(direction);
|
|
712
714
|
}
|
|
713
715
|
|
|
716
|
+
/**
|
|
717
|
+
* Opt-in opaque occlusion. Supply an unfiltered, linear color texture whose
|
|
718
|
+
* red channel is positive view-space distance in metres, cleared to >=65000.
|
|
719
|
+
* Render it with this camera BEFORE update(), excluding sky and transparent
|
|
720
|
+
* objects. The host owns the texture. Pass null for legacy backdrop depth.
|
|
721
|
+
*/
|
|
722
|
+
setSceneDepthTexture(map = null) {
|
|
723
|
+
this.#assertLive();
|
|
724
|
+
if (map && this.#cloud.sceneDepthNode.value === map) return;
|
|
725
|
+
this.#cloud.setSceneDepthTexture(map);
|
|
726
|
+
this.#cloudLayer.material.depthTest = map === null;
|
|
727
|
+
this.#cloudLayer.material.needsUpdate = true;
|
|
728
|
+
this.#reprojection.reset();
|
|
729
|
+
}
|
|
730
|
+
|
|
714
731
|
/** The sky dome. */
|
|
715
732
|
get dome() {
|
|
716
733
|
return this.#dome;
|
|
@@ -942,8 +959,8 @@ export class SkySystem {
|
|
|
942
959
|
const enabled = this.#clouds.enabled === true;
|
|
943
960
|
this.#post.cloudsEnabled.value = enabled ? 1 : 0;
|
|
944
961
|
this.#cloudLayer.mesh.visible = enabled;
|
|
945
|
-
this.#cloudShadow.enabled = enabled;
|
|
946
|
-
if (enabled) {
|
|
962
|
+
this.#cloudShadow.enabled = enabled && this.cloudShadowsEnabled;
|
|
963
|
+
if (this.#cloudShadow.enabled) {
|
|
947
964
|
// The shadow bake samples an explicit mip before the primary cloud pass
|
|
948
965
|
// runs, so upload the authored 3D mip pyramid here as well as in the
|
|
949
966
|
// reconstruction path. The helper is idempotent per renderer.
|
|
@@ -1221,6 +1238,7 @@ export class SkySystem {
|
|
|
1221
1238
|
height,
|
|
1222
1239
|
historyDiv: this.#quality.cloudHistoryDiv,
|
|
1223
1240
|
shape: this.#clouds.shape,
|
|
1241
|
+
wind: this.#clouds.wind,
|
|
1224
1242
|
width,
|
|
1225
1243
|
});
|
|
1226
1244
|
}
|
|
@@ -1246,11 +1264,9 @@ export class SkySystem {
|
|
|
1246
1264
|
* `depthWrite: false` keeps the layer out of everyone else's depth test, and it
|
|
1247
1265
|
* draws before host transparency, so glass and particles composite over it.
|
|
1248
1266
|
*
|
|
1249
|
-
*
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
* transmittance-weighted mean cloud distance; writing that as the layer's depth
|
|
1253
|
-
* is the fix, and it needs the reconstruction to carry the depth through.
|
|
1267
|
+
* Without a scene-depth input, occlusion is still based on the carrier.
|
|
1268
|
+
* setSceneDepthTexture opts into clipped integration and disables this
|
|
1269
|
+
* carrier depth test so cloud in front of opaque geometry can composite.
|
|
1254
1270
|
*
|
|
1255
1271
|
* Premultiplied over, not alpha blending: the marcher writes
|
|
1256
1272
|
* `vec4(scatteredRadiance, viewTransmittance)`, and the composite the cloud
|
|
@@ -1430,6 +1446,13 @@ export class SkySystem {
|
|
|
1430
1446
|
max(atmosphere.fogFarFadeEnd, atmosphere.fogFarFadeStart.add(1)),
|
|
1431
1447
|
distance,
|
|
1432
1448
|
).toVar();
|
|
1449
|
+
// The cloud march already applies aerial perspective at cloud depth.
|
|
1450
|
+
// Fog only the opaque contribution behind it, preserving its radiance.
|
|
1451
|
+
If(this.#cloud.uniforms.sceneDepthEnabled.greaterThan(0.5)
|
|
1452
|
+
.and(this.#post.cloudsEnabled.greaterThan(0.5)), () => {
|
|
1453
|
+
const cloud = this.#cloudTextureNode.sample(screenUV);
|
|
1454
|
+
skyTarget.assign(skyTarget.mul(cloud.a).add(cloud.rgb));
|
|
1455
|
+
});
|
|
1433
1456
|
rgb.assign(mix(mix(skyTarget, rgb, transmittance), skyTarget, farFade));
|
|
1434
1457
|
});
|
|
1435
1458
|
|
|
@@ -52,7 +52,7 @@ export function createPerspectiveRemovalSettings(options = null) {
|
|
|
52
52
|
|
|
53
53
|
return {
|
|
54
54
|
amount: enabled ? requestedAmount : 0,
|
|
55
|
-
enabled
|
|
55
|
+
enabled,
|
|
56
56
|
endHeight: numberOption(firstDefined(source, ['endHeight']), DEFAULT_PERSPECTIVE_REMOVAL_SETTINGS.endHeight, { min: -100, max: 1000 }),
|
|
57
57
|
radius: numberOption(firstDefined(source, ['radius']), DEFAULT_PERSPECTIVE_REMOVAL_SETTINGS.radius, { min: 0.05, max: 50 }),
|
|
58
58
|
startHeight: numberOption(firstDefined(source, ['startHeight']), DEFAULT_PERSPECTIVE_REMOVAL_SETTINGS.startHeight, { min: -100, max: 1000 }),
|