@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,161 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isFoliageMaterial,
|
|
5
|
+
isUtilityTextureLabel,
|
|
6
|
+
textureLabel,
|
|
7
|
+
textureSourceUrl,
|
|
8
|
+
} from './environmentMaterialClassifier.js';
|
|
9
|
+
|
|
10
|
+
export const fallbackEnvironmentWhiteTexture = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);
|
|
11
|
+
fallbackEnvironmentWhiteTexture.colorSpace = THREE.SRGBColorSpace;
|
|
12
|
+
fallbackEnvironmentWhiteTexture.needsUpdate = true;
|
|
13
|
+
|
|
14
|
+
export const fallbackEnvironmentBlackTexture = new THREE.DataTexture(new Uint8Array([0, 0, 0, 255]), 1, 1);
|
|
15
|
+
fallbackEnvironmentBlackTexture.colorSpace = THREE.NoColorSpace;
|
|
16
|
+
fallbackEnvironmentBlackTexture.needsUpdate = true;
|
|
17
|
+
|
|
18
|
+
// Flat +Z normal for materials compiled without a real normal map bound.
|
|
19
|
+
export const fallbackEnvironmentNormalTexture = new THREE.DataTexture(new Uint8Array([128, 128, 255, 255]), 1, 1);
|
|
20
|
+
fallbackEnvironmentNormalTexture.colorSpace = THREE.NoColorSpace;
|
|
21
|
+
fallbackEnvironmentNormalTexture.needsUpdate = true;
|
|
22
|
+
|
|
23
|
+
const textureLoader = new THREE.TextureLoader();
|
|
24
|
+
const textureExistenceCache = new Map();
|
|
25
|
+
const textureLoadCache = new Map();
|
|
26
|
+
|
|
27
|
+
export function textureKindCandidateUrl(sourceUrl, kind) {
|
|
28
|
+
if (!sourceUrl) return null;
|
|
29
|
+
|
|
30
|
+
const candidate = sourceUrl.replace(
|
|
31
|
+
/(^|[_\-/])(diffuse|lsab|smbe|normal|nrm|height|ao|mask|esa)(\.[a-z0-9]+)([?#].*)?$/i,
|
|
32
|
+
`$1${kind}$3$4`,
|
|
33
|
+
);
|
|
34
|
+
return candidate === sourceUrl ? null : candidate;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function textureUrlExists(url) {
|
|
38
|
+
if (!url) return false;
|
|
39
|
+
if (!textureExistenceCache.has(url)) {
|
|
40
|
+
textureExistenceCache.set(url, fetch(url, { method: 'HEAD' })
|
|
41
|
+
.then((response) => response.ok)
|
|
42
|
+
.catch(() => false));
|
|
43
|
+
}
|
|
44
|
+
return textureExistenceCache.get(url);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function copyTextureTransform(target, source) {
|
|
48
|
+
if (!source) return target;
|
|
49
|
+
target.wrapS = source.wrapS;
|
|
50
|
+
target.wrapT = source.wrapT;
|
|
51
|
+
target.repeat.copy(source.repeat);
|
|
52
|
+
target.offset.copy(source.offset);
|
|
53
|
+
target.center.copy(source.center);
|
|
54
|
+
target.rotation = source.rotation;
|
|
55
|
+
target.flipY = source.flipY;
|
|
56
|
+
target.channel = source.channel;
|
|
57
|
+
return target;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function loadEnvironmentTexture(url, referenceTexture, { colorSpace = THREE.SRGBColorSpace } = {}) {
|
|
61
|
+
const cacheKey = `${url}|${colorSpace}`;
|
|
62
|
+
if (!textureLoadCache.has(cacheKey)) {
|
|
63
|
+
textureLoadCache.set(cacheKey, new Promise((resolve, reject) => {
|
|
64
|
+
textureLoader.load(
|
|
65
|
+
url,
|
|
66
|
+
(texture) => {
|
|
67
|
+
texture.colorSpace = colorSpace;
|
|
68
|
+
copyTextureTransform(texture, referenceTexture);
|
|
69
|
+
texture.needsUpdate = true;
|
|
70
|
+
resolve(texture);
|
|
71
|
+
},
|
|
72
|
+
undefined,
|
|
73
|
+
reject,
|
|
74
|
+
);
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return textureLoadCache.get(cacheKey);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Loads a sibling texture by filename-suffix convention when the asset pack
|
|
82
|
+
// uses one (e.g. Liyue's *_Diffuse/_Normal/_SMBE files). Returns null when the
|
|
83
|
+
// convention does not resolve, so standard-map sources always win.
|
|
84
|
+
async function resolveConventionTexture(sourceUrl, referenceTexture, kind, options) {
|
|
85
|
+
const candidateUrl = textureKindCandidateUrl(sourceUrl, kind);
|
|
86
|
+
if (!candidateUrl || !await textureUrlExists(candidateUrl)) return null;
|
|
87
|
+
return loadEnvironmentTexture(candidateUrl, referenceTexture, options).catch(() => null);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Resolves everything the environment shader can consume for one source
|
|
91
|
+
// material. Standard Three.js/GLTF maps take priority; filename-convention
|
|
92
|
+
// siblings (Liyue-style packs) fill the gaps; author hooks in
|
|
93
|
+
// material.userData override both:
|
|
94
|
+
// userData.envNormalMap / envAoMap / envLightMap / envEmissiveMap
|
|
95
|
+
export async function resolveEnvironmentTextureSet(mat) {
|
|
96
|
+
const sourceMap = mat?.map ?? null;
|
|
97
|
+
const sourceLabel = textureLabel(sourceMap);
|
|
98
|
+
const sourceUrl = textureSourceUrl(sourceMap);
|
|
99
|
+
const sourceMapIsUtility = isUtilityTextureLabel(sourceLabel || sourceUrl);
|
|
100
|
+
const candidateUrl = sourceMapIsUtility ? textureKindCandidateUrl(sourceUrl, 'Diffuse') : null;
|
|
101
|
+
const resolvedDiffuseMap = candidateUrl && await textureUrlExists(candidateUrl)
|
|
102
|
+
? await loadEnvironmentTexture(candidateUrl, sourceMap).catch(() => null)
|
|
103
|
+
: null;
|
|
104
|
+
const packedCandidateUrls = sourceMapIsUtility
|
|
105
|
+
? []
|
|
106
|
+
: [
|
|
107
|
+
['SMBE', textureKindCandidateUrl(sourceUrl, 'SMBE')],
|
|
108
|
+
['LSAB', textureKindCandidateUrl(sourceUrl, 'LSAB')],
|
|
109
|
+
['ESA', textureKindCandidateUrl(sourceUrl, 'ESA')],
|
|
110
|
+
].filter(([, url]) => Boolean(url));
|
|
111
|
+
let packedMap = null;
|
|
112
|
+
let packedMapKind = null;
|
|
113
|
+
|
|
114
|
+
for (const [kind, packedCandidateUrl] of packedCandidateUrls) {
|
|
115
|
+
if (!await textureUrlExists(packedCandidateUrl)) continue;
|
|
116
|
+
packedMap = await loadEnvironmentTexture(packedCandidateUrl, sourceMap).catch(() => null);
|
|
117
|
+
if (packedMap) {
|
|
118
|
+
packedMap.colorSpace = THREE.NoColorSpace;
|
|
119
|
+
packedMap.needsUpdate = true;
|
|
120
|
+
packedMapKind = kind;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const foliageAlphaMap = isFoliageMaterial(mat) && ['LSAB', 'ESA'].includes(packedMapKind)
|
|
125
|
+
? packedMap
|
|
126
|
+
: null;
|
|
127
|
+
|
|
128
|
+
const userData = mat?.userData ?? {};
|
|
129
|
+
const normalMap = userData.envNormalMap
|
|
130
|
+
?? mat?.normalMap
|
|
131
|
+
?? (sourceMapIsUtility ? null : await resolveConventionTexture(
|
|
132
|
+
sourceUrl, sourceMap, 'Normal', { colorSpace: THREE.NoColorSpace }));
|
|
133
|
+
const aoMap = userData.envAoMap ?? mat?.aoMap ?? null;
|
|
134
|
+
const lightMap = userData.envLightMap ?? mat?.lightMap ?? null;
|
|
135
|
+
const emissiveMap = userData.envEmissiveMap ?? mat?.emissiveMap ?? null;
|
|
136
|
+
const baseMap = resolvedDiffuseMap
|
|
137
|
+
?? (sourceMapIsUtility ? fallbackEnvironmentWhiteTexture : sourceMap)
|
|
138
|
+
?? fallbackEnvironmentWhiteTexture;
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
alphaFromLuminance: Boolean(foliageAlphaMap || sourceMapIsUtility),
|
|
142
|
+
alphaMap: mat?.alphaMap ?? (sourceMapIsUtility ? sourceMap : foliageAlphaMap),
|
|
143
|
+
aoMap,
|
|
144
|
+
aoMapIntensity: Number.isFinite(mat?.aoMapIntensity) ? mat.aoMapIntensity : 1,
|
|
145
|
+
baseMap,
|
|
146
|
+
baseMapWasUtility: sourceMapIsUtility,
|
|
147
|
+
emissiveColor: mat?.emissive?.isColor && (mat.emissive.r || mat.emissive.g || mat.emissive.b)
|
|
148
|
+
? mat.emissive.clone()
|
|
149
|
+
: new THREE.Color(1, 1, 1),
|
|
150
|
+
emissiveMap,
|
|
151
|
+
lightMap,
|
|
152
|
+
lightMapIntensity: Number.isFinite(mat?.lightMapIntensity) ? mat.lightMapIntensity : 1,
|
|
153
|
+
normalMap,
|
|
154
|
+
normalScale: mat?.normalScale?.isVector2 ? mat.normalScale.clone() : new THREE.Vector2(1, 1),
|
|
155
|
+
packedMap,
|
|
156
|
+
resolvedDiffuseMap: Boolean(resolvedDiffuseMap),
|
|
157
|
+
// True when the material arrived with no real color texture at all — the
|
|
158
|
+
// untextured/flat-color input class that gets the designed gradient look.
|
|
159
|
+
untextured: baseMap === fallbackEnvironmentWhiteTexture,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
import { environmentBackdropPeriodForHour, applyEnvironmentLampEmissive } from './environmentRigs.js';
|
|
4
|
+
|
|
5
|
+
// One authority for time-of-day environment state. sampleEnvironmentTimeOfDay
|
|
6
|
+
// interpolates keyframed lighting values for any hour; applyEnvironmentTimeOfDay
|
|
7
|
+
// pushes the sampled state into the rigs and converted materials in one call.
|
|
8
|
+
//
|
|
9
|
+
// Keyframes describe the classic anime-interior day: cool night with lamps
|
|
10
|
+
// carrying the room, warm low morning sun, neutral bright midday, amber
|
|
11
|
+
// evening, back to night.
|
|
12
|
+
|
|
13
|
+
const TIME_KEYFRAMES = [
|
|
14
|
+
{
|
|
15
|
+
hour: 0,
|
|
16
|
+
sunColor: 0x9bbcff,
|
|
17
|
+
sunIntensity: 0.1,
|
|
18
|
+
ambientScale: 0.55,
|
|
19
|
+
lampScale: 1.35,
|
|
20
|
+
skyGroundTint: [0.82, 0.86, 1.0],
|
|
21
|
+
skyTopTint: [0.62, 0.72, 1.02],
|
|
22
|
+
fogColor: [0.32, 0.38, 0.55],
|
|
23
|
+
accentScale: 0.2,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
hour: 6,
|
|
27
|
+
sunColor: 0xffb56a,
|
|
28
|
+
sunIntensity: 0.45,
|
|
29
|
+
ambientScale: 0.8,
|
|
30
|
+
lampScale: 0.7,
|
|
31
|
+
skyGroundTint: [1.1, 0.94, 0.82],
|
|
32
|
+
skyTopTint: [0.95, 0.9, 1.0],
|
|
33
|
+
fogColor: [0.85, 0.7, 0.55],
|
|
34
|
+
accentScale: 0.85,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
hour: 12,
|
|
38
|
+
sunColor: 0xfff1d8,
|
|
39
|
+
sunIntensity: 1.0,
|
|
40
|
+
ambientScale: 1.0,
|
|
41
|
+
lampScale: 0.0,
|
|
42
|
+
skyGroundTint: [1.05, 0.97, 0.9],
|
|
43
|
+
skyTopTint: [0.86, 0.96, 1.08],
|
|
44
|
+
fogColor: [0.78, 0.85, 0.95],
|
|
45
|
+
accentScale: 1.0,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
hour: 18,
|
|
49
|
+
sunColor: 0xff884b,
|
|
50
|
+
sunIntensity: 0.5,
|
|
51
|
+
ambientScale: 0.78,
|
|
52
|
+
lampScale: 0.8,
|
|
53
|
+
skyGroundTint: [1.14, 0.9, 0.74],
|
|
54
|
+
skyTopTint: [0.92, 0.84, 0.96],
|
|
55
|
+
fogColor: [0.82, 0.6, 0.45],
|
|
56
|
+
accentScale: 0.9,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
hour: 21,
|
|
60
|
+
sunColor: 0x9bbcff,
|
|
61
|
+
sunIntensity: 0.14,
|
|
62
|
+
ambientScale: 0.58,
|
|
63
|
+
lampScale: 1.3,
|
|
64
|
+
skyGroundTint: [0.84, 0.87, 1.0],
|
|
65
|
+
skyTopTint: [0.64, 0.74, 1.02],
|
|
66
|
+
fogColor: [0.34, 0.4, 0.58],
|
|
67
|
+
accentScale: 0.3,
|
|
68
|
+
},
|
|
69
|
+
// Wrap key (same as hour 0) so interpolation is seamless across midnight.
|
|
70
|
+
{
|
|
71
|
+
hour: 24,
|
|
72
|
+
sunColor: 0x9bbcff,
|
|
73
|
+
sunIntensity: 0.1,
|
|
74
|
+
ambientScale: 0.55,
|
|
75
|
+
lampScale: 1.35,
|
|
76
|
+
skyGroundTint: [0.82, 0.86, 1.0],
|
|
77
|
+
skyTopTint: [0.62, 0.72, 1.02],
|
|
78
|
+
fogColor: [0.32, 0.38, 0.55],
|
|
79
|
+
accentScale: 0.2,
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
function lerpColorHex(a, b, t) {
|
|
84
|
+
return new THREE.Color(a).lerp(new THREE.Color(b), t);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function lerpColorArray(a, b, t) {
|
|
88
|
+
return new THREE.Color(
|
|
89
|
+
THREE.MathUtils.lerp(a[0], b[0], t),
|
|
90
|
+
THREE.MathUtils.lerp(a[1], b[1], t),
|
|
91
|
+
THREE.MathUtils.lerp(a[2], b[2], t),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function sampleEnvironmentTimeOfDay(hour) {
|
|
96
|
+
const normalized = ((Number(hour) % 24) + 24) % 24;
|
|
97
|
+
let previous = TIME_KEYFRAMES[0];
|
|
98
|
+
let next = TIME_KEYFRAMES[TIME_KEYFRAMES.length - 1];
|
|
99
|
+
for (let i = 0; i < TIME_KEYFRAMES.length - 1; i += 1) {
|
|
100
|
+
if (normalized >= TIME_KEYFRAMES[i].hour && normalized <= TIME_KEYFRAMES[i + 1].hour) {
|
|
101
|
+
previous = TIME_KEYFRAMES[i];
|
|
102
|
+
next = TIME_KEYFRAMES[i + 1];
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const span = Math.max(next.hour - previous.hour, 0.001);
|
|
107
|
+
const t = THREE.MathUtils.clamp((normalized - previous.hour) / span, 0, 1);
|
|
108
|
+
|
|
109
|
+
// Sun tracks east-to-west over the day; ratios feed the sun rig placement.
|
|
110
|
+
const dayT = normalized / 24;
|
|
111
|
+
const azimuth = (dayT - 0.5) * Math.PI * 1.6;
|
|
112
|
+
const elevation = Math.max(Math.sin((normalized - 6) / 12 * Math.PI), 0.06);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
accentScale: THREE.MathUtils.lerp(previous.accentScale, next.accentScale, t),
|
|
116
|
+
ambientScale: THREE.MathUtils.lerp(previous.ambientScale, next.ambientScale, t),
|
|
117
|
+
backdropPeriod: environmentBackdropPeriodForHour(normalized),
|
|
118
|
+
fogColor: lerpColorArray(previous.fogColor, next.fogColor, t),
|
|
119
|
+
hour: normalized,
|
|
120
|
+
lampScale: THREE.MathUtils.lerp(previous.lampScale, next.lampScale, t),
|
|
121
|
+
skyGroundTint: lerpColorArray(previous.skyGroundTint, next.skyGroundTint, t),
|
|
122
|
+
skyTopTint: lerpColorArray(previous.skyTopTint, next.skyTopTint, t),
|
|
123
|
+
sunColor: lerpColorHex(previous.sunColor, next.sunColor, t),
|
|
124
|
+
sunIntensity: THREE.MathUtils.lerp(previous.sunIntensity, next.sunIntensity, t),
|
|
125
|
+
sunSourceRatios: {
|
|
126
|
+
x: Math.sin(azimuth) * 0.85,
|
|
127
|
+
y: 0.4 + elevation * 0.6,
|
|
128
|
+
z: -Math.cos(azimuth) * 0.85,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Pushes a sampled state into whatever pieces the caller wires up. Every
|
|
134
|
+
// target is optional, so integrators adopt this incrementally.
|
|
135
|
+
export function applyEnvironmentTimeOfDay(state, {
|
|
136
|
+
backdrop = null,
|
|
137
|
+
environmentRoot = null,
|
|
138
|
+
lampRig = null,
|
|
139
|
+
sunRig = null,
|
|
140
|
+
sunIntensityScale = 1,
|
|
141
|
+
} = {}) {
|
|
142
|
+
if (!state) return;
|
|
143
|
+
|
|
144
|
+
sunRig?.setState({
|
|
145
|
+
color: state.sunColor,
|
|
146
|
+
intensity: state.sunIntensity * sunIntensityScale,
|
|
147
|
+
sourceRatios: state.sunSourceRatios,
|
|
148
|
+
diskOpacity: 0.62 * state.accentScale,
|
|
149
|
+
spillOpacity: 0.3 * state.accentScale,
|
|
150
|
+
beamOpacity: 0.28 * state.accentScale,
|
|
151
|
+
shaftOpacity: 0.1 * state.accentScale,
|
|
152
|
+
});
|
|
153
|
+
lampRig?.setIntensity(state.lampScale);
|
|
154
|
+
if (environmentRoot && lampRig) applyEnvironmentLampEmissive(environmentRoot, Math.max(state.lampScale, 0.15));
|
|
155
|
+
backdrop?.setPeriod(state.backdropPeriod);
|
|
156
|
+
|
|
157
|
+
environmentRoot?.traverse?.((obj) => {
|
|
158
|
+
if (!obj.isMesh || !obj.material) return;
|
|
159
|
+
const materials = Array.isArray(obj.material) ? obj.material : [obj.material];
|
|
160
|
+
for (const mat of materials) {
|
|
161
|
+
if (!mat?.userData?.environmentMaterial || !mat.uniforms) continue;
|
|
162
|
+
mat.uniforms.skyGroundTint?.value?.copy?.(state.skyGroundTint);
|
|
163
|
+
mat.uniforms.skyTopTint?.value?.copy?.(state.skyTopTint);
|
|
164
|
+
mat.uniforms.heightFogColor?.value?.copy?.(state.fogColor);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
// Conversion-time per-vertex ambient occlusion for environment meshes.
|
|
4
|
+
//
|
|
5
|
+
// This is the grounding lever for untextured/flat-color scenes: one bake at
|
|
6
|
+
// conversion, zero per-frame cost, consumed by the environment shader through
|
|
7
|
+
// the envVertexAo attribute (USE_ENV_VERTEX_AO). Deterministic by design —
|
|
8
|
+
// fixed hemisphere directions, no randomness — so captures are repeatable.
|
|
9
|
+
//
|
|
10
|
+
// Uses three-mesh-bvh for the occluder queries; if the dependency cannot be
|
|
11
|
+
// loaded the bake is skipped with a warning rather than falling back to a
|
|
12
|
+
// pathologically slow linear raycast.
|
|
13
|
+
|
|
14
|
+
const EPSILON_RATIO = 0.01;
|
|
15
|
+
|
|
16
|
+
// Fixed cosine-weighted hemisphere directions (golden-spiral layout) in
|
|
17
|
+
// tangent space (+Z = surface normal).
|
|
18
|
+
function hemisphereDirections(count) {
|
|
19
|
+
const directions = [];
|
|
20
|
+
const golden = Math.PI * (3 - Math.sqrt(5));
|
|
21
|
+
for (let i = 0; i < count; i += 1) {
|
|
22
|
+
// Cosine-weighted: z = sqrt(1 - t) concentrates samples toward the pole.
|
|
23
|
+
const t = (i + 0.5) / count;
|
|
24
|
+
const z = Math.sqrt(1 - t);
|
|
25
|
+
const radius = Math.sqrt(t);
|
|
26
|
+
const angle = golden * i;
|
|
27
|
+
directions.push(new THREE.Vector3(
|
|
28
|
+
Math.cos(angle) * radius,
|
|
29
|
+
Math.sin(angle) * radius,
|
|
30
|
+
z,
|
|
31
|
+
));
|
|
32
|
+
}
|
|
33
|
+
return directions;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildOccluderGeometry(occluderRoot) {
|
|
37
|
+
const positions = [];
|
|
38
|
+
const vertex = new THREE.Vector3();
|
|
39
|
+
occluderRoot.updateMatrixWorld(true);
|
|
40
|
+
occluderRoot.traverse((obj) => {
|
|
41
|
+
if (!obj.isMesh || !obj.geometry?.attributes?.position) return;
|
|
42
|
+
if (obj.userData?.environmentShaderExclude) return;
|
|
43
|
+
if (obj.userData?.environmentVertexAoOccluderExclude) return;
|
|
44
|
+
const geometry = obj.geometry;
|
|
45
|
+
const position = geometry.attributes.position;
|
|
46
|
+
const index = geometry.index;
|
|
47
|
+
const count = index ? index.count : position.count;
|
|
48
|
+
for (let i = 0; i < count; i += 1) {
|
|
49
|
+
const vertexIndex = index ? index.getX(i) : i;
|
|
50
|
+
vertex.fromBufferAttribute(position, vertexIndex).applyMatrix4(obj.matrixWorld);
|
|
51
|
+
positions.push(vertex.x, vertex.y, vertex.z);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
if (positions.length === 0) return null;
|
|
55
|
+
const merged = new THREE.BufferGeometry();
|
|
56
|
+
merged.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
57
|
+
return merged;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function bakeEnvironmentVertexAo(meshes, {
|
|
61
|
+
occluderRoot = null,
|
|
62
|
+
environmentBox = null,
|
|
63
|
+
rayCount = 12,
|
|
64
|
+
maxDistance = null,
|
|
65
|
+
vertexBudget = 200000,
|
|
66
|
+
// Occlusion floor keeps fully-enclosed corners readable instead of black.
|
|
67
|
+
occlusionFloor = 0.22,
|
|
68
|
+
// Yield to the main thread between slices so big bakes do not jank a
|
|
69
|
+
// running frame loop; pass 0 to bake synchronously (deterministic scripts).
|
|
70
|
+
sliceSize = 6000,
|
|
71
|
+
onProgress = null,
|
|
72
|
+
shouldContinue = () => true,
|
|
73
|
+
} = {}) {
|
|
74
|
+
const targets = (Array.isArray(meshes) ? meshes : [meshes]).filter((mesh) => mesh?.isMesh);
|
|
75
|
+
if (targets.length === 0 || !occluderRoot) return { bakedMeshCount: 0, skippedMeshCount: 0 };
|
|
76
|
+
|
|
77
|
+
let MeshBVH;
|
|
78
|
+
try {
|
|
79
|
+
({ MeshBVH } = await import('three-mesh-bvh'));
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.warn('[environmentVertexAo] three-mesh-bvh unavailable; skipping vertex AO bake.', error);
|
|
82
|
+
return { bakedMeshCount: 0, skippedMeshCount: targets.length };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const occluderGeometry = buildOccluderGeometry(occluderRoot);
|
|
86
|
+
if (!occluderGeometry) return { bakedMeshCount: 0, skippedMeshCount: targets.length };
|
|
87
|
+
const bvh = new MeshBVH(occluderGeometry);
|
|
88
|
+
|
|
89
|
+
function abortedResult() {
|
|
90
|
+
occluderGeometry.dispose();
|
|
91
|
+
return {
|
|
92
|
+
aborted: true,
|
|
93
|
+
bakedMeshCount,
|
|
94
|
+
skippedMeshCount: targets.length - bakedMeshCount,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const size = environmentBox?.getSize(new THREE.Vector3()) ?? null;
|
|
99
|
+
const reach = Number.isFinite(maxDistance) && maxDistance > 0
|
|
100
|
+
? maxDistance
|
|
101
|
+
: size
|
|
102
|
+
? Math.max((size.x + size.y + size.z) / 3 * 0.35, 0.5)
|
|
103
|
+
: 4;
|
|
104
|
+
const epsilon = reach * EPSILON_RATIO;
|
|
105
|
+
const directions = hemisphereDirections(Math.max(4, Math.round(rayCount)));
|
|
106
|
+
|
|
107
|
+
const ray = new THREE.Ray();
|
|
108
|
+
const normalMatrix = new THREE.Matrix3();
|
|
109
|
+
const worldPosition = new THREE.Vector3();
|
|
110
|
+
const worldNormal = new THREE.Vector3();
|
|
111
|
+
const tangent = new THREE.Vector3();
|
|
112
|
+
const bitangent = new THREE.Vector3();
|
|
113
|
+
const sampleDir = new THREE.Vector3();
|
|
114
|
+
|
|
115
|
+
let bakedMeshCount = 0;
|
|
116
|
+
let skippedMeshCount = 0;
|
|
117
|
+
let usedVertexBudget = 0;
|
|
118
|
+
let processedSinceYield = 0;
|
|
119
|
+
|
|
120
|
+
for (const mesh of targets) {
|
|
121
|
+
if (!shouldContinue()) return abortedResult();
|
|
122
|
+
const geometry = mesh.geometry;
|
|
123
|
+
const position = geometry?.attributes?.position;
|
|
124
|
+
const normal = geometry?.attributes?.normal;
|
|
125
|
+
if (!position || !normal) {
|
|
126
|
+
skippedMeshCount += 1;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (usedVertexBudget + position.count > vertexBudget) {
|
|
130
|
+
skippedMeshCount += 1;
|
|
131
|
+
console.warn(
|
|
132
|
+
`[environmentVertexAo] Skipping "${mesh.name || 'unnamed mesh'}" (${position.count} verts): `
|
|
133
|
+
+ `vertex budget ${vertexBudget} exhausted. Raise vertexAoOptions.vertexBudget to include it.`,
|
|
134
|
+
);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
usedVertexBudget += position.count;
|
|
138
|
+
|
|
139
|
+
mesh.updateMatrixWorld(true);
|
|
140
|
+
normalMatrix.getNormalMatrix(mesh.matrixWorld);
|
|
141
|
+
const ao = new Float32Array(position.count);
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < position.count; i += 1) {
|
|
144
|
+
worldPosition.fromBufferAttribute(position, i).applyMatrix4(mesh.matrixWorld);
|
|
145
|
+
worldNormal.fromBufferAttribute(normal, i).applyMatrix3(normalMatrix).normalize();
|
|
146
|
+
|
|
147
|
+
// Orthonormal basis around the world normal.
|
|
148
|
+
if (Math.abs(worldNormal.x) > 0.9) tangent.set(0, 1, 0);
|
|
149
|
+
else tangent.set(1, 0, 0);
|
|
150
|
+
bitangent.crossVectors(worldNormal, tangent).normalize();
|
|
151
|
+
tangent.crossVectors(bitangent, worldNormal).normalize();
|
|
152
|
+
|
|
153
|
+
let occlusion = 0;
|
|
154
|
+
let totalWeight = 0;
|
|
155
|
+
for (const dir of directions) {
|
|
156
|
+
sampleDir.copy(tangent).multiplyScalar(dir.x)
|
|
157
|
+
.addScaledVector(bitangent, dir.y)
|
|
158
|
+
.addScaledVector(worldNormal, dir.z)
|
|
159
|
+
.normalize();
|
|
160
|
+
ray.origin.copy(worldPosition).addScaledVector(worldNormal, epsilon);
|
|
161
|
+
ray.direction.copy(sampleDir);
|
|
162
|
+
const weight = dir.z;
|
|
163
|
+
totalWeight += weight;
|
|
164
|
+
const hit = bvh.raycastFirst(ray, THREE.DoubleSide);
|
|
165
|
+
if (hit && hit.distance < reach) {
|
|
166
|
+
occlusion += weight * (1 - hit.distance / reach);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const visibility = 1 - (totalWeight > 0 ? occlusion / totalWeight : 0);
|
|
170
|
+
ao[i] = THREE.MathUtils.clamp(
|
|
171
|
+
occlusionFloor + (1 - occlusionFloor) * visibility, 0, 1);
|
|
172
|
+
|
|
173
|
+
processedSinceYield += 1;
|
|
174
|
+
if (sliceSize > 0 && processedSinceYield >= sliceSize) {
|
|
175
|
+
processedSinceYield = 0;
|
|
176
|
+
onProgress?.(usedVertexBudget, vertexBudget);
|
|
177
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
178
|
+
if (!shouldContinue()) return abortedResult();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
geometry.setAttribute('envVertexAo', new THREE.BufferAttribute(ao, 1));
|
|
183
|
+
bakedMeshCount += 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
occluderGeometry.dispose();
|
|
187
|
+
return { bakedMeshCount, skippedMeshCount };
|
|
188
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Environment shading barrel. Import from '@call-me-sensei/toonlab/environment'.
|
|
2
|
+
export * from './environmentMaterialAdapter.js';
|
|
3
|
+
export * from './environmentSettings.js';
|
|
4
|
+
export * from './environmentPresets.js';
|
|
5
|
+
export * from './environmentRigs.js';
|
|
6
|
+
export * from './environmentTimeOfDay.js';
|
|
7
|
+
export * from './environmentAmbientProbe.js';
|
|
8
|
+
export * from './environmentPlanarReflection.js';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
import { copyTextureTransform } from './environmentTextureResolver.js';
|
|
4
|
+
|
|
5
|
+
// Photoscan assets (Megascans/Fab exports) read as "posterized photos" under
|
|
6
|
+
// the toon environment shader: their albedo carries photographic micro-detail
|
|
7
|
+
// (grain, dirt, cracks, baked-in AO) that stylized stages never have, and
|
|
8
|
+
// their detail maps (normal/AO) respond too literally to light. This module
|
|
9
|
+
// is the opt-in stylization pass that pushes such assets toward an authored,
|
|
10
|
+
// hand-painted read without touching the geometry:
|
|
11
|
+
//
|
|
12
|
+
// 1. Albedo simplify — redraw the base map at a capped size through a blur
|
|
13
|
+
// + saturation lift so photo grain collapses into painterly gradients.
|
|
14
|
+
// 2. Detail compression — reduce normal-map strength, soften AO and warm
|
|
15
|
+
// its tint (roughness/metalness are already ignored by the toon path).
|
|
16
|
+
// 3. Painterly color response — raise saturation, cool the shade tint, and
|
|
17
|
+
// add a subtle broad specular sheen so edges catch the sun.
|
|
18
|
+
//
|
|
19
|
+
// Applied per-material inside applyEnvironmentShader when scanStylize is
|
|
20
|
+
// 'auto' (detected scan assets only) or true; never applied when false or
|
|
21
|
+
// when material.userData.envScanStylize === false.
|
|
22
|
+
|
|
23
|
+
// Fab/Megascans exports name meshes `<assetId>_LOD0_TIER2_...` and materials
|
|
24
|
+
// `<Asset_Name>_<assetId>_<Quality>`; the LOD/TIER token is the reliable
|
|
25
|
+
// tell. Explicit 'megascan'/'quixel' naming also qualifies.
|
|
26
|
+
const SCAN_NAME_PATTERN = /(_lod\d+_tier\d+)|megascan|quixel/i;
|
|
27
|
+
|
|
28
|
+
export function isScanAssetMaterial(obj, mat) {
|
|
29
|
+
if (mat?.userData?.envScanStylize === false) return false;
|
|
30
|
+
if (mat?.userData?.envScanStylize === true) return true;
|
|
31
|
+
return SCAN_NAME_PATTERN.test(`${obj?.name ?? ''} ${mat?.name ?? ''}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const DEFAULT_SCAN_STYLIZE_PARAMS = {
|
|
35
|
+
// Albedo simplification. Blur is in pixels at the capped size, so it scales
|
|
36
|
+
// with however large the source texture was.
|
|
37
|
+
albedoMaxSize: 512,
|
|
38
|
+
albedoBlur: 1.6,
|
|
39
|
+
albedoSaturate: 1.18,
|
|
40
|
+
albedoBrightness: 1.04,
|
|
41
|
+
// Detail-map response compression.
|
|
42
|
+
normalMapStrength: 0.35,
|
|
43
|
+
aoMapStrength: 0.65,
|
|
44
|
+
aoWarmth: 0.7,
|
|
45
|
+
// Painterly color response: saturated color, cool shade, sun-catch sheen.
|
|
46
|
+
saturation: 1.2,
|
|
47
|
+
shadowTintColor: [0.7, 0.76, 0.94],
|
|
48
|
+
specularStrength: 0.1,
|
|
49
|
+
specularShininess: 24,
|
|
50
|
+
specularSoftness: 0.5,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const stylizedTextureCache = new WeakMap();
|
|
54
|
+
|
|
55
|
+
// Returns a simplified copy of a scan albedo texture (cached per source).
|
|
56
|
+
// The source draws twice: once sharp so canvas-edge blur transparency never
|
|
57
|
+
// shows at UV seams, once through the blur/saturate filter on top.
|
|
58
|
+
export function stylizeScanBaseMap(texture, params = DEFAULT_SCAN_STYLIZE_PARAMS) {
|
|
59
|
+
const image = texture?.image;
|
|
60
|
+
if (!image || !(image.width > 0) || !(image.height > 0)) return texture;
|
|
61
|
+
if (stylizedTextureCache.has(texture)) return stylizedTextureCache.get(texture);
|
|
62
|
+
|
|
63
|
+
const scale = Math.min(1, params.albedoMaxSize / Math.max(image.width, image.height));
|
|
64
|
+
const canvas = document.createElement('canvas');
|
|
65
|
+
canvas.width = Math.max(1, Math.round(image.width * scale));
|
|
66
|
+
canvas.height = Math.max(1, Math.round(image.height * scale));
|
|
67
|
+
const ctx = canvas.getContext('2d');
|
|
68
|
+
ctx.imageSmoothingEnabled = true;
|
|
69
|
+
ctx.imageSmoothingQuality = 'high';
|
|
70
|
+
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
71
|
+
ctx.filter = `blur(${params.albedoBlur}px) saturate(${params.albedoSaturate}) brightness(${params.albedoBrightness})`;
|
|
72
|
+
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
73
|
+
|
|
74
|
+
const stylized = new THREE.CanvasTexture(canvas);
|
|
75
|
+
copyTextureTransform(stylized, texture);
|
|
76
|
+
stylized.colorSpace = texture.colorSpace ?? THREE.SRGBColorSpace;
|
|
77
|
+
stylized.needsUpdate = true;
|
|
78
|
+
stylizedTextureCache.set(texture, stylized);
|
|
79
|
+
return stylized;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Compresses the detail-map and color response of a converted environment
|
|
83
|
+
// ShaderMaterial. No-op for the basic/standard debug material modes.
|
|
84
|
+
export function applyScanStylizeToMaterial(material, params = DEFAULT_SCAN_STYLIZE_PARAMS) {
|
|
85
|
+
const uniforms = material?.uniforms;
|
|
86
|
+
if (!uniforms) return material;
|
|
87
|
+
if (uniforms.normalMapStrength) uniforms.normalMapStrength.value = params.normalMapStrength;
|
|
88
|
+
if (uniforms.aoMapStrength) {
|
|
89
|
+
uniforms.aoMapStrength.value = Math.min(uniforms.aoMapStrength.value, params.aoMapStrength);
|
|
90
|
+
}
|
|
91
|
+
if (uniforms.aoWarmth) uniforms.aoWarmth.value = params.aoWarmth;
|
|
92
|
+
if (uniforms.saturation) uniforms.saturation.value = params.saturation;
|
|
93
|
+
if (uniforms.shadowTintColor && params.shadowTintColor) {
|
|
94
|
+
uniforms.shadowTintColor.value.setRGB(...params.shadowTintColor);
|
|
95
|
+
}
|
|
96
|
+
if (uniforms.specularStrength && params.specularStrength > uniforms.specularStrength.value) {
|
|
97
|
+
uniforms.specularStrength.value = params.specularStrength;
|
|
98
|
+
uniforms.specularShininess.value = params.specularShininess;
|
|
99
|
+
uniforms.specularSoftness.value = params.specularSoftness;
|
|
100
|
+
}
|
|
101
|
+
material.userData.scanStylized = true;
|
|
102
|
+
return material;
|
|
103
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export * from './toon/toonMaterialAdapter.js';
|
|
2
|
+
export * from './toon/toonSettings.js';
|
|
3
|
+
export * from './toon/characterRenderPasses.js';
|
|
4
|
+
export * from './environment/environmentMaterialAdapter.js';
|
|
5
|
+
export * from './environment/environmentSettings.js';
|
|
6
|
+
export * from './environment/environmentAmbientProbe.js';
|
|
7
|
+
export * from './environment/environmentPlanarReflection.js';
|
|
8
|
+
export * from './environment/environmentPresets.js';
|
|
9
|
+
export * from './environment/environmentRigs.js';
|
|
10
|
+
export * from './environment/environmentTimeOfDay.js';
|
|
11
|
+
export * from './post/postProcessing.js';
|
|
12
|
+
export * from './water/water.js';
|
|
13
|
+
export * from './sky/stylizedSky.js';
|
|
14
|
+
export * from './vegetation/stylizedGrass.js';
|
|
15
|
+
export * from './vegetation/stylizedTreeFoliage.js';
|
|
16
|
+
export * from './vegetation/stylizedTree.js';
|
|
17
|
+
export * from './vegetation/stylizedFlowers.js';
|
|
18
|
+
export * from './character/characterRig.js';
|
|
19
|
+
export * from './character/freestyleSwimClip.js';
|
|
20
|
+
export * from './rockgen/index.js';
|
|
21
|
+
export * from './debrisgen/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../character/modelLoader.js';
|