rayzee 11.6.0 → 11.8.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 (72) hide show
  1. package/README.md +26 -14
  2. package/dist/{ArchiveImporter-iofnC8zB.js → ArchiveImporter-_sJm5uaK.js} +1923 -1243
  3. package/dist/{BidirectionalIntegrator-CqAilt2I.js → BidirectionalIntegrator-Dzt4Z97x.js} +290 -289
  4. package/dist/{ColorManagement-oeEk7oSu.js → ColorManagement-30MfpHwQ.js} +1 -1
  5. package/dist/{EngineIssues-CIZnXHsF.js → EngineIssues-xljoXM0E.js} +18 -8
  6. package/dist/{NeuralRetouch-DFI4pOjO.js → NeuralRetouch-BKQCsmEs.js} +2 -2
  7. package/dist/{NeuralSuperRes-ChCiiilS.js → NeuralSuperRes-C4ymq68e.js} +2 -2
  8. package/dist/{PathTracer-BX4sJLx7.js → PathTracer-C_to_i40.js} +3158 -3102
  9. package/dist/{PhysicalSky-Biv39eTA.js → PhysicalSky-bMHbm_7H.js} +1 -1
  10. package/dist/{RayzeeRenderer-D8XRKU3A.js → RayzeeRenderer-B_aB6VTJ.js} +370 -372
  11. package/dist/SceneMetadata-CSmlJW4c.js +183 -0
  12. package/dist/{ToneMapGPU-sAs8wdgI.js → ToneMapGPU-4GPwoG_c.js} +13 -6
  13. package/dist/addons/archives.es.js +1 -1
  14. package/dist/addons/bidirectional.es.js +1 -1
  15. package/dist/addons/color.es.js +1 -1
  16. package/dist/addons/physical-sky.es.js +1 -1
  17. package/dist/addons/storage.es.js +1 -1
  18. package/dist/{openStorage-Fe9BoS1_.js → openStorage-B085D9FR.js} +1 -1
  19. package/dist/rayzee-core.es.js +5 -5
  20. package/dist/rayzee.es.js +917 -1624
  21. package/dist/rayzee.umd.js +113 -156
  22. package/dist/{sceneCachePolicy-W5PLbzuR.js → sceneCachePolicy-D_uBSbQF.js} +2 -2
  23. package/package.json +1 -1
  24. package/src/EngineIssues.js +20 -6
  25. package/src/Passes/AIUpscaler.js +4 -0
  26. package/src/Passes/OIDNDenoiser.js +17 -17
  27. package/src/PathTracerApp.js +16 -18
  28. package/src/Pipeline/BindingAudit.js +2 -3
  29. package/src/Pipeline/PipelineContext.js +0 -1
  30. package/src/Pipeline/RenderPipeline.js +1 -2
  31. package/src/Pipeline/RenderStage.js +1 -1
  32. package/src/Processor/ArchiveImporter.js +23 -6
  33. package/src/Processor/AssetLoader.js +15 -0
  34. package/src/Processor/LightSerializer.js +6 -2
  35. package/src/Processor/PBRT/PBRTMaterials.js +2 -1
  36. package/src/Processor/PBRT/PBRTSceneBuilder.js +1 -1
  37. package/src/Processor/PackedRayBuffer.js +2 -2
  38. package/src/Processor/PlatformImageLoader.js +9 -0
  39. package/src/Processor/StorageTexturePool.js +1 -1
  40. package/src/Processor/TextureCreator.js +44 -4
  41. package/src/Processor/ToneMapCPU.js +34 -5
  42. package/src/Processor/ToneMapGPU.js +15 -9
  43. package/src/Processor/USD/USDScene.js +981 -17
  44. package/src/RayzeeRenderer.js +82 -27
  45. package/src/Stages/AutoExposure.js +21 -8
  46. package/src/Stages/Compositor.js +39 -1
  47. package/src/Stages/DenoiserSettings.js +5 -81
  48. package/src/Stages/LocalExposure.js +44 -18
  49. package/src/Stages/MotionVector.js +21 -215
  50. package/src/Stages/NormalDepth.js +12 -8
  51. package/src/Stages/PathTracer.js +26 -2
  52. package/src/Stages/PathTracerStage.js +13 -18
  53. package/src/TSL/CameraRay.js +12 -1
  54. package/src/TSL/DebugKernel.js +3 -2
  55. package/src/TSL/FinalWriteKernel.js +6 -9
  56. package/src/TSL/GenerateKernel.js +6 -2
  57. package/src/TSL/LightSplatKernel.js +7 -4
  58. package/src/TSL/LightsCore.js +6 -2
  59. package/src/TSL/LightsSampling.js +45 -12
  60. package/src/TSL/SceneResources.js +11 -0
  61. package/src/TSL/ShadeKernel.js +4 -5
  62. package/src/index.js +1 -1
  63. package/src/integrators/BidirectionalIntegrator.js +1 -0
  64. package/src/managers/CameraManager.js +9 -0
  65. package/src/managers/DenoisingManager.js +79 -187
  66. package/src/managers/InteractionManager.js +6 -0
  67. package/src/managers/UniformManager.js +3 -1
  68. package/dist/SceneMetadata-D2Sh-0e-.js +0 -110
  69. package/src/Stages/ASVGF.js +0 -932
  70. package/src/Stages/BilateralFilter.js +0 -486
  71. package/src/Stages/EdgeFilter.js +0 -545
  72. package/src/Stages/Variance.js +0 -443
@@ -13,7 +13,7 @@ import {
13
13
  localId, workgroupId,
14
14
  } from 'three/tsl';
15
15
 
16
- import { generateRayFromCamera } from './CameraRay.js';
16
+ import { generateRayFromCamera, cameraClipDistance } from './CameraRay.js';
17
17
  import { Ray } from './Struct.js';
18
18
  import { TraceDebugMode } from './Debugger.js';
19
19
  import { pcgHash, getStratifiedSample } from './Random.js';
@@ -28,6 +28,7 @@ export function buildDebugKernel( params ) {
28
28
  cameraWorldMatrix, cameraProjectionMatrixInverse, cameraProjectionMatrix, cameraViewMatrix,
29
29
  cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
30
30
  enableDOF, focalLength, aperture, focusDistance, unitsPerMetre, apertureScale, anamorphicRatio, dofMode, dofBlur,
31
+ cameraClipStart,
31
32
  bvhBuffer, triangleBuffer, materialBuffer,
32
33
  envTexture, environmentMatrix, environmentIntensity, enableEnvironmentLight,
33
34
  visMode, debugVisScale,
@@ -66,7 +67,7 @@ export function buildDebugKernel( params ) {
66
67
 
67
68
  // Modes 1-8, 10 — shared per-mode debug color (primary-ray trace + counters).
68
69
  color.assign( TraceDebugMode(
69
- ray.origin, ray.direction,
70
+ ray.origin.add( ray.direction.mul( cameraClipDistance( ray.direction, cameraClipStart, cameraWorldMatrix, cameraProjection ) ) ), ray.direction,
70
71
  bvhBuffer, triangleBuffer, materialBuffer,
71
72
  envTexture, environmentMatrix, environmentIntensity, enableEnvironmentLight,
72
73
  visMode, debugVisScale,
@@ -39,7 +39,6 @@ export function buildFinalWriteKernel( params ) {
39
39
  enableAccumulation, hasPreviousAccumulated, accumulationAlpha, cameraIsMoving,
40
40
  // Aux MRT accumulates on its own epoch; hasPreviousAux=0 → write it verbatim instead of mixing.
41
41
  hasPreviousAux, auxAccumulationAlpha,
42
- transparentBackground,
43
42
  prevAccumTexture, prevAlbedoTexture, prevNormalDepthTexture,
44
43
  renderWidth,
45
44
  chunkRowBase, chunkRows, // row band offset (global first row) + row count for this chunk
@@ -48,7 +47,7 @@ export function buildFinalWriteKernel( params ) {
48
47
  // on): when off, skip the G-buffer decode, the prev-frame aux mix, and the two aux stores.
49
48
  auxGBufferEnabled, gBuffer = true, // compiled in only when requested (PathTracer.requestOutput( 'gBuffer' ))
50
49
  // Clean-aux normal (1 = temporally accumulate + renormalize the aux normal). On only for clean-aux
51
- // OIDN models (calb_cnrm/high, alb_nrm/balanced); off for fast/ASVGF which want the bump normal.
50
+ // OIDN models (calb_cnrm/high, alb_nrm/balanced); off otherwise, as the fast model wants the bump normal.
52
51
  cleanAuxNormalEnabled,
53
52
  // Tier-1 convergence early-stop: per-pixel Welford luminance second moment + converged bit (stamped
54
53
  // into streak bit 31; counted with 3×3 erosion by countConvergedDilated at the next frame start).
@@ -85,7 +84,9 @@ export function buildFinalWriteKernel( params ) {
85
84
 
86
85
  const sampleColor = readRayRadiance( rayBufferRO, rayID );
87
86
  const finalColor = sampleColor.xyz.toVar();
88
- const outputAlpha = select( transparentBackground, sampleColor.w, float( 1.0 ) ).toVar();
87
+ // Coverage, whatever the backdrop: 0 where the camera saw past everything opaque. What shows or reads the
88
+ // image makes it opaque unless the background is transparent; auto exposure leaves a solid colour out by it.
89
+ const outputAlpha = sampleColor.w.toVar();
89
90
 
90
91
  // A pixel frozen in a prior frame was skipped by Generate, so its rayBuffer sample is stale — pass
91
92
  // the accumulated colour through unchanged (below) instead of mixing it. Inert unless adaptiveOn.
@@ -126,7 +127,7 @@ export function buildFinalWriteKernel( params ) {
126
127
  finalHitDist.assign( mix( prevAlbedoSample.w, finalHitDist, auxAccumulationAlpha ) );
127
128
 
128
129
  // NORMAL: by default keep this frame's POINT-SAMPLED normal — it varies with the bump,
129
- // which fast/ASVGF want to preserve edge detail. But a CLEAN-AUX OIDN model (calb_cnrm/high,
130
+ // which the fast model wants to preserve edge detail. But a CLEAN-AUX OIDN model (calb_cnrm/high,
130
131
  // alb_nrm/balanced) trusts the aux and is fed per-frame point-sampled NOISE → leaked noise
131
132
  // (high) / over-smoothing (balanced). When cleanAuxNormalOn, temporally accumulate it like
132
133
  // the colour. Average the RAW unit normals (decode 0.5+0.5 → [-1,1]) and RENORMALIZE — a
@@ -145,11 +146,7 @@ export function buildFinalWriteKernel( params ) {
145
146
 
146
147
  } );
147
148
 
148
- If( transparentBackground, () => {
149
-
150
- outputAlpha.assign( select( wasFrozen, prevAccumSample.w, mix( prevAccumSample.w, sampleColor.w, accumulationAlpha ) ) );
151
-
152
- } );
149
+ outputAlpha.assign( select( wasFrozen, prevAccumSample.w, mix( prevAccumSample.w, sampleColor.w, accumulationAlpha ) ) );
153
150
 
154
151
  } );
155
152
 
@@ -22,7 +22,7 @@ import {
22
22
  getStratifiedSample,
23
23
  } from './Random.js';
24
24
 
25
- import { generateRayFromCamera } from './CameraRay.js';
25
+ import { generateRayFromCamera, cameraClipDistance } from './CameraRay.js';
26
26
  import { Ray } from './Struct.js';
27
27
  import { RAY_FLAG, COUNTER } from '../Processor/QueueManager.js';
28
28
  import {
@@ -41,6 +41,7 @@ export function buildGenerateKernel( params ) {
41
41
  cameraWorldMatrix, cameraProjectionMatrixInverse,
42
42
  cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
43
43
  enableDOF, focalLength, aperture, focusDistance, unitsPerMetre, apertureScale, anamorphicRatio, dofMode, dofBlur,
44
+ cameraClipStart,
44
45
  renderWidth,
45
46
  chunkRowBase, chunkRows, // row band offset (global first row) + row count for this chunk
46
47
  transmissiveBounces, // per-ray refraction budget (megakernel parity: PathTracerCore.js:606)
@@ -79,7 +80,10 @@ export function buildGenerateKernel( params ) {
79
80
  enableDOF, focalLength, aperture, focusDistance, unitsPerMetre, apertureScale, anamorphicRatio, dofMode, dofBlur,
80
81
  ) );
81
82
 
82
- writeRayOriginMeta( rayBufferRW, rayID, ray.origin, int( 0 ), int( 0 ) );
83
+ // The ray starts at the clip plane. The bidirectional weights still measure the first segment from the camera:
84
+ // the skipped length rides in the alpha-skip distance (mis_ray_t), as a cutout's would.
85
+ const clipAlong = cameraClipDistance( ray.direction, cameraClipStart, cameraWorldMatrix, cameraProjection ).toVar();
86
+ writeRayOriginMeta( rayBufferRW, rayID, ray.origin.add( ray.direction.mul( clipAlong ) ), int( 0 ), int( 0 ), uint( 0 ), bdpt ? clipAlong : null );
83
87
  // A fresh camera ray is NOT redirected — REDIRECTED stays clear so it sees the direct backdrop;
84
88
  // ShadeKernel sets REDIRECTED on the first direction-changing interaction (see RAY_FLAG.REDIRECTED).
85
89
  writeRayDirFlags( rayBufferRW, rayID, ray.direction, uint( RAY_FLAG.ACTIVE ) );
@@ -29,7 +29,7 @@ export function buildLightSplatKernel( params ) {
29
29
  hitBufferRO, splatBuffer,
30
30
  bvhBuffer, triangleBuffer, materialBuffer,
31
31
  lightPaths, slotsPerPath, lightTag, strategyView,
32
- cameraPosition, cameraForward, cameraViewMatrix, cameraProjectionMatrix, pixelArea,
32
+ cameraPosition, cameraForward, cameraClipStart, cameraViewMatrix, cameraProjectionMatrix, pixelArea,
33
33
  renderWidth, renderHeight,
34
34
  globalIlluminationIntensity, fireflyThreshold, accumFrame, frame,
35
35
  mergeVm = null,
@@ -68,7 +68,10 @@ export function buildLightSplatKernel( params ) {
68
68
  const culled = diffuseTransmission
69
69
  ? select( behind, v.extra.bitAnd( uint( 2 ) ), v.extra.bitAnd( uint( 1 ) ) ).notEqual( uint( 0 ) )
70
70
  : v.extra.notEqual( uint( 0 ) );
71
- If( ( diffuseTransmission ? cosFacet.equal( 0.0 ) : cosFacet.lessThanEqual( 0.0 ) ).or( cosCamera.lessThanEqual( 1e-6 ) ).or( culled ), () => {
71
+ // The camera sees nothing in front of its clip plane, this far back along the line to it.
72
+ const clipAlong = cameraClipStart.div( max( cosCamera, 1e-6 ) ).toVar();
73
+ If( ( diffuseTransmission ? cosFacet.equal( 0.0 ) : cosFacet.lessThanEqual( 0.0 ) ).or( cosCamera.lessThanEqual( 1e-6 ) ).or( culled )
74
+ .or( dist2.lessThanEqual( clipAlong.mul( clipAlong ) ) ), () => {
72
75
 
73
76
  Return();
74
77
 
@@ -123,9 +126,9 @@ export function buildLightSplatKernel( params ) {
123
126
  const origin = offsetRayOrigin( v.position, diffuseTransmission ? select( behind, v.facetN.negate(), v.facetN ) : v.facetN ).toVar();
124
127
  const segment = cameraPosition.sub( origin ).toVar();
125
128
  const segmentLength = length( segment ).toVar();
126
- // This segment stands in for the primary ray, which sees through the faces it culls.
129
+ // This segment stands in for the primary ray, which sees through the faces it culls and starts at the clip plane.
127
130
  const visibility = traceShadowRayRefractiveOpaque(
128
- origin, segment.div( segmentLength ), segmentLength.mul( SHADOW_END ),
131
+ origin, segment.div( segmentLength ), segmentLength.sub( clipAlong ).mul( SHADOW_END ),
129
132
  traverseBVHShadowCameraCulled, bvhBuffer, triangleBuffer, materialBuffer,
130
133
  );
131
134
 
@@ -60,6 +60,7 @@ export const PointLight = struct( {
60
60
  intensity: 'float',
61
61
  distance: 'float', // cutoff distance (0 = infinite)
62
62
  decay: 'float', // decay exponent (2 = physically correct)
63
+ radius: 'float', // 0 = a point; else a disc of this radius facing the lit point (Cycles' soft falloff)
63
64
  } );
64
65
 
65
66
  export const SpotLight = struct( {
@@ -75,6 +76,7 @@ export const SpotLight = struct( {
75
76
  goboIntensity: 'float', // mask strength multiplier (0 = no mask, 1 = full mask)
76
77
  iesIndex: 'int', // -1 = no IES, otherwise index into iesProfiles array
77
78
  iesIntensity: 'float', // blend [0,1] between flat (0) and full IES distribution (1)
79
+ radius: 'float', // as PointLight's
78
80
  } );
79
81
 
80
82
  export const LightSample = struct( {
@@ -173,7 +175,7 @@ export const getAreaLight = Fn( ( [ areaLightsBuffer, index ] ) => {
173
175
 
174
176
  export const getPointLight = Fn( ( [ pointLightsBuffer, index ] ) => {
175
177
 
176
- const baseIndex = index.mul( 9 );
178
+ const baseIndex = index.mul( 10 );
177
179
  return PointLight( {
178
180
  position: vec3(
179
181
  pointLightsBuffer.element( baseIndex ),
@@ -188,6 +190,7 @@ export const getPointLight = Fn( ( [ pointLightsBuffer, index ] ) => {
188
190
  intensity: pointLightsBuffer.element( baseIndex.add( 6 ) ),
189
191
  distance: pointLightsBuffer.element( baseIndex.add( 7 ) ),
190
192
  decay: pointLightsBuffer.element( baseIndex.add( 8 ) ),
193
+ radius: pointLightsBuffer.element( baseIndex.add( 9 ) ),
191
194
  } );
192
195
 
193
196
  } );
@@ -220,7 +223,8 @@ export const getSpotLight = Fn( ( [ spotLightsBuffer, index ] ) => {
220
223
  goboIntensity: spotLightsBuffer.element( baseIndex.add( 15 ) ),
221
224
  iesIndex: int( spotLightsBuffer.element( baseIndex.add( 16 ) ) ),
222
225
  iesIntensity: spotLightsBuffer.element( baseIndex.add( 17 ) ),
223
- // slots 18, 19 reserved (vec4 padding)
226
+ radius: spotLightsBuffer.element( baseIndex.add( 18 ) ),
227
+ // slot 19 reserved (vec4 padding)
224
228
  } );
225
229
 
226
230
  } );
@@ -86,7 +86,9 @@ import {
86
86
  offsetRayOrigin,
87
87
  SHADOW_END,
88
88
  REC709_LUMINANCE_COEFFICIENTS,
89
+ constructTBN,
89
90
  } from './Common.js';
91
+ import { usesLightRadius } from './SceneResources.js';
90
92
  import { sampleEnvironment, sampleEnvironmentExact } from './Environment.js';
91
93
  import { shadowTerminatorOrigin } from './ShadowTerminator.js';
92
94
  import { sunRadianceToward, sampleSunDisc } from './Sun.js';
@@ -233,8 +235,30 @@ export const sampleRectAreaLight = Fn( ( [ light, rayOrigin, ruv, lightSelection
233
235
 
234
236
  } );
235
237
 
236
- // Enhanced spot light sampling with radius support
237
- export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin, lightSelectionPdf ] ) => {
238
+ /**
239
+ * A point or spot light with a radius is a disc of it facing the lit point, as Cycles' with Soft Falloff: uniform on the
240
+ * disc, so on its axis the light gives intensity / ( d² + r² ) where a point gives intensity / d². Moves `toLight` and
241
+ * `distSq` to the sampled point and returns the disc's tilt toward it. No BSDF ray hits these lights, so no MIS.
242
+ */
243
+ const aimAtLightDisc = ( light, toLight, distSq, ruv ) => {
244
+
245
+ const facing = float( 1.0 ).toVar();
246
+ If( light.radius.greaterThan( 0.0 ), () => {
247
+
248
+ const axis = toLight.div( sqrt( distSq ) ).toVar();
249
+ const r = light.radius.mul( sqrt( ruv.x ) );
250
+ const phi = ruv.y.mul( 2 * Math.PI );
251
+ toLight.addAssign( constructTBN( { N: axis } ).mul( vec3( r.mul( cos( phi ) ), r.mul( sin( phi ) ), 0.0 ) ) );
252
+ distSq.assign( dot( toLight, toLight ) );
253
+ facing.assign( max( dot( toLight, axis ).div( sqrt( distSq ) ), 0.0 ) );
254
+
255
+ } );
256
+ return facing;
257
+
258
+ };
259
+
260
+ // Spot light sampling; a radius makes it a disc (aimAtLightDisc), its cone still read from the centre.
261
+ export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin, lightSelectionPdf, ruv ], builder ) => {
238
262
 
239
263
  const ls_valid = tslBool( false ).toVar();
240
264
  const ls_direction = vec3( 0.0, 1.0, 0.0 ).toVar();
@@ -250,11 +274,16 @@ export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin, lightSelectio
250
274
  // Guard against zero distance
251
275
  If( lightDistSq.greaterThanEqual( 1e-20 ), () => {
252
276
 
277
+ // Compiled in only while some light has a radius (SceneResources lightRadius); otherwise the exact point code.
278
+ const sized = usesLightRadius( builder );
279
+ const centerDir = sized ? toLight.div( sqrt( lightDistSq ) ).toVar() : null;
280
+ const facing = sized ? aimAtLightDisc( light, toLight, lightDistSq, ruv ) : null;
253
281
  const lightDist = sqrt( lightDistSq ).toVar();
254
282
  const lightDir = toLight.div( lightDist ).toVar();
283
+ const coneDir = sized ? centerDir : lightDir;
255
284
 
256
285
  // Check cone attenuation
257
- const spotCosAngle = dot( lightDir.negate(), light.direction ).toVar();
286
+ const spotCosAngle = dot( coneDir.negate(), light.direction ).toVar();
258
287
  const coneCosAngle = cos( light.angle ).toVar();
259
288
 
260
289
  ls_direction.assign( lightDir );
@@ -268,10 +297,11 @@ export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin, lightSelectio
268
297
  const distanceAttenuation = getDistanceAttenuation( { lightDistance: lightDist, cutoffDistance: light.distance, decayExponent: light.decay } );
269
298
 
270
299
  // Gobo projection mask + IES photometric profile — both 1.0 when not assigned.
271
- const goboMask = sampleSpotGoboMask( light, lightDir );
272
- const iesProfile = sampleIESProfile( light, lightDir );
300
+ const goboMask = sampleSpotGoboMask( light, coneDir );
301
+ const iesProfile = sampleIESProfile( light, coneDir );
273
302
 
274
- ls_emission.assign( light.color.mul( light.intensity ).mul( distanceAttenuation ).mul( coneAttenuation ).mul( goboMask ).mul( iesProfile ) );
303
+ const emission = light.color.mul( light.intensity ).mul( distanceAttenuation ).mul( coneAttenuation ).mul( goboMask ).mul( iesProfile );
304
+ ls_emission.assign( sized ? emission.mul( facing ) : emission );
275
305
 
276
306
  } );
277
307
 
@@ -290,7 +320,7 @@ export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin, lightSelectio
290
320
  } );
291
321
 
292
322
  // Enhanced point light sampling with distance attenuation
293
- export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSelectionPdf ] ) => {
323
+ export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSelectionPdf, ruv ], builder ) => {
294
324
 
295
325
  const ls_valid = tslBool( false ).toVar();
296
326
  const ls_direction = vec3( 0.0, 1.0, 0.0 ).toVar();
@@ -306,6 +336,8 @@ export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSe
306
336
  // Guard against zero distance
307
337
  If( lightDistSq.greaterThanEqual( 1e-20 ), () => {
308
338
 
339
+ const sized = usesLightRadius( builder );
340
+ const facing = sized ? aimAtLightDisc( light, toLight, lightDistSq, ruv ) : null;
309
341
  const lightDist = sqrt( lightDistSq ).toVar();
310
342
  const lightDir = toLight.div( lightDist );
311
343
 
@@ -315,7 +347,8 @@ export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSe
315
347
  ls_lightType.assign( int( LIGHT_TYPE_POINT ) );
316
348
  ls_direction.assign( lightDir );
317
349
  ls_distance.assign( lightDist );
318
- ls_emission.assign( light.color.mul( light.intensity ).mul( distanceAttenuation ) );
350
+ const emission = light.color.mul( light.intensity ).mul( distanceAttenuation );
351
+ ls_emission.assign( sized ? emission.mul( facing ) : emission );
319
352
  ls_pdf.assign( lightSelectionPdf );
320
353
  ls_valid.assign( tslBool( true ) );
321
354
 
@@ -614,7 +647,7 @@ export const sampleLightWithImportance = Fn( ( [
614
647
 
615
648
  If( light.intensity.greaterThan( 0.0 ), () => {
616
649
 
617
- const ptSample = LightSample.wrap( samplePointLightWithAttenuation( light, rayOrigin, lightSelectionPdf ) );
650
+ const ptSample = LightSample.wrap( samplePointLightWithAttenuation( light, rayOrigin, lightSelectionPdf, vec2( randomSeed.y, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 15 ) ), rngState, resolution, frame ) ) ) );
618
651
  r_valid.assign( ptSample.valid );
619
652
  r_direction.assign( ptSample.direction );
620
653
  r_emission.assign( ptSample.emission );
@@ -639,7 +672,7 @@ export const sampleLightWithImportance = Fn( ( [
639
672
 
640
673
  If( light.intensity.greaterThan( 0.0 ), () => {
641
674
 
642
- const spotSample = LightSample.wrap( sampleSpotLightWithRadius( light, rayOrigin, lightSelectionPdf ) );
675
+ const spotSample = LightSample.wrap( sampleSpotLightWithRadius( light, rayOrigin, lightSelectionPdf, vec2( randomSeed.y, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 15 ) ), rngState, resolution, frame ) ) ) );
643
676
  r_valid.assign( spotSample.valid );
644
677
  r_direction.assign( spotSample.direction );
645
678
  r_emission.assign( spotSample.emission );
@@ -702,7 +735,7 @@ export const sampleLightWithImportance = Fn( ( [
702
735
  If( selectedType.equal( int( 2 ) ).and( selectedIdx.greaterThanEqual( int( 0 ) ) ), () => {
703
736
 
704
737
  const light = PointLight.wrap( getPointLight( pointLightsBuffer, selectedIdx ) );
705
- const ptSample = LightSample.wrap( samplePointLightWithAttenuation( light, rayOrigin, pdf ) );
738
+ const ptSample = LightSample.wrap( samplePointLightWithAttenuation( light, rayOrigin, pdf, vec2( randomSeed.y, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 15 ) ), rngState, resolution, frame ) ) ) );
706
739
  r_valid.assign( ptSample.valid );
707
740
  r_direction.assign( ptSample.direction );
708
741
  r_emission.assign( ptSample.emission );
@@ -716,7 +749,7 @@ export const sampleLightWithImportance = Fn( ( [
716
749
  If( selectedType.equal( int( 3 ) ).and( selectedIdx.greaterThanEqual( int( 0 ) ) ), () => {
717
750
 
718
751
  const light = SpotLight.wrap( getSpotLight( spotLightsBuffer, selectedIdx ) );
719
- const spotSample = LightSample.wrap( sampleSpotLightWithRadius( light, rayOrigin, pdf ) );
752
+ const spotSample = LightSample.wrap( sampleSpotLightWithRadius( light, rayOrigin, pdf, vec2( randomSeed.y, getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 15 ) ), rngState, resolution, frame ) ) ) );
720
753
  r_valid.assign( spotSample.valid );
721
754
  r_direction.assign( spotSample.direction );
722
755
  r_emission.assign( spotSample.emission );
@@ -12,6 +12,7 @@
12
12
  * @property {?Node} iesProfiles - IES profile texture array, or null
13
13
  * @property {?Node} alphaShadows - int uniform, 1 = shadow rays test texture alpha; null = never
14
14
  * @property {?Object<string, boolean>} materialLayers - the MATERIAL_LAYERS some material uses; null = all of them
15
+ * @property {boolean} [lightRadius] - some point or spot light has a radius (path tracing only); false = all are points
15
16
  */
16
17
 
17
18
  import { context } from 'three/tsl';
@@ -49,3 +50,13 @@ export function materialLayers( builder ) {
49
50
  return builder.context[ KEY ]?.materialLayers ?? ALL_MATERIAL_LAYERS;
50
51
 
51
52
  }
53
+
54
+ /**
55
+ * Whether the kernel being built samples light radii. Off unless a point or spot light has a radius, and off for the
56
+ * bidirectional integrator, whose light paths leave a light's centre. Outside a scene kernel (a test) it is on.
57
+ */
58
+ export function usesLightRadius( builder ) {
59
+
60
+ return builder.context[ KEY ]?.lightRadius ?? true;
61
+
62
+ }
@@ -125,7 +125,7 @@ export function buildShadeKernel( params ) {
125
125
  emissiveBoost, enableEmissiveTriangleSampling,
126
126
  lightBVHNodeCount, reverseMapVec4Offset,
127
127
  maxRayCount,
128
- // Aux G-buffer (normal/depth/albedo + surface ID) feeds only the denoiser/OIDN MRT. Compiled in only when
128
+ // Aux G-buffer (normal/depth/albedo) feeds only the denoiser/OIDN MRT. Compiled in only when
129
129
  // requested (PathTracer.requestOutput( 'gBuffer' )); then a live uniform (1 = denoiser on) skips the writes
130
130
  // while nothing consumes them.
131
131
  auxGBufferEnabled, gBuffer = true,
@@ -289,7 +289,7 @@ export function buildShadeKernel( params ) {
289
289
  // This bounce's block of sampler dimensions — see the budget table in Random.js.
290
290
  const dimBase = int( currentBounce ).mul( int( SAMPLER_DIMS_PER_BOUNCE ) );
291
291
 
292
- // DDFA see-through aux tint carried across smooth glass/mirror; committed into the OIDN/ASVGF
292
+ // DDFA see-through aux tint carried across smooth glass/mirror; committed into the OIDN/NRD
293
293
  // albedo guide at the first diffuse-enough surface (or env). Mutated locally, persisted on every
294
294
  // deferring continue (a missed persist = stale tint). 1 = untinted (direct hit → today's albedo).
295
295
  const featCarry = aux ? readFeatureThroughput( rayBufferRW, rayID ).toVar() : null;
@@ -424,7 +424,7 @@ export function buildShadeKernel( params ) {
424
424
 
425
425
  // The catcher is a real ground surface for the denoiser — write the plane's normal/depth
426
426
  // + a neutral albedo (black albedo would break OIDN demodulation) and mark the pixel a
427
- // valid surface, so OIDN/ASVGF don't smear the caught shadow as a background miss.
427
+ // valid surface, so OIDN/NRD don't smear the caught shadow as a background miss.
428
428
  if ( aux ) If( aux, () => {
429
429
 
430
430
  const planeDepth = computeNDCDepth( { worldPos: planePoint, cameraProjectionMatrix, cameraViewMatrix } );
@@ -653,8 +653,7 @@ export function buildShadeKernel( params ) {
653
653
  // surface that escapes to env keeps alpha 1 (HAS_HIT_OPAQUE set), so glass-in-front-of-an-
654
654
  // object stays opaque while glass-in-front-of-sky exports see-through.
655
655
  // Ungated (was transparentBackground-only) so the alpha lane always carries coverage: it is the
656
- // only hit/miss signal valid at FinalWrite. The image is unchanged — FinalWrite forces alpha 1
657
- // when transparent-bg is off.
656
+ // only hit/miss signal valid at FinalWrite, which accumulates it as is.
658
657
  If( flags.bitAnd( uint( RAY_FLAG.HAS_HIT_OPAQUE ) ).equal( uint( 0 ) ), () => {
659
658
 
660
659
  currentRadiance.w.assign( 0.0 );
package/src/index.js CHANGED
@@ -63,7 +63,7 @@ export {
63
63
  } from './EngineDefaults.js';
64
64
  export { MAX_RESERVABLE_RENDER_SIZE } from './Processor/StorageTexturePool.js';
65
65
  export {
66
- DENOISER_DEFAULTS, ASVGF_QUALITY_PRESETS, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS, NRD_HIT_DIST_A,
66
+ DENOISER_DEFAULTS, DENOISER_STRATEGIES, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS, NRD_HIT_DIST_A,
67
67
  NRD_HIT_DIST_B,
68
68
  } from './Stages/DenoiserSettings.js';
69
69
  export { SKY_DEFAULTS, DEFAULT_SUN_PATH } from './managers/EnvironmentManager.js';
@@ -336,6 +336,7 @@ export class BidirectionalIntegrator {
336
336
  strategyView: bd.strategyView,
337
337
  cameraPosition: bd.cameraPosition,
338
338
  cameraForward: bd.cameraForward,
339
+ cameraClipStart: this.pt.cameraClipStart,
339
340
  cameraViewMatrix: this.pt.cameraViewMatrix,
340
341
  cameraProjectionMatrix: this.pt.cameraProjectionMatrix,
341
342
  pixelArea: bd.pixelArea,
@@ -93,6 +93,7 @@ export class CameraManager extends EventDispatcher {
93
93
 
94
94
  this.cameras = cameras;
95
95
  this._userCameraCounter = 0;
96
+ this.camera.userData.clipStart = 0;
96
97
  this.resetAutoFocus();
97
98
 
98
99
  }
@@ -120,6 +121,7 @@ export class CameraManager extends EventDispatcher {
120
121
  cam.userData.__rayzeeUserCamera = true;
121
122
  cam.userData.__rayzeeEffects = this._captureEffects();
122
123
  if ( this.controls ) cam.userData.__rayzeeOrbitTarget = this.controls.target.clone();
124
+ if ( src.userData.clipStart > 0 ) cam.userData.clipStart = src.userData.clipStart;
123
125
 
124
126
  this._userCameraCounter += 1;
125
127
  cam.name = name || `View Camera ${this._userCameraCounter}`;
@@ -310,6 +312,7 @@ export class CameraManager extends EventDispatcher {
310
312
  this.camera.fov = s.fov;
311
313
  this.camera.near = s.near;
312
314
  this.camera.far = s.far;
315
+ this.camera.userData.clipStart = 0;
313
316
  this.camera.updateProjectionMatrix();
314
317
  this.camera.updateMatrixWorld( true );
315
318
 
@@ -380,6 +383,8 @@ export class CameraManager extends EventDispatcher {
380
383
  if ( source.isPerspectiveCamera ) this.camera.fov = source.fov;
381
384
  this.camera.near = source.near;
382
385
  this.camera.far = source.far;
386
+ // Only an imported camera's clip start clips the render: see PathTracerStage._updateCameraUniforms.
387
+ this.camera.userData.clipStart = source.userData?.clipStart ?? 0;
383
388
  this.camera.updateProjectionMatrix();
384
389
  this.camera.updateMatrixWorld( true );
385
390
 
@@ -611,6 +616,7 @@ export class CameraManager extends EventDispatcher {
611
616
  fov: camera.fov,
612
617
  near: camera.near,
613
618
  far: camera.far,
619
+ clipStart: camera.userData.clipStart ?? 0,
614
620
  zoom: camera.zoom,
615
621
  orthographic: !! camera.orthographic,
616
622
  orthoHalfHeight: camera.orthoHalfHeight,
@@ -641,6 +647,7 @@ export class CameraManager extends EventDispatcher {
641
647
  aspect: cam.aspect ?? null,
642
648
  near: cam.near,
643
649
  far: cam.far,
650
+ clipStart: cam.userData.clipStart ?? 0,
644
651
  zoom: cam.zoom,
645
652
  position: cam.position.toArray(),
646
653
  quaternion: cam.quaternion.toArray(),
@@ -684,6 +691,7 @@ export class CameraManager extends EventDispatcher {
684
691
  cam.updateMatrixWorld( true );
685
692
  cam.userData.__rayzeeUserCamera = true;
686
693
  cam.userData.__rayzeeEffects = fromPortable( entry.effects );
694
+ if ( entry.clipStart > 0 ) cam.userData.clipStart = entry.clipStart;
687
695
  if ( entry.orbitTarget ) cam.userData.__rayzeeOrbitTarget = new Vector3().fromArray( entry.orbitTarget );
688
696
  indexMap.set( entry.index, this.cameras.length );
689
697
  this.cameras.push( cam );
@@ -730,6 +738,7 @@ export class CameraManager extends EventDispatcher {
730
738
  camera.fov = v.fov;
731
739
  camera.near = v.near;
732
740
  camera.far = v.far;
741
+ camera.userData.clipStart = v.clipStart ?? 0;
733
742
  this._setView( v.orthographic, v.orthoHalfHeight );
734
743
  camera.zoom = v.zoom ?? 1;
735
744
  camera.updateProjectionMatrix();