rayzee 5.1.0 → 5.1.1

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": "5.1.0",
3
+ "version": "5.1.1",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -142,7 +142,6 @@ export class RenderStage {
142
142
 
143
143
  const renderMode = context.getState( 'renderMode' ) || 0;
144
144
  const tileRenderingComplete = context.getState( 'tileRenderingComplete' );
145
- const frame = context.getState( 'frame' ) || 0;
146
145
 
147
146
  switch ( this.executionMode ) {
148
147
 
@@ -1,4 +1,4 @@
1
- import { Box3, Vector3, RectAreaLight, Color, FloatType, LinearFilter, EquirectangularReflectionMapping, LinearMipmapLinearFilter,
1
+ import { Box3, Vector3, RectAreaLight, Color, FloatType, LinearFilter, EquirectangularReflectionMapping,
2
2
  TextureLoader, Mesh, MeshStandardMaterial, MeshPhysicalMaterial, CircleGeometry, Points, PointsMaterial, LoadingManager, EventDispatcher
3
3
  } from 'three';
4
4
  import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
@@ -171,8 +171,6 @@ export class AssetLoader extends EventDispatcher {
171
171
  }
172
172
 
173
173
  texture.generateMipmaps = true;
174
- // texture.minFilter = LinearMipmapLinearFilter;
175
- // texture.magFilter = LinearFilter;
176
174
 
177
175
  this.applyEnvironmentToScene( texture );
178
176
  this.dispatchEvent( { type: 'load', texture } );
@@ -353,7 +351,7 @@ export class AssetLoader extends EventDispatcher {
353
351
 
354
352
  }
355
353
 
356
- async findAndLoadModelFromZip( zip, filename ) {
354
+ async findAndLoadModelFromZip( zip ) {
357
355
 
358
356
  const mainModelFiles = [
359
357
  'scene.gltf', 'scene.glb', 'model.gltf', 'model.glb',
@@ -459,7 +457,6 @@ export class AssetLoader extends EventDispatcher {
459
457
  if ( extension === 'gltf' ) {
460
458
 
461
459
  const gltfContent = strFromU8( fileContent );
462
- const gltfJson = JSON.parse( gltfContent );
463
460
  const manager = new LoadingManager();
464
461
  const gltfDir = filePath.split( '/' ).slice( 0, - 1 ).join( '/' );
465
462
 
@@ -1,4 +1,4 @@
1
- import { DataUtils, HalfFloatType, FloatType } from 'three';
1
+ import { DataUtils, HalfFloatType, FloatType, SRGBColorSpace } from 'three';
2
2
 
3
3
  /**
4
4
  * Binary search to find the closest index
@@ -38,18 +38,35 @@ function colorToLuminance( r, g, b ) {
38
38
 
39
39
  }
40
40
 
41
+ /**
42
+ * sRGB to linear conversion (IEC 61966-2-1 transfer function)
43
+ */
44
+ function sRGBToLinear( c ) {
45
+
46
+ return c <= 0.04045 ? c / 12.92 : ( ( c + 0.055 ) / 1.055 ) ** 2.4;
47
+
48
+ }
49
+
41
50
  /**
42
51
  * Extract Float32 RGBA pixel data from an environment map.
43
- * Handles HalfFloat/integer type conversion and Y-flip.
52
+ * Handles HalfFloat/integer type conversion, canvas extraction for
53
+ * non-DataTexture images (JPG/PNG), sRGB-to-linear conversion, and Y-flip.
44
54
  * @returns {{ floatData: Float32Array, width: number, height: number }}
45
55
  */
46
56
  export function extractFloatData( envMap ) {
47
57
 
48
- const { width, height, data } = envMap.image;
58
+ const { width, height } = envMap.image;
59
+ let data = envMap.image.data;
60
+ let needsSRGBToLinear = false;
49
61
 
62
+ // No CPU-accessible data — extract from HTMLImageElement / ImageBitmap via canvas
50
63
  if ( ! data ) {
51
64
 
52
- throw new Error( 'EquirectHDRInfo: Environment map must have CPU-accessible image data. Render target textures are not supported.' );
65
+ const canvas = new OffscreenCanvas( width, height );
66
+ const ctx = canvas.getContext( '2d' );
67
+ ctx.drawImage( envMap.image, 0, 0, width, height );
68
+ data = ctx.getImageData( 0, 0, width, height ).data;
69
+ needsSRGBToLinear = true;
53
70
 
54
71
  }
55
72
 
@@ -72,7 +89,7 @@ export function extractFloatData( envMap ) {
72
89
 
73
90
  } else {
74
91
 
75
- // Integer types (Uint8, Int16, etc.)
92
+ // Integer types (Uint8, Uint8Clamped, Int16, etc.)
76
93
  let maxIntValue;
77
94
  if ( data instanceof Int8Array || data instanceof Int16Array || data instanceof Int32Array ) {
78
95
 
@@ -93,6 +110,26 @@ export function extractFloatData( envMap ) {
93
110
 
94
111
  }
95
112
 
113
+ // Also flag sRGB conversion for DataTextures explicitly marked as sRGB
114
+ if ( ! needsSRGBToLinear && envMap.colorSpace === SRGBColorSpace ) {
115
+
116
+ needsSRGBToLinear = true;
117
+
118
+ }
119
+
120
+ // Convert sRGB to linear so CDF luminance matches GPU-sampled linear values
121
+ if ( needsSRGBToLinear ) {
122
+
123
+ for ( let i = 0, l = floatData.length; i < l; i += 4 ) {
124
+
125
+ floatData[ i ] = sRGBToLinear( floatData[ i ] );
126
+ floatData[ i + 1 ] = sRGBToLinear( floatData[ i + 1 ] );
127
+ floatData[ i + 2 ] = sRGBToLinear( floatData[ i + 2 ] );
128
+
129
+ }
130
+
131
+ }
132
+
96
133
  // Remove Y-flip for CDF computation
97
134
  if ( envMap.flipY ) {
98
135
 
@@ -820,8 +820,8 @@ export class SceneProcessor {
820
820
 
821
821
  // Factory method for creating any additional scene elements
822
822
  // Currently returns an empty array by default
823
- const white = new Color( 0xffffff );
824
- const black = new Color( 0x000000 );
823
+ // const white = new Color( 0xffffff );
824
+ // const black = new Color( 0x000000 );
825
825
  return [
826
826
  // { position: new Vector3( - 4, 2, 0 ), radius: 0.8, material: { color: white, emissive: black, emissiveIntensity: 0, roughness: 1.0 } },
827
827
  // { position: new Vector3( - 1.5, 2, 0 ), radius: 0.8, material: { color: white, emissive: black, emissiveIntensity: 0, roughness: 1.0 } },
@@ -268,7 +268,7 @@ export const traverseBVH = Fn( ( [
268
268
  } );
269
269
 
270
270
  // If we found a very close hit, we can terminate early
271
- If( closestHit.didHit.and( closestHit.dst.lessThan( 0.001 ) ), () => {
271
+ If( closestHit.didHit.and( closestHit.dst.lessThan( 1e-6 ) ), () => {
272
272
 
273
273
  Break();
274
274
 
@@ -356,7 +356,8 @@ export const TraceDebugMode = Fn( ( [
356
356
  const bounceDir = cosineWeightedSample( { N: normalA, xi } ).toVar();
357
357
 
358
358
  // Trace secondary ray from the hit point (offset along normal to avoid self-intersection)
359
- const bounceOrigin = hitInfo.hitPoint.add( normalA.mul( 0.001 ) ).toVar();
359
+ const debugEps = max( float( 1e-4 ), length( hitInfo.hitPoint ).mul( 1e-6 ) );
360
+ const bounceOrigin = hitInfo.hitPoint.add( normalA.mul( debugEps ) ).toVar();
360
361
  const bounceRay = Ray( { origin: bounceOrigin, direction: bounceDir } );
361
362
 
362
363
  const bounceHit = HitInfo.wrap( traverseBVH(
@@ -257,7 +257,7 @@ export const intersectAreaLight = Fn( ( [ light, rayOrigin, rayDirection ] ) =>
257
257
  const t = dot( light.position.sub( rayOrigin ), normal ).mul( invDenom ).toVar();
258
258
 
259
259
  // Skip intersections behind the ray
260
- If( t.greaterThan( 0.001 ), () => {
260
+ If( t.greaterThan( 1e-5 ), () => {
261
261
 
262
262
  // Optimized rectangle test using vector rejection
263
263
  const hitPoint = rayOrigin.add( rayDirection.mul( t ) );
@@ -142,8 +142,9 @@ export const traceShadowRay = Fn( ( [
142
142
  } );
143
143
 
144
144
  // Continue ray past transmissive surface
145
- rayOrigin.assign( shadowHit.hitPoint.add( dir.mul( 0.001 ) ) );
146
- remainingDist.subAssign( shadowHit.dst.add( 0.001 ) );
145
+ const transEps = max( float( 1e-5 ), length( shadowHit.hitPoint ).mul( 1e-6 ) );
146
+ rayOrigin.assign( shadowHit.hitPoint.add( dir.mul( transEps ) ) );
147
+ remainingDist.subAssign( shadowHit.dst.add( transEps ) );
147
148
 
148
149
  } ).ElseIf( shadowMaterial.transparent, () => {
149
150
 
@@ -158,8 +159,9 @@ export const traceShadowRay = Fn( ( [
158
159
  } );
159
160
 
160
161
  // Continue ray past transparent surface
161
- rayOrigin.assign( shadowHit.hitPoint.add( dir.mul( 0.001 ) ) );
162
- remainingDist.subAssign( shadowHit.dst.add( 0.001 ) );
162
+ const alphaEps = max( float( 1e-5 ), length( shadowHit.hitPoint ).mul( 1e-6 ) );
163
+ rayOrigin.assign( shadowHit.hitPoint.add( dir.mul( alphaEps ) ) );
164
+ remainingDist.subAssign( shadowHit.dst.add( alphaEps ) );
163
165
 
164
166
  } ).Else( () => {
165
167
 
@@ -257,7 +259,7 @@ export const estimateLightImportance = Fn( ( [ light, hitPoint, normal, material
257
259
 
258
260
  If( lightFacing.greaterThan( 0.0 ), () => {
259
261
 
260
- const solidAngle = light.area.div( max( distSq, 0.1 ) );
262
+ const solidAngle = light.area.div( max( distSq, 1e-4 ) );
261
263
  const power = light.intensity.mul( dot( light.color, REC709_LUMINANCE_COEFFICIENTS ) ).mul( light.area );
262
264
 
263
265
  // Material-aware weighting
@@ -659,7 +661,7 @@ export const calculatePointLightContribution = Fn( ( [
659
661
  const rayOffset = calculateRayOffset( hitPoint, normal, material );
660
662
  const rayOrigin = hitPoint.add( rayOffset );
661
663
 
662
- const visibility = traceShadowRayFn( rayOrigin, lightDir, distance.sub( 0.001 ), rngState );
664
+ const visibility = traceShadowRayFn( rayOrigin, lightDir, distance.mul( 0.999 ), rngState );
663
665
 
664
666
  If( visibility.greaterThan( 0.0 ), () => {
665
667
 
@@ -711,7 +713,7 @@ export const calculateSpotLightContribution = Fn( ( [
711
713
  const rayOffset = calculateRayOffset( hitPoint, normal, material );
712
714
  const rayOrigin = hitPoint.add( rayOffset );
713
715
 
714
- const visibility = traceShadowRayFn( rayOrigin, lightDir, distance.sub( 0.001 ), rngState );
716
+ const visibility = traceShadowRayFn( rayOrigin, lightDir, distance.mul( 0.999 ), rngState );
715
717
 
716
718
  If( visibility.greaterThan( 0.0 ), () => {
717
719
 
@@ -71,6 +71,7 @@ import {
71
71
  calculatePointLightImportance,
72
72
  calculateSpotLightImportance,
73
73
  traceShadowRay,
74
+ calculateRayOffset,
74
75
  } from './LightsDirect.js';
75
76
 
76
77
  import { traverseBVHShadow } from './BVHTraversal.js';
@@ -940,7 +941,7 @@ export const calculateDirectLightingUnified = Fn( ( [
940
941
  ] ) => {
941
942
 
942
943
  const totalContribution = vec3( 0.0 ).toVar();
943
- const rayOrigin = hitPoint.add( hitNormal.mul( 0.001 ) ).toVar();
944
+ const rayOrigin = hitPoint.add( calculateRayOffset( hitPoint, hitNormal, material ) ).toVar();
944
945
 
945
946
  // Early exit for highly emissive surfaces
946
947
  If( material.emissiveIntensity.lessThanEqual( 10.0 ), () => {
@@ -1041,7 +1042,7 @@ export const calculateDirectLightingUnified = Fn( ( [
1041
1042
 
1042
1043
  If( NoL.greaterThan( 0.0 ).and( lightImportance.mul( NoL ).greaterThan( importanceThreshold ) ).and( isDirectionValid( { direction: lightSample.direction, surfaceNormal: hitNormal } ) ), () => {
1043
1044
 
1044
- const shadowDistance = min( lightSample.distance.sub( 0.001 ), float( 1000.0 ) ).toVar();
1045
+ const shadowDistance = min( lightSample.distance.mul( 0.999 ), float( 1000.0 ) ).toVar();
1045
1046
  const visibility = traceShadowRay(
1046
1047
  rayOrigin, lightSample.direction, shadowDistance, rngState,
1047
1048
  traverseBVHShadow,
@@ -880,7 +880,8 @@ export const Trace = Fn( ( [
880
880
  // For transmission: offset along the old ray direction to push through the surface
881
881
  const reflectOffsetDir = select( interaction.entering, N, N.negate() );
882
882
  const offsetDir = select( interaction.didReflect, reflectOffsetDir, rayDirection );
883
- rayOrigin.assign( hitInfo.hitPoint.add( offsetDir.mul( 0.001 ) ) );
883
+ const bounceEps = max( float( 1e-4 ), length( hitInfo.hitPoint ).mul( 1e-6 ) );
884
+ rayOrigin.assign( hitInfo.hitPoint.add( offsetDir.mul( bounceEps ) ) );
884
885
  rayDirection.assign( interaction.direction );
885
886
 
886
887
  stateIsPrimaryRay.assign( tslBool( false ) );
@@ -1054,7 +1055,7 @@ export const Trace = Fn( ( [
1054
1055
 
1055
1056
  const rayOffset = calculateRayOffset( hitInfo.hitPoint, N, material );
1056
1057
  const rayOrigin = hitInfo.hitPoint.add( rayOffset );
1057
- const shadowDist = emissiveSample.distance.sub( 0.001 );
1058
+ const shadowDist = emissiveSample.distance.mul( 0.999 );
1058
1059
  const visibility = traceShadowRayWrapped( rayOrigin, emissiveSample.direction, shadowDist, rngState );
1059
1060
 
1060
1061
  If( visibility.greaterThan( 0.0 ), () => {
@@ -1137,7 +1138,7 @@ export const Trace = Fn( ( [
1137
1138
  throughput.mulAssign( indirectResult.throughput );
1138
1139
 
1139
1140
  // Prepare for next bounce
1140
- rayOrigin.assign( hitInfo.hitPoint.add( N.mul( 0.001 ) ) );
1141
+ rayOrigin.assign( hitInfo.hitPoint.add( calculateRayOffset( hitInfo.hitPoint, N, material ) ) );
1141
1142
  rayDirection.assign( indirectResult.direction );
1142
1143
  prevBouncePdf.assign( indirectResult.combinedPdf );
1143
1144
 
@@ -312,7 +312,7 @@ export class EnvironmentManager {
312
312
  const startTime = performance.now();
313
313
  const textureForCDF = this.scene.environment;
314
314
 
315
- if ( ! textureForCDF.image || ! textureForCDF.image.data ) {
315
+ if ( ! textureForCDF.image ) {
316
316
 
317
317
  this._updateCDFStorageBuffers();
318
318
  this.uniforms.set( 'envTotalSum', 0.0 );