rayzee 8.5.0 → 8.6.0

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.
Files changed (60) hide show
  1. package/README.md +60 -4
  2. package/dist/{DLSSNeural-BNSJ8V3z.js → DLSSNeural-BhBCBgKf.js} +5 -5
  3. package/dist/{DLSSSuperRes-BoEW89Cl.js → DLSSSuperRes-BCA7F_k5.js} +5 -5
  4. package/dist/ToneMapGPU-BzcLheoR.js +533 -0
  5. package/dist/rayzee.es.js +27198 -9047
  6. package/dist/rayzee.umd.js +255 -187
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +15 -2
  9. package/src/Color/BakedViews.js +134 -0
  10. package/src/Color/BuiltinViews.js +261 -0
  11. package/src/Color/ColorManagement.js +1130 -0
  12. package/src/Color/ColorSpaces.js +432 -0
  13. package/src/Color/Displays.js +19 -0
  14. package/src/Color/InputColorSpaces.js +243 -0
  15. package/src/Color/LutBake.js +371 -0
  16. package/src/Color/OcioRuntime.js +338 -0
  17. package/src/Color/OcioViews.js +419 -0
  18. package/src/Color/ViewTransforms.js +323 -0
  19. package/src/Color/WorkingMatrix.js +86 -0
  20. package/src/EngineDefaults.js +55 -3
  21. package/src/EngineIssues.js +3 -0
  22. package/src/Passes/AIUpscaler.js +16 -7
  23. package/src/PathTracerApp.js +227 -7
  24. package/src/Processor/AssetLoader.js +1 -2
  25. package/src/Processor/EmissiveTriangleBuilder.js +34 -2
  26. package/src/Processor/GeometryExtractor.js +151 -138
  27. package/src/Processor/LightSerializer.js +8 -3
  28. package/src/Processor/MaterialPacking.js +168 -0
  29. package/src/Processor/PackedRayBuffer.js +6 -2
  30. package/src/Processor/ProceduralSky.js +4 -0
  31. package/src/Processor/SceneProcessor.js +25 -1
  32. package/src/Processor/SimpleSky.js +8 -0
  33. package/src/Processor/TextureCreator.js +137 -91
  34. package/src/Processor/TextureReadback.js +92 -0
  35. package/src/Processor/ToneMapCPU.js +80 -165
  36. package/src/Processor/ToneMapGPU.js +122 -151
  37. package/src/RenderSettings.js +1 -0
  38. package/src/Stages/Compositor.js +11 -2
  39. package/src/Stages/PathTracer.js +2 -0
  40. package/src/Stages/PathTracerStage.js +1 -1
  41. package/src/TSL/Common.js +15 -0
  42. package/src/TSL/Debugger.js +2 -3
  43. package/src/TSL/EmissiveSampling.js +6 -8
  44. package/src/TSL/ExtendKernel.js +9 -0
  45. package/src/TSL/Fresnel.js +23 -1
  46. package/src/TSL/HitFacet.js +102 -0
  47. package/src/TSL/LightsDirect.js +12 -33
  48. package/src/TSL/LightsSampling.js +15 -7
  49. package/src/TSL/MaterialEvaluation.js +26 -19
  50. package/src/TSL/MaterialProperties.js +74 -37
  51. package/src/TSL/MaterialTransmission.js +2 -3
  52. package/src/TSL/ShadeKernel.js +34 -21
  53. package/src/TSL/ShadowTerminator.js +125 -0
  54. package/src/TSL/Struct.js +9 -0
  55. package/src/TSL/Subsurface.js +2 -3
  56. package/src/index.js +29 -1
  57. package/src/managers/EnvironmentManager.js +20 -0
  58. package/src/managers/MaterialDataManager.js +118 -208
  59. package/src/managers/UniformManager.js +1 -0
  60. package/dist/ToneMapGPU-DzqSyzTx.js +0 -341
@@ -1,4 +1,4 @@
1
- import { WebGPURenderer, RectAreaLightNode, SRGBColorSpace } from 'three/webgpu';
1
+ import { WebGPURenderer, RectAreaLightNode, SRGBColorSpace, LinearSRGBColorSpace } from 'three/webgpu';
2
2
  import { texture as _tslTexture, cubeTexture as _tslCubeTexture } from 'three/tsl';
3
3
  import {
4
4
  Scene, EventDispatcher, Box3
@@ -20,12 +20,15 @@ import { CompletionTracker } from './Pipeline/CompletionTracker.js';
20
20
  import { ENGINE_DEFAULTS as DEFAULT_STATE, PRODUCTION_RENDER_CONFIG, INTERACTIVE_RENDER_CONFIG, modePresetSettings, MAX_STORAGE_TEXTURE_SIZE, MAX_RESERVABLE_RENDER_SIZE, setReservedRenderSize, getRenderProfile } from './EngineDefaults.js';
21
21
  import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples, disposeObjectFromMemory, disposeRenderer } from './Processor/utils.js';
22
22
  import { BuildTimer } from './Processor/BuildTimer.js';
23
+ import { TextureReadback } from './Processor/TextureReadback.js';
23
24
  import { createLogger, fmt } from './utils/Logger.js';
24
25
  import { InteractionManager } from './managers/InteractionManager.js';
25
26
  import { EngineEvents } from './EngineEvents.js';
26
27
  import { IssueLog, ISSUE_CODES } from './EngineIssues.js';
27
28
  import { SETTING_SOURCE } from './RenderSettings.js';
28
29
  import { toneMapToRGBA8 } from './Processor/ToneMapCPU.js';
30
+ import { ColorManagement, setActiveColorManagement } from './Color/ColorManagement.js';
31
+ import { getViewTransform } from './Color/ViewTransforms.js';
29
32
  import { AssetLoader } from './Processor/AssetLoader.js';
30
33
  import { SceneProcessor } from './Processor/SceneProcessor.js';
31
34
 
@@ -163,6 +166,15 @@ export class PathTracerApp extends EventDispatcher {
163
166
  onIssue: ( issue ) => this.dispatchEvent( { type: EngineEvents.ISSUE, issue } ),
164
167
  } );
165
168
 
169
+ /**
170
+ * Colour management: what the engine renders in, shows it as, and hands out.
171
+ *
172
+ * Inert until a host loads an OCIO config — until then the working space is linear
173
+ * Rec.709 and the view transforms are three.js's own seven, exactly as before.
174
+ */
175
+ this.color = new ColorManagement( { issues: this._issues } );
176
+ setActiveColorManagement( this.color );
177
+
166
178
  // ── Settings (single source of truth for all render parameters) ──
167
179
  this.settings = new RenderSettings(
168
180
  {
@@ -598,6 +610,12 @@ export class PathTracerApp extends EventDispatcher {
598
610
 
599
611
  this._issues.detach(); // onIssue captures `this`; see IssueLog.detach()
600
612
 
613
+ // Holds the renderer and the issue log, both of which outlive it otherwise.
614
+ this.color?.dispose();
615
+ this.color = null;
616
+ this._textureReadback?.dispose();
617
+ this._textureReadback = null;
618
+
601
619
  this.interactionManager?.deselect?.();
602
620
  this.transformManager?.detach?.();
603
621
 
@@ -2312,15 +2330,23 @@ export class PathTracerApp extends EventDispatcher {
2312
2330
  * the viewport shows.
2313
2331
  *
2314
2332
  * @param {Object} [options]
2315
- * @param {'linear'|'srgb'} [options.colorSpace='srgb']
2333
+ * @param {string} [options.colorSpace='srgb'] - `'srgb'` for display bytes through the active
2334
+ * view transform, `'linear'` for the raw working-space accumulation, or the name of a colour
2335
+ * space in the loaded OCIO config for a delivery buffer (float, e.g. `'ACES2065-1'`)
2316
2336
  * @param {boolean} [options.preserveAlpha=false] - srgb only
2337
+ * @param {'accumulation'|'display'} [options.source='accumulation'] - `'display'` reads what the
2338
+ * viewport is showing — denoised when a denoiser has run, without bloom — instead of the raw
2339
+ * accumulation
2317
2340
  * @returns {Promise<{data: Float32Array|Uint8ClampedArray, width: number, height: number, colorSpace: string}>}
2318
2341
  */
2319
- async renderToBuffer( { colorSpace = 'srgb', preserveAlpha = false } = {} ) {
2342
+ async renderToBuffer( { colorSpace = 'srgb', preserveAlpha = false, source = 'accumulation' } = {} ) {
2320
2343
 
2321
- if ( colorSpace !== 'linear' && colorSpace !== 'srgb' ) {
2344
+ const named = colorSpace !== 'linear' && colorSpace !== 'srgb';
2345
+ if ( named && ! this.color?.hasConfig ) {
2322
2346
 
2323
- throw new Error( `renderToBuffer: colorSpace must be 'linear' or 'srgb', got "${colorSpace}"` );
2347
+ throw new Error(
2348
+ `renderToBuffer: colorSpace must be 'linear' or 'srgb' without a colour config loaded, got "${colorSpace}"`
2349
+ );
2324
2350
 
2325
2351
  }
2326
2352
 
@@ -2331,9 +2357,27 @@ export class PathTracerApp extends EventDispatcher {
2331
2357
  // The pool over-allocates to the reserve, so the texture is larger than the frame.
2332
2358
  const { width, height } = stage;
2333
2359
 
2334
- const linear = await this.renderer.readRenderTargetPixelsAsync( target, 0, 0, width, height, 0 );
2360
+ const linear = source === 'display'
2361
+ ? await this._readDisplaySource( target, width, height )
2362
+ : await this.renderer.readRenderTargetPixelsAsync( target, 0, 0, width, height, 0 );
2363
+
2364
+ // `colorSpace` stays 'linear' — callers branch on it. `workingSpace` is the new, additive
2365
+ // answer to "linear in what primaries", which only means something once a config is loaded.
2366
+ if ( colorSpace === 'linear' ) {
2367
+
2368
+ return { data: linear, width, height, colorSpace, workingSpace: this.color?.workingSpace ?? null };
2369
+
2370
+ }
2371
+
2372
+ // A named space is a delivery buffer, not a picture: scene-referred float in whatever the
2373
+ // config calls that space. Saving an EXR for a compositor and grading through an sRGB view
2374
+ // on screen are different questions, and this is the one that answers the first.
2375
+ if ( named ) {
2376
+
2377
+ const { rgba, colorSpace: got } = this.color.exportPixels( linear, colorSpace );
2378
+ return { data: rgba, width, height, colorSpace: got };
2335
2379
 
2336
- if ( colorSpace === 'linear' ) return { data: linear, width, height, colorSpace };
2380
+ }
2337
2381
 
2338
2382
  return {
2339
2383
  data: toneMapToRGBA8( linear, {
@@ -2765,6 +2809,20 @@ export class PathTracerApp extends EventDispatcher {
2765
2809
 
2766
2810
  }
2767
2811
 
2812
+ /**
2813
+ * Where a material property's value came from: `'material'` (the three.js material carried
2814
+ * it), `'mapped'` (converted from a Basic/Lambert/Phong/Toon material), `'default'` (the model
2815
+ * never said; filled from MATERIAL_DEFAULTS) or `'host'` (set through setMaterialProperty).
2816
+ * @param {number} materialIndex
2817
+ * @param {string} property
2818
+ * @returns {string|undefined}
2819
+ */
2820
+ getMaterialPropertySource( materialIndex, property ) {
2821
+
2822
+ return this.stages.pathTracer?.materialData.getMaterialPropertySource( materialIndex, property );
2823
+
2824
+ }
2825
+
2768
2826
  setMaterialProperty( materialIndex, property, value ) {
2769
2827
 
2770
2828
  this.stages.pathTracer?.materialData.updateMaterialProperty( materialIndex, property, value );
@@ -2917,6 +2975,163 @@ export class PathTracerApp extends EventDispatcher {
2917
2975
 
2918
2976
  }
2919
2977
 
2978
+ /**
2979
+ * What the Compositor is drawing from, as linear float. Falls back to the accumulation when no
2980
+ * denoiser has published anything, which is also what the viewport shows then.
2981
+ * @private
2982
+ */
2983
+ async _readDisplaySource( accumulation, width, height ) {
2984
+
2985
+ const context = this.pipeline?.context;
2986
+ const texture = context && this.stages.compositor?.resolveLightTexture( context );
2987
+ if ( ! texture || texture === accumulation.texture ) {
2988
+
2989
+ return await this.renderer.readRenderTargetPixelsAsync( accumulation, 0, 0, width, height, 0 );
2990
+
2991
+ }
2992
+
2993
+ this._textureReadback ??= new TextureReadback( this.renderer );
2994
+ return await this._textureReadback.read( texture, width, height );
2995
+
2996
+ }
2997
+
2998
+ /**
2999
+ * Set what colour space one texture is in, then rebuild so it takes effect.
3000
+ *
3001
+ * The per-image colour space every OCIO application offers. Three kinds of answer:
3002
+ * - `null` — automatic: tag, override, the config's file rules, then three.js
3003
+ * - `'srgb'`/`'linear'` — three.js's own two, which work with no config at all
3004
+ * - a config space — anything the loaded config names, including log and camera spaces
3005
+ *
3006
+ * A config space is carried as `userData.ocioColorSpace`, and the texture's three.js colour space
3007
+ * is set to match the colour pool while it is, so harmonization leaves the stored bytes as the
3008
+ * author wrote them — the named transform has to read them raw. The original is kept and put
3009
+ * back when the choice is cleared.
3010
+ *
3011
+ * @param {import('three').Texture} texture
3012
+ * @param {?string} choice
3013
+ */
3014
+ async setTextureColorSpace( texture, choice ) {
3015
+
3016
+ if ( ! texture?.isTexture ) throw new Error( 'setTextureColorSpace wants a three.js texture' );
3017
+
3018
+ const ud = texture.userData ?? ( texture.userData = {} );
3019
+ if ( ud.__rayzeeOriginalColorSpace === undefined ) ud.__rayzeeOriginalColorSpace = texture.colorSpace;
3020
+ const original = ud.__rayzeeOriginalColorSpace;
3021
+
3022
+ delete ud.ocioColorSpace;
3023
+ delete ud.__rayzeeColorSpaceChoice;
3024
+
3025
+ if ( choice === null || choice === undefined ) {
3026
+
3027
+ texture.colorSpace = original;
3028
+ delete ud.__rayzeeOriginalColorSpace;
3029
+
3030
+ } else if ( choice === 'srgb' || choice === 'linear' ) {
3031
+
3032
+ texture.colorSpace = choice === 'srgb' ? SRGBColorSpace : LinearSRGBColorSpace;
3033
+ ud.__rayzeeColorSpaceChoice = choice;
3034
+
3035
+ } else {
3036
+
3037
+ if ( ! this.color?.hasConfig ) throw new Error( `"${choice}" needs a colour config loaded` );
3038
+ ud.ocioColorSpace = choice;
3039
+ texture.colorSpace = SRGBColorSpace;
3040
+
3041
+ }
3042
+
3043
+ texture.needsUpdate = true;
3044
+ if ( this.stages.pathTracer?.sdfs ) await this.rebuildMaterials();
3045
+
3046
+ }
3047
+
3048
+ /**
3049
+ * Load a colour config, keeping the scene consistent with it.
3050
+ *
3051
+ * Prefer this to `app.color.loadConfig()` whenever a scene is loaded. A working space adopted
3052
+ * under the previous config is undone first, *while that config is still loaded* — the
3053
+ * environment is converted in place, and only the config that converted it can convert it
3054
+ * back. With `adoptWorkingSpace` the scene is rebuilt in the new space afterwards.
3055
+ *
3056
+ * @param {Object} options - as `ColorManagement.loadConfig`
3057
+ * @returns {Promise<Object>} the config description
3058
+ */
3059
+ async loadColorConfig( options = {} ) {
3060
+
3061
+ await this._leaveColorWorkingSpace();
3062
+ const shown = getViewTransform( this.renderer?.toneMapping );
3063
+ const described = await this.color.loadConfig( options );
3064
+ if ( this.color.workingSpaceAdopted ) await this.applyColorWorkingSpace();
3065
+ // A baked view the config kept is still what is on screen.
3066
+ const kept = shown?.source === 'ocio' && getViewTransform( this.renderer?.toneMapping ) === shown;
3067
+ if ( ! kept || this.color.workingSpaceAdopted ) this.reset();
3068
+ return described;
3069
+
3070
+ }
3071
+
3072
+ /** Unload the colour config, putting the scene back into linear Rec.709 first. */
3073
+ async unloadColorConfig() {
3074
+
3075
+ await this._leaveColorWorkingSpace();
3076
+ this.color.unloadConfig();
3077
+ this.reset();
3078
+
3079
+ }
3080
+
3081
+ /** @private */
3082
+ async _leaveColorWorkingSpace() {
3083
+
3084
+ if ( ! this.color?.workingSpaceAdopted ) return;
3085
+ this.color.setWorkingSpace( null );
3086
+ await this.applyColorWorkingSpace();
3087
+
3088
+ }
3089
+
3090
+ /**
3091
+ * Rebuild everything the working space touches, after it has been changed.
3092
+ *
3093
+ * Changing what the engine renders in is not a display setting — it changes what every texture,
3094
+ * tint and light colour already loaded *means*. Textures and materials are re-packed from their
3095
+ * pristine three.js sources, so they convert cleanly; the environment is converted where it
3096
+ * lies, from whichever space it currently holds, and its importance-sampling table is rebuilt
3097
+ * because its pixels moved.
3098
+ *
3099
+ * Call this after `app.color.setWorkingSpace()`. Doing nothing instead leaves a scene half in
3100
+ * one space and half in another, which reads as a colour cast with no obvious cause.
3101
+ */
3102
+ async applyColorWorkingSpace() {
3103
+
3104
+ const env = this.environmentManager?.getEnvironmentTexture?.();
3105
+ let envChanged = false;
3106
+
3107
+ if ( env && this.color?.hasConfig ) {
3108
+
3109
+ try {
3110
+
3111
+ envChanged = this.color.convertTexturePixels( env );
3112
+
3113
+ } catch ( error ) {
3114
+
3115
+ log.warn( `environment colour conversion failed: ${error.message}` );
3116
+
3117
+ }
3118
+
3119
+ }
3120
+
3121
+ // Nothing to rebuild before a scene exists; anything loaded later converts on its way in.
3122
+ if ( this.stages.pathTracer?.sdfs ) {
3123
+
3124
+ await this.rebuildMaterials();
3125
+ this._uploadEmissivePayload( this._sdf?.rebuildEmissiveColors?.() ?? null );
3126
+
3127
+ }
3128
+
3129
+ if ( envChanged ) await this.environmentManager?.buildEnvironmentCDF?.();
3130
+
3131
+ this.reset();
3132
+
3133
+ }
3134
+
2920
3135
  /**
2921
3136
  * Full material rebuild (required after texture changes).
2922
3137
  * @param {import('three').Scene} [scene]
@@ -3044,6 +3259,11 @@ export class PathTracerApp extends EventDispatcher {
3044
3259
  this.renderer.toneMappingExposure = 1.0;
3045
3260
  this.renderer.setPixelRatio( 1.0 );
3046
3261
 
3262
+ // Hands every registered view transform to the renderer and keeps `outputColorSpace` in
3263
+ // step with whichever is selected — an OCIO view already encoded for its display must not
3264
+ // be encoded a second time by the output pass.
3265
+ this.color.attachRenderer( this.renderer );
3266
+
3047
3267
  }
3048
3268
 
3049
3269
  _initCameraManager() {
@@ -851,8 +851,7 @@ export class AssetLoader extends EventDispatcher {
851
851
  this.targetModel = group;
852
852
 
853
853
  // The light's own orientation and `scale` are already baked into the texture, so the
854
- // scene is only correct at rotation 0 / intensity 1. Without this the viewer profile's
855
- // default 270° rotation lands on top and the sky sits 90° from where pbrt puts it.
854
+ // scene is only correct at rotation 0 / intensity 1 — pinned, whatever the host's defaults.
856
855
  if ( environment?.texture ) this.sceneMetadata = { environment: { rotation: 0, intensity: 1 } };
857
856
 
858
857
  updateLoading( { isLoading: true, status: 'Processing PBRT geometry...', progress: 10 } );
@@ -10,6 +10,28 @@ import { DataTexture, RGBAFormat, FloatType, NearestFilter } from 'three';
10
10
  import { TRIANGLE_DATA_LAYOUT, TRI_MATERIAL_MASK } from '../EngineDefaults.js';
11
11
  import { LightBVHBuilder } from './LightBVHBuilder.js';
12
12
  import { createLogger, fmt } from '../utils/Logger.js';
13
+ import { convertLinearTriple, getWorkingMatrix } from '../Color/WorkingMatrix.js';
14
+
15
+ const NO_EMISSION = Object.freeze( { r: 0, g: 0, b: 0 } );
16
+
17
+ /**
18
+ * A material's emission in the working space.
19
+ *
20
+ * The emitter seen directly reads the material buffer, which is already converted, and so does the
21
+ * shader's pick probability. This buffer is the other copy of the same colour — the one next-event
22
+ * estimation lights the scene with — so it has to move with them, or a working space adopted from a
23
+ * config lights everything in the wrong primaries and weights the pick by a colour nothing renders.
24
+ */
25
+ function workingEmissive( emissive ) {
26
+
27
+ if ( ! emissive ) return NO_EMISSION;
28
+ if ( ! getWorkingMatrix() ) return emissive;
29
+
30
+ const rgb = [ emissive.r, emissive.g, emissive.b ];
31
+ convertLinearTriple( rgb );
32
+ return { r: rgb[ 0 ], g: rgb[ 1 ], b: rgb[ 2 ] };
33
+
34
+ }
13
35
 
14
36
  const log = createLogger( 'emissive' );
15
37
 
@@ -62,6 +84,9 @@ export class EmissiveTriangleBuilder {
62
84
  const flatU = chunked ? null : triangleData;
63
85
  const flatF = chunked ? null : new Float32Array( triangleData.buffer, triangleData.byteOffset, triangleData.length );
64
86
 
87
+ // Once per material, not once per triangle: this loop runs over every triangle in the scene.
88
+ const emissiveOf = new Map();
89
+
65
90
  for ( let i = 0; i < triangleCount; i ++ ) {
66
91
 
67
92
  const triU = chunked ? chunked.chunkFor( i ) : flatU;
@@ -75,7 +100,14 @@ export class EmissiveTriangleBuilder {
75
100
  if ( ! material ) continue;
76
101
 
77
102
  // Check if emissive
78
- const emissive = material.emissive || { r: 0, g: 0, b: 0 };
103
+ let emissive = emissiveOf.get( material );
104
+ if ( emissive === undefined ) {
105
+
106
+ emissive = workingEmissive( material.emissive );
107
+ emissiveOf.set( material, emissive );
108
+
109
+ }
110
+
79
111
  const emissiveIntensity = material.emissiveIntensity || 0;
80
112
 
81
113
  const isEmissive = emissiveIntensity > 0 && (
@@ -461,7 +493,7 @@ export class EmissiveTriangleBuilder {
461
493
  */
462
494
  updateMaterialEmissive( materialIndex, material, triangleData, materials, triangleCount ) {
463
495
 
464
- const emissive = material.emissive || { r: 0, g: 0, b: 0 };
496
+ const emissive = workingEmissive( material.emissive );
465
497
  const emissiveIntensity = material.emissiveIntensity || 0;
466
498
  const isNowEmissive = emissiveIntensity > 0 && ( emissive.r > 0 || emissive.g > 0 || emissive.b > 0 );
467
499