rayzee 6.0.1 → 6.2.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/README.md +5 -0
- package/dist/assets/TexturesWorker-DBqGmVdR.js.map +1 -1
- package/dist/rayzee.es.js +2421 -2078
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +55 -52
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/EngineDefaults.js +3 -0
- package/src/PathTracerApp.js +18 -8
- package/src/Pipeline/RenderStage.js +3 -0
- package/src/Processor/IESParser.js +340 -0
- package/src/Processor/LightSerializer.js +32 -4
- package/src/Processor/SceneProcessor.js +0 -1
- package/src/Processor/ShaderBuilder.js +40 -1
- package/src/Processor/Workers/TexturesWorker.js +1 -1
- package/src/RenderSettings.js +3 -0
- package/src/Stages/NormalDepth.js +3 -19
- package/src/Stages/PathTracer.js +15 -9
- package/src/TSL/BVHTraversal.js +4 -6
- package/src/TSL/Common.js +1 -1
- package/src/TSL/Debugger.js +0 -2
- package/src/TSL/EmissiveSampling.js +20 -22
- package/src/TSL/Environment.js +60 -14
- package/src/TSL/Fresnel.js +13 -4
- package/src/TSL/LightsCore.js +238 -5
- package/src/TSL/LightsDirect.js +16 -5
- package/src/TSL/LightsIndirect.js +4 -37
- package/src/TSL/LightsSampling.js +119 -185
- package/src/TSL/MaterialEvaluation.js +25 -14
- package/src/TSL/MaterialProperties.js +14 -34
- package/src/TSL/MaterialTransmission.js +18 -37
- package/src/TSL/PathTracer.js +25 -7
- package/src/TSL/PathTracerCore.js +144 -139
- package/src/TSL/Struct.js +7 -1
- package/src/TSL/TextureSampling.js +2 -2
- package/src/index.js +2 -0
- package/src/managers/AnimationManager.js +3 -6
- package/src/managers/DenoisingManager.js +1 -1
- package/src/managers/GoboManager.js +277 -0
- package/src/managers/IESManager.js +268 -0
- package/src/managers/LightManager.js +33 -1
- package/src/managers/TransformManager.js +3 -3
- package/src/managers/UniformManager.js +5 -5
|
@@ -21,9 +21,7 @@ import {
|
|
|
21
21
|
float,
|
|
22
22
|
vec2,
|
|
23
23
|
vec3,
|
|
24
|
-
vec4,
|
|
25
24
|
int,
|
|
26
|
-
uint,
|
|
27
25
|
bool as tslBool,
|
|
28
26
|
max,
|
|
29
27
|
min,
|
|
@@ -34,14 +32,10 @@ import {
|
|
|
34
32
|
dot,
|
|
35
33
|
cross,
|
|
36
34
|
normalize,
|
|
37
|
-
length,
|
|
38
|
-
clamp,
|
|
39
35
|
mix,
|
|
40
36
|
select,
|
|
41
37
|
If,
|
|
42
38
|
Loop,
|
|
43
|
-
Break,
|
|
44
|
-
texture,
|
|
45
39
|
} from 'three/tsl';
|
|
46
40
|
|
|
47
41
|
import {
|
|
@@ -59,9 +53,12 @@ import {
|
|
|
59
53
|
getDistanceAttenuation,
|
|
60
54
|
getSpotAttenuation,
|
|
61
55
|
intersectAreaLight,
|
|
56
|
+
sampleSpotGoboMask,
|
|
57
|
+
sampleDirectionalGoboMask,
|
|
58
|
+
sampleIESProfile,
|
|
62
59
|
} from './LightsCore.js';
|
|
63
60
|
|
|
64
|
-
import { MISStrategy } from './Struct.js';
|
|
61
|
+
import { MISStrategy, DotProducts } from './Struct.js';
|
|
65
62
|
import {
|
|
66
63
|
calculateDirectionalLightImportance,
|
|
67
64
|
estimateLightImportance,
|
|
@@ -71,7 +68,7 @@ import {
|
|
|
71
68
|
} from './LightsDirect.js';
|
|
72
69
|
|
|
73
70
|
import { traverseBVHShadow } from './BVHTraversal.js';
|
|
74
|
-
import {
|
|
71
|
+
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
75
72
|
import { calculateVNDFPDF } from './MaterialProperties.js';
|
|
76
73
|
import { RandomValue } from './Random.js';
|
|
77
74
|
import {
|
|
@@ -79,13 +76,12 @@ import {
|
|
|
79
76
|
PI,
|
|
80
77
|
PI_INV,
|
|
81
78
|
EPSILON,
|
|
82
|
-
MIN_PDF,
|
|
83
79
|
powerHeuristic,
|
|
84
80
|
balanceHeuristic,
|
|
81
|
+
computeDotProducts,
|
|
85
82
|
} from './Common.js';
|
|
86
83
|
import {
|
|
87
84
|
sampleEquirectProbability,
|
|
88
|
-
sampleEquirect,
|
|
89
85
|
} from './Environment.js';
|
|
90
86
|
|
|
91
87
|
const TWO_PI = 2.0 * PI;
|
|
@@ -151,7 +147,7 @@ export const sampleRectAreaLight = Fn( ( [ light, rayOrigin, ruv, lightSelection
|
|
|
151
147
|
} );
|
|
152
148
|
|
|
153
149
|
// Enhanced spot light sampling with radius support
|
|
154
|
-
export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin,
|
|
150
|
+
export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin, lightSelectionPdf ] ) => {
|
|
155
151
|
|
|
156
152
|
const ls_valid = tslBool( false ).toVar();
|
|
157
153
|
const ls_direction = vec3( 0.0, 1.0, 0.0 ).toVar();
|
|
@@ -161,11 +157,13 @@ export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin, ruv, lightSel
|
|
|
161
157
|
const ls_lightType = int( LIGHT_TYPE_SPOT ).toVar();
|
|
162
158
|
|
|
163
159
|
const toLight = light.position.sub( rayOrigin ).toVar();
|
|
164
|
-
|
|
160
|
+
// Guard via lengthSq so the sqrt is skipped on rejected (zero-distance) samples.
|
|
161
|
+
const lightDistSq = dot( toLight, toLight ).toVar();
|
|
165
162
|
|
|
166
163
|
// Guard against zero distance
|
|
167
|
-
If(
|
|
164
|
+
If( lightDistSq.greaterThanEqual( 1e-20 ), () => {
|
|
168
165
|
|
|
166
|
+
const lightDist = sqrt( lightDistSq ).toVar();
|
|
169
167
|
const lightDir = toLight.div( lightDist ).toVar();
|
|
170
168
|
|
|
171
169
|
// Check cone attenuation
|
|
@@ -185,7 +183,11 @@ export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin, ruv, lightSel
|
|
|
185
183
|
const coneAttenuation = getSpotAttenuation( { coneCosine: coneCosAngle, penumbraCosine: penumbraCosAngle, angleCosine: spotCosAngle } );
|
|
186
184
|
const distanceAttenuation = getDistanceAttenuation( { lightDistance: lightDist, cutoffDistance: light.distance, decayExponent: light.decay } );
|
|
187
185
|
|
|
188
|
-
|
|
186
|
+
// Gobo projection mask + IES photometric profile — both 1.0 when not assigned.
|
|
187
|
+
const goboMask = sampleSpotGoboMask( light, lightDir );
|
|
188
|
+
const iesProfile = sampleIESProfile( light, lightDir );
|
|
189
|
+
|
|
190
|
+
ls_emission.assign( light.color.mul( light.intensity ).mul( distanceAttenuation ).mul( coneAttenuation ).mul( goboMask ).mul( iesProfile ) );
|
|
189
191
|
|
|
190
192
|
} );
|
|
191
193
|
|
|
@@ -213,11 +215,13 @@ export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSe
|
|
|
213
215
|
const ls_lightType = int( LIGHT_TYPE_POINT ).toVar();
|
|
214
216
|
|
|
215
217
|
const toLight = light.position.sub( rayOrigin ).toVar();
|
|
216
|
-
|
|
218
|
+
// Guard via lengthSq so the sqrt is skipped on rejected (zero-distance) samples.
|
|
219
|
+
const lightDistSq = dot( toLight, toLight ).toVar();
|
|
217
220
|
|
|
218
221
|
// Guard against zero distance
|
|
219
|
-
If(
|
|
222
|
+
If( lightDistSq.greaterThanEqual( 1e-20 ), () => {
|
|
220
223
|
|
|
224
|
+
const lightDist = sqrt( lightDistSq ).toVar();
|
|
221
225
|
const lightDir = toLight.div( lightDist ).toVar();
|
|
222
226
|
|
|
223
227
|
// Calculate distance attenuation using the light's actual distance and decay properties
|
|
@@ -247,8 +251,12 @@ export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSe
|
|
|
247
251
|
// Importance-Weighted Light Sampling
|
|
248
252
|
// =============================================================================
|
|
249
253
|
|
|
250
|
-
//
|
|
251
|
-
//
|
|
254
|
+
// Single-pass weighted-reservoir sampling: each light's importance is evaluated
|
|
255
|
+
// exactly once. Compared to the previous 3-pass CDF (sum-then-walk-then-sample)
|
|
256
|
+
// this halves the importance evaluations and storage-buffer reads per NEE call.
|
|
257
|
+
// Selection rule: with seen_weight = sum of weights so far, replace current
|
|
258
|
+
// winner with this candidate with probability w_i / seen_weight. Result is
|
|
259
|
+
// unbiased; PDF = winnerImportance / totalWeight, identical to the CDF form.
|
|
252
260
|
export const sampleLightWithImportance = Fn( ( [
|
|
253
261
|
rayOrigin, normal, material, randomSeed, bounceIndex, rngState,
|
|
254
262
|
// Light buffers + counts
|
|
@@ -273,8 +281,13 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
273
281
|
const totalWeight = float( 0.0 ).toVar();
|
|
274
282
|
const lightIndex = int( 0 ).toVar();
|
|
275
283
|
|
|
284
|
+
// Reservoir state: winning light's type/index/importance.
|
|
285
|
+
const selectedType = int( - 1 ).toVar(); // 0=dir, 1=area, 2=point, 3=spot
|
|
286
|
+
const selectedIdx = int( - 1 ).toVar();
|
|
287
|
+
const selectedImportance = float( 0.0 ).toVar();
|
|
288
|
+
|
|
276
289
|
// =====================================================================
|
|
277
|
-
// PASS
|
|
290
|
+
// SINGLE PASS: reservoir-sample across all four light type buffers
|
|
278
291
|
// =====================================================================
|
|
279
292
|
|
|
280
293
|
If( numDirectionalLights.greaterThan( int( 0 ) ), () => {
|
|
@@ -284,7 +297,17 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
284
297
|
If( lightIndex.lessThan( int( 16 ) ), () => {
|
|
285
298
|
|
|
286
299
|
const light = DirectionalLight.wrap( getDirectionalLight( directionalLightsBuffer, i ) );
|
|
287
|
-
|
|
300
|
+
const importance = calculateDirectionalLightImportance( light, normal, material, bounceIndex ).toVar();
|
|
301
|
+
totalWeight.addAssign( importance );
|
|
302
|
+
If( importance.greaterThan( 0.0 ).and(
|
|
303
|
+
RandomValue( rngState ).mul( totalWeight ).lessThan( importance )
|
|
304
|
+
), () => {
|
|
305
|
+
|
|
306
|
+
selectedType.assign( 0 );
|
|
307
|
+
selectedIdx.assign( i );
|
|
308
|
+
selectedImportance.assign( importance );
|
|
309
|
+
|
|
310
|
+
} );
|
|
288
311
|
lightIndex.addAssign( 1 );
|
|
289
312
|
|
|
290
313
|
} );
|
|
@@ -300,8 +323,17 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
300
323
|
If( lightIndex.lessThan( int( 16 ) ), () => {
|
|
301
324
|
|
|
302
325
|
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, i ) );
|
|
303
|
-
const importance = select( light.intensity.greaterThan( 0.0 ), estimateLightImportance( light, rayOrigin, normal, material ), float( 0.0 ) );
|
|
326
|
+
const importance = select( light.intensity.greaterThan( 0.0 ), estimateLightImportance( light, rayOrigin, normal, material ), float( 0.0 ) ).toVar();
|
|
304
327
|
totalWeight.addAssign( importance );
|
|
328
|
+
If( importance.greaterThan( 0.0 ).and(
|
|
329
|
+
RandomValue( rngState ).mul( totalWeight ).lessThan( importance )
|
|
330
|
+
), () => {
|
|
331
|
+
|
|
332
|
+
selectedType.assign( 1 );
|
|
333
|
+
selectedIdx.assign( i );
|
|
334
|
+
selectedImportance.assign( importance );
|
|
335
|
+
|
|
336
|
+
} );
|
|
305
337
|
lightIndex.addAssign( 1 );
|
|
306
338
|
|
|
307
339
|
} );
|
|
@@ -317,7 +349,17 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
317
349
|
If( lightIndex.lessThan( int( 16 ) ), () => {
|
|
318
350
|
|
|
319
351
|
const light = PointLight.wrap( getPointLight( pointLightsBuffer, i ) );
|
|
320
|
-
|
|
352
|
+
const importance = calculatePointLightImportance( light, rayOrigin, normal, material ).toVar();
|
|
353
|
+
totalWeight.addAssign( importance );
|
|
354
|
+
If( importance.greaterThan( 0.0 ).and(
|
|
355
|
+
RandomValue( rngState ).mul( totalWeight ).lessThan( importance )
|
|
356
|
+
), () => {
|
|
357
|
+
|
|
358
|
+
selectedType.assign( 2 );
|
|
359
|
+
selectedIdx.assign( i );
|
|
360
|
+
selectedImportance.assign( importance );
|
|
361
|
+
|
|
362
|
+
} );
|
|
321
363
|
lightIndex.addAssign( 1 );
|
|
322
364
|
|
|
323
365
|
} );
|
|
@@ -333,7 +375,17 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
333
375
|
If( lightIndex.lessThan( int( 16 ) ), () => {
|
|
334
376
|
|
|
335
377
|
const light = SpotLight.wrap( getSpotLight( spotLightsBuffer, i ) );
|
|
336
|
-
|
|
378
|
+
const importance = calculateSpotLightImportance( light, rayOrigin, normal, material ).toVar();
|
|
379
|
+
totalWeight.addAssign( importance );
|
|
380
|
+
If( importance.greaterThan( 0.0 ).and(
|
|
381
|
+
RandomValue( rngState ).mul( totalWeight ).lessThan( importance )
|
|
382
|
+
), () => {
|
|
383
|
+
|
|
384
|
+
selectedType.assign( 3 );
|
|
385
|
+
selectedIdx.assign( i );
|
|
386
|
+
selectedImportance.assign( importance );
|
|
387
|
+
|
|
388
|
+
} );
|
|
337
389
|
lightIndex.addAssign( 1 );
|
|
338
390
|
|
|
339
391
|
} );
|
|
@@ -366,8 +418,9 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
366
418
|
|
|
367
419
|
If( light.intensity.greaterThan( 0.0 ), () => {
|
|
368
420
|
|
|
421
|
+
const dirGoboMask = sampleDirectionalGoboMask( light, rayOrigin );
|
|
369
422
|
r_direction.assign( normalize( light.direction ) );
|
|
370
|
-
r_emission.assign( light.color.mul( light.intensity ) );
|
|
423
|
+
r_emission.assign( light.color.mul( light.intensity ).mul( dirGoboMask ) );
|
|
371
424
|
r_distance.assign( 1e6 );
|
|
372
425
|
r_lightType.assign( int( LIGHT_TYPE_DIRECTIONAL ) );
|
|
373
426
|
r_valid.assign( tslBool( true ) );
|
|
@@ -440,8 +493,7 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
440
493
|
|
|
441
494
|
If( light.intensity.greaterThan( 0.0 ), () => {
|
|
442
495
|
|
|
443
|
-
const
|
|
444
|
-
const spotSample = LightSample.wrap( sampleSpotLightWithRadius( light, rayOrigin, uv, lightSelectionPdf ) );
|
|
496
|
+
const spotSample = LightSample.wrap( sampleSpotLightWithRadius( light, rayOrigin, lightSelectionPdf ) );
|
|
445
497
|
r_valid.assign( spotSample.valid );
|
|
446
498
|
r_direction.assign( spotSample.direction );
|
|
447
499
|
r_emission.assign( spotSample.emission );
|
|
@@ -459,128 +511,8 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
459
511
|
} ).Else( () => {
|
|
460
512
|
|
|
461
513
|
// =================================================================
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
const selectionValue = randomSeed.x.mul( totalWeight ).toVar();
|
|
466
|
-
const cumulative = float( 0.0 ).toVar();
|
|
467
|
-
lightIndex.assign( 0 );
|
|
468
|
-
|
|
469
|
-
// Track which light was selected
|
|
470
|
-
const selectedType = int( - 1 ).toVar(); // 0=dir, 1=area, 2=point, 3=spot
|
|
471
|
-
const selectedIdx = int( - 1 ).toVar();
|
|
472
|
-
const selectedImportance = float( 0.0 ).toVar();
|
|
473
|
-
|
|
474
|
-
// Directional lights
|
|
475
|
-
If( numDirectionalLights.greaterThan( int( 0 ) ), () => {
|
|
476
|
-
|
|
477
|
-
Loop( { start: int( 0 ), end: numDirectionalLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
478
|
-
|
|
479
|
-
If( lightIndex.lessThan( int( 16 ) ).and( selectedType.lessThan( int( 0 ) ) ), () => {
|
|
480
|
-
|
|
481
|
-
const light = DirectionalLight.wrap( getDirectionalLight( directionalLightsBuffer, i ) );
|
|
482
|
-
const importance = calculateDirectionalLightImportance( light, rayOrigin, normal, material, bounceIndex ).toVar();
|
|
483
|
-
const prevCumulative = cumulative.toVar();
|
|
484
|
-
cumulative.addAssign( importance );
|
|
485
|
-
|
|
486
|
-
If( selectionValue.greaterThan( prevCumulative ).and( selectionValue.lessThanEqual( cumulative ) ), () => {
|
|
487
|
-
|
|
488
|
-
selectedType.assign( 0 );
|
|
489
|
-
selectedIdx.assign( i );
|
|
490
|
-
selectedImportance.assign( importance );
|
|
491
|
-
|
|
492
|
-
} );
|
|
493
|
-
|
|
494
|
-
} );
|
|
495
|
-
lightIndex.addAssign( 1 );
|
|
496
|
-
|
|
497
|
-
} );
|
|
498
|
-
|
|
499
|
-
} );
|
|
500
|
-
|
|
501
|
-
// Area lights
|
|
502
|
-
If( numAreaLights.greaterThan( int( 0 ) ), () => {
|
|
503
|
-
|
|
504
|
-
Loop( { start: int( 0 ), end: numAreaLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
505
|
-
|
|
506
|
-
If( lightIndex.lessThan( int( 16 ) ).and( selectedType.lessThan( int( 0 ) ) ), () => {
|
|
507
|
-
|
|
508
|
-
const light = AreaLight.wrap( getAreaLight( areaLightsBuffer, i ) );
|
|
509
|
-
const importance = select( light.intensity.greaterThan( 0.0 ), estimateLightImportance( light, rayOrigin, normal, material ), float( 0.0 ) ).toVar();
|
|
510
|
-
const prevCumulative = cumulative.toVar();
|
|
511
|
-
cumulative.addAssign( importance );
|
|
512
|
-
|
|
513
|
-
If( selectionValue.greaterThan( prevCumulative ).and( selectionValue.lessThanEqual( cumulative ) ), () => {
|
|
514
|
-
|
|
515
|
-
selectedType.assign( 1 );
|
|
516
|
-
selectedIdx.assign( i );
|
|
517
|
-
selectedImportance.assign( importance );
|
|
518
|
-
|
|
519
|
-
} );
|
|
520
|
-
|
|
521
|
-
} );
|
|
522
|
-
lightIndex.addAssign( 1 );
|
|
523
|
-
|
|
524
|
-
} );
|
|
525
|
-
|
|
526
|
-
} );
|
|
527
|
-
|
|
528
|
-
// Point lights
|
|
529
|
-
If( numPointLights.greaterThan( int( 0 ) ), () => {
|
|
530
|
-
|
|
531
|
-
Loop( { start: int( 0 ), end: numPointLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
532
|
-
|
|
533
|
-
If( lightIndex.lessThan( int( 16 ) ).and( selectedType.lessThan( int( 0 ) ) ), () => {
|
|
534
|
-
|
|
535
|
-
const light = PointLight.wrap( getPointLight( pointLightsBuffer, i ) );
|
|
536
|
-
const importance = calculatePointLightImportance( light, rayOrigin, normal, material ).toVar();
|
|
537
|
-
const prevCumulative = cumulative.toVar();
|
|
538
|
-
cumulative.addAssign( importance );
|
|
539
|
-
|
|
540
|
-
If( selectionValue.greaterThan( prevCumulative ).and( selectionValue.lessThanEqual( cumulative ) ), () => {
|
|
541
|
-
|
|
542
|
-
selectedType.assign( 2 );
|
|
543
|
-
selectedIdx.assign( i );
|
|
544
|
-
selectedImportance.assign( importance );
|
|
545
|
-
|
|
546
|
-
} );
|
|
547
|
-
|
|
548
|
-
} );
|
|
549
|
-
lightIndex.addAssign( 1 );
|
|
550
|
-
|
|
551
|
-
} );
|
|
552
|
-
|
|
553
|
-
} );
|
|
554
|
-
|
|
555
|
-
// Spot lights
|
|
556
|
-
If( numSpotLights.greaterThan( int( 0 ) ), () => {
|
|
557
|
-
|
|
558
|
-
Loop( { start: int( 0 ), end: numSpotLights, type: 'int', condition: '<' }, ( { i } ) => {
|
|
559
|
-
|
|
560
|
-
If( lightIndex.lessThan( int( 16 ) ).and( selectedType.lessThan( int( 0 ) ) ), () => {
|
|
561
|
-
|
|
562
|
-
const light = SpotLight.wrap( getSpotLight( spotLightsBuffer, i ) );
|
|
563
|
-
const importance = calculateSpotLightImportance( light, rayOrigin, normal, material ).toVar();
|
|
564
|
-
const prevCumulative = cumulative.toVar();
|
|
565
|
-
cumulative.addAssign( importance );
|
|
566
|
-
|
|
567
|
-
If( selectionValue.greaterThan( prevCumulative ).and( selectionValue.lessThanEqual( cumulative ) ), () => {
|
|
568
|
-
|
|
569
|
-
selectedType.assign( 3 );
|
|
570
|
-
selectedIdx.assign( i );
|
|
571
|
-
selectedImportance.assign( importance );
|
|
572
|
-
|
|
573
|
-
} );
|
|
574
|
-
|
|
575
|
-
} );
|
|
576
|
-
lightIndex.addAssign( 1 );
|
|
577
|
-
|
|
578
|
-
} );
|
|
579
|
-
|
|
580
|
-
} );
|
|
581
|
-
|
|
582
|
-
// =================================================================
|
|
583
|
-
// PASS 3: Sample the selected light (outside loops)
|
|
514
|
+
// Sample the reservoir-selected light. selectedType / selectedIdx /
|
|
515
|
+
// selectedImportance were populated during the single-pass walk above.
|
|
584
516
|
// =================================================================
|
|
585
517
|
|
|
586
518
|
// Guard division by zero
|
|
@@ -617,8 +549,9 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
617
549
|
|
|
618
550
|
} );
|
|
619
551
|
|
|
552
|
+
const dirGoboMask = sampleDirectionalGoboMask( light, rayOrigin );
|
|
620
553
|
r_direction.assign( direction );
|
|
621
|
-
r_emission.assign( light.color.mul( light.intensity ) );
|
|
554
|
+
r_emission.assign( light.color.mul( light.intensity ).mul( dirGoboMask ) );
|
|
622
555
|
r_distance.assign( 1e6 );
|
|
623
556
|
r_pdf.assign( dirPdf.mul( pdf ) );
|
|
624
557
|
r_lightType.assign( int( LIGHT_TYPE_DIRECTIONAL ) );
|
|
@@ -659,8 +592,7 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
659
592
|
If( selectedType.equal( int( 3 ) ).and( selectedIdx.greaterThanEqual( int( 0 ) ) ), () => {
|
|
660
593
|
|
|
661
594
|
const light = SpotLight.wrap( getSpotLight( spotLightsBuffer, selectedIdx ) );
|
|
662
|
-
const
|
|
663
|
-
const spotSample = LightSample.wrap( sampleSpotLightWithRadius( light, rayOrigin, uv, pdf ) );
|
|
595
|
+
const spotSample = LightSample.wrap( sampleSpotLightWithRadius( light, rayOrigin, pdf ) );
|
|
664
596
|
r_valid.assign( spotSample.valid );
|
|
665
597
|
r_direction.assign( spotSample.direction );
|
|
666
598
|
r_emission.assign( spotSample.emission );
|
|
@@ -689,14 +621,14 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
689
621
|
// Material PDF Calculation for MIS
|
|
690
622
|
// =============================================================================
|
|
691
623
|
|
|
692
|
-
//
|
|
693
|
-
|
|
624
|
+
// PDF computation given precomputed dot products. Use this when the caller
|
|
625
|
+
// already has dots from a paired evaluateMaterialResponseFromDots invocation
|
|
626
|
+
// to avoid recomputing H + dots.
|
|
627
|
+
export const calculateMaterialPDFFromDots = Fn( ( [ material, dots ] ) => {
|
|
694
628
|
|
|
695
|
-
const NoV =
|
|
696
|
-
const NoL =
|
|
697
|
-
const
|
|
698
|
-
const NoH = max( float( 0.0 ), dot( normal, H ) ).toVar();
|
|
699
|
-
const VoH = max( float( 0.0 ), dot( viewDir, H ) ).toVar();
|
|
629
|
+
const NoV = dots.NoV.toVar();
|
|
630
|
+
const NoL = dots.NoL.toVar();
|
|
631
|
+
const NoH = dots.NoH.toVar();
|
|
700
632
|
|
|
701
633
|
// Calculate lobe weights
|
|
702
634
|
const diffuseWeight = float( 1.0 ).sub( material.metalness ).mul(
|
|
@@ -739,6 +671,15 @@ export const calculateMaterialPDF = Fn( ( [ viewDir, lightDir, normal, material
|
|
|
739
671
|
|
|
740
672
|
} );
|
|
741
673
|
|
|
674
|
+
// Wrapper that computes dots internally. Use this when the caller doesn't
|
|
675
|
+
// already have dots; otherwise prefer calculateMaterialPDFFromDots.
|
|
676
|
+
export const calculateMaterialPDF = Fn( ( [ viewDir, lightDir, normal, material ] ) => {
|
|
677
|
+
|
|
678
|
+
const dots = DotProducts.wrap( computeDotProducts( normal, viewDir, lightDir ) );
|
|
679
|
+
return calculateMaterialPDFFromDots( material, dots );
|
|
680
|
+
|
|
681
|
+
} );
|
|
682
|
+
|
|
742
683
|
// =============================================================================
|
|
743
684
|
// Unified Direct Lighting System
|
|
744
685
|
// =============================================================================
|
|
@@ -752,7 +693,7 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
752
693
|
// BRDF sample (DirectionSample fields)
|
|
753
694
|
brdfSampleDirection, brdfSamplePdf, brdfSampleValue,
|
|
754
695
|
// Tracing context
|
|
755
|
-
|
|
696
|
+
bounceIndex, rngState,
|
|
756
697
|
// Light data
|
|
757
698
|
directionalLightsBuffer, numDirectionalLights,
|
|
758
699
|
areaLightsBuffer, numAreaLights,
|
|
@@ -772,13 +713,18 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
772
713
|
const totalContribution = vec3( 0.0 ).toVar();
|
|
773
714
|
const rayOrigin = hitPoint.add( hitNormal.mul( 0.001 ) ).toVar();
|
|
774
715
|
|
|
716
|
+
// Binds BVH params so shadow-ray sites at varying call depths use a 3-arg call
|
|
717
|
+
const shadow = Fn( ( [ origin, dir, maxDist ] ) =>
|
|
718
|
+
traceShadowRay( origin, dir, maxDist, traverseBVHShadow, bvhBuffer, triangleBuffer, materialBuffer )
|
|
719
|
+
);
|
|
720
|
+
|
|
775
721
|
// Early exit for highly emissive surfaces
|
|
776
722
|
If( material.emissiveIntensity.lessThanEqual( 10.0 ), () => {
|
|
777
723
|
|
|
778
724
|
// Adaptive MIS Strategy Selection
|
|
779
725
|
const currentThroughput = vec3( 1.0 ).toVar();
|
|
780
726
|
const misResult = MISStrategy.wrap( selectOptimalMISStrategy(
|
|
781
|
-
material.roughness, material.metalness,
|
|
727
|
+
material.roughness, material.metalness, bounceIndex, currentThroughput
|
|
782
728
|
) );
|
|
783
729
|
|
|
784
730
|
// Extract MIS fields to mutable variables
|
|
@@ -872,18 +818,15 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
872
818
|
If( NoL.greaterThan( 0.0 ).and( lightImportance.mul( NoL ).greaterThan( importanceThreshold ) ).and( isDirectionValid( { direction: lightSample.direction, surfaceNormal: hitNormal } ) ), () => {
|
|
873
819
|
|
|
874
820
|
const shadowDistance = min( lightSample.distance.sub( 0.001 ), float( 1000.0 ) ).toVar();
|
|
875
|
-
const visibility =
|
|
876
|
-
rayOrigin, lightSample.direction, shadowDistance, rngState,
|
|
877
|
-
traverseBVHShadow,
|
|
878
|
-
bvhBuffer,
|
|
879
|
-
triangleBuffer,
|
|
880
|
-
materialBuffer,
|
|
881
|
-
);
|
|
821
|
+
const visibility = shadow( rayOrigin, lightSample.direction, shadowDistance );
|
|
882
822
|
|
|
883
823
|
If( visibility.greaterThan( 0.0 ), () => {
|
|
884
824
|
|
|
885
|
-
|
|
886
|
-
|
|
825
|
+
// Share H + dot products between BRDF eval and PDF — otherwise each
|
|
826
|
+
// would recompute normalize(V+L) + 5 dot products independently.
|
|
827
|
+
const sharedDots = DotProducts.wrap( computeDotProducts( hitNormal, viewDir, lightSample.direction ) );
|
|
828
|
+
const brdfValue = evaluateMaterialResponseFromDots( material, sharedDots );
|
|
829
|
+
const bPdf = calculateMaterialPDFFromDots( material, sharedDots ).toVar();
|
|
887
830
|
|
|
888
831
|
const misW = float( 1.0 ).toVar();
|
|
889
832
|
|
|
@@ -974,13 +917,7 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
974
917
|
If( hitDistance.greaterThan( 0.0 ), () => {
|
|
975
918
|
|
|
976
919
|
const shadowDistance = min( hitDistance.sub( 0.001 ), float( 1000.0 ) ).toVar();
|
|
977
|
-
const visibility =
|
|
978
|
-
rayOrigin, brdfSampleDirection, shadowDistance, rngState,
|
|
979
|
-
traverseBVHShadow,
|
|
980
|
-
bvhBuffer,
|
|
981
|
-
triangleBuffer,
|
|
982
|
-
materialBuffer,
|
|
983
|
-
);
|
|
920
|
+
const visibility = shadow( rayOrigin, brdfSampleDirection, shadowDistance );
|
|
984
921
|
|
|
985
922
|
If( visibility.greaterThan( 0.0 ), () => {
|
|
986
923
|
|
|
@@ -1046,18 +983,15 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1046
983
|
|
|
1047
984
|
If( NoL.greaterThan( 0.0 ).and( isDirectionValid( { direction: envDirection, surfaceNormal: hitNormal } ) ), () => {
|
|
1048
985
|
|
|
1049
|
-
const visibility =
|
|
1050
|
-
rayOrigin, envDirection, float( 1000.0 ), rngState,
|
|
1051
|
-
traverseBVHShadow,
|
|
1052
|
-
bvhBuffer,
|
|
1053
|
-
triangleBuffer,
|
|
1054
|
-
materialBuffer,
|
|
1055
|
-
);
|
|
986
|
+
const visibility = shadow( rayOrigin, envDirection, float( 1000.0 ) );
|
|
1056
987
|
|
|
1057
988
|
If( visibility.greaterThan( 0.0 ), () => {
|
|
1058
989
|
|
|
1059
|
-
|
|
1060
|
-
|
|
990
|
+
// Share H + dots between env BRDF/PDF — same redundancy fix as the
|
|
991
|
+
// discrete-light path above.
|
|
992
|
+
const envDots = DotProducts.wrap( computeDotProducts( hitNormal, viewDir, envDirection ) );
|
|
993
|
+
const brdfValue = evaluateMaterialResponseFromDots( material, envDots );
|
|
994
|
+
const bPdf = calculateMaterialPDFFromDots( material, envDots ).toVar();
|
|
1061
995
|
|
|
1062
996
|
// Balance heuristic for env MIS — optimal for MIS-compensated PDFs (Karlík et al. 2019).
|
|
1063
997
|
// The implicit path uses material combinedPdf as prevBouncePdf at the miss check.
|
|
@@ -3,14 +3,14 @@ import {
|
|
|
3
3
|
If, max, min, clamp, mix
|
|
4
4
|
} from 'three/tsl';
|
|
5
5
|
|
|
6
|
-
import { DotProducts } from './Struct.js';
|
|
6
|
+
import { DotProducts, DFGResult } from './Struct.js';
|
|
7
7
|
import {
|
|
8
8
|
PI, PI_INV, EPSILON, MIN_CLEARCOAT_ROUGHNESS,
|
|
9
9
|
computeDotProducts,
|
|
10
10
|
} from './Common.js';
|
|
11
11
|
import { fresnelSchlick, fresnelSchlickFloat, dielectricF0 } from './Fresnel.js';
|
|
12
12
|
import {
|
|
13
|
-
DistributionGGX, SheenDistribution, GeometrySmith,
|
|
13
|
+
DistributionGGX, SheenDistribution, GeometrySmith, evaluateDFG,
|
|
14
14
|
} from './MaterialProperties.js';
|
|
15
15
|
import { evalIridescence } from './MaterialProperties.js';
|
|
16
16
|
|
|
@@ -22,7 +22,10 @@ import { evalIridescence } from './MaterialProperties.js';
|
|
|
22
22
|
// Main Material Response Evaluation
|
|
23
23
|
// -----------------------------------------------------------------------------
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
// Body of evaluateMaterialResponse taking precomputed dot products. Callers
|
|
26
|
+
// that also need calculateMaterialPDF for the same (V, L, N) should share dots
|
|
27
|
+
// to save one computeDotProducts call.
|
|
28
|
+
export const evaluateMaterialResponseFromDots = Fn( ( [ material, dots ] ) => {
|
|
26
29
|
|
|
27
30
|
const result = vec3( 0.0 ).toVar();
|
|
28
31
|
|
|
@@ -37,9 +40,6 @@ export const evaluateMaterialResponse = Fn( ( [ V, L, N, material ] ) => {
|
|
|
37
40
|
|
|
38
41
|
} ).Else( () => {
|
|
39
42
|
|
|
40
|
-
// Calculate all dot products once
|
|
41
|
-
const dots = DotProducts.wrap( computeDotProducts( N, V, L ) );
|
|
42
|
-
|
|
43
43
|
// Calculate base F0 with specular parameters, clamped to physically valid range
|
|
44
44
|
const F0 = clamp(
|
|
45
45
|
mix( dielectricF0( material.ior ).mul( material.specularColor ), material.color.rgb, material.metalness )
|
|
@@ -82,12 +82,13 @@ export const evaluateMaterialResponse = Fn( ( [ V, L, N, material ] ) => {
|
|
|
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 ) );
|
|
84
84
|
|
|
85
|
-
//
|
|
86
|
-
|
|
85
|
+
// Shared DFG evaluation — compensation factor and total directional albedo
|
|
86
|
+
// come from the same polynomial.
|
|
87
|
+
const dfg = DFGResult.wrap( evaluateDFG( F0, dots.NoV, material.roughness ) );
|
|
88
|
+
const specular = specularSS.mul( dfg.compensation );
|
|
87
89
|
|
|
88
90
|
// Diffuse energy budget from hemisphere-integrated specular albedo (includes multiscatter)
|
|
89
|
-
const
|
|
90
|
-
const kD = vec3( 1.0 ).sub( E_total ).mul( float( 1.0 ).sub( material.metalness ) );
|
|
91
|
+
const kD = vec3( 1.0 ).sub( dfg.E_total ).mul( float( 1.0 ).sub( material.metalness ) );
|
|
91
92
|
const diffuse = kD.mul( materialColor ).mul( PI_INV );
|
|
92
93
|
|
|
93
94
|
const baseLayer = diffuse.add( specular ).toVar();
|
|
@@ -118,6 +119,15 @@ export const evaluateMaterialResponse = Fn( ( [ V, L, N, material ] ) => {
|
|
|
118
119
|
|
|
119
120
|
} );
|
|
120
121
|
|
|
122
|
+
// Wrapper that computes dot products internally. Use this when you don't already
|
|
123
|
+
// have dots; otherwise prefer evaluateMaterialResponseFromDots to share the work.
|
|
124
|
+
export const evaluateMaterialResponse = Fn( ( [ V, L, N, material ] ) => {
|
|
125
|
+
|
|
126
|
+
const dots = DotProducts.wrap( computeDotProducts( N, V, L ) );
|
|
127
|
+
return evaluateMaterialResponseFromDots( material, dots );
|
|
128
|
+
|
|
129
|
+
} );
|
|
130
|
+
|
|
121
131
|
// -----------------------------------------------------------------------------
|
|
122
132
|
// Layered BRDF Evaluation (for clearcoat)
|
|
123
133
|
// -----------------------------------------------------------------------------
|
|
@@ -138,12 +148,13 @@ export const evaluateLayeredBRDF = Fn( ( [ dots, material ] ) => {
|
|
|
138
148
|
const F = fresnelSchlick( dots.VoH, F0 ).toVar();
|
|
139
149
|
const baseBRDFSS = D.mul( G ).mul( F ).div( max( float( 4.0 ).mul( dots.NoV ).mul( dots.NoL ), EPSILON ) );
|
|
140
150
|
|
|
141
|
-
//
|
|
142
|
-
|
|
151
|
+
// Shared DFG evaluation — compensation factor and total directional albedo
|
|
152
|
+
// come from the same polynomial.
|
|
153
|
+
const dfg = DFGResult.wrap( evaluateDFG( F0, dots.NoV, material.roughness ) );
|
|
154
|
+
const baseBRDF = baseBRDFSS.mul( dfg.compensation );
|
|
143
155
|
|
|
144
156
|
// Diffuse energy budget from hemisphere-integrated specular albedo (includes multiscatter)
|
|
145
|
-
const
|
|
146
|
-
const kD = vec3( 1.0 ).sub( E_total ).mul( float( 1.0 ).sub( material.metalness ) );
|
|
157
|
+
const kD = vec3( 1.0 ).sub( dfg.E_total ).mul( float( 1.0 ).sub( material.metalness ) );
|
|
147
158
|
const diffuse = kD.mul( material.color.rgb ).div( PI );
|
|
148
159
|
const baseLayer = diffuse.add( baseBRDF );
|
|
149
160
|
|