rayzee 8.8.2 → 9.1.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 (104) hide show
  1. package/README.md +210 -20
  2. package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
  3. package/dist/{NeuralSuperRes-Dc6jRYke.js → NeuralSuperRes-CbNvQjCw.js} +5 -2
  4. package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
  5. package/dist/rayzee.es.js +10795 -5324
  6. package/dist/rayzee.umd.js +279 -268
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +4 -0
  9. package/src/EngineDefaults.js +60 -76
  10. package/src/EngineEvents.js +7 -0
  11. package/src/EngineIssues.js +6 -0
  12. package/src/Headless.js +3 -1
  13. package/src/Passes/AIUpscaler.js +2 -1
  14. package/src/Passes/OIDNDenoiser.js +3 -6
  15. package/src/Passes/OIDNTemporalHistory.js +25 -7
  16. package/src/PathTracerApp.js +313 -25
  17. package/src/Processor/ArchiveCache.js +120 -0
  18. package/src/Processor/ArchiveReader.js +179 -32
  19. package/src/Processor/AssetLoader.js +523 -76
  20. package/src/Processor/AtmosphereModel.js +465 -0
  21. package/src/Processor/BVHBuilder.js +4 -1
  22. package/src/Processor/BVHLeafFold.js +100 -0
  23. package/src/Processor/BVHRefitter.js +98 -214
  24. package/src/Processor/ChunkedRecords.js +189 -4
  25. package/src/Processor/EmissiveTriangleBuilder.js +8 -0
  26. package/src/Processor/GeometryExtractor.js +238 -40
  27. package/src/Processor/InstanceTable.js +55 -37
  28. package/src/Processor/PBRT/PBRTCurves.js +104 -27
  29. package/src/Processor/PBRT/PBRTParser.js +132 -22
  30. package/src/Processor/PBRT/PBRTSceneBuilder.js +210 -55
  31. package/src/Processor/PBRT/buffers.js +27 -0
  32. package/src/Processor/PBRT/index.js +55 -15
  33. package/src/Processor/PackedRayBuffer.js +10 -13
  34. package/src/Processor/ParallelBVHBuilder.js +36 -15
  35. package/src/Processor/PhysicalSky.js +308 -0
  36. package/src/Processor/QueueManager.js +1 -0
  37. package/src/Processor/SceneProcessor.js +1132 -181
  38. package/src/Processor/SplitBLAS.js +249 -0
  39. package/src/Processor/SunPosition.js +41 -0
  40. package/src/Processor/TextureCreator.js +21 -0
  41. package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
  42. package/src/Processor/Workers/BVHWorker.js +17 -13
  43. package/src/Processor/ZipReader.js +207 -0
  44. package/src/RenderSettings.js +49 -8
  45. package/src/SceneState/SceneState.js +367 -0
  46. package/src/SceneState/portable.js +52 -0
  47. package/src/Stages/MotionVector.js +8 -42
  48. package/src/Stages/NRD.js +30 -21
  49. package/src/Stages/NormalDepth.js +12 -23
  50. package/src/Stages/PathTracer.js +114 -6
  51. package/src/Stages/PathTracerStage.js +251 -104
  52. package/src/Storage/BLASCache.js +190 -0
  53. package/src/Storage/CDFCache.js +77 -0
  54. package/src/Storage/DownloadCache.js +266 -0
  55. package/src/Storage/GeometrySpill.js +217 -0
  56. package/src/Storage/SceneGraphCodec.js +503 -0
  57. package/src/Storage/SpillStore.js +112 -0
  58. package/src/Storage/StorageManager.js +934 -0
  59. package/src/Storage/StorageOps.js +137 -0
  60. package/src/Storage/StorageWorker.js +37 -0
  61. package/src/Storage/events.js +29 -0
  62. package/src/Storage/identity.js +77 -0
  63. package/src/Storage/index.js +9 -0
  64. package/src/Storage/inlineTransport.js +24 -0
  65. package/src/Storage/locks.js +99 -0
  66. package/src/Storage/openStorage.js +87 -0
  67. package/src/Storage/sceneCachePolicy.js +12 -0
  68. package/src/Storage/shared.js +9 -0
  69. package/src/Storage/transport.js +62 -0
  70. package/src/TSL/Atmosphere.js +684 -0
  71. package/src/TSL/BVHTraversal.js +288 -239
  72. package/src/TSL/CameraRay.js +99 -14
  73. package/src/TSL/Common.js +11 -2
  74. package/src/TSL/DebugKernel.js +2 -2
  75. package/src/TSL/Displacement.js +6 -6
  76. package/src/TSL/EmissiveSampling.js +5 -5
  77. package/src/TSL/EnvironmentCDF.js +176 -0
  78. package/src/TSL/GenerateKernel.js +5 -5
  79. package/src/TSL/HitFacet.js +4 -4
  80. package/src/TSL/LightsDirect.js +8 -8
  81. package/src/TSL/LightsSampling.js +55 -37
  82. package/src/TSL/Random.js +1 -0
  83. package/src/TSL/ShadeKernel.js +75 -19
  84. package/src/TSL/Sun.js +40 -0
  85. package/src/TSL/TextureSampling.js +6 -7
  86. package/src/index.js +22 -0
  87. package/src/managers/CameraManager.js +412 -56
  88. package/src/managers/DenoisingManager.js +4 -3
  89. package/src/managers/EnvironmentManager.js +263 -33
  90. package/src/managers/GoboManager.js +19 -8
  91. package/src/managers/IESManager.js +3 -3
  92. package/src/managers/InteractionManager.js +30 -2
  93. package/src/managers/LightManager.js +98 -5
  94. package/src/managers/MaterialDataManager.js +22 -2
  95. package/src/managers/TransformManager.js +26 -0
  96. package/src/managers/UniformManager.js +11 -7
  97. package/src/managers/VideoRenderManager.js +38 -17
  98. package/src/managers/ViewCamera.js +77 -0
  99. package/src/managers/WalkControls.js +262 -0
  100. package/src/managers/helpers/OutlineHelper.js +22 -2
  101. package/src/managers/timeline/CameraTrack.js +235 -0
  102. package/src/managers/timeline/TimelineManager.js +146 -0
  103. package/src/version.js +3 -0
  104. package/src/Processor/ProceduralSky.js +0 -291
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "8.8.2",
3
+ "version": "9.1.0",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -56,6 +56,10 @@ const config = {
56
56
  // Prefix used when the engine writes to client-side stores (IndexedDB, etc).
57
57
  // Set to a unique value to avoid collisions when multiple apps embed the engine on the same origin.
58
58
  cacheNamespace: 'rayzee',
59
+
60
+ // On-disk storage (origin private file system) under a directory named by cacheNamespace:
61
+ // 'auto' uses it where the browser has one, false turns every storage feature off.
62
+ storage: 'auto',
59
63
  };
60
64
 
61
65
  /**
@@ -1,3 +1,10 @@
1
+ import { sunPosition, dayOfYearForMonth } from './Processor/SunPosition.js';
2
+
3
+ /** The default sun as a time of day (solar hours), month and latitude; north lies along −Z. */
4
+ export const DEFAULT_SUN_PATH = Object.freeze( { time: 10, month: 3, latitude: 40 } );
5
+
6
+ const DEFAULT_SUN = sunPosition( { hours: DEFAULT_SUN_PATH.time, dayOfYear: dayOfYearForMonth( DEFAULT_SUN_PATH.month ), latitude: DEFAULT_SUN_PATH.latitude } );
7
+
1
8
  /**
2
9
  * Engine-owned default values and configuration constants.
3
10
  * These are used exclusively by the rendering engine (src/core/)
@@ -14,6 +21,7 @@ export const RENDER_PROFILES = Object.freeze( {
14
21
  environmentRotation: 0.0, // degrees — the HDRI as authored, as Blender shows it
15
22
  toneMapping: 6, // AgXToneMapping
16
23
  saturation: 1.0, // no grade
24
+ dofMode: 'look', // depth of field set by how blurry it looks, not by a lens — right at any scene scale
17
25
  } ),
18
26
  physical: Object.freeze( {
19
27
  areaLightIntensityScale: 1.0,
@@ -22,6 +30,7 @@ export const RENDER_PROFILES = Object.freeze( {
22
30
  // engine's own corpus (shade 1.48 vs AgX 3.01 from identical radiance).
23
31
  toneMapping: 6,
24
32
  saturation: 1.0, // no grade
33
+ dofMode: 'physical',
25
34
  } ),
26
35
  } );
27
36
 
@@ -92,16 +101,19 @@ export const ENGINE_DEFAULTS = {
92
101
  // Solid Color Sky
93
102
  solidSkyColor: '#87CEEB',
94
103
 
95
- // Procedural Sky Parameters (Preetham Model - Clear Morning preset)
96
- skySunAzimuth: 90,
97
- skySunElevation: 20,
98
- skySunIntensity: 15.0,
99
- skyRayleighDensity: 0.9,
100
- skyTurbidity: 0.8,
101
- skyMieAnisotropy: 0.76,
102
- skyPreset: 'clearMorning',
103
-
104
- // Camera projection — 'perspective' | 'equirectangular'. Panorama ranges are UI-facing degrees.
104
+ // Physical sky (Processor/PhysicalSky.js) — the Clear Day preset
105
+ skySunAzimuth: 180 - DEFAULT_SUN.azimuth,
106
+ skySunElevation: DEFAULT_SUN.elevation,
107
+ skySunStrength: 1, // artistic multiplier on the disc and its light; 1 is physical
108
+ skySunSize: 0.53, // angular diameter, degrees
109
+ skyTurbidity: 2, // 1 is aerosol-free air, ~2 a clear day, 6+ hazy
110
+ skyOzone: 300, // Dobson units
111
+ skyAirDensity: 1,
112
+ skyGroundAlbedo: '#959595', // 0.3 linear
113
+ skyAltitude: 50, // metres
114
+ skyPreset: 'clearDay',
115
+
116
+ // Camera projection — 'perspective' | 'orthographic' | 'equirectangular'. Panorama ranges are UI-facing degrees.
105
117
  cameraProjection: 'perspective',
106
118
  panoramaLonRange: [ - 180, 180 ],
107
119
  panoramaLatRange: [ - 90, 90 ],
@@ -114,6 +126,9 @@ export const ENGINE_DEFAULTS = {
114
126
  focalLength: 50,
115
127
  apertureScale: 1.0,
116
128
  anamorphicRatio: 1.0,
129
+ unitsPerMetre: 1, // scene units per real metre; the lens is specified in mm
130
+ dofMode: RENDER_PROFILES.viewer.dofMode, // 'look' | 'physical'
131
+ dofBlur: 0.05, // look mode: a far background's blur, as a fraction of the image height
117
132
 
118
133
  // Auto-focus
119
134
  autoFocusMode: 'auto', // 'manual' | 'auto'
@@ -252,6 +267,9 @@ export const ENGINE_DEFAULTS = {
252
267
  autoExposureAdaptSpeedDark: 0.5,
253
268
  };
254
269
 
270
+ /** The `cameraProjection` uniform's value for each setting value. */
271
+ export const CAMERA_PROJECTION_IDS = Object.freeze( { perspective: 0, equirectangular: 1, orthographic: 2 } );
272
+
255
273
  // Ray distance NormalDepth writes on a miss. Max finite half-float, so miss−miss diffs stay 0
256
274
  // rather than Inf−Inf=NaN.
257
275
  export const GBUFFER_MISS_DEPTH = 65504.0;
@@ -419,99 +437,56 @@ export const CAMERA_RANGES = {
419
437
  }
420
438
  };
421
439
 
440
+ // `exposure` (EV) makes up about two thirds of the light a lower sun loses, as a photographer
441
+ // would: a sunset still reads darker than noon.
422
442
  export const SKY_PRESETS = {
423
- clearMorning: {
424
- name: "Clear Morning",
425
- sunAzimuth: 90,
426
- sunElevation: 20,
427
- sunIntensity: 15.0,
428
- rayleighDensity: 0.9,
429
- turbidity: 0.8,
430
- },
431
- clearNoon: {
432
- name: "Clear Noon",
433
- sunAzimuth: 0,
434
- sunElevation: 75,
435
- sunIntensity: 20.0,
436
- rayleighDensity: 1.0,
437
- turbidity: 0.3,
438
- },
439
- overcast: {
440
- name: "Overcast",
441
- sunAzimuth: 0,
442
- sunElevation: 45,
443
- sunIntensity: 6.0,
444
- rayleighDensity: 0.6,
445
- turbidity: 4.0,
446
- },
447
- goldenHour: {
448
- name: "Golden Hour",
449
- sunAzimuth: 270,
450
- sunElevation: 10,
451
- sunIntensity: 19.0,
452
- rayleighDensity: 0.8,
453
- turbidity: 1.2,
454
- },
455
- sunset: {
456
- name: "Sunset",
457
- sunAzimuth: 270,
458
- sunElevation: 2,
459
- sunIntensity: 18.0,
460
- rayleighDensity: 0.7,
461
- turbidity: 2.0,
462
- },
463
- dusk: {
464
- name: "Dusk",
465
- sunAzimuth: 270,
466
- sunElevation: - 8,
467
- sunIntensity: 8.0,
468
- rayleighDensity: 0.5,
469
- turbidity: 1.5,
470
- }
443
+ clearDay: { name: 'Clear Day', sunAzimuth: 180 - DEFAULT_SUN.azimuth, sunElevation: DEFAULT_SUN.elevation, turbidity: 2, exposure: 0 },
444
+ clearMorning: { name: 'Clear Morning', sunAzimuth: 90, sunElevation: 15, turbidity: 2, exposure: 1.4 },
445
+ clearNoon: { name: 'Clear Noon', sunAzimuth: 180, sunElevation: 70, turbidity: 2, exposure: 0 },
446
+ hazyAfternoon: { name: 'Hazy Afternoon', sunAzimuth: 225, sunElevation: 35, turbidity: 4.5, exposure: 0.4 },
447
+ goldenHour: { name: 'Golden Hour', sunAzimuth: 270, sunElevation: 6, turbidity: 2.5, exposure: 2.7 },
448
+ sunset: { name: 'Sunset', sunAzimuth: 270, sunElevation: 1, turbidity: 3, exposure: 4.2 },
449
+ blueHour: { name: 'Blue Hour', sunAzimuth: 270, sunElevation: - 4, turbidity: 2, exposure: 7 },
450
+ mountain: { name: 'Mountain Air', sunAzimuth: 135, sunElevation: 40, turbidity: 1.3, altitude: 3000, exposure: 0 },
471
451
  };
472
452
 
473
453
  export const CAMERA_PRESETS = {
474
454
  portrait: {
475
455
  name: "Portrait",
476
456
  description: "Shallow depth of field, background blur",
477
- fov: 45,
478
- focusDistance: 1.5,
457
+ dofBlur: 0.10,
479
458
  aperture: 1.4,
480
459
  focalLength: 135,
481
- apertureScale: 1.5
460
+ apertureScale: 1.0
482
461
  },
483
462
  landscape: {
484
463
  name: "Landscape",
485
464
  description: "Maximum depth of field, everything in focus",
486
- fov: 65,
487
- focusDistance: 10.0,
465
+ dofBlur: 0.001,
488
466
  aperture: 16.0,
489
467
  focalLength: 24,
490
468
  apertureScale: 0.5
491
469
  },
492
470
  macro: {
493
471
  name: "Macro",
494
- description: "Extreme close-up with thin focus plane",
495
- fov: 40,
496
- focusDistance: 0.3,
497
- aperture: 2.0,
472
+ description: "Very thin focus plane for close-ups",
473
+ dofBlur: 0.14,
474
+ aperture: 1.4,
498
475
  focalLength: 100,
499
476
  apertureScale: 2.0
500
477
  },
501
478
  product: {
502
479
  name: "Product",
503
480
  description: "Sharp detail with subtle background separation",
504
- fov: 50,
505
- focusDistance: 0.8,
481
+ dofBlur: 0.013,
506
482
  aperture: 2.8,
507
483
  focalLength: 85,
508
- apertureScale: 1.0
484
+ apertureScale: 0.4
509
485
  },
510
486
  architectural: {
511
487
  name: "Architectural",
512
- description: "Wide view with deep focus",
513
- fov: 75,
514
- focusDistance: 5.0,
488
+ description: "Deep focus for rooms and buildings",
489
+ dofBlur: 0.001,
515
490
  aperture: 11.0,
516
491
  focalLength: 16,
517
492
  apertureScale: 0.5
@@ -519,11 +494,10 @@ export const CAMERA_PRESETS = {
519
494
  cinematic: {
520
495
  name: "Cinematic",
521
496
  description: "Dramatic depth separation with anamorphic bokeh",
522
- fov: 35,
523
- focusDistance: 3.0,
497
+ dofBlur: 0.13,
524
498
  aperture: 1.4,
525
499
  focalLength: 200,
526
- apertureScale: 1.8,
500
+ apertureScale: 1.0,
527
501
  anamorphicRatio: 1.5
528
502
  }
529
503
  };
@@ -834,6 +808,16 @@ export const BVH_LEAF_MARKERS = {
834
808
  FRONTIER: 0x40000002, // parallel-build placeholder, overwritten during assembly
835
809
  };
836
810
 
811
+ /**
812
+ * A triangle leaf of at most this many triangles is folded into its parent: the child's slot ([3]
813
+ * or [7]) holds `~( first << 4 | count )`, the very value traversal pushes, so an inner node is
814
+ * read exactly as before and only popping a negative entry differs. Node indices stay below
815
+ * {@link BVH_MAX_INDEX}, leaf tags between it and 2^31, folded leaves from 2^31 up — so the count
816
+ * has four bits and the first triangle must stay below {@link BVH_FOLDED_FIRST_LIMIT}.
817
+ */
818
+ export const BVH_FOLDED_LEAF_MAX = 15;
819
+ export const BVH_FOLDED_FIRST_LIMIT = 1 << 27;
820
+
837
821
  /** A u32 view over a float buffer, for writing index fields as exact bit patterns. */
838
822
  export function bvhIndexView( f32 ) {
839
823
 
@@ -36,6 +36,7 @@ export const EngineEvents = {
36
36
 
37
37
  // Camera
38
38
  AUTO_FOCUS_UPDATED: 'engine:autoFocusUpdated',
39
+ ORTHO_HEIGHT_UPDATED: 'engine:orthoHeightUpdated',
39
40
  AUTO_EXPOSURE_UPDATED: 'engine:autoExposureUpdated',
40
41
  AF_POINT_PLACED: 'engine:afPointPlaced',
41
42
 
@@ -48,6 +49,9 @@ export const EngineEvents = {
48
49
  ANIMATION_STOPPED: 'engine:animationStopped',
49
50
  ANIMATION_FINISHED: 'engine:animationFinished',
50
51
 
52
+ // Timeline (authored keyframes)
53
+ TIMELINE_CHANGED: 'engine:timelineChanged',
54
+
51
55
  // Video rendering
52
56
  VIDEO_RENDER_PROGRESS: 'engine:videoRenderProgress',
53
57
  VIDEO_RENDER_COMPLETE: 'engine:videoRenderComplete',
@@ -61,4 +65,7 @@ export const EngineEvents = {
61
65
 
62
66
  // Scene metadata
63
67
  SCENE_METADATA_APPLIED: 'engine:sceneMetadataApplied',
68
+
69
+ // Storage (OPFS): usage or entries changed
70
+ STORAGE_CHANGED: 'engine:storageChanged',
64
71
  };
@@ -24,6 +24,12 @@ export const ISSUE_CODES = Object.freeze( {
24
24
  VIEW_TRANSFORM_DISPLAY_MISMATCH: 'viewTransform.display_mismatch',
25
25
  VIEW_TRANSFORM_BAKE_FAILED: 'viewTransform.bake_failed',
26
26
  COLOR_CONFIG_LOAD_FAILED: 'color.config_load_failed',
27
+ STORAGE_UNAVAILABLE: 'storage.unavailable',
28
+ STORAGE_QUOTA_EXCEEDED: 'storage.quota_exceeded',
29
+ STORAGE_WRITE_FAILED: 'storage.write_failed',
30
+ STORAGE_READ_FAILED: 'storage.read_failed',
31
+ STORAGE_ENTRY_CORRUPT: 'storage.entry_corrupt',
32
+ STORAGE_CACHE_MISMATCH: 'storage.cache_mismatch',
27
33
  } );
28
34
 
29
35
  /** Strict throws on ERROR only. */
package/src/Headless.js CHANGED
@@ -81,11 +81,13 @@ export async function openHeadless( {
81
81
  profile = 'physical',
82
82
  deterministic = true,
83
83
  settings = null,
84
+ storage = false,
84
85
  } = {} ) {
85
86
 
86
87
  if ( ! canvas ) throw new Error( 'openHeadless: a canvas is required' );
87
88
 
88
- const app = new PathTracerApp( canvas, { autoResize: false, strict, profile } );
89
+ // Off unless asked for: cached state from an earlier run must not change what a batch renders.
90
+ const app = new PathTracerApp( canvas, { autoResize: false, strict, profile, storage } );
89
91
 
90
92
  try {
91
93
 
@@ -205,7 +205,7 @@ export class AIUpscaler extends EventDispatcher {
205
205
 
206
206
  };
207
207
 
208
- const { ortRuntimeUrl, ortWasmPaths, cacheNamespace } = getAssetConfig();
208
+ const { ortRuntimeUrl, ortWasmPaths, cacheNamespace, storage } = getAssetConfig();
209
209
  this._worker.addEventListener( 'message', handler );
210
210
  this._worker.postMessage( {
211
211
  type: 'load',
@@ -214,6 +214,7 @@ export class AIUpscaler extends EventDispatcher {
214
214
  ortRuntimeUrl,
215
215
  ortWasmPaths,
216
216
  cacheNamespace,
217
+ storage,
217
218
  } );
218
219
 
219
220
  } );
@@ -21,6 +21,7 @@ async function getInitUNetFromBuffer() {
21
21
  }
22
22
 
23
23
  import { getAssetConfig } from '../AssetConfig.js';
24
+ import { sharedDownloads, DOWNLOAD_POLICY } from '../Storage/DownloadCache.js';
24
25
 
25
26
  const MODEL_CONFIG = {
26
27
  // No cleanAux flag in oidn-web, so the blob is it — and it must match what setCleanAuxNormal
@@ -466,12 +467,8 @@ export class OIDNDenoiser extends EventDispatcher {
466
467
  let bytes = this._weights.get( url );
467
468
  if ( ! bytes ) {
468
469
 
469
- bytes = fetch( url ).then( res => {
470
-
471
- if ( ! res.ok ) throw new Error( `HTTP ${ res.status } fetching ${ url }` );
472
- return res.arrayBuffer();
473
-
474
- } );
470
+ bytes = sharedDownloads().fetch( url, { policy: DOWNLOAD_POLICY.IMMUTABLE } )
471
+ .then( ( { file, release } ) => file.arrayBuffer().finally( release ) );
475
472
  bytes.catch( () => this._weights.delete( url ) );
476
473
  this._weights.set( url, bytes );
477
474
 
@@ -22,10 +22,11 @@ export const HISTORY_DEFAULTS = Object.freeze( {
22
22
  } );
23
23
 
24
24
  const WG = 8;
25
- const PARAMS_BYTES = 288;
25
+ const PARAMS_BYTES = 304;
26
26
  const MIN_MOVED_CAPACITY = 16;
27
27
  const FLAG_CLEAN_AUX = 1;
28
28
  const FLAG_SPLIT_COPIES = 2;
29
+ const FLAG_ORTHOGRAPHIC = 4;
29
30
  const PICK_MOVED = 0x40000000;
30
31
 
31
32
  const COMMON_WGSL = /* wgsl */`
@@ -47,6 +48,7 @@ struct Params {
47
48
  prevCamPos: vec4<f32>,
48
49
  moved: vec4<u32>,
49
50
  movedLength: f32,
51
+ prevCamDir: vec4<f32>,
50
52
  };
51
53
 
52
54
  @group(0) @binding(0) var<uniform> P: Params;
@@ -104,9 +106,19 @@ fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
104
106
 
105
107
  let size = vec2<f32>( P.size );
106
108
  let uv = ( vec2<f32>( gid.xy ) + 0.5 ) / size;
107
- let rc = P.projInv * vec4<f32>( uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 1.0, 1.0 );
108
- let dir = normalize( ( P.camWorld * vec4<f32>( rc.xyz / rc.w, 0.0 ) ).xyz );
109
- var wp = P.camWorld[ 3 ].xyz + dir * g.w;
109
+ // Depth is along the ray: from the camera, or from its image plane when orthographic.
110
+ let ortho = ( P.flags & ${ FLAG_ORTHOGRAPHIC }u ) != 0u;
111
+ let ndc = vec2<f32>( uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0 );
112
+ var origin = P.camWorld[ 3 ].xyz;
113
+ var dir: vec3<f32>;
114
+ if ( ortho ) {
115
+ origin = ( P.camWorld * vec4<f32>( ( P.projInv * vec4<f32>( ndc, 0.0, 1.0 ) ).xy, 0.0, 1.0 ) ).xyz;
116
+ dir = - normalize( P.camWorld[ 2 ].xyz );
117
+ } else {
118
+ let rc = P.projInv * vec4<f32>( ndc, 1.0, 1.0 );
119
+ dir = normalize( ( P.camWorld * vec4<f32>( rc.xyz / rc.w, 0.0 ) ).xyz );
120
+ }
121
+ var wp = origin + dir * g.w;
110
122
  var n = g.xyz * 2.0 - 1.0;
111
123
 
112
124
  // A moved object's surface is looked for where the object was.
@@ -132,7 +144,7 @@ fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
132
144
  let pos = vec2<f32>( clip.x / clip.w * 0.5 + 0.5, 0.5 - clip.y / clip.w * 0.5 ) * size - 0.5;
133
145
  let base = vec2<i32>( floor( pos ) );
134
146
  let f = fract( pos );
135
- let expected = length( wp - P.prevCamPos.xyz );
147
+ let expected = select( length( wp - P.prevCamPos.xyz ), dot( wp - P.prevCamPos.xyz, P.prevCamDir.xyz ), ortho );
136
148
 
137
149
  for ( var j = 0; j < 2; j++ ) {
138
150
  for ( var i = 0; i < 2; i++ ) {
@@ -306,6 +318,7 @@ export class OIDNTemporalHistory {
306
318
  this._paramF32 = new Float32Array( this._paramData );
307
319
  this._prevViewProj = new Float32Array( 16 );
308
320
  this._prevCamPos = new Float32Array( 4 );
321
+ this._prevCamDir = new Float32Array( 4 );
309
322
 
310
323
  }
311
324
 
@@ -325,7 +338,7 @@ export class OIDNTemporalHistory {
325
338
  /**
326
339
  * Blends the frame just traced (a restart: one sample) into the reprojected history.
327
340
  * @param {Object} src - GPUTextures { color, albedo, normal, geo, geoPrev, shading, leaf } and { width, height }
328
- * @param {{ world: Float32Array, projInv: Float32Array, viewProj: Float32Array }} camera - of that frame
341
+ * @param {{ world: Float32Array, projInv: Float32Array, viewProj: Float32Array, orthographic: boolean }} camera - of that frame
329
342
  * @param {?{ count: number, leaves: Uint32Array, toPrev: Float32Array }} [moved] - TLAS leaves
330
343
  * (ascending) that moved since the last frame, with each one's current→previous world matrix
331
344
  */
@@ -524,7 +537,7 @@ export class OIDNTemporalHistory {
524
537
  u[ 0 ] = this.width;
525
538
  u[ 1 ] = this.height;
526
539
  u[ 2 ] = reset ? 1 : 0;
527
- u[ 3 ] = ( s.cleanAux ? FLAG_CLEAN_AUX : 0 ) | ( s.splitCopies ? FLAG_SPLIT_COPIES : 0 );
540
+ u[ 3 ] = ( s.cleanAux ? FLAG_CLEAN_AUX : 0 ) | ( s.splitCopies ? FLAG_SPLIT_COPIES : 0 ) | ( camera.orthographic ? FLAG_ORTHOGRAPHIC : 0 );
528
541
  f[ 4 ] = s.maxLength;
529
542
  f[ 5 ] = s.smoothLength;
530
543
  f[ 6 ] = s.depthTolerance;
@@ -539,6 +552,7 @@ export class OIDNTemporalHistory {
539
552
  f.set( this._prevCamPos, 60 );
540
553
  u[ 64 ] = this._movedCount;
541
554
  f[ 68 ] = s.movedLength;
555
+ f.set( this._prevCamDir, 72 );
542
556
  this.device.queue.writeBuffer( this._paramsBuffer, 0, this._paramData );
543
557
 
544
558
  }
@@ -549,6 +563,10 @@ export class OIDNTemporalHistory {
549
563
  this._prevCamPos[ 0 ] = camera.world[ 12 ];
550
564
  this._prevCamPos[ 1 ] = camera.world[ 13 ];
551
565
  this._prevCamPos[ 2 ] = camera.world[ 14 ];
566
+ const back = Math.hypot( camera.world[ 8 ], camera.world[ 9 ], camera.world[ 10 ] );
567
+ this._prevCamDir[ 0 ] = - camera.world[ 8 ] / back;
568
+ this._prevCamDir[ 1 ] = - camera.world[ 9 ] / back;
569
+ this._prevCamDir[ 2 ] = - camera.world[ 10 ] / back;
552
570
 
553
571
  }
554
572