rayzee 6.2.0 → 6.3.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.
package/src/TSL/Common.js CHANGED
@@ -204,7 +204,7 @@ export const applySoftSuppressionRGB = wgslFn( `
204
204
  export const classifyMaterial = Fn( ( [ metalness, roughness, transmission, clearcoat, emissive ] ) => {
205
205
 
206
206
  const isMetallic = metalness.greaterThan( 0.7 ).toVar();
207
- const isRough = roughness.greaterThan( 0.8 ).toVar();
207
+ const isRough = roughness.greaterThan( 0.8 );
208
208
  const isSmooth = roughness.lessThan( 0.3 ).toVar();
209
209
  const isTransmissive = transmission.greaterThan( 0.5 ).toVar();
210
210
  const hasClearcoat = clearcoat.greaterThan( 0.5 ).toVar();
@@ -218,8 +218,7 @@ export const classifyMaterial = Fn( ( [ metalness, roughness, transmission, clea
218
218
  .add( float( 0.25 ).mul( float( isSmooth ) ) )
219
219
  .add( float( 0.45 ).mul( float( isTransmissive ) ) )
220
220
  .add( float( 0.35 ).mul( float( hasClearcoat ) ) )
221
- .add( float( 0.3 ).mul( float( isEmissive ) ) )
222
- .toVar();
221
+ .add( float( 0.3 ).mul( float( isEmissive ) ) );
223
222
 
224
223
  // Add material interaction complexity
225
224
  const interactionComplexity = float( 0.0 ).toVar();
@@ -255,7 +254,7 @@ export const selectOptimalMISStrategy = Fn( ( [ roughness, metalness, bounceInde
255
254
  const brdfWeight = float( 0.5 ).toVar();
256
255
  const lightWeight = float( 0.5 ).toVar();
257
256
  const useBRDFSampling = tslBool( true );
258
- const useLightSampling = throughputStrength.greaterThan( 0.01 ).toVar();
257
+ const useLightSampling = throughputStrength.greaterThan( 0.01 );
259
258
 
260
259
  If( roughness.lessThan( 0.1 ).and( metalness.greaterThan( 0.8 ) ), () => {
261
260
 
@@ -124,15 +124,15 @@ export const TraceDebugMode = Fn( ( [
124
124
  } ).toVar();
125
125
 
126
126
  const envLuminance = dot( envSample.xyz, REC709_LUMINANCE_COEFFICIENTS ).toVar();
127
- const rawLuminance = envLuminance.toVar();
127
+ const rawLuminance = envLuminance;
128
128
 
129
129
  // Adaptive scaling
130
130
  const adaptiveScale = max( debugVisScale.mul( 0.1 ), 0.001 );
131
- const scaledLuminance = envLuminance.div( adaptiveScale ).toVar();
131
+ const scaledLuminance = envLuminance.div( adaptiveScale );
132
132
 
133
133
  // Logarithmic scaling for better dynamic range
134
134
  const logLuminance = log( envLuminance.add( 1e-6 ) );
135
- const logScaled = logLuminance.add( 10.0 ).div( 10.0 ).toVar();
135
+ const logScaled = logLuminance.add( 10.0 ).div( 10.0 );
136
136
 
137
137
  // Choose scaling based on debugVisScale
138
138
  const finalValue = select( debugVisScale.greaterThan( 1.0 ), scaledLuminance, logScaled ).toVar();
@@ -327,7 +327,7 @@ export const TraceDebugMode = Fn( ( [
327
327
  material, hitInfo.uv, hitInfo.normal,
328
328
  ) ).toVar();
329
329
 
330
- const albedoA = matSamples.albedo.rgb.toVar();
330
+ const albedoA = matSamples.albedo.rgb;
331
331
  const normalA = normalize( matSamples.normal ).toVar();
332
332
 
333
333
  // Generate per-pixel per-frame random seed for stochastic bounce direction
@@ -339,11 +339,11 @@ export const TraceDebugMode = Fn( ( [
339
339
  // Cosine-weighted hemisphere sample around the surface normal
340
340
  const xi_r1 = RandomValue( rngState ).toVar();
341
341
  const xi_r2 = RandomValue( rngState ).toVar();
342
- const xi = vec2( xi_r1, xi_r2 ).toVar();
342
+ const xi = vec2( xi_r1, xi_r2 );
343
343
  const bounceDir = cosineWeightedSample( { N: normalA, xi } ).toVar();
344
344
 
345
345
  // Trace secondary ray from the hit point (offset along normal to avoid self-intersection)
346
- const bounceOrigin = hitInfo.hitPoint.add( normalA.mul( 0.001 ) ).toVar();
346
+ const bounceOrigin = hitInfo.hitPoint.add( normalA.mul( 0.001 ) );
347
347
  const bounceRay = Ray( { origin: bounceOrigin, direction: bounceDir } );
348
348
 
349
349
  const bounceHit = HitInfo.wrap( traverseBVH(
@@ -45,15 +45,15 @@ export const refineDisplacedIntersection = Fn( ( [
45
45
  const triIdx = hitInfo.triangleIndex;
46
46
 
47
47
  const pA = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 0 ), int( TRI_STRIDE ) ).xyz.toVar();
48
- const pB = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 1 ), int( TRI_STRIDE ) ).xyz.toVar();
49
- const pC = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 2 ), int( TRI_STRIDE ) ).xyz.toVar();
48
+ const pB = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 1 ), int( TRI_STRIDE ) ).xyz;
49
+ const pC = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 2 ), int( TRI_STRIDE ) ).xyz;
50
50
 
51
51
  const uvData1 = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 6 ), int( TRI_STRIDE ) ).toVar();
52
- const uvData2 = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 7 ), int( TRI_STRIDE ) ).toVar();
52
+ const uvData2 = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 7 ), int( TRI_STRIDE ) );
53
53
 
54
54
  const uvA = uvData1.xy.toVar();
55
- const uvB = uvData1.zw.toVar();
56
- const uvC = uvData2.xy.toVar();
55
+ const uvB = uvData1.zw;
56
+ const uvC = uvData2.xy;
57
57
 
58
58
  // Compute tangent vectors from triangle edges + UV differences
59
59
  const edge1 = pB.sub( pA ).toVar();
@@ -94,8 +94,8 @@ export const refineDisplacedIntersection = Fn( ( [
94
94
  const rayProjDotT = dot( rayProj, T );
95
95
  const rayProjDotB = dot( rayProj, B );
96
96
 
97
- const du_dt = BdotB.mul( rayProjDotT ).sub( TdotB.mul( rayProjDotB ) ).mul( invDetJ ).toVar();
98
- const dv_dt = TdotT.mul( rayProjDotB ).sub( TdotB.mul( rayProjDotT ) ).mul( invDetJ ).toVar();
97
+ const du_dt = BdotB.mul( rayProjDotT ).sub( TdotB.mul( rayProjDotB ) ).mul( invDetJ );
98
+ const dv_dt = TdotT.mul( rayProjDotB ).sub( TdotB.mul( rayProjDotT ) ).mul( invDetJ );
99
99
  const dUV_dt = vec2( du_dt, dv_dt ).toVar();
100
100
 
101
101
  // Ray height change per unit dt: how fast the ray moves along the surface normal
@@ -103,7 +103,7 @@ export const refineDisplacedIntersection = Fn( ( [
103
103
 
104
104
  // March range: displacement shell extends ±0.5*scale from base surface
105
105
  // Compute dt range to traverse the full shell
106
- const absNdotD = max( abs( dh_ray_dt ), 0.001 ).toVar();
106
+ const absNdotD = max( abs( dh_ray_dt ), 0.001 );
107
107
  const dtShell = scale.div( absNdotD ).toVar();
108
108
 
109
109
  // Adaptive step count: full steps on primary ray, half on deeper bounces
@@ -113,7 +113,7 @@ export const refineDisplacedIntersection = Fn( ( [
113
113
 
114
114
  // Start from above the shell, march through
115
115
  const dtStart = dtShell.negate().toVar();
116
- const dtEnd = dtShell.toVar();
116
+ const dtEnd = dtShell;
117
117
  const dtStep = dtEnd.sub( dtStart ).div( float( marchSteps ) ).toVar();
118
118
 
119
119
  // Track for binary refinement
@@ -129,10 +129,10 @@ export const refineDisplacedIntersection = Fn( ( [
129
129
  const dt = dtStart.add( dtStep.mul( float( i ) ) ).toVar();
130
130
 
131
131
  // UV at this point along the ray
132
- const marchUV = hitInfo.uv.add( dUV_dt.mul( dt ) ).toVar();
132
+ const marchUV = hitInfo.uv.add( dUV_dt.mul( dt ) );
133
133
 
134
134
  // Ray height above base surface at this dt
135
- const rayHeight = dt.mul( dh_ray_dt ).toVar();
135
+ const rayHeight = dt.mul( dh_ray_dt );
136
136
 
137
137
  // Displaced surface height
138
138
  const heightSample = sampleDisplacementMap(
@@ -165,7 +165,7 @@ export const refineDisplacedIntersection = Fn( ( [
165
165
  Loop( { start: int( 0 ), end: int( BINARY_STEPS ), type: 'int', condition: '<' }, () => {
166
166
 
167
167
  const midDt = loDt.add( hiDt ).mul( 0.5 ).toVar();
168
- const midUV = hitInfo.uv.add( dUV_dt.mul( midDt ) ).toVar();
168
+ const midUV = hitInfo.uv.add( dUV_dt.mul( midDt ) );
169
169
  const midRayHeight = midDt.mul( dh_ray_dt );
170
170
  const midSample = sampleDisplacementMap(
171
171
  displacementMaps, material.displacementMapIndex, midUV, material.displacementTransform,
@@ -187,7 +187,7 @@ export const refineDisplacedIntersection = Fn( ( [
187
187
  // Final intersection
188
188
  const finalDt = loDt.add( hiDt ).mul( 0.5 ).toVar();
189
189
  const finalUV = hitInfo.uv.add( dUV_dt.mul( finalDt ) ).toVar();
190
- const finalPoint = hitInfo.hitPoint.add( rayDir.mul( finalDt ) ).toVar();
190
+ const finalPoint = hitInfo.hitPoint.add( rayDir.mul( finalDt ) );
191
191
 
192
192
  const finalHeight = sampleDisplacementMap(
193
193
  displacementMaps, material.displacementMapIndex, finalUV, material.displacementTransform,
@@ -47,19 +47,19 @@ export const sampleEquirect = Fn( ( [ environment, direction, environmentMatrix,
47
47
 
48
48
  // sin(theta) matches the CDF's solid-angle weighting (lum * sinTheta)
49
49
  const sinTheta = sin( uv.y.mul( Math.PI ) ).toVar();
50
- const lum = dot( color, REC709_LUMINANCE_COEFFICIENTS ).toVar();
51
- const weightedLum = lum.mul( sinTheta ).toVar();
50
+ const lum = dot( color, REC709_LUMINANCE_COEFFICIENTS );
51
+ const weightedLum = lum.mul( sinTheta );
52
52
  // MIS Compensation: subtract delta to match the sharpened CDF
53
- const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) ).toVar();
54
- const pdf = compensatedWeight.div( envTotalSum ).toVar();
53
+ const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) );
54
+ const pdf = compensatedWeight.div( envTotalSum );
55
55
 
56
56
  // Inline equirectDirectionPdf using the uv + sinTheta already in scope —
57
57
  // the helper would otherwise re-derive uv via atan2+acos and recompute sin.
58
58
  const dirPdf = sinTheta.greaterThan( 0.0 ).select(
59
59
  float( 1.0 ).div( float( 2.0 * Math.PI * Math.PI ).mul( sinTheta ) ),
60
60
  float( 0.0 )
61
- ).toVar();
62
- const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf ).toVar();
61
+ );
62
+ const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf );
63
63
 
64
64
  result.assign( vec4( color, finalPdf ) );
65
65
 
@@ -127,18 +127,18 @@ export const sampleEquirectProbability = Fn( ( [
127
127
 
128
128
  // Calculate PDF — sin(theta) weighting + MIS Compensation (Karlík et al. 2019)
129
129
  const sinTheta = sin( uv.y.mul( Math.PI ) ).toVar();
130
- const lum = dot( color.div( environmentIntensity ), REC709_LUMINANCE_COEFFICIENTS ).toVar();
131
- const weightedLum = lum.mul( sinTheta ).toVar();
132
- const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) ).toVar();
133
- const pdf = compensatedWeight.div( envTotalSum ).toVar();
130
+ const lum = dot( color.div( environmentIntensity ), REC709_LUMINANCE_COEFFICIENTS );
131
+ const weightedLum = lum.mul( sinTheta );
132
+ const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) );
133
+ const pdf = compensatedWeight.div( envTotalSum );
134
134
 
135
135
  // Inline equirectDirectionPdf — uv + sinTheta are already in scope, so we
136
136
  // skip the helper's redundant uv-from-direction + sin recompute.
137
137
  const dirPdf = sinTheta.greaterThan( 0.0 ).select(
138
138
  float( 1.0 ).div( float( 2.0 * Math.PI * Math.PI ).mul( sinTheta ) ),
139
139
  float( 0.0 )
140
- ).toVar();
141
- const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf ).toVar();
140
+ );
141
+ const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf );
142
142
 
143
143
  return vec4( direction, finalPdf );
144
144
 
@@ -291,18 +291,17 @@ export const sampleSpotGoboMask = /*@__PURE__*/ Fn( ( [ light, lightDir ] ) => {
291
291
  vec3( 1.0, 0.0, 0.0 ),
292
292
  );
293
293
  const T = normalize( cross( up, forward ) ).toVar();
294
- const B = cross( forward, T ).toVar();
294
+ const B = cross( forward, T );
295
295
 
296
296
  // Project onto plane perpendicular to forward at distance 1
297
297
  const invCos = float( 1.0 ).div( cosAlpha ).toVar();
298
- const px = dot( toSurface, T ).mul( invCos ).toVar();
299
- const py = dot( toSurface, B ).mul( invCos ).toVar();
298
+ const px = dot( toSurface, T ).mul( invCos );
299
+ const py = dot( toSurface, B ).mul( invCos );
300
300
 
301
301
  // Cone edge → ±tan(angle); map to UV [0,1]
302
- const tanA = max( tan( light.angle ), float( 1e-4 ) ).toVar();
303
- const invTan = float( 0.5 ).div( tanA ).toVar();
304
- const u = clamp( px.mul( invTan ).add( 0.5 ), float( 0.0 ), float( 1.0 ) ).toVar();
305
- const v = clamp( py.mul( invTan ).add( 0.5 ), float( 0.0 ), float( 1.0 ) ).toVar();
302
+ const invTan = float( 0.5 ).div( max( tan( light.angle ), float( 1e-4 ) ) ).toVar();
303
+ const u = clamp( px.mul( invTan ).add( 0.5 ), float( 0.0 ), float( 1.0 ) );
304
+ const v = clamp( py.mul( invTan ).add( 0.5 ), float( 0.0 ), float( 1.0 ) );
306
305
 
307
306
  if ( _goboMapsTexNode ) {
308
307
 
@@ -354,15 +353,15 @@ export const sampleDirectionalGoboMask = /*@__PURE__*/ Fn( ( [ light, surfacePoi
354
353
  vec3( 1.0, 0.0, 0.0 ),
355
354
  );
356
355
  const T = normalize( cross( up, axis ) ).toVar();
357
- const B = cross( axis, T ).toVar();
356
+ const B = cross( axis, T );
358
357
 
359
358
  const invScale = float( 1.0 ).div( max( light.goboScale, float( 1e-4 ) ) ).toVar();
360
359
  const u = dot( surfacePoint, T ).mul( invScale ).add( 0.5 ).toVar();
361
360
  const v = dot( surfacePoint, B ).mul( invScale ).add( 0.5 ).toVar();
362
361
 
363
362
  // Tile by fract so a single mask can cover any scene size.
364
- const uTiled = u.sub( u.floor() ).toVar();
365
- const vTiled = v.sub( v.floor() ).toVar();
363
+ const uTiled = u.sub( u.floor() );
364
+ const vTiled = v.sub( v.floor() );
366
365
 
367
366
  if ( _goboMapsTexNode ) {
368
367
 
@@ -417,9 +416,9 @@ export const sampleIESProfile = /*@__PURE__*/ Fn( ( [ light, lightDir ] ) => {
417
416
 
418
417
  // Vertical angle: between forward axis and emission direction. 0 = on axis (V=0),
419
418
  // PI = anti-axis (V=1).
420
- const cosV = clamp( dot( toSurface, forward ), float( - 1.0 ), float( 1.0 ) ).toVar();
421
- const vAngle = acos( cosV ).toVar();
422
- const v = clamp( vAngle.div( float( Math.PI ) ), float( 0.0 ), float( 1.0 ) ).toVar();
419
+ const cosV = clamp( dot( toSurface, forward ), float( - 1.0 ), float( 1.0 ) );
420
+ const vAngle = acos( cosV );
421
+ const v = clamp( vAngle.div( float( Math.PI ) ), float( 0.0 ), float( 1.0 ) );
423
422
 
424
423
  // Horizontal angle: project emission direction onto plane perpendicular to forward.
425
424
  const up = select(
@@ -428,13 +427,13 @@ export const sampleIESProfile = /*@__PURE__*/ Fn( ( [ light, lightDir ] ) => {
428
427
  vec3( 1.0, 0.0, 0.0 ),
429
428
  );
430
429
  const T = normalize( cross( up, forward ) ).toVar();
431
- const B = cross( forward, T ).toVar();
430
+ const B = cross( forward, T );
432
431
 
433
432
  const px = dot( toSurface, T );
434
433
  const py = dot( toSurface, B );
435
434
  // atan2 → [-PI, PI]; remap to [0, 2PI] then to [0, 1].
436
435
  const phi = atan( py, px );
437
- const u = phi.div( float( 2.0 * Math.PI ) ).add( 0.5 ).toVar();
436
+ const u = phi.div( float( 2.0 * Math.PI ) ).add( 0.5 );
438
437
 
439
438
  if ( _iesProfilesTexNode ) {
440
439
 
@@ -268,11 +268,8 @@ export const traceShadowRay = Fn( ( [
268
268
 
269
269
  export const calculateRayOffset = Fn( ( [ hitPoint, normal, material ] ) => {
270
270
 
271
- // Base epsilon scaled by scene size
272
- const scaleEpsilon = max( float( 1e-4 ), length( hitPoint ).mul( 1e-6 ) ).toVar();
273
-
274
- // Adjust for material properties
275
- const materialEpsilon = scaleEpsilon.toVar();
271
+ // Base epsilon scaled by scene size; adjusted by material properties below.
272
+ const materialEpsilon = max( float( 1e-4 ), length( hitPoint ).mul( 1e-6 ) ).toVar();
276
273
 
277
274
  If( material.transmission.greaterThan( 0.0 ), () => {
278
275
 
@@ -71,17 +71,17 @@ export const calculateTransmissionPDF = Fn( ( [ V, L, N, ior, roughness, enterin
71
71
 
72
72
  } );
73
73
 
74
- const VoH = abs( dot( V, H ) ).toVar();
74
+ const VoH = abs( dot( V, H ) );
75
75
  const LoH = abs( dot( L, H ) ).toVar();
76
76
  const NoH = abs( dot( N, H ) ).toVar();
77
77
 
78
78
  // GGX distribution
79
- const D = DistributionGGX( NoH, roughness ).toVar();
79
+ const D = DistributionGGX( NoH, roughness );
80
80
 
81
81
  // Jacobian for transmission
82
82
  const denom_inner = VoH.add( LoH.mul( eta ) ).toVar();
83
- const denom = denom_inner.mul( denom_inner ).toVar();
84
- const jacobian = LoH.mul( eta ).mul( eta ).div( max( denom, EPSILON ) ).toVar();
83
+ const denom = denom_inner.mul( denom_inner );
84
+ const jacobian = LoH.mul( eta ).mul( eta ).div( max( denom, EPSILON ) );
85
85
 
86
86
  return D.mul( NoH ).mul( jacobian );
87
87
 
@@ -92,10 +92,10 @@ export const calculateClearcoatPDF = Fn( ( [ V, L, N, clearcoatRoughness ] ) =>
92
92
 
93
93
  const H_raw = V.add( L ).toVar();
94
94
  const lenSq = dot( H_raw, H_raw ).toVar();
95
- const H = select( lenSq.greaterThan( EPSILON ), H_raw.div( sqrt( lenSq ) ), N ).toVar();
95
+ const H = select( lenSq.greaterThan( EPSILON ), H_raw.div( sqrt( lenSq ) ), N );
96
96
 
97
- const NoH = max( dot( N, H ), 0.0 ).toVar();
98
- const NoV = max( dot( N, V ), 0.0 ).toVar();
97
+ const NoH = max( dot( N, H ), 0.0 );
98
+ const NoV = max( dot( N, V ), 0.0 );
99
99
 
100
100
  return calculateVNDFPDF( NoH, NoV, clearcoatRoughness );
101
101
 
@@ -259,15 +259,14 @@ export const calculateIndirectLighting = Fn( ( [
259
259
  const validInput = samplingInfo.diffuseImportance.greaterThanEqual( 0.0 )
260
260
  .and( samplingInfo.specularImportance.greaterThanEqual( 0.0 ) )
261
261
  .and( samplingInfo.transmissionImportance.greaterThanEqual( 0.0 ) )
262
- .and( samplingInfo.clearcoatImportance.greaterThanEqual( 0.0 ) )
263
- .toVar();
262
+ .and( samplingInfo.clearcoatImportance.greaterThanEqual( 0.0 ) );
264
263
 
265
264
  If( validInput.not(), () => {
266
265
 
267
266
  // Fallback to diffuse sampling
268
267
  const r1_fb = RandomValue( rngState ).toVar();
269
268
  const r2_fb = RandomValue( rngState ).toVar();
270
- const sampleRand = vec2( r1_fb, r2_fb ).toVar();
269
+ const sampleRand = vec2( r1_fb, r2_fb );
271
270
  r_direction.assign( cosineWeightedSample( N, sampleRand ) );
272
271
  r_throughput.assign( material.color.xyz );
273
272
  r_misWeight.assign( 1.0 );
@@ -312,7 +311,7 @@ export const calculateIndirectLighting = Fn( ( [
312
311
  } ).ElseIf( selectedStrategy.equal( int( 3 ) ), () => {
313
312
 
314
313
  // Strategy 3: Transmission
315
- const entering = dot( V, N ).greaterThan( 0.0 ).toVar();
314
+ const entering = dot( V, N ).greaterThan( 0.0 );
316
315
  // pathWavelength=0 — MIS evaluation reads only direction/PDF, no spectral tint
317
316
  const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission(
318
317
  V, N, material.ior, material.roughness, entering, material.dispersion, sampleRand, rngState, float( 0.0 )
@@ -333,7 +332,7 @@ export const calculateIndirectLighting = Fn( ( [
333
332
  // For transmission directions (below surface), use |cos| instead of max(cos, 0)
334
333
  const rawNoL = dot( N, sampleDir ).toVar();
335
334
  const NoL = max( rawNoL, 0.0 ).toVar();
336
- const absNoL = abs( rawNoL ).toVar();
335
+ const absNoL = abs( rawNoL );
337
336
 
338
337
  // Calculate combined PDF for MIS (material strategies only)
339
338
  const combinedPdf = float( 0.0 ).toVar();
@@ -351,25 +350,26 @@ export const calculateIndirectLighting = Fn( ( [
351
350
 
352
351
  If( weights.useDiffuse, () => {
353
352
 
354
- const diffusePdf = cosineWeightedPDF( NoL ).toVar();
355
- combinedPdf.addAssign( weights.diffuseWeight.mul( diffusePdf ) );
353
+ combinedPdf.addAssign( weights.diffuseWeight.mul( cosineWeightedPDF( NoL ) ) );
356
354
 
357
355
  } );
358
356
 
359
357
  If( weights.useTransmission.and( material.transmission.greaterThan( 0.0 ) ), () => {
360
358
 
361
359
  // Calculate transmission PDF for this direction
362
- const entering = dot( V, N ).greaterThan( 0.0 ).toVar();
363
- const transmissionPdf = calculateTransmissionPDF( V, sampleDir, N, material.ior, material.roughness, entering ).toVar();
364
- combinedPdf.addAssign( weights.transmissionWeight.mul( transmissionPdf ) );
360
+ const entering = dot( V, N ).greaterThan( 0.0 );
361
+ combinedPdf.addAssign( weights.transmissionWeight.mul(
362
+ calculateTransmissionPDF( V, sampleDir, N, material.ior, material.roughness, entering )
363
+ ) );
365
364
 
366
365
  } );
367
366
 
368
367
  If( weights.useClearcoat.and( material.clearcoat.greaterThan( 0.0 ) ), () => {
369
368
 
370
369
  // Calculate clearcoat PDF for this direction
371
- const clearcoatPdf = calculateClearcoatPDF( V, sampleDir, N, material.clearcoatRoughness ).toVar();
372
- combinedPdf.addAssign( weights.clearcoatWeight.mul( clearcoatPdf ) );
370
+ combinedPdf.addAssign( weights.clearcoatWeight.mul(
371
+ calculateClearcoatPDF( V, sampleDir, N, material.clearcoatRoughness )
372
+ ) );
373
373
 
374
374
  } );
375
375
 
@@ -382,10 +382,9 @@ export const calculateIndirectLighting = Fn( ( [
382
382
 
383
383
  // Throughput calculation: use |cos| for transmission, max(cos,0) for reflection strategies
384
384
  const cosineWeight = select( selectedStrategy.equal( int( 3 ) ), absNoL, NoL );
385
- const throughput = sampleBrdfValue.mul( cosineWeight ).mul( misWeight ).div( samplePdf ).toVar();
386
385
 
387
386
  r_direction.assign( sampleDir );
388
- r_throughput.assign( throughput );
387
+ r_throughput.assign( sampleBrdfValue.mul( cosineWeight ).mul( misWeight ).div( samplePdf ) );
389
388
  r_misWeight.assign( misWeight );
390
389
  r_pdf.assign( samplePdf );
391
390
  r_combinedPdf.assign( combinedPdf );
@@ -107,8 +107,7 @@ export const sampleRectAreaLight = Fn( ( [ light, rayOrigin, ruv, lightSelection
107
107
  // Sample random position on rectangle (u/v are half-vectors, so map [0,1] → [-1,1])
108
108
  const randomPos = light.position
109
109
  .add( light.u.mul( ruv.x.mul( 2.0 ).sub( 1.0 ) ) )
110
- .add( light.v.mul( ruv.y.mul( 2.0 ).sub( 1.0 ) ) )
111
- .toVar();
110
+ .add( light.v.mul( ruv.y.mul( 2.0 ).sub( 1.0 ) ) );
112
111
 
113
112
  const toLight = randomPos.sub( rayOrigin ).toVar();
114
113
  const lightDistSq = dot( toLight, toLight ).toVar();
@@ -222,7 +221,7 @@ export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSe
222
221
  If( lightDistSq.greaterThanEqual( 1e-20 ), () => {
223
222
 
224
223
  const lightDist = sqrt( lightDistSq ).toVar();
225
- const lightDir = toLight.div( lightDist ).toVar();
224
+ const lightDir = toLight.div( lightDist );
226
225
 
227
226
  // Calculate distance attenuation using the light's actual distance and decay properties
228
227
  const distanceAttenuation = getDistanceAttenuation( { lightDistance: lightDist, cutoffDistance: light.distance, decayExponent: light.decay } );
@@ -400,7 +399,7 @@ export const sampleLightWithImportance = Fn( ( [
400
399
 
401
400
  If( totalWeight.lessThanEqual( 0.0 ), () => {
402
401
 
403
- const lightSelection = randomSeed.x.mul( float( totalLights ) ).toVar();
402
+ const lightSelection = randomSeed.x.mul( float( totalLights ) );
404
403
  const selectedLight = int( lightSelection ).toVar();
405
404
  // Guard division by zero
406
405
  const lightSelectionPdf = float( 1.0 ).div( max( float( totalLights ), 1.0 ) ).toVar();
@@ -544,7 +543,7 @@ export const sampleLightWithImportance = Fn( ( [
544
543
  w.mul( cosTheta ).add( u.mul( cos( phi ) ).add( v.mul( sin( phi ) ) ).mul( sinTheta ) )
545
544
  ) );
546
545
  // Guard division: (1.0 - cosHalfAngle) could be zero if angle is 0
547
- const solidAngle = float( TWO_PI ).mul( max( float( 1.0 ).sub( cosHalfAngle ), 1e-10 ) ).toVar();
546
+ const solidAngle = float( TWO_PI ).mul( max( float( 1.0 ).sub( cosHalfAngle ), 1e-10 ) );
548
547
  dirPdf.assign( float( 1.0 ).div( solidAngle ) );
549
548
 
550
549
  } );
@@ -626,9 +625,9 @@ export const sampleLightWithImportance = Fn( ( [
626
625
  // to avoid recomputing H + dots.
627
626
  export const calculateMaterialPDFFromDots = Fn( ( [ material, dots ] ) => {
628
627
 
629
- const NoV = dots.NoV.toVar();
628
+ const NoV = dots.NoV;
630
629
  const NoL = dots.NoL.toVar();
631
- const NoH = dots.NoH.toVar();
630
+ const NoH = dots.NoH;
632
631
 
633
632
  // Calculate lobe weights
634
633
  const diffuseWeight = float( 1.0 ).sub( material.metalness ).mul(
@@ -660,7 +659,7 @@ export const calculateMaterialPDFFromDots = Fn( ( [ material, dots ] ) => {
660
659
  // Specular PDF (VNDF sampling used in path tracer)
661
660
  If( specularWeight.greaterThan( 0.0 ).and( NoL.greaterThan( 0.0 ) ), () => {
662
661
 
663
- const roughness = max( material.roughness, 0.02 ).toVar();
662
+ const roughness = max( material.roughness, 0.02 );
664
663
  pdf.addAssign( specularWeight.mul( calculateVNDFPDF( NoH, NoV, roughness ) ) );
665
664
 
666
665
  } );
@@ -774,10 +773,10 @@ export const calculateDirectLightingUnified = Fn( ( [
774
773
  const sampleBRDF = tslBool( false ).toVar();
775
774
 
776
775
  // Calculate effective weights for probability (only include light weight if lights exist)
777
- const effectiveLightWeight = select( hasDiscreteLights, lightWeight, float( 0.0 ) ).toVar();
776
+ const effectiveLightWeight = select( hasDiscreteLights, lightWeight, float( 0.0 ) );
778
777
  // Guard division
779
- const invTotalSamplingWeight = float( 1.0 ).div( max( totalSamplingWeight, 1e-10 ) ).toVar();
780
- const cumulativeLight = effectiveLightWeight.mul( invTotalSamplingWeight ).toVar();
778
+ const invTotalSamplingWeight = float( 1.0 ).div( max( totalSamplingWeight, 1e-10 ) );
779
+ const cumulativeLight = effectiveLightWeight.mul( invTotalSamplingWeight );
781
780
 
782
781
  If( rand.lessThan( cumulativeLight ).and( useLightSampling ).and( hasDiscreteLights ), () => {
783
782
 
@@ -813,11 +812,11 @@ export const calculateDirectLightingUnified = Fn( ( [
813
812
  If( lightSample.valid.and( lightSample.pdf.greaterThan( 0.0 ) ), () => {
814
813
 
815
814
  const NoL = max( float( 0.0 ), dot( hitNormal, lightSample.direction ) ).toVar();
816
- const lightImportance = lightSample.emission.x.add( lightSample.emission.y ).add( lightSample.emission.z ).toVar();
815
+ const lightImportance = lightSample.emission.x.add( lightSample.emission.y ).add( lightSample.emission.z );
817
816
 
818
817
  If( NoL.greaterThan( 0.0 ).and( lightImportance.mul( NoL ).greaterThan( importanceThreshold ) ).and( isDirectionValid( { direction: lightSample.direction, surfaceNormal: hitNormal } ) ), () => {
819
818
 
820
- const shadowDistance = min( lightSample.distance.sub( 0.001 ), float( 1000.0 ) ).toVar();
819
+ const shadowDistance = min( lightSample.distance.sub( 0.001 ), float( 1000.0 ) );
821
820
  const visibility = shadow( rayOrigin, lightSample.direction, shadowDistance );
822
821
 
823
822
  If( visibility.greaterThan( 0.0 ), () => {
@@ -832,8 +831,8 @@ export const calculateDirectLightingUnified = Fn( ( [
832
831
 
833
832
  If( bPdf.greaterThan( 0.0 ).and( useBRDFSampling ), () => {
834
833
 
835
- const lightPdfWeighted = lightSample.pdf.mul( lightWeight ).toVar();
836
- const brdfPdfWeighted = bPdf.mul( brdfWeight ).toVar();
834
+ const lightPdfWeighted = lightSample.pdf.mul( lightWeight );
835
+ const brdfPdfWeighted = bPdf.mul( brdfWeight );
837
836
 
838
837
  // Apply power heuristic only for area lights — the BRDF path can
839
838
  // intersect area lights, so both strategies contribute and MIS is valid.
@@ -916,7 +915,7 @@ export const calculateDirectLightingUnified = Fn( ( [
916
915
 
917
916
  If( hitDistance.greaterThan( 0.0 ), () => {
918
917
 
919
- const shadowDistance = min( hitDistance.sub( 0.001 ), float( 1000.0 ) ).toVar();
918
+ const shadowDistance = min( hitDistance.sub( 0.001 ), float( 1000.0 ) );
920
919
  const visibility = shadow( rayOrigin, brdfSampleDirection, shadowDistance );
921
920
 
922
921
  If( visibility.greaterThan( 0.0 ), () => {
@@ -925,16 +924,16 @@ export const calculateDirectLightingUnified = Fn( ( [
925
924
 
926
925
  If( lightFacing.greaterThan( 0.0 ), () => {
927
926
 
928
- const lightDistSq = hitDistance.mul( hitDistance ).toVar();
927
+ const lightDistSq = hitDistance.mul( hitDistance );
929
928
  // Guard division
930
929
  const lightPdf = lightDistSq.div( max( light.area.mul( lightFacing ), EPSILON ) ).toVar();
931
930
  lightPdf.divAssign( max( float( totalLights ), 1.0 ) );
932
931
 
933
- const brdfPdfWeighted = brdfSamplePdf.mul( brdfWeight ).toVar();
934
- const lightPdfWeighted = lightPdf.mul( lightWeight ).toVar();
932
+ const brdfPdfWeighted = brdfSamplePdf.mul( brdfWeight );
933
+ const lightPdfWeighted = lightPdf.mul( lightWeight );
935
934
  const misW = powerHeuristic( { pdf1: brdfPdfWeighted, pdf2: lightPdfWeighted } ).toVar();
936
935
 
937
- const lightEmission = light.color.mul( light.intensity ).toVar();
936
+ const lightEmission = light.color.mul( light.intensity );
938
937
  // Guard division
939
938
  const brdfContribution = lightEmission.mul( brdfSampleValue ).mul( NoL ).mul( visibility ).mul( misW ).div( max( brdfSamplePdf, 1e-10 ) );
940
939
  totalContribution.addAssign( brdfContribution.mul( totalSamplingWeight ).div( max( brdfWeight, 1e-10 ) ) );
@@ -77,7 +77,7 @@ export const evaluateMaterialResponseFromDots = Fn( ( [ material, dots ] ) => {
77
77
  // Precalculate shared terms
78
78
  const D = DistributionGGX( dots.NoH, material.roughness );
79
79
  const G = GeometrySmith( dots.NoV, dots.NoL, material.roughness );
80
- const F = fresnelSchlick( dots.VoH, F0 ).toVar();
80
+ const F = fresnelSchlick( dots.VoH, F0 );
81
81
 
82
82
  // Single-scatter specular BRDF
83
83
  const specularSS = D.mul( G ).mul( F ).div( max( float( 4.0 ).mul( dots.NoV ).mul( dots.NoL ), EPSILON ) );
@@ -145,7 +145,7 @@ export const evaluateLayeredBRDF = Fn( ( [ dots, material ] ) => {
145
145
 
146
146
  const D = DistributionGGX( dots.NoH, material.roughness );
147
147
  const G = GeometrySmith( dots.NoV, dots.NoL, material.roughness );
148
- const F = fresnelSchlick( dots.VoH, F0 ).toVar();
148
+ const F = fresnelSchlick( dots.VoH, F0 );
149
149
  const baseBRDFSS = D.mul( G ).mul( F ).div( max( float( 4.0 ).mul( dots.NoV ).mul( dots.NoL ), EPSILON ) );
150
150
 
151
151
  // Shared DFG evaluation — compensation factor and total directional albedo
@@ -156,7 +156,7 @@ export const evalIridescence = Fn( ( [ outsideIOR, eta2, cosTheta1, thinFilmThic
156
156
  const iridescenceIor = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) ).toVar();
157
157
 
158
158
  // Evaluate the cosTheta on the base layer (Snell law)
159
- const sinTheta2Sq = square( { x: outsideIOR.div( iridescenceIor ) } ).mul( float( 1.0 ).sub( square( { x: cosTheta1 } ) ) ).toVar();
159
+ const sinTheta2Sq = square( { x: outsideIOR.div( iridescenceIor ) } ).mul( float( 1.0 ).sub( square( { x: cosTheta1 } ) ) );
160
160
 
161
161
  // Handle TIR
162
162
  const cosTheta2Sq = float( 1.0 ).sub( sinTheta2Sq ).toVar();
@@ -171,11 +171,11 @@ export const evalIridescence = Fn( ( [ outsideIOR, eta2, cosTheta1, thinFilmThic
171
171
  const cosTheta2 = sqrt( cosTheta2Sq ).toVar();
172
172
 
173
173
  // First interface
174
- const R0 = iorToFresnel0( iridescenceIor, outsideIOR ).toVar();
174
+ const R0 = iorToFresnel0( iridescenceIor, outsideIOR );
175
175
  const R12 = fresnelSchlickFloat( cosTheta1, R0 ).toVar();
176
176
  const T121 = float( 1.0 ).sub( R12 ).toVar();
177
- const phi12 = iridescenceIor.lessThan( outsideIOR ).select( float( PI ), float( 0.0 ) ).toVar();
178
- const phi21 = float( PI ).sub( phi12 ).toVar();
177
+ const phi12 = iridescenceIor.lessThan( outsideIOR ).select( float( PI ), float( 0.0 ) );
178
+ const phi21 = float( PI ).sub( phi12 );
179
179
 
180
180
  // Second interface
181
181
  const baseIOR = fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) ).toVar();
@@ -189,7 +189,7 @@ export const evalIridescence = Fn( ( [ outsideIOR, eta2, cosTheta1, thinFilmThic
189
189
  baseIOR.x.lessThan( iridescenceIor ).select( float( PI ), float( 0.0 ) ),
190
190
  baseIOR.y.lessThan( iridescenceIor ).select( float( PI ), float( 0.0 ) ),
191
191
  baseIOR.z.lessThan( iridescenceIor ).select( float( PI ), float( 0.0 ) )
192
- ).toVar();
192
+ );
193
193
 
194
194
  const OPD = float( 2.0 ).mul( iridescenceIor ).mul( thinFilmThickness ).mul( cosTheta2 ).toVar();
195
195
  const phi = vec3( phi21 ).add( phi23 ).toVar();
@@ -134,16 +134,16 @@ const wavelengthToXYZ = /*@__PURE__*/ Fn( ( [ wl ] ) => {
134
134
  export const sampleWavelengthForDispersion = Fn( ( [ baseIOR, dispersionStrength, random ] ) => {
135
135
 
136
136
  const wl = mix( float( 380.0 ), float( 700.0 ), random ).toVar();
137
- const sampledIOR = iorFromWavelength( baseIOR, dispersionStrength, wl ).toVar();
137
+ const sampledIOR = iorFromWavelength( baseIOR, dispersionStrength, wl );
138
138
 
139
139
  const xyz = wavelengthToXYZ( wl ).toVar();
140
140
  const rgb = vec3(
141
141
  xyz.x.mul( 3.2406 ).sub( xyz.y.mul( 1.5372 ) ).sub( xyz.z.mul( 0.4986 ) ),
142
142
  xyz.x.mul( - 0.9689 ).add( xyz.y.mul( 1.8758 ) ).add( xyz.z.mul( 0.0415 ) ),
143
143
  xyz.x.mul( 0.0557 ).sub( xyz.y.mul( 0.2040 ) ).add( xyz.z.mul( 1.0570 ) ),
144
- ).toVar();
144
+ );
145
145
 
146
- const colorWeight = max( rgb, vec3( 0.0 ) ).mul( vec3( 1.819, 2.773, 2.928 ) ).toVar();
146
+ const colorWeight = max( rgb, vec3( 0.0 ) ).mul( vec3( 1.819, 2.773, 2.928 ) );
147
147
 
148
148
  return SpectralSample( {
149
149
  wavelength: wl,
@@ -390,9 +390,7 @@ export const handleTransmission = Fn( ( [
390
390
  const metallicReflect = float( 0.95 );
391
391
 
392
392
  // Blend based on metalness
393
- const baseReflectProb = mix( dielectricReflect, metallicReflect, material.metalness ).toVar();
394
-
395
- reflectProb.assign( baseReflectProb );
393
+ reflectProb.assign( mix( dielectricReflect, metallicReflect, material.metalness ) );
396
394
 
397
395
  } );
398
396
 
@@ -182,9 +182,10 @@ export const pathTracerMain = ( params ) => {
182
182
 
183
183
  const baseSeed = getDecorrelatedSeed( { pixelCoord, rayIndex: int( 0 ), frame } ).toVar();
184
184
 
185
- // MRT data
185
+ // MRT — linearDepth is ray distance (sky/miss = 1e6), the convention
186
+ // MotionVector + ASVGF expect downstream.
186
187
  const worldNormal = vec3( 0.0, 0.0, 1.0 ).toVar();
187
- const linearDepth = float( 1.0 ).toVar();
188
+ const linearDepth = float( 1e6 ).toVar();
188
189
 
189
190
  // Accumulate per-sample alpha for transparent background (0.0 = env, 1.0 = geometry)
190
191
  const pixelAlpha = float( 0.0 ).toVar();
@@ -321,10 +322,7 @@ export const pathTracerMain = ( params ) => {
321
322
  If( traceResult.firstHitDistance.lessThan( 1e9 ), () => {
322
323
 
323
324
  worldNormal.assign( normalize( traceResult.objectNormal ) );
324
-
325
- linearDepth.assign( computeNDCDepth( {
326
- worldPos: traceResult.firstHitPoint, cameraProjectionMatrix, cameraViewMatrix,
327
- } ) );
325
+ linearDepth.assign( traceResult.firstHitDistance );
328
326
 
329
327
  } );
330
328