rayzee 5.11.0 → 6.0.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.
Files changed (44) hide show
  1. package/README.md +81 -24
  2. package/dist/assets/AIUpscalerWorker-AXN-lKWN.js +2 -0
  3. package/dist/assets/AIUpscalerWorker-AXN-lKWN.js.map +1 -0
  4. package/dist/rayzee.es.js +1238 -1825
  5. package/dist/rayzee.es.js.map +1 -1
  6. package/dist/rayzee.umd.js +50 -74
  7. package/dist/rayzee.umd.js.map +1 -1
  8. package/package.json +1 -4
  9. package/src/AssetConfig.js +56 -0
  10. package/src/EngineDefaults.js +5 -3
  11. package/src/EngineEvents.js +1 -0
  12. package/src/Passes/AIUpscaler.js +44 -22
  13. package/src/Passes/OIDNDenoiser.js +4 -104
  14. package/src/PathTracerApp.js +54 -65
  15. package/src/Processor/AssetLoader.js +5 -2
  16. package/src/Processor/Workers/AIUpscalerWorker.js +21 -6
  17. package/src/Stages/ASVGF.js +6 -27
  18. package/src/Stages/AdaptiveSampling.js +10 -26
  19. package/src/Stages/PathTracer.js +4 -5
  20. package/src/TSL/BVHTraversal.js +3 -30
  21. package/src/TSL/Clearcoat.js +1 -6
  22. package/src/TSL/Common.js +1 -14
  23. package/src/TSL/Debugger.js +0 -12
  24. package/src/TSL/EmissiveSampling.js +0 -16
  25. package/src/TSL/Environment.js +0 -7
  26. package/src/TSL/LightsDirect.js +3 -379
  27. package/src/TSL/LightsIndirect.js +2 -1
  28. package/src/TSL/LightsSampling.js +0 -171
  29. package/src/TSL/MaterialEvaluation.js +3 -103
  30. package/src/TSL/MaterialProperties.js +1 -56
  31. package/src/TSL/MaterialSampling.js +2 -284
  32. package/src/TSL/MaterialTransmission.js +80 -276
  33. package/src/TSL/PathTracerCore.js +8 -1
  34. package/src/TSL/Random.js +0 -23
  35. package/src/TSL/Struct.js +0 -21
  36. package/src/TSL/TextureSampling.js +0 -69
  37. package/src/index.js +5 -2
  38. package/src/managers/DenoisingManager.js +13 -5
  39. package/src/managers/VideoRenderManager.js +4 -4
  40. package/dist/assets/AIUpscalerWorker-D58dcMrY.js +0 -2
  41. package/dist/assets/AIUpscalerWorker-D58dcMrY.js.map +0 -1
  42. package/src/Processor/createRenderTargetHelper.js +0 -521
  43. package/src/TSL/RayIntersection.js +0 -162
  44. package/src/managers/helpers/StatsHelper.js +0 -45
@@ -1,9 +1,9 @@
1
1
  import {
2
- Fn, float, vec3, int,
3
- If, max, min, dot, sqrt, clamp, mix, normalize, pow
2
+ Fn, float, vec3,
3
+ If, max, min, clamp, mix
4
4
  } from 'three/tsl';
5
5
 
6
- import { DotProducts, MaterialCache } from './Struct.js';
6
+ import { DotProducts } from './Struct.js';
7
7
  import {
8
8
  PI, PI_INV, EPSILON, MIN_CLEARCOAT_ROUGHNESS,
9
9
  computeDotProducts,
@@ -118,110 +118,10 @@ export const evaluateMaterialResponse = Fn( ( [ V, L, N, material ] ) => {
118
118
 
119
119
  } );
120
120
 
121
- // -----------------------------------------------------------------------------
122
- // Cached Material Response Evaluation (Optimized)
123
- // -----------------------------------------------------------------------------
124
-
125
- export const evaluateMaterialResponseCached = Fn( ( [ V, L, N, material, cache ] ) => {
126
-
127
- const result = vec3( 0.0 ).toVar();
128
-
129
- If( cache.isPurelyDiffuse, () => {
130
-
131
- result.assign( cache.diffuseColor );
132
-
133
- } ).Else( () => {
134
-
135
- const H = V.add( L ).toVar();
136
- const lenSq = dot( H, H );
137
- H.assign( lenSq.greaterThan( EPSILON ).select( H.div( sqrt( lenSq ) ), vec3( 0.0, 0.0, 1.0 ) ) );
138
- const NoL = max( dot( N, L ), EPSILON );
139
- const NoH = max( dot( N, H ), EPSILON );
140
- const VoH = max( dot( V, H ), EPSILON );
141
-
142
- const isTransmission = cache.NoV.mul( NoL ).lessThan( 0.0 );
143
- If( isTransmission.and( material.transmission.greaterThan( 0.0 ) ), () => {
144
-
145
- result.assign( evaluateMaterialResponse( V, L, N, material ) );
146
-
147
- } ).Else( () => {
148
-
149
- const F0 = cache.F0.toVar();
150
-
151
- // Iridescence
152
- If( material.iridescence.greaterThan( 0.0 ), () => {
153
-
154
- // Per glTF KHR_materials_iridescence spec: use max thickness when no texture
155
- const thickness = material.iridescenceThicknessRange.y;
156
- const iridescenceFresnel = evalIridescence( float( 1.0 ), material.iridescenceIOR, VoH, thickness, F0 );
157
- F0.assign( clamp( mix( F0, iridescenceFresnel, material.iridescence ), vec3( 0.0 ), vec3( 1.0 ) ) );
158
-
159
- } );
160
-
161
- // Use precomputed values
162
- const denom = NoH.mul( NoH ).mul( cache.alpha2.sub( 1.0 ) ).add( 1.0 );
163
- const D = cache.alpha2.div( max( float( PI ).mul( denom ).mul( denom ), EPSILON ) );
164
-
165
- const ggx1 = NoL.div( NoL.mul( float( 1.0 ).sub( cache.k ) ).add( cache.k ) );
166
- const ggx2 = cache.NoV.div( cache.NoV.mul( float( 1.0 ).sub( cache.k ) ).add( cache.k ) );
167
- const G = ggx1.mul( ggx2 );
168
-
169
- const F = fresnelSchlick( VoH, F0 ).toVar();
170
-
171
- // Single-scatter specular BRDF
172
- const specularDenom = max( float( 4.0 ).mul( cache.NoV ).mul( NoL ), EPSILON );
173
- const specularSS = D.mul( G ).mul( F ).div( specularDenom );
174
-
175
- // Kulla-Conty multiscatter energy compensation for rough surfaces
176
- const specular = specularSS.mul( multiscatterCompensation( F0, cache.NoV, material.roughness ) );
177
- // Diffuse energy budget from hemisphere-integrated specular albedo (includes multiscatter)
178
- const E_total = specularDirectionalAlbedo( F0, cache.NoV, material.roughness );
179
- const kD = vec3( 1.0 ).sub( E_total ).mul( float( 1.0 ).sub( material.metalness ) );
180
- const diffuse = kD.mul( material.color.rgb ).mul( PI_INV );
181
-
182
- const baseLayer = diffuse.add( specular ).toVar();
183
-
184
- // Sheen
185
- If( material.sheen.greaterThan( 0.0 ), () => {
186
-
187
- const sheenDist = SheenDistribution( NoH, material.sheenRoughness );
188
- const sheenTerm = material.sheenColor.mul( material.sheen ).mul( sheenDist ).mul( NoL );
189
- // Hemisphere-averaged sheen reflectance for energy-conserving base layer attenuation
190
- const avgSheenFactor = float( 1.0 ).sub( material.sheenRoughness ).mul( 0.5 ).add( 0.25 );
191
- const sheenReflectance = clamp( material.sheenColor.mul( material.sheen ).mul( avgSheenFactor ), vec3( 0.0 ), vec3( 1.0 ) );
192
- const sheenAttenuation = vec3( 1.0 ).sub( sheenReflectance );
193
-
194
- result.assign( baseLayer.mul( sheenAttenuation ).add( sheenTerm ) );
195
-
196
- } ).Else( () => {
197
-
198
- result.assign( baseLayer );
199
-
200
- } );
201
-
202
- } );
203
-
204
- } );
205
-
206
- return result;
207
-
208
- } );
209
-
210
121
  // -----------------------------------------------------------------------------
211
122
  // Layered BRDF Evaluation (for clearcoat)
212
123
  // -----------------------------------------------------------------------------
213
124
 
214
- // Helper function to calculate energy conservation for layered materials
215
- export const calculateLayerAttenuation = Fn( ( [ clearcoat, VoH ] ) => {
216
-
217
- // Fresnel term for clearcoat layer (using f0 = 0.04 for dielectric)
218
- const F = fresnelSchlickFloat( VoH, float( 0.04 ) );
219
- // Two-interface clearcoat attenuation: (1-F)² blended by clearcoat strength
220
- // = 1 - clearcoat * F * (2 - F)
221
- return float( 1.0 ).sub( clearcoat.mul( F ).mul( float( 2.0 ).sub( F ) ) );
222
-
223
- } );
224
-
225
125
  // Evaluate both clearcoat and base layer BRDFs
226
126
  export const evaluateLayeredBRDF = Fn( ( [ dots, material ] ) => {
227
127
 
@@ -324,64 +324,9 @@ export const calculateBRDFWeights = Fn( ( [ material, mc, cache ] ) => {
324
324
  } );
325
325
 
326
326
  // -----------------------------------------------------------------------------
327
- // Material Importance and Sampling Info
327
+ // Importance Sampling Info
328
328
  // -----------------------------------------------------------------------------
329
329
 
330
- export const getMaterialImportance = Fn( ( [ material, mc ] ) => {
331
-
332
- const result = float( 0.0 ).toVar();
333
-
334
- // Early out for specialized materials
335
- If( material.transmission.greaterThan( 0.0 ).or( material.clearcoat.greaterThan( 0.0 ) ), () => {
336
-
337
- result.assign( 0.95 );
338
-
339
- } ).Else( () => {
340
-
341
- // Base importance from complexity score
342
- const baseImportance = mc.complexityScore.toVar();
343
-
344
- // Enhanced emissive importance
345
- const emissiveImportance = float( 0.0 ).toVar();
346
- If( mc.isEmissive, () => {
347
-
348
- const emissiveLuminance = dot( material.emissive, REC709_LUMINANCE_COEFFICIENTS );
349
- emissiveImportance.assign( min( float( 0.6 ), emissiveLuminance.mul( material.emissiveIntensity ).mul( 0.25 ) ) );
350
-
351
- } );
352
-
353
- // Material-specific boosts
354
- const materialBoost = float( 0.0 ).toVar();
355
- If( mc.isMetallic.and( mc.isSmooth ), () => {
356
-
357
- materialBoost.addAssign( 0.25 );
358
-
359
- } ).ElseIf( mc.isMetallic, () => {
360
-
361
- materialBoost.addAssign( 0.15 );
362
-
363
- } );
364
-
365
- If( mc.isTransmissive, () => {
366
-
367
- materialBoost.addAssign( 0.2 );
368
-
369
- } );
370
- If( mc.hasClearcoat, () => {
371
-
372
- materialBoost.addAssign( 0.1 );
373
-
374
- } );
375
-
376
- const totalImportance = max( baseImportance.add( materialBoost ), emissiveImportance );
377
- result.assign( clamp( totalImportance, 0.0, 1.0 ) );
378
-
379
- } );
380
-
381
- return result;
382
-
383
- } );
384
-
385
330
  export const getImportanceSamplingInfo = Fn( ( [
386
331
  material, bounceIndex, mc,
387
332
  environmentIntensity, useEnvMapIS, enableEnvironmentLight
@@ -1,24 +1,9 @@
1
- import {
2
- Fn, wgslFn, float, vec3, If, max, min, abs, normalize, reflect, refract, dot, pow
3
- } from 'three/tsl';
4
-
5
- import {
6
- MultiLobeWeights, DirectionSample, MaterialCache, MaterialClassification
7
- } from './Struct.js';
8
-
9
- import {
10
- PI, PI_INV, MIN_PDF, classifyMaterial,
11
- } from './Common.js';
12
- import { dielectricF0 } from './Fresnel.js';
13
-
14
- import { calculateBRDFWeights, calculateGGXPDF } from './MaterialProperties.js';
15
- import { RandomValue } from './Random.js';
1
+ import { wgslFn } from 'three/tsl';
16
2
 
17
3
  // =============================================================================
18
4
  // MATERIAL SAMPLING
19
5
  // =============================================================================
20
- // This file contains importance sampling functions for various BRDF lobes
21
- // including GGX, cosine-weighted hemisphere, VNDF, and multi-lobe MIS.
6
+ // Importance sampling primitives: GGX, cosine-weighted hemisphere, VNDF.
22
7
 
23
8
  // -----------------------------------------------------------------------------
24
9
  // Basic Sampling Functions
@@ -63,12 +48,6 @@ export const cosineWeightedSample = /*@__PURE__*/ wgslFn( `
63
48
  }
64
49
  ` );
65
50
 
66
- export const cosineWeightedPDF = Fn( ( [ NoL ] ) => {
67
-
68
- return max( NoL, MIN_PDF ).mul( PI_INV );
69
-
70
- } );
71
-
72
51
  // -----------------------------------------------------------------------------
73
52
  // VNDF Sampling (Visible Normal Distribution Function)
74
53
  // -----------------------------------------------------------------------------
@@ -95,264 +74,3 @@ export const sampleGGXVNDF = /*@__PURE__*/ wgslFn( `
95
74
  return normalize( vec3f( alpha * Nh.x, alpha * Nh.y, max( 0.0f, Nh.z ) ) );
96
75
  }
97
76
  ` );
98
-
99
- // -----------------------------------------------------------------------------
100
- // Multi-Lobe MIS Sampling
101
- // -----------------------------------------------------------------------------
102
-
103
- // Enhanced sampling weights calculation for multi-lobe MIS
104
- export const calculateSamplingWeights = Fn( ( [ V, N, material ] ) => {
105
-
106
- // Get material classification for optimized calculations
107
- const mc = MaterialClassification.wrap( classifyMaterial( material ) );
108
-
109
- // Create temporary cache values
110
- const tempInvRoughness = float( 1.0 ).sub( material.roughness );
111
- const tempMetalFactor = float( 0.5 ).add( float( 0.5 ).mul( material.metalness ) );
112
- const tempIorFactor = min( float( 2.0 ).div( material.ior ), 1.0 );
113
- const tempMaxSheenColor = max( material.sheenColor.r, max( material.sheenColor.g, material.sheenColor.b ) );
114
-
115
- // Create temporary cache for calculations
116
- const tempCache = MaterialCache( {
117
- F0: dielectricF0( material.ior ),
118
- NoV: float( 0.5 ),
119
- diffuseColor: material.color.rgb,
120
- isPurelyDiffuse: false,
121
- alpha: material.roughness.mul( material.roughness ),
122
- k: material.roughness.add( 1.0 ).mul( material.roughness.add( 1.0 ) ).div( 8.0 ),
123
- alpha2: material.roughness.mul( material.roughness ).mul( material.roughness ).mul( material.roughness ),
124
- invRoughness: tempInvRoughness,
125
- metalFactor: tempMetalFactor,
126
- iorFactor: tempIorFactor,
127
- maxSheenColor: tempMaxSheenColor,
128
- } );
129
-
130
- // Calculate base BRDF weights
131
- const brdfWeights = BRDFWeights.wrap( calculateBRDFWeights( material, mc, tempCache ) );
132
-
133
- // Calculate view-dependent factors
134
- const NoV = max( dot( N, V ), 0.0 );
135
- const fresnelFactor = pow( float( 1.0 ).sub( NoV ), 5.0 );
136
-
137
- // Energy-conserving Fresnel redistribution: energy lost by diffuse transfers to specular
138
- // This preserves the total (diffuse + specular) weight while shifting toward specular at grazing angles
139
- const fresnelTransfer = brdfWeights.diffuse.mul( fresnelFactor );
140
- const diffuse = brdfWeights.diffuse.sub( fresnelTransfer ).toVar();
141
- const specular = brdfWeights.specular.add( fresnelTransfer ).toVar();
142
-
143
- // Other lobes remain unchanged — no artificial inflation
144
- const clearcoat = brdfWeights.clearcoat.toVar();
145
- const transmission = brdfWeights.transmission.mul( tempIorFactor ).toVar();
146
- const sheen = brdfWeights.sheen.toVar();
147
- const iridescence = brdfWeights.iridescence.toVar();
148
-
149
- // Calculate total weight for normalization
150
- const totalWeight = max(
151
- diffuse.add( specular ).add( clearcoat ).add( transmission ).add( sheen ).add( iridescence ),
152
- 1e-6
153
- ).toVar();
154
-
155
- // Normalize weights
156
- const invTotal = float( 1.0 ).div( totalWeight );
157
-
158
- return MultiLobeWeights( {
159
- diffuse: diffuse.mul( invTotal ),
160
- specular: specular.mul( invTotal ),
161
- clearcoat: clearcoat.mul( invTotal ),
162
- transmission: transmission.mul( invTotal ),
163
- sheen: sheen.mul( invTotal ),
164
- iridescence: iridescence.mul( invTotal ),
165
- totalWeight,
166
- } );
167
-
168
- } );
169
-
170
- // Calculate MIS weight considering all possible sampling strategies
171
- export const calculateMultiLobeMISWeight = Fn( ( [
172
- sampledDirection, V, N, material, weights, selectedPdf
173
- ] ) => {
174
-
175
- // Calculate PDFs for all possible sampling strategies
176
- const diffusePdf = float( 0.0 ).toVar();
177
- const specularPdf = float( 0.0 ).toVar();
178
- const clearcoatPdf = float( 0.0 ).toVar();
179
- const transmissionPdf = float( 0.0 ).toVar();
180
- const sheenPdf = float( 0.0 ).toVar();
181
-
182
- const NoL = dot( N, sampledDirection );
183
-
184
- // Diffuse PDF
185
- If( NoL.greaterThan( 0.0 ), () => {
186
-
187
- diffusePdf.assign( NoL.div( PI ) );
188
-
189
- } );
190
-
191
- // Specular PDF
192
- const H = normalize( V.add( sampledDirection ) ).toVar();
193
- const NoH = max( dot( N, H ), 0.0 );
194
- const VoH = max( dot( V, H ), 0.0 );
195
- const NoV = max( dot( N, V ), 0.0 );
196
-
197
- If( NoH.greaterThan( 0.0 ).and( VoH.greaterThan( 0.0 ) ).and( NoV.greaterThan( 0.0 ) ), () => {
198
-
199
- specularPdf.assign( calculateGGXPDF( NoH, VoH, material.roughness ) );
200
-
201
- // Clearcoat PDF (using clearcoat roughness)
202
- If( material.clearcoat.greaterThan( 0.0 ), () => {
203
-
204
- clearcoatPdf.assign( calculateGGXPDF( NoH, VoH, material.clearcoatRoughness ) );
205
-
206
- } );
207
-
208
- } );
209
-
210
- // Transmission PDF (simplified)
211
- If( material.transmission.greaterThan( 0.0 ).and( NoL.lessThan( 0.0 ) ), () => {
212
-
213
- // For transmission, we're sampling the opposite hemisphere
214
- transmissionPdf.assign( abs( NoL ).div( PI ) );
215
-
216
- } );
217
-
218
- // Sheen PDF (approximated as diffuse)
219
- If( material.sheen.greaterThan( 0.0 ).and( NoL.greaterThan( 0.0 ) ), () => {
220
-
221
- sheenPdf.assign( NoL.div( PI ) );
222
-
223
- } );
224
-
225
- // Calculate weighted PDFs for each lobe
226
- const weightedDiffusePdf = weights.diffuse.mul( diffusePdf );
227
- const weightedSpecularPdf = weights.specular.mul( specularPdf );
228
- const weightedClearcoatPdf = weights.clearcoat.mul( clearcoatPdf );
229
- const weightedTransmissionPdf = weights.transmission.mul( transmissionPdf );
230
- const weightedSheenPdf = weights.sheen.mul( sheenPdf );
231
- const weightedIridescencePdf = weights.iridescence.mul( diffusePdf );
232
-
233
- // Power heuristic (β=2): sum of squared weighted PDFs
234
- const sumSquaredPdfs = weightedDiffusePdf.mul( weightedDiffusePdf )
235
- .add( weightedSpecularPdf.mul( weightedSpecularPdf ) )
236
- .add( weightedClearcoatPdf.mul( weightedClearcoatPdf ) )
237
- .add( weightedTransmissionPdf.mul( weightedTransmissionPdf ) )
238
- .add( weightedSheenPdf.mul( weightedSheenPdf ) )
239
- .add( weightedIridescencePdf.mul( weightedIridescencePdf ) );
240
-
241
- // MIS weight: selectedPdf² / Σ(pdf_i²)
242
- const misWeight = float( 1.0 ).toVar();
243
-
244
- If( sumSquaredPdfs.greaterThan( 0.0 ).and( selectedPdf.greaterThan( 0.0 ) ), () => {
245
-
246
- const selectedPdfSquared = selectedPdf.mul( selectedPdf );
247
- misWeight.assign( selectedPdfSquared.div( sumSquaredPdfs ) );
248
-
249
- } );
250
-
251
- return misWeight;
252
-
253
- } );
254
-
255
- // Multi-lobe MIS for complex materials
256
- // Note: evaluateMaterialResponse is imported from MaterialEvaluation.js at usage site
257
- export const sampleMaterialWithMultiLobeMIS = Fn( ( [
258
- V, N, material, xi, rngState, evaluateMaterialResponse
259
- ] ) => {
260
-
261
- // Calculate individual lobe weights
262
- const weights = calculateSamplingWeights( V, N, material );
263
-
264
- // Multi-importance sampling across different lobes
265
- const rand = RandomValue( rngState );
266
- const cumulativeDiffuse = weights.diffuse;
267
- const cumulativeSpecular = cumulativeDiffuse.add( weights.specular );
268
- const cumulativeClearcoat = cumulativeSpecular.add( weights.clearcoat );
269
- const cumulativeTransmission = cumulativeClearcoat.add( weights.transmission );
270
-
271
- const sampledDirection = vec3( 0.0 ).toVar();
272
- const lobePdf = float( 0.0 ).toVar();
273
- const resultPdf = float( 0.0 ).toVar();
274
-
275
- If( rand.lessThan( cumulativeDiffuse ), () => {
276
-
277
- // Diffuse sampling
278
- sampledDirection.assign( ImportanceSampleCosine( { N, xi } ) );
279
- lobePdf.assign( max( dot( N, sampledDirection ), 0.0 ).div( PI ) );
280
- resultPdf.assign( lobePdf.mul( weights.diffuse ) );
281
-
282
- } ).ElseIf( rand.lessThan( cumulativeSpecular ), () => {
283
-
284
- // Specular sampling
285
- const H = ImportanceSampleGGX( { N, roughness: material.roughness, Xi: xi } ).toVar();
286
- sampledDirection.assign( reflect( V.negate(), H ) );
287
-
288
- If( dot( N, sampledDirection ).greaterThan( 0.0 ), () => {
289
-
290
- const NoH = max( dot( N, H ), 0.0 );
291
- const VoH = max( dot( V, H ), 0.0 );
292
- lobePdf.assign( calculateGGXPDF( NoH, VoH, material.roughness ) );
293
-
294
- } );
295
-
296
- resultPdf.assign( lobePdf.mul( weights.specular ) );
297
-
298
- } ).ElseIf( rand.lessThan( cumulativeClearcoat ).and( material.clearcoat.greaterThan( 0.0 ) ), () => {
299
-
300
- // Clearcoat sampling
301
- const H = ImportanceSampleGGX( { N, roughness: material.clearcoatRoughness, Xi: xi } ).toVar();
302
- sampledDirection.assign( reflect( V.negate(), H ) );
303
-
304
- If( dot( N, sampledDirection ).greaterThan( 0.0 ), () => {
305
-
306
- const NoH = max( dot( N, H ), 0.0 );
307
- const VoH = max( dot( V, H ), 0.0 );
308
- lobePdf.assign( calculateGGXPDF( NoH, VoH, material.clearcoatRoughness ) );
309
-
310
- } );
311
-
312
- resultPdf.assign( lobePdf.mul( weights.clearcoat ) );
313
-
314
- } ).ElseIf( rand.lessThan( cumulativeTransmission ).and( material.transmission.greaterThan( 0.0 ) ), () => {
315
-
316
- // Transmission sampling - simplified approach
317
- const H = ImportanceSampleGGX( { N, roughness: material.roughness, Xi: xi } ).toVar();
318
- const refractionDir = refract( V.negate(), H, float( 1.0 ).div( material.ior ) ).toVar();
319
-
320
- If( dot( refractionDir, refractionDir ).greaterThan( 0.001 ), () => {
321
-
322
- sampledDirection.assign( normalize( refractionDir ) );
323
- const NoH = max( dot( N, H ), 0.0 );
324
- const VoH = max( dot( V, H ), 0.0 );
325
- lobePdf.assign( calculateGGXPDF( NoH, VoH, material.roughness ) );
326
-
327
- } ).Else( () => {
328
-
329
- // Total internal reflection - fallback to specular
330
- sampledDirection.assign( reflect( V.negate(), H ) );
331
- lobePdf.assign( 0.1 );
332
-
333
- } );
334
-
335
- resultPdf.assign( lobePdf.mul( weights.transmission ) );
336
-
337
- } ).Else( () => {
338
-
339
- // Fallback to diffuse sampling for sheen/iridescence
340
- sampledDirection.assign( ImportanceSampleCosine( { N, xi } ) );
341
- lobePdf.assign( max( dot( N, sampledDirection ), 0.0 ).div( PI ) );
342
- resultPdf.assign( lobePdf.mul( weights.sheen.add( weights.iridescence ) ) );
343
-
344
- } );
345
-
346
- // Calculate MIS weight considering all possible sampling strategies
347
- const misWeight = calculateMultiLobeMISWeight( sampledDirection, V, N, material, weights, resultPdf );
348
-
349
- const resultValue = evaluateMaterialResponse( V, sampledDirection, N, material ).toVar();
350
- resultValue.mulAssign( misWeight );
351
-
352
- return DirectionSample( {
353
- direction: sampledDirection,
354
- value: resultValue,
355
- pdf: resultPdf,
356
- } );
357
-
358
- } );