Setting the file. One moment.
Ice Material · Frost Sequence Camera Orbit · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page Three Mesh BVH LICENSE
Position
39 of 76 source/src/ice/ IceMaterial.ts
TypeScript · 600 lines · 22 KB
Fn
,
10 vec3 ,
11 vec4 ,
12 float ,
13 uniform ,
14 varying ,
15 texture ,
16 vec2 ,
17 positionLocal ,
18 normalLocal ,
19 cameraPosition ,
20 transformNormalToView ,
21 normalize ,
22 dot ,
23 length ,
24 max ,
25 min ,
26 abs ,
27 pow ,
28 exp ,
29 mix ,
30 smoothstep ,
31 clamp ,
32 reflect ,
33 select ,
34 If ,
35 Loop ,
36 Break ,
37 int ,
38 output ,
39 screenUV ,
40 } = tsl;
41 import type { ShapeSpec } from "../shape/sdf" ;
42 import { ErosionField } from "../erosion/ErosionField" ;
43 import {
44 fbm,
45 gnoise,
46 vnoise3,
47 hash31,
48 voronoiCell,
49 voronoiCell8,
50 voronoiEdge,
51 softThreshold,
52 saturate,
53 } from "../tsl/noise" ;
54 import { D } from "../dials/store" ;
55 import { rng } from "../core/seed" ;
56 import { ICE_VARIANT } from "./variant" ;
57 import { opticalTransport } from "./OpticalTransport" ;
58 import { iceSurface, iceSmudgeDirections } from "./SharedSurface" ;
59 import { iceDetail, iceFrost, iceInclusions } from "./SurfaceDetail" ;
60 import { MATERIAL_FEATURES } from "./features" ;
61
62 type N = any ;
63
64 export interface IceMaterialOptions {
65 shape : ShapeSpec ;
66 erosion : ErosionField ;
67 seed : number ;
68 blueNoise : THREE . Texture ;
69 dispersion ?: boolean ;
70 voronoiCells ?: "27" | "8" ;
71 environment ?: THREE . Texture ;
72 fractureDetail ?: THREE . Texture ;
73 backdrop ?: any ;
74 meshEntry ?: any ;
75 glassMatcap ?: any ;
76 }
77 export interface IceMaterialBundle {
78 material : THREE . MeshPhysicalNodeMaterial ;
79 uniforms : Record < string , any >;
80 features : Record < string , any >;
81 update (
82 t : number ,
83 dt : number ,
84 keyLight : THREE . DirectionalLight ,
85 objectGroup : THREE . Object3D ,
86 ) : void ;
87 }
88
89 let frameCounter = 0 ;
90 // HyperFrames adapter: keep the original deterministic frame clock.
91 let frameIndexOverride = - 1 ;
92 export function setIceFrameIndex ( index : number ) {
93 frameIndexOverride = index;
94 }
95
96 export function createIceMaterial ({
97 shape ,
98 erosion ,
99 seed ,
100 environment ,
101 fractureDetail ,
102 backdrop ,
103 meshEntry ,
104 glassMatcap ,
105 voronoiCells = "27" ,
106 } : IceMaterialOptions ) : IceMaterialBundle {
107 const voro = voronoiCells === "8" ? voronoiCell8 : voronoiCell;
108 const rand = rng (seed + 11 );
109 const u = {
110 model: uniform ( new THREE . Matrix4 ()),
111 envStrength: uniform ( 1 ),
112 backlight: uniform ( 0.4 ),
113 inclusionAmount: uniform ( 0.3 ),
114 inclusionScale: uniform ( 0.4 ),
115 baseColor: uniform ( new THREE . Color ( "#f8f8f8" )),
116 ior: uniform ( 1.36 ),
117 dispersion: uniform ( 0.02 ),
118 thicknessScale: uniform ( 1 ),
119 attDist: uniform ( 0.9 ),
120 attColor: uniform ( new THREE . Color ( "#cfd2d4" )),
121 baseRough: uniform ( 0.05 ),
122 frostScale: uniform ( 1.6 ),
123 frostThreshold: uniform ( 0.5 ),
124 frostSoftness: uniform ( 0.28 ),
125 frostRough: uniform ( 0.65 ),
126 frostDiffuse: uniform ( 0.55 ),
127 crystalBump: uniform ( 0.12 ),
128 crystalScale: uniform ( 28 ),
129 crackLarge: uniform ( 1.6 ),
130 crackWarp: uniform ( 0.45 ),
131 crackCoverage: uniform ( 0.55 ),
132 regionScale: uniform ( 0.8 ),
133 regionCoverage: uniform ( 0.7 ),
134 veinScale: uniform ( 5 ),
135 veinContrast: uniform ( 0.7 ),
136 crackFine: uniform ( 5.5 ),
137 crackFineAmount: uniform ( 0.45 ),
138 fineCoverage: uniform ( 0.35 ),
139 fineNearLarge: uniform ( 0.75 ),
140 crackWidth: uniform ( 0.004 ),
141 crackBright: uniform ( 0.4 ),
142 crackDark: uniform ( 0.5 ),
143 crackRefr: uniform ( 0.02 ),
144 surfaceCrack: uniform ( 0.35 ),
145 crackSteps: uniform ( 14 ),
146 smudgeAmount: uniform ( 0.6 ),
147 smudgeCoverage: uniform ( 0.55 ),
148 smudgeMaskScale: uniform ( 1.2 ),
149 smudgeAniso: uniform ( 6 ),
150 smudgeRough: uniform ( 0.35 ),
151 smudgeWhite: uniform ( 0.08 ),
152 smudgeScale: uniform ( 3.2 ),
153 microBump: uniform ( 0.25 ),
154 microScale: uniform ( 60 ),
155 microCoverage: uniform ( 0.6 ),
156 rippleBump: uniform ( 0.3 ),
157 rippleScale: uniform ( 9 ),
158 bumpMaskScale: uniform ( 1.5 ),
159 clearcoat: uniform ( 0.3 ),
160 clearcoatRough: uniform ( 0.08 ),
161 specularIntensity: uniform ( 1 ),
162 interiorScatter: uniform ( 0.25 ),
163 deformAmp: uniform ( 0.045 ),
164 deformFreq: uniform ( 1.1 ),
165 seed: uniform (seed),
166 edgeWidth: uniform ( 0.35 ),
167 edgeInset: uniform ( 0.06 ),
168 edgeBump: uniform ( 0.6 ),
169 edgeBumpScale: uniform ( 55 ),
170 refrostStrength: uniform ( 1 ),
171 cutThreshold: uniform ( 0.7 ),
172 cutSoft: uniform ( 0.04 ),
173 interiorSteps: uniform ( 24 ),
174 interiorFrost: uniform ( 0 ),
175 crumbleGlow: uniform ( 0.15 ),
176 edgeWhite: uniform ( 0.5 ),
177 lightDir: uniform ( new THREE . Vector3 ( 0 , 1 , 0 )),
178 modelInv: uniform ( new THREE . Matrix4 ()),
179 jitter: uniform ( new THREE . Vector2 ()),
180 adaptiveSteps: uniform ( 0 ),
181 powderTone: uniform ( new THREE . Color ( "#dcdcdc" )),
182 keyIntensity: uniform ( 3.2 ),
183 keyColor: uniform ( new THREE . Color ( "#ffffff" )),
184 hover: uniform ( 0 ),
185 };
186 const f = Object. fromEntries (
187 Object. keys ( MATERIAL_FEATURES ). map (( name ) => [name, uniform ( Number ( MATERIAL_FEATURES [name]))]),
188 ) as Record < string , any >;
189 const seedV = vec3 (seed * 0.731 , seed * 0.137 , seed * 0.529 );
190 const smudgeDirs = iceSmudgeDirections (seed);
191
192 const material : any = RW .matcap
193 ? new THREE . MeshBasicNodeMaterial ()
194 : new THREE . MeshPhysicalNodeMaterial ();
195 const lightDir = new THREE . Vector3 ();
196 const objectRotation = new THREE . Quaternion ();
197 let settingsVersion = - 1 ;
198 // Render the raymarched solid-ice path directly: BackSide keeps the original interior shading, visible from the
199 // intact first frame (DoubleSide would draw this proxy first, then cover it with the front-shell finish).
200 // cutRes finds the first remaining solid along the ray; cutHit discards the proxy when the ray holds no ice.
201 material.side = THREE .BackSide;
202 material.transparent = false ;
203 material.metalness = 0 ;
204 material.transmission = 1 ; // enables the transmission pass; the node below drives the value
205
206 // ---------- vertex: deformation + erosion inset + crack notches
207 const deformAt = ( p : N ) => fbm (p. mul (u.deformFreq). add (seedV), 3 ). mul (u.deformAmp);
208 // crumbly edge band just below the cut threshold
209 const edgeBandOf = ( er : N ) =>
210 smoothstep (u.cutThreshold. sub (u.edgeWidth), u.cutThreshold. sub (u.edgeWidth. mul ( 0.15 )), er);
211 const displaced = Fn (() => {
212 const p = positionLocal,
213 n = normalLocal;
214 const d = deformAt (p);
215 const er = erosion. sample (p).r;
216 const edge = edgeBandOf (er);
217 const notch = erosion
218 . sampleCrack (p)
219 .w. mul (u.surfaceCrack)
220 . mul (f.cracks)
221 . mul (f.surfaceBumps)
222 . mul ( 0.012 );
223 return p. add (n. mul (d. sub (edge. mul (u.edgeInset)). sub (notch)));
224 })();
225 material.positionNode = displaced;
226 const vPos = varying (displaced);
227
228 // ---------- one material, evaluated at the first remaining solid on the ray
229 const camObj = u.modelInv. mul ( vec4 (cameraPosition, 1.0 )).xyz;
230 const V = normalize (vPos. sub (camObj));
231 const L = u.lightDir;
232 const meshPosition = meshEntry?.position. sample (screenUV);
233 const meshNormal = meshEntry?.normal. sample (screenUV).xyz;
234 const meshValid = meshEntry
235 ? meshEntry.depth
236 . sample (screenUV)
237 .r. lessThan ( 0.999999 )
238 . and (erosion. sample (meshPosition.xyz).r. lessThan (u.cutThreshold))
239 : null ;
240 const cutRes = Fn (() => {
241 const ro = camObj;
242 // The voxel SDF clamps to its border outside [-bound, bound]. Those values
243 // are not distances from the camera: marching from t=0 can jump across the
244 // entire thin logo/text and leave cutHit=-1. Enter the texture's valid box
245 // analytically before sampling, and never search beyond the proxy surface.
246 const reciprocal = ( d : N ) =>
247 select (d. greaterThanEqual ( 0 ), float ( 1 ), float ( - 1 )). div ( max ( abs (d), 1e-6 ));
248 const invRay = vec3 ( reciprocal ( V .x), reciprocal ( V .y), reciprocal ( V .z));
249 const a = vec3 ( - shape.bound). sub (ro). mul (invRay);
250 const b = vec3 (shape.bound). sub (ro). mul (invRay);
251 const near = min (a, b),
252 far = max (a, b);
253 const tStart = max ( max (near.x, max (near.y, near.z)), 0 );
254 const tMax = min ( min (far.x, min (far.y, far.z)), length (vPos. sub (camObj)));
255 const result = vec4 (vPos, - 1 ). toVar ();
256 if (meshEntry)
257 If (meshValid, () => {
258 result. assign ( vec4 (meshPosition.xyz, length (meshPosition.xyz. sub (camObj))));
259 });
260 If (tMax. greaterThan (tStart). and (result.w. lessThan ( 0 )), () => {
261 // Raster coverage already establishes the exact mesh silhouette. The
262 // interpolated voxel field has a finite surface error, especially in thin
263 // rotating glyphs. A strict negative-distance test clipped their far edge.
264 const voxel = float (( 2 * erosion.bound) / erosion.res);
265 const surfaceVoxel = shape.voxelSize ?? voxel;
266 const shell = surfaceVoxel. mul ( 0.4 );
267 const t = tStart. toVar ();
268 Loop ( 192 , () => {
269 If (t. greaterThan (tMax), () => {
270 Break ();
271 });
272 const q = ro. add ( V . mul (t));
273 const d = shape. sdfNode (q);
274 If (d. lessThanEqual ( 0.001 ). and (erosion. sample (q).r. lessThan (u.cutThreshold)), () => {
275 result. assign ( vec4 (q, t));
276 Break ();
277 });
278 // Conservative steps near the surface and through cut-away ice; no
279 // whole-chord subsampling that can step across a thin surviving glyph.
280 t. addAssign ( max (d. sub (shell). mul ( 0.7 ), surfaceVoxel. mul ( 0.35 )));
281 });
282 // The rasterized far surface is an exact endpoint, even when the voxel
283 // SDF misses a bevel. Only accept it when the same erosion field is solid.
284 If (result.w. lessThan ( 0 ). and (erosion. sample (vPos).r. lessThan (u.cutThreshold)), () => {
285 result. assign ( vec4 (vPos, tMax));
286 });
287 });
288 return result;
289 })();
290 const cutHit = cutRes.w;
291 const cutPos = cutRes.xyz;
292 // outward normal of the solid at the cut: erosion gradient (points into the void), else the shell normal
293 const ge = 0.02 ;
294 const cutGrad = vec3 (
295 erosion. sample (cutPos. add ( vec3 (ge, 0 , 0 ))).r. sub (erosion. sample (cutPos. sub ( vec3 (ge, 0 , 0 ))).r),
296 erosion. sample (cutPos. add ( vec3 ( 0 , ge, 0 ))).r. sub (erosion. sample (cutPos. sub ( vec3 ( 0 , ge, 0 ))).r),
297 erosion. sample (cutPos. add ( vec3 ( 0 , 0 , ge))).r. sub (erosion. sample (cutPos. sub ( vec3 ( 0 , 0 , ge))).r),
298 );
299 const cutGradLen = length (cutGrad);
300 // Shade the actual hit surface, not the proxy's far wall.
301 const shapeEpsilon = shape.voxelSize ? shape.voxelSize. mul ( 0.75 ) : float (ge);
302 const shapeGrad = vec3 (
303 shape
304 . sdfNode (cutPos. add ( vec3 (shapeEpsilon, 0 , 0 )))
305 . sub (shape. sdfNode (cutPos. sub ( vec3 (shapeEpsilon, 0 , 0 )))),
306 shape
307 . sdfNode (cutPos. add ( vec3 ( 0 , shapeEpsilon, 0 )))
308 . sub (shape. sdfNode (cutPos. sub ( vec3 ( 0 , shapeEpsilon, 0 )))),
309 shape
310 . sdfNode (cutPos. add ( vec3 ( 0 , 0 , shapeEpsilon)))
311 . sub (shape. sdfNode (cutPos. sub ( vec3 ( 0 , 0 , shapeEpsilon)))),
312 );
313 const sdfNormal = () =>
314 select (
315 length (shapeGrad). greaterThan ( 1e-5 ),
316 shapeGrad. div ( max ( length (shapeGrad), 1e-5 )),
317 normalLocal. negate (),
318 );
319 const shapeNormal = meshEntry
320 ? Fn (() => {
321 const n = vec3 ( 0 ). toVar ();
322 If (meshValid, () => n. assign ( normalize (meshNormal))). Else (() => n. assign ( sdfNormal ()));
323 return n;
324 })()
325 : sdfNormal ();
326 const nBase = normalize (
327 mix (
328 shapeNormal,
329 cutGrad. div ( max (cutGradLen, 1e-5 )),
330 smoothstep ( 0.02 , 0.15 , cutGradLen). mul (f.cutNormals),
331 ),
332 );
333 const p = cutPos;
334 const { frost , smudge , surfCrack , nSurface , regionMask , roughness } = iceSurface (
335 p,
336 nBase,
337 seedV,
338 smudgeDirs,
339 erosion,
340 u,
341 f,
342 RW .aa,
343 );
344 // The back-face proxy is flipped by Three; supply the opposite normal.
345 material.normalNode = transformNormalToView (nSurface. negate ());
346 const tExit = length (vPos. sub (cutPos));
347
348 // Sparse hairline fissures at several depths. Cell transitions used to shade
349 // entire planes, covering the transparent windows with white polygon tiles.
350 // Evaluate actual distance to each fissure instead; energy is bounded and
351 // normalized so extra sampling cannot turn the whole volume opaque.
352 const march = Fn (() => {
353 const glint = float ( 0 ). toVar ();
354 const steps = clamp (u.crackSteps. div ( 4 ). floor (), 2 , 6 ). toVar ();
355 Loop ({ start: int ( 0 ), end: int (steps), type: "int" , condition: "<" }, ({ i } : any ) => {
356 const depth = float (i). add ( 0.5 ). div (steps);
357 const q = cutPos. add ( V . mul (tExit). mul (depth)). toVar ();
358 const helper = erosion. sampleCrack (q);
359 const qw = ErosionField. warpDomain (q, helper.xyz, u.crackLarge, u.crackWarp). add (
360 seedV. mul ( 0.37 ),
361 );
362 const edge = (erosion as any ).materialPlanes
363 ? (erosion as any ).materialPlanes. sample (qw)
364 : voronoiEdge (qw, u.seed, u.crackCoverage);
365 const width = max (u.crackWidth. mul (u.crackLarge), 0.001 );
366 const fissure = float ( 1 ). sub ( smoothstep (width, width. mul ( 2.5 ), edge.w));
367 const vein = float ( 1 ). sub (
368 u.veinContrast. mul (
369 fbm (q. mul (u.veinScale). add (seedV. mul ( 41 )), 2 )
370 . mul ( 0.5 )
371 . add ( 0.5 ),
372 ),
373 );
374 const fineEdge = (erosion as any ).materialPlanes
375 ? (erosion as any ).materialPlanes. sample (
376 qw. mul (u.crackFine. div (u.crackLarge)). add (seedV),
377 true ,
378 )
379 : voronoiEdge (
380 qw. mul (u.crackFine. div (u.crackLarge)). add (seedV),
381 u.seed. add ( 7 ),
382 u.fineCoverage,
383 );
384 const fineWidth = max (u.crackWidth. mul (u.crackFine). mul ( 0.35 ), 0.001 );
385 const branch = float ( 1 ). sub ( smoothstep (fineWidth, fineWidth. mul ( 2.5 ), fineEdge.w));
386 const nearLarge = mix (
387 float ( 1 ),
388 float ( 1 ). sub ( smoothstep ( 0.03 , 0.25 , edge.w)),
389 u.fineNearLarge,
390 );
391 const feather = smoothstep ( 0.24 , 0.68 , fbm (q. mul ( 38 ). add (seedV), 2 ). mul ( 0.5 ). add ( 0.5 ));
392 const filaments = fissure. mul (feather). add (branch. mul (u.crackFineAmount). mul (nearLarge));
393 const solid = smoothstep ( 0.85 , 0.35 , erosion. sample (q).r);
394 const illumination = abs ( dot (edge.xyz, L )). mul ( 0.6 ). add ( 0.4 );
395 glint. addAssign (
396 filaments
397 . mul (vein)
398 . mul (regionMask)
399 . mul (solid)
400 . mul (illumination)
401 . mul ( exp (depth. mul (tExit). div ( max (u.attDist, 0.01 )). negate ())),
402 );
403 });
404 const density = clamp (glint. div (steps), 0 , 1 );
405 return vec4 (density. mul (u.crackBright), float ( 1 ). sub (density. mul (u.crackDark). mul ( 0.15 )), 0 , 0 );
406 })();
407 const m = Fn (() => {
408 const result = vec4 ( 0 , 1 , 0 , 0 ). toVar ();
409 If (f.cracks. greaterThan ( 0.5 ), () => {
410 result. assign (march);
411 });
412 return result;
413 })();
414
415 // Clear dielectric first. The seven surface layers remain independent and sparse.
416 // Surface frost mostly broadens reflection; it must not turn the whole volume white.
417 const opticalPath = max (tExit. mul (u.thicknessScale), 0.001 );
418 const densityAt = ( q : N , depth : N ) =>
419 iceDetail (fractureDetail, q, nBase, u.inclusionScale, depth);
420 const inclusions =
421 ICE_VARIANT === "photographic"
422 ? iceInclusions (fractureDetail, p, nBase, V , tExit, u.inclusionScale, u.inclusionAmount). mul (
423 f.scatter,
424 )
425 : float ( 0 );
426 const frostCover = clamp (
427 frost. mul (u.frostDiffuse). add (smudge. mul (u.smudgeWhite)). add (inclusions. mul ( 0.65 )),
428 0 ,
429 0.65 ,
430 );
431 const surfaceDetail =
432 ICE_VARIANT === "photographic" ? densityAt (p, float ( 0 )). mul (f.frost) : float ( 0 );
433 material.roughnessNode = clamp (roughness. add (surfaceDetail. mul ( 0.22 )), 0.015 , 0.8 );
434 material.iorNode = u.ior;
435 material.dispersionNode = u.dispersion. mul (f.dispersion);
436 material.thicknessNode = opticalPath;
437 material.attenuationDistanceNode = max (u.attDist, 0.01 );
438 material.attenuationColorNode = mix ( vec3 ( 1 ), u.attColor, f.absorption);
439 material.colorNode = u.baseColor;
440 material.clearcoatNode = u.clearcoat. mul (f.clearcoat);
441 material.clearcoatRoughnessNode = u.clearcoatRough;
442 material.specularIntensityNode = u.specularIntensity. mul (f.reflections);
443 const volumeGlint = m.x. add (surfCrack. mul ( 0.3 )). mul (u.keyIntensity). mul ( 0.3 );
444 const scatter = frostCover. mul (u.interiorScatter). mul (f.scatter);
445 material.emissiveNode = u.keyColor. mul (volumeGlint. add (scatter. mul ( 0.3 )));
446 if ( RW .matcap) {
447 const nv = normalize ( transformNormalToView (nSurface)),
448 uv = nv.xy. mul ( 0.495 ). add ( 0.5 );
449 const spread = roughness. mul (roughness). mul ( 0.06 ),
450 capSample = ( v : N ) => texture (glassMatcap, clamp (v, 0.002 , 0.998 )).rgb;
451 const cap = capSample (uv)
452 . mul ( 0.4 )
453 . add ( capSample (uv. add ( vec2 (spread, 0 ))). mul ( 0.15 ))
454 . add ( capSample (uv. sub ( vec2 (spread, 0 ))). mul ( 0.15 ))
455 . add ( capSample (uv. add ( vec2 ( 0 , spread))). mul ( 0.15 ))
456 . add ( capSample (uv. sub ( vec2 ( 0 , spread))). mul ( 0.15 ))
457 . mul (u.envStrength);
458 material.outputNode = Fn (() => {
459 cutHit. lessThan ( 0 ). discard ();
460 const cover = max (frostCover, float ( 1 ). sub (f.transmission));
461 const frosted = u.baseColor. mul (cap. mul ( 0.5 ). add ( vec3 ( 0.2 )). mul (u.keyIntensity));
462 return vec4 (
463 mix (cap. mul (m.y), frosted, cover)
464 . add (u.keyColor. mul (volumeGlint))
465 . add (u.keyColor. mul (inclusions. mul (u.backlight). mul ( 0.8 ))),
466 1 ,
467 );
468 })();
469 } else if ( ICE_VARIANT === "physical" ) {
470 material.transmissionNode = f.transmission. mul ( float ( 1 ). sub (frostCover));
471 material.outputNode = Fn (() => {
472 cutHit. lessThan ( 0 ). discard ();
473 return vec4 (output.rgb. mul (m.y), output.a);
474 })();
475 } else {
476 const optical = RW .fast
477 ? fastOptics ({ ray: V , normal: nSurface, environment, u, f, roughness, path: tExit })
478 : opticalTransport ({
479 shape,
480 erosion,
481 p,
482 ray: V ,
483 normal: nSurface,
484 environment,
485 backdrop,
486 u,
487 f,
488 roughness,
489 });
490 // Keep physical lighting for the frosted fraction; custom transport owns the clear fraction.
491 material.transmission = 0 ;
492 material.transmissionNode = null ;
493 material.outputNode = Fn (() => {
494 cutHit. lessThan ( 0 ). discard ();
495 const clear = optical.reflected. add (optical.transmitted. mul (m.y));
496 const cover = max (frostCover, float ( 1 ). sub (f.transmission));
497 const lit = clear. mul ( float ( 1 ). sub (cover)). add (output.rgb. mul (cover));
498 const frozenLight = u.keyColor. mul (inclusions. mul (u.backlight). mul ( 0.8 ));
499 return vec4 (lit. add (frozenLight). add (u.keyColor. mul (volumeGlint)), 1 );
500 })();
501 }
502
503 function update (
504 _t : number ,
505 _dt : number ,
506 keyLight : THREE . DirectionalLight ,
507 objectGroup : THREE . Object3D ,
508 ) {
509 if ( D .version !== settingsVersion) {
510 settingsVersion = D .version;
511 const I = D .ice,
512 E = D .erosion,
513 S = D .shape,
514 H = D .healing;
515 for ( const name of Object. keys (f))
516 f[name].value = Number ( I .features?.[name] ?? MATERIAL_FEATURES [name]);
517 u.baseColor.value. set ( I .baseColor ?? "#ffffff" );
518 u.envStrength.value = I .envIntensity;
519 u.backlight.value = I .backlight ?? 0.4 ;
520 u.inclusionAmount.value = I .inclusionAmount ?? 0.3 ;
521 u.inclusionScale.value = I .inclusionScale ?? 0.4 ;
522 u.ior.value = I .ior;
523 u.dispersion.value = I .dispersion;
524 u.thicknessScale.value = I .thicknessScale;
525 u.attDist.value = I .attenuationDistance;
526 u.attColor.value. set ( I .attenuationColor);
527 u.baseRough.value = I .baseRoughness;
528 u.frostScale.value = I .frost.scale;
529 u.frostThreshold.value = I .frost.threshold;
530 u.frostSoftness.value = I .frost.softness;
531 u.frostRough.value = I .frost.roughness;
532 u.frostDiffuse.value = I .frost.diffuse;
533 u.crystalBump.value = I .frost.crystalBump;
534 u.crystalScale.value = I .frost.crystalScale;
535 const C = I .cracks;
536 u.crackLarge.value = C .largeScale;
537 u.crackWarp.value = C .warp;
538 u.crackCoverage.value = C .coverage;
539 u.regionScale.value = C .regionScale;
540 u.regionCoverage.value = C .regionCoverage;
541 u.veinScale.value = C .veinScale;
542 u.veinContrast.value = C .veinContrast;
543 u.crackFine.value = C .fineScale;
544 u.crackFineAmount.value = C .fineAmount;
545 u.fineCoverage.value = C .fineCoverage;
546 u.fineNearLarge.value = C .fineNearLarge;
547 u.crackWidth.value = C .width;
548 u.crackBright.value = C .brightness;
549 u.crackDark.value = C .darkness;
550 u.crackRefr.value = C .refraction;
551 u.surfaceCrack.value = C .surfaceStrength;
552 u.crackSteps.value = C .steps;
553 u.adaptiveSteps.value = D .performance.adaptiveSteps ? 1 : 0 ;
554 u.smudgeAmount.value = I .smudges.amount;
555 u.smudgeCoverage.value = I .smudges.coverage;
556 u.smudgeMaskScale.value = I .smudges.maskScale;
557 u.smudgeAniso.value = I .smudges.anisotropy;
558 u.smudgeRough.value = I .smudges.roughness;
559 u.smudgeWhite.value = I .smudges.whiteness;
560 u.smudgeScale.value = I .smudges.scale;
561 u.microBump.value = I .bumps.microBump;
562 u.microScale.value = I .bumps.microScale;
563 u.microCoverage.value = I .bumps.microCoverage;
564 u.rippleBump.value = I .bumps.rippleBump;
565 u.rippleScale.value = I .bumps.rippleScale;
566 u.bumpMaskScale.value = I .bumps.maskScale;
567 u.clearcoat.value = I .clearcoat;
568 u.clearcoatRough.value = I .clearcoatRoughness;
569 u.specularIntensity.value = I .specularIntensity;
570 u.interiorScatter.value = I .interiorScatter;
571 u.deformAmp.value = S .deformAmplitude;
572 u.deformFreq.value = S .deformFrequency;
573 u.edgeWidth.value = E .edgeWidth;
574 u.cutThreshold.value = E .cutThreshold;
575 u.cutSoft.value = E .cutSoftness;
576 u.edgeInset.value = E .edgeInset;
577 u.interiorSteps.value = E .interiorSteps;
578 u.interiorFrost.value = E .interiorFrost;
579 u.crumbleGlow.value = I .crumbleGlow;
580 u.edgeWhite.value = I .edgeWhiteness;
581 u.edgeBump.value = E .edgeBump;
582 u.edgeBumpScale.value = E .edgeBumpScale;
583 u.refrostStrength.value = H .refrostStrength;
584 u.powderTone.value. set ( D .powder.baseTone);
585 u.keyIntensity.value = D .lighting.key.intensity;
586 u.keyColor.value. set ( D .lighting.key.color);
587 }
588 // key direction in object space
589 lightDir. copy (keyLight.position). sub (keyLight.target.position). normalize ();
590 objectGroup. getWorldQuaternion (objectRotation). invert ();
591 u.lightDir.value. copy (lightDir. applyQuaternion (objectRotation));
592 u.model.value. copy (objectGroup.matrixWorld);
593 u.modelInv.value. copy (objectGroup.matrixWorld). invert ();
594 // deterministic per-frame blue-noise offset (golden-ratio sequence) so TRAA converges instead of blinking
595 const frameIndex = frameIndexOverride >= 0 ? frameIndexOverride : ++ frameCounter;
596 u.jitter.value. set ((frameIndex * 0.7548776662 ) % 1 , (frameIndex * 0.5698402909 ) % 1 );
597 }
598
599 return { material, uniforms: u, features: f, update };
600 }