@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,318 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
import { createEnvironmentAoOverlayNodeMaterial } from '../shaders-tsl/environment-ao-overlay.js';
|
|
4
|
+
import {
|
|
5
|
+
createEnvironmentNodeMaterial,
|
|
6
|
+
environmentSharedUniformNodes,
|
|
7
|
+
} from '../shaders-tsl/environment.js';
|
|
8
|
+
import {
|
|
9
|
+
isEmissiveEnvironmentMaterial,
|
|
10
|
+
isFoliageMaterial,
|
|
11
|
+
isWindowCutoutMaterial,
|
|
12
|
+
materialBaseColor,
|
|
13
|
+
sourceOpacity,
|
|
14
|
+
usesAlphaCutout,
|
|
15
|
+
} from './environmentMaterialClassifier.js';
|
|
16
|
+
import {
|
|
17
|
+
ENVIRONMENT_DEBUG_MODES,
|
|
18
|
+
applyEnvironmentSettingsToMaterial,
|
|
19
|
+
createEnvironmentSettings,
|
|
20
|
+
normalizeEnvironmentDebugMode,
|
|
21
|
+
} from './environmentSettings.js';
|
|
22
|
+
import {
|
|
23
|
+
fallbackEnvironmentBlackTexture,
|
|
24
|
+
fallbackEnvironmentWhiteTexture,
|
|
25
|
+
} from './environmentTextureResolver.js';
|
|
26
|
+
|
|
27
|
+
// Uniform objects shared by reference across every environment material, so a
|
|
28
|
+
// single write drives the whole converted scene: the animation clock, the
|
|
29
|
+
// drifting cloud-shadow field, the debug selector, the interior openings, the
|
|
30
|
+
// ambient probe, and the floor reflection pass output. These names are
|
|
31
|
+
// excluded from the per-material settings snapshot (see
|
|
32
|
+
// ENVIRONMENT_SHARED_UNIFORM_NAMES).
|
|
33
|
+
const environmentSharedUniforms = {
|
|
34
|
+
time: { value: 0 },
|
|
35
|
+
cloudShadowStrength: { value: 0 },
|
|
36
|
+
cloudShadowCoverage: { value: 0.45 },
|
|
37
|
+
cloudShadowScale: { value: 0.012 },
|
|
38
|
+
cloudShadowVelocity: { value: new THREE.Vector2(0.02, 0.006) },
|
|
39
|
+
envDebugMode: { value: 0 },
|
|
40
|
+
environmentOpenings: {
|
|
41
|
+
value: [new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4()],
|
|
42
|
+
},
|
|
43
|
+
environmentOpeningCount: { value: 0 },
|
|
44
|
+
ambientProbe: {
|
|
45
|
+
value: [
|
|
46
|
+
new THREE.Color(1, 1, 1), new THREE.Color(1, 1, 1),
|
|
47
|
+
new THREE.Color(1, 1, 1), new THREE.Color(1, 1, 1),
|
|
48
|
+
new THREE.Color(1, 1, 1), new THREE.Color(1, 1, 1),
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
planarReflectionMap: { value: fallbackEnvironmentBlackTexture },
|
|
52
|
+
planarReflectionMatrix: { value: new THREE.Matrix4() },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// The module-level setters below write the classic shared uniforms and
|
|
56
|
+
// mirror each write into the TSL shared uniform nodes
|
|
57
|
+
// (src/shaders-tsl/environment.js), so scene-wide updates reach both
|
|
58
|
+
// backends. Per-material writes (applyEnvironmentSettingsToMaterial, HUD)
|
|
59
|
+
// need no mirroring: on TSL the material `.uniforms` slots ARE the shared
|
|
60
|
+
// nodes, exactly like the classic shared-by-reference uniform objects.
|
|
61
|
+
|
|
62
|
+
// Advance the shared environment clock once per frame (drives cloud drift).
|
|
63
|
+
export function advanceEnvironmentShaderTime(delta) {
|
|
64
|
+
environmentSharedUniforms.time.value += Math.min(Math.max(delta ?? 0, 0), 0.1);
|
|
65
|
+
environmentSharedUniformNodes.time.value = environmentSharedUniforms.time.value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Reset the shared clock (deterministic captures need a repeatable time).
|
|
69
|
+
export function resetEnvironmentShaderTime(value = 0) {
|
|
70
|
+
environmentSharedUniforms.time.value = Number.isFinite(value) ? value : 0;
|
|
71
|
+
environmentSharedUniformNodes.time.value = environmentSharedUniforms.time.value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Retune the scene-wide cloud shadows. strength 0 (the default) disables the
|
|
75
|
+
// effect, so indoor scenes never see it. velocity is uv-space drift per
|
|
76
|
+
// second (worldDrift = velocity / scale).
|
|
77
|
+
export function setEnvironmentCloudShadow({ strength, coverage, scale, velocity } = {}) {
|
|
78
|
+
if (Number.isFinite(strength)) environmentSharedUniforms.cloudShadowStrength.value = strength;
|
|
79
|
+
if (Number.isFinite(coverage)) environmentSharedUniforms.cloudShadowCoverage.value = coverage;
|
|
80
|
+
if (Number.isFinite(scale)) environmentSharedUniforms.cloudShadowScale.value = scale;
|
|
81
|
+
if (velocity) {
|
|
82
|
+
environmentSharedUniforms.cloudShadowVelocity.value.set(
|
|
83
|
+
velocity[0] ?? velocity.x ?? 0, velocity[1] ?? velocity.y ?? 0);
|
|
84
|
+
}
|
|
85
|
+
environmentSharedUniformNodes.cloudShadowStrength.value = environmentSharedUniforms.cloudShadowStrength.value;
|
|
86
|
+
environmentSharedUniformNodes.cloudShadowCoverage.value = environmentSharedUniforms.cloudShadowCoverage.value;
|
|
87
|
+
environmentSharedUniformNodes.cloudShadowScale.value = environmentSharedUniforms.cloudShadowScale.value;
|
|
88
|
+
environmentSharedUniformNodes.cloudShadowVelocity.value.copy(environmentSharedUniforms.cloudShadowVelocity.value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function forEachEnvironmentShaderMaterial(root, callback) {
|
|
92
|
+
root?.traverse?.((obj) => {
|
|
93
|
+
if (!obj.isMesh || !obj.material) return;
|
|
94
|
+
const materials = Array.isArray(obj.material) ? obj.material : [obj.material];
|
|
95
|
+
for (const mat of materials) {
|
|
96
|
+
if (mat?.userData?.environmentMaterial) callback(mat, obj);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Switch every converted environment material under root to a debug view
|
|
102
|
+
// ('off' restores normal rendering). Materials compile the debug branch out
|
|
103
|
+
// unless a debug view has been requested at least once.
|
|
104
|
+
export function setEnvironmentDebugOutput(root, mode = 'off') {
|
|
105
|
+
const resolved = normalizeEnvironmentDebugMode(mode);
|
|
106
|
+
environmentSharedUniforms.envDebugMode.value = resolved;
|
|
107
|
+
// Node materials compile the debug table in from the start (and pre-set the
|
|
108
|
+
// define marker), so on the TSL backend this stays a pure uniform write.
|
|
109
|
+
environmentSharedUniformNodes.envDebugMode.value = resolved;
|
|
110
|
+
forEachEnvironmentShaderMaterial(root, (mat) => {
|
|
111
|
+
if (resolved > 0 && !mat.defines?.ENV_DEBUG_VIEWS) {
|
|
112
|
+
mat.defines = { ...mat.defines, ENV_DEBUG_VIEWS: 1 };
|
|
113
|
+
mat.needsUpdate = true;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return resolved;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Register up to four interior "openings" (windows, doors, strong lamps):
|
|
120
|
+
// world position + light reach radius. Interior occlusion darkens surfaces
|
|
121
|
+
// away from every opening; passing an empty list disables the term.
|
|
122
|
+
export function setEnvironmentOpenings(openings = []) {
|
|
123
|
+
const slots = environmentSharedUniforms.environmentOpenings.value;
|
|
124
|
+
const count = Math.min(Array.isArray(openings) ? openings.length : 0, slots.length);
|
|
125
|
+
for (let i = 0; i < slots.length; i += 1) {
|
|
126
|
+
const opening = openings[i];
|
|
127
|
+
if (i < count && opening) {
|
|
128
|
+
const position = opening.position ?? opening;
|
|
129
|
+
slots[i].set(
|
|
130
|
+
position.x ?? position[0] ?? 0,
|
|
131
|
+
position.y ?? position[1] ?? 0,
|
|
132
|
+
position.z ?? position[2] ?? 0,
|
|
133
|
+
opening.radius ?? opening.w ?? 1,
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
slots[i].set(0, 0, 0, 0);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
environmentSharedUniforms.environmentOpeningCount.value = count;
|
|
140
|
+
for (let i = 0; i < slots.length; i += 1) {
|
|
141
|
+
environmentSharedUniformNodes.environmentOpenings.array[i].copy(slots[i]);
|
|
142
|
+
}
|
|
143
|
+
environmentSharedUniformNodes.environmentOpeningCount.value = count;
|
|
144
|
+
return count;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Write the six-direction ambient probe (+x, -x, +y, -y, +z, -z). Colors
|
|
148
|
+
// come from captureEnvironmentAmbientProbe or can be authored directly.
|
|
149
|
+
export function setEnvironmentAmbientProbeColors(colors = []) {
|
|
150
|
+
const slots = environmentSharedUniforms.ambientProbe.value;
|
|
151
|
+
for (let i = 0; i < slots.length; i += 1) {
|
|
152
|
+
const color = colors[i];
|
|
153
|
+
if (color?.isColor) slots[i].copy(color);
|
|
154
|
+
else if (Array.isArray(color)) slots[i].setRGB(color[0] ?? 1, color[1] ?? 1, color[2] ?? 1);
|
|
155
|
+
environmentSharedUniformNodes.ambientProbe.array[i].copy(slots[i]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Bind (or clear) the shared planar-reflection pass output consumed by
|
|
160
|
+
// glossFloor-role materials.
|
|
161
|
+
export function setEnvironmentPlanarReflection({ texture, matrix } = {}) {
|
|
162
|
+
environmentSharedUniforms.planarReflectionMap.value = texture ?? fallbackEnvironmentBlackTexture;
|
|
163
|
+
if (matrix) environmentSharedUniforms.planarReflectionMatrix.value.copy(matrix);
|
|
164
|
+
environmentSharedUniformNodes.planarReflectionMap.value = environmentSharedUniforms.planarReflectionMap.value;
|
|
165
|
+
environmentSharedUniformNodes.planarReflectionMatrix.value.copy(environmentSharedUniforms.planarReflectionMatrix.value);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function createEnvironmentAoMaterial(mat, textureSet) {
|
|
169
|
+
const material = createEnvironmentAoOverlayNodeMaterial({
|
|
170
|
+
aoMap: textureSet.alphaMap ?? textureSet.baseMap ?? fallbackEnvironmentWhiteTexture,
|
|
171
|
+
side: mat?.side ?? THREE.DoubleSide,
|
|
172
|
+
});
|
|
173
|
+
material.name = mat?.name ?? '';
|
|
174
|
+
material.visible = mat?.visible ?? true;
|
|
175
|
+
material.userData.environmentAoOverlay = true;
|
|
176
|
+
return material;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createEnvironmentShadowMaterial(mat, { hasVertexColors = false } = {}) {
|
|
180
|
+
const material = new THREE.MeshBasicMaterial({
|
|
181
|
+
color: 0x3d2d20,
|
|
182
|
+
opacity: 0.18,
|
|
183
|
+
side: mat?.side ?? THREE.DoubleSide,
|
|
184
|
+
transparent: true,
|
|
185
|
+
vertexColors: hasVertexColors,
|
|
186
|
+
depthWrite: false,
|
|
187
|
+
});
|
|
188
|
+
material.name = mat?.name ?? '';
|
|
189
|
+
material.visible = mat?.visible ?? true;
|
|
190
|
+
material.userData.environmentShadow = true;
|
|
191
|
+
return material;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function createEnvironmentWindowOpeningMaterial(mat) {
|
|
195
|
+
const material = new THREE.MeshBasicMaterial({
|
|
196
|
+
color: 0x000000,
|
|
197
|
+
opacity: 0,
|
|
198
|
+
side: mat?.side ?? THREE.DoubleSide,
|
|
199
|
+
transparent: true,
|
|
200
|
+
depthWrite: false,
|
|
201
|
+
});
|
|
202
|
+
material.name = mat?.name ?? '';
|
|
203
|
+
material.visible = false;
|
|
204
|
+
material.userData.windowCutout = true;
|
|
205
|
+
return material;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function alphaCutoffFor(mat, isFoliage) {
|
|
209
|
+
if (isFoliage) return Math.max(mat?.alphaTest ?? 0.12, 0.08);
|
|
210
|
+
return usesAlphaCutout(mat) ? Math.max(mat?.alphaTest ?? 0.35, 0.35) : -1.0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function createEnvironmentMaterial(mat, textureSet, {
|
|
214
|
+
environmentBox = null,
|
|
215
|
+
environmentSettings = createEnvironmentSettings(),
|
|
216
|
+
hasSun = false,
|
|
217
|
+
hasUv2 = false,
|
|
218
|
+
hasVertexAo = false,
|
|
219
|
+
hasVertexColors = false,
|
|
220
|
+
openWindows = false,
|
|
221
|
+
role = null,
|
|
222
|
+
} = {}) {
|
|
223
|
+
const features = environmentSettings.features;
|
|
224
|
+
const isFoliage = role ? role === 'foliage' : isFoliageMaterial(mat);
|
|
225
|
+
const isEmissive = role ? role === 'emissive' : isEmissiveEnvironmentMaterial(mat);
|
|
226
|
+
const isWindow = role ? role === 'window' : isWindowCutoutMaterial(mat);
|
|
227
|
+
const isGlossFloor = role === 'glossFloor';
|
|
228
|
+
const alphaCutout = usesAlphaCutout(mat);
|
|
229
|
+
const alphaBlend = mat?.transparent === true && sourceOpacity(mat) < 0.999;
|
|
230
|
+
const windowCutout = features.windowCutout && openWindows && isWindow;
|
|
231
|
+
if (windowCutout) return createEnvironmentWindowOpeningMaterial(mat);
|
|
232
|
+
|
|
233
|
+
const useNormalMap = Boolean(textureSet.normalMap) && features.normalMap;
|
|
234
|
+
const useAoMap = Boolean(textureSet.aoMap) && features.aoMap;
|
|
235
|
+
const useLightMap = Boolean(textureSet.lightMap) && features.lightMap;
|
|
236
|
+
const useEmissiveMap = Boolean(textureSet.emissiveMap) && features.emissiveMap;
|
|
237
|
+
const useUntextured = Boolean(textureSet.untextured) && features.untexturedGradient;
|
|
238
|
+
const usePlanarReflection = isGlossFloor && features.planarReflection;
|
|
239
|
+
const aoUsesUv2 = hasUv2 && (textureSet.aoMap?.channel ?? 1) >= 1;
|
|
240
|
+
const lightUsesUv2 = hasUv2 && (textureSet.lightMap?.channel ?? 1) >= 1;
|
|
241
|
+
const useUv2 = (useAoMap && aoUsesUv2) || (useLightMap && lightUsesUv2);
|
|
242
|
+
|
|
243
|
+
const material = createEnvironmentNodeMaterial({
|
|
244
|
+
alphaBlend,
|
|
245
|
+
alphaCutoff: alphaCutoffFor(mat, isFoliage),
|
|
246
|
+
baseColor: materialBaseColor(mat, textureSet),
|
|
247
|
+
environmentBox,
|
|
248
|
+
flags: {
|
|
249
|
+
aoUsesUv2: useUv2 && aoUsesUv2,
|
|
250
|
+
hasAlphaMap: Boolean(textureSet.alphaMap),
|
|
251
|
+
hasAoMap: useAoMap,
|
|
252
|
+
hasEmissiveMap: useEmissiveMap,
|
|
253
|
+
hasLightMap: useLightMap,
|
|
254
|
+
hasNormalMap: useNormalMap,
|
|
255
|
+
hasPackedMap: Boolean(textureSet.packedMap),
|
|
256
|
+
hasPlanarReflection: usePlanarReflection,
|
|
257
|
+
hasUntextured: useUntextured,
|
|
258
|
+
hasVertexAo,
|
|
259
|
+
lightUsesUv2: useUv2 && lightUsesUv2,
|
|
260
|
+
useUv2,
|
|
261
|
+
useVertexColors: hasVertexColors,
|
|
262
|
+
},
|
|
263
|
+
hasSun,
|
|
264
|
+
isEmissive,
|
|
265
|
+
isFoliage,
|
|
266
|
+
isGlossFloor,
|
|
267
|
+
opacity: sourceOpacity(mat),
|
|
268
|
+
side: mat?.side ?? THREE.DoubleSide,
|
|
269
|
+
textureSet,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const baseMapTexture = textureSet.baseMap;
|
|
273
|
+
if (baseMapTexture?.isTexture) {
|
|
274
|
+
if (baseMapTexture.matrixAutoUpdate) baseMapTexture.updateMatrix();
|
|
275
|
+
material.uniforms.baseMapTransform.value.copy(baseMapTexture.matrix);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
material.name = mat?.name ?? '';
|
|
279
|
+
material.visible = mat?.visible ?? true;
|
|
280
|
+
material.userData.windowCutout = windowCutout;
|
|
281
|
+
material.userData.environmentMaterial = true;
|
|
282
|
+
material.userData.environmentRole = role ?? (isFoliage ? 'foliage' : isEmissive ? 'emissive' : 'standard');
|
|
283
|
+
material.userData.environmentFeatures = { ...features };
|
|
284
|
+
return applyEnvironmentSettingsToMaterial(material, environmentSettings);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function createDebugEnvironmentMaterial(mat, shaderMode, textureSet, { hasVertexColors = false } = {}) {
|
|
288
|
+
if (shaderMode === 'basic') {
|
|
289
|
+
const material = new THREE.MeshBasicMaterial({
|
|
290
|
+
alphaTest: usesAlphaCutout(mat) ? Math.max(mat?.alphaTest ?? 0.35, 0.35) : mat?.alphaTest ?? 0,
|
|
291
|
+
color: materialBaseColor(mat, textureSet),
|
|
292
|
+
map: textureSet.baseMap === fallbackEnvironmentWhiteTexture ? null : textureSet.baseMap,
|
|
293
|
+
opacity: sourceOpacity(mat),
|
|
294
|
+
side: mat?.side ?? THREE.DoubleSide,
|
|
295
|
+
transparent: mat?.transparent === true && sourceOpacity(mat) < 0.999,
|
|
296
|
+
vertexColors: hasVertexColors,
|
|
297
|
+
});
|
|
298
|
+
material.visible = mat?.visible ?? true;
|
|
299
|
+
return material;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const material = new THREE.MeshStandardMaterial({
|
|
303
|
+
alphaTest: usesAlphaCutout(mat) ? Math.max(mat?.alphaTest ?? 0.35, 0.35) : mat?.alphaTest ?? 0,
|
|
304
|
+
color: materialBaseColor(mat, textureSet),
|
|
305
|
+
map: textureSet.baseMap === fallbackEnvironmentWhiteTexture ? null : textureSet.baseMap,
|
|
306
|
+
normalMap: mat?.normalMap ?? null,
|
|
307
|
+
roughness: 0.82,
|
|
308
|
+
metalness: 0.02,
|
|
309
|
+
opacity: sourceOpacity(mat),
|
|
310
|
+
side: mat?.side ?? THREE.DoubleSide,
|
|
311
|
+
transparent: mat?.transparent === true && sourceOpacity(mat) < 0.999,
|
|
312
|
+
vertexColors: hasVertexColors,
|
|
313
|
+
});
|
|
314
|
+
material.visible = mat?.visible ?? true;
|
|
315
|
+
return material;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export { ENVIRONMENT_DEBUG_MODES };
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// Sun shadow pass for the node backends (WebGPU / forced WebGL2).
|
|
2
|
+
//
|
|
3
|
+
// The classic pipeline gets scene shadows from three's shadow-map system via
|
|
4
|
+
// materials with lights:true; the TSL toon/environment materials never use
|
|
5
|
+
// the node lighting system, so nothing would render the sun's shadow map.
|
|
6
|
+
// This pass renders the scene from the shadow-casting directional light's
|
|
7
|
+
// own shadow camera (the sun rig configures its bounds) into a float color
|
|
8
|
+
// target holding linear window depth, then publishes map/matrix through the
|
|
9
|
+
// shared uniforms in src/shaders-tsl/chunks/environment-sun-shadow.js, which
|
|
10
|
+
// both the environment material (getShadowMask replacement) and the toon
|
|
11
|
+
// character (sceneShadowVisibility) sample.
|
|
12
|
+
//
|
|
13
|
+
// Classic WebGL path: unused — three's own shadow system keeps doing the job.
|
|
14
|
+
|
|
15
|
+
import * as THREE from 'three';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
applyShadowClipAdjust,
|
|
19
|
+
createPassDepthColorMaterial,
|
|
20
|
+
} from '../shaders-tsl/chunks/pass-depth-color.js';
|
|
21
|
+
import { environmentSunShadow, sunShadowMapNode } from '../shaders-tsl/chunks/environment-sun-shadow.js';
|
|
22
|
+
|
|
23
|
+
export function createEnvironmentSunShadowPass({ renderer, scene } = {}) {
|
|
24
|
+
if (!renderer || !scene) {
|
|
25
|
+
throw new Error('createEnvironmentSunShadowPass requires { renderer, scene }.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const isNodeBackend = Boolean(renderer.isWebGPURenderer);
|
|
29
|
+
let shadowTarget = null;
|
|
30
|
+
const shadowMatrix = new THREE.Matrix4();
|
|
31
|
+
const depthMaterialCache = new WeakMap();
|
|
32
|
+
const skinnedDepthMaterialCache = new WeakMap();
|
|
33
|
+
// Swapped material (or material ARRAY) per mesh, keyed by the source
|
|
34
|
+
// material identity: handing the renderer a fresh array every frame churns
|
|
35
|
+
// its render-object cache, destroying object uniform buffers that queued
|
|
36
|
+
// submits still reference (WebGPU validation error).
|
|
37
|
+
const meshSwapCache = new WeakMap();
|
|
38
|
+
const materialRestores = [];
|
|
39
|
+
const visibilityRestores = [];
|
|
40
|
+
let cachedSunLight = null;
|
|
41
|
+
|
|
42
|
+
function findShadowSun() {
|
|
43
|
+
if (cachedSunLight?.parent && cachedSunLight.visible && cachedSunLight.castShadow) {
|
|
44
|
+
return cachedSunLight;
|
|
45
|
+
}
|
|
46
|
+
cachedSunLight = null;
|
|
47
|
+
scene.traverse((obj) => {
|
|
48
|
+
if (cachedSunLight) return;
|
|
49
|
+
if (obj.isDirectionalLight && obj.visible && obj.castShadow) cachedSunLight = obj;
|
|
50
|
+
});
|
|
51
|
+
return cachedSunLight;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// three's WebGLShadowMap renders shadow depth with flipped culling unless
|
|
55
|
+
// material.shadowSide overrides it (FrontSide casters render their back
|
|
56
|
+
// faces). One-sided planes facing the light — the environments' baked
|
|
57
|
+
// shadow-decal meshes — therefore never occlude in the classic map; the
|
|
58
|
+
// pass must match or those decals black out the sunlit interior.
|
|
59
|
+
const shadowSideFor = {
|
|
60
|
+
[THREE.FrontSide]: THREE.BackSide,
|
|
61
|
+
[THREE.BackSide]: THREE.FrontSide,
|
|
62
|
+
[THREE.DoubleSide]: THREE.DoubleSide,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function cacheForDepthMaterial(object) {
|
|
66
|
+
if (!object?.isSkinnedMesh) return depthMaterialCache;
|
|
67
|
+
let cache = skinnedDepthMaterialCache.get(object);
|
|
68
|
+
if (!cache) {
|
|
69
|
+
cache = new WeakMap();
|
|
70
|
+
skinnedDepthMaterialCache.set(object, cache);
|
|
71
|
+
}
|
|
72
|
+
return cache;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function depthMaterialFor(material, object = null) {
|
|
76
|
+
const cache = cacheForDepthMaterial(object);
|
|
77
|
+
let depthMaterial = cache.get(material);
|
|
78
|
+
if (!depthMaterial) {
|
|
79
|
+
if (typeof material?.userData?.createDepthColorVariant === 'function') {
|
|
80
|
+
// Materials with custom vertex displacement (tree foliage cards)
|
|
81
|
+
// provide their own cutout depth variant.
|
|
82
|
+
depthMaterial = material.userData.createDepthColorVariant();
|
|
83
|
+
} else {
|
|
84
|
+
// Toon/environment TSL materials expose their cutout through the
|
|
85
|
+
// same-name uniform surface; classic materials through alphaTest/map.
|
|
86
|
+
const cutoff = material?.uniforms?.aCutoff?.value ??
|
|
87
|
+
material?.uniforms?.alphaCutoff?.value ??
|
|
88
|
+
(material?.alphaTest > 0 ? material.alphaTest : -1);
|
|
89
|
+
const map = material?.uniforms?.base?.value ??
|
|
90
|
+
material?.uniforms?.baseMap?.value ??
|
|
91
|
+
material?.map ?? null;
|
|
92
|
+
depthMaterial = createPassDepthColorMaterial({
|
|
93
|
+
alphaTest: cutoff > 0 ? cutoff : 0,
|
|
94
|
+
map: cutoff > 0 ? map : null,
|
|
95
|
+
side: THREE.DoubleSide,
|
|
96
|
+
});
|
|
97
|
+
depthMaterial.side = material?.shadowSide ?? shadowSideFor[material?.side] ?? THREE.DoubleSide;
|
|
98
|
+
}
|
|
99
|
+
cache.set(material, depthMaterial);
|
|
100
|
+
}
|
|
101
|
+
// three's WebGLShadowMap skips invisible source materials per group
|
|
102
|
+
// (multi-material building meshes carry their opened window panes as a
|
|
103
|
+
// hidden group); keep the depth replacement in step every update.
|
|
104
|
+
depthMaterial.visible = material?.visible !== false;
|
|
105
|
+
return depthMaterial;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Static scenes render the shadow map once: r185's render-object
|
|
109
|
+
// revalidation churn (dispose + recreate) intermittently destroys buffers
|
|
110
|
+
// that queued submits still reference, and a pass that stops submitting
|
|
111
|
+
// when nothing changed sidesteps the race entirely. Callers pass
|
|
112
|
+
// `dynamic: true` while animation moves casters.
|
|
113
|
+
let lastRenderSignature = '';
|
|
114
|
+
|
|
115
|
+
function renderSignature(sun) {
|
|
116
|
+
return `${sun.uuid}:${sun.matrixWorld.elements.join(',')}:${sun.target?.matrixWorld.elements.join(',')}:${sun.shadow.camera.far}:${scene.children.length}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function update({ dynamic = false } = {}) {
|
|
120
|
+
if (!isNodeBackend) return;
|
|
121
|
+
|
|
122
|
+
// Note: renderer.shadowMap stays DISABLED on the node backends (the labs
|
|
123
|
+
// gate it) — three's own shadow-map pass would re-render every skinned
|
|
124
|
+
// caster with built-in buffer skinning (the >256-bone UBO overflow) and
|
|
125
|
+
// nothing consumes its maps here. The light's castShadow flag alone
|
|
126
|
+
// drives this pass.
|
|
127
|
+
const sun = findShadowSun();
|
|
128
|
+
if (!sun) {
|
|
129
|
+
environmentSunShadow.ready.value = false;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!dynamic) {
|
|
134
|
+
const signature = renderSignature(sun);
|
|
135
|
+
if (signature === lastRenderSignature && environmentSunShadow.ready.value) return;
|
|
136
|
+
lastRenderSignature = signature;
|
|
137
|
+
} else {
|
|
138
|
+
lastRenderSignature = '';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const shadow = sun.shadow;
|
|
142
|
+
const mapSize = Math.max(shadow.mapSize?.x ?? 2048, 1);
|
|
143
|
+
if (!shadowTarget || shadowTarget.width !== mapSize) {
|
|
144
|
+
shadowTarget?.dispose();
|
|
145
|
+
shadowTarget = new THREE.WebGLRenderTarget(mapSize, mapSize, {
|
|
146
|
+
depthBuffer: true,
|
|
147
|
+
stencilBuffer: false,
|
|
148
|
+
});
|
|
149
|
+
shadowTarget.texture.name = 'EnvironmentSunShadow.Depth';
|
|
150
|
+
shadowTarget.texture.type = THREE.FloatType;
|
|
151
|
+
shadowTarget.texture.minFilter = THREE.NearestFilter;
|
|
152
|
+
shadowTarget.texture.magFilter = THREE.NearestFilter;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// The sun rig frames the light's orthographic shadow camera bounds, but
|
|
156
|
+
// POSING the camera (position/orientation from light + target) is a
|
|
157
|
+
// side effect of three's own shadow pass — which is disabled on the node
|
|
158
|
+
// backends. updateMatrices() is that pose step as a public API.
|
|
159
|
+
sun.updateMatrixWorld(true);
|
|
160
|
+
sun.target?.updateMatrixWorld(true);
|
|
161
|
+
shadow.updateMatrices(sun);
|
|
162
|
+
const shadowCamera = shadow.camera;
|
|
163
|
+
shadowCamera.coordinateSystem = renderer.coordinateSystem;
|
|
164
|
+
shadowCamera.updateProjectionMatrix();
|
|
165
|
+
shadowCamera.updateMatrixWorld(true);
|
|
166
|
+
shadowMatrix.multiplyMatrices(shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse);
|
|
167
|
+
applyShadowClipAdjust(shadowMatrix, renderer);
|
|
168
|
+
|
|
169
|
+
scene.updateMatrixWorld(true);
|
|
170
|
+
|
|
171
|
+
// Swap every visible caster to a depth-color material (cutout-aware);
|
|
172
|
+
// hide non-casters and derived hulls/shells for the duration.
|
|
173
|
+
materialRestores.length = 0;
|
|
174
|
+
visibilityRestores.length = 0;
|
|
175
|
+
scene.traverse((obj) => {
|
|
176
|
+
if (!obj.isMesh || !obj.visible) return;
|
|
177
|
+
if (!obj.castShadow || obj.userData?.isToonOutline || obj.userData?.isToonFurShell) {
|
|
178
|
+
obj.visible = false;
|
|
179
|
+
visibilityRestores.push(obj);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// three's WebGLShadowMap skips materials with visible === false; the
|
|
183
|
+
// environment's window-opening materials rely on that so sunlight can
|
|
184
|
+
// stream through opened windows. Match it before the material swap.
|
|
185
|
+
const sourceMaterials = Array.isArray(obj.material) ? obj.material : [obj.material];
|
|
186
|
+
if (!sourceMaterials.some((mat) => mat && mat.visible !== false)) {
|
|
187
|
+
obj.visible = false;
|
|
188
|
+
visibilityRestores.push(obj);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (obj.isSkinnedMesh) obj.skeleton?.update();
|
|
192
|
+
materialRestores.push({ material: obj.material, mesh: obj });
|
|
193
|
+
const customDepthMaterial = obj.customDepthMaterial ?? null;
|
|
194
|
+
let swap = meshSwapCache.get(obj);
|
|
195
|
+
if (!swap || swap.source !== obj.material || swap.customDepthMaterial !== customDepthMaterial) {
|
|
196
|
+
swap = {
|
|
197
|
+
customDepthMaterial,
|
|
198
|
+
source: obj.material,
|
|
199
|
+
swapped: customDepthMaterial ?? (
|
|
200
|
+
Array.isArray(obj.material)
|
|
201
|
+
? obj.material.map((mat) => depthMaterialFor(mat, obj))
|
|
202
|
+
: depthMaterialFor(obj.material, obj)
|
|
203
|
+
),
|
|
204
|
+
};
|
|
205
|
+
meshSwapCache.set(obj, swap);
|
|
206
|
+
} else if (!customDepthMaterial && Array.isArray(swap.swapped)) {
|
|
207
|
+
// Keep per-group visibility in step (depthMaterialFor refreshes the
|
|
208
|
+
// single-material case internally).
|
|
209
|
+
for (let i = 0; i < swap.source.length; i += 1) {
|
|
210
|
+
depthMaterialFor(swap.source[i], obj);
|
|
211
|
+
}
|
|
212
|
+
} else if (!customDepthMaterial) {
|
|
213
|
+
depthMaterialFor(swap.source, obj);
|
|
214
|
+
}
|
|
215
|
+
obj.material = swap.swapped;
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const previousTarget = renderer.getRenderTarget();
|
|
219
|
+
const previousBackground = scene.background;
|
|
220
|
+
// If a lab keeps three's native shadows on alongside this pass (the water
|
|
221
|
+
// playground does, for its MeshToonMaterial ground), rendering our
|
|
222
|
+
// depth-swapped scene must not also trigger three's native shadow-map
|
|
223
|
+
// render of that swapped scene — disable it just for this render.
|
|
224
|
+
const previousShadowAutoUpdate = renderer.shadowMap.enabled;
|
|
225
|
+
renderer.shadowMap.enabled = false;
|
|
226
|
+
scene.background = null;
|
|
227
|
+
renderer.setRenderTarget(shadowTarget);
|
|
228
|
+
renderer.setClearColor(0xffffff, 1);
|
|
229
|
+
renderer.clear();
|
|
230
|
+
renderer.render(scene, shadowCamera);
|
|
231
|
+
renderer.setRenderTarget(previousTarget);
|
|
232
|
+
renderer.shadowMap.enabled = previousShadowAutoUpdate;
|
|
233
|
+
scene.background = previousBackground;
|
|
234
|
+
|
|
235
|
+
for (const { material, mesh } of materialRestores) mesh.material = material;
|
|
236
|
+
for (const obj of visibilityRestores) obj.visible = true;
|
|
237
|
+
materialRestores.length = 0;
|
|
238
|
+
visibilityRestores.length = 0;
|
|
239
|
+
|
|
240
|
+
environmentSunShadow.matrix.value.copy(shadowMatrix);
|
|
241
|
+
environmentSunShadow.mapSize.value = mapSize;
|
|
242
|
+
environmentSunShadow.radius.value = shadow.radius ?? 1;
|
|
243
|
+
// Native shadow maps keep far more precision than the old packed-depth
|
|
244
|
+
// approximation. Keep the shared node-backend pass in float precision too,
|
|
245
|
+
// otherwise low sunset angles can collapse canopy-vs-grass depth and make
|
|
246
|
+
// nearby tree shadows disappear while taller/farther trees still work.
|
|
247
|
+
environmentSunShadow.bias.value = shadow.bias ?? -0.00004;
|
|
248
|
+
sunShadowMapNode().value = shadowTarget.texture;
|
|
249
|
+
environmentSunShadow.ready.value = true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function dispose() {
|
|
253
|
+
shadowTarget?.dispose();
|
|
254
|
+
shadowTarget = null;
|
|
255
|
+
environmentSunShadow.ready.value = false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
get shadowTexture() {
|
|
260
|
+
return shadowTarget?.texture ?? null;
|
|
261
|
+
},
|
|
262
|
+
get shadowMatrix() {
|
|
263
|
+
return shadowMatrix;
|
|
264
|
+
},
|
|
265
|
+
dispose,
|
|
266
|
+
update,
|
|
267
|
+
};
|
|
268
|
+
}
|