rayzee 11.8.0 → 11.9.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 (53) hide show
  1. package/README.md +18 -6
  2. package/dist/{ArchiveImporter-_sJm5uaK.js → ArchiveImporter-BJxszU_k.js} +1549 -1232
  3. package/dist/{BidirectionalIntegrator-Dzt4Z97x.js → BidirectionalIntegrator-DWg66vAd.js} +240 -240
  4. package/dist/{BufferLayout-xPRH9jYD.js → BufferLayout-Bzgccg1o.js} +1 -0
  5. package/dist/{ColorManagement-30MfpHwQ.js → ColorManagement-B_HjAiy2.js} +1 -1
  6. package/dist/{EngineIssues-xljoXM0E.js → EngineIssues-CfJiBaNj.js} +1 -0
  7. package/dist/{NeuralRetouch-BKQCsmEs.js → NeuralRetouch-BD9E-Yht.js} +1 -1
  8. package/dist/{NeuralSuperRes-C4ymq68e.js → NeuralSuperRes-C8Mf3tJX.js} +1 -1
  9. package/dist/{PathTracer-C_to_i40.js → PathTracer-DeE9UJ1n.js} +3433 -3363
  10. package/dist/{PhysicalSky-bMHbm_7H.js → PhysicalSky-cKy75-oR.js} +45 -45
  11. package/dist/{RayzeeRenderer-B_aB6VTJ.js → RayzeeRenderer-CzLyBA3G.js} +13 -8
  12. package/dist/{SceneMetadata-CSmlJW4c.js → SceneMetadata-C17UfOEp.js} +1 -1
  13. package/dist/{SolarLimb-bZE6y63S.js → SolarLimb-DRYy8tln.js} +184 -172
  14. package/dist/addons/archives.es.js +1 -1
  15. package/dist/addons/bidirectional.es.js +1 -1
  16. package/dist/addons/color.es.js +1 -1
  17. package/dist/addons/physical-sky.es.js +1 -1
  18. package/dist/addons/storage.es.js +1 -1
  19. package/dist/{openStorage-B085D9FR.js → openStorage-CdyVjmps.js} +1 -1
  20. package/dist/rayzee-core.es.js +7 -7
  21. package/dist/rayzee.es.js +567 -559
  22. package/dist/rayzee.umd.js +106 -101
  23. package/dist/{sceneCachePolicy-D_uBSbQF.js → sceneCachePolicy-ywbGKHD3.js} +9 -6
  24. package/package.json +1 -1
  25. package/src/EngineDefaults.js +1 -0
  26. package/src/EngineIssues.js +1 -0
  27. package/src/Passes/OIDNTemporalHistory.js +1 -1
  28. package/src/Processor/ArchiveImporter.js +83 -21
  29. package/src/Processor/AssetLoader.js +7 -0
  30. package/src/Processor/BufferLayout.js +3 -3
  31. package/src/Processor/GeometryExtractor.js +12 -4
  32. package/src/Processor/MaterialPacking.js +2 -2
  33. package/src/Processor/PBRT/PBRTMaterials.js +47 -12
  34. package/src/Processor/PBRT/PBRTParser.js +144 -22
  35. package/src/Processor/PBRT/PBRTSceneBuilder.js +287 -67
  36. package/src/Processor/PBRT/PBRTTextureBake.js +191 -9
  37. package/src/Processor/PBRT/index.js +19 -4
  38. package/src/Processor/SceneProcessor.js +42 -26
  39. package/src/RayzeeRenderer.js +4 -2
  40. package/src/Stages/DenoiserSettings.js +0 -4
  41. package/src/Stages/NRD.js +53 -24
  42. package/src/Stages/NormalDepth.js +5 -3
  43. package/src/Stages/PathTracer.js +2 -0
  44. package/src/Stages/PathTracerStage.js +5 -1
  45. package/src/Storage/SceneGraphCodec.js +6 -3
  46. package/src/TSL/BVHTraversal.js +33 -20
  47. package/src/TSL/Common.js +25 -8
  48. package/src/TSL/Displacement.js +66 -113
  49. package/src/TSL/LightsDirect.js +33 -11
  50. package/src/TSL/SceneResources.js +8 -0
  51. package/src/TSL/ShadeKernel.js +2 -2
  52. package/src/TSL/Struct.js +1 -0
  53. package/src/managers/MaterialDataManager.js +11 -0
@@ -1,33 +1,39 @@
1
1
  import { sceneResources } from './SceneResources.js';
2
- import { Fn, float, vec2, int, If, Loop, abs, normalize, dot, max, uintBitsToFloat } from 'three/tsl';
2
+ import {
3
+ Fn, wgslFn, float, vec2, int, bool as tslBool, If, Loop, Break, abs, normalize, dot, cross, min, max, clamp, ceil, select, uintBitsToFloat,
4
+ } from 'three/tsl';
3
5
 
4
6
  import { struct } from './patches.js';
5
7
  import { triangleRow, instanceRows, instanceDirToWorld } from './Common.js';
6
8
  import { sampleDisplacementMap, bucketTexelSize } from './TextureSampling.js';
7
9
 
8
- // Ray-displacement intersection configuration
9
- const MAX_MARCH_STEPS = 32;
10
- const MIN_MARCH_STEPS = 16;
11
- const BINARY_STEPS = 5;
10
+ const MIN_MARCH_STEPS = 8;
11
+ const MAX_MARCH_STEPS = 64;
12
+ const MAX_BOUNCE_MARCH_STEPS = 16;
13
+ const STEPS_PER_TEXEL = 0.25;
14
+ const BINARY_STEPS = 6;
15
+ const SLOPE_TEXELS = 1.5;
12
16
 
13
17
  export const DisplacementResult = struct( {
14
18
  hitPoint: 'vec3',
15
19
  uv: 'vec2',
16
20
  normal: 'vec3',
17
- height: 'float',
18
21
  } );
19
22
 
23
+ // Texture-space UV per raw UV, per axis.
24
+ const uvTransformScale = /*@__PURE__*/ wgslFn( `
25
+ fn uvTransformScale( transform: mat3x3f ) -> vec2f {
26
+ return max( vec2f( length( transform[0].xy ), length( transform[1].xy ) ), vec2f( 1e-6 ) );
27
+ }
28
+ ` );
29
+
20
30
  /**
21
31
  * Tessellation-free displacement mapping via analytical UV ray marching.
22
32
  *
23
- * Instead of projecting march points onto the triangle to get barycentrics (which
24
- * causes UV distortion from clamping), this computes the ray's UV trajectory
25
- * analytically using the triangle's actual tangent vectors (dP/du, dP/dv).
26
- *
27
33
  * The ray is parameterized as: P(dt) = hitPoint + rayDir * dt
28
34
  * UV along the ray: UV(dt) = hitUV + dUV_dt * dt
29
35
  * Ray height above surface: h_ray(dt) = dt * dot(rayDir, N)
30
- * Displaced surface height: h_surf(dt) = (sample(UV(dt)) - 0.5) * scale
36
+ * Displaced surface height: h_surf(dt) = sample(UV(dt)) * displacementScale + displacementBias, as three.js
31
37
  *
32
38
  * We find dt where h_ray(dt) = h_surf(dt).
33
39
  */
@@ -38,7 +44,6 @@ export const refineDisplacedIntersection = Fn( ( [
38
44
  const resultHitPoint = hitInfo.hitPoint.toVar();
39
45
  const resultUV = hitInfo.uv.toVar();
40
46
  const resultNormal = hitInfo.normal.toVar();
41
- const resultHeight = float( 0.0 ).toVar();
42
47
 
43
48
  // Fetch triangle vertex data
44
49
  const triIdx = hitInfo.triangleIndex;
@@ -83,16 +88,13 @@ export const refineDisplacedIntersection = Fn( ( [
83
88
  const B = edge2.mul( dUV1.x ).sub( edge1.mul( dUV2.x ) ).mul( invDet ).toVar();
84
89
  const N = hitInfo.normal.toVar();
85
90
 
86
- const scale = material.displacementScale.div( float( 10.0 ) ); // Arbitrary scale factor for height
91
+ const scale = material.displacementScale;
92
+ const bias = material.displacementBias;
87
93
  const rayDir = ray.direction;
88
94
 
89
- // Compute UV velocity along the ray: dUV/dt
90
- // Project ray direction onto surface tangent plane
91
95
  const NdotD = dot( rayDir, N ).toVar();
92
96
  const rayProj = rayDir.sub( N.mul( NdotD ) ).toVar();
93
97
 
94
- // Solve: rayProj = du_dt * T + dv_dt * B
95
- // Using the Gram matrix inverse of [T, B]
96
98
  const TdotT = dot( T, T );
97
99
  const TdotB = dot( T, B );
98
100
  const BdotB = dot( B, B );
@@ -100,89 +102,60 @@ export const refineDisplacedIntersection = Fn( ( [
100
102
 
101
103
  If( abs( detJ ).greaterThan( 1e-10 ), () => {
102
104
 
105
+ // Dual basis of (T, B): dot( Td, T ) = 1, dot( Td, B ) = 0, and likewise Bd.
103
106
  const invDetJ = float( 1.0 ).div( detJ );
104
- const rayProjDotT = dot( rayProj, T );
105
- const rayProjDotB = dot( rayProj, B );
106
-
107
- const du_dt = BdotB.mul( rayProjDotT ).sub( TdotB.mul( rayProjDotB ) ).mul( invDetJ );
108
- const dv_dt = TdotT.mul( rayProjDotB ).sub( TdotB.mul( rayProjDotT ) ).mul( invDetJ );
109
- const dUV_dt = vec2( du_dt, dv_dt ).toVar();
110
-
111
- // Ray height change per unit dt: how fast the ray moves along the surface normal
112
- const dh_ray_dt = NdotD.toVar();
113
-
114
- // March range: displacement shell extends ±0.5*scale from base surface
115
- // Compute dt range to traverse the full shell
116
- const absNdotD = max( abs( dh_ray_dt ), 0.001 );
117
- const dtShell = scale.div( absNdotD ).toVar();
107
+ const Td = T.mul( BdotB ).sub( B.mul( TdotB ) ).mul( invDetJ ).toVar();
108
+ const Bd = B.mul( TdotT ).sub( T.mul( TdotB ) ).mul( invDetJ ).toVar();
109
+ const dUV_dt = vec2( dot( rayProj, Td ), dot( rayProj, Bd ) ).toVar();
118
110
 
119
- // Adaptive step count: full steps on primary ray, half on deeper bounces
120
- const marchSteps = int( bounceIndex.equal( int( 0 ) ).select(
121
- int( MAX_MARCH_STEPS ), int( MIN_MARCH_STEPS )
122
- ) ).toVar();
111
+ // +1 when the ray arrives from the side N points to.
112
+ const side = select( NdotD.lessThan( 0.0 ), float( 1.0 ), float( - 1.0 ) ).toVar();
113
+ const under = ( dt, height ) => dt.mul( NdotD ).sub( height.mul( scale ).add( bias ) ).mul( side ).lessThanEqual( 0.0 );
114
+ const heightAt = uv => sampleDisplacementMap( material.displacementMapIndex, uv, material.displacementTransform );
123
115
 
124
- // Start from above the shell, march through
125
- const dtStart = dtShell.negate().toVar();
126
- const dtEnd = dtShell;
127
- const dtStep = dtEnd.sub( dtStart ).div( float( marchSteps ) ).toVar();
116
+ // Enter the shell [bias, bias + scale] on the ray's side, never behind the ray's origin.
117
+ const top = max( bias, bias.add( scale ) );
118
+ const bottom = min( bias, bias.add( scale ) );
119
+ const slope = side.negate().mul( max( abs( NdotD ), 0.001 ) );
120
+ const dtStart = max( select( side.greaterThan( 0.0 ), top, bottom ).div( slope ), hitInfo.dst.negate() ).toVar();
121
+ const dtEnd = select( side.greaterThan( 0.0 ), bottom, top ).div( slope ).toVar();
128
122
 
129
- // Track for binary refinement
130
- const prevDt = dtStart.toVar();
131
- const currDt = dtStart.toVar();
132
- const found = int( 0 ).toVar();
123
+ // One step per few texels the ray crosses, so a grazing ray does not step over the gaps.
124
+ const texelsPerUV = uvTransformScale( { transform: material.displacementTransform } )
125
+ .div( bucketTexelSize( sceneResources( builder ).linearBuckets, material.displacementMapIndex ) ).toVar();
126
+ const texelsCrossed = dUV_dt.mul( texelsPerUV ).length().mul( dtEnd.sub( dtStart ) );
127
+ const maxSteps = select( bounceIndex.equal( int( 0 ) ), float( MAX_MARCH_STEPS ), float( MAX_BOUNCE_MARCH_STEPS ) );
128
+ const marchSteps = select( dtEnd.greaterThan( dtStart ),
129
+ int( clamp( ceil( texelsCrossed.mul( STEPS_PER_TEXEL ) ), float( MIN_MARCH_STEPS ), maxSteps ) ), int( 0 ) ).toVar();
130
+ const dtStep = dtEnd.sub( dtStart ).div( max( float( marchSteps ), 1.0 ) ).toVar();
133
131
 
134
- // Linear march through displacement shell
135
- Loop( { start: int( 0 ), end: marchSteps, type: 'int', condition: '<' }, ( { i } ) => {
132
+ const loDt = dtStart.toVar();
133
+ const hiDt = dtStart.toVar();
134
+ const found = tslBool( false ).toVar();
136
135
 
137
- If( found.equal( int( 0 ) ), () => {
136
+ Loop( { start: int( 1 ), end: marchSteps.add( 1 ), type: 'int', condition: '<' }, ( { i } ) => {
138
137
 
139
- const dt = dtStart.add( dtStep.mul( float( i ) ) ).toVar();
138
+ const dt = dtStart.add( dtStep.mul( float( i ) ) ).toVar();
140
139
 
141
- // UV at this point along the ray
142
- const marchUV = hitInfo.uv.add( dUV_dt.mul( dt ) );
140
+ If( under( dt, heightAt( hitInfo.uv.add( dUV_dt.mul( dt ) ) ) ), () => {
143
141
 
144
- // Ray height above base surface at this dt
145
- const rayHeight = dt.mul( dh_ray_dt );
146
-
147
- // Displaced surface height
148
- const heightSample = sampleDisplacementMap(
149
- material.displacementMapIndex, marchUV, material.displacementTransform,
150
- );
151
- const surfaceHeight = heightSample.sub( 0.5 ).mul( scale );
152
-
153
- // Ray crosses below displaced surface → intersection
154
- If( rayHeight.lessThanEqual( surfaceHeight ).and( i.greaterThan( int( 0 ) ) ), () => {
155
-
156
- found.assign( 1 );
157
- currDt.assign( dt );
158
-
159
- } ).Else( () => {
160
-
161
- prevDt.assign( dt );
162
-
163
- } );
142
+ found.assign( true );
143
+ hiDt.assign( dt );
144
+ Break();
164
145
 
165
146
  } );
166
147
 
167
- } );
148
+ loDt.assign( dt );
168
149
 
169
- // Binary refinement
170
- If( found.equal( int( 1 ) ), () => {
150
+ } );
171
151
 
172
- const loDt = prevDt.toVar();
173
- const hiDt = currDt.toVar();
152
+ If( found, () => {
174
153
 
175
154
  Loop( { start: int( 0 ), end: int( BINARY_STEPS ), type: 'int', condition: '<' }, () => {
176
155
 
177
156
  const midDt = loDt.add( hiDt ).mul( 0.5 ).toVar();
178
- const midUV = hitInfo.uv.add( dUV_dt.mul( midDt ) );
179
- const midRayHeight = midDt.mul( dh_ray_dt );
180
- const midSample = sampleDisplacementMap(
181
- material.displacementMapIndex, midUV, material.displacementTransform,
182
- );
183
- const midSurfHeight = midSample.sub( 0.5 ).mul( scale );
184
157
 
185
- If( midRayHeight.lessThanEqual( midSurfHeight ), () => {
158
+ If( under( midDt, heightAt( hitInfo.uv.add( dUV_dt.mul( midDt ) ) ) ), () => {
186
159
 
187
160
  hiDt.assign( midDt );
188
161
 
@@ -194,40 +167,20 @@ export const refineDisplacedIntersection = Fn( ( [
194
167
 
195
168
  } );
196
169
 
197
- // Final intersection
198
170
  const finalDt = loDt.add( hiDt ).mul( 0.5 ).toVar();
199
171
  const finalUV = hitInfo.uv.add( dUV_dt.mul( finalDt ) ).toVar();
200
- const finalPoint = hitInfo.hitPoint.add( rayDir.mul( finalDt ) );
201
-
202
- const finalHeight = sampleDisplacementMap(
203
- material.displacementMapIndex, finalUV, material.displacementTransform,
204
- );
205
- const displacedHeight = finalHeight.sub( 0.5 ).mul( scale );
206
-
207
- // Compute displaced normal from height-field gradients using UV tangent vectors.
208
- // Finite-difference step = one texel of the SELECTED bucket array's real dimensions.
209
- const texel = bucketTexelSize( sceneResources( builder ).linearBuckets, material.displacementMapIndex ).toVar();
210
- const hC = finalHeight;
211
- const hU = sampleDisplacementMap(
212
- material.displacementMapIndex, finalUV.add( vec2( texel.x, 0.0 ) ), material.displacementTransform,
213
- );
214
- const hV = sampleDisplacementMap(
215
- material.displacementMapIndex, finalUV.add( vec2( 0.0, texel.y ) ), material.displacementTransform,
216
- );
217
-
218
- // Perturb normal using actual tangent/bitangent from UV parameterization
219
- const Tn = normalize( T ).toVar();
220
- const Bn = normalize( B ).toVar();
221
- const gradU = hU.sub( hC ).mul( scale );
222
- const gradV = hV.sub( hC ).mul( scale );
223
- const displacedNormal = normalize(
224
- N.sub( Tn.mul( gradU ) ).sub( Bn.mul( gradV ) )
225
- );
226
-
227
- resultHitPoint.assign( finalPoint );
172
+
173
+ // Central differences over a few texels: an 8-bit map's steps make one-texel slopes flicker.
174
+ const step = vec2( SLOPE_TEXELS ).div( texelsPerUV ).toVar();
175
+ const dhdu = heightAt( finalUV.add( vec2( step.x, 0.0 ) ) ).sub( heightAt( finalUV.sub( vec2( step.x, 0.0 ) ) ) ).mul( scale ).div( step.x.mul( 2.0 ) );
176
+ const dhdv = heightAt( finalUV.add( vec2( 0.0, step.y ) ) ).sub( heightAt( finalUV.sub( vec2( 0.0, step.y ) ) ) ).mul( scale ).div( step.y.mul( 2.0 ) );
177
+ resultNormal.assign( normalize( N.sub( Td.mul( dhdu ) ).sub( Bd.mul( dhdv ) ) ) );
178
+
179
+ // A point behind the base triangle goes up onto its plane: rays spawned below it would hit it.
180
+ const faceN = normalize( cross( Td, Bd ) );
181
+ const moveDir = select( finalDt.greaterThan( 0.0 ), rayDir.sub( faceN.mul( dot( rayDir, faceN ) ) ), rayDir );
182
+ resultHitPoint.assign( hitInfo.hitPoint.add( moveDir.mul( finalDt ) ) );
228
183
  resultUV.assign( finalUV );
229
- resultNormal.assign( displacedNormal );
230
- resultHeight.assign( displacedHeight );
231
184
 
232
185
  } );
233
186
 
@@ -235,6 +188,6 @@ export const refineDisplacedIntersection = Fn( ( [
235
188
 
236
189
  } );
237
190
 
238
- return DisplacementResult( { hitPoint: resultHitPoint, uv: resultUV, normal: resultNormal, height: resultHeight } );
191
+ return DisplacementResult( { hitPoint: resultHitPoint, uv: resultUV, normal: resultNormal } );
239
192
 
240
193
  } );
@@ -2,7 +2,7 @@
2
2
  // Direct lighting calculations including shadow ray tracing
3
3
  // and contribution calculations for all light types.
4
4
 
5
- import { sceneResources } from './SceneResources.js';
5
+ import { sceneResources, usesTransmissionMaps } from './SceneResources.js';
6
6
  import {
7
7
  Fn,
8
8
  float,
@@ -30,6 +30,7 @@ import { Ray, ShadowMaterial, HitInfo } from './Struct.js';
30
30
  import {
31
31
  REC709_LUMINANCE_COEFFICIENTS, getShadowMaterial, instanceRows,
32
32
  instanceFaceNormalToWorld, triangleRow, shadowFlagsSettle, offsetRayOrigin,
33
+ getDatafromStorageBuffer, MATERIAL_SLOT, MATERIAL_SLOTS,
33
34
  } from './Common.js';
34
35
  import { fresnelDielectric } from './Fresnel.js';
35
36
  import { calculateBeerLawAbsorption } from './MaterialTransmission.js';
@@ -49,7 +50,8 @@ const makeTraceShadowRay = ( refractiveBlocks ) => Fn( ( [
49
50
  materialBuffer,
50
51
  ], builder ) => {
51
52
 
52
- const { alphaShadows, shadowAlbedoMaps } = sceneResources( builder );
53
+ const { alphaShadows, shadowAlbedoMaps, linearBuckets } = sceneResources( builder );
54
+ const transmissionMaps = ! refractiveBlocks && usesTransmissionMaps( builder );
53
55
  const transmittance = float( 1.0 ).toVar();
54
56
  const rayOrigin = origin.toVar();
55
57
  const remainingDist = float( maxDist ).toVar();
@@ -88,6 +90,19 @@ const makeTraceShadowRay = ( refractiveBlocks ) => Fn( ( [
88
90
  // Fetch material for the hit surface (thin reader: 7 slots instead of 27)
89
91
  const shadowMaterial = ShadowMaterial.wrap( getShadowMaterial( shadowHit.materialIndex, materialBuffer ) );
90
92
 
93
+ // The hit's texture coordinates through the albedo transform, which Shade reads the extension maps with too.
94
+ const hitTextureUV = () => {
95
+
96
+ const baryU = shadowHit.uv.x;
97
+ const baryV = shadowHit.uv.y;
98
+ const baryW = float( 1.0 ).sub( baryU ).sub( baryV );
99
+ const uvData1 = uintBitsToFloat( triangleRow( triangleBuffer, shadowHit.triangleIndex, 3 ) );
100
+ const uvData2 = uintBitsToFloat( triangleRow( triangleBuffer, shadowHit.triangleIndex, 4 ).xy );
101
+ const hitUV = uvData1.xy.mul( baryW ).add( uvData1.zw.mul( baryU ) ).add( uvData2.mul( baryV ) );
102
+ return getTransformedUV( { uv: hitUV, transform: shadowMaterial.albedoTransform } );
103
+
104
+ };
105
+
91
106
  // ---------------------------------------------------------------
92
107
  // Alpha-cutout handling (MASK / BLEND with albedo texture alpha)
93
108
  // Gated by runtime uniform + alphaMode check — zero overhead for opaque materials.
@@ -106,14 +121,7 @@ const makeTraceShadowRay = ( refractiveBlocks ) => Fn( ( [
106
121
 
107
122
  If( shadowMaterial.albedoMapIndex.greaterThanEqual( int( 0 ) ), () => {
108
123
 
109
- const baryU = shadowHit.uv.x;
110
- const baryV = shadowHit.uv.y;
111
- const baryW = float( 1.0 ).sub( baryU ).sub( baryV );
112
- const uvData1 = uintBitsToFloat( triangleRow( triangleBuffer, shadowHit.triangleIndex, 3 ) );
113
- const uvData2 = uintBitsToFloat( triangleRow( triangleBuffer, shadowHit.triangleIndex, 4 ).xy );
114
- const hitUV = uvData1.xy.mul( baryW ).add( uvData1.zw.mul( baryU ) ).add( uvData2.mul( baryV ) );
115
- const albedoUV = getTransformedUV( { uv: hitUV, transform: shadowMaterial.albedoTransform } );
116
- texAlpha.assign( sampleBucket( shadowAlbedoMaps, shadowMaterial.albedoMapIndex, albedoUV ).a );
124
+ texAlpha.assign( sampleBucket( shadowAlbedoMaps, shadowMaterial.albedoMapIndex, hitTextureUV() ).a );
117
125
 
118
126
  } );
119
127
 
@@ -167,6 +175,20 @@ const makeTraceShadowRay = ( refractiveBlocks ) => Fn( ( [
167
175
 
168
176
  } : () => {
169
177
 
178
+ let transmission = shadowMaterial.transmission;
179
+ if ( transmissionMaps ) {
180
+
181
+ // glTF's transmission is the factor × the map's R: where the map is black the surface is opaque.
182
+ transmission = transmission.toVar();
183
+ const mapIndex = int( getDatafromStorageBuffer( materialBuffer, shadowHit.materialIndex, int( MATERIAL_SLOT.EXT_MAP_INDICES_A ), int( MATERIAL_SLOTS ) ).r ).toVar();
184
+ If( mapIndex.greaterThanEqual( int( 0 ) ), () => {
185
+
186
+ transmission.mulAssign( sampleBucket( linearBuckets, mapIndex, hitTextureUV() ).r );
187
+
188
+ } );
189
+
190
+ }
191
+
170
192
  // Deferred geometric-normal compute — refetch triangle positions and
171
193
  // derive the normal here so opaque/alpha-cutout shadow hits don't pay
172
194
  // the cross+normalize cost in BVH traversal.
@@ -204,7 +226,7 @@ const makeTraceShadowRay = ( refractiveBlocks ) => Fn( ( [
204
226
  // Shadow rays go straight through, so both faces see the outside-in angle and eta.
205
227
  const fresnel = fresnelDielectric( abs( dot( dir, N ) ), max( shadowMaterial.ior, 1.0 ) );
206
228
 
207
- const matTransmittance = float( 1.0 ).sub( fresnel ).mul( shadowMaterial.transmission );
229
+ const matTransmittance = float( 1.0 ).sub( fresnel ).mul( transmission );
208
230
  transmittance.mulAssign( matTransmittance );
209
231
 
210
232
  // Early exit if almost no light passes through
@@ -13,6 +13,7 @@
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
15
  * @property {boolean} [lightRadius] - some point or spot light has a radius (path tracing only); false = all are points
16
+ * @property {boolean} [transmissionMaps] - some material has a transmission map; false = shadow rays never sample one
16
17
  */
17
18
 
18
19
  import { context } from 'three/tsl';
@@ -60,3 +61,10 @@ export function usesLightRadius( builder ) {
60
61
  return builder.context[ KEY ]?.lightRadius ?? true;
61
62
 
62
63
  }
64
+
65
+ /** Whether the kernel being built has shadow rays sample transmission maps. Outside a scene kernel (a test) it does. */
66
+ export function usesTransmissionMaps( builder ) {
67
+
68
+ return builder.context[ KEY ]?.transmissionMaps ?? true;
69
+
70
+ }
@@ -917,7 +917,7 @@ export function buildShadeKernel( params ) {
917
917
  const displacedNormal = N.toVar();
918
918
  If(
919
919
  material.displacementMapIndex.greaterThanEqual( int( 0 ) )
920
- .and( material.displacementScale.greaterThan( 0.0 ) ),
920
+ .and( material.displacementScale.notEqual( 0.0 ) ),
921
921
  () => {
922
922
 
923
923
  const dispRay = Ray( { origin, direction } );
@@ -1003,7 +1003,7 @@ export function buildShadeKernel( params ) {
1003
1003
  const albedo = matSamples.albedo.toVar();
1004
1004
  If(
1005
1005
  material.displacementMapIndex.greaterThanEqual( int( 0 ) )
1006
- .and( material.displacementScale.greaterThan( 0.0 ) ),
1006
+ .and( material.displacementScale.notEqual( 0.0 ) ),
1007
1007
  () => {
1008
1008
 
1009
1009
  N.assign( normalize( displacedNormal.add( matSamples.normal.sub( normalize( hitNormal ) ) ) ) );
package/src/TSL/Struct.js CHANGED
@@ -36,6 +36,7 @@ export const RayTracingMaterial = struct( {
36
36
  bumpMapIndex: 'int',
37
37
  bumpScale: 'float',
38
38
  displacementScale: 'float',
39
+ displacementBias: 'float',
39
40
  metalnessMapIndex: 'int',
40
41
  roughnessMapIndex: 'int',
41
42
  displacementMapIndex: 'int',
@@ -342,6 +342,16 @@ export class MaterialDataManager {
342
342
 
343
343
  }
344
344
 
345
+ /** Whether some material has a transmission map: only then do shadow rays sample one. */
346
+ hasTransmissionMaps() {
347
+
348
+ const data = this.materialStorageAttr?.array;
349
+ if ( ! data ) return false;
350
+ for ( let i = 0; i < this.materialCount; i ++ ) if ( data[ i * M.FLOATS_PER_MATERIAL + M.TRANSMISSION_MAP_INDEX ] >= 0 ) return true;
351
+ return false;
352
+
353
+ }
354
+
345
355
  /**
346
356
  * Where a material property's value came from, as a MATERIAL_VALUE_SOURCE. `default` means the
347
357
  * model never said and the engine filled it in.
@@ -503,6 +513,7 @@ export class MaterialDataManager {
503
513
  break;
504
514
  case 'bumpScale': data[ stride + M.BUMP_SCALE ] = value; break;
505
515
  case 'displacementScale': data[ stride + M.DISPLACEMENT_SCALE ] = value; break;
516
+ case 'displacementBias': data[ stride + M.DISPLACEMENT_BIAS ] = value; break;
506
517
  case 'subsurface': data[ stride + M.SUBSURFACE ] = value; break;
507
518
  case 'subsurfaceRadiusScale': data[ stride + M.SUBSURFACE_RADIUS_SCALE ] = value; break;
508
519
  case 'subsurfaceAnisotropy': data[ stride + M.SUBSURFACE_ANISOTROPY ] = value; break;