rayzee 4.8.11 → 4.8.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "4.8.11",
3
+ "version": "4.8.13",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -383,23 +383,53 @@ export class OIDNDenoiser extends EventDispatcher {
383
383
  // Ensure storage buffers are sized correctly (recreate on resolution change)
384
384
  this._ensureGPUInputBuffers( width, height );
385
385
 
386
- // Copy render target textures → GPU storage buffers in a single command submission.
387
- // copyTextureToBuffer requires COPY_SRC on the texture (Three.js render targets have it)
388
- // and COPY_DST on the buffer. bytesPerRow for rgba32float = width * 16.
386
+ // Copy render target textures → tightly packed GPU storage buffers for oidn-web.
387
+ // copyTextureToBuffer requires bytesPerRow to be a multiple of 256. When the tight
388
+ // row size (width * 16) isn't aligned, copy via a padded staging buffer per texture
389
+ // then strip padding row-by-row.
389
390
  const encoder = device.createCommandEncoder( { label: 'oidn-tex-to-buf' } );
390
- const bytesPerRow = width * 16; // rgba32float = 4 channels × 4 bytes
391
+ const tightRowBytes = width * 16; // rgba32float
392
+ const paddedRowBytes = Math.ceil( tightRowBytes / 256 ) * 256;
393
+ const needsPadStrip = paddedRowBytes !== tightRowBytes;
394
+ const stagingBufs = [];
391
395
 
392
- const copyTex = ( tex, buf ) => encoder.copyTextureToBuffer(
393
- { texture: tex, mipLevel: 0 },
394
- { buffer: buf, offset: 0, bytesPerRow, rowsPerImage: height },
395
- { width, height, depthOrArrayLayers: 1 }
396
- );
396
+ const copyTex = ( tex, tightBuf ) => {
397
+
398
+ if ( ! needsPadStrip ) {
399
+
400
+ encoder.copyTextureToBuffer(
401
+ { texture: tex, mipLevel: 0 },
402
+ { buffer: tightBuf, offset: 0, bytesPerRow: tightRowBytes, rowsPerImage: height },
403
+ { width, height, depthOrArrayLayers: 1 }
404
+ );
405
+
406
+ } else {
407
+
408
+ const padBuf = device.createBuffer( { size: paddedRowBytes * height, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC } );
409
+ stagingBufs.push( padBuf );
410
+
411
+ encoder.copyTextureToBuffer(
412
+ { texture: tex, mipLevel: 0 },
413
+ { buffer: padBuf, offset: 0, bytesPerRow: paddedRowBytes, rowsPerImage: height },
414
+ { width, height, depthOrArrayLayers: 1 }
415
+ );
416
+
417
+ for ( let row = 0; row < height; row ++ ) {
418
+
419
+ encoder.copyBufferToBuffer( padBuf, row * paddedRowBytes, tightBuf, row * tightRowBytes, tightRowBytes );
420
+
421
+ }
422
+
423
+ }
424
+
425
+ };
397
426
 
398
427
  copyTex( textures.color, this._gpuInputBuffers.color );
399
428
  copyTex( textures.albedo, this._gpuInputBuffers.albedo );
400
429
  copyTex( textures.normal, this._gpuInputBuffers.normal );
401
430
 
402
431
  device.queue.submit( [ encoder.finish() ] );
432
+ for ( const buf of stagingBufs ) buf.destroy();
403
433
 
404
434
  // Cache alpha channel from input color buffer when transparent background is enabled.
405
435
  // OIDN only processes RGB — the alpha channel is lost, so we read it before denoising.
@@ -440,7 +470,7 @@ export class OIDNDenoiser extends EventDispatcher {
440
470
  this._destroyGPUInputBuffers();
441
471
 
442
472
  const device = this.gpuDevice;
443
- const byteSize = width * height * 4 * 4; // rgba32float = 16 bytes/pixel
473
+ const byteSize = width * height * 16; // rgba32float, tightly packed for oidn-web
444
474
  const usage = GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC;
445
475
 
446
476
  this._gpuInputBuffers.color = device.createBuffer( { label: 'oidn-in-color', size: byteSize, usage } );
@@ -466,7 +496,7 @@ export class OIDNDenoiser extends EventDispatcher {
466
496
  */
467
497
  async _cacheInputAlpha( device, width, height ) {
468
498
 
469
- const byteSize = width * height * 4 * 4; // rgba32float
499
+ const byteSize = width * height * 16; // rgba32float, tightly packed
470
500
  const staging = device.createBuffer( {
471
501
  label: 'oidn-alpha-staging',
472
502
  size: byteSize,
@@ -565,9 +595,16 @@ export class OIDNDenoiser extends EventDispatcher {
565
595
 
566
596
  const device = this.gpuDevice;
567
597
  const fullWidth = outputData.width;
598
+ const fullHeight = outputData.height;
568
599
  const bytesPerPixel = 16; // rgba32float = 4 × float32
569
- const tileRowBytes = tile.width * bytesPerPixel;
570
- const tileByteSize = tile.width * tile.height * bytesPerPixel;
600
+
601
+ // Clamp tile to image bounds (edge tiles may extend past the image)
602
+ const clampedW = Math.min( tile.width, fullWidth - tile.x );
603
+ const clampedH = Math.min( tile.height, fullHeight - tile.y );
604
+ if ( clampedW <= 0 || clampedH <= 0 ) return;
605
+
606
+ const tileRowBytes = clampedW * bytesPerPixel;
607
+ const tileByteSize = clampedW * clampedH * bytesPerPixel;
571
608
 
572
609
  const staging = device.createBuffer( {
573
610
  size: tileByteSize,
@@ -577,7 +614,7 @@ export class OIDNDenoiser extends EventDispatcher {
577
614
  // Copy each tile row from its position in the full output buffer
578
615
  const enc = device.createCommandEncoder();
579
616
 
580
- for ( let row = 0; row < tile.height; row ++ ) {
617
+ for ( let row = 0; row < clampedH; row ++ ) {
581
618
 
582
619
  const srcOffset = ( ( tile.y + row ) * fullWidth + tile.x ) * bytesPerPixel;
583
620
  const dstOffset = row * tileRowBytes;
@@ -591,7 +628,7 @@ export class OIDNDenoiser extends EventDispatcher {
591
628
  staging.mapAsync( GPUMapMode.READ ).then( () => {
592
629
 
593
630
  const f32 = new Float32Array( staging.getMappedRange() );
594
- const tileImageData = new ImageData( tile.width, tile.height );
631
+ const tileImageData = new ImageData( clampedW, clampedH );
595
632
  const exposure = this.getExposure();
596
633
  const saturation = this.getSaturation();
597
634
  const tmFn = TONE_MAP_FNS.get( this.getToneMapping() ) || TONE_MAP_FNS.get( ACESFilmicToneMapping );
@@ -617,8 +654,8 @@ export class OIDNDenoiser extends EventDispatcher {
617
654
 
618
655
  if ( alpha ) {
619
656
 
620
- const px = ( i >> 2 ) % tile.width;
621
- const py = ( i >> 2 ) / tile.width | 0;
657
+ const px = ( i >> 2 ) % clampedW;
658
+ const py = ( i >> 2 ) / clampedW | 0;
622
659
  tileImageData.data[ i + 3 ] = alpha[ ( tile.y + py ) * alphaW + tile.x + px ];
623
660
 
624
661
  } else {
@@ -1214,7 +1214,7 @@ export class AssetLoader extends EventDispatcher {
1214
1214
 
1215
1215
  const light = new RectAreaLight(
1216
1216
  new Color( ...userData.color ),
1217
- userData.intensity,
1217
+ userData.intensity / Math.PI, // Adjust intensity for better visual results
1218
1218
  userData.width,
1219
1219
  userData.height
1220
1220
  );
@@ -391,11 +391,12 @@ export const traverseBVHShadow = Fn( ( [
391
391
  bvhBuffer,
392
392
  triangleBuffer,
393
393
  materialBuffer,
394
+ maxShadowDist,
394
395
  ] ) => {
395
396
 
396
397
  const closestHit = HitInfo( {
397
398
  didHit: false,
398
- dst: float( 1e20 ),
399
+ dst: maxShadowDist,
399
400
  hitPoint: vec3( 0.0 ),
400
401
  normal: vec3( 0.0 ),
401
402
  uv: vec2( 0.0 ),
@@ -79,6 +79,7 @@ export const traceShadowRay = Fn( ( [
79
79
 
80
80
  const transmittance = float( 1.0 ).toVar();
81
81
  const rayOrigin = origin.toVar();
82
+ const remainingDist = float( maxDist ).toVar();
82
83
 
83
84
  const MAX_SHADOW_TRANSMISSIONS = 8;
84
85
 
@@ -91,10 +92,11 @@ export const traceShadowRay = Fn( ( [
91
92
  bvhBuffer,
92
93
  triangleBuffer,
93
94
  materialBuffer,
95
+ remainingDist,
94
96
  ) );
95
97
 
96
- // No hit or hit beyond light distance
97
- If( shadowHit.didHit.not().or( shadowHit.dst.greaterThan( maxDist ) ), () => {
98
+ // No hit within remaining distance to light
99
+ If( shadowHit.didHit.not(), () => {
98
100
 
99
101
  Break();
100
102
 
@@ -139,8 +141,9 @@ export const traceShadowRay = Fn( ( [
139
141
 
140
142
  } );
141
143
 
142
- // Continue ray
144
+ // Continue ray past transmissive surface
143
145
  rayOrigin.assign( shadowHit.hitPoint.add( dir.mul( 0.001 ) ) );
146
+ remainingDist.subAssign( shadowHit.dst.add( 0.001 ) );
144
147
 
145
148
  } ).ElseIf( shadowMaterial.transparent, () => {
146
149
 
@@ -154,8 +157,9 @@ export const traceShadowRay = Fn( ( [
154
157
 
155
158
  } );
156
159
 
157
- // Continue ray
160
+ // Continue ray past transparent surface
158
161
  rayOrigin.assign( shadowHit.hitPoint.add( dir.mul( 0.001 ) ) );
162
+ remainingDist.subAssign( shadowHit.dst.add( 0.001 ) );
159
163
 
160
164
  } ).Else( () => {
161
165