@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,260 @@
|
|
|
1
|
+
// TSL port of src/shaders/chunks/environment-fragment-lighting.glsl — the
|
|
2
|
+
// wrapped-diffuse light bands, banded Blinn-Phong specular, six-direction
|
|
3
|
+
// ambient-probe irradiance, and the derivative-TBN normal perturbation.
|
|
4
|
+
//
|
|
5
|
+
// Scene light data comes from the shared toonSceneLights uniforms
|
|
6
|
+
// (chunks/character-scene-lights.js) instead of three's <lights_pars_begin>
|
|
7
|
+
// blocks; syncToonSceneLights replicates WebGLLights' view-space conventions,
|
|
8
|
+
// so the light ingredients match what the GLSL loops consumed. The GLSL
|
|
9
|
+
// directionalLights[i] loop is evaluated against the mirrored main light —
|
|
10
|
+
// environment scenes use a single sun, and the NUM_DIR_LIGHTS == 0 fallback
|
|
11
|
+
// (L = normalize(vec3(0.35, 0.75, 0.55)), white color) maps onto
|
|
12
|
+
// hasMainLight = 0 with identical band math.
|
|
13
|
+
//
|
|
14
|
+
// NOTE (preserved GLSL quirk): the wrapped diffuse terms dot the "world"
|
|
15
|
+
// normal — which the vertex stage actually writes in VIEW space — against
|
|
16
|
+
// three's view-space light data. That mismatch is part of the approved
|
|
17
|
+
// baseline look, so it is ported as-is; the specular term additionally
|
|
18
|
+
// re-applies the view matrix to the already-view-space normal, exactly like
|
|
19
|
+
// the GLSL did.
|
|
20
|
+
//
|
|
21
|
+
// GLSL out-parameters (strongestLight) become returned { light, strongest }
|
|
22
|
+
// node-var pairs. Uniform-driven early returns keep their If() shape.
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
clamp,
|
|
26
|
+
cross,
|
|
27
|
+
dFdx,
|
|
28
|
+
dFdy,
|
|
29
|
+
dot,
|
|
30
|
+
float,
|
|
31
|
+
Fn,
|
|
32
|
+
If,
|
|
33
|
+
inverseSqrt,
|
|
34
|
+
length,
|
|
35
|
+
max,
|
|
36
|
+
mix,
|
|
37
|
+
normalize,
|
|
38
|
+
pow,
|
|
39
|
+
select,
|
|
40
|
+
smoothstep,
|
|
41
|
+
vec3,
|
|
42
|
+
vec4,
|
|
43
|
+
} from 'three/tsl';
|
|
44
|
+
|
|
45
|
+
import {
|
|
46
|
+
getMainLightDirection,
|
|
47
|
+
MAX_TOON_POINT_LIGHTS,
|
|
48
|
+
MAX_TOON_SPOT_LIGHTS,
|
|
49
|
+
toonSceneLights,
|
|
50
|
+
} from './character-scene-lights.js';
|
|
51
|
+
|
|
52
|
+
function maxComponent3(value) {
|
|
53
|
+
return max(value.r, max(value.g, value.b));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// three's getDistanceAttenuation (common.glsl) — physically based falloff
|
|
57
|
+
// with the artist cutoff window, matching getPointLightInfo/getSpotLightInfo.
|
|
58
|
+
const distanceAttenuation = /*@__PURE__*/ Fn(([lightDistance, cutoffDistance, decayExponent]) => {
|
|
59
|
+
const distanceFalloff = float(1.0).div(max(pow(lightDistance, decayExponent), 0.01)).toVar();
|
|
60
|
+
If(cutoffDistance.greaterThan(0.0), () => {
|
|
61
|
+
const ratio = lightDistance.div(cutoffDistance);
|
|
62
|
+
const ratio2 = ratio.mul(ratio);
|
|
63
|
+
const window1 = ratio2.mul(ratio2).oneMinus().clamp(0.0, 1.0);
|
|
64
|
+
distanceFalloff.mulAssign(window1.mul(window1));
|
|
65
|
+
});
|
|
66
|
+
return distanceFalloff;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export function createEnvironmentLightingChunk({ u, tex, flags, cameraViewMatrixNode }) {
|
|
70
|
+
const s = toonSceneLights;
|
|
71
|
+
|
|
72
|
+
// The wrapped half-Lambert curve (0.16..0.86 smoothstep window) is the
|
|
73
|
+
// canonical toonlab band; consumers outside this stitched shader must keep
|
|
74
|
+
// the constants in step (see the GLSL chunk header).
|
|
75
|
+
const evaluateDirectionalLight = (normal) => {
|
|
76
|
+
const strongest = float(0.0).toVar();
|
|
77
|
+
const light = vec3(0.0).toVar();
|
|
78
|
+
If(u.enableDirectionalLights.greaterThanEqual(0.5), () => {
|
|
79
|
+
// GLSL NUM_DIR_LIGHTS > 0 uses directionalLights[0]; NUM_DIR_LIGHTS == 0
|
|
80
|
+
// falls back to the fixed direction with white color. Both cases share
|
|
81
|
+
// the band formulas, so hasMainLight selects between them at runtime.
|
|
82
|
+
const L = getMainLightDirection();
|
|
83
|
+
const lightColor = mix(vec3(1.0), s.mainLightColor, s.hasMainLight);
|
|
84
|
+
const halfLambert = dot(normal, L).mul(0.5).add(0.5);
|
|
85
|
+
const wrappedLight = smoothstep(
|
|
86
|
+
float(0.16).sub(u.shadeSoftness),
|
|
87
|
+
float(0.86).add(u.shadeSoftness),
|
|
88
|
+
halfLambert,
|
|
89
|
+
);
|
|
90
|
+
const lightBand = mix(1.0, wrappedLight, u.shadeStrength);
|
|
91
|
+
strongest.assign(wrappedLight);
|
|
92
|
+
light.assign(lightColor.mul(lightBand));
|
|
93
|
+
});
|
|
94
|
+
return { light, strongest };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const evaluatePointLights = (normal, geometryPosition) => {
|
|
98
|
+
const strongest = float(0.0).toVar();
|
|
99
|
+
const light = vec3(0.0).toVar();
|
|
100
|
+
If(u.enablePointLights.greaterThanEqual(0.5), () => {
|
|
101
|
+
for (let i = 0; i < MAX_TOON_POINT_LIGHTS; i += 1) {
|
|
102
|
+
If(float(i).lessThan(s.pointLightCount), () => {
|
|
103
|
+
// getPointLightInfo(pointLights[i], geometryPosition, pointLight)
|
|
104
|
+
const lVector = s.pointLightPositions.element(i).sub(geometryPosition);
|
|
105
|
+
const lightDistance = length(lVector);
|
|
106
|
+
const direction = lVector.div(max(lightDistance, 1e-6));
|
|
107
|
+
const params = s.pointLightParams.element(i);
|
|
108
|
+
const attenuation = distanceAttenuation(lightDistance, params.x, params.y);
|
|
109
|
+
const color = s.pointLightColors.element(i).mul(attenuation).toVar();
|
|
110
|
+
If(maxComponent3(color).greaterThan(0.0), () => { // pointLight.visible
|
|
111
|
+
const halfLambert = dot(normal, direction).mul(0.5).add(0.5);
|
|
112
|
+
const wrappedLight = smoothstep(0.08, 0.88, halfLambert);
|
|
113
|
+
const lightBand = mix(0.18, wrappedLight, u.shadeStrength);
|
|
114
|
+
strongest.assign(max(strongest, wrappedLight.mul(maxComponent3(color))));
|
|
115
|
+
light.addAssign(color.mul(lightBand));
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return { light, strongest };
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const evaluateSpotLights = (normal, geometryPosition) => {
|
|
124
|
+
const strongest = float(0.0).toVar();
|
|
125
|
+
const light = vec3(0.0).toVar();
|
|
126
|
+
If(u.enableSpotLights.greaterThanEqual(0.5).and(u.spotLightStrength.greaterThan(0.0)), () => {
|
|
127
|
+
for (let i = 0; i < MAX_TOON_SPOT_LIGHTS; i += 1) {
|
|
128
|
+
If(float(i).lessThan(s.spotLightCount), () => {
|
|
129
|
+
// getSpotLightInfo(spotLights[i], geometryPosition, spotLight)
|
|
130
|
+
const lVector = s.spotLightPositions.element(i).sub(geometryPosition);
|
|
131
|
+
const lightDistance = length(lVector);
|
|
132
|
+
const direction = lVector.div(max(lightDistance, 1e-6));
|
|
133
|
+
const params = s.spotLightParams.element(i);
|
|
134
|
+
const angleCos = dot(direction, normalize(s.spotLightDirections.element(i)));
|
|
135
|
+
const spotAttenuation = smoothstep(params.z, params.w, angleCos);
|
|
136
|
+
const attenuation = distanceAttenuation(lightDistance, params.x, params.y)
|
|
137
|
+
.mul(spotAttenuation);
|
|
138
|
+
const color = s.spotLightColors.element(i).mul(attenuation).toVar();
|
|
139
|
+
If(maxComponent3(color).greaterThan(0.0), () => { // spotLight.visible
|
|
140
|
+
const halfLambert = dot(normal, direction).mul(0.5).add(0.5);
|
|
141
|
+
const wrappedLight = smoothstep(0.08, 0.88, halfLambert);
|
|
142
|
+
const lightBand = mix(0.12, wrappedLight, u.shadeStrength);
|
|
143
|
+
strongest.assign(max(strongest, wrappedLight.mul(maxComponent3(color))));
|
|
144
|
+
light.addAssign(color.mul(lightBand));
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return { light, strongest };
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Banded Blinn-Phong highlight for glossy interiors. The GLSL transforms
|
|
153
|
+
// its (already view-space, see quirk note above) normal by viewMatrix again
|
|
154
|
+
// — ported verbatim for parity.
|
|
155
|
+
const evaluateEnvironmentSpecular = (worldNormal, geometryPosition, sunlightVisibility) => {
|
|
156
|
+
const result = vec3(0.0).toVar();
|
|
157
|
+
const normalView = normalize(cameraViewMatrixNode.mul(vec4(worldNormal, 0.0)).xyz);
|
|
158
|
+
const viewDir = normalize(geometryPosition.negate());
|
|
159
|
+
|
|
160
|
+
const bandedSpecular = (lightDirection) => {
|
|
161
|
+
const halfDir = normalize(normalize(lightDirection).add(viewDir));
|
|
162
|
+
const spec = pow(max(dot(normalView, halfDir), 0.0), u.specularShininess);
|
|
163
|
+
return smoothstep(
|
|
164
|
+
float(0.5).sub(u.specularSoftness),
|
|
165
|
+
float(0.5).add(u.specularSoftness),
|
|
166
|
+
spec,
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Directional loop — main light only, gated on presence (the GLSL
|
|
171
|
+
// NUM_DIR_LIGHTS == 0 build compiles no directional specular at all).
|
|
172
|
+
If(s.hasMainLight.greaterThan(0.5), () => {
|
|
173
|
+
const spec = bandedSpecular(s.mainLightDirection);
|
|
174
|
+
result.addAssign(s.mainLightColor.mul(spec).mul(sunlightVisibility));
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
for (let i = 0; i < MAX_TOON_POINT_LIGHTS; i += 1) {
|
|
178
|
+
If(float(i).lessThan(s.pointLightCount), () => {
|
|
179
|
+
const lVector = s.pointLightPositions.element(i).sub(geometryPosition);
|
|
180
|
+
const lightDistance = length(lVector);
|
|
181
|
+
const direction = lVector.div(max(lightDistance, 1e-6));
|
|
182
|
+
const params = s.pointLightParams.element(i);
|
|
183
|
+
const attenuation = distanceAttenuation(lightDistance, params.x, params.y);
|
|
184
|
+
const color = s.pointLightColors.element(i).mul(attenuation).toVar();
|
|
185
|
+
If(maxComponent3(color).greaterThan(0.0), () => {
|
|
186
|
+
result.addAssign(color.mul(bandedSpecular(direction)));
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
for (let i = 0; i < MAX_TOON_SPOT_LIGHTS; i += 1) {
|
|
192
|
+
If(float(i).lessThan(s.spotLightCount), () => {
|
|
193
|
+
const lVector = s.spotLightPositions.element(i).sub(geometryPosition);
|
|
194
|
+
const lightDistance = length(lVector);
|
|
195
|
+
const direction = lVector.div(max(lightDistance, 1e-6));
|
|
196
|
+
const params = s.spotLightParams.element(i);
|
|
197
|
+
const angleCos = dot(direction, normalize(s.spotLightDirections.element(i)));
|
|
198
|
+
const spotAttenuation = smoothstep(params.z, params.w, angleCos);
|
|
199
|
+
const attenuation = distanceAttenuation(lightDistance, params.x, params.y)
|
|
200
|
+
.mul(spotAttenuation);
|
|
201
|
+
const color = s.spotLightColors.element(i).mul(attenuation).toVar();
|
|
202
|
+
If(maxComponent3(color).greaterThan(0.0), () => {
|
|
203
|
+
result.addAssign(color.mul(bandedSpecular(direction)));
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return result;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Six-direction irradiance from the scene ambient probe (shared uniform
|
|
212
|
+
// array). Element access on uniform arrays is a pure expression, so the
|
|
213
|
+
// ternaries stay select()s like the GLSL.
|
|
214
|
+
const environmentProbeIrradiance = (normal) => {
|
|
215
|
+
const probe = u.ambientProbe;
|
|
216
|
+
const weights = normal.mul(normal);
|
|
217
|
+
const x = select(normal.x.greaterThanEqual(0.0), probe.element(0), probe.element(1));
|
|
218
|
+
const y = select(normal.y.greaterThanEqual(0.0), probe.element(2), probe.element(3));
|
|
219
|
+
const z = select(normal.z.greaterThanEqual(0.0), probe.element(4), probe.element(5));
|
|
220
|
+
return x.mul(weights.x).add(y.mul(weights.y)).add(z.mul(weights.z));
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Derivative-TBN normal perturbation (mirrors three's tangentless
|
|
224
|
+
// normal_fragment_maps path) — only built when the material has a normal
|
|
225
|
+
// map (the USE_ENV_NORMAL_MAP define analog).
|
|
226
|
+
const perturbEnvironmentNormal = flags.hasNormalMap
|
|
227
|
+
? (normal, worldPosition, uvNode) => {
|
|
228
|
+
const mapN = tex.normalMapTex.sample(uvNode).xyz.mul(2.0).sub(1.0).toVar();
|
|
229
|
+
mapN.xy.mulAssign(u.normalMapScale.mul(u.normalMapStrength));
|
|
230
|
+
|
|
231
|
+
const q0 = dFdx(worldPosition);
|
|
232
|
+
const q1 = dFdy(worldPosition);
|
|
233
|
+
const st0 = dFdx(uvNode);
|
|
234
|
+
const st1 = dFdy(uvNode);
|
|
235
|
+
|
|
236
|
+
const n = normalize(normal);
|
|
237
|
+
const q1perp = cross(q1, n);
|
|
238
|
+
const q0perp = cross(n, q0);
|
|
239
|
+
const tangent = q1perp.mul(st0.x).add(q0perp.mul(st1.x));
|
|
240
|
+
const bitangent = q1perp.mul(st0.y).add(q0perp.mul(st1.y));
|
|
241
|
+
|
|
242
|
+
const det = max(dot(tangent, tangent), dot(bitangent, bitangent));
|
|
243
|
+
const scale = select(det.equal(0.0), float(0.0), inverseSqrt(det));
|
|
244
|
+
return normalize(
|
|
245
|
+
tangent.mul(mapN.x.mul(scale))
|
|
246
|
+
.add(bitangent.mul(mapN.y.mul(scale)))
|
|
247
|
+
.add(n.mul(mapN.z)),
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
: null;
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
environmentProbeIrradiance,
|
|
254
|
+
evaluateDirectionalLight,
|
|
255
|
+
evaluateEnvironmentSpecular,
|
|
256
|
+
evaluatePointLights,
|
|
257
|
+
evaluateSpotLights,
|
|
258
|
+
perturbEnvironmentNormal,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Shared sun-shadow sampling for TSL environment + character materials —
|
|
2
|
+
// the node-backend replacement for three's <shadowmask_pars_fragment>
|
|
3
|
+
// getShadowMask() (the classic renderer's shadow system is driven by
|
|
4
|
+
// materials with lights:true, which the toon pipeline never uses on the node
|
|
5
|
+
// backends).
|
|
6
|
+
//
|
|
7
|
+
// A dedicated pass (src/environment/environmentSunShadowPass.js) renders the
|
|
8
|
+
// scene from the shadow-casting sun into a float color target (linear window
|
|
9
|
+
// depth — see docs/tsl-conventions.md on depth-texture sampling) and fills
|
|
10
|
+
// these shared uniforms. Until that pass runs, `ready` stays false and the
|
|
11
|
+
// mask is 1.0 everywhere — identical to a scene with no shadow-casting sun.
|
|
12
|
+
|
|
13
|
+
import * as THREE from 'three';
|
|
14
|
+
import {
|
|
15
|
+
clamp,
|
|
16
|
+
dot,
|
|
17
|
+
float,
|
|
18
|
+
Fn,
|
|
19
|
+
If,
|
|
20
|
+
mix,
|
|
21
|
+
smoothstep,
|
|
22
|
+
step,
|
|
23
|
+
texture,
|
|
24
|
+
uniform,
|
|
25
|
+
vec2,
|
|
26
|
+
vec4,
|
|
27
|
+
} from 'three/tsl';
|
|
28
|
+
|
|
29
|
+
export const environmentSunShadow = {
|
|
30
|
+
bias: uniform(0.0025),
|
|
31
|
+
map: null, // TextureNode, created lazily below (needs a fallback texture)
|
|
32
|
+
mapSize: uniform(2048),
|
|
33
|
+
matrix: uniform(new THREE.Matrix4()),
|
|
34
|
+
radius: uniform(1),
|
|
35
|
+
ready: uniform(false, 'bool'),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
let fallbackTexture = null;
|
|
39
|
+
|
|
40
|
+
export function sunShadowMapNode() {
|
|
41
|
+
if (!environmentSunShadow.map) {
|
|
42
|
+
if (!fallbackTexture) {
|
|
43
|
+
const data = new Uint8Array([255, 255, 255, 255]);
|
|
44
|
+
fallbackTexture = new THREE.DataTexture(data, 1, 1);
|
|
45
|
+
fallbackTexture.needsUpdate = true;
|
|
46
|
+
}
|
|
47
|
+
environmentSunShadow.map = texture(fallbackTexture);
|
|
48
|
+
}
|
|
49
|
+
return environmentSunShadow.map;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* getShadowMask() replacement: 1 fully lit, 0 fully shadowed. worldPosition
|
|
54
|
+
* is the fragment's world position node. The matrix is CPU-composed to yield
|
|
55
|
+
* GL-convention clip coords under the shader's *0.5+0.5 remap on every
|
|
56
|
+
* backend (see shadowClipAdjust in the pass).
|
|
57
|
+
*/
|
|
58
|
+
export const sampleEnvironmentSunShadow = /*@__PURE__*/ Fn(([worldPosition]) => {
|
|
59
|
+
const shadow = float(1.0).toVar();
|
|
60
|
+
If(environmentSunShadow.ready, () => {
|
|
61
|
+
const shadowNdc = environmentSunShadow.matrix.mul(vec4(worldPosition, 1.0));
|
|
62
|
+
const coord = shadowNdc.xyz.div(shadowNdc.w).mul(0.5).add(0.5).toVar();
|
|
63
|
+
const inside = coord.x.greaterThanEqual(0.0).and(coord.x.lessThanEqual(1.0))
|
|
64
|
+
.and(coord.y.greaterThanEqual(0.0)).and(coord.y.lessThanEqual(1.0))
|
|
65
|
+
.and(coord.z.lessThanEqual(1.0)).and(coord.z.greaterThanEqual(0.0));
|
|
66
|
+
If(inside, () => {
|
|
67
|
+
const texel = float(1.0).div(environmentSunShadow.mapSize);
|
|
68
|
+
const reference = coord.z.sub(environmentSunShadow.bias);
|
|
69
|
+
const map = sunShadowMapNode();
|
|
70
|
+
// 3×3 PCF (classic PCFSoftShadowMap kernel footprint): averages the
|
|
71
|
+
// 8-bit-quantized self-shadowing into the fine dither the approved
|
|
72
|
+
// interior look was tuned around; radius scales the spread.
|
|
73
|
+
const spread = texel.mul(environmentSunShadow.radius);
|
|
74
|
+
const visibility = float(0.0).toVar();
|
|
75
|
+
for (let y = -1; y <= 1; y += 1) {
|
|
76
|
+
for (let x = -1; x <= 1; x += 1) {
|
|
77
|
+
visibility.addAssign(step(
|
|
78
|
+
reference,
|
|
79
|
+
map.sample(coord.xy.add(vec2(x, y).mul(spread))).level(0).x,
|
|
80
|
+
));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
shadow.assign(visibility.div(9.0));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
return shadow;
|
|
87
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Manual linear scene fog for the world-anchored foliage cards (grass, tree
|
|
2
|
+
// canopies) on the node backends.
|
|
3
|
+
//
|
|
4
|
+
// Why not `material.fog = true`? Those materials override `vertexNode` with a
|
|
5
|
+
// custom world-space billboard, so their real rendered depth lives in a
|
|
6
|
+
// varying (viewPosition.z). three's built-in node fog instead reads the
|
|
7
|
+
// built-in `positionView` (modelViewMatrix * positionLocal), which is derived
|
|
8
|
+
// independently of the custom vertex — for a distant billboard that view-Z is
|
|
9
|
+
// wrong and the crown over-fogs into the sky (only visible under aggressive
|
|
10
|
+
// near fog like the water lab's). The classic GLSL path never had this: its
|
|
11
|
+
// `fog_vertex` chunk reads the same billboarded mvPosition the vertex outputs.
|
|
12
|
+
//
|
|
13
|
+
// So we mirror the GLSL `fog_fragment` exactly — linear smoothstep(near, far)
|
|
14
|
+
// on the true billboarded view depth — and set `material.fog = false`.
|
|
15
|
+
//
|
|
16
|
+
// Defaults are a no-op (far = 1e9), so a material whose lab never calls
|
|
17
|
+
// syncFoliageFog renders unfogged rather than wrong.
|
|
18
|
+
|
|
19
|
+
import * as THREE from 'three';
|
|
20
|
+
import { mix, smoothstep, uniform, vec3 } from 'three/tsl';
|
|
21
|
+
|
|
22
|
+
/** Fog uniform block for a foliage material. Default = effectively no fog. */
|
|
23
|
+
export function createFoliageFogUniforms() {
|
|
24
|
+
return {
|
|
25
|
+
uFogColor: uniform(new THREE.Color(0xffffff)),
|
|
26
|
+
uFogNear: uniform(1),
|
|
27
|
+
uFogFar: uniform(1e9),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Applies linear fog to `color` using the billboarded view depth.
|
|
33
|
+
* @param {*} color vec3 node (mutated via return)
|
|
34
|
+
* @param {*} vViewZ varying float: viewPosition.z (negative forward)
|
|
35
|
+
* @param {*} u the fog uniform block
|
|
36
|
+
* @returns fogged vec3 node
|
|
37
|
+
*/
|
|
38
|
+
export function applyFoliageFog(color, vViewZ, u) {
|
|
39
|
+
// GLSL fog_fragment uses vFogDepth = -mvPosition.z (positive). viewPosition.z
|
|
40
|
+
// is negative forward, so depth = -vViewZ.
|
|
41
|
+
const fogFactor = smoothstep(u.uFogNear, u.uFogFar, vViewZ.negate());
|
|
42
|
+
return mix(color, vec3(u.uFogColor), fogFactor);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Copies a scene's linear Fog into a foliage material's fog uniforms. Call
|
|
47
|
+
* once per frame (fog color/range can change with time of day). A null/absent
|
|
48
|
+
* fog, or a non-linear FogExp2, disables fog (far = 1e9).
|
|
49
|
+
*/
|
|
50
|
+
export function syncFoliageFog(material, fog) {
|
|
51
|
+
const u = material?.uniforms;
|
|
52
|
+
if (!u?.uFogFar) return;
|
|
53
|
+
if (fog && fog.isFog && typeof fog.near === 'number') {
|
|
54
|
+
u.uFogColor.value.copy(fog.color);
|
|
55
|
+
u.uFogNear.value = fog.near;
|
|
56
|
+
u.uFogFar.value = fog.far;
|
|
57
|
+
} else {
|
|
58
|
+
u.uFogFar.value = 1e9;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Depth-as-color pass material for the node backends, shared by the
|
|
2
|
+
// character passes (scene depth prepass, self-shadow map) and the
|
|
3
|
+
// environment sun-shadow pass.
|
|
4
|
+
//
|
|
5
|
+
// The node renderer's WGSL and GLSL builders type depth-texture samples
|
|
6
|
+
// differently (float vs vec4 — docs/tsl-conventions.md), so passes write
|
|
7
|
+
// linear window depth into a float COLOR attachment instead: [0,1] window
|
|
8
|
+
// depth is numerically identical on both coordinate systems for perspective
|
|
9
|
+
// and orthographic cameras alike. The select keys off the active camera, so
|
|
10
|
+
// one material serves both projections.
|
|
11
|
+
//
|
|
12
|
+
// Manual samplers of these targets need the GL-convention matrix pre-adjust
|
|
13
|
+
// exported below (node-renderer RTs are written top-down on BOTH backends;
|
|
14
|
+
// WebGPU clip z is additionally already [0,1]).
|
|
15
|
+
|
|
16
|
+
import * as THREE from 'three';
|
|
17
|
+
import {
|
|
18
|
+
cameraFar,
|
|
19
|
+
cameraNear,
|
|
20
|
+
cameraProjectionMatrix,
|
|
21
|
+
positionView,
|
|
22
|
+
select,
|
|
23
|
+
texture as textureNode,
|
|
24
|
+
vec3,
|
|
25
|
+
vec4,
|
|
26
|
+
viewZToOrthographicDepth,
|
|
27
|
+
viewZToPerspectiveDepth,
|
|
28
|
+
} from 'three/tsl';
|
|
29
|
+
import { MeshBasicNodeMaterial } from 'three/webgpu';
|
|
30
|
+
|
|
31
|
+
import { withToonStorageSkinning } from './character-skinning.js';
|
|
32
|
+
|
|
33
|
+
// Skinned meshes (MMD-scale skeletons included) skin through the storage/PBO
|
|
34
|
+
// path on the WebGL2 backend — see character-skinning.js.
|
|
35
|
+
export const PassBasicNodeMaterial = withToonStorageSkinning(MeshBasicNodeMaterial);
|
|
36
|
+
|
|
37
|
+
export function createPassDepthColorMaterial({
|
|
38
|
+
alphaTest = 0,
|
|
39
|
+
map = null,
|
|
40
|
+
side = THREE.DoubleSide,
|
|
41
|
+
// The classic sun shadow map stored packed-RGBA8 depth; its ~1/255
|
|
42
|
+
// quantization produces the dithered self-shadowing the environment look
|
|
43
|
+
// was tuned around. The sun pass opts in to reproduce that exact
|
|
44
|
+
// mechanism; the character passes keep full float precision (classic used
|
|
45
|
+
// 24-bit depth textures there).
|
|
46
|
+
quantize256 = false,
|
|
47
|
+
} = {}) {
|
|
48
|
+
const material = new PassBasicNodeMaterial({ side });
|
|
49
|
+
// three's shadow-pass exemption: keeps the render-object cache key free of
|
|
50
|
+
// per-frame scene state (background/environment toggled around the pass),
|
|
51
|
+
// which otherwise disposes+recreates every depth render object each frame —
|
|
52
|
+
// destroying object uniform buffers that in-flight submits still reference.
|
|
53
|
+
material.isShadowPassMaterial = true;
|
|
54
|
+
const orthographic = cameraProjectionMatrix.element(3).w.equal(1.0);
|
|
55
|
+
let depth01 = select(
|
|
56
|
+
orthographic,
|
|
57
|
+
viewZToOrthographicDepth(positionView.z, cameraNear, cameraFar),
|
|
58
|
+
viewZToPerspectiveDepth(positionView.z, cameraNear, cameraFar),
|
|
59
|
+
);
|
|
60
|
+
if (quantize256) depth01 = depth01.mul(255.0).floor().div(255.0);
|
|
61
|
+
const alpha = map && alphaTest > 0 ? textureNode(map).a : 1.0;
|
|
62
|
+
material.colorNode = vec4(vec3(depth01), alpha);
|
|
63
|
+
if (alphaTest > 0) material.alphaTest = alphaTest;
|
|
64
|
+
return material;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Pre-multiply onto proj * viewInverse so a shader using the GL-convention
|
|
68
|
+
// `clip.xyz * 0.5 + 0.5` remap samples the right texel and compares the right
|
|
69
|
+
// depth on each backend.
|
|
70
|
+
export const shadowClipAdjustWebGPU = new THREE.Matrix4().set(
|
|
71
|
+
1, 0, 0, 0,
|
|
72
|
+
0, -1, 0, 0,
|
|
73
|
+
0, 0, 2, -1,
|
|
74
|
+
0, 0, 0, 1,
|
|
75
|
+
);
|
|
76
|
+
export const shadowClipAdjustGL = new THREE.Matrix4().set(
|
|
77
|
+
1, 0, 0, 0,
|
|
78
|
+
0, -1, 0, 0,
|
|
79
|
+
0, 0, 1, 0,
|
|
80
|
+
0, 0, 0, 1,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
export function applyShadowClipAdjust(matrix, renderer) {
|
|
84
|
+
return matrix.premultiply(
|
|
85
|
+
renderer.coordinateSystem === THREE.WebGPUCoordinateSystem
|
|
86
|
+
? shadowClipAdjustWebGPU
|
|
87
|
+
: shadowClipAdjustGL,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// TSL port of src/shaders/chunks/stylized-cloud-shadow.glsl — scrolling
|
|
2
|
+
// procedural cloud shadows for outdoor stylized scenes. A cheap 3-octave
|
|
3
|
+
// value-noise fbm (same rotation/lacunarity family as the sky's cloud fbm)
|
|
4
|
+
// sampled in world XZ and drifted by a velocity. Pure functions — callers
|
|
5
|
+
// pass their own uniforms, so grass, tree canopies, terrain, and any future
|
|
6
|
+
// surface share one implementation without colliding uniform names.
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
clamp,
|
|
10
|
+
dot,
|
|
11
|
+
float,
|
|
12
|
+
floor,
|
|
13
|
+
Fn,
|
|
14
|
+
fract,
|
|
15
|
+
If,
|
|
16
|
+
mix,
|
|
17
|
+
smoothstep,
|
|
18
|
+
vec2,
|
|
19
|
+
vec3,
|
|
20
|
+
} from 'three/tsl';
|
|
21
|
+
|
|
22
|
+
export const cloudShadowHash = /*@__PURE__*/ Fn(([p]) => {
|
|
23
|
+
const p3 = fract(vec3(p.x, p.y, p.x).mul(0.1031)).toVar();
|
|
24
|
+
p3.addAssign(dot(p3, p3.yzx.add(33.33)));
|
|
25
|
+
return fract(p3.x.add(p3.y).mul(p3.z));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const cloudShadowNoise = /*@__PURE__*/ Fn(([p]) => {
|
|
29
|
+
const i = floor(p);
|
|
30
|
+
const f = fract(p);
|
|
31
|
+
const u = f.mul(f).mul(f.mul(-2.0).add(3.0));
|
|
32
|
+
const a = cloudShadowHash(i);
|
|
33
|
+
const b = cloudShadowHash(i.add(vec2(1.0, 0.0)));
|
|
34
|
+
const c = cloudShadowHash(i.add(vec2(0.0, 1.0)));
|
|
35
|
+
const d = cloudShadowHash(i.add(vec2(1.0, 1.0)));
|
|
36
|
+
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* GLSL stylizedCloudShadow(worldXZ, time, strength, coverage, scale,
|
|
41
|
+
* velocity): sunlight visibility factor in [1 - strength, 1]. strength below
|
|
42
|
+
* 0.001 short-circuits to fully lit (same If shape as the GLSL early return,
|
|
43
|
+
* so the fbm costs nothing while disabled).
|
|
44
|
+
*/
|
|
45
|
+
export const stylizedCloudShadow = /*@__PURE__*/ Fn(([worldXZ, time, strength, coverage, scale, velocity]) => {
|
|
46
|
+
const visibility = float(1.0).toVar();
|
|
47
|
+
If(strength.greaterThanEqual(0.001), () => {
|
|
48
|
+
const uv = worldXZ.mul(scale).add(velocity.mul(time)).toVar();
|
|
49
|
+
const value = float(0.0).toVar();
|
|
50
|
+
let amplitude = 0.5;
|
|
51
|
+
let total = 0.0;
|
|
52
|
+
for (let i = 0; i < 3; i += 1) {
|
|
53
|
+
value.addAssign(cloudShadowNoise(uv).mul(amplitude));
|
|
54
|
+
total += amplitude;
|
|
55
|
+
// GLSL: uv = mat2(0.8, 0.6, -0.6, 0.8) * uv * 2.02 + vec2(17.13, 9.77),
|
|
56
|
+
// written out component-wise (columns (0.8, 0.6) and (-0.6, 0.8)) —
|
|
57
|
+
// TSL matN() scalar constructors are row-major (docs/tsl-conventions.md).
|
|
58
|
+
uv.assign(vec2(
|
|
59
|
+
uv.x.mul(0.8).add(uv.y.mul(-0.6)),
|
|
60
|
+
uv.x.mul(0.6).add(uv.y.mul(0.8)),
|
|
61
|
+
).mul(2.02).add(vec2(17.13, 9.77)));
|
|
62
|
+
amplitude *= 0.5;
|
|
63
|
+
}
|
|
64
|
+
value.divAssign(Math.max(total, 1e-4));
|
|
65
|
+
const threshold = mix(0.72, 0.4, clamp(coverage, 0.0, 1.0));
|
|
66
|
+
const cloud = smoothstep(threshold, threshold.add(0.17), value);
|
|
67
|
+
visibility.assign(cloud.mul(clamp(strength, 0.0, 1.0)).oneMinus());
|
|
68
|
+
});
|
|
69
|
+
return visibility;
|
|
70
|
+
});
|