@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,963 @@
|
|
|
1
|
+
// TSL port of src/shaders/anime.vert.glsl + anime.frag.glsl — the toon
|
|
2
|
+
// character material. Consumed by toonMaterialAdapter's createAnimeMaterial on
|
|
3
|
+
// the TSL backend; receives the same resolved settings objects the GLSL
|
|
4
|
+
// ShaderMaterial uniform block is built from, and exposes `.uniforms` under
|
|
5
|
+
// the exact GLSL uniform names so every adapter/HUD write-through works
|
|
6
|
+
// unchanged on both backends.
|
|
7
|
+
//
|
|
8
|
+
// Porting notes (docs/tsl-conventions.md):
|
|
9
|
+
// - Optional samplers are gated by the same conditions as the GLSL defines
|
|
10
|
+
// (JS guards instead of #ifdef) so unused textures never enter the graph.
|
|
11
|
+
// - Runtime feature toggles stay uniform-driven branches, exactly like GLSL.
|
|
12
|
+
// - Scene lights come from chunks/character-scene-lights.js (shared uniforms
|
|
13
|
+
// mirrored from the scene each frame) instead of <lights_pars_begin>.
|
|
14
|
+
// - getShadowMask() (scene shadow maps on the character) is a Phase 3 item;
|
|
15
|
+
// until then sceneShadowVisibility falls back to 1.0 exactly like a scene
|
|
16
|
+
// with no shadow-casting lights.
|
|
17
|
+
// - Render-target-fed textures sample at explicit level 0 (WGSL forbids
|
|
18
|
+
// implicit-derivative sampling in non-uniform control flow).
|
|
19
|
+
|
|
20
|
+
import * as THREE from 'three';
|
|
21
|
+
import {
|
|
22
|
+
abs,
|
|
23
|
+
cameraFar,
|
|
24
|
+
cameraNear,
|
|
25
|
+
cameraProjectionMatrix,
|
|
26
|
+
cameraViewMatrix,
|
|
27
|
+
clamp,
|
|
28
|
+
Discard,
|
|
29
|
+
dot,
|
|
30
|
+
float,
|
|
31
|
+
floor,
|
|
32
|
+
Fn,
|
|
33
|
+
frontFacing,
|
|
34
|
+
If,
|
|
35
|
+
int,
|
|
36
|
+
length,
|
|
37
|
+
mat3,
|
|
38
|
+
max,
|
|
39
|
+
min,
|
|
40
|
+
mix,
|
|
41
|
+
mod,
|
|
42
|
+
normalize,
|
|
43
|
+
normalLocal,
|
|
44
|
+
normalView,
|
|
45
|
+
pow,
|
|
46
|
+
screenCoordinate,
|
|
47
|
+
select,
|
|
48
|
+
smoothstep,
|
|
49
|
+
texture,
|
|
50
|
+
transformNormalToView,
|
|
51
|
+
uniform,
|
|
52
|
+
uv,
|
|
53
|
+
varying,
|
|
54
|
+
vec2,
|
|
55
|
+
vec3,
|
|
56
|
+
vec4,
|
|
57
|
+
viewZToOrthographicDepth,
|
|
58
|
+
viewZToPerspectiveDepth,
|
|
59
|
+
} from 'three/tsl';
|
|
60
|
+
import { NodeMaterial } from 'three/webgpu';
|
|
61
|
+
import { attribute, positionLocal, positionWorld, modelWorldMatrix } from 'three/tsl';
|
|
62
|
+
|
|
63
|
+
import { DEFAULT_FUR_SETTINGS } from '../toon/settings/furSettings.js';
|
|
64
|
+
import { maxColorComponent } from './chunks/character-color.js';
|
|
65
|
+
import { sampleEnvironmentSunShadow } from './chunks/environment-sun-shadow.js';
|
|
66
|
+
import { updateToonStorageSkinning, withToonStorageSkinning } from './chunks/character-skinning.js';
|
|
67
|
+
|
|
68
|
+
export { updateToonStorageSkinning, withToonStorageSkinning };
|
|
69
|
+
|
|
70
|
+
// three r185's built-in SkinningNode keeps bone matrices in a uniform buffer,
|
|
71
|
+
// which exceeds GL_MAX_UNIFORM_BLOCK_SIZE for MMD-scale skeletons (>256
|
|
72
|
+
// bones) on the forced-WebGL2 backend. The mixin reroutes skinning to a
|
|
73
|
+
// storage/PBO path there; WebGPU keeps the built-in buffer path.
|
|
74
|
+
const ToonAnimeNodeMaterial = withToonStorageSkinning(NodeMaterial);
|
|
75
|
+
import { createHighlightsChunk } from './chunks/character-highlights.js';
|
|
76
|
+
import { createLightingChunk } from './chunks/character-lighting.js';
|
|
77
|
+
import { createMaterialMapChunk } from './chunks/character-material-maps.js';
|
|
78
|
+
import { createRolesChunk } from './chunks/character-roles.js';
|
|
79
|
+
import {
|
|
80
|
+
createLocalLightEvaluators,
|
|
81
|
+
getMainLightDirection,
|
|
82
|
+
getMainLightColor,
|
|
83
|
+
syncToonSceneLights,
|
|
84
|
+
toonSceneLights,
|
|
85
|
+
} from './chunks/character-scene-lights.js';
|
|
86
|
+
import { createShadowColorChunk } from './chunks/character-shadow-color.js';
|
|
87
|
+
|
|
88
|
+
export { syncToonSceneLights, toonSceneLights };
|
|
89
|
+
|
|
90
|
+
const fallbackWhiteTexture = /* created lazily */ (() => {
|
|
91
|
+
let tex = null;
|
|
92
|
+
return () => {
|
|
93
|
+
if (!tex) {
|
|
94
|
+
const data = new Uint8Array([255, 255, 255, 255]);
|
|
95
|
+
tex = new THREE.DataTexture(data, 1, 1);
|
|
96
|
+
tex.needsUpdate = true;
|
|
97
|
+
}
|
|
98
|
+
return tex;
|
|
99
|
+
};
|
|
100
|
+
})();
|
|
101
|
+
|
|
102
|
+
function boolUniform(value) {
|
|
103
|
+
return uniform(Boolean(value), 'bool');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function intUniform(value) {
|
|
107
|
+
return uniform(Math.trunc(value ?? 0), 'int');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 4x4 Bayer threshold, closed form of the GLSL table:
|
|
111
|
+
// t = (4*M2[x&1][y&1] + M2[(x>>1)&1][(y>>1)&1] + 1) / 17, M2 = [[0,2],[3,1]].
|
|
112
|
+
function bayerThreshold(pixel) {
|
|
113
|
+
const m2 = (a, b) => select(a.equal(0.0), select(b.equal(0.0), float(0.0), float(2.0)), select(b.equal(0.0), float(3.0), float(1.0)));
|
|
114
|
+
const fx = mod(pixel.x, 4.0);
|
|
115
|
+
const fy = mod(pixel.y, 4.0);
|
|
116
|
+
const x1 = mod(fx, 2.0);
|
|
117
|
+
const y1 = mod(fy, 2.0);
|
|
118
|
+
const x2 = mod(floor(fx.div(2.0)), 2.0);
|
|
119
|
+
const y2 = mod(floor(fy.div(2.0)), 2.0);
|
|
120
|
+
return m2(x1, y1).mul(4.0).add(m2(x2, y2)).add(1.0).div(17.0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function createAnimeNodeMaterial(params) {
|
|
124
|
+
const {
|
|
125
|
+
alphaBlend = false,
|
|
126
|
+
alphaTest = -1.0,
|
|
127
|
+
averageShadow,
|
|
128
|
+
averageShadowSettings,
|
|
129
|
+
base = null,
|
|
130
|
+
baseColor = new THREE.Color(1, 1, 1),
|
|
131
|
+
baseMapSaturation = 1,
|
|
132
|
+
celShadeSettings,
|
|
133
|
+
contactShadow,
|
|
134
|
+
debugOutputMode = 0,
|
|
135
|
+
depthWrite = null,
|
|
136
|
+
eyeHighlight,
|
|
137
|
+
faceHeadSpaceModeValue = 1,
|
|
138
|
+
faceLightingSettings,
|
|
139
|
+
glitter,
|
|
140
|
+
hairHighlight,
|
|
141
|
+
indirectLight,
|
|
142
|
+
indirectLightSettings,
|
|
143
|
+
isEye = false,
|
|
144
|
+
isFace = false,
|
|
145
|
+
isFurShell = false,
|
|
146
|
+
isHair = false,
|
|
147
|
+
isOutline = false,
|
|
148
|
+
isSkin = false,
|
|
149
|
+
localLight,
|
|
150
|
+
materialRole = 0,
|
|
151
|
+
opacity = 1,
|
|
152
|
+
outline,
|
|
153
|
+
perspectiveRemovalSettings,
|
|
154
|
+
resolvedMaterialMaps,
|
|
155
|
+
rimLight,
|
|
156
|
+
sceneShadow,
|
|
157
|
+
sceneShadowSettings,
|
|
158
|
+
selfShadow,
|
|
159
|
+
selfShadowSettings,
|
|
160
|
+
shadowColorSettings,
|
|
161
|
+
side = THREE.DoubleSide,
|
|
162
|
+
skinToneSettings,
|
|
163
|
+
sourceBaseColor = new THREE.Color(1, 1, 1),
|
|
164
|
+
specular,
|
|
165
|
+
sticker,
|
|
166
|
+
transparent = false,
|
|
167
|
+
} = params;
|
|
168
|
+
|
|
169
|
+
const maps = resolvedMaterialMaps;
|
|
170
|
+
|
|
171
|
+
// Compile-time graph shape — mirrors the GLSL defines, except hasDebugViews:
|
|
172
|
+
// the debug select-chain always compiles in (it adds no bindings — all its
|
|
173
|
+
// inputs are already in the graph) so setToonDebugOutput stays a pure
|
|
174
|
+
// uniform write on this backend instead of a define flip + recompile.
|
|
175
|
+
const flags = {
|
|
176
|
+
hasAoMap: Boolean(maps.hasAoMap),
|
|
177
|
+
hasDebugViews: true,
|
|
178
|
+
hasDetailMap: Boolean(maps.hasDetailMap),
|
|
179
|
+
hasEmissiveMap: Boolean(maps.hasEmissiveMap),
|
|
180
|
+
hasEyeHighlightMask: Boolean(eyeHighlight.maskMap),
|
|
181
|
+
hasGlitter: Boolean(glitter.enabled),
|
|
182
|
+
hasHairHighlightMask: Boolean(hairHighlight.maskMap),
|
|
183
|
+
hasMatcapMap: Boolean(maps.hasMatcapMap),
|
|
184
|
+
hasMetalnessMap: Boolean(maps.hasMetalnessMap),
|
|
185
|
+
hasNormalMap: Boolean(maps.hasNormalMap),
|
|
186
|
+
hasRampMap: Boolean(maps.hasRampMap),
|
|
187
|
+
hasRoughnessMap: Boolean(maps.hasRoughnessMap),
|
|
188
|
+
hasSpecularColorMap: Boolean(maps.hasSpecularColorMap),
|
|
189
|
+
hasSpecularMask: Boolean(specular.maskMap),
|
|
190
|
+
hasSticker: Boolean(sticker.enabled),
|
|
191
|
+
isFurShell: Boolean(isFurShell),
|
|
192
|
+
isOutlinePass: Boolean(isOutline),
|
|
193
|
+
useAverageShadowMeasure: (averageShadowSettings.measuredBlend ?? 0) > 0,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// ---- Uniforms (GLSL names; UniformNodes expose `.value` like
|
|
197
|
+
// ShaderMaterial uniform entries) ----
|
|
198
|
+
const u = {
|
|
199
|
+
base: texture(base || fallbackWhiteTexture()),
|
|
200
|
+
gCutoff: uniform(-1.0),
|
|
201
|
+
aCutoff: uniform(alphaTest),
|
|
202
|
+
alphaBlend: boolUniform(alphaBlend),
|
|
203
|
+
|
|
204
|
+
baseColor: uniform(new THREE.Vector4(baseColor.r, baseColor.g, baseColor.b, opacity)),
|
|
205
|
+
sourceBaseColor: uniform(new THREE.Vector4(sourceBaseColor.r, sourceBaseColor.g, sourceBaseColor.b, opacity)),
|
|
206
|
+
baseColor2: uniform(new THREE.Vector4(1, 1, 1, 1)),
|
|
207
|
+
baseMapBrightness: uniform(1.0),
|
|
208
|
+
baseMapHue: uniform(0.0),
|
|
209
|
+
baseMapSaturation: uniform(baseMapSaturation),
|
|
210
|
+
baseMapValue: uniform(1.0),
|
|
211
|
+
baseMapGamma: uniform(1.0),
|
|
212
|
+
|
|
213
|
+
isOutline: boolUniform(isOutline),
|
|
214
|
+
isSkin: boolUniform(isSkin),
|
|
215
|
+
isFace: boolUniform(isFace),
|
|
216
|
+
isEye: boolUniform(isEye),
|
|
217
|
+
isHair: boolUniform(isHair),
|
|
218
|
+
materialRole: intUniform(materialRole),
|
|
219
|
+
debugOutputMode: intUniform(debugOutputMode),
|
|
220
|
+
|
|
221
|
+
enableShadowColor: boolUniform(shadowColorSettings.enabled),
|
|
222
|
+
celShadeMidPoint: uniform(celShadeSettings.bodyCelMidPoint),
|
|
223
|
+
celShadeSoftness: uniform(celShadeSettings.bodyCelSoftness),
|
|
224
|
+
celShadeMidPointForFaceArea: uniform(faceLightingSettings.faceCelMidPoint),
|
|
225
|
+
celShadeSoftnessForFaceArea: uniform(faceLightingSettings.faceCelSoftness),
|
|
226
|
+
mainLightIgnoreCelShade: uniform(celShadeSettings.bodyMainLightIgnoreCelShade),
|
|
227
|
+
mainLightIgnoreCelShadeForFaceArea: uniform(faceLightingSettings.faceMainLightIgnoreCelShade),
|
|
228
|
+
faceSceneShadowStrength: uniform(faceLightingSettings.faceSceneShadowStrength),
|
|
229
|
+
faceLocalLightLift: uniform(faceLightingSettings.faceLocalLightLift),
|
|
230
|
+
faceNormalProxyBlend: uniform(faceLightingSettings.faceNormalProxyBlend),
|
|
231
|
+
faceProxyNormalObject: uniform(new THREE.Vector3(...faceLightingSettings.faceProxyNormal).normalize()),
|
|
232
|
+
|
|
233
|
+
selfShadowTintColor: uniform(shadowColorSettings.selfShadowTintColor.clone?.() ?? new THREE.Color(shadowColorSettings.selfShadowTintColor)),
|
|
234
|
+
selfShadowAreaHSVStrength: uniform(shadowColorSettings.selfShadowAreaHSVStrength),
|
|
235
|
+
selfShadowAreaHueOffset: uniform(shadowColorSettings.selfShadowAreaHueOffset),
|
|
236
|
+
selfShadowAreaSaturationBoost: uniform(shadowColorSettings.selfShadowAreaSaturationBoost),
|
|
237
|
+
selfShadowAreaValueMul: uniform(shadowColorSettings.selfShadowAreaValueMul),
|
|
238
|
+
selfShadowAlbedoMulStrength: uniform(shadowColorSettings.selfShadowAlbedoMulStrength),
|
|
239
|
+
|
|
240
|
+
litToShadowTransitionAreaIntensity: uniform(shadowColorSettings.transitionAreaIntensity),
|
|
241
|
+
litToShadowTransitionAreaTintColor: uniform(shadowColorSettings.transitionAreaTintColor.clone?.() ?? new THREE.Color(shadowColorSettings.transitionAreaTintColor)),
|
|
242
|
+
litToShadowTransitionAreaHueOffset: uniform(shadowColorSettings.transitionAreaHueOffset),
|
|
243
|
+
litToShadowTransitionAreaSaturationBoost: uniform(shadowColorSettings.transitionAreaSaturationBoost),
|
|
244
|
+
litToShadowTransitionAreaValueMul: uniform(shadowColorSettings.transitionAreaValueMul),
|
|
245
|
+
|
|
246
|
+
overrideBySkinShadowTintColor: uniform(skinToneSettings.skinShadowTintStrength),
|
|
247
|
+
skinShadowTintColor: uniform(skinToneSettings.skinShadowTint.clone?.() ?? new THREE.Color(skinToneSettings.skinShadowTint)),
|
|
248
|
+
skinShadowBrightness: uniform(skinToneSettings.skinShadowBrightness),
|
|
249
|
+
skinShadowSaturation: uniform(skinToneSettings.skinShadowSaturation),
|
|
250
|
+
skinShadowTintColor2: uniform(new THREE.Color(1, 1, 1)),
|
|
251
|
+
overrideByFaceShadowTintColor: uniform(skinToneSettings.faceShadowTintStrength),
|
|
252
|
+
faceShadowTintColor: uniform(skinToneSettings.faceShadowTint.clone?.() ?? new THREE.Color(skinToneSettings.faceShadowTint)),
|
|
253
|
+
faceShadowBrightness: uniform(skinToneSettings.faceShadowBrightness),
|
|
254
|
+
faceShadowSaturation: uniform(skinToneSettings.faceShadowSaturation),
|
|
255
|
+
faceShadowTintColor2: uniform(new THREE.Color(1, 1, 1)),
|
|
256
|
+
lowSaturationFallbackColor: uniform(
|
|
257
|
+
shadowColorSettings.lowSaturationFallbackColor.clone?.() ??
|
|
258
|
+
new THREE.Vector4().copy(shadowColorSettings.lowSaturationFallbackColor),
|
|
259
|
+
),
|
|
260
|
+
|
|
261
|
+
ambientTint: uniform(indirectLightSettings.ambientTint.clone()),
|
|
262
|
+
indirectLightIntensity: uniform(indirectLight.intensity),
|
|
263
|
+
minimumIndirectLight: uniform(indirectLight.minimumIndirectLight),
|
|
264
|
+
mainLightMaxContribution: uniform(1.08),
|
|
265
|
+
skinMinimumIndirectLight: uniform(skinToneSettings.skinMinimumIndirectLight),
|
|
266
|
+
faceMinimumIndirectLight: uniform(skinToneSettings.faceMinimumIndirectLight),
|
|
267
|
+
skinMaxDirectLight: uniform(skinToneSettings.skinMaxDirectLight),
|
|
268
|
+
faceMaxDirectLight: uniform(skinToneSettings.faceMaxDirectLight),
|
|
269
|
+
receivedShadowStrength: uniform(sceneShadow.strength),
|
|
270
|
+
receivedShadowMinLight: uniform(sceneShadow.minLight),
|
|
271
|
+
receivedShadowAreaStrength: uniform(sceneShadowSettings.shadowAreaStrength),
|
|
272
|
+
averageShadowStrength: uniform(averageShadow.strength),
|
|
273
|
+
averageShadowMinLight: uniform(averageShadow.minLight),
|
|
274
|
+
averageShadowSoftness: uniform(averageShadowSettings.softness),
|
|
275
|
+
selfShadowSourceMode: intUniform(selfShadowSettings.sourceMode),
|
|
276
|
+
selfShadowStrength: uniform(selfShadow.strength),
|
|
277
|
+
selfShadowMinLight: uniform(selfShadow.minLight),
|
|
278
|
+
selfShadowAreaStrength: uniform(selfShadowSettings.shadowAreaStrength),
|
|
279
|
+
environmentIndirectLight: uniform(indirectLightSettings.environmentIndirectLight),
|
|
280
|
+
localLightIntensity: uniform(localLight.intensity),
|
|
281
|
+
localLightMaxContribution: uniform(localLight.maxContribution),
|
|
282
|
+
localLightShadowLift: uniform(localLight.shadowLift),
|
|
283
|
+
hemisphereLightIntensity: uniform(indirectLightSettings.hemisphereLightIntensity),
|
|
284
|
+
|
|
285
|
+
useSpecular: boolUniform(specular.enabled),
|
|
286
|
+
specularIntensity: uniform(specular.intensity),
|
|
287
|
+
specularColor: uniform(specular.color.clone()),
|
|
288
|
+
specularAreaRemapMidPoint: uniform(specular.midPoint),
|
|
289
|
+
specularAreaRemapRange: uniform(specular.range),
|
|
290
|
+
specularPower: uniform(specular.power),
|
|
291
|
+
specularShowInShadowArea: uniform(specular.showInShadowArea),
|
|
292
|
+
useSpecularMask: boolUniform(specular.useMask),
|
|
293
|
+
specularMaskStrength: uniform(specular.maskStrength),
|
|
294
|
+
specularMaskChannel: intUniform(specular.maskChannel),
|
|
295
|
+
|
|
296
|
+
useRimLight: boolUniform(rimLight.enabled),
|
|
297
|
+
rimTintColor: uniform(rimLight.tintColor.clone()),
|
|
298
|
+
rimIntensity: uniform(rimLight.intensity),
|
|
299
|
+
rimMidPoint: uniform(rimLight.midPoint),
|
|
300
|
+
rimSoftness: uniform(rimLight.softness),
|
|
301
|
+
rimMixWithBaseMapColor: uniform(rimLight.mixWithBaseMapColor),
|
|
302
|
+
rimBlockByShadow: uniform(rimLight.blockByShadow),
|
|
303
|
+
rimLightMode: intUniform(rimLight.modeValue),
|
|
304
|
+
rimDepthWidth: uniform(rimLight.depthWidth),
|
|
305
|
+
rimDepthThresholdOffset: uniform(rimLight.depthThresholdOffset),
|
|
306
|
+
rimDepthFadeRange: uniform(rimLight.depthFadeRange),
|
|
307
|
+
rimDepthSafeDistance: uniform(rimLight.depthSafeDistance),
|
|
308
|
+
rimDepthCloseWidthReduce: uniform(rimLight.depthCloseWidthReduce),
|
|
309
|
+
rimDepthDottedLineFix: boolUniform(rimLight.depthDottedLineFix),
|
|
310
|
+
rimDepthMask3D: boolUniform(rimLight.depthMask3D),
|
|
311
|
+
rimDepthFadeStartDistance: uniform(rimLight.depthFadeStartDistance),
|
|
312
|
+
rimDepthFadeEndDistance: uniform(rimLight.depthFadeEndDistance),
|
|
313
|
+
|
|
314
|
+
sceneDepthReady: boolUniform(false),
|
|
315
|
+
sceneDepthTexture: texture(fallbackWhiteTexture()),
|
|
316
|
+
sceneDepthResolution: uniform(new THREE.Vector2(1, 1)),
|
|
317
|
+
cameraNearPlane: uniform(0.1),
|
|
318
|
+
cameraFarPlane: uniform(100),
|
|
319
|
+
cameraProjection11: uniform(2.75),
|
|
320
|
+
|
|
321
|
+
contactShadowStrength: uniform(contactShadow.strength),
|
|
322
|
+
contactShadowWidth: uniform(contactShadow.width),
|
|
323
|
+
contactShadowThresholdOffset: uniform(contactShadow.thresholdOffset),
|
|
324
|
+
contactShadowFadeRange: uniform(contactShadow.fadeRange),
|
|
325
|
+
contactShadowFaceHeadUpBlend: uniform(contactShadow.faceHeadUpBlend),
|
|
326
|
+
|
|
327
|
+
charSelfShadowReady: boolUniform(false),
|
|
328
|
+
charSelfShadowMap: texture(fallbackWhiteTexture()),
|
|
329
|
+
charSelfShadowMatrix: uniform(new THREE.Matrix4()),
|
|
330
|
+
charSelfShadowLightDirection: uniform(new THREE.Vector3(0, 1, 0)),
|
|
331
|
+
charSelfShadowTexelSize: uniform(1 / 2048),
|
|
332
|
+
charSelfShadowQuality: uniform(2),
|
|
333
|
+
charSelfShadowSharpen: uniform(0.25),
|
|
334
|
+
charSelfShadowNormalBias: uniform(0.02),
|
|
335
|
+
charSelfShadowDepthBias: uniform(0.02),
|
|
336
|
+
charSelfShadowFadeDistance: uniform(20),
|
|
337
|
+
charSelfShadowNdotLFix: boolUniform(true),
|
|
338
|
+
|
|
339
|
+
headDataReady: boolUniform(false),
|
|
340
|
+
headPositionWS: uniform(new THREE.Vector3()),
|
|
341
|
+
headForwardWS: uniform(new THREE.Vector3(0, 0, 1)),
|
|
342
|
+
headUpWS: uniform(new THREE.Vector3(0, 1, 0)),
|
|
343
|
+
faceHeadSpaceMode: intUniform(faceHeadSpaceModeValue),
|
|
344
|
+
faceSphereBlend: uniform(faceLightingSettings.faceSphereBlend),
|
|
345
|
+
|
|
346
|
+
averageShadowMeasureReady: boolUniform(false),
|
|
347
|
+
averageShadowMeasureTexture: texture(fallbackWhiteTexture()),
|
|
348
|
+
averageShadowMeasureSlot: uniform(0.5),
|
|
349
|
+
averageShadowMeasuredBlend: uniform(averageShadowSettings.measuredBlend ?? 0),
|
|
350
|
+
|
|
351
|
+
ditherOpacity: uniform(1),
|
|
352
|
+
edgeAntiAliasStrength: uniform(celShadeSettings.edgeAntiAliasStrength ?? 1),
|
|
353
|
+
specularDirectionMode: intUniform(specular.directionModeValue ?? 0),
|
|
354
|
+
|
|
355
|
+
useGlitter: boolUniform(glitter.enabled),
|
|
356
|
+
glitterIntensity: uniform(glitter.intensity),
|
|
357
|
+
glitterDensity: uniform(glitter.density),
|
|
358
|
+
glitterSize: uniform(glitter.size),
|
|
359
|
+
glitterRandomNormalStrength: uniform(glitter.randomNormalStrength),
|
|
360
|
+
glitterShowInShadowArea: uniform(glitter.showInShadowArea),
|
|
361
|
+
glitterUvChannel: intUniform(glitter.uvChannel),
|
|
362
|
+
|
|
363
|
+
useSticker: boolUniform(sticker.enabled),
|
|
364
|
+
stickerBlendMode: intUniform(sticker.blendModeValue),
|
|
365
|
+
stickerStrength: uniform(sticker.strength),
|
|
366
|
+
stickerRepeat: uniform(new THREE.Vector2(...sticker.repeat)),
|
|
367
|
+
stickerOffset: uniform(new THREE.Vector2(...sticker.offset)),
|
|
368
|
+
stickerUvChannel: intUniform(sticker.uvChannel),
|
|
369
|
+
|
|
370
|
+
perspectiveRemovalAmount: uniform(perspectiveRemovalSettings.amount ?? 0),
|
|
371
|
+
perspectiveRemovalRadius: uniform(perspectiveRemovalSettings.radius ?? 1.4),
|
|
372
|
+
perspectiveRemovalStartHeight: uniform(perspectiveRemovalSettings.startHeight ?? 0),
|
|
373
|
+
perspectiveRemovalEndHeight: uniform(perspectiveRemovalSettings.endHeight ?? 1),
|
|
374
|
+
|
|
375
|
+
// Fur uniforms live on every material so fur shell clones can vary
|
|
376
|
+
// furLayer per shell (same as the GLSL uniform block).
|
|
377
|
+
furLayer: uniform(1),
|
|
378
|
+
furLength: uniform(DEFAULT_FUR_SETTINGS.length),
|
|
379
|
+
furGravity: uniform(DEFAULT_FUR_SETTINGS.gravity),
|
|
380
|
+
furDensity: uniform(DEFAULT_FUR_SETTINGS.density),
|
|
381
|
+
furRootOffset: uniform(DEFAULT_FUR_SETTINGS.rootOffset),
|
|
382
|
+
furRootShade: uniform(DEFAULT_FUR_SETTINGS.rootShade),
|
|
383
|
+
|
|
384
|
+
useHairHighlight: boolUniform(hairHighlight.enabled),
|
|
385
|
+
hairHighlightDirection: uniform(new THREE.Vector3(...hairHighlight.direction)),
|
|
386
|
+
hairHighlightIntensity: uniform(hairHighlight.intensity),
|
|
387
|
+
hairHighlightMaskChannel: intUniform(hairHighlight.maskChannel),
|
|
388
|
+
hairHighlightMaskStrength: uniform(hairHighlight.maskStrength),
|
|
389
|
+
hairHighlightMode: intUniform(hairHighlight.modeValue),
|
|
390
|
+
hairHighlightShadowFloor: uniform(hairHighlight.shadowFloor),
|
|
391
|
+
hairHighlightSideBandPower: uniform(hairHighlight.sideBandPower),
|
|
392
|
+
hairHighlightStrandPower: uniform(hairHighlight.strandPower),
|
|
393
|
+
hairHighlightUvBandAxis: intUniform(hairHighlight.uvBandAxis),
|
|
394
|
+
hairHighlightUvBandCenter: uniform(hairHighlight.uvBandCenter),
|
|
395
|
+
hairHighlightUvBandHalfWidth: uniform(hairHighlight.uvBandHalfWidth),
|
|
396
|
+
useHairHighlightMask: boolUniform(hairHighlight.useMask),
|
|
397
|
+
useEyeHighlight: boolUniform(eyeHighlight.enabled),
|
|
398
|
+
useEyeHighlightMask: boolUniform(eyeHighlight.useMask),
|
|
399
|
+
eyeHighlightColor: uniform(eyeHighlight.color.clone()),
|
|
400
|
+
eyeHighlightIntensity: uniform(eyeHighlight.intensity),
|
|
401
|
+
eyeHighlightMaskChannel: intUniform(eyeHighlight.maskChannel),
|
|
402
|
+
eyeHighlightMaskStrength: uniform(eyeHighlight.maskStrength),
|
|
403
|
+
eyeHighlightPower: uniform(eyeHighlight.power),
|
|
404
|
+
eyeHighlightShowInShadowArea: uniform(eyeHighlight.showInShadowArea),
|
|
405
|
+
|
|
406
|
+
hasMaterialNormalMap: boolUniform(maps.hasNormalMap),
|
|
407
|
+
useMaterialNormalMap: boolUniform(maps.hasNormalMap && maps.normalStrength > 0),
|
|
408
|
+
materialNormalScale: uniform(maps.normalScale),
|
|
409
|
+
materialNormalStrength: uniform(maps.normalStrength),
|
|
410
|
+
hasMaterialAoMap: boolUniform(maps.hasAoMap),
|
|
411
|
+
materialAoStrength: uniform(maps.aoStrength),
|
|
412
|
+
hasMaterialDetailMap: boolUniform(maps.hasDetailMap),
|
|
413
|
+
materialDetailRepeat: uniform(maps.detailRepeat),
|
|
414
|
+
materialDetailStrength: uniform(maps.detailStrength),
|
|
415
|
+
hasMaterialEmissiveMap: boolUniform(maps.hasEmissiveMap),
|
|
416
|
+
materialEmissiveColor: uniform(maps.emissiveColor),
|
|
417
|
+
materialEmissiveStrength: uniform(maps.emissiveStrength),
|
|
418
|
+
hasMaterialMatcapMap: boolUniform(maps.hasMatcapMap),
|
|
419
|
+
materialMatcapStrength: uniform(maps.matcapStrength),
|
|
420
|
+
hasMaterialMetalnessMap: boolUniform(maps.hasMetalnessMap),
|
|
421
|
+
materialMetalness: uniform(maps.metalness),
|
|
422
|
+
materialMetalnessStrength: uniform(maps.metalnessStrength),
|
|
423
|
+
hasMaterialRampMap: boolUniform(maps.hasRampMap),
|
|
424
|
+
materialRampStrength: uniform(maps.rampStrength),
|
|
425
|
+
hasMaterialRoughnessMap: boolUniform(maps.hasRoughnessMap),
|
|
426
|
+
materialRoughness: uniform(maps.roughness),
|
|
427
|
+
materialRoughnessStrength: uniform(maps.roughnessStrength),
|
|
428
|
+
hasMaterialSpecularColorMap: boolUniform(maps.hasSpecularColorMap),
|
|
429
|
+
materialSpecularColorStrength: uniform(maps.specularColorStrength),
|
|
430
|
+
|
|
431
|
+
outlineTintColor: uniform(outline.tintColor.clone()),
|
|
432
|
+
outlineTintColorSkinAreaOverride: uniform(outline.skinTintColor.clone()),
|
|
433
|
+
outlineSkinAreaOverrideStrength: uniform(outline.skinTintStrength),
|
|
434
|
+
outlineLightingMix: uniform(outline.lightingMix),
|
|
435
|
+
outlineMinBrightness: uniform(outline.minBrightness),
|
|
436
|
+
outlineMaxBrightness: uniform(outline.maxBrightness),
|
|
437
|
+
|
|
438
|
+
outlineDepthOffset: uniform(outline.depthOffset),
|
|
439
|
+
outlineThickness: uniform(outline.width),
|
|
440
|
+
outlineScreenSpaceFix: uniform(outline.screenSpaceWidth ?? 0),
|
|
441
|
+
outlineReferenceDistance: uniform(outline.referenceDistance ?? 4),
|
|
442
|
+
outlineReferenceProjection11: uniform(1 / Math.tan(THREE.MathUtils.degToRad((outline.referenceFov ?? 40) / 2))),
|
|
443
|
+
outlineWidthFadeDistance: uniform(outline.widthFadeDistance ?? 12),
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
// Optional-map texture nodes (only created when the map exists — the
|
|
447
|
+
// GLSL-define analog). Also registered in `u` so uniform-name `.value`
|
|
448
|
+
// writes keep working for texture swaps.
|
|
449
|
+
const tex = { sceneDepthTexture: u.sceneDepthTexture, charSelfShadowMap: u.charSelfShadowMap, averageShadowMeasureTexture: u.averageShadowMeasureTexture };
|
|
450
|
+
const registerMap = (name, map) => {
|
|
451
|
+
const node = texture(map);
|
|
452
|
+
u[name] = node;
|
|
453
|
+
tex[name] = node;
|
|
454
|
+
};
|
|
455
|
+
if (flags.hasSpecularMask) registerMap('specularMaskMap', specular.maskMap);
|
|
456
|
+
if (flags.hasHairHighlightMask) registerMap('hairHighlightMaskMap', hairHighlight.maskMap);
|
|
457
|
+
if (flags.hasEyeHighlightMask) registerMap('eyeHighlightMaskMap', eyeHighlight.maskMap);
|
|
458
|
+
if (flags.hasNormalMap) registerMap('materialNormalMap', maps.normalMap);
|
|
459
|
+
if (flags.hasAoMap) registerMap('materialAoMap', maps.aoMap);
|
|
460
|
+
if (flags.hasDetailMap) registerMap('materialDetailMap', maps.detailMap);
|
|
461
|
+
if (flags.hasEmissiveMap) registerMap('materialEmissiveMap', maps.emissiveMap);
|
|
462
|
+
if (flags.hasMatcapMap) registerMap('materialMatcapMap', maps.matcapMap);
|
|
463
|
+
if (flags.hasMetalnessMap) registerMap('materialMetalnessMap', maps.metalnessMap);
|
|
464
|
+
if (flags.hasRampMap) registerMap('materialRampMap', maps.rampMap);
|
|
465
|
+
if (flags.hasRoughnessMap) registerMap('materialRoughnessMap', maps.roughnessMap);
|
|
466
|
+
if (flags.hasSpecularColorMap) registerMap('materialSpecularColorMap', maps.specularColorMap);
|
|
467
|
+
if (flags.hasSticker) registerMap('stickerMap', sticker.map);
|
|
468
|
+
|
|
469
|
+
const material = new ToonAnimeNodeMaterial();
|
|
470
|
+
material.name = 'ToonAnimeNode';
|
|
471
|
+
material.lights = false;
|
|
472
|
+
material.fog = false;
|
|
473
|
+
material.side = isOutline ? THREE.BackSide : side;
|
|
474
|
+
material.transparent = alphaBlend || transparent || opacity < 1;
|
|
475
|
+
material.alphaTest = 0; // alpha cutout is in-graph (aCutoff), like the GLSL
|
|
476
|
+
material.depthWrite = depthWrite ?? (!alphaBlend && !transparent);
|
|
477
|
+
if (isOutline) {
|
|
478
|
+
material.depthTest = outline.depthTest;
|
|
479
|
+
material.depthWrite = outline.depthWrite;
|
|
480
|
+
material.polygonOffset = outline.polygonOffset;
|
|
481
|
+
material.polygonOffsetFactor = outline.polygonOffsetFactor;
|
|
482
|
+
material.polygonOffsetUnits = outline.polygonOffsetUnits;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ---- Vertex stage ----
|
|
486
|
+
// positionWorld/normalLocal reflect skinning + morphs (NodeMaterial runs
|
|
487
|
+
// setupPosition before vertexNode is evaluated). Varyings are declared up
|
|
488
|
+
// front and assigned imperatively inside the single vertexNode Fn — the
|
|
489
|
+
// GLSL builder cannot stack VarNodes reached only through varying inputs.
|
|
490
|
+
const vWorldPosition = varying(vec3(), 'vToonWorldPosition');
|
|
491
|
+
const vWorldNormal = varying(vec3(), 'vToonWorldNormal');
|
|
492
|
+
const vFaceProxyNormal = varying(vec3(), 'vToonFaceProxyNormal');
|
|
493
|
+
const vViewPos = varying(vec4(), 'vToonViewPos');
|
|
494
|
+
const vViewDir = varying(vec3(), 'vToonViewDir');
|
|
495
|
+
|
|
496
|
+
material.vertexNode = Fn(() => {
|
|
497
|
+
const worldNormal = normalize(mat3(modelWorldMatrix).mul(normalLocal)).toVar();
|
|
498
|
+
const worldPos = positionWorld.toVar();
|
|
499
|
+
if (flags.isFurShell) {
|
|
500
|
+
// Shell fur: push outward along the (skinned) world normal; gravity
|
|
501
|
+
// sags the direction toward world-down more at the tip.
|
|
502
|
+
const furDirection = vec3(
|
|
503
|
+
worldNormal.x,
|
|
504
|
+
worldNormal.y.sub(u.furGravity.mul(u.furLayer)),
|
|
505
|
+
worldNormal.z,
|
|
506
|
+
);
|
|
507
|
+
worldPos.addAssign(normalize(furDirection).mul(u.furLength).mul(u.furLayer));
|
|
508
|
+
}
|
|
509
|
+
vWorldPosition.assign(worldPos);
|
|
510
|
+
vWorldNormal.assign(worldNormal);
|
|
511
|
+
vFaceProxyNormal.assign(transformNormalToView(u.faceProxyNormalObject));
|
|
512
|
+
|
|
513
|
+
const viewPos = cameraViewMatrix.mul(vec4(worldPos, 1.0)).toVar();
|
|
514
|
+
if (flags.isOutlinePass) {
|
|
515
|
+
// Screen-space width correction (see anime.vert.glsl for rationale).
|
|
516
|
+
const p11 = cameraProjectionMatrix.element(1).y;
|
|
517
|
+
const orthographic = cameraProjectionMatrix.element(3).w.equal(1.0);
|
|
518
|
+
const viewDistance = select(
|
|
519
|
+
orthographic,
|
|
520
|
+
float(2.0).div(max(abs(p11), 1e-4)),
|
|
521
|
+
abs(viewPos.z),
|
|
522
|
+
);
|
|
523
|
+
const distanceFix = min(viewDistance, u.outlineWidthFadeDistance).div(max(u.outlineReferenceDistance, 1e-4));
|
|
524
|
+
const fovFix = select(orthographic, float(1.0), u.outlineReferenceProjection11.div(max(abs(p11), 1e-4)));
|
|
525
|
+
const widthFix = select(
|
|
526
|
+
u.outlineScreenSpaceFix.greaterThan(0.0),
|
|
527
|
+
mix(1.0, distanceFix.mul(fovFix), u.outlineScreenSpaceFix),
|
|
528
|
+
float(1.0),
|
|
529
|
+
);
|
|
530
|
+
viewPos.xyz.addAssign(normalize(normalView).mul(u.outlineThickness).mul(widthFix));
|
|
531
|
+
viewPos.z.subAssign(u.outlineDepthOffset);
|
|
532
|
+
}
|
|
533
|
+
vViewPos.assign(viewPos);
|
|
534
|
+
vViewDir.assign(normalize(viewPos.xyz.negate()));
|
|
535
|
+
|
|
536
|
+
const clipPosition = cameraProjectionMatrix.mul(viewPos).toVar();
|
|
537
|
+
|
|
538
|
+
// Perspective removal: flatten clip XY toward the head-center depth.
|
|
539
|
+
const perspective = cameraProjectionMatrix.element(3).w.notEqual(1.0);
|
|
540
|
+
If(u.perspectiveRemovalAmount.greaterThan(0.0).and(u.headDataReady).and(perspective), () => {
|
|
541
|
+
const sphereMask = clamp(
|
|
542
|
+
worldPos.sub(u.headPositionWS).length().div(max(u.perspectiveRemovalRadius, 1e-4)).oneMinus(),
|
|
543
|
+
0.0,
|
|
544
|
+
1.0,
|
|
545
|
+
);
|
|
546
|
+
const heightMask = clamp(
|
|
547
|
+
worldPos.y.sub(u.perspectiveRemovalStartHeight)
|
|
548
|
+
.div(max(u.perspectiveRemovalEndHeight.sub(u.perspectiveRemovalStartHeight), 1e-4)),
|
|
549
|
+
0.0,
|
|
550
|
+
1.0,
|
|
551
|
+
);
|
|
552
|
+
const removalAmount = u.perspectiveRemovalAmount.mul(sphereMask).mul(heightMask);
|
|
553
|
+
const centerViewZ = cameraViewMatrix.mul(vec4(u.headPositionWS, 1.0)).z;
|
|
554
|
+
const flattenedXY = clipPosition.xy.mul(abs(clipPosition.w)).div(max(abs(centerViewZ), 1e-4));
|
|
555
|
+
clipPosition.xy.assign(mix(clipPosition.xy, flattenedXY, removalAmount));
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
return clipPosition;
|
|
559
|
+
})();
|
|
560
|
+
|
|
561
|
+
// ---- Fragment stage ----
|
|
562
|
+
const vUv = uv();
|
|
563
|
+
const vUv2 = attribute('uv2', 'vec2');
|
|
564
|
+
const v = { vFaceProxyNormal, vUv, vUv2, vViewDir, vViewPos, vWorldNormal, vWorldPosition };
|
|
565
|
+
|
|
566
|
+
material.userData.createDepthColorVariant = () => {
|
|
567
|
+
const depthMaterial = new ToonAnimeNodeMaterial();
|
|
568
|
+
depthMaterial.name = `${material.name || 'ToonAnimeNode'}Depth`;
|
|
569
|
+
depthMaterial.lights = false;
|
|
570
|
+
depthMaterial.fog = false;
|
|
571
|
+
depthMaterial.side = material.side;
|
|
572
|
+
depthMaterial.transparent = false;
|
|
573
|
+
depthMaterial.depthWrite = true;
|
|
574
|
+
depthMaterial.depthTest = material.depthTest;
|
|
575
|
+
depthMaterial.isShadowPassMaterial = true;
|
|
576
|
+
depthMaterial.vertexNode = material.vertexNode;
|
|
577
|
+
depthMaterial.fragmentNode = Fn(() => {
|
|
578
|
+
const baseTex = u.base.sample(vUv).toVar();
|
|
579
|
+
const rawAlpha = baseTex.a.mul(u.baseColor.w).mul(u.baseColor2.w).toVar();
|
|
580
|
+
const greenCut = u.gCutoff.greaterThanEqual(0.0)
|
|
581
|
+
.and(baseTex.rgb.sub(vec3(0.0, 1.0, 0.0)).length().lessThan(u.gCutoff));
|
|
582
|
+
const alphaCut = u.aCutoff.greaterThanEqual(0.0).and(rawAlpha.lessThan(u.aCutoff));
|
|
583
|
+
Discard(greenCut.or(alphaCut));
|
|
584
|
+
|
|
585
|
+
const orthographic = cameraProjectionMatrix.element(3).w.equal(1.0);
|
|
586
|
+
const depth01 = select(
|
|
587
|
+
orthographic,
|
|
588
|
+
viewZToOrthographicDepth(vViewPos.z, cameraNear, cameraFar),
|
|
589
|
+
viewZToPerspectiveDepth(vViewPos.z, cameraNear, cameraFar),
|
|
590
|
+
);
|
|
591
|
+
return vec4(vec3(depth01), 1.0);
|
|
592
|
+
})();
|
|
593
|
+
return depthMaterial;
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
const lighting = createLightingChunk({
|
|
597
|
+
cameraViewMatrixNode: cameraViewMatrix,
|
|
598
|
+
flags,
|
|
599
|
+
frontFacingNode: frontFacing,
|
|
600
|
+
tex,
|
|
601
|
+
u,
|
|
602
|
+
v,
|
|
603
|
+
});
|
|
604
|
+
const materialMaps = createMaterialMapChunk({ flags, tex, u, v });
|
|
605
|
+
const shadowColorChunk = createShadowColorChunk({ u });
|
|
606
|
+
const highlights = createHighlightsChunk({ flags, tex, u, v, toonEdgeSmooth: lighting.toonEdgeSmooth });
|
|
607
|
+
const roles = createRolesChunk({ flags, u });
|
|
608
|
+
const localEvaluators = createLocalLightEvaluators({ localLightBand: lighting.localLightBand });
|
|
609
|
+
|
|
610
|
+
material.fragmentNode = Fn(() => {
|
|
611
|
+
const baseTex = u.base.sample(vUv).toVar();
|
|
612
|
+
const rawAlpha = baseTex.a.mul(u.baseColor.w).mul(u.baseColor2.w).toVar();
|
|
613
|
+
|
|
614
|
+
const greenCut = u.gCutoff.greaterThanEqual(0.0)
|
|
615
|
+
.and(baseTex.rgb.sub(vec3(0.0, 1.0, 0.0)).length().lessThan(u.gCutoff));
|
|
616
|
+
const alphaCut = u.aCutoff.greaterThanEqual(0.0).and(rawAlpha.lessThan(u.aCutoff));
|
|
617
|
+
Discard(greenCut.or(alphaCut));
|
|
618
|
+
|
|
619
|
+
// 4x4 Bayer screen-door fade (needs no sorting, keeps depth writes).
|
|
620
|
+
If(u.ditherOpacity.lessThan(1.0), () => {
|
|
621
|
+
const pixel = floor(screenCoordinate.xy);
|
|
622
|
+
Discard(u.ditherOpacity.lessThan(bayerThreshold(pixel)));
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
if (flags.isFurShell) {
|
|
626
|
+
// Shell fur strand cutout (see anime.frag.glsl).
|
|
627
|
+
const furUv = vUv.mul(u.furDensity).mul(100.0).toVar();
|
|
628
|
+
const furCellUv = furUv.fract().sub(0.5);
|
|
629
|
+
const furSeed3 = vec3(dot(floor(furUv), vec2(127.1, 311.7))).mul(vec3(0.1031, 0.103, 0.0973)).fract().toVar();
|
|
630
|
+
const furSeed = furSeed3.x.add(furSeed3.y).mul(furSeed3.z)
|
|
631
|
+
.add(dot(floor(furUv), vec2(0.013, 0.027))).fract();
|
|
632
|
+
const furNoise = clamp(furSeed.mul(length(furCellUv).mul(1.4).oneMinus()), 0.0, 1.0);
|
|
633
|
+
const furShift = u.furLayer.sub(u.furLayer.mul(u.furRootOffset)).add(u.furRootOffset);
|
|
634
|
+
const furAlpha = clamp(furNoise.sub(furShift.mul(abs(furShift)).mul(abs(furShift))), 0.0, 1.0);
|
|
635
|
+
Discard(clamp(furAlpha.mul(5.0).sub(2.0), 0.0, 1.0).lessThanEqual(0.0));
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const alpha = select(u.alphaBlend, rawAlpha, float(1.0)).toVar();
|
|
639
|
+
|
|
640
|
+
// Albedo edits (character-fragment-color.glsl).
|
|
641
|
+
const sourceAlbedo = baseTex.rgb.mul(u.sourceBaseColor.rgb).toVar();
|
|
642
|
+
const editedRgb = baseTex.rgb.mul(u.baseColor.rgb).mul(u.baseColor2.rgb).mul(u.baseMapBrightness);
|
|
643
|
+
const albedoVar = applyBaseColorEditNode(editedRgb, u).toVar();
|
|
644
|
+
albedoVar.assign(materialMaps.applyMaterialDetail(albedoVar));
|
|
645
|
+
|
|
646
|
+
if (flags.hasSticker) {
|
|
647
|
+
If(u.useSticker, () => {
|
|
648
|
+
const stickerUv = select(u.stickerUvChannel.equal(1), vUv2, vUv)
|
|
649
|
+
.mul(u.stickerRepeat).add(u.stickerOffset);
|
|
650
|
+
const stickerTexel = tex.stickerMap.sample(stickerUv);
|
|
651
|
+
const blendAmount = clamp(stickerTexel.a.mul(u.stickerStrength), 0.0, 1.0);
|
|
652
|
+
const added = albedoVar.add(stickerTexel.rgb.mul(blendAmount));
|
|
653
|
+
const multiplied = albedoVar.mul(mix(vec3(1.0), stickerTexel.rgb, blendAmount));
|
|
654
|
+
const mixed = mix(albedoVar, stickerTexel.rgb, blendAmount);
|
|
655
|
+
albedoVar.assign(select(u.stickerBlendMode.equal(1), added,
|
|
656
|
+
select(u.stickerBlendMode.equal(2), multiplied, mixed)));
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
const albedoFinal = albedoVar;
|
|
660
|
+
|
|
661
|
+
const N = normalize(normalView).mul(select(frontFacing, float(1.0), float(-1.0))).toVar();
|
|
662
|
+
N.assign(materialMaps.applyMaterialNormalMap(N));
|
|
663
|
+
N.assign(lighting.resolveLightingNormal(N));
|
|
664
|
+
|
|
665
|
+
const V = normalize(vViewDir).toVar();
|
|
666
|
+
const geometryPosition = vViewPos.xyz;
|
|
667
|
+
const L = getMainLightDirection().toVar();
|
|
668
|
+
const H = normalize(L.add(V)).toVar();
|
|
669
|
+
|
|
670
|
+
const NoV = clamp(dotNode(N, V), 0.0, 1.0).toVar();
|
|
671
|
+
const NoL = clamp(dotNode(N, L), 0.0, 1.0).toVar();
|
|
672
|
+
const NoH = clamp(dotNode(N, H), 0.0, 1.0).toVar();
|
|
673
|
+
|
|
674
|
+
// Scene shadow mask: getShadowMask() replacement — the shared sun-shadow
|
|
675
|
+
// pass publishes map+matrix; inert (1.0) until it runs, which matches a
|
|
676
|
+
// scene with no shadow-casting lights.
|
|
677
|
+
const sceneShadowVisibility = sampleEnvironmentSunShadow(vWorldPosition).toVar();
|
|
678
|
+
if (flags.useAverageShadowMeasure) {
|
|
679
|
+
If(u.averageShadowMeasureReady.and(u.averageShadowMeasuredBlend.greaterThan(0.0)), () => {
|
|
680
|
+
const measured = tex.averageShadowMeasureTexture
|
|
681
|
+
.sample(vec2(u.averageShadowMeasureSlot, 0.5)).level(0).x;
|
|
682
|
+
sceneShadowVisibility.assign(mix(sceneShadowVisibility, measured, u.averageShadowMeasuredBlend));
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const materialShadowStrength = u.receivedShadowStrength
|
|
687
|
+
.mul(select(u.isFace, u.faceSceneShadowStrength, float(1.0))).toVar();
|
|
688
|
+
const directVisibility = mix(1.0, max(sceneShadowVisibility, u.receivedShadowMinLight), materialShadowStrength);
|
|
689
|
+
const selfShadowVisibility = lighting.getCharacterSelfShadowVisibility(sceneShadowVisibility).toVar();
|
|
690
|
+
const selfShadowDirectVisibility = mix(1.0, max(selfShadowVisibility, u.selfShadowMinLight), u.selfShadowStrength);
|
|
691
|
+
const selfShadowArea = mix(1.0, selfShadowDirectVisibility, u.selfShadowAreaStrength);
|
|
692
|
+
const combinedDirectVisibility = directVisibility.mul(selfShadowArea);
|
|
693
|
+
const finalDirectVisibility = lighting.applyAverageShadowVisibility(
|
|
694
|
+
combinedDirectVisibility, sceneShadowVisibility, selfShadowVisibility,
|
|
695
|
+
).toVar();
|
|
696
|
+
|
|
697
|
+
const materialAo = flags.hasAoMap ? materialMaps.sampleMaterialAo().toVar() : float(1.0);
|
|
698
|
+
const materialRoughnessValue = materialMaps.sampleMaterialRoughness().toVar();
|
|
699
|
+
const materialMetalnessValue = materialMaps.sampleMaterialMetalness().toVar();
|
|
700
|
+
|
|
701
|
+
const { contactShadow: contactShadowValue, depthRim } = lighting.evaluateDepthEffects(N, L, NoL, NoV);
|
|
702
|
+
|
|
703
|
+
const finalShadowArea = lighting.calcCelShade(N, L)
|
|
704
|
+
.mul(contactShadowValue)
|
|
705
|
+
.mul(mix(1.0, finalDirectVisibility, u.receivedShadowAreaStrength))
|
|
706
|
+
.toVar();
|
|
707
|
+
const shadowColor = shadowColorChunk.calculateShadowColor(albedoFinal, finalShadowArea).toVar();
|
|
708
|
+
const lightColorIndependentLitColor = mix(shadowColor, albedoFinal, finalShadowArea)
|
|
709
|
+
.mul(mix(vec3(1.0), materialMaps.sampleMaterialRamp(finalShadowArea), u.materialRampStrength))
|
|
710
|
+
.toVar();
|
|
711
|
+
const rimMask = lighting.calculateRimMask(NoV, NoL, finalShadowArea, depthRim).toVar();
|
|
712
|
+
const specularArea = highlights.calculateSpecularArea(NoH, NoV, finalShadowArea, materialRoughnessValue).toVar();
|
|
713
|
+
const hairHighlightMaskValue = highlights.calculateHairHighlightMask(V, N, H, vUv, finalShadowArea).toVar();
|
|
714
|
+
const eyeHighlightMaskValue = highlights.calculateEyeHighlightMask(L, N, V, vUv).toVar();
|
|
715
|
+
|
|
716
|
+
// ---- Outline pass output ----
|
|
717
|
+
if (flags.isOutlinePass) {
|
|
718
|
+
const outlineTint = mix(
|
|
719
|
+
u.outlineTintColor,
|
|
720
|
+
u.outlineTintColorSkinAreaOverride,
|
|
721
|
+
select(u.isSkin, u.outlineSkinAreaOverrideStrength, float(0.0)),
|
|
722
|
+
);
|
|
723
|
+
const outlineColor = lightColorIndependentLitColor.mul(outlineTint).toVar();
|
|
724
|
+
const outlineLighting = mix(1.0, finalShadowArea.mul(0.45).add(0.55), u.outlineLightingMix);
|
|
725
|
+
outlineColor.mulAssign(outlineLighting);
|
|
726
|
+
const outlineBrightness = maxColorComponent(outlineColor);
|
|
727
|
+
const clampedBrightness = clamp(outlineBrightness, u.outlineMinBrightness, u.outlineMaxBrightness);
|
|
728
|
+
outlineColor.mulAssign(clampedBrightness.div(max(outlineBrightness, 0.001)));
|
|
729
|
+
|
|
730
|
+
if (flags.hasDebugViews) {
|
|
731
|
+
const debugColor = buildDebugColor({
|
|
732
|
+
albedo: albedoFinal, alpha, contactShadowValue, depthRim,
|
|
733
|
+
eyeHighlightMaskValue, finalDirectVisibility, finalShadowArea,
|
|
734
|
+
hairHighlightMaskValue, litColor: null, materialAo, materialMaps,
|
|
735
|
+
materialMetalnessValue, materialRoughnessValue, N, rimMask, roles,
|
|
736
|
+
sceneShadowVisibility, selfShadowVisibility, shadowColor,
|
|
737
|
+
sourceAlbedo, specularArea, u,
|
|
738
|
+
});
|
|
739
|
+
// mix + scalar mask instead of a conditional root: see the masked-sum
|
|
740
|
+
// note in buildDebugColor.
|
|
741
|
+
const debugMask = select(u.debugOutputMode.greaterThan(0), float(1.0), float(0.0));
|
|
742
|
+
return mix(vec4(outlineColor, alpha), debugColor, debugMask);
|
|
743
|
+
}
|
|
744
|
+
return vec4(outlineColor, alpha);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// ---- Lit accumulation ----
|
|
748
|
+
const mainLight = getMainLightColor(u.mainLightMaxContribution).toVar();
|
|
749
|
+
const directResult = mainLight.mul(lightColorIndependentLitColor).mul(finalDirectVisibility).toVar();
|
|
750
|
+
const maxDirectLight = select(u.isFace, u.faceMaxDirectLight, select(u.isSkin, u.skinMaxDirectLight, float(100.0)));
|
|
751
|
+
directResult.assign(min(directResult, albedoFinal.mul(maxDirectLight)));
|
|
752
|
+
|
|
753
|
+
const hemisphereFill = localEvaluators.evaluateHemisphereFill(N, u.hemisphereLightIntensity);
|
|
754
|
+
const ambientSource = max(toonSceneLights.ambientLightColor.add(hemisphereFill), vec3(u.environmentIndirectLight));
|
|
755
|
+
const indirectResult = ambientSource.mul(u.ambientTint).mul(u.indirectLightIntensity)
|
|
756
|
+
.mul(mix(shadowColor, albedoFinal, max(finalShadowArea, u.minimumIndirectLight)));
|
|
757
|
+
const color = directResult.add(indirectResult).toVar();
|
|
758
|
+
|
|
759
|
+
const { localLight: localLightValue, strongestLocalLight } = localEvaluators.evaluateLocalLightFill(
|
|
760
|
+
N, geometryPosition, u.localLightIntensity, u.localLightMaxContribution,
|
|
761
|
+
);
|
|
762
|
+
const localFillMask = smoothstep(float(0.015), max(0.06, u.localLightMaxContribution.mul(0.55)), strongestLocalLight);
|
|
763
|
+
const localLitColor = mix(shadowColor, albedoFinal, max(finalShadowArea, u.localLightShadowLift));
|
|
764
|
+
color.addAssign(localLightValue.mul(localLitColor));
|
|
765
|
+
color.assign(mix(color, max(color, albedoFinal.mul(u.localLightShadowLift)), localFillMask.mul(0.08)));
|
|
766
|
+
color.mulAssign(mix(1.0, materialAo, u.materialAoStrength));
|
|
767
|
+
|
|
768
|
+
If(u.useSpecular, () => {
|
|
769
|
+
const resolvedSpecularColor = u.specularColor
|
|
770
|
+
.mul(mix(vec3(1.0), materialMaps.sampleMaterialSpecularColor(), u.materialSpecularColorStrength))
|
|
771
|
+
.toVar();
|
|
772
|
+
resolvedSpecularColor.assign(mix(resolvedSpecularColor, albedoFinal, materialMetalnessValue.mul(u.materialMetalnessStrength)));
|
|
773
|
+
const resolvedSpecularIntensity = u.specularIntensity
|
|
774
|
+
.mul(materialMetalnessValue.mul(u.materialMetalnessStrength).add(1.0));
|
|
775
|
+
color.addAssign(resolvedSpecularColor.mul(mainLight).mul(specularArea).mul(resolvedSpecularIntensity).mul(finalDirectVisibility));
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
If(u.useHairHighlight, () => {
|
|
779
|
+
color.addAssign(mainLight.mul(albedoFinal).mul(hairHighlightMaskValue).mul(u.hairHighlightIntensity).mul(finalDirectVisibility));
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
If(u.useEyeHighlight, () => {
|
|
783
|
+
color.addAssign(
|
|
784
|
+
u.eyeHighlightColor.mul(eyeHighlightMaskValue).mul(u.eyeHighlightIntensity)
|
|
785
|
+
.mul(mix(finalDirectVisibility, 1.0, u.eyeHighlightShowInShadowArea)),
|
|
786
|
+
);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
If(u.useRimLight, () => {
|
|
790
|
+
const rimColor = mainLight.mul(u.rimTintColor).mul(mix(vec3(1.0), albedoFinal, u.rimMixWithBaseMapColor));
|
|
791
|
+
color.addAssign(rimColor.mul(rimMask).mul(u.rimIntensity).mul(finalDirectVisibility));
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
if (flags.hasGlitter) {
|
|
795
|
+
If(u.useGlitter, () => {
|
|
796
|
+
const glitterUv = select(u.glitterUvChannel.equal(1), vUv2, vUv);
|
|
797
|
+
const glitterResult = highlights.evaluateGlitter(glitterUv, V, N, L);
|
|
798
|
+
const glitterVisibility = mix(finalShadowArea.mul(finalDirectVisibility), 1.0, u.glitterShowInShadowArea);
|
|
799
|
+
color.addAssign(glitterResult.mul(mainLight).mul(u.glitterIntensity).mul(glitterVisibility));
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (flags.isFurShell) {
|
|
804
|
+
// Cheap fur self-occlusion: darken roots.
|
|
805
|
+
color.mulAssign(mix(u.furRootShade, float(1.0), u.furLayer));
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (flags.hasMatcapMap) {
|
|
809
|
+
If(u.materialMatcapStrength.greaterThan(0.0).and(u.hasMaterialMatcapMap), () => {
|
|
810
|
+
color.addAssign(albedoFinal.mul(materialMaps.sampleMaterialMatcap(N)).mul(u.materialMatcapStrength));
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
If(u.materialEmissiveStrength.greaterThan(0.0), () => {
|
|
815
|
+
color.addAssign(materialMaps.sampleMaterialEmissive().mul(u.materialEmissiveStrength));
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
color.assign(max(color, vec3(0.0)));
|
|
819
|
+
|
|
820
|
+
if (flags.hasDebugViews) {
|
|
821
|
+
const debugColor = buildDebugColor({
|
|
822
|
+
albedo: albedoFinal, alpha, contactShadowValue, depthRim,
|
|
823
|
+
eyeHighlightMaskValue, finalDirectVisibility, finalShadowArea,
|
|
824
|
+
hairHighlightMaskValue, litColor: color, materialAo, materialMaps,
|
|
825
|
+
materialMetalnessValue, materialRoughnessValue, N, rimMask, roles,
|
|
826
|
+
sceneShadowVisibility, selfShadowVisibility, shadowColor,
|
|
827
|
+
sourceAlbedo, specularArea, u,
|
|
828
|
+
});
|
|
829
|
+
// mix + scalar mask instead of a conditional root (see buildDebugColor).
|
|
830
|
+
const debugMask = select(u.debugOutputMode.greaterThan(0), float(1.0), float(0.0));
|
|
831
|
+
return mix(vec4(color, alpha), debugColor, debugMask);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return vec4(color, alpha);
|
|
835
|
+
})();
|
|
836
|
+
|
|
837
|
+
// Same-name uniform slots (ShaderMaterial-compatible `.value` access).
|
|
838
|
+
material.uniforms = u;
|
|
839
|
+
material.userData.isToonNodeMaterial = true;
|
|
840
|
+
material.userData.toonFlags = flags;
|
|
841
|
+
|
|
842
|
+
// Fur shells need per-shell materials (the GLSL path deep-clones uniform
|
|
843
|
+
// dicts; NodeMaterial.clone would SHARE uniform nodes, so shells rebuild
|
|
844
|
+
// with the same params + current uniform values instead).
|
|
845
|
+
material.userData.createFurShellVariant = (furSettings, layer) => {
|
|
846
|
+
const shellMaterial = createAnimeNodeMaterial({ ...params, isFurShell: true });
|
|
847
|
+
copyToonUniformValues(shellMaterial.uniforms, u);
|
|
848
|
+
shellMaterial.uniforms.furLayer.value = layer;
|
|
849
|
+
shellMaterial.uniforms.furLength.value = furSettings.length;
|
|
850
|
+
shellMaterial.uniforms.furGravity.value = furSettings.gravity;
|
|
851
|
+
shellMaterial.uniforms.furDensity.value = furSettings.density;
|
|
852
|
+
shellMaterial.uniforms.furRootOffset.value = furSettings.rootOffset;
|
|
853
|
+
shellMaterial.uniforms.furRootShade.value = furSettings.rootShade;
|
|
854
|
+
// Shells are cutout-discarded in the fragment shader; keep depth writes
|
|
855
|
+
// so they sort correctly against outlines and transparents.
|
|
856
|
+
shellMaterial.transparent = false;
|
|
857
|
+
shellMaterial.depthWrite = true;
|
|
858
|
+
return shellMaterial;
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
// Scene lights are mirrored into the shared uniforms once per rendered
|
|
862
|
+
// frame from the converted meshes (see toonMaterialAdapter).
|
|
863
|
+
return material;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Mirrors current uniform values from one anime node material to another
|
|
867
|
+
// (the fur-shell analog of ShaderMaterial.clone's uniform deep copy).
|
|
868
|
+
function copyToonUniformValues(target, source) {
|
|
869
|
+
for (const [name, node] of Object.entries(target)) {
|
|
870
|
+
const sourceNode = source[name];
|
|
871
|
+
if (!sourceNode || sourceNode === node) continue;
|
|
872
|
+
const value = sourceNode.value;
|
|
873
|
+
if (value === undefined) continue;
|
|
874
|
+
if (value?.isColor || value?.isVector2 || value?.isVector3 || value?.isVector4 || value?.isMatrix4) {
|
|
875
|
+
node.value?.copy?.(value);
|
|
876
|
+
} else {
|
|
877
|
+
node.value = value;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// dot() with a name that doesn't collide with the import list ordering above.
|
|
883
|
+
function dotNode(a, b) {
|
|
884
|
+
return a.dot(b);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// applyBaseColorEdit (character-fragment-color.glsl) — kept here because it
|
|
888
|
+
// mixes uniform access with the color-chunk helpers.
|
|
889
|
+
import { applyHSVChange as _applyHSVChange, hsvToRgb as _hsvToRgb, rgbToHsv as _rgbToHsv } from './chunks/character-color.js';
|
|
890
|
+
import { fract as _fract } from 'three/tsl';
|
|
891
|
+
|
|
892
|
+
function applyBaseColorEditNode(editedRgb, u) {
|
|
893
|
+
const hsv = _rgbToHsv(max(editedRgb, vec3(0.0))).toVar();
|
|
894
|
+
hsv.x.assign(_fract(hsv.x.add(u.baseMapHue)));
|
|
895
|
+
hsv.y.assign(clamp(hsv.y.mul(u.baseMapSaturation), 0.0, 1.0));
|
|
896
|
+
hsv.z.mulAssign(u.baseMapValue);
|
|
897
|
+
const color = _hsvToRgb(hsv);
|
|
898
|
+
return pow(max(color, vec3(0.0)), vec3(max(u.baseMapGamma, 0.01)));
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Debug output select-chain (character-fragment-debug.glsl + the mode table
|
|
902
|
+
// in anime.frag.glsl). Only built when debugOutputMode > 0 at creation time
|
|
903
|
+
// (the TOON_DEBUG_VIEWS define analog).
|
|
904
|
+
function buildDebugColor(ctx) {
|
|
905
|
+
const {
|
|
906
|
+
albedo, alpha, contactShadowValue, depthRim, eyeHighlightMaskValue,
|
|
907
|
+
finalDirectVisibility, finalShadowArea, hairHighlightMaskValue, litColor,
|
|
908
|
+
materialAo, materialMaps, materialMetalnessValue, materialRoughnessValue,
|
|
909
|
+
N, rimMask, roles, sceneShadowVisibility, selfShadowVisibility,
|
|
910
|
+
shadowColor, sourceAlbedo, specularArea, u,
|
|
911
|
+
} = ctx;
|
|
912
|
+
|
|
913
|
+
const dbg = (color) => vec4(max(vec3(color), vec3(0.0)), alpha);
|
|
914
|
+
|
|
915
|
+
const entries = [
|
|
916
|
+
[1, dbg(albedo)],
|
|
917
|
+
[2, dbg(vec3(finalShadowArea))],
|
|
918
|
+
[3, dbg(vec3(sceneShadowVisibility))],
|
|
919
|
+
[4, dbg(shadowColor)],
|
|
920
|
+
[6, dbg(roles.debugMaterialRoleColor())],
|
|
921
|
+
[7, vec4(max(vec3(alpha), vec3(0.0)), 1.0)],
|
|
922
|
+
[8, dbg(sourceAlbedo)],
|
|
923
|
+
[9, dbg(vec3(selfShadowVisibility))],
|
|
924
|
+
[10, dbg(vec3(finalDirectVisibility))],
|
|
925
|
+
[11, dbg(vec3(rimMask))],
|
|
926
|
+
[12, dbg(vec3(specularArea))],
|
|
927
|
+
[13, dbg(vec3(hairHighlightMaskValue))],
|
|
928
|
+
[14, dbg(vec3(eyeHighlightMaskValue))],
|
|
929
|
+
[15, dbg(materialMaps.sampleMaterialNormalMapColor())],
|
|
930
|
+
[16, dbg(vec3(materialAo))],
|
|
931
|
+
[17, dbg(materialMaps.sampleMaterialEmissive())],
|
|
932
|
+
[18, dbg(materialMaps.sampleMaterialMatcap(N))],
|
|
933
|
+
[19, dbg(materialMaps.sampleMaterialRamp(finalShadowArea))],
|
|
934
|
+
[20, dbg(materialMaps.sampleMaterialDetail())],
|
|
935
|
+
[21, dbg(vec3(materialRoughnessValue))],
|
|
936
|
+
[22, dbg(vec3(materialMetalnessValue))],
|
|
937
|
+
[23, dbg(vec3(max(depthRim, 0.0)))],
|
|
938
|
+
[24, dbg(vec3(contactShadowValue))],
|
|
939
|
+
];
|
|
940
|
+
if (litColor !== null) {
|
|
941
|
+
const lightingFactor = litColor.div(max(albedo, vec3(0.06)));
|
|
942
|
+
entries.push([5, dbg(clamp(lightingFactor.mul(0.5), vec3(0.0), vec3(1.0)))]);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Masked sum instead of nested selects: deep ConditionalNode chains hit the
|
|
946
|
+
// GLSL builder's detached type-resolution fallback (see the vec4-root note
|
|
947
|
+
// at the call sites); per-entry scalar masks stay flat and hazard-free.
|
|
948
|
+
let result = vec4(0.0, 0.0, 0.0, alpha).mul(knownModeMask(u, entries).oneMinus());
|
|
949
|
+
for (const [mode, value] of entries) {
|
|
950
|
+
result = result.add(vec4(value).mul(select(u.debugOutputMode.equal(mode), float(1.0), float(0.0))));
|
|
951
|
+
}
|
|
952
|
+
return result;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// 1 when debugOutputMode matches any table entry, else 0 — keeps the
|
|
956
|
+
// fallback-black default for unknown modes without a conditional chain.
|
|
957
|
+
function knownModeMask(u, entries) {
|
|
958
|
+
let mask = float(0.0);
|
|
959
|
+
for (const [mode] of entries) {
|
|
960
|
+
mask = mask.add(select(u.debugOutputMode.equal(mode), float(1.0), float(0.0)));
|
|
961
|
+
}
|
|
962
|
+
return mask.min(1.0);
|
|
963
|
+
}
|