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
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * CameraRay.js — primary camera ray generation.
3
3
  *
4
- * Two projections behind one `cameraProjection` uniform: 0 = pinhole (NDC through
5
- * cameraProjectionMatrixInverse), 1 = equirectangular 360 panorama. Both branches live in the same
6
- * kernel, so switching modes writes a uniform and resets accumulation — it never recompiles WGSL.
4
+ * Three projections behind one `cameraProjection` uniform (CAMERA_PROJECTION_IDS): pinhole (NDC through
5
+ * cameraProjectionMatrixInverse), equirectangular 360 panorama, and orthographic (parallel rays from the
6
+ * camera's image plane). All live in the same kernel, so switching modes writes a uniform and resets
7
+ * accumulation — it never recompiles WGSL.
7
8
  *
8
9
  * Equirect mapping, for uv ∈ [0,1] with v = 0 at the top row:
9
10
  * lon = mix( lonMin, lonMax, u ), lat = mix( latMax, latMin, v )
@@ -12,15 +13,81 @@
12
13
  */
13
14
 
14
15
  import {
15
- Fn, vec3, vec4, float, int,
16
- If, normalize, mat3, mix, sin, cos, cross, dot,
16
+ Fn, vec3, vec4, float, int, uniform,
17
+ If, normalize, mat3, mix, sin, cos, cross, dot, select,
17
18
  } from 'three/tsl';
19
+ import { Vector2 } from 'three';
18
20
 
19
21
  import { Ray } from './Struct.js';
20
22
  import { constructTBN } from './Common.js';
21
23
  import { RandomPointInCircle } from './Random.js';
24
+ import { CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
22
25
 
23
- /** World-space primary ray direction for pixel uv. Origin is always cameraWorldMatrix[3]. */
26
+ const PANORAMA = int( CAMERA_PROJECTION_IDS.equirectangular );
27
+ const ORTHOGRAPHIC = int( CAMERA_PROJECTION_IDS.orthographic );
28
+
29
+ export const isOrthographic = cameraProjection => cameraProjection.equal( ORTHOGRAPHIC );
30
+
31
+ /**
32
+ * The projection uniforms a stage re-tracing the path tracer's primary rays needs. `sync` copies the path
33
+ * tracer's, once a frame.
34
+ */
35
+ export function cameraRayUniforms() {
36
+
37
+ const u = {
38
+ cameraProjection: uniform( 0, 'int' ),
39
+ panoLonRange: uniform( new Vector2(), 'vec2' ),
40
+ panoLatRange: uniform( new Vector2(), 'vec2' ),
41
+ panoLevelHorizon: uniform( 1, 'int' ),
42
+ /** @param {import('../managers/UniformManager.js').UniformManager} source */
43
+ sync( source ) {
44
+
45
+ u.cameraProjection.value = source.get( 'cameraProjection' ).value;
46
+ u.panoLonRange.value.copy( source.get( 'panoLonRange' ).value );
47
+ u.panoLatRange.value.copy( source.get( 'panoLatRange' ).value );
48
+ u.panoLevelHorizon.value = source.get( 'panoLevelHorizon' ).value;
49
+
50
+ },
51
+ };
52
+ return u;
53
+
54
+ }
55
+
56
+ /** Pixel uv's primary ray, for a stage holding {@link cameraRayUniforms}. */
57
+ export const cameraRayOf = ( uv01, cameraWorldMatrix, cameraProjectionMatrixInverse, u ) => ( {
58
+ origin: cameraRayOrigin( uv01, cameraWorldMatrix, cameraProjectionMatrixInverse, u.cameraProjection ),
59
+ direction: cameraRayDirection(
60
+ uv01, cameraWorldMatrix, cameraProjectionMatrixInverse,
61
+ u.cameraProjection, u.panoLonRange, u.panoLatRange, u.panoLevelHorizon
62
+ ),
63
+ } );
64
+
65
+ /** The point `distance` along pixel uv's primary ray — where a depth the path tracer measured lies. */
66
+ export const cameraRayPoint = ( uv01, distance, cameraWorldMatrix, cameraProjectionMatrixInverse, u ) => {
67
+
68
+ const { origin, direction } = cameraRayOf( uv01, cameraWorldMatrix, cameraProjectionMatrixInverse, u );
69
+ return origin.add( direction.mul( distance ) );
70
+
71
+ };
72
+
73
+ /** World-space primary ray origin for pixel uv: the camera, or its image plane when orthographic. */
74
+ export const cameraRayOrigin = Fn( ( [ uv01, cameraWorldMatrix, cameraProjectionMatrixInverse, cameraProjection ] ) => {
75
+
76
+ const origin = vec3( cameraWorldMatrix[ 3 ] ).toVar();
77
+
78
+ If( isOrthographic( cameraProjection ), () => {
79
+
80
+ // An orthographic inverse maps x and y alone, whatever the depth.
81
+ const onPlane = cameraProjectionMatrixInverse.mul( vec4( uv01.x.mul( 2.0 ).sub( 1.0 ), float( 1.0 ).sub( uv01.y.mul( 2.0 ) ), 0.0, 1.0 ) );
82
+ origin.assign( cameraWorldMatrix.mul( vec4( onPlane.xy, 0.0, 1.0 ) ).xyz );
83
+
84
+ } );
85
+
86
+ return origin;
87
+
88
+ } );
89
+
90
+ /** World-space primary ray direction for pixel uv, from {@link cameraRayOrigin}. */
24
91
  export const cameraRayDirection = Fn( ( [
25
92
  uv01, cameraWorldMatrix, cameraProjectionMatrixInverse,
26
93
  cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon
@@ -28,7 +95,7 @@ export const cameraRayDirection = Fn( ( [
28
95
 
29
96
  const direction = vec3( 0.0 ).toVar();
30
97
 
31
- If( cameraProjection.equal( int( 1 ) ), () => {
98
+ If( cameraProjection.equal( PANORAMA ), () => {
32
99
 
33
100
  const lon = mix( panoLonRange.x, panoLonRange.y, uv01.x ).toVar();
34
101
  const lat = mix( panoLatRange.y, panoLatRange.x, uv01.y ).toVar();
@@ -68,6 +135,10 @@ export const cameraRayDirection = Fn( ( [
68
135
  .sub( back.mul( cos( lon ).mul( cosLat ) ) )
69
136
  ) );
70
137
 
138
+ } ).ElseIf( isOrthographic( cameraProjection ), () => {
139
+
140
+ direction.assign( normalize( vec3( cameraWorldMatrix[ 2 ] ) ).negate() );
141
+
71
142
  } ).Else( () => {
72
143
 
73
144
  const ndcPos = vec3( uv01.x.mul( 2.0 ).sub( 1.0 ), float( 1.0 ).sub( uv01.y.mul( 2.0 ) ), 1.0 );
@@ -89,10 +160,10 @@ export const generateRayFromCamera = Fn( ( [
89
160
  uv01, rngState,
90
161
  cameraWorldMatrix, cameraProjectionMatrixInverse,
91
162
  cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
92
- enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio
163
+ enableDOF, focalLength, aperture, focusDistance, unitsPerMetre, apertureScale, anamorphicRatio, dofMode, dofBlur
93
164
  ] ) => {
94
165
 
95
- const rayOriginWorld = vec3( cameraWorldMatrix[ 3 ] ).toVar();
166
+ const rayOriginWorld = cameraRayOrigin( uv01, cameraWorldMatrix, cameraProjectionMatrixInverse, cameraProjection ).toVar();
96
167
  // .toVar() so the two reads below don't inline the whole projection graph twice.
97
168
  const rayDirectionWorld = cameraRayDirection(
98
169
  uv01, cameraWorldMatrix, cameraProjectionMatrixInverse,
@@ -102,10 +173,20 @@ export const generateRayFromCamera = Fn( ( [
102
173
  const resultOrigin = rayOriginWorld.toVar();
103
174
  const resultDirection = rayDirectionWorld.toVar();
104
175
 
105
- If( enableDOF.and( focalLength.greaterThan( 0.0 ) ).and( aperture.lessThan( 64.0 ) ).and( focusDistance.greaterThan( 0.001 ) ), () => {
176
+ const lookMode = dofMode.equal( int( 1 ) );
177
+ const lensOpen = lookMode.and( dofBlur.greaterThan( 0.0 ) ).or( lookMode.not().and( focalLength.greaterThan( 0.0 ) ).and( aperture.lessThan( 64.0 ) ) );
106
178
 
107
- const effectiveAperture = focalLength.div( aperture );
108
- const apertureRadius = effectiveAperture.mul( 0.001 ).mul( sceneScale ).mul( apertureScale );
179
+ If( enableDOF.and( lensOpen ).and( focusDistance.greaterThan( 0.001 ) ), () => {
180
+
181
+ // Orthographic 1: the blur then does not change with where the camera stands.
182
+ const halfViewHeight = select( cameraProjection.equal( PANORAMA ), panoLatRange.y.sub( panoLatRange.x ).mul( 0.5 ),
183
+ select( isOrthographic( cameraProjection ), float( 1.0 ), cameraProjectionMatrixInverse[ 1 ].y ) );
184
+ const apertureRadius = select( lookMode,
185
+ // A far background blurs by dofBlur of the image height, whatever the scene's scale.
186
+ dofBlur.mul( focusDistance ).mul( halfViewHeight ),
187
+ // f/N is the aperture's diameter, not its radius.
188
+ focalLength.div( aperture.mul( 2.0 ) ).mul( 0.001 ).mul( unitsPerMetre ).mul( apertureScale )
189
+ );
109
190
 
110
191
  const randomPoint = RandomPointInCircle( rngState );
111
192
  // Anamorphic squeeze — stretch horizontally for oval bokeh
@@ -113,8 +194,9 @@ export const generateRayFromCamera = Fn( ( [
113
194
  const lensY = randomPoint.y;
114
195
 
115
196
  const lensOffset = vec3( 0.0 ).toVar();
197
+ const focusAlongRay = focusDistance.toVar();
116
198
 
117
- If( cameraProjection.equal( int( 1 ) ), () => {
199
+ If( cameraProjection.equal( PANORAMA ), () => {
118
200
 
119
201
  // Every pixel points a different way, so the lens plane is built around the ray. The
120
202
  // camera's right/up would skew bokeh into a slit away from the image centre.
@@ -126,10 +208,13 @@ export const generateRayFromCamera = Fn( ( [
126
208
  const camUp = normalize( vec3( cameraWorldMatrix[ 1 ] ) );
127
209
  lensOffset.assign( camRight.mul( lensX ).add( camUp.mul( lensY ) ) );
128
210
 
211
+ // A flat focal plane: focusDistance is depth along the view axis, not distance along the ray.
212
+ focusAlongRay.assign( focusDistance.div( dot( rayDirectionWorld, normalize( vec3( cameraWorldMatrix[ 2 ] ) ) ).negate() ) );
213
+
129
214
  } );
130
215
 
131
216
  resultOrigin.assign( rayOriginWorld.add( lensOffset.mul( apertureRadius ) ) );
132
- resultDirection.assign( normalize( rayOriginWorld.add( rayDirectionWorld.mul( focusDistance ) ).sub( resultOrigin ) ) );
217
+ resultDirection.assign( normalize( rayOriginWorld.add( rayDirectionWorld.mul( focusAlongRay ) ).sub( resultOrigin ) ) );
133
218
 
134
219
  } );
135
220
 
package/src/TSL/Common.js CHANGED
@@ -329,8 +329,17 @@ export const getDatafromStorageBuffer = Fn( ( [ buffer, stride, sampleIndex, dat
329
329
 
330
330
  } );
331
331
 
332
- /** uvec4 lanes per triangle record — see TRIANGLE_DATA_LAYOUT. */
333
- export const TRI_STRIDE = 5;
332
+ /**
333
+ * The five uvec4 rows of a triangle record (TRIANGLE_DATA_LAYOUT) live in two GPU buffers: rows
334
+ * 0–2, positions with their packed normals, in `tris.geo`; rows 3–4, UVs, flags and mesh index,
335
+ * in `tris.shade`. One buffer of 80 B a triangle hit the 4 GB storage-buffer limit at 53.6M.
336
+ */
337
+ export const TRI_GEO_ROWS = 3;
338
+ export const TRI_SHADE_ROWS = 2;
339
+
340
+ export const triangleRow = ( tris, triIndex, row ) => row < TRI_GEO_ROWS
341
+ ? getDatafromStorageBuffer( tris.geo, triIndex, int( row ), int( TRI_GEO_ROWS ) )
342
+ : getDatafromStorageBuffer( tris.shade, triIndex, int( row - TRI_GEO_ROWS ), int( TRI_SHADE_ROWS ) );
334
343
 
335
344
  // Unit normal to an octahedral snorm16 pair; GPU twin of packNormalOct in EngineDefaults.
336
345
  export const packNormalOct = /*@__PURE__*/ wgslFn( `
@@ -27,7 +27,7 @@ export function buildDebugKernel( params ) {
27
27
  resolution, renderWidth, renderHeight,
28
28
  cameraWorldMatrix, cameraProjectionMatrixInverse, cameraProjectionMatrix, cameraViewMatrix,
29
29
  cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
30
- enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
30
+ enableDOF, focalLength, aperture, focusDistance, unitsPerMetre, apertureScale, anamorphicRatio, dofMode, dofBlur,
31
31
  bvhBuffer, triangleBuffer, materialBuffer,
32
32
  envTexture, environmentMatrix, environmentIntensity, enableEnvironmentLight,
33
33
  visMode, debugVisScale,
@@ -50,7 +50,7 @@ export function buildDebugKernel( params ) {
50
50
  pixelCoord.div( resolution ), seed,
51
51
  cameraWorldMatrix, cameraProjectionMatrixInverse,
52
52
  cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
53
- enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
53
+ enableDOF, focalLength, aperture, focusDistance, unitsPerMetre, apertureScale, anamorphicRatio, dofMode, dofBlur,
54
54
  ) );
55
55
 
56
56
  const color = vec4( 1.0, 0.0, 1.0, 1.0 ).toVar();
@@ -1,7 +1,7 @@
1
1
  import { Fn, float, vec2, int, If, Loop, abs, normalize, dot, max, uintBitsToFloat } from 'three/tsl';
2
2
 
3
3
  import { struct } from './patches.js';
4
- import { getDatafromStorageBuffer, TRI_STRIDE, instanceRows, instanceDirToWorld } from './Common.js';
4
+ import { triangleRow, instanceRows, instanceDirToWorld } from './Common.js';
5
5
  import { sampleDisplacementMap, bucketTexelSize, getLinearBucketTextures } from './TextureSampling.js';
6
6
 
7
7
  // Ray-displacement intersection configuration
@@ -42,12 +42,12 @@ export const refineDisplacedIntersection = Fn( ( [
42
42
  // Fetch triangle vertex data
43
43
  const triIdx = hitInfo.triangleIndex;
44
44
 
45
- const pA = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIdx, int( 0 ), int( TRI_STRIDE ) ).xyz ).toVar();
46
- const pB = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIdx, int( 1 ), int( TRI_STRIDE ) ).xyz );
47
- const pC = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIdx, int( 2 ), int( TRI_STRIDE ) ).xyz );
45
+ const pA = uintBitsToFloat( triangleRow( triangleBuffer, triIdx, 0 ).xyz ).toVar();
46
+ const pB = uintBitsToFloat( triangleRow( triangleBuffer, triIdx, 1 ).xyz );
47
+ const pC = uintBitsToFloat( triangleRow( triangleBuffer, triIdx, 2 ).xyz );
48
48
 
49
- const uvData1 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIdx, int( 3 ), int( TRI_STRIDE ) ) ).toVar();
50
- const uvData2 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIdx, int( 4 ), int( TRI_STRIDE ) ).xy );
49
+ const uvData1 = uintBitsToFloat( triangleRow( triangleBuffer, triIdx, 3 ) ).toVar();
50
+ const uvData2 = uintBitsToFloat( triangleRow( triangleBuffer, triIdx, 4 ).xy );
51
51
 
52
52
  const uvA = uvData1.xy.toVar();
53
53
  const uvB = uvData1.zw;
@@ -30,7 +30,7 @@ import { struct } from './patches.js';
30
30
  import {
31
31
  MIN_PDF, getDatafromStorageBuffer, powerHeuristic, MATERIAL_SLOTS, MATERIAL_SLOT,
32
32
  computeDotProductsAniso, instanceRows, instanceNormalToWorld, instancePointToWorld,
33
- unpackTriangleNormal, TRI_STRIDE, SHADOW_END
33
+ unpackTriangleNormal, triangleRow, SHADOW_END
34
34
  } from './Common.js';
35
35
  import { TRI_MATERIAL_MASK } from '../EngineDefaults.js';
36
36
  import { getRandomSample1D, getRandomSample2D } from './Random.js';
@@ -298,11 +298,11 @@ export const TriangleData = struct( {
298
298
  export const fetchTriangleData = Fn( ( [ triangleIndex, triangleBuffer, bvhBuffer, instanceLeaf ] ) => {
299
299
 
300
300
  // Positions carry their packed normal in .w
301
- const pos0 = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 0 ), int( TRI_STRIDE ) ).toVar();
302
- const pos1 = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 1 ), int( TRI_STRIDE ) ).toVar();
303
- const pos2 = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 2 ), int( TRI_STRIDE ) ).toVar();
301
+ const pos0 = triangleRow( triangleBuffer, triangleIndex, 0 ).toVar();
302
+ const pos1 = triangleRow( triangleBuffer, triangleIndex, 1 ).toVar();
303
+ const pos2 = triangleRow( triangleBuffer, triangleIndex, 2 ).toVar();
304
304
 
305
- const uvMat = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 4 ), int( TRI_STRIDE ) );
305
+ const uvMat = triangleRow( triangleBuffer, triangleIndex, 4 );
306
306
 
307
307
  const v0 = uintBitsToFloat( pos0.xyz ).toVar(), v1 = uintBitsToFloat( pos1.xyz ).toVar(), v2 = uintBitsToFloat( pos2.xyz ).toVar();
308
308
  const n0 = unpackTriangleNormal( pos0.w ).toVar(), n1 = unpackTriangleNormal( pos1.w ).toVar(), n2 = unpackTriangleNormal( pos2.w ).toVar();
@@ -0,0 +1,176 @@
1
+ import { Fn, float, int, uint, vec2, vec4, If, Loop, Return, instanceIndex, max, sin, dot, select } from 'three/tsl';
2
+
3
+ import { REC709_LUMINANCE_COEFFICIENTS } from './Common.js';
4
+
5
+ /** Floats per row of the packed table: width + 1 texels, padded to the 256 bytes a buffer→texture copy needs. */
6
+ export const cdfRowStride = width => Math.ceil( ( width + 1 ) * 4 / 256 ) * 64;
7
+
8
+ // First index in [0, n) whose value is ≥ target, n − 1 when none is — as the CPU builder's search.
9
+ function lowerBound( n, valueAt, target ) {
10
+
11
+ const lo = int( 0 ).toVar();
12
+ const hi = int( n - 1 ).toVar();
13
+ Loop( { start: int( 0 ), end: int( Math.ceil( Math.log2( n ) ) + 1 ), type: 'int', condition: '<', name: 'step' }, () => {
14
+
15
+ If( lo.lessThan( hi ), () => {
16
+
17
+ const mid = lo.add( hi ).div( 2 ).toVar();
18
+ If( valueAt( mid ).lessThan( target ), () => {
19
+
20
+ lo.assign( mid.add( 1 ) );
21
+
22
+ } ).Else( () => {
23
+
24
+ hi.assign( mid );
25
+
26
+ } );
27
+
28
+ } );
29
+
30
+ } );
31
+ return lo;
32
+
33
+ }
34
+
35
+ // select(), never mix(): a + t·(b − a) is not b at t = 1, and the search compares a prefix sum
36
+ // against the row total it ends on.
37
+
38
+ /**
39
+ * The GPU twin of `EquirectHDRInfo.computeCDF`, into the packed (width + 1) × height table the
40
+ * sampler reads. `stats[ 1 ]` ends as ( totalSum, compensationDelta, compensated, 0 ).
41
+ * @returns {Array} kernels, dispatched in order
42
+ */
43
+ export function buildEnvironmentCDFKernels( { pixels, rows, prefix, stats, cdf, width, height } ) {
44
+
45
+ const stride = cdfRowStride( width );
46
+ const sinTheta = y => sin( float( y ).add( 0.5 ).mul( Math.PI / height ) );
47
+ const weight = ( y, x, sinT ) => dot( pixels.element( y.mul( width ).add( x ) ).xyz, REC709_LUMINANCE_COEFFICIENTS ).mul( sinT );
48
+ const firstThreadOnly = () => If( instanceIndex.greaterThan( uint( 0 ) ), () => {
49
+
50
+ Return();
51
+
52
+ } );
53
+
54
+ const rowTotals = Fn( () => {
55
+
56
+ const y = instanceIndex;
57
+ If( y.greaterThanEqual( uint( height ) ), () => {
58
+
59
+ Return();
60
+
61
+ } );
62
+
63
+ const sinT = sinTheta( y ).toVar();
64
+ const total = float( 0 ).toVar();
65
+ Loop( { start: int( 0 ), end: int( width ), type: 'int', condition: '<' }, ( { i } ) => {
66
+
67
+ total.addAssign( weight( y, uint( i ), sinT ) );
68
+
69
+ } );
70
+ rows.element( y ).assign( vec4( total, 0, 0, 0 ) );
71
+
72
+ } )().compute( height, [ 64 ] );
73
+
74
+ const mean = Fn( () => {
75
+
76
+ firstThreadOnly();
77
+ const total = float( 0 ).toVar();
78
+ Loop( { start: int( 0 ), end: int( height ), type: 'int', condition: '<' }, ( { i } ) => {
79
+
80
+ total.addAssign( rows.element( i ).x );
81
+
82
+ } );
83
+ stats.element( 0 ).assign( vec4( total, total.div( width * height ), 0, 0 ) );
84
+
85
+ } )().compute( 1, [ 1 ] );
86
+
87
+ const prefixSums = Fn( () => {
88
+
89
+ const y = instanceIndex;
90
+ If( y.greaterThanEqual( uint( height ) ), () => {
91
+
92
+ Return();
93
+
94
+ } );
95
+
96
+ const sinT = sinTheta( y ).toVar();
97
+ const delta = stats.element( 0 ).y.toVar();
98
+ const raw = float( 0 ).toVar();
99
+ const compensated = float( 0 ).toVar();
100
+ Loop( { start: int( 0 ), end: int( width ), type: 'int', condition: '<' }, ( { i } ) => {
101
+
102
+ const w = weight( y, uint( i ), sinT ).toVar();
103
+ raw.addAssign( w );
104
+ compensated.addAssign( max( w.sub( delta ), 0 ) );
105
+ prefix.element( y.mul( width ).add( uint( i ) ) ).assign( vec2( raw, compensated ) );
106
+
107
+ } );
108
+ rows.element( y ).assign( vec4( raw, compensated, 0, 0 ) );
109
+
110
+ } )().compute( height, [ 64 ] );
111
+
112
+ const marginal = Fn( () => {
113
+
114
+ firstThreadOnly();
115
+ const compensatedTotal = float( 0 ).toVar();
116
+ Loop( { start: int( 0 ), end: int( height ), type: 'int', condition: '<' }, ( { i } ) => {
117
+
118
+ compensatedTotal.addAssign( rows.element( i ).y );
119
+
120
+ } );
121
+ const useCompensated = compensatedTotal.greaterThan( 0 ).toVar();
122
+
123
+ const cumulative = float( 0 ).toVar();
124
+ Loop( { start: int( 0 ), end: int( height ), type: 'int', condition: '<' }, ( { i } ) => {
125
+
126
+ const r = rows.element( i ).toVar();
127
+ cumulative.addAssign( select( useCompensated, r.y, r.x ) );
128
+ rows.element( i ).assign( vec4( r.x, r.y, cumulative, 0 ) );
129
+
130
+ } );
131
+
132
+ const s = stats.element( 0 ).toVar();
133
+ stats.element( 1 ).assign( select( useCompensated, vec4( compensatedTotal, s.y, 1, 0 ), vec4( s.x, 0, 0, 0 ) ) );
134
+
135
+ Loop( { start: int( 0 ), end: int( height ), type: 'int', condition: '<', name: 'row' }, ( { row } ) => {
136
+
137
+ const k = select(
138
+ cumulative.greaterThan( 0 ),
139
+ float( lowerBound( height, j => rows.element( j ).z, float( row ).add( 1 ).div( height ).mul( cumulative ) ) ),
140
+ float( height - 1 ),
141
+ );
142
+ cdf.element( uint( row ).mul( stride ).add( width ) ).assign( k.add( 0.5 ).div( height ) );
143
+
144
+ } );
145
+
146
+ } )().compute( 1, [ 1 ] );
147
+
148
+ const conditional = Fn( () => {
149
+
150
+ const idx = instanceIndex;
151
+ If( idx.greaterThanEqual( uint( width * height ) ), () => {
152
+
153
+ Return();
154
+
155
+ } );
156
+
157
+ const x = idx.mod( width );
158
+ const y = idx.div( width );
159
+ const useCompensated = stats.element( 1 ).z.greaterThan( 0 ).toVar();
160
+ const r = rows.element( y ).toVar();
161
+ const rowTotal = select( useCompensated, r.y, r.x ).toVar();
162
+ const base = y.mul( width ).toVar();
163
+ const k = lowerBound( width, j => {
164
+
165
+ const p = prefix.element( base.add( uint( j ) ) );
166
+ return select( useCompensated, p.y, p.x );
167
+
168
+ }, float( x ).add( 1 ).div( width ).mul( rowTotal ) );
169
+ const column = select( rowTotal.greaterThan( 0 ), float( k ), float( width - 1 ) );
170
+ cdf.element( y.mul( stride ).add( x ) ).assign( column.add( 0.5 ).div( width ) );
171
+
172
+ } )().compute( width * height, [ 64 ] );
173
+
174
+ return [ rowTotals, mean, prefixSums, marginal, conditional ];
175
+
176
+ }
@@ -28,7 +28,7 @@ import { RAY_FLAG, COUNTER } from '../Processor/QueueManager.js';
28
28
  import {
29
29
  writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf,
30
30
  writeRayRadiance, writeGBuffer,
31
- writeMediumStack, writeFeatureThroughput,
31
+ writeMediumStack, writeFeatureThroughput, writeRngState,
32
32
  } from '../Processor/PackedRayBuffer.js';
33
33
 
34
34
  const WG_SIZE = 16;
@@ -36,11 +36,11 @@ const WG_SIZE = 16;
36
36
  export function buildGenerateKernel( params ) {
37
37
 
38
38
  const {
39
- rayBufferRW, rngBufferRW, gBufferRW,
39
+ rayBufferRW, hitBufferRW, gBufferRW,
40
40
  resolution, frame,
41
41
  cameraWorldMatrix, cameraProjectionMatrixInverse,
42
42
  cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
43
- enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
43
+ enableDOF, focalLength, aperture, focusDistance, unitsPerMetre, apertureScale, anamorphicRatio, dofMode, dofBlur,
44
44
  renderWidth,
45
45
  chunkRowBase, chunkRows, // row band offset (global first row) + row count for this chunk
46
46
  transmissiveBounces, // per-ray refraction budget (megakernel parity: PathTracerCore.js:606)
@@ -78,7 +78,7 @@ export function buildGenerateKernel( params ) {
78
78
  jitteredUV, seed,
79
79
  cameraWorldMatrix, cameraProjectionMatrixInverse,
80
80
  cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
81
- enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
81
+ enableDOF, focalLength, aperture, focusDistance, unitsPerMetre, apertureScale, anamorphicRatio, dofMode, dofBlur,
82
82
  ) );
83
83
 
84
84
  writeRayOriginMeta( rayBufferRW, rayID, ray.origin, int( 0 ), int( 0 ) );
@@ -104,7 +104,7 @@ export function buildGenerateKernel( params ) {
104
104
 
105
105
  writeMediumStack( rayBufferRW, rayID, uint( 0 ), uint( transmissiveBounces ), float( 1.0 ), float( 1.0 ), float( 1.0 ) );
106
106
 
107
- rngBufferRW.element( rayID ).assign( seed );
107
+ writeRngState( hitBufferRW, rayID, seed );
108
108
 
109
109
  };
110
110
 
@@ -15,7 +15,7 @@ import {
15
15
  packHalf2x16, unpackHalf2x16,
16
16
  } from 'three/tsl';
17
17
  import {
18
- getDatafromStorageBuffer, unpackTriangleNormal, TRI_STRIDE, instanceRows, instanceFaceNormalToWorld,
18
+ unpackTriangleNormal, triangleRow, instanceRows, instanceFaceNormalToWorld,
19
19
  } from './Common.js';
20
20
  import { shadowTerminatorLift } from './ShadowTerminator.js';
21
21
 
@@ -30,9 +30,9 @@ export function hitFacet( { triangleBuffer, bvhBuffer, triIdx, instanceLeaf, hit
30
30
 
31
31
  If( didHit, () => {
32
32
 
33
- const recA = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 0 ), int( TRI_STRIDE ) ).toVar();
34
- const recB = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 1 ), int( TRI_STRIDE ) ).toVar();
35
- const recC = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 2 ), int( TRI_STRIDE ) ).toVar();
33
+ const recA = triangleRow( triangleBuffer, triIdx, 0 ).toVar();
34
+ const recB = triangleRow( triangleBuffer, triIdx, 1 ).toVar();
35
+ const recC = triangleRow( triangleBuffer, triIdx, 2 ).toVar();
36
36
  const V0 = uintBitsToFloat( recA.xyz ).toVar();
37
37
  const V1 = uintBitsToFloat( recB.xyz ).toVar();
38
38
  const V2 = uintBitsToFloat( recC.xyz ).toVar();
@@ -27,8 +27,8 @@ import {
27
27
 
28
28
  import { Ray, ShadowMaterial, HitInfo } from './Struct.js';
29
29
  import {
30
- REC709_LUMINANCE_COEFFICIENTS, getShadowMaterial, getDatafromStorageBuffer, instanceRows,
31
- instanceFaceNormalToWorld, TRI_STRIDE, getAlphaShadowsUniform, shadowFlagsSettle, offsetRayOrigin,
30
+ REC709_LUMINANCE_COEFFICIENTS, getShadowMaterial, instanceRows,
31
+ instanceFaceNormalToWorld, triangleRow, getAlphaShadowsUniform, shadowFlagsSettle, offsetRayOrigin,
32
32
  } from './Common.js';
33
33
  import { fresnelDielectric } from './Fresnel.js';
34
34
  import { calculateBeerLawAbsorption } from './MaterialTransmission.js';
@@ -92,7 +92,7 @@ export const traceShadowRay = Fn( ( [
92
92
  // Opaque fast-path: check the per-triangle blocker bit, set at extraction time when
93
93
  // alphaMode/transparent/transmission/opacity all indicate a fully opaque surface.
94
94
  // Short-circuits the 7-slot getShadowMaterial fetch and the whole alpha decision tree.
95
- const flags = getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 4 ), int( TRI_STRIDE ) ).z;
95
+ const flags = triangleRow( triangleBuffer, shadowHit.triangleIndex, 4 ).z;
96
96
  If( shadowFlagsSettle( flags ), () => {
97
97
 
98
98
  transmittance.assign( 0.0 );
@@ -125,8 +125,8 @@ export const traceShadowRay = Fn( ( [
125
125
  const baryU = shadowHit.uv.x;
126
126
  const baryV = shadowHit.uv.y;
127
127
  const baryW = float( 1.0 ).sub( baryU ).sub( baryV );
128
- const uvData1 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 3 ), int( TRI_STRIDE ) ) );
129
- const uvData2 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 4 ), int( TRI_STRIDE ) ).xy );
128
+ const uvData1 = uintBitsToFloat( triangleRow( triangleBuffer, shadowHit.triangleIndex, 3 ) );
129
+ const uvData2 = uintBitsToFloat( triangleRow( triangleBuffer, shadowHit.triangleIndex, 4 ).xy );
130
130
  const hitUV = uvData1.xy.mul( baryW ).add( uvData1.zw.mul( baryU ) ).add( uvData2.mul( baryV ) );
131
131
  const albedoUV = getTransformedUV( { uv: hitUV, transform: shadowMaterial.albedoTransform } );
132
132
  texAlpha.assign( sampleBucket( _shadowAlbedoMaps, shadowMaterial.albedoMapIndex, albedoUV ).a );
@@ -181,9 +181,9 @@ export const traceShadowRay = Fn( ( [
181
181
  // Deferred geometric-normal compute — refetch triangle positions and
182
182
  // derive the normal here so opaque/alpha-cutout shadow hits don't pay
183
183
  // the cross+normalize cost in BVH traversal.
184
- const pA = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 0 ), int( TRI_STRIDE ) ).xyz );
185
- const pB = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 1 ), int( TRI_STRIDE ) ).xyz );
186
- const pC = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 2 ), int( TRI_STRIDE ) ).xyz );
184
+ const pA = uintBitsToFloat( triangleRow( triangleBuffer, shadowHit.triangleIndex, 0 ).xyz );
185
+ const pB = uintBitsToFloat( triangleRow( triangleBuffer, shadowHit.triangleIndex, 1 ).xyz );
186
+ const pC = uintBitsToFloat( triangleRow( triangleBuffer, shadowHit.triangleIndex, 2 ).xyz );
187
187
  // Positions are in the blocker's object space; the cross product is a normal, so
188
188
  // it rides the inverse-transpose back out rather than the forward matrix.
189
189
  const objNormal = normalize( cross( pB.sub( pA ), pC.sub( pA ) ) ).toVar();