@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,52 @@
|
|
|
1
|
+
// TSL port of src/shaders/chunks/character-fragment-roles.glsl — material
|
|
2
|
+
// role ids and the role debug view color.
|
|
3
|
+
|
|
4
|
+
import { select, vec3 } from 'three/tsl';
|
|
5
|
+
|
|
6
|
+
export const MATERIAL_ROLE_DEFAULT = 0;
|
|
7
|
+
export const MATERIAL_ROLE_COSTUME = 1;
|
|
8
|
+
export const MATERIAL_ROLE_SKIN = 2;
|
|
9
|
+
export const MATERIAL_ROLE_FACE = 3;
|
|
10
|
+
export const MATERIAL_ROLE_HAIR = 4;
|
|
11
|
+
export const MATERIAL_ROLE_EYE = 5;
|
|
12
|
+
export const MATERIAL_ROLE_EYE_HIGHLIGHT = 6;
|
|
13
|
+
export const MATERIAL_ROLE_BLUSH = 7;
|
|
14
|
+
export const MATERIAL_ROLE_TRANSPARENT_OVERLAY = 8;
|
|
15
|
+
export const MATERIAL_ROLE_METAL = 9;
|
|
16
|
+
export const MATERIAL_ROLE_OUTLINE = 10;
|
|
17
|
+
export const MATERIAL_ROLE_IRIS = 11;
|
|
18
|
+
export const MATERIAL_ROLE_PUPIL = 12;
|
|
19
|
+
export const MATERIAL_ROLE_SCLERA = 13;
|
|
20
|
+
export const MATERIAL_ROLE_CATCHLIGHT = 14;
|
|
21
|
+
|
|
22
|
+
const ROLE_DEBUG_COLORS = [
|
|
23
|
+
[MATERIAL_ROLE_OUTLINE, [0.02, 0.02, 0.024]],
|
|
24
|
+
[MATERIAL_ROLE_CATCHLIGHT, [1.0, 0.96, 0.64]],
|
|
25
|
+
[MATERIAL_ROLE_EYE_HIGHLIGHT, [1.0, 1.0, 1.0]],
|
|
26
|
+
[MATERIAL_ROLE_PUPIL, [0.08, 0.08, 0.11]],
|
|
27
|
+
[MATERIAL_ROLE_IRIS, [0.52, 0.28, 1.0]],
|
|
28
|
+
[MATERIAL_ROLE_SCLERA, [0.7, 0.92, 1.0]],
|
|
29
|
+
[MATERIAL_ROLE_EYE, [0.18, 0.55, 1.0]],
|
|
30
|
+
[MATERIAL_ROLE_FACE, [1.0, 0.42, 0.58]],
|
|
31
|
+
[MATERIAL_ROLE_SKIN, [1.0, 0.73, 0.42]],
|
|
32
|
+
[MATERIAL_ROLE_HAIR, [0.28, 0.68, 1.0]],
|
|
33
|
+
[MATERIAL_ROLE_BLUSH, [1.0, 0.32, 0.48]],
|
|
34
|
+
[MATERIAL_ROLE_TRANSPARENT_OVERLAY, [0.74, 0.45, 1.0]],
|
|
35
|
+
[MATERIAL_ROLE_METAL, [1.0, 0.77, 0.23]],
|
|
36
|
+
[MATERIAL_ROLE_COSTUME, [0.44, 0.5, 0.62]],
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export function createRolesChunk({ u, flags }) {
|
|
40
|
+
const debugMaterialRoleColor = () => {
|
|
41
|
+
if (flags.isOutlinePass) return vec3(0.02, 0.02, 0.024);
|
|
42
|
+
let color = vec3(0.56, 0.58, 0.62);
|
|
43
|
+
// Build the GLSL if-chain back-to-front so earlier entries win.
|
|
44
|
+
for (let i = ROLE_DEBUG_COLORS.length - 1; i >= 0; i -= 1) {
|
|
45
|
+
const [role, rgb] = ROLE_DEBUG_COLORS[i];
|
|
46
|
+
color = select(u.materialRole.equal(role), vec3(...rgb), color);
|
|
47
|
+
}
|
|
48
|
+
return color;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return { debugMaterialRoleColor };
|
|
52
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// TSL replacement for the three.js classic light uniforms the GLSL character
|
|
2
|
+
// shader consumes via <lights_pars_begin> (directionalLights[0], pointLights,
|
|
3
|
+
// spotLights, hemisphereLights, ambientLightColor).
|
|
4
|
+
//
|
|
5
|
+
// The node renderer has its own light system (LightsNode + LightingModel),
|
|
6
|
+
// but the toon model needs raw ingredients — the first directional light as
|
|
7
|
+
// "the main light", per-light toon bands instead of physical accumulation,
|
|
8
|
+
// and the shadow mask separate from the light color — so the light state is
|
|
9
|
+
// mirrored into shared uniforms instead. `syncToonSceneLights(scene, camera)`
|
|
10
|
+
// runs from the converted meshes' onBeforeRender (both renderers call it),
|
|
11
|
+
// so no per-lab plumbing is needed; every toon material shares these nodes.
|
|
12
|
+
//
|
|
13
|
+
// View-space conventions and attenuation math replicate three's WebGLLights
|
|
14
|
+
// exactly (direction = toward the light, color premultiplied by intensity).
|
|
15
|
+
|
|
16
|
+
import * as THREE from 'three';
|
|
17
|
+
import {
|
|
18
|
+
dot,
|
|
19
|
+
float,
|
|
20
|
+
Fn,
|
|
21
|
+
If,
|
|
22
|
+
max,
|
|
23
|
+
mix,
|
|
24
|
+
normalize,
|
|
25
|
+
pow,
|
|
26
|
+
smoothstep,
|
|
27
|
+
uniform,
|
|
28
|
+
uniformArray,
|
|
29
|
+
vec3,
|
|
30
|
+
} from 'three/tsl';
|
|
31
|
+
|
|
32
|
+
export const MAX_TOON_POINT_LIGHTS = 8;
|
|
33
|
+
export const MAX_TOON_SPOT_LIGHTS = 4;
|
|
34
|
+
export const MAX_TOON_HEMI_LIGHTS = 2;
|
|
35
|
+
|
|
36
|
+
function vectorArray(count) {
|
|
37
|
+
return Array.from({ length: count }, () => new THREE.Vector3());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const toonSceneLights = {
|
|
41
|
+
ambientLightColor: uniform(new THREE.Color(0, 0, 0)),
|
|
42
|
+
hasMainLight: uniform(0, 'float'),
|
|
43
|
+
mainLightColor: uniform(new THREE.Color(1, 1, 1)),
|
|
44
|
+
mainLightDirection: uniform(new THREE.Vector3(0.35, 0.75, 0.55)),
|
|
45
|
+
|
|
46
|
+
pointLightColors: uniformArray(vectorArray(MAX_TOON_POINT_LIGHTS), 'vec3'),
|
|
47
|
+
pointLightCount: uniform(0, 'float'),
|
|
48
|
+
// x: cutoff distance, y: decay exponent
|
|
49
|
+
pointLightParams: uniformArray(
|
|
50
|
+
Array.from({ length: MAX_TOON_POINT_LIGHTS }, () => new THREE.Vector2()),
|
|
51
|
+
'vec2',
|
|
52
|
+
),
|
|
53
|
+
pointLightPositions: uniformArray(vectorArray(MAX_TOON_POINT_LIGHTS), 'vec3'),
|
|
54
|
+
|
|
55
|
+
spotLightColors: uniformArray(vectorArray(MAX_TOON_SPOT_LIGHTS), 'vec3'),
|
|
56
|
+
spotLightCount: uniform(0, 'float'),
|
|
57
|
+
spotLightDirections: uniformArray(vectorArray(MAX_TOON_SPOT_LIGHTS), 'vec3'),
|
|
58
|
+
// x: cutoff distance, y: decay, z: coneCos, w: penumbraCos
|
|
59
|
+
spotLightParams: uniformArray(
|
|
60
|
+
Array.from({ length: MAX_TOON_SPOT_LIGHTS }, () => new THREE.Vector4()),
|
|
61
|
+
'vec4',
|
|
62
|
+
),
|
|
63
|
+
spotLightPositions: uniformArray(vectorArray(MAX_TOON_SPOT_LIGHTS), 'vec3'),
|
|
64
|
+
|
|
65
|
+
hemiLightCount: uniform(0, 'float'),
|
|
66
|
+
hemiLightDirections: uniformArray(vectorArray(MAX_TOON_HEMI_LIGHTS), 'vec3'),
|
|
67
|
+
hemiLightGroundColors: uniformArray(vectorArray(MAX_TOON_HEMI_LIGHTS), 'vec3'),
|
|
68
|
+
hemiLightSkyColors: uniformArray(vectorArray(MAX_TOON_HEMI_LIGHTS), 'vec3'),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const workColor = new THREE.Color();
|
|
72
|
+
const workVectorA = new THREE.Vector3();
|
|
73
|
+
const workVectorB = new THREE.Vector3();
|
|
74
|
+
|
|
75
|
+
function lightIsOn(light) {
|
|
76
|
+
return light.visible !== false && (light.intensity ?? 0) > 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Mirrors the scene's light state into the shared toon light uniforms —
|
|
81
|
+
* the node-backend equivalent of three's WebGLLights setup for the subset
|
|
82
|
+
* the character shader reads. Cheap enough to run once per rendered frame.
|
|
83
|
+
*/
|
|
84
|
+
export function syncToonSceneLights(scene, camera) {
|
|
85
|
+
if (!scene || !camera) return;
|
|
86
|
+
const viewMatrix = camera.matrixWorldInverse;
|
|
87
|
+
const s = toonSceneLights;
|
|
88
|
+
|
|
89
|
+
let ambientR = 0;
|
|
90
|
+
let ambientG = 0;
|
|
91
|
+
let ambientB = 0;
|
|
92
|
+
let mainLightFound = false;
|
|
93
|
+
let pointCount = 0;
|
|
94
|
+
let spotCount = 0;
|
|
95
|
+
let hemiCount = 0;
|
|
96
|
+
|
|
97
|
+
scene.traverse((light) => {
|
|
98
|
+
if (!light.isLight || !lightIsOn(light)) return;
|
|
99
|
+
|
|
100
|
+
if (light.isAmbientLight) {
|
|
101
|
+
ambientR += light.color.r * light.intensity;
|
|
102
|
+
ambientG += light.color.g * light.intensity;
|
|
103
|
+
ambientB += light.color.b * light.intensity;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (light.isDirectionalLight) {
|
|
108
|
+
// First directional light in traversal order is "the main light",
|
|
109
|
+
// matching directionalLights[0] in the classic pipeline.
|
|
110
|
+
if (mainLightFound) return;
|
|
111
|
+
mainLightFound = true;
|
|
112
|
+
workVectorA.setFromMatrixPosition(light.matrixWorld);
|
|
113
|
+
workVectorB.setFromMatrixPosition(light.target.matrixWorld);
|
|
114
|
+
workVectorA.sub(workVectorB).transformDirection(viewMatrix);
|
|
115
|
+
s.mainLightDirection.value.copy(workVectorA);
|
|
116
|
+
workColor.copy(light.color).multiplyScalar(light.intensity);
|
|
117
|
+
s.mainLightColor.value.copy(workColor);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (light.isPointLight && pointCount < MAX_TOON_POINT_LIGHTS) {
|
|
122
|
+
const index = pointCount;
|
|
123
|
+
pointCount += 1;
|
|
124
|
+
workVectorA.setFromMatrixPosition(light.matrixWorld).applyMatrix4(viewMatrix);
|
|
125
|
+
s.pointLightPositions.array[index].copy(workVectorA);
|
|
126
|
+
workColor.copy(light.color).multiplyScalar(light.intensity);
|
|
127
|
+
s.pointLightColors.array[index].set(workColor.r, workColor.g, workColor.b);
|
|
128
|
+
s.pointLightParams.array[index].set(light.distance ?? 0, light.decay ?? 2);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (light.isSpotLight && spotCount < MAX_TOON_SPOT_LIGHTS) {
|
|
133
|
+
const index = spotCount;
|
|
134
|
+
spotCount += 1;
|
|
135
|
+
workVectorA.setFromMatrixPosition(light.matrixWorld).applyMatrix4(viewMatrix);
|
|
136
|
+
s.spotLightPositions.array[index].copy(workVectorA);
|
|
137
|
+
workVectorA.setFromMatrixPosition(light.matrixWorld);
|
|
138
|
+
workVectorB.setFromMatrixPosition(light.target.matrixWorld);
|
|
139
|
+
workVectorA.sub(workVectorB).transformDirection(viewMatrix);
|
|
140
|
+
s.spotLightDirections.array[index].copy(workVectorA);
|
|
141
|
+
workColor.copy(light.color).multiplyScalar(light.intensity);
|
|
142
|
+
s.spotLightColors.array[index].set(workColor.r, workColor.g, workColor.b);
|
|
143
|
+
s.spotLightParams.array[index].set(
|
|
144
|
+
light.distance ?? 0,
|
|
145
|
+
light.decay ?? 2,
|
|
146
|
+
Math.cos(light.angle),
|
|
147
|
+
Math.cos(light.angle * (1 - light.penumbra)),
|
|
148
|
+
);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (light.isHemisphereLight && hemiCount < MAX_TOON_HEMI_LIGHTS) {
|
|
153
|
+
const index = hemiCount;
|
|
154
|
+
hemiCount += 1;
|
|
155
|
+
workVectorA.setFromMatrixPosition(light.matrixWorld).transformDirection(viewMatrix);
|
|
156
|
+
s.hemiLightDirections.array[index].copy(workVectorA);
|
|
157
|
+
workColor.copy(light.color).multiplyScalar(light.intensity);
|
|
158
|
+
s.hemiLightSkyColors.array[index].set(workColor.r, workColor.g, workColor.b);
|
|
159
|
+
workColor.copy(light.groundColor).multiplyScalar(light.intensity);
|
|
160
|
+
s.hemiLightGroundColors.array[index].set(workColor.r, workColor.g, workColor.b);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
s.ambientLightColor.value.setRGB(ambientR, ambientG, ambientB);
|
|
165
|
+
s.hasMainLight.value = mainLightFound ? 1 : 0;
|
|
166
|
+
s.pointLightCount.value = pointCount;
|
|
167
|
+
s.spotLightCount.value = spotCount;
|
|
168
|
+
s.hemiLightCount.value = hemiCount;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** getMainLightDirection(): first directional light or the GLSL fallback. */
|
|
172
|
+
export const getMainLightDirection = /*@__PURE__*/ Fn(() => {
|
|
173
|
+
const fallback = normalize(vec3(0.35, 0.75, 0.55));
|
|
174
|
+
const main = normalize(toonSceneLights.mainLightDirection);
|
|
175
|
+
return mix(fallback, main, toonSceneLights.hasMainLight);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
/** getMainLightColor(): clamped main light color or white fallback. */
|
|
179
|
+
export function getMainLightColor(mainLightMaxContribution) {
|
|
180
|
+
const clamped = min3(vec3(mainLightMaxContribution), toonSceneLights.mainLightColor);
|
|
181
|
+
return mix(vec3(1.0), clamped, toonSceneLights.hasMainLight);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function min3(a, b) {
|
|
185
|
+
return vec3(a).min(vec3(b));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// three's getDistanceAttenuation (common.glsl) — physically based falloff
|
|
189
|
+
// with the artist cutoff window.
|
|
190
|
+
const distanceAttenuation = /*@__PURE__*/ Fn(([lightDistance, cutoffDistance, decayExponent]) => {
|
|
191
|
+
const distanceFalloff = float(1.0).div(max(pow(lightDistance, decayExponent), 0.01)).toVar();
|
|
192
|
+
If(cutoffDistance.greaterThan(0.0), () => {
|
|
193
|
+
const ratio = lightDistance.div(cutoffDistance);
|
|
194
|
+
const ratio2 = ratio.mul(ratio);
|
|
195
|
+
const window1 = ratio2.mul(ratio2).oneMinus().clamp(0.0, 1.0);
|
|
196
|
+
distanceFalloff.mulAssign(window1.mul(window1));
|
|
197
|
+
});
|
|
198
|
+
return distanceFalloff;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* evaluateLocalLightFill + evaluateHemisphereFill, assembled per material.
|
|
203
|
+
* `localLightBand` is the toon band the GLSL applies per local light; the
|
|
204
|
+
* caller provides it (it depends on material uniforms). Returns
|
|
205
|
+
* { localLight, strongestLocalLight, hemisphereFill } node factories.
|
|
206
|
+
*/
|
|
207
|
+
export function createLocalLightEvaluators({ localLightBand }) {
|
|
208
|
+
const s = toonSceneLights;
|
|
209
|
+
|
|
210
|
+
const evaluateLocalLightFill = (normal, geometryPosition, localLightIntensity, localLightMaxContribution) => {
|
|
211
|
+
const localLight = vec3(0.0).toVar();
|
|
212
|
+
|
|
213
|
+
for (let i = 0; i < MAX_TOON_POINT_LIGHTS; i += 1) {
|
|
214
|
+
If(float(i).lessThan(s.pointLightCount), () => {
|
|
215
|
+
const lVector = s.pointLightPositions.element(i).sub(geometryPosition);
|
|
216
|
+
const lightDistance = lVector.length();
|
|
217
|
+
const direction = lVector.div(max(lightDistance, 1e-6));
|
|
218
|
+
const params = s.pointLightParams.element(i);
|
|
219
|
+
const attenuation = distanceAttenuation(lightDistance, params.x, params.y);
|
|
220
|
+
const color = s.pointLightColors.element(i).mul(attenuation).toVar();
|
|
221
|
+
If(maxComponent3(color).greaterThan(0.0), () => {
|
|
222
|
+
const band = localLightBand(normal, direction);
|
|
223
|
+
localLight.addAssign(color.mul(band));
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (let i = 0; i < MAX_TOON_SPOT_LIGHTS; i += 1) {
|
|
229
|
+
If(float(i).lessThan(s.spotLightCount), () => {
|
|
230
|
+
const lVector = s.spotLightPositions.element(i).sub(geometryPosition);
|
|
231
|
+
const lightDistance = lVector.length();
|
|
232
|
+
const direction = lVector.div(max(lightDistance, 1e-6));
|
|
233
|
+
const params = s.spotLightParams.element(i);
|
|
234
|
+
const angleCos = dot(direction, normalize(s.spotLightDirections.element(i)));
|
|
235
|
+
const spotAttenuation = smoothstep(params.z, params.w, angleCos);
|
|
236
|
+
const attenuation = distanceAttenuation(lightDistance, params.x, params.y)
|
|
237
|
+
.mul(spotAttenuation);
|
|
238
|
+
const color = s.spotLightColors.element(i).mul(attenuation).toVar();
|
|
239
|
+
If(maxComponent3(color).greaterThan(0.0), () => {
|
|
240
|
+
const band = localLightBand(normal, direction);
|
|
241
|
+
localLight.addAssign(color.mul(band));
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const limited = min3(localLight.mul(localLightIntensity), vec3(localLightMaxContribution)).toVar();
|
|
247
|
+
const strongestLocalLight = maxComponent3(limited);
|
|
248
|
+
return { localLight: limited, strongestLocalLight };
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const evaluateHemisphereFill = (normal, hemisphereLightIntensity) => {
|
|
252
|
+
const hemisphereFill = vec3(0.0).toVar();
|
|
253
|
+
for (let i = 0; i < MAX_TOON_HEMI_LIGHTS; i += 1) {
|
|
254
|
+
If(float(i).lessThan(s.hemiLightCount), () => {
|
|
255
|
+
const dotNL = dot(normal, normalize(s.hemiLightDirections.element(i)));
|
|
256
|
+
const hemiDiffuseWeight = dotNL.mul(0.5).add(0.5);
|
|
257
|
+
hemisphereFill.addAssign(
|
|
258
|
+
mix(s.hemiLightGroundColors.element(i), s.hemiLightSkyColors.element(i), hemiDiffuseWeight),
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return hemisphereFill.mul(hemisphereLightIntensity);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return { evaluateHemisphereFill, evaluateLocalLightFill };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function maxComponent3(value) {
|
|
269
|
+
return max(value.r, max(value.g, value.b));
|
|
270
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// TSL port of src/shaders/chunks/character-fragment-shadow-color.glsl —
|
|
2
|
+
// the tinted/HSV-shifted albedo used inside shadow areas.
|
|
3
|
+
|
|
4
|
+
import { abs, clamp, max, mix, vec3 } from 'three/tsl';
|
|
5
|
+
|
|
6
|
+
import { adjustSaturation, applyHSVChange, rgbToHsv } from './character-color.js';
|
|
7
|
+
|
|
8
|
+
export function createShadowColorChunk({ u }) {
|
|
9
|
+
const calculateShadowColor = (albedo, finalShadowArea) => {
|
|
10
|
+
const transitionArea = clamp(
|
|
11
|
+
abs(finalShadowArea.sub(0.5)).mul(2.0).oneMinus().mul(u.litToShadowTransitionAreaIntensity),
|
|
12
|
+
0.0,
|
|
13
|
+
1.0,
|
|
14
|
+
);
|
|
15
|
+
const hueOffset = u.selfShadowAreaHueOffset.mul(u.selfShadowAreaHSVStrength)
|
|
16
|
+
.add(u.litToShadowTransitionAreaHueOffset.mul(transitionArea));
|
|
17
|
+
const saturationBoost = u.selfShadowAreaSaturationBoost.mul(u.selfShadowAreaHSVStrength)
|
|
18
|
+
.add(u.litToShadowTransitionAreaSaturationBoost.mul(transitionArea));
|
|
19
|
+
const valueMul = mix(1.0, u.selfShadowAreaValueMul, u.selfShadowAreaHSVStrength)
|
|
20
|
+
.mul(mix(1.0, u.litToShadowTransitionAreaValueMul, transitionArea));
|
|
21
|
+
|
|
22
|
+
const originalHSV = rgbToHsv(max(albedo, vec3(0.0)));
|
|
23
|
+
const result = applyHSVChange(albedo, hueOffset, saturationBoost, valueMul).toVar();
|
|
24
|
+
|
|
25
|
+
const fallbackColor = albedo.mul(u.lowSaturationFallbackColor.rgb);
|
|
26
|
+
const fallbackMask = mix(1.0, clamp(originalHSV.y.mul(5.0), 0.0, 1.0), u.lowSaturationFallbackColor.a);
|
|
27
|
+
result.assign(mix(fallbackColor, result, fallbackMask));
|
|
28
|
+
|
|
29
|
+
result.mulAssign(u.selfShadowTintColor);
|
|
30
|
+
result.mulAssign(mix(vec3(1.0), u.litToShadowTransitionAreaTintColor, transitionArea));
|
|
31
|
+
|
|
32
|
+
// Skin/face overrides (uniform-mixed like the GLSL branches — isSkin and
|
|
33
|
+
// isFace are per-material constants carried as uniforms).
|
|
34
|
+
const skinShadow = adjustSaturation(
|
|
35
|
+
albedo.mul(u.skinShadowTintColor).mul(u.skinShadowTintColor2).mul(u.skinShadowBrightness),
|
|
36
|
+
u.skinShadowSaturation,
|
|
37
|
+
);
|
|
38
|
+
result.assign(mix(
|
|
39
|
+
result,
|
|
40
|
+
mix(result, skinShadow, u.overrideBySkinShadowTintColor),
|
|
41
|
+
u.isSkin.select(1.0, 0.0),
|
|
42
|
+
));
|
|
43
|
+
|
|
44
|
+
const faceShadow = adjustSaturation(
|
|
45
|
+
albedo.mul(u.faceShadowTintColor).mul(u.faceShadowTintColor2).mul(u.faceShadowBrightness),
|
|
46
|
+
u.faceShadowSaturation,
|
|
47
|
+
);
|
|
48
|
+
result.assign(mix(
|
|
49
|
+
result,
|
|
50
|
+
mix(result, faceShadow, u.overrideByFaceShadowTintColor),
|
|
51
|
+
u.isFace.select(1.0, 0.0),
|
|
52
|
+
));
|
|
53
|
+
|
|
54
|
+
result.assign(mix(result, result.mul(albedo), u.selfShadowAlbedoMulStrength));
|
|
55
|
+
|
|
56
|
+
// enableShadowColor=false → plain albedo (GLSL early return).
|
|
57
|
+
return mix(albedo, result, u.enableShadowColor.select(1.0, 0.0));
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return { calculateShadowColor };
|
|
61
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Storage-buffer skinning for the toon character on the forced-WebGL2
|
|
2
|
+
// backend.
|
|
3
|
+
//
|
|
4
|
+
// three r185's built-in SkinningNode stores bone matrices in a uniform
|
|
5
|
+
// buffer; GL_MAX_UNIFORM_BLOCK_SIZE (16KB ≈ 256 bones) makes every MMD-scale
|
|
6
|
+
// skeleton (300-800 bones) fail to link on the WebGL2 backend. This chunk
|
|
7
|
+
// stores the same skeleton.boneMatrices array as a storage attribute with
|
|
8
|
+
// setPBO(true): the GLSL builder emits it as a float DataTexture + texelFetch
|
|
9
|
+
// (the classic boneTexture technique), while WebGPU would emit a real storage
|
|
10
|
+
// buffer. The math replicates three's getSkinnedPosition/NormalAndTangent.
|
|
11
|
+
//
|
|
12
|
+
// ToonAnimeNodeMaterial.setupPosition routes skinned meshes here on the
|
|
13
|
+
// WebGL2 backend only; WebGPU keeps the built-in path.
|
|
14
|
+
|
|
15
|
+
import * as THREE from 'three';
|
|
16
|
+
import {
|
|
17
|
+
attribute,
|
|
18
|
+
mat4,
|
|
19
|
+
morphReference,
|
|
20
|
+
normalLocal,
|
|
21
|
+
positionLocal,
|
|
22
|
+
storage,
|
|
23
|
+
uniform,
|
|
24
|
+
vec4,
|
|
25
|
+
} from 'three/tsl';
|
|
26
|
+
|
|
27
|
+
// One storage attribute per skeleton, shared by every material/mesh using it.
|
|
28
|
+
const boneStorageBySkeleton = new WeakMap();
|
|
29
|
+
|
|
30
|
+
function boneStorageEntry(skeleton) {
|
|
31
|
+
let entry = boneStorageBySkeleton.get(skeleton);
|
|
32
|
+
if (!entry) {
|
|
33
|
+
// Make sure the matrices reflect the current pose before the first
|
|
34
|
+
// texture upload (nothing else updates the skeleton on this path).
|
|
35
|
+
skeleton.update();
|
|
36
|
+
// Bone matrices as bones×4 vec4 columns (column-major float array).
|
|
37
|
+
const attributeBuffer = new THREE.InstancedBufferAttribute(skeleton.boneMatrices, 4);
|
|
38
|
+
const node = storage(attributeBuffer, 'vec4', skeleton.bones.length * 4)
|
|
39
|
+
.setPBO(true)
|
|
40
|
+
.toReadOnly();
|
|
41
|
+
entry = { attributeBuffer, node };
|
|
42
|
+
boneStorageBySkeleton.set(skeleton, entry);
|
|
43
|
+
}
|
|
44
|
+
return entry;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Applies storage-buffer skinning to positionLocal/normalLocal in place.
|
|
49
|
+
* Call inside setupPosition (an active stack is required).
|
|
50
|
+
*/
|
|
51
|
+
export function applyToonStorageSkinning(skinnedMesh) {
|
|
52
|
+
const skeleton = skinnedMesh.skeleton;
|
|
53
|
+
const bones = boneStorageEntry(skeleton).node;
|
|
54
|
+
|
|
55
|
+
const bindMatrix = uniform(skinnedMesh.bindMatrix, 'mat4');
|
|
56
|
+
const bindMatrixInverse = uniform(skinnedMesh.bindMatrixInverse, 'mat4');
|
|
57
|
+
const skinIndex = attribute('skinIndex', 'uvec4');
|
|
58
|
+
const skinWeight = attribute('skinWeight', 'vec4');
|
|
59
|
+
|
|
60
|
+
const boneMatrix = (index) => {
|
|
61
|
+
const base = index.mul(4);
|
|
62
|
+
return mat4(
|
|
63
|
+
bones.element(base),
|
|
64
|
+
bones.element(base.add(1)),
|
|
65
|
+
bones.element(base.add(2)),
|
|
66
|
+
bones.element(base.add(3)),
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const boneMatX = boneMatrix(skinIndex.x);
|
|
71
|
+
const boneMatY = boneMatrix(skinIndex.y);
|
|
72
|
+
const boneMatZ = boneMatrix(skinIndex.z);
|
|
73
|
+
const boneMatW = boneMatrix(skinIndex.w);
|
|
74
|
+
|
|
75
|
+
// Position (three's getSkinnedPosition).
|
|
76
|
+
const skinVertex = bindMatrix.mul(vec4(positionLocal, 1.0));
|
|
77
|
+
const skinned = boneMatX.mul(skinVertex).mul(skinWeight.x)
|
|
78
|
+
.add(boneMatY.mul(skinVertex).mul(skinWeight.y))
|
|
79
|
+
.add(boneMatZ.mul(skinVertex).mul(skinWeight.z))
|
|
80
|
+
.add(boneMatW.mul(skinVertex).mul(skinWeight.w));
|
|
81
|
+
const skinPosition = bindMatrixInverse.mul(skinned).xyz;
|
|
82
|
+
|
|
83
|
+
// Normal (three's getSkinnedNormalAndTangent, tangent omitted — the toon
|
|
84
|
+
// shader derives its TBN from screen derivatives).
|
|
85
|
+
let skinMatrix = boneMatX.mul(skinWeight.x)
|
|
86
|
+
.add(boneMatY.mul(skinWeight.y))
|
|
87
|
+
.add(boneMatZ.mul(skinWeight.z))
|
|
88
|
+
.add(boneMatW.mul(skinWeight.w));
|
|
89
|
+
skinMatrix = bindMatrixInverse.mul(skinMatrix).mul(bindMatrix);
|
|
90
|
+
const skinNormal = skinMatrix.mul(vec4(normalLocal, 0.0)).xyz;
|
|
91
|
+
|
|
92
|
+
positionLocal.assign(skinPosition);
|
|
93
|
+
normalLocal.assign(skinNormal);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Subclasses a node material so skinned meshes skin through the storage/PBO
|
|
98
|
+
* path on non-WebGPU backends (built-in buffer skinning elsewhere). Used by
|
|
99
|
+
* the anime material and by the character-pass depth/mask materials, which
|
|
100
|
+
* render the same MMD-scale skeletons.
|
|
101
|
+
*/
|
|
102
|
+
export function withToonStorageSkinning(BaseNodeMaterial) {
|
|
103
|
+
return class extends BaseNodeMaterial {
|
|
104
|
+
setupPosition(builder) {
|
|
105
|
+
const { object, geometry } = builder;
|
|
106
|
+
const useStorageSkinning = object.isSkinnedMesh === true &&
|
|
107
|
+
builder.renderer?.backend?.isWebGPUBackend !== true;
|
|
108
|
+
if (!useStorageSkinning) return super.setupPosition(builder);
|
|
109
|
+
|
|
110
|
+
// Mirrors NodeMaterial.setupPosition for the paths a skinned character
|
|
111
|
+
// can hit (morphs, then skinning); batching/instancing/displacement do
|
|
112
|
+
// not apply to skinned toon meshes.
|
|
113
|
+
if (geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color) {
|
|
114
|
+
morphReference(object);
|
|
115
|
+
}
|
|
116
|
+
applyToonStorageSkinning(object);
|
|
117
|
+
return positionLocal;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Per-frame CPU side: recompute bone matrices and re-upload the PBO texture.
|
|
124
|
+
* Runs from the converted meshes' onBeforeRender (idempotent per frame).
|
|
125
|
+
*/
|
|
126
|
+
export function updateToonStorageSkinning(skinnedMesh) {
|
|
127
|
+
const skeleton = skinnedMesh?.skeleton;
|
|
128
|
+
if (!skeleton) return;
|
|
129
|
+
const entry = boneStorageBySkeleton.get(skeleton);
|
|
130
|
+
if (!entry) return;
|
|
131
|
+
skeleton.update();
|
|
132
|
+
const attributeBuffer = entry.attributeBuffer;
|
|
133
|
+
// The GLSL builder's PBO setup REPLACES the attribute array with a padded
|
|
134
|
+
// copy (pow2 texture dimensions) — mirror the live bone matrices into it
|
|
135
|
+
// before flagging the wrapping DataTexture (attribute.pbo) for re-upload.
|
|
136
|
+
if (attributeBuffer.array !== skeleton.boneMatrices) {
|
|
137
|
+
attributeBuffer.array.set(skeleton.boneMatrices, 0);
|
|
138
|
+
}
|
|
139
|
+
if (attributeBuffer.pbo) attributeBuffer.pbo.needsUpdate = true;
|
|
140
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// TSL port of src/shaders/chunks/environment-fragment-color.glsl — color
|
|
2
|
+
// helpers for the environment shader: Rec.709 luma, saturation remap, and the
|
|
3
|
+
// blue/cyan window-pane detector used by the open-windows cutout.
|
|
4
|
+
//
|
|
5
|
+
// Leaf node→node helpers, exported as Fn() per docs/tsl-conventions.md.
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
dot,
|
|
9
|
+
Fn,
|
|
10
|
+
max,
|
|
11
|
+
min,
|
|
12
|
+
mix,
|
|
13
|
+
smoothstep,
|
|
14
|
+
vec3,
|
|
15
|
+
} from 'three/tsl';
|
|
16
|
+
|
|
17
|
+
/** GLSL envLuma(color): Rec.709 luminance. */
|
|
18
|
+
export const envLuma = /*@__PURE__*/ Fn(([color]) => {
|
|
19
|
+
return dot(color, vec3(0.2126, 0.7152, 0.0722));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/** GLSL applySaturation(color, amount): lerp from grayscale luma. */
|
|
23
|
+
export const applySaturation = /*@__PURE__*/ Fn(([color, amount]) => {
|
|
24
|
+
const luma = dot(color, vec3(0.2126, 0.7152, 0.0722));
|
|
25
|
+
return mix(vec3(luma), color, amount);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* GLSL windowPaneMask(color): 1 where the texel reads as a blue/cyan glass
|
|
30
|
+
* pane, guarded against bright cloth/paper. Same smoothstep windows.
|
|
31
|
+
*/
|
|
32
|
+
export const windowPaneMask = /*@__PURE__*/ Fn(([color]) => {
|
|
33
|
+
const maxChannel = max(color.r, max(color.g, color.b));
|
|
34
|
+
const minChannel = min(color.r, min(color.g, color.b));
|
|
35
|
+
const colorSpread = maxChannel.sub(minChannel);
|
|
36
|
+
const bluePane = smoothstep(0.03, 0.16, color.b.sub(color.r))
|
|
37
|
+
.mul(smoothstep(0.00, 0.10, color.g.sub(color.r)))
|
|
38
|
+
.mul(smoothstep(0.24, 0.54, color.b))
|
|
39
|
+
.mul(smoothstep(0.03, 0.22, colorSpread));
|
|
40
|
+
const cyanPane = smoothstep(0.02, 0.14, color.g.sub(color.r))
|
|
41
|
+
.mul(smoothstep(0.02, 0.14, color.b.sub(color.r)))
|
|
42
|
+
.mul(smoothstep(0.24, 0.52, max(color.g, color.b)))
|
|
43
|
+
.mul(smoothstep(0.02, 0.18, colorSpread));
|
|
44
|
+
const clothOrPaperGuard = smoothstep(1.48, 2.15, color.r.add(color.g).add(color.b)).oneMinus();
|
|
45
|
+
|
|
46
|
+
return max(bluePane, cyanPane).mul(clothOrPaperGuard);
|
|
47
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// TSL port of src/shaders/chunks/environment-fragment-debug.glsl —
|
|
2
|
+
// environment debug views. Mode ids must match ENVIRONMENT_DEBUG_MODES in
|
|
3
|
+
// environmentSettings.js.
|
|
4
|
+
//
|
|
5
|
+
// On the node backends the debug table is always compiled in (it adds zero
|
|
6
|
+
// bindings — every input is already in the graph), so the GLSL
|
|
7
|
+
// ENV_DEBUG_VIEWS define flip becomes a pure write of the shared envDebugMode
|
|
8
|
+
// uniform (docs/tsl-conventions.md, debug-views precedent). The GLSL if-chain
|
|
9
|
+
// becomes a masked arithmetic sum: deep nested select() chains trip the GLSL
|
|
10
|
+
// builder's detached type-resolution fallback, and the fragment root must not
|
|
11
|
+
// be a ConditionalNode.
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
float,
|
|
15
|
+
floor,
|
|
16
|
+
select,
|
|
17
|
+
vec3,
|
|
18
|
+
vec4,
|
|
19
|
+
} from 'three/tsl';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* environmentDebugColor(...) — returns finalColor when mode matches no table
|
|
23
|
+
* entry (including mode 0 / off), else the selected debug view.
|
|
24
|
+
*/
|
|
25
|
+
export function environmentDebugColor({
|
|
26
|
+
mode,
|
|
27
|
+
albedo,
|
|
28
|
+
litColor,
|
|
29
|
+
ambient,
|
|
30
|
+
directLight,
|
|
31
|
+
pointLight,
|
|
32
|
+
spotLight,
|
|
33
|
+
sunlightVisibility,
|
|
34
|
+
aoMul,
|
|
35
|
+
bakedGi,
|
|
36
|
+
normal,
|
|
37
|
+
vertexAo,
|
|
38
|
+
specular,
|
|
39
|
+
emissiveMask,
|
|
40
|
+
windowMask,
|
|
41
|
+
roomOcclusion,
|
|
42
|
+
alpha,
|
|
43
|
+
finalColor,
|
|
44
|
+
}) {
|
|
45
|
+
const m = floor(mode.add(0.5)); // GLSL: int m = int(mode + 0.5)
|
|
46
|
+
|
|
47
|
+
const entries = [
|
|
48
|
+
[1, vec4(albedo, 1.0)],
|
|
49
|
+
[2, vec4(litColor, 1.0)],
|
|
50
|
+
[3, vec4(ambient, 1.0)],
|
|
51
|
+
[4, vec4(directLight, 1.0)],
|
|
52
|
+
[5, vec4(vec3(sunlightVisibility), 1.0)],
|
|
53
|
+
[6, vec4(pointLight, 1.0)],
|
|
54
|
+
[7, vec4(spotLight, 1.0)],
|
|
55
|
+
[8, vec4(aoMul, 1.0)],
|
|
56
|
+
[9, vec4(bakedGi, 1.0)],
|
|
57
|
+
[10, vec4(normal.mul(0.5).add(0.5), 1.0)],
|
|
58
|
+
[11, vec4(vec3(vertexAo), 1.0)],
|
|
59
|
+
[12, vec4(specular, 1.0)],
|
|
60
|
+
[13, vec4(vec3(emissiveMask), 1.0)],
|
|
61
|
+
[14, vec4(vec3(windowMask), 1.0)],
|
|
62
|
+
[15, vec4(vec3(roomOcclusion.oneMinus()), 1.0)],
|
|
63
|
+
[16, vec4(vec3(alpha), 1.0)],
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
// 1 when the mode matches any table entry, else 0 — keeps the finalColor
|
|
67
|
+
// fallback for off/unknown modes without a conditional chain.
|
|
68
|
+
let knownMask = float(0.0);
|
|
69
|
+
for (const [id] of entries) {
|
|
70
|
+
knownMask = knownMask.add(select(m.equal(float(id)), float(1.0), float(0.0)));
|
|
71
|
+
}
|
|
72
|
+
knownMask = knownMask.min(1.0);
|
|
73
|
+
|
|
74
|
+
let result = vec4(finalColor).mul(knownMask.oneMinus());
|
|
75
|
+
for (const [id, value] of entries) {
|
|
76
|
+
result = result.add(vec4(value).mul(select(m.equal(float(id)), float(1.0), float(0.0))));
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|