rayzee 8.5.0 → 8.6.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/README.md +60 -56
- package/dist/{DLSSNeural-BNSJ8V3z.js → NeuralRetouch-CYOJ21Wn.js} +33 -33
- package/dist/{DLSSSuperRes-BoEW89Cl.js → NeuralSuperRes-Dc6jRYke.js} +25 -25
- package/dist/ToneMapGPU-BVE852iN.js +533 -0
- package/dist/rayzee.es.js +27235 -9074
- package/dist/rayzee.umd.js +255 -187
- package/package.json +1 -1
- package/src/AssetConfig.js +21 -7
- package/src/Color/BakedViews.js +134 -0
- package/src/Color/BuiltinViews.js +261 -0
- package/src/Color/ColorManagement.js +1130 -0
- package/src/Color/ColorSpaces.js +432 -0
- package/src/Color/Displays.js +19 -0
- package/src/Color/InputColorSpaces.js +243 -0
- package/src/Color/LutBake.js +371 -0
- package/src/Color/OcioRuntime.js +338 -0
- package/src/Color/OcioViews.js +419 -0
- package/src/Color/ViewTransforms.js +323 -0
- package/src/Color/WorkingMatrix.js +86 -0
- package/src/EngineDefaults.js +55 -3
- package/src/EngineIssues.js +3 -0
- package/src/Passes/AIUpscaler.js +16 -7
- package/src/Passes/OIDNDenoiser.js +13 -5
- package/src/PathTracerApp.js +227 -7
- package/src/Processor/AssetLoader.js +1 -2
- package/src/Processor/EmissiveTriangleBuilder.js +34 -2
- package/src/Processor/GeometryExtractor.js +151 -138
- package/src/Processor/LightSerializer.js +8 -3
- package/src/Processor/MaterialPacking.js +168 -0
- package/src/Processor/PackedRayBuffer.js +6 -2
- package/src/Processor/ProceduralSky.js +4 -0
- package/src/Processor/SceneProcessor.js +25 -1
- package/src/Processor/SimpleSky.js +8 -0
- package/src/Processor/TextureCreator.js +137 -91
- package/src/Processor/TextureReadback.js +92 -0
- package/src/Processor/ToneMapCPU.js +80 -165
- package/src/Processor/ToneMapGPU.js +122 -151
- package/src/RenderSettings.js +1 -0
- package/src/Stages/Compositor.js +11 -2
- package/src/Stages/PathTracer.js +2 -0
- package/src/Stages/PathTracerStage.js +1 -1
- package/src/TSL/Common.js +15 -0
- package/src/TSL/Debugger.js +2 -3
- package/src/TSL/EmissiveSampling.js +6 -8
- package/src/TSL/ExtendKernel.js +9 -0
- package/src/TSL/Fresnel.js +23 -1
- package/src/TSL/HitFacet.js +102 -0
- package/src/TSL/LightsDirect.js +12 -33
- package/src/TSL/LightsSampling.js +15 -7
- package/src/TSL/MaterialEvaluation.js +26 -19
- package/src/TSL/MaterialProperties.js +74 -37
- package/src/TSL/MaterialTransmission.js +2 -3
- package/src/TSL/ShadeKernel.js +34 -21
- package/src/TSL/ShadowTerminator.js +125 -0
- package/src/TSL/Struct.js +9 -0
- package/src/TSL/Subsurface.js +2 -3
- package/src/index.js +32 -4
- package/src/managers/DenoisingManager.js +60 -44
- package/src/managers/EnvironmentManager.js +20 -0
- package/src/managers/MaterialDataManager.js +118 -208
- package/src/managers/UniformManager.js +1 -0
- package/src/{dlss/DLSSNeural.js → neural/NeuralRetouch.js} +42 -41
- package/src/{dlss/DLSSSuperRes.js → neural/NeuralSuperRes.js} +27 -26
- package/dist/ToneMapGPU-DzqSyzTx.js +0 -341
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace, LinearSRGBColorSpace, RepeatWrapping } from "three";
|
|
2
|
-
import { alignBucketWidth, TEXTURE_CONSTANTS, MEMORY_CONSTANTS,
|
|
1
|
+
import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace, LinearSRGBColorSpace, RepeatWrapping, FloatType, HalfFloatType } from "three";
|
|
2
|
+
import { alignBucketWidth, TEXTURE_CONSTANTS, MEMORY_CONSTANTS, MATERIAL_DATA_LAYOUT, BVH_LEAF_MARKERS, assertBVHIndexFits, bvhIndexView } from '../EngineDefaults.js';
|
|
3
|
+
import { packMaterial } from './MaterialPacking.js';
|
|
3
4
|
import TexturesWorker from './Workers/TexturesWorker.js?worker&inline';
|
|
4
5
|
import { ISSUE_CODES } from '../EngineIssues.js';
|
|
5
6
|
import { linearToSRGB } from './ToneMapCPU.js';
|
|
7
|
+
import { getActiveColorManagement } from '../Color/ColorManagement.js';
|
|
6
8
|
import { createLogger } from '../utils/Logger.js';
|
|
7
9
|
|
|
8
10
|
const log = createLogger( 'textures' );
|
|
@@ -383,7 +385,11 @@ class TextureCache {
|
|
|
383
385
|
const height = texture.image.height || 0;
|
|
384
386
|
const src = texture.image.src || texture.uuid || '';
|
|
385
387
|
const flipFlag = texture.flipY === false ? 'n' : 'f';
|
|
386
|
-
|
|
388
|
+
// What the texture is read *as* is part of its identity: without these, changing a
|
|
389
|
+
// map's colour space handed back the array packed under the old one, and the change
|
|
390
|
+
// silently did nothing.
|
|
391
|
+
const space = `${texture.colorSpace ?? ''}|${texture.userData?.ocioColorSpace ?? ''}`;
|
|
392
|
+
hash += `${width}x${height}_${src.slice( - 8 )}_${flipFlag}_${space}_`;
|
|
387
393
|
|
|
388
394
|
}
|
|
389
395
|
|
|
@@ -572,6 +578,114 @@ export class TextureCreator {
|
|
|
572
578
|
|
|
573
579
|
}
|
|
574
580
|
|
|
581
|
+
/**
|
|
582
|
+
* Convert each colour layer into the working space, once a config is loaded.
|
|
583
|
+
*
|
|
584
|
+
* Runs after `_harmonizeTransfer`, so every layer is stored the way the colour pool expects:
|
|
585
|
+
* sRGB-encoded. Two routes from there:
|
|
586
|
+
*
|
|
587
|
+
* - **Named** — a tag, an override or one of the config's file rules says what the texture is
|
|
588
|
+
* (ACEScct, a camera log, sRGB). The full OCIO transform runs on the bytes as stored. Runs
|
|
589
|
+
* whether or not a working space is adopted: a log-encoded texture is wrong in linear
|
|
590
|
+
* Rec.709 too.
|
|
591
|
+
* - **Primaries** — nothing explicit, so the texture is what three.js assumed. Only the
|
|
592
|
+
* primaries change, and only once a working space is adopted.
|
|
593
|
+
*
|
|
594
|
+
* The named route reads the bytes as the author stored them, so it is refused for a layer
|
|
595
|
+
* harmonization re-encoded, and for a float source the packer already quantized — in both the
|
|
596
|
+
* bytes are no longer in the space the name describes, and converting them would be confidently
|
|
597
|
+
* wrong. Those fall back to the primaries route and say so.
|
|
598
|
+
*
|
|
599
|
+
* @param {import('three').DataArrayTexture} arrayTex
|
|
600
|
+
* @param {Array<import('three').Texture>} textures - pre-normalization list, in layer order
|
|
601
|
+
* @param {boolean} srgbPool
|
|
602
|
+
* @param {Set<number>} [reencoded] - layers `_harmonizeTransfer` changed
|
|
603
|
+
*/
|
|
604
|
+
_applyColorManagement( arrayTex, textures, srgbPool, reencoded = new Set() ) {
|
|
605
|
+
|
|
606
|
+
const cm = getActiveColorManagement();
|
|
607
|
+
if ( ! cm?.hasConfig ) return;
|
|
608
|
+
|
|
609
|
+
const data = arrayTex?.image?.data;
|
|
610
|
+
if ( ! data ) return;
|
|
611
|
+
|
|
612
|
+
// A data pool carries numbers, not colour — a primaries matrix would corrupt it.
|
|
613
|
+
if ( ! srgbPool ) return;
|
|
614
|
+
|
|
615
|
+
const kept = textures.filter( t => t?.image );
|
|
616
|
+
const layerSize = arrayTex.image.width * arrayTex.image.height * 4;
|
|
617
|
+
const encoding = 'srgb';
|
|
618
|
+
let named = 0, primaries = 0;
|
|
619
|
+
const refused = [];
|
|
620
|
+
|
|
621
|
+
for ( let layer = 0; layer < kept.length; layer ++ ) {
|
|
622
|
+
|
|
623
|
+
const start = layer * layerSize;
|
|
624
|
+
const end = Math.min( start + layerSize, data.length );
|
|
625
|
+
if ( start >= data.length ) break;
|
|
626
|
+
|
|
627
|
+
const texture = kept[ layer ];
|
|
628
|
+
const view = data.subarray( start, end );
|
|
629
|
+
|
|
630
|
+
let resolved = null;
|
|
631
|
+
try {
|
|
632
|
+
|
|
633
|
+
resolved = cm.resolveTexture( texture );
|
|
634
|
+
|
|
635
|
+
} catch ( error ) {
|
|
636
|
+
|
|
637
|
+
log.warn( `could not resolve the colour space of layer ${layer}: ${error.message}` );
|
|
638
|
+
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
let space = resolved && resolved.via !== 'three' ? resolved.colorSpace : null;
|
|
642
|
+
const floatSource = texture.type === FloatType || texture.type === HalfFloatType;
|
|
643
|
+
if ( space && ( reencoded.has( layer ) || floatSource ) ) {
|
|
644
|
+
|
|
645
|
+
refused.push( `${texture.name || `layer ${layer}`} (${space})` );
|
|
646
|
+
space = null;
|
|
647
|
+
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
try {
|
|
651
|
+
|
|
652
|
+
if ( cm.convertTextureBytes( view, { space, encoding } ) ) {
|
|
653
|
+
|
|
654
|
+
if ( space ) named ++;
|
|
655
|
+
else primaries ++;
|
|
656
|
+
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
} catch ( error ) {
|
|
660
|
+
|
|
661
|
+
this._issues?.warn(
|
|
662
|
+
ISSUE_CODES.TEXTURE_PROCESSING_FALLBACK,
|
|
663
|
+
`colour conversion failed for layer ${layer} — left in its original space`,
|
|
664
|
+
{ cause: String( error?.message ?? error ), space }
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if ( refused.length ) {
|
|
672
|
+
|
|
673
|
+
this._issues?.warn(
|
|
674
|
+
ISSUE_CODES.TEXTURE_PROCESSING_FALLBACK,
|
|
675
|
+
`${refused.length} colour map(s) name a colour space their stored bytes are no longer in ` +
|
|
676
|
+
'(re-encoded or quantized on the way in); converted by primaries only',
|
|
677
|
+
{ textures: refused.slice( 0, 8 ) }
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if ( named + primaries === 0 ) return;
|
|
683
|
+
|
|
684
|
+
arrayTex.needsUpdate = true;
|
|
685
|
+
log.info( `colour maps into ${cm.workingSpace}: ${named} by named space, ${primaries} by primaries, of ${kept.length}` );
|
|
686
|
+
|
|
687
|
+
}
|
|
688
|
+
|
|
575
689
|
/**
|
|
576
690
|
* Re-encodes any layer whose declared colour space disagrees with its pool, so the GPU's
|
|
577
691
|
* decode — or absence of one — lands on the values the author stored. An untagged texture
|
|
@@ -583,8 +697,9 @@ export class TextureCreator {
|
|
|
583
697
|
*/
|
|
584
698
|
_harmonizeTransfer( arrayTex, textures, srgbPool ) {
|
|
585
699
|
|
|
700
|
+
const touched = new Set();
|
|
586
701
|
const data = arrayTex?.image?.data;
|
|
587
|
-
if ( ! data ) return;
|
|
702
|
+
if ( ! data ) return touched;
|
|
588
703
|
|
|
589
704
|
// Mirrors _normalizeTexturesForProcessing's skip rule, which is what sets layer order.
|
|
590
705
|
const kept = textures.filter( t => t?.image );
|
|
@@ -612,10 +727,11 @@ export class TextureCreator {
|
|
|
612
727
|
}
|
|
613
728
|
|
|
614
729
|
converted.push( kept[ layer ].name || kept[ layer ].source?.uuid?.slice( 0, 8 ) || `layer ${layer}` );
|
|
730
|
+
touched.add( layer );
|
|
615
731
|
|
|
616
732
|
}
|
|
617
733
|
|
|
618
|
-
if ( converted.length === 0 ) return;
|
|
734
|
+
if ( converted.length === 0 ) return touched;
|
|
619
735
|
|
|
620
736
|
arrayTex.needsUpdate = true;
|
|
621
737
|
const names = converted.slice( 0, 4 ).join( ', ' ) + ( converted.length > 4 ? `, +${converted.length - 4} more` : '' );
|
|
@@ -632,6 +748,8 @@ export class TextureCreator {
|
|
|
632
748
|
|
|
633
749
|
}
|
|
634
750
|
|
|
751
|
+
return touched;
|
|
752
|
+
|
|
635
753
|
}
|
|
636
754
|
|
|
637
755
|
// Unified texture processing with strategy selection
|
|
@@ -640,8 +758,13 @@ export class TextureCreator {
|
|
|
640
758
|
if ( ! textures || textures.length === 0 ) return null;
|
|
641
759
|
|
|
642
760
|
// Pool is part of the identity: the same list packed for the colour pool and the data pool
|
|
643
|
-
// gets different pixels (see _harmonizeTransfer), so one key must not serve both.
|
|
644
|
-
|
|
761
|
+
// gets different pixels (see _harmonizeTransfer), so one key must not serve both. The
|
|
762
|
+
// working space is part of it for the same reason — adopting one rewrites every colour
|
|
763
|
+
// map, and a key that ignored it would hand back pixels converted for the previous space.
|
|
764
|
+
// So is the config and any override, which change what a texture is read *as*.
|
|
765
|
+
const cm = getActiveColorManagement();
|
|
766
|
+
const colour = cm?.inputKey ?? '-';
|
|
767
|
+
const cacheKey = `${srgbPool ? 's' : 'l'}:${colour}:${this.textureCache.generateHash( textures )}`;
|
|
645
768
|
const cached = this.textureCache.get( cacheKey );
|
|
646
769
|
if ( cached ) return cached;
|
|
647
770
|
|
|
@@ -673,7 +796,8 @@ export class TextureCreator {
|
|
|
673
796
|
// Cache successful result
|
|
674
797
|
if ( result ) {
|
|
675
798
|
|
|
676
|
-
this._harmonizeTransfer( result, textures, srgbPool );
|
|
799
|
+
const reencoded = this._harmonizeTransfer( result, textures, srgbPool );
|
|
800
|
+
this._applyColorManagement( result, textures, srgbPool, reencoded );
|
|
677
801
|
this.textureCache.set( cacheKey, result );
|
|
678
802
|
|
|
679
803
|
}
|
|
@@ -688,7 +812,8 @@ export class TextureCreator {
|
|
|
688
812
|
{ cause: String( error?.message ?? error ) }
|
|
689
813
|
);
|
|
690
814
|
const fallback = await this.processOnMainThreadSync( normalized );
|
|
691
|
-
this._harmonizeTransfer( fallback, textures, srgbPool );
|
|
815
|
+
const reencoded = this._harmonizeTransfer( fallback, textures, srgbPool );
|
|
816
|
+
this._applyColorManagement( fallback, textures, srgbPool, reencoded );
|
|
692
817
|
return fallback;
|
|
693
818
|
|
|
694
819
|
} finally {
|
|
@@ -1036,88 +1161,9 @@ export class TextureCreator {
|
|
|
1036
1161
|
*/
|
|
1037
1162
|
createMaterialRawData( materials ) {
|
|
1038
1163
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const totalMaterials = materials.length;
|
|
1043
|
-
|
|
1044
|
-
const size = totalMaterials * dataLengthPerMaterial;
|
|
1045
|
-
const data = new Float32Array( size );
|
|
1046
|
-
|
|
1047
|
-
for ( let i = 0; i < totalMaterials; i ++ ) {
|
|
1048
|
-
|
|
1049
|
-
const mat = materials[ i ];
|
|
1050
|
-
const stride = i * dataLengthPerMaterial;
|
|
1051
|
-
|
|
1052
|
-
const mapMatrix = mat.mapMatrix ?? DEFAULT_TEXTURE_MATRIX;
|
|
1053
|
-
const normalMapMatrices = mat.normalMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
|
|
1054
|
-
const roughnessMapMatrices = mat.roughnessMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
|
|
1055
|
-
const metalnessMapMatrices = mat.metalnessMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
|
|
1056
|
-
const emissiveMapMatrices = mat.emissiveMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
|
|
1057
|
-
const bumpMapMatrices = mat.bumpMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
|
|
1058
|
-
const displacementMapMatrices = mat.displacementMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
|
|
1059
|
-
|
|
1060
|
-
// Slot order: shadow/culling → BxDF core → maps → extended → displacement → transforms
|
|
1061
|
-
// Must match MATERIAL_DATA_LAYOUT in EngineDefaults.js exactly.
|
|
1062
|
-
const materialData = [
|
|
1063
|
-
// Slot 0: shadow core (ior, transmission, thickness, emissiveIntensity)
|
|
1064
|
-
mat.ior, mat.transmission, mat.thickness, mat.emissiveIntensity,
|
|
1065
|
-
// Slot 1: shadow (attenuationColor, attenuationDistance)
|
|
1066
|
-
mat.attenuationColor.r, mat.attenuationColor.g, mat.attenuationColor.b, normalizeAttenuationDistance( mat.attenuationDistance ),
|
|
1067
|
-
// Slot 2: shadow + culling (opacity, side, transparent, alphaTest)
|
|
1068
|
-
mat.opacity, mat.side, mat.transparent, mat.alphaTest,
|
|
1069
|
-
// Slot 3: shadow (alphaMode, depthWrite, normalScale)
|
|
1070
|
-
mat.alphaMode, mat.depthWrite, mat.normalScale?.x ?? 1, mat.normalScale?.y ?? 1,
|
|
1071
|
-
// Slot 4: BxDF core (color, metalness)
|
|
1072
|
-
mat.color.r, mat.color.g, mat.color.b, mat.metalness,
|
|
1073
|
-
// Slot 5: BxDF core (emissive, roughness)
|
|
1074
|
-
mat.emissive.r, mat.emissive.g, mat.emissive.b, mat.roughness,
|
|
1075
|
-
// Slot 6: map indices A (albedo, normal, roughness, metalness)
|
|
1076
|
-
mat.map, mat.normalMap, mat.roughnessMap, mat.metalnessMap,
|
|
1077
|
-
// Slot 7: map indices B (emissive, bump, clearcoat, clearcoatRoughness)
|
|
1078
|
-
mat.emissiveMap, mat.bumpMap, mat.clearcoat, mat.clearcoatRoughness,
|
|
1079
|
-
// Slot 8: extended BxDF (dispersion, visible, sheen, sheenRoughness)
|
|
1080
|
-
mat.dispersion, mat.visible, mat.sheen, mat.sheenRoughness,
|
|
1081
|
-
// Slot 9: extended BxDF (sheenColor, reserved)
|
|
1082
|
-
mat.sheenColor.r, mat.sheenColor.g, mat.sheenColor.b, 1,
|
|
1083
|
-
// Slot 10: extended BxDF (specularIntensity, specularColor)
|
|
1084
|
-
mat.specularIntensity, mat.specularColor.r, mat.specularColor.g, mat.specularColor.b,
|
|
1085
|
-
// Slot 11: extended BxDF (iridescence)
|
|
1086
|
-
mat.iridescence, mat.iridescenceIOR, mat.iridescenceThicknessRange[ 0 ], mat.iridescenceThicknessRange[ 1 ],
|
|
1087
|
-
// Slot 12: displacement
|
|
1088
|
-
mat.bumpScale, mat.displacementScale, mat.displacementMap, 0,
|
|
1089
|
-
mapMatrix[ 0 ], mapMatrix[ 1 ], mapMatrix[ 2 ], mapMatrix[ 3 ],
|
|
1090
|
-
mapMatrix[ 4 ], mapMatrix[ 5 ], mapMatrix[ 6 ], mapMatrix[ 7 ],
|
|
1091
|
-
normalMapMatrices[ 0 ], normalMapMatrices[ 1 ], normalMapMatrices[ 2 ], normalMapMatrices[ 3 ],
|
|
1092
|
-
normalMapMatrices[ 4 ], normalMapMatrices[ 5 ], normalMapMatrices[ 6 ], normalMapMatrices[ 7 ],
|
|
1093
|
-
roughnessMapMatrices[ 0 ], roughnessMapMatrices[ 1 ], roughnessMapMatrices[ 2 ], roughnessMapMatrices[ 3 ],
|
|
1094
|
-
roughnessMapMatrices[ 4 ], roughnessMapMatrices[ 5 ], roughnessMapMatrices[ 6 ], roughnessMapMatrices[ 7 ],
|
|
1095
|
-
metalnessMapMatrices[ 0 ], metalnessMapMatrices[ 1 ], metalnessMapMatrices[ 2 ], metalnessMapMatrices[ 3 ],
|
|
1096
|
-
metalnessMapMatrices[ 4 ], metalnessMapMatrices[ 5 ], metalnessMapMatrices[ 6 ], metalnessMapMatrices[ 7 ],
|
|
1097
|
-
emissiveMapMatrices[ 0 ], emissiveMapMatrices[ 1 ], emissiveMapMatrices[ 2 ], emissiveMapMatrices[ 3 ],
|
|
1098
|
-
emissiveMapMatrices[ 4 ], emissiveMapMatrices[ 5 ], emissiveMapMatrices[ 6 ], emissiveMapMatrices[ 7 ],
|
|
1099
|
-
bumpMapMatrices[ 0 ], bumpMapMatrices[ 1 ], bumpMapMatrices[ 2 ], bumpMapMatrices[ 3 ],
|
|
1100
|
-
bumpMapMatrices[ 4 ], bumpMapMatrices[ 5 ], bumpMapMatrices[ 6 ], bumpMapMatrices[ 7 ],
|
|
1101
|
-
displacementMapMatrices[ 0 ], displacementMapMatrices[ 1 ], displacementMapMatrices[ 2 ], displacementMapMatrices[ 3 ],
|
|
1102
|
-
displacementMapMatrices[ 4 ], displacementMapMatrices[ 5 ], displacementMapMatrices[ 6 ], displacementMapMatrices[ 7 ],
|
|
1103
|
-
// Slot 27: subsurface (subsurfaceColor.rgb, subsurface weight)
|
|
1104
|
-
mat.subsurfaceColor?.r ?? 1, mat.subsurfaceColor?.g ?? 1, mat.subsurfaceColor?.b ?? 1, mat.subsurface ?? 0,
|
|
1105
|
-
// Slot 28: subsurface (subsurfaceRadius.rgb, subsurfaceRadiusScale)
|
|
1106
|
-
mat.subsurfaceRadius?.[ 0 ] ?? 1, mat.subsurfaceRadius?.[ 1 ] ?? 0.2, mat.subsurfaceRadius?.[ 2 ] ?? 0.1, mat.subsurfaceRadiusScale ?? 1,
|
|
1107
|
-
// Slot 29: subsurfaceAnisotropy g + surface anisotropy (strength, rotation, map index)
|
|
1108
|
-
mat.subsurfaceAnisotropy ?? 0, mat.anisotropy ?? 0, mat.anisotropyRotation ?? 0, mat.anisotropyMap ?? - 1,
|
|
1109
|
-
// Slot 30: extension map indices A (transmission, clearcoat, clearcoatRoughness, sheenColor)
|
|
1110
|
-
mat.transmissionMap ?? - 1, mat.clearcoatMap ?? - 1, mat.clearcoatRoughnessMap ?? - 1, mat.sheenColorMap ?? - 1,
|
|
1111
|
-
// Slot 31: extension map indices B (sheenRoughness, iridescence, iridescenceThickness, specularIntensity)
|
|
1112
|
-
mat.sheenRoughnessMap ?? - 1, mat.iridescenceMap ?? - 1, mat.iridescenceThicknessMap ?? - 1, mat.specularIntensityMap ?? - 1,
|
|
1113
|
-
// Slot 32: extension map indices C (specularColor + 3 reserved)
|
|
1114
|
-
mat.specularColorMap ?? - 1, 0, 0, 0,
|
|
1115
|
-
];
|
|
1116
|
-
|
|
1117
|
-
data.set( materialData, stride );
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1164
|
+
const stride = MATERIAL_DATA_LAYOUT.FLOATS_PER_MATERIAL;
|
|
1165
|
+
const data = new Float32Array( materials.length * stride );
|
|
1166
|
+
for ( let i = 0; i < materials.length; i ++ ) packMaterial( data, i * stride, materials[ i ] );
|
|
1121
1167
|
return data;
|
|
1122
1168
|
|
|
1123
1169
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Any texture the pipeline publishes, read back as linear float RGBA.
|
|
3
|
+
*
|
|
4
|
+
* The denoisers publish their output as whatever they happen to own — a render target's texture,
|
|
5
|
+
* or, for OIDN, an ExternalTexture wrapping a raw GPUTexture that no render target owns — and only a
|
|
6
|
+
* render target can be read back. So this draws the texture into one float target of its own and
|
|
7
|
+
* reads that.
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ Drawn through `outputNode`, not `colorNode`: a colour node gets the renderer's tone mapping,
|
|
10
|
+
* output transfer and a forced alpha of 1, and this has to return the light itself.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { RenderTarget, FloatType, RGBAFormat, NearestFilter } from 'three';
|
|
14
|
+
import { QuadMesh, NodeMaterial } from 'three/webgpu';
|
|
15
|
+
import { texture as tslTexture, screenCoordinate, ivec2 } from 'three/tsl';
|
|
16
|
+
|
|
17
|
+
export class TextureReadback {
|
|
18
|
+
|
|
19
|
+
constructor( renderer ) {
|
|
20
|
+
|
|
21
|
+
this.renderer = renderer;
|
|
22
|
+
this.target = null;
|
|
23
|
+
this.material = new NodeMaterial();
|
|
24
|
+
this.material.name = 'rayzee:texture-readback';
|
|
25
|
+
this.quad = new QuadMesh( this.material );
|
|
26
|
+
this._node = null;
|
|
27
|
+
this._source = null;
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_ensureTarget( width, height ) {
|
|
32
|
+
|
|
33
|
+
if ( this.target && this.target.width === width && this.target.height === height ) return;
|
|
34
|
+
|
|
35
|
+
this.target?.dispose();
|
|
36
|
+
this.target = new RenderTarget( width, height, {
|
|
37
|
+
type: FloatType, format: RGBAFormat, depthBuffer: false,
|
|
38
|
+
minFilter: NearestFilter, magFilter: NearestFilter,
|
|
39
|
+
} );
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {import('three').Texture} source
|
|
45
|
+
* @returns {Promise<Float32Array>} `width * height * 4`, top row first
|
|
46
|
+
*/
|
|
47
|
+
async read( source, width, height ) {
|
|
48
|
+
|
|
49
|
+
this._ensureTarget( width, height );
|
|
50
|
+
|
|
51
|
+
if ( this._source !== source ) {
|
|
52
|
+
|
|
53
|
+
// Fetched by pixel, not sampled over 0..1: the pools are allocated larger than the frame,
|
|
54
|
+
// and sampling would squash the whole allocation into it.
|
|
55
|
+
this._node = tslTexture( source ).load( ivec2( screenCoordinate.xy ) );
|
|
56
|
+
this.material.outputNode = this._node;
|
|
57
|
+
this.material.needsUpdate = true;
|
|
58
|
+
this._source = source;
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const previous = this.renderer.getRenderTarget();
|
|
63
|
+
this.renderer.setRenderTarget( this.target );
|
|
64
|
+
this.quad.render( this.renderer );
|
|
65
|
+
this.renderer.setRenderTarget( previous );
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
|
|
69
|
+
return await this.renderer.readRenderTargetPixelsAsync( this.target, 0, 0, width, height );
|
|
70
|
+
|
|
71
|
+
} finally {
|
|
72
|
+
|
|
73
|
+
// A float target the size of the frame — 132 MB at 4K — for something done once per
|
|
74
|
+
// saved file. Not worth keeping between saves.
|
|
75
|
+
this.target.dispose();
|
|
76
|
+
this.target = null;
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
dispose() {
|
|
83
|
+
|
|
84
|
+
this.target?.dispose();
|
|
85
|
+
this.material.dispose();
|
|
86
|
+
this.target = null;
|
|
87
|
+
this._source = null;
|
|
88
|
+
this._node = null;
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}
|