rayzee 6.5.0 → 7.1.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 +24 -5
- package/dist/rayzee.es.js +7624 -7063
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +157 -236
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +26 -9
- package/src/PathTracerApp.js +118 -26
- package/src/Pipeline/PipelineContext.js +1 -2
- package/src/Pipeline/RenderPipeline.js +1 -1
- package/src/Pipeline/RenderStage.js +1 -1
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/GeometryExtractor.js +6 -0
- package/src/Processor/KernelManager.js +277 -0
- package/src/Processor/PackedRayBuffer.js +291 -0
- package/src/Processor/QueueManager.js +173 -0
- package/src/Processor/SceneProcessor.js +1 -0
- package/src/Processor/ShaderBuilder.js +11 -317
- package/src/Processor/StorageTexturePool.js +29 -15
- package/src/Processor/VRAMTracker.js +169 -0
- package/src/Processor/utils.js +11 -110
- package/src/RenderSettings.js +0 -3
- package/src/Stages/ASVGF.js +151 -78
- package/src/Stages/BilateralFilter.js +34 -10
- package/src/Stages/EdgeFilter.js +2 -3
- package/src/Stages/MotionVector.js +16 -9
- package/src/Stages/NormalDepth.js +17 -5
- package/src/Stages/PathTracer.js +671 -1456
- package/src/Stages/PathTracerStage.js +1451 -0
- package/src/Stages/SSRC.js +32 -15
- package/src/Stages/Variance.js +35 -12
- package/src/TSL/CompactKernel.js +110 -0
- package/src/TSL/DebugKernel.js +98 -0
- package/src/TSL/Environment.js +13 -11
- package/src/TSL/ExtendKernel.js +75 -0
- package/src/TSL/FinalWriteKernel.js +121 -0
- package/src/TSL/GenerateKernel.js +111 -0
- package/src/TSL/LightsSampling.js +2 -2
- package/src/TSL/PathTracerCore.js +43 -1039
- package/src/TSL/ShadeKernel.js +876 -0
- package/src/TSL/patches.js +81 -4
- package/src/index.js +3 -0
- package/src/managers/CameraManager.js +1 -1
- package/src/managers/DenoisingManager.js +40 -75
- package/src/managers/EnvironmentManager.js +30 -39
- package/src/managers/OverlayManager.js +7 -22
- package/src/managers/UniformManager.js +0 -3
- package/src/managers/helpers/TileHelper.js +2 -2
- package/src/Stages/AdaptiveSampling.js +0 -483
- package/src/TSL/PathTracer.js +0 -384
- package/src/managers/TileManager.js +0 -298
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PathTracerCore.js -
|
|
2
|
+
* PathTracerCore.js — shared path-tracing sampling helpers (TSL).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* - generateSampledDirection — BRDF direction sampling with multi-lobe CDF
|
|
10
|
-
* - handleRussianRoulette — adaptive path termination (inlines path importance)
|
|
11
|
-
* - sampleBackgroundLighting — environment background sampling
|
|
12
|
-
* - regularizePathContribution — firefly suppression
|
|
13
|
-
* - Trace — main path tracing loop
|
|
4
|
+
* Imported by the wavefront kernels (GenerateKernel / ShadeKernel):
|
|
5
|
+
* - generateSampledDirection — BRDF direction sampling with multi-lobe CDF
|
|
6
|
+
* - regularizePathContribution — firefly suppression
|
|
7
|
+
* - computeNDCDepth — world position → NDC depth [0,1]
|
|
8
|
+
* - handleRussianRoulette — adaptive path termination
|
|
14
9
|
*/
|
|
15
10
|
|
|
16
11
|
import {
|
|
@@ -19,136 +14,43 @@ import {
|
|
|
19
14
|
float,
|
|
20
15
|
vec2,
|
|
21
16
|
vec3,
|
|
22
|
-
vec4,
|
|
23
17
|
int,
|
|
24
|
-
bool as tslBool,
|
|
25
18
|
max,
|
|
26
19
|
min,
|
|
27
|
-
exp,
|
|
28
|
-
log,
|
|
29
20
|
clamp,
|
|
30
|
-
mix,
|
|
31
21
|
dot,
|
|
32
|
-
normalize,
|
|
33
|
-
length,
|
|
34
22
|
reflect,
|
|
35
23
|
If,
|
|
36
|
-
|
|
37
|
-
Break,
|
|
38
|
-
Continue,
|
|
39
|
-
select,
|
|
24
|
+
mix,
|
|
40
25
|
smoothstep,
|
|
41
|
-
|
|
26
|
+
exp,
|
|
27
|
+
select,
|
|
42
28
|
} from 'three/tsl';
|
|
43
29
|
|
|
44
|
-
import { struct } from './patches.js';
|
|
45
|
-
|
|
46
30
|
import {
|
|
47
31
|
PI_INV,
|
|
48
|
-
MIN_ROUGHNESS,
|
|
49
32
|
MAX_ROUGHNESS,
|
|
50
33
|
MIN_CLEARCOAT_ROUGHNESS,
|
|
51
34
|
MIN_PDF,
|
|
52
|
-
maxComponent,
|
|
53
|
-
classifyMaterial,
|
|
54
35
|
constructTBN,
|
|
55
36
|
calculateFireflyThreshold,
|
|
56
37
|
applySoftSuppressionRGB,
|
|
57
|
-
getMaterial,
|
|
58
|
-
powerHeuristic,
|
|
59
|
-
balanceHeuristic,
|
|
60
|
-
computeDotProducts,
|
|
61
38
|
} from './Common.js';
|
|
62
|
-
import {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
MaterialCache,
|
|
66
|
-
BRDFWeights,
|
|
67
|
-
Ray,
|
|
68
|
-
HitInfo,
|
|
69
|
-
MaterialSamples,
|
|
70
|
-
RayTracingMaterial,
|
|
71
|
-
ImportanceSamplingInfo,
|
|
72
|
-
DotProducts,
|
|
73
|
-
} from './Struct.js';
|
|
74
|
-
import { RandomValue, getRandomSample } from './Random.js';
|
|
75
|
-
import { traverseBVH } from './BVHTraversal.js';
|
|
76
|
-
import { sampleEnvironment, sampleEquirect, getGroundProjectedDirection } from './Environment.js';
|
|
77
|
-
import { sampleAllMaterialTextures } from './TextureSampling.js';
|
|
78
|
-
import { refineDisplacedIntersection, DisplacementResult } from './Displacement.js';
|
|
79
|
-
import { handleMaterialTransparency, MaterialInteractionResult, sampleMicrofacetTransmission, MicrofacetTransmissionResult } from './MaterialTransmission.js';
|
|
80
|
-
import { subsurfaceCoefficients, sampleChromaticCollision, sampleHenyeyGreenstein, MediumCoeffs, CollisionSample } from './Subsurface.js';
|
|
39
|
+
import { DirectionSample, MaterialCache } from './Struct.js';
|
|
40
|
+
import { RandomValue } from './Random.js';
|
|
41
|
+
import { sampleMicrofacetTransmission, MicrofacetTransmissionResult } from './MaterialTransmission.js';
|
|
81
42
|
import {
|
|
82
43
|
SheenDistribution,
|
|
83
44
|
calculateVNDFPDF,
|
|
84
45
|
calculateBRDFWeights,
|
|
85
|
-
createMaterialCache,
|
|
86
|
-
getImportanceSamplingInfo,
|
|
87
46
|
} from './MaterialProperties.js';
|
|
88
|
-
import { evaluateMaterialResponse
|
|
47
|
+
import { evaluateMaterialResponse } from './MaterialEvaluation.js';
|
|
89
48
|
import { dielectricF0 } from './Fresnel.js';
|
|
90
49
|
import {
|
|
91
50
|
ImportanceSampleCosine,
|
|
92
51
|
ImportanceSampleGGX,
|
|
93
52
|
sampleGGXVNDF,
|
|
94
53
|
} from './MaterialSampling.js';
|
|
95
|
-
import { sampleClearcoat, ClearcoatResult } from './Clearcoat.js';
|
|
96
|
-
import { calculateDirectLightingUnified, calculateMaterialPDFFromDots } from './LightsSampling.js';
|
|
97
|
-
import { calculateIndirectLighting } from './LightsIndirect.js';
|
|
98
|
-
import { IndirectLightingResult } from './LightsCore.js';
|
|
99
|
-
import { calculateEmissiveTriangleContribution, calculateEmissiveLightPdf, EmissiveSample } from './EmissiveSampling.js';
|
|
100
|
-
import { sampleLightBVHTriangle } from './LightBVHSampling.js';
|
|
101
|
-
import { traceShadowRay, calculateRayOffset } from './LightsDirect.js';
|
|
102
|
-
import { traverseBVHShadow } from './BVHTraversal.js';
|
|
103
|
-
|
|
104
|
-
// =============================================================================
|
|
105
|
-
// Constants
|
|
106
|
-
// =============================================================================
|
|
107
|
-
|
|
108
|
-
// Ray type enumeration
|
|
109
|
-
const RAY_TYPE_CAMERA = 0;
|
|
110
|
-
const RAY_TYPE_REFLECTION = 1;
|
|
111
|
-
const RAY_TYPE_TRANSMISSION = 2;
|
|
112
|
-
const RAY_TYPE_DIFFUSE = 3;
|
|
113
|
-
|
|
114
|
-
// Trace result struct
|
|
115
|
-
export const TraceResult = struct( {
|
|
116
|
-
radiance: 'vec4',
|
|
117
|
-
objectNormal: 'vec3',
|
|
118
|
-
objectColor: 'vec3',
|
|
119
|
-
objectID: 'float',
|
|
120
|
-
firstHitPoint: 'vec3',
|
|
121
|
-
firstHitDistance: 'float',
|
|
122
|
-
} );
|
|
123
|
-
|
|
124
|
-
// =============================================================================
|
|
125
|
-
// Material Classification Caching
|
|
126
|
-
// =============================================================================
|
|
127
|
-
|
|
128
|
-
// OPTIMIZED: Consolidated material classification with material change detection
|
|
129
|
-
// Note: In TSL, we cannot use inout on PathState, so we pass individual cache fields
|
|
130
|
-
// and return classification. PathState cache management happens in the caller.
|
|
131
|
-
export const getOrCreateMaterialClassification = Fn( ( [
|
|
132
|
-
material, materialIndex,
|
|
133
|
-
classificationCached, lastMaterialIndex,
|
|
134
|
-
cachedClassification,
|
|
135
|
-
] ) => {
|
|
136
|
-
|
|
137
|
-
const result = cachedClassification.toVar();
|
|
138
|
-
|
|
139
|
-
If( classificationCached.not().or( lastMaterialIndex.notEqual( materialIndex ) ), () => {
|
|
140
|
-
|
|
141
|
-
result.assign( classifyMaterial(
|
|
142
|
-
material.metalness, material.roughness,
|
|
143
|
-
material.transmission, material.clearcoat,
|
|
144
|
-
material.emissive, material.subsurface,
|
|
145
|
-
) );
|
|
146
|
-
|
|
147
|
-
} );
|
|
148
|
-
|
|
149
|
-
return result;
|
|
150
|
-
|
|
151
|
-
} );
|
|
152
54
|
|
|
153
55
|
// =============================================================================
|
|
154
56
|
// BRDF Direction Sampling
|
|
@@ -296,23 +198,45 @@ export const generateSampledDirection = Fn( ( [
|
|
|
296
198
|
} );
|
|
297
199
|
|
|
298
200
|
// =============================================================================
|
|
299
|
-
//
|
|
201
|
+
// Firefly Suppression
|
|
300
202
|
// =============================================================================
|
|
301
203
|
|
|
204
|
+
export const regularizePathContribution = /*@__PURE__*/ wgslFn( `
|
|
205
|
+
fn regularizePathContribution( contribution: vec3f, pathLength: f32, fireflyThreshold: f32, frame: i32 ) -> vec3f {
|
|
206
|
+
let threshold = calculateFireflyThreshold( fireflyThreshold, i32( pathLength ), frame );
|
|
207
|
+
return applySoftSuppressionRGB( contribution, threshold, 0.5f );
|
|
208
|
+
}
|
|
209
|
+
`, [ calculateFireflyThreshold, applySoftSuppressionRGB ] );
|
|
210
|
+
|
|
211
|
+
// ── Shared sampling helpers (used by the wavefront kernels) ──
|
|
212
|
+
|
|
213
|
+
// World position → NDC depth [0,1] for motion-vector reprojection.
|
|
214
|
+
export const computeNDCDepth = /*@__PURE__*/ wgslFn( `
|
|
215
|
+
fn computeNDCDepth( worldPos: vec3f, cameraProjectionMatrix: mat4x4f, cameraViewMatrix: mat4x4f ) -> f32 {
|
|
216
|
+
let clipPos = cameraProjectionMatrix * cameraViewMatrix * vec4f( worldPos, 1.0f );
|
|
217
|
+
let ndcDepth = clipPos.z / clipPos.w * 0.5f + 0.5f;
|
|
218
|
+
return clamp( ndcDepth, 0.0f, 1.0f );
|
|
219
|
+
}
|
|
220
|
+
` );
|
|
221
|
+
|
|
222
|
+
// Adaptive Russian roulette (megakernel parity: PathTracerCore.js:302 on `main`, gap #7). Returns the
|
|
223
|
+
// survival probability (≥minProb) when the path continues, or 0.0 when terminated. Material-importance +
|
|
224
|
+
// throughput + env-direction aware, with a dynamic minBounces floor and exponential depth decay — replaces
|
|
225
|
+
// the flat `clamp(maxThroughput,0.05,0.95)` test. Unbiased either way; this just terminates the *right* rays
|
|
226
|
+
// (keeps smooth-metal / transmissive / emissive chains alive longer) → less noise per sample.
|
|
227
|
+
// Takes the already-computed MaterialClassification `mc` directly (the wavefront classifies once per shade).
|
|
302
228
|
export const handleRussianRoulette = Fn( ( [
|
|
303
|
-
depth, throughput,
|
|
304
|
-
classificationCached, lastMaterialIndex, cachedClassification,
|
|
229
|
+
depth, throughput, mc, rayDirection, rngState,
|
|
305
230
|
enableEnvironmentLight, useEnvMapIS,
|
|
306
231
|
] ) => {
|
|
307
232
|
|
|
308
233
|
const result = float( 1.0 ).toVar();
|
|
309
234
|
|
|
310
|
-
// Always continue for first few bounces
|
|
311
235
|
If( depth.greaterThanEqual( int( 3 ) ), () => {
|
|
312
236
|
|
|
313
|
-
const throughputStrength = max(
|
|
237
|
+
const throughputStrength = max( max( max( throughput.x, throughput.y ), throughput.z ), 0.0 ).toVar();
|
|
314
238
|
|
|
315
|
-
// Energy-conserving early termination for very low throughput paths
|
|
239
|
+
// Energy-conserving early termination for very low throughput paths (compensated)
|
|
316
240
|
If( throughputStrength.lessThan( 0.0008 ).and( depth.greaterThan( int( 4 ) ) ), () => {
|
|
317
241
|
|
|
318
242
|
const lowThroughputProb = max( throughputStrength.mul( 125.0 ), 0.01 );
|
|
@@ -321,19 +245,8 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
321
245
|
|
|
322
246
|
} ).Else( () => {
|
|
323
247
|
|
|
324
|
-
//
|
|
325
|
-
const mc = MaterialClassification.wrap( getOrCreateMaterialClassification(
|
|
326
|
-
material, materialIndex,
|
|
327
|
-
classificationCached, lastMaterialIndex, cachedClassification,
|
|
328
|
-
) ).toVar();
|
|
329
|
-
|
|
248
|
+
// Importance boosts: deeper budget for transport types that physically carry energy farther.
|
|
330
249
|
const materialImportance = mc.complexityScore.toVar();
|
|
331
|
-
|
|
332
|
-
// Boost importance for special materials — depth hierarchy reflects
|
|
333
|
-
// how many bounces each transport type physically needs:
|
|
334
|
-
// Specular metals: deepest (mirror chains carry energy efficiently)
|
|
335
|
-
// Transmissive: deep (caustics, internal reflections)
|
|
336
|
-
// Emissive: shallowest (emission already collected, continuation rarely valuable)
|
|
337
250
|
If( mc.isMetallic.and( mc.isSmooth ).and( depth.lessThan( int( 7 ) ) ), () => {
|
|
338
251
|
|
|
339
252
|
materialImportance.addAssign( 0.3 );
|
|
@@ -369,7 +282,6 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
369
282
|
|
|
370
283
|
} ).Else( () => {
|
|
371
284
|
|
|
372
|
-
// Path contribution estimate — reuses throughputStrength + mc from outer scope.
|
|
373
285
|
const estMaterialImportance = mc.complexityScore.toVar();
|
|
374
286
|
If( mc.isMetallic.and( mc.isSmooth ), () => {
|
|
375
287
|
|
|
@@ -401,27 +313,21 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
401
313
|
mix( estMaterialImportance.mul( 0.7 ), directionImportance, 0.3 ),
|
|
402
314
|
).mul( throughputWeight ).toVar();
|
|
403
315
|
|
|
404
|
-
// Smooth
|
|
405
|
-
// Early behavior: throughput + material driven, generous
|
|
316
|
+
// Smooth early→deep continuation probability (no discrete depth brackets)
|
|
406
317
|
const earlyProb = clamp(
|
|
407
318
|
materialImportance.mul( 0.4 ).add( throughputStrength.mul( 0.6 ) ).mul( 1.2 ),
|
|
408
319
|
0.15, 0.95,
|
|
409
320
|
);
|
|
410
|
-
// Deep behavior: aggressive termination, material-aware floor
|
|
411
321
|
const deepProb = clamp(
|
|
412
322
|
throughputStrength.mul( 0.4 ).add( materialImportance.mul( 0.1 ) ),
|
|
413
323
|
0.03, 0.6,
|
|
414
324
|
);
|
|
415
325
|
|
|
416
|
-
// Smooth blend from early → deep using depth relative to minBounces
|
|
417
|
-
// At minBounces: t=0 (earlyProb), at minBounces+10: t=1 (deepProb)
|
|
418
326
|
const depthT = clamp( float( depth.sub( minBounces ) ).div( 10.0 ), 0.0, 1.0 );
|
|
419
327
|
const rrProb = mix( earlyProb, deepProb, depthT ).toVar();
|
|
420
328
|
|
|
421
|
-
// Mix in path contribution for direction-aware survival
|
|
422
329
|
rrProb.assign( mix( rrProb, max( rrProb, pathContribution ), 0.4 ) );
|
|
423
330
|
|
|
424
|
-
// Material-specific boosts
|
|
425
331
|
If( materialImportance.greaterThan( 0.5 ), () => {
|
|
426
332
|
|
|
427
333
|
const boostFactor = materialImportance.sub( 0.5 ).mul( 0.6 );
|
|
@@ -429,12 +335,10 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
429
335
|
|
|
430
336
|
} );
|
|
431
337
|
|
|
432
|
-
// Exponential depth decay
|
|
433
338
|
const depthDecay = float( 0.12 ).add( materialImportance.mul( 0.08 ) );
|
|
434
339
|
const depthFactor = exp( float( depth.sub( minBounces ) ).negate().mul( depthDecay ) );
|
|
435
340
|
rrProb.mulAssign( depthFactor );
|
|
436
341
|
|
|
437
|
-
// Minimum probability floor
|
|
438
342
|
const minProb = select( mc.isEmissive, float( 0.04 ), float( 0.02 ) );
|
|
439
343
|
rrProb.assign( max( rrProb, minProb ) );
|
|
440
344
|
|
|
@@ -450,903 +354,3 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
450
354
|
return result;
|
|
451
355
|
|
|
452
356
|
} );
|
|
453
|
-
|
|
454
|
-
// =============================================================================
|
|
455
|
-
// Background & Environment Sampling
|
|
456
|
-
// =============================================================================
|
|
457
|
-
|
|
458
|
-
export const sampleBackgroundLighting = Fn( ( [
|
|
459
|
-
isPrimaryRay, rayOrigin, direction,
|
|
460
|
-
envTexture, envMatrix, environmentIntensity, enableEnvironmentLight,
|
|
461
|
-
showBackground, backgroundIntensity,
|
|
462
|
-
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
463
|
-
] ) => {
|
|
464
|
-
|
|
465
|
-
// Only hide background for primary camera rays when showBackground is false
|
|
466
|
-
const envColor = vec4( 0.0 ).toVar();
|
|
467
|
-
|
|
468
|
-
If( isPrimaryRay.and( showBackground.not() ), () => {
|
|
469
|
-
|
|
470
|
-
// Return zero
|
|
471
|
-
envColor.assign( vec4( 0.0 ) );
|
|
472
|
-
|
|
473
|
-
} ).Else( () => {
|
|
474
|
-
|
|
475
|
-
// Primary-ray only: indirect bounces must see the raw envmap so shading stays physically correct.
|
|
476
|
-
const effectiveDir = direction.toVar();
|
|
477
|
-
If( isPrimaryRay.and( groundProjectionEnabled ), () => {
|
|
478
|
-
|
|
479
|
-
effectiveDir.assign( getGroundProjectedDirection(
|
|
480
|
-
rayOrigin, direction, groundProjectionRadius, groundProjectionHeight,
|
|
481
|
-
) );
|
|
482
|
-
|
|
483
|
-
} );
|
|
484
|
-
|
|
485
|
-
const sampled = sampleEnvironment( {
|
|
486
|
-
tex: envTexture, samp: sampler( envTexture ), direction: effectiveDir, environmentMatrix: envMatrix, environmentIntensity, enableEnvironmentLight,
|
|
487
|
-
} );
|
|
488
|
-
|
|
489
|
-
If( isPrimaryRay, () => {
|
|
490
|
-
|
|
491
|
-
envColor.assign( sampled.mul( backgroundIntensity ) );
|
|
492
|
-
|
|
493
|
-
} ).Else( () => {
|
|
494
|
-
|
|
495
|
-
envColor.assign( sampled );
|
|
496
|
-
|
|
497
|
-
} );
|
|
498
|
-
|
|
499
|
-
} );
|
|
500
|
-
|
|
501
|
-
return envColor;
|
|
502
|
-
|
|
503
|
-
} );
|
|
504
|
-
|
|
505
|
-
// =============================================================================
|
|
506
|
-
// Firefly Suppression
|
|
507
|
-
// =============================================================================
|
|
508
|
-
|
|
509
|
-
export const regularizePathContribution = /*@__PURE__*/ wgslFn( `
|
|
510
|
-
fn regularizePathContribution( contribution: vec3f, pathLength: f32, fireflyThreshold: f32, frame: i32 ) -> vec3f {
|
|
511
|
-
let threshold = calculateFireflyThreshold( fireflyThreshold, i32( pathLength ), frame );
|
|
512
|
-
return applySoftSuppressionRGB( contribution, threshold, 0.5f );
|
|
513
|
-
}
|
|
514
|
-
`, [ calculateFireflyThreshold, applySoftSuppressionRGB ] );
|
|
515
|
-
|
|
516
|
-
// =============================================================================
|
|
517
|
-
// Main Path Tracing Loop
|
|
518
|
-
// =============================================================================
|
|
519
|
-
|
|
520
|
-
export const Trace = Fn( ( [
|
|
521
|
-
ray, rngState, rayIndex,
|
|
522
|
-
// BVH / Scene
|
|
523
|
-
bvhBuffer,
|
|
524
|
-
triangleBuffer,
|
|
525
|
-
materialBuffer,
|
|
526
|
-
// Texture arrays for material sampling
|
|
527
|
-
albedoMaps, normalMaps, bumpMaps,
|
|
528
|
-
metalnessMaps, roughnessMaps, emissiveMaps,
|
|
529
|
-
displacementMaps,
|
|
530
|
-
// Lights
|
|
531
|
-
directionalLightsBuffer, numDirectionalLights,
|
|
532
|
-
areaLightsBuffer, numAreaLights,
|
|
533
|
-
pointLightsBuffer, numPointLights,
|
|
534
|
-
spotLightsBuffer, numSpotLights,
|
|
535
|
-
// Environment
|
|
536
|
-
envTexture, environmentIntensity, envMatrix,
|
|
537
|
-
envCDFBuffer,
|
|
538
|
-
envTotalSum, envCompensationDelta, envResolution,
|
|
539
|
-
enableEnvironmentLight, useEnvMapIS,
|
|
540
|
-
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
541
|
-
// Rendering parameters
|
|
542
|
-
maxBounceCount, transmissiveBounces, maxSubsurfaceSteps,
|
|
543
|
-
backgroundIntensity, showBackground, transparentBackground,
|
|
544
|
-
fireflyThreshold, globalIlluminationIntensity,
|
|
545
|
-
enableEmissiveTriangleSampling,
|
|
546
|
-
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
547
|
-
lightBVHBuffer, lightBVHNodeCount,
|
|
548
|
-
// Per-pixel info
|
|
549
|
-
pixelCoord, resolution, frame,
|
|
550
|
-
] ) => {
|
|
551
|
-
|
|
552
|
-
const radiance = vec3( 0.0 ).toVar();
|
|
553
|
-
const throughput = vec3( 1.0 ).toVar();
|
|
554
|
-
const alpha = float( 1.0 ).toVar();
|
|
555
|
-
const hasHitOpaqueSurface = tslBool( false ).toVar(); // Tracks if ray chain has hit non-transmissive geometry
|
|
556
|
-
const prevBouncePdf = float( 0.0 ).toVar(); // 0 = camera ray (skip MIS for directly visible emissive)
|
|
557
|
-
|
|
558
|
-
// Output data
|
|
559
|
-
const objectNormal = vec3( 0.0 ).toVar();
|
|
560
|
-
const objectColor = vec3( 0.0 ).toVar();
|
|
561
|
-
const objectID = float( - 1000.0 ).toVar();
|
|
562
|
-
const firstHitPoint = ray.origin.toVar();
|
|
563
|
-
const firstHitDistance = float( 1e10 ).toVar();
|
|
564
|
-
// OIDN clean-aux: extend albedo/normal capture through specular/transmissive
|
|
565
|
-
// surfaces so aux features describe what's actually visible (e.g. scenery
|
|
566
|
-
// behind glass), not the glass surface itself.
|
|
567
|
-
const auxLocked = tslBool( false ).toVar();
|
|
568
|
-
|
|
569
|
-
// Medium stack for transmission (slots 1-3 for nested media, depth 0 = air).
|
|
570
|
-
// Each slot tracks IOR plus KHR_materials_volume attenuation (color + distance)
|
|
571
|
-
// so per-bounce in-volume absorption uses the actual ray path length.
|
|
572
|
-
const mediumStackDepth = int( 0 ).toVar();
|
|
573
|
-
const mediumStack_ior_1 = float( 1.0 ).toVar();
|
|
574
|
-
const mediumStack_ior_2 = float( 1.0 ).toVar();
|
|
575
|
-
const mediumStack_ior_3 = float( 1.0 ).toVar();
|
|
576
|
-
const mediumStack_attColor_1 = vec3( 1.0 ).toVar();
|
|
577
|
-
const mediumStack_attColor_2 = vec3( 1.0 ).toVar();
|
|
578
|
-
const mediumStack_attColor_3 = vec3( 1.0 ).toVar();
|
|
579
|
-
const mediumStack_attDist_1 = float( 0.0 ).toVar();
|
|
580
|
-
const mediumStack_attDist_2 = float( 0.0 ).toVar();
|
|
581
|
-
const mediumStack_attDist_3 = float( 0.0 ).toVar();
|
|
582
|
-
// Precomputed Beer-Lambert absorption coefficient sigma_a = -log(attColor)/attDist.
|
|
583
|
-
// Stored at push time so per-bounce absorption inside a medium becomes a single
|
|
584
|
-
// exp(-sigma_a * thickness) instead of a log + div + exp every bounce.
|
|
585
|
-
const mediumStack_sigmaA_1 = vec3( 0.0 ).toVar();
|
|
586
|
-
const mediumStack_sigmaA_2 = vec3( 0.0 ).toVar();
|
|
587
|
-
const mediumStack_sigmaA_3 = vec3( 0.0 ).toVar();
|
|
588
|
-
// Subsurface: sigma_s>0 makes the medium scatter (random walk) rather than absorb straight.
|
|
589
|
-
const mediumStack_sigmaS_1 = vec3( 0.0 ).toVar();
|
|
590
|
-
const mediumStack_sigmaS_2 = vec3( 0.0 ).toVar();
|
|
591
|
-
const mediumStack_sigmaS_3 = vec3( 0.0 ).toVar();
|
|
592
|
-
const mediumStack_sigmaT_1 = vec3( 0.0 ).toVar();
|
|
593
|
-
const mediumStack_sigmaT_2 = vec3( 0.0 ).toVar();
|
|
594
|
-
const mediumStack_sigmaT_3 = vec3( 0.0 ).toVar();
|
|
595
|
-
const mediumStack_g_1 = float( 0.0 ).toVar();
|
|
596
|
-
const mediumStack_g_2 = float( 0.0 ).toVar();
|
|
597
|
-
const mediumStack_g_3 = float( 0.0 ).toVar();
|
|
598
|
-
// Walk-step budget for the whole path (bounded per render mode + RR).
|
|
599
|
-
const sssSteps = int( 0 ).toVar();
|
|
600
|
-
|
|
601
|
-
// Locked at the first dispersive transmission; reused for subsequent transmissions on
|
|
602
|
-
// the path so multi-bounce dispersion doesn't collapse under repeated colorWeight ×.
|
|
603
|
-
const pathWavelength = float( 0.0 ).toVar();
|
|
604
|
-
|
|
605
|
-
// Render state
|
|
606
|
-
const stateTraversals = maxBounceCount.toVar();
|
|
607
|
-
const stateTransmissiveTraversals = transmissiveBounces.toVar();
|
|
608
|
-
const stateRayType = int( RAY_TYPE_CAMERA ).toVar();
|
|
609
|
-
const stateIsPrimaryRay = tslBool( true ).toVar();
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
// Path state cache fields (managed individually since TSL can't do inout struct)
|
|
613
|
-
const psWeightsComputed = tslBool( false ).toVar();
|
|
614
|
-
const psClassificationCached = tslBool( false ).toVar();
|
|
615
|
-
const psMaterialCacheCached = tslBool( false ).toVar();
|
|
616
|
-
const psLastMaterialIndex = int( - 1 ).toVar();
|
|
617
|
-
|
|
618
|
-
// Cached classification
|
|
619
|
-
const psCachedClassification = MaterialClassification( {
|
|
620
|
-
isMetallic: false, isRough: false, isSmooth: false,
|
|
621
|
-
isTransmissive: false, hasClearcoat: false, isEmissive: false,
|
|
622
|
-
complexityScore: float( 0.0 ),
|
|
623
|
-
} ).toVar();
|
|
624
|
-
|
|
625
|
-
// Cached BRDF weights
|
|
626
|
-
const psCachedBrdfWeights = BRDFWeights( {
|
|
627
|
-
specular: float( 0.5 ), diffuse: float( 0.5 ),
|
|
628
|
-
sheen: float( 0.0 ), clearcoat: float( 0.0 ),
|
|
629
|
-
transmission: float( 0.0 ), iridescence: float( 0.0 ),
|
|
630
|
-
} ).toVar();
|
|
631
|
-
|
|
632
|
-
// Cached material cache
|
|
633
|
-
const psCachedMaterialCache = MaterialCache( {
|
|
634
|
-
F0: vec3( 0.04 ), NoV: float( 1.0 ),
|
|
635
|
-
diffuseColor: vec3( 0.0 ), isPurelyDiffuse: false,
|
|
636
|
-
alpha: float( 0.0 ), k: float( 0.0 ), alpha2: float( 0.0 ),
|
|
637
|
-
invRoughness: float( 1.0 ), metalFactor: float( 0.5 ),
|
|
638
|
-
iorFactor: float( 1.0 ), maxSheenColor: float( 0.0 ),
|
|
639
|
-
} ).toVar();
|
|
640
|
-
|
|
641
|
-
// Track effective bounces
|
|
642
|
-
const effectiveBounces = int( 0 ).toVar();
|
|
643
|
-
|
|
644
|
-
// Mutable ray
|
|
645
|
-
const rayOrigin = ray.origin.toVar();
|
|
646
|
-
const rayDirection = ray.direction.toVar();
|
|
647
|
-
|
|
648
|
-
// Main bounce loop
|
|
649
|
-
Loop( { start: int( 0 ), end: maxBounceCount.add( transmissiveBounces ).add( maxSubsurfaceSteps ).add( 1 ), type: 'int', condition: '<' }, ( { i: bounceIndex } ) => {
|
|
650
|
-
|
|
651
|
-
// Update state
|
|
652
|
-
stateTraversals.assign( maxBounceCount.sub( effectiveBounces ) );
|
|
653
|
-
stateIsPrimaryRay.assign( bounceIndex.equal( int( 0 ) ) );
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
// Check bounce budget
|
|
657
|
-
If( effectiveBounces.greaterThan( maxBounceCount ), () => {
|
|
658
|
-
|
|
659
|
-
Break();
|
|
660
|
-
|
|
661
|
-
} );
|
|
662
|
-
|
|
663
|
-
// Non-compounding GI intensity: applied per-bounce to radiance, not throughput
|
|
664
|
-
const giScale = select( bounceIndex.greaterThan( int( 0 ) ), globalIlluminationIntensity, float( 1.0 ) );
|
|
665
|
-
|
|
666
|
-
// Traverse BVH
|
|
667
|
-
const currentRay = Ray( { origin: rayOrigin, direction: rayDirection } );
|
|
668
|
-
const hitInfo = HitInfo.wrap( traverseBVH(
|
|
669
|
-
currentRay,
|
|
670
|
-
bvhBuffer,
|
|
671
|
-
triangleBuffer,
|
|
672
|
-
mediumStackDepth.greaterThan( int( 0 ) ), // inside a medium → bypass front/back culling
|
|
673
|
-
) ).toVar();
|
|
674
|
-
|
|
675
|
-
// In-medium transport: glass (sigma_s==0) absorbs along a straight line; subsurface
|
|
676
|
-
// (sigma_s>0) random-walks and may scatter mid-flight.
|
|
677
|
-
If( hitInfo.didHit.and( mediumStackDepth.greaterThan( int( 0 ) ) ), () => {
|
|
678
|
-
|
|
679
|
-
// Load current-medium coefficients (chained branch — divergence-safe).
|
|
680
|
-
const mSigmaA = vec3( 0.0 ).toVar();
|
|
681
|
-
const mSigmaS = vec3( 0.0 ).toVar();
|
|
682
|
-
const mSigmaT = vec3( 0.0 ).toVar();
|
|
683
|
-
const mG = float( 0.0 ).toVar();
|
|
684
|
-
If( mediumStackDepth.equal( int( 1 ) ), () => {
|
|
685
|
-
|
|
686
|
-
mSigmaA.assign( mediumStack_sigmaA_1 ); mSigmaS.assign( mediumStack_sigmaS_1 );
|
|
687
|
-
mSigmaT.assign( mediumStack_sigmaT_1 ); mG.assign( mediumStack_g_1 );
|
|
688
|
-
|
|
689
|
-
} ).ElseIf( mediumStackDepth.equal( int( 2 ) ), () => {
|
|
690
|
-
|
|
691
|
-
mSigmaA.assign( mediumStack_sigmaA_2 ); mSigmaS.assign( mediumStack_sigmaS_2 );
|
|
692
|
-
mSigmaT.assign( mediumStack_sigmaT_2 ); mG.assign( mediumStack_g_2 );
|
|
693
|
-
|
|
694
|
-
} ).ElseIf( mediumStackDepth.equal( int( 3 ) ), () => {
|
|
695
|
-
|
|
696
|
-
mSigmaA.assign( mediumStack_sigmaA_3 ); mSigmaS.assign( mediumStack_sigmaS_3 );
|
|
697
|
-
mSigmaT.assign( mediumStack_sigmaT_3 ); mG.assign( mediumStack_g_3 );
|
|
698
|
-
|
|
699
|
-
} );
|
|
700
|
-
|
|
701
|
-
If( maxComponent( { v: mSigmaS } ).lessThanEqual( 0.0 ), () => {
|
|
702
|
-
|
|
703
|
-
// Non-scattering medium (glass): straight-line Beer-Lambert absorption.
|
|
704
|
-
throughput.mulAssign( exp( mSigmaA.mul( hitInfo.dst ).negate() ) );
|
|
705
|
-
|
|
706
|
-
} ).Else( () => {
|
|
707
|
-
|
|
708
|
-
// Scattering medium (subsurface): chromatic distance sampling for this segment.
|
|
709
|
-
const coll = CollisionSample.wrap( sampleChromaticCollision(
|
|
710
|
-
mSigmaT, mSigmaS, throughput, hitInfo.dst, rngState,
|
|
711
|
-
) ).toVar();
|
|
712
|
-
throughput.mulAssign( coll.weight );
|
|
713
|
-
|
|
714
|
-
If( coll.didScatter, () => {
|
|
715
|
-
|
|
716
|
-
// Scatter: move to the collision point, redirect via the HG phase function,
|
|
717
|
-
// continue as a free bounce (no camera-bounce cost).
|
|
718
|
-
const xi2 = vec2( RandomValue( rngState ), RandomValue( rngState ) );
|
|
719
|
-
const scatterPoint = rayOrigin.add( rayDirection.mul( coll.t ) );
|
|
720
|
-
rayOrigin.assign( scatterPoint );
|
|
721
|
-
rayDirection.assign( sampleHenyeyGreenstein( rayDirection, mG, xi2 ) );
|
|
722
|
-
sssSteps.addAssign( 1 );
|
|
723
|
-
stateIsPrimaryRay.assign( tslBool( false ) );
|
|
724
|
-
|
|
725
|
-
// Per-mode step cap: bounded walk (terminate — energy-loss-only bias).
|
|
726
|
-
If( sssSteps.greaterThanEqual( maxSubsurfaceSteps ), () => {
|
|
727
|
-
|
|
728
|
-
Break();
|
|
729
|
-
|
|
730
|
-
} );
|
|
731
|
-
|
|
732
|
-
// Russian roulette so the walk self-terminates before the cap.
|
|
733
|
-
const rrP = clamp( maxComponent( { v: throughput } ), 0.02, 1.0 ).toVar();
|
|
734
|
-
If( RandomValue( rngState ).greaterThan( rrP ), () => {
|
|
735
|
-
|
|
736
|
-
Break();
|
|
737
|
-
|
|
738
|
-
} );
|
|
739
|
-
throughput.divAssign( rrP );
|
|
740
|
-
|
|
741
|
-
Continue();
|
|
742
|
-
|
|
743
|
-
} );
|
|
744
|
-
|
|
745
|
-
// No scatter: reached the boundary (weight applied); fall through to surface handling.
|
|
746
|
-
|
|
747
|
-
} );
|
|
748
|
-
|
|
749
|
-
} );
|
|
750
|
-
|
|
751
|
-
If( hitInfo.didHit.not(), () => {
|
|
752
|
-
|
|
753
|
-
// ENVIRONMENT LIGHTING
|
|
754
|
-
const envColor = sampleBackgroundLighting(
|
|
755
|
-
stateIsPrimaryRay, rayOrigin, rayDirection,
|
|
756
|
-
envTexture, envMatrix, environmentIntensity, enableEnvironmentLight,
|
|
757
|
-
showBackground, backgroundIntensity,
|
|
758
|
-
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
759
|
-
);
|
|
760
|
-
|
|
761
|
-
// MIS weight for implicit environment hit — prevents double-counting with NEE.
|
|
762
|
-
// Primary rays and camera rays (prevBouncePdf == 0) get full weight.
|
|
763
|
-
// Secondary rays use power heuristic between the scatter PDF and the
|
|
764
|
-
// environment importance-sampling PDF, mirroring the emissive MIS at line ~978.
|
|
765
|
-
const envMisWeight = float( 1.0 ).toVar();
|
|
766
|
-
If( prevBouncePdf.greaterThan( 0.0 ).and( enableEnvironmentLight ).and( useEnvMapIS ), () => {
|
|
767
|
-
|
|
768
|
-
const envEval = sampleEquirect(
|
|
769
|
-
envTexture, rayDirection, envMatrix, envTotalSum, envCompensationDelta, envResolution,
|
|
770
|
-
);
|
|
771
|
-
const envPdf = envEval.w.toVar();
|
|
772
|
-
If( envPdf.greaterThan( 0.0 ), () => {
|
|
773
|
-
|
|
774
|
-
envMisWeight.assign( balanceHeuristic( { pdf1: prevBouncePdf, pdf2: envPdf } ) );
|
|
775
|
-
|
|
776
|
-
} );
|
|
777
|
-
|
|
778
|
-
} );
|
|
779
|
-
|
|
780
|
-
radiance.addAssign( regularizePathContribution( {
|
|
781
|
-
contribution: envColor.xyz.mul( throughput ).mul( giScale ).mul( envMisWeight ), pathLength: float( bounceIndex ), fireflyThreshold, frame: int( frame ),
|
|
782
|
-
} ) );
|
|
783
|
-
|
|
784
|
-
// Transparent background: only transparent if ray escaped WITHOUT hitting opaque geometry first.
|
|
785
|
-
// Secondary bounces from opaque surfaces escaping to env should NOT make the pixel transparent.
|
|
786
|
-
If( transparentBackground.and( hasHitOpaqueSurface.not() ), () => {
|
|
787
|
-
|
|
788
|
-
alpha.assign( 0.0 );
|
|
789
|
-
|
|
790
|
-
} ).ElseIf( transparentBackground.not(), () => {
|
|
791
|
-
|
|
792
|
-
alpha.mulAssign( envColor.a );
|
|
793
|
-
|
|
794
|
-
} );
|
|
795
|
-
|
|
796
|
-
Break();
|
|
797
|
-
|
|
798
|
-
} );
|
|
799
|
-
|
|
800
|
-
// Get full material (30 reads). Lazy transform loading was tested but regressed
|
|
801
|
-
// textured scenes due to identity-construct + conditional-assign overhead.
|
|
802
|
-
// Shadow rays use getShadowMaterial() (7 reads) — the real bandwidth win.
|
|
803
|
-
const material = RayTracingMaterial.wrap( getMaterial( hitInfo.materialIndex, materialBuffer ) ).toVar();
|
|
804
|
-
|
|
805
|
-
// Tessellation-free displacement — refine intersection with ray-height field marching
|
|
806
|
-
const samplingUV = hitInfo.uv.toVar();
|
|
807
|
-
const displacedNormal = hitInfo.normal.toVar();
|
|
808
|
-
|
|
809
|
-
If( material.displacementMapIndex.greaterThanEqual( int( 0 ) ).and( material.displacementScale.greaterThan( 0.0 ) ), () => {
|
|
810
|
-
|
|
811
|
-
const dispResult = DisplacementResult.wrap( refineDisplacedIntersection(
|
|
812
|
-
currentRay, hitInfo, triangleBuffer, displacementMaps, material, bounceIndex,
|
|
813
|
-
) ).toVar();
|
|
814
|
-
samplingUV.assign( dispResult.uv );
|
|
815
|
-
displacedNormal.assign( dispResult.normal );
|
|
816
|
-
hitInfo.hitPoint.assign( dispResult.hitPoint );
|
|
817
|
-
|
|
818
|
-
} );
|
|
819
|
-
|
|
820
|
-
// Sample all textures using displacement-refined UVs
|
|
821
|
-
const matSamples = MaterialSamples.wrap( sampleAllMaterialTextures(
|
|
822
|
-
albedoMaps, normalMaps, bumpMaps, metalnessMaps, roughnessMaps, emissiveMaps,
|
|
823
|
-
material, samplingUV, hitInfo.normal,
|
|
824
|
-
) ).toVar();
|
|
825
|
-
|
|
826
|
-
// Update material with texture samples
|
|
827
|
-
material.color.assign( matSamples.albedo );
|
|
828
|
-
material.metalness.assign( clamp( matSamples.metalness, 0.0, 1.0 ) );
|
|
829
|
-
material.roughness.assign( clamp( matSamples.roughness, MIN_ROUGHNESS, MAX_ROUGHNESS ) );
|
|
830
|
-
|
|
831
|
-
// Blend displaced normal with texture normal map — displacement provides macro shape, normal map adds micro detail
|
|
832
|
-
const N = matSamples.normal.toVar();
|
|
833
|
-
If( material.displacementMapIndex.greaterThanEqual( int( 0 ) ).and( material.displacementScale.greaterThan( 0.0 ) ), () => {
|
|
834
|
-
|
|
835
|
-
N.assign( normalize( displacedNormal.add( matSamples.normal.sub( hitInfo.normal ) ) ) );
|
|
836
|
-
|
|
837
|
-
} );
|
|
838
|
-
|
|
839
|
-
// Compute current and previous medium IOR from stack for transmission
|
|
840
|
-
const currentMediumIOR = float( 1.0 ).toVar();
|
|
841
|
-
const previousMediumIOR = float( 1.0 ).toVar();
|
|
842
|
-
If( mediumStackDepth.equal( int( 1 ) ), () => {
|
|
843
|
-
|
|
844
|
-
currentMediumIOR.assign( mediumStack_ior_1 );
|
|
845
|
-
|
|
846
|
-
} ).ElseIf( mediumStackDepth.equal( int( 2 ) ), () => {
|
|
847
|
-
|
|
848
|
-
currentMediumIOR.assign( mediumStack_ior_2 );
|
|
849
|
-
previousMediumIOR.assign( mediumStack_ior_1 );
|
|
850
|
-
|
|
851
|
-
} ).ElseIf( mediumStackDepth.equal( int( 3 ) ), () => {
|
|
852
|
-
|
|
853
|
-
currentMediumIOR.assign( mediumStack_ior_3 );
|
|
854
|
-
previousMediumIOR.assign( mediumStack_ior_2 );
|
|
855
|
-
|
|
856
|
-
} );
|
|
857
|
-
|
|
858
|
-
// Handle transparent materials
|
|
859
|
-
const interaction = MaterialInteractionResult.wrap( handleMaterialTransparency(
|
|
860
|
-
currentRay, N, material, rngState,
|
|
861
|
-
stateTransmissiveTraversals,
|
|
862
|
-
currentMediumIOR, previousMediumIOR,
|
|
863
|
-
pathWavelength,
|
|
864
|
-
) ).toVar();
|
|
865
|
-
pathWavelength.assign( interaction.pathWavelength );
|
|
866
|
-
|
|
867
|
-
If( interaction.continueRay, () => {
|
|
868
|
-
|
|
869
|
-
const isFreeBounce = tslBool( false ).toVar();
|
|
870
|
-
|
|
871
|
-
If( interaction.isTransmissive.and( stateTransmissiveTraversals.greaterThan( int( 0 ) ) ), () => {
|
|
872
|
-
|
|
873
|
-
stateTransmissiveTraversals.subAssign( 1 );
|
|
874
|
-
stateRayType.assign( int( RAY_TYPE_TRANSMISSION ) );
|
|
875
|
-
isFreeBounce.assign( tslBool( true ) );
|
|
876
|
-
|
|
877
|
-
// Update medium stack only if we actually transmitted (not TIR/reflection)
|
|
878
|
-
If( interaction.didReflect.not(), () => {
|
|
879
|
-
|
|
880
|
-
If( interaction.entering, () => {
|
|
881
|
-
|
|
882
|
-
// Push new medium onto stack (IOR + KHR_materials_volume attenuation)
|
|
883
|
-
If( mediumStackDepth.lessThan( int( 3 ) ), () => {
|
|
884
|
-
|
|
885
|
-
mediumStackDepth.addAssign( 1 );
|
|
886
|
-
|
|
887
|
-
// Precompute sigma_a = -log(attColor)/attDist once at push time.
|
|
888
|
-
// attDist==0 means "no absorption" — store sigma_a=0 so exp() returns 1.
|
|
889
|
-
const mSigmaA = select(
|
|
890
|
-
material.attenuationDistance.greaterThan( 0.0 ),
|
|
891
|
-
log( max( material.attenuationColor, vec3( 0.001 ) ) ).negate().div( material.attenuationDistance ),
|
|
892
|
-
vec3( 0.0 )
|
|
893
|
-
).toVar();
|
|
894
|
-
|
|
895
|
-
If( mediumStackDepth.equal( int( 1 ) ), () => {
|
|
896
|
-
|
|
897
|
-
mediumStack_ior_1.assign( material.ior );
|
|
898
|
-
mediumStack_attColor_1.assign( material.attenuationColor );
|
|
899
|
-
mediumStack_attDist_1.assign( material.attenuationDistance );
|
|
900
|
-
mediumStack_sigmaA_1.assign( mSigmaA );
|
|
901
|
-
mediumStack_sigmaS_1.assign( vec3( 0.0 ) ); // glass: no scattering
|
|
902
|
-
|
|
903
|
-
} ).ElseIf( mediumStackDepth.equal( int( 2 ) ), () => {
|
|
904
|
-
|
|
905
|
-
mediumStack_ior_2.assign( material.ior );
|
|
906
|
-
mediumStack_attColor_2.assign( material.attenuationColor );
|
|
907
|
-
mediumStack_attDist_2.assign( material.attenuationDistance );
|
|
908
|
-
mediumStack_sigmaA_2.assign( mSigmaA );
|
|
909
|
-
mediumStack_sigmaS_2.assign( vec3( 0.0 ) ); // glass: no scattering
|
|
910
|
-
|
|
911
|
-
} ).ElseIf( mediumStackDepth.equal( int( 3 ) ), () => {
|
|
912
|
-
|
|
913
|
-
mediumStack_ior_3.assign( material.ior );
|
|
914
|
-
mediumStack_attColor_3.assign( material.attenuationColor );
|
|
915
|
-
mediumStack_attDist_3.assign( material.attenuationDistance );
|
|
916
|
-
mediumStack_sigmaA_3.assign( mSigmaA );
|
|
917
|
-
mediumStack_sigmaS_3.assign( vec3( 0.0 ) ); // glass: no scattering
|
|
918
|
-
|
|
919
|
-
} );
|
|
920
|
-
|
|
921
|
-
} );
|
|
922
|
-
|
|
923
|
-
} ).Else( () => {
|
|
924
|
-
|
|
925
|
-
// Pop medium from stack
|
|
926
|
-
If( mediumStackDepth.greaterThan( int( 0 ) ), () => {
|
|
927
|
-
|
|
928
|
-
mediumStackDepth.subAssign( 1 );
|
|
929
|
-
|
|
930
|
-
} );
|
|
931
|
-
|
|
932
|
-
} );
|
|
933
|
-
|
|
934
|
-
} );
|
|
935
|
-
|
|
936
|
-
} ).ElseIf( interaction.isSubsurface, () => {
|
|
937
|
-
|
|
938
|
-
// Subsurface boundary: free bounce (SSS step budget, not camera bounces). Push on enter, pop on exit.
|
|
939
|
-
isFreeBounce.assign( tslBool( true ) );
|
|
940
|
-
stateRayType.assign( int( RAY_TYPE_DIFFUSE ) );
|
|
941
|
-
|
|
942
|
-
If( interaction.didReflect.not(), () => {
|
|
943
|
-
|
|
944
|
-
If( interaction.entering, () => {
|
|
945
|
-
|
|
946
|
-
If( mediumStackDepth.lessThan( int( 3 ) ), () => {
|
|
947
|
-
|
|
948
|
-
mediumStackDepth.addAssign( 1 );
|
|
949
|
-
|
|
950
|
-
const ssCoeffs = MediumCoeffs.wrap( subsurfaceCoefficients(
|
|
951
|
-
material.subsurfaceColor, material.subsurfaceRadius, material.subsurfaceRadiusScale,
|
|
952
|
-
) ).toVar();
|
|
953
|
-
const ssG = clamp( material.subsurfaceAnisotropy, - 0.99, 0.99 ).toVar();
|
|
954
|
-
|
|
955
|
-
If( mediumStackDepth.equal( int( 1 ) ), () => {
|
|
956
|
-
|
|
957
|
-
mediumStack_ior_1.assign( material.ior );
|
|
958
|
-
mediumStack_sigmaA_1.assign( ssCoeffs.sigmaA );
|
|
959
|
-
mediumStack_sigmaS_1.assign( ssCoeffs.sigmaS );
|
|
960
|
-
mediumStack_sigmaT_1.assign( ssCoeffs.sigmaT );
|
|
961
|
-
mediumStack_g_1.assign( ssG );
|
|
962
|
-
|
|
963
|
-
} ).ElseIf( mediumStackDepth.equal( int( 2 ) ), () => {
|
|
964
|
-
|
|
965
|
-
mediumStack_ior_2.assign( material.ior );
|
|
966
|
-
mediumStack_sigmaA_2.assign( ssCoeffs.sigmaA );
|
|
967
|
-
mediumStack_sigmaS_2.assign( ssCoeffs.sigmaS );
|
|
968
|
-
mediumStack_sigmaT_2.assign( ssCoeffs.sigmaT );
|
|
969
|
-
mediumStack_g_2.assign( ssG );
|
|
970
|
-
|
|
971
|
-
} ).ElseIf( mediumStackDepth.equal( int( 3 ) ), () => {
|
|
972
|
-
|
|
973
|
-
mediumStack_ior_3.assign( material.ior );
|
|
974
|
-
mediumStack_sigmaA_3.assign( ssCoeffs.sigmaA );
|
|
975
|
-
mediumStack_sigmaS_3.assign( ssCoeffs.sigmaS );
|
|
976
|
-
mediumStack_sigmaT_3.assign( ssCoeffs.sigmaT );
|
|
977
|
-
mediumStack_g_3.assign( ssG );
|
|
978
|
-
|
|
979
|
-
} );
|
|
980
|
-
|
|
981
|
-
} );
|
|
982
|
-
|
|
983
|
-
} ).Else( () => {
|
|
984
|
-
|
|
985
|
-
If( mediumStackDepth.greaterThan( int( 0 ) ), () => {
|
|
986
|
-
|
|
987
|
-
mediumStackDepth.subAssign( 1 );
|
|
988
|
-
|
|
989
|
-
} );
|
|
990
|
-
|
|
991
|
-
} );
|
|
992
|
-
|
|
993
|
-
} );
|
|
994
|
-
|
|
995
|
-
} ).ElseIf( interaction.isAlphaSkip, () => {
|
|
996
|
-
|
|
997
|
-
isFreeBounce.assign( tslBool( true ) );
|
|
998
|
-
|
|
999
|
-
} );
|
|
1000
|
-
|
|
1001
|
-
// Update ray and continue
|
|
1002
|
-
throughput.mulAssign( interaction.throughput );
|
|
1003
|
-
|
|
1004
|
-
// Transparent background: defer alpha decision to final hit/miss
|
|
1005
|
-
// Normal mode: apply material transparency alpha (blend/mask/transmission)
|
|
1006
|
-
If( transparentBackground.not(), () => {
|
|
1007
|
-
|
|
1008
|
-
alpha.mulAssign( interaction.alpha );
|
|
1009
|
-
|
|
1010
|
-
} );
|
|
1011
|
-
|
|
1012
|
-
// For reflection (Fresnel/TIR): offset along the geometric normal to stay on the same side
|
|
1013
|
-
// For transmission: offset along the old ray direction to push through the surface
|
|
1014
|
-
const reflectOffsetDir = select( interaction.entering, N, N.negate() );
|
|
1015
|
-
const offsetDir = select( interaction.didReflect, reflectOffsetDir, rayDirection );
|
|
1016
|
-
rayOrigin.assign( hitInfo.hitPoint.add( offsetDir.mul( 0.001 ) ) );
|
|
1017
|
-
rayDirection.assign( interaction.direction );
|
|
1018
|
-
|
|
1019
|
-
stateIsPrimaryRay.assign( tslBool( false ) );
|
|
1020
|
-
|
|
1021
|
-
// Reset material-dependent caches
|
|
1022
|
-
psWeightsComputed.assign( tslBool( false ) );
|
|
1023
|
-
psMaterialCacheCached.assign( tslBool( false ) );
|
|
1024
|
-
|
|
1025
|
-
If( isFreeBounce.not(), () => {
|
|
1026
|
-
|
|
1027
|
-
effectiveBounces.addAssign( 1 );
|
|
1028
|
-
|
|
1029
|
-
} );
|
|
1030
|
-
|
|
1031
|
-
Continue();
|
|
1032
|
-
|
|
1033
|
-
} );
|
|
1034
|
-
|
|
1035
|
-
// Apply transparency alpha (skip in transparent background mode — alpha is binary hit/miss)
|
|
1036
|
-
If( transparentBackground.not(), () => {
|
|
1037
|
-
|
|
1038
|
-
alpha.mulAssign( interaction.alpha );
|
|
1039
|
-
|
|
1040
|
-
} );
|
|
1041
|
-
|
|
1042
|
-
// Ray hit non-transmissive geometry — lock alpha at 1.0 for subsequent bounces
|
|
1043
|
-
hasHitOpaqueSurface.assign( tslBool( true ) );
|
|
1044
|
-
|
|
1045
|
-
const randomSample = getRandomSample( pixelCoord, rayIndex, bounceIndex, rngState, int( - 1 ), resolution, frame ).toVar();
|
|
1046
|
-
|
|
1047
|
-
const V = rayDirection.negate().toVar();
|
|
1048
|
-
|
|
1049
|
-
// Two-sided shading: flip the shading normal into the viewer's hemisphere.
|
|
1050
|
-
// This is the opaque reflection path (transmissive rays already Continue'd),
|
|
1051
|
-
// so it never disturbs dielectric entering/exiting. No-op when N already
|
|
1052
|
-
// faces V; rescues meshes with inward-facing normals (common in imported
|
|
1053
|
-
// scenes, e.g. pbrt PLY assets) that would otherwise shade black.
|
|
1054
|
-
If( dot( N, V ).lessThan( 0.0 ), () => {
|
|
1055
|
-
|
|
1056
|
-
N.assign( N.negate() );
|
|
1057
|
-
|
|
1058
|
-
} );
|
|
1059
|
-
|
|
1060
|
-
material.sheenRoughness.assign( clamp( material.sheenRoughness, MIN_ROUGHNESS, MAX_ROUGHNESS ) );
|
|
1061
|
-
|
|
1062
|
-
// Sync material classification cache up front — the materialCache, BRDF
|
|
1063
|
-
// sample, importance sampling, and Russian roulette all consume it.
|
|
1064
|
-
// getOrCreateMaterialClassification is a cache hit when materialIndex
|
|
1065
|
-
// matches the previous bounce; otherwise it runs classifyMaterial once.
|
|
1066
|
-
// Doing this here eliminates a redundant classifyMaterial that previously
|
|
1067
|
-
// fired after generateSampledDirection to "sync" the caller's variable.
|
|
1068
|
-
psCachedClassification.assign( MaterialClassification.wrap( getOrCreateMaterialClassification(
|
|
1069
|
-
material, hitInfo.materialIndex,
|
|
1070
|
-
psClassificationCached, psLastMaterialIndex, psCachedClassification,
|
|
1071
|
-
) ) );
|
|
1072
|
-
psClassificationCached.assign( tslBool( true ) );
|
|
1073
|
-
psLastMaterialIndex.assign( hitInfo.materialIndex );
|
|
1074
|
-
|
|
1075
|
-
// Create material cache if needed
|
|
1076
|
-
If( psMaterialCacheCached.not(), () => {
|
|
1077
|
-
|
|
1078
|
-
psCachedMaterialCache.assign( createMaterialCache( N, V, material, matSamples, psCachedClassification ) );
|
|
1079
|
-
psMaterialCacheCached.assign( tslBool( true ) );
|
|
1080
|
-
|
|
1081
|
-
} );
|
|
1082
|
-
|
|
1083
|
-
// BRDF sampling
|
|
1084
|
-
const brdfDir = vec3( 0.0 ).toVar();
|
|
1085
|
-
const brdfValue = vec3( 0.0 ).toVar();
|
|
1086
|
-
const brdfPdf = float( 0.0 ).toVar();
|
|
1087
|
-
|
|
1088
|
-
// Handle clearcoat
|
|
1089
|
-
If( material.clearcoat.greaterThan( 0.0 ), () => {
|
|
1090
|
-
|
|
1091
|
-
const ccResult = ClearcoatResult.wrap( sampleClearcoat(
|
|
1092
|
-
currentRay, hitInfo, material, randomSample, rngState,
|
|
1093
|
-
) );
|
|
1094
|
-
brdfDir.assign( ccResult.L );
|
|
1095
|
-
brdfValue.assign( ccResult.brdf );
|
|
1096
|
-
brdfPdf.assign( ccResult.pdf );
|
|
1097
|
-
|
|
1098
|
-
} ).Else( () => {
|
|
1099
|
-
|
|
1100
|
-
// Classification was already synced at the top of the bounce — pass
|
|
1101
|
-
// psCachedClassification directly so generateSampledDirection doesn't
|
|
1102
|
-
// have to call classifyMaterial again internally.
|
|
1103
|
-
const brdfSample = DirectionSample.wrap( generateSampledDirection(
|
|
1104
|
-
V, N, material, randomSample, rngState,
|
|
1105
|
-
psCachedClassification,
|
|
1106
|
-
psWeightsComputed, psCachedBrdfWeights,
|
|
1107
|
-
psMaterialCacheCached, psCachedMaterialCache,
|
|
1108
|
-
) );
|
|
1109
|
-
brdfDir.assign( brdfSample.direction );
|
|
1110
|
-
brdfValue.assign( brdfSample.value );
|
|
1111
|
-
brdfPdf.assign( brdfSample.pdf );
|
|
1112
|
-
|
|
1113
|
-
psWeightsComputed.assign( tslBool( true ) );
|
|
1114
|
-
|
|
1115
|
-
} );
|
|
1116
|
-
|
|
1117
|
-
// 1. EMISSIVE CONTRIBUTION (with MIS when direct emissive sampling is active)
|
|
1118
|
-
If( length( matSamples.emissive ).greaterThan( 0.0 ), () => {
|
|
1119
|
-
|
|
1120
|
-
const emissiveMISWeight = float( 1.0 ).toVar();
|
|
1121
|
-
|
|
1122
|
-
// Apply MIS when emissive direct sampling is active and this isn't a camera ray hit
|
|
1123
|
-
If( enableEmissiveTriangleSampling.equal( int( 1 ) )
|
|
1124
|
-
.and( emissiveTriangleCount.greaterThan( int( 0 ) ) )
|
|
1125
|
-
.and( prevBouncePdf.greaterThan( 0.0 ) ), () => {
|
|
1126
|
-
|
|
1127
|
-
const lightPdf = calculateEmissiveLightPdf(
|
|
1128
|
-
hitInfo.triangleIndex, hitInfo.dst, rayDirection, rayOrigin,
|
|
1129
|
-
triangleBuffer, materialBuffer, emissiveTotalPower,
|
|
1130
|
-
);
|
|
1131
|
-
|
|
1132
|
-
emissiveMISWeight.assign(
|
|
1133
|
-
powerHeuristic( { pdf1: prevBouncePdf, pdf2: lightPdf } )
|
|
1134
|
-
);
|
|
1135
|
-
|
|
1136
|
-
} );
|
|
1137
|
-
|
|
1138
|
-
radiance.addAssign( regularizePathContribution( {
|
|
1139
|
-
contribution: matSamples.emissive.mul( throughput ).mul( giScale ).mul( emissiveMISWeight ),
|
|
1140
|
-
pathLength: float( bounceIndex ), fireflyThreshold, frame: int( frame ),
|
|
1141
|
-
} ) );
|
|
1142
|
-
|
|
1143
|
-
} );
|
|
1144
|
-
|
|
1145
|
-
// 2. DIRECT LIGHTING
|
|
1146
|
-
const directLight = calculateDirectLightingUnified(
|
|
1147
|
-
hitInfo.hitPoint, N, material,
|
|
1148
|
-
V,
|
|
1149
|
-
brdfDir, brdfPdf, brdfValue,
|
|
1150
|
-
bounceIndex, rngState,
|
|
1151
|
-
directionalLightsBuffer, numDirectionalLights,
|
|
1152
|
-
areaLightsBuffer, numAreaLights,
|
|
1153
|
-
pointLightsBuffer, numPointLights,
|
|
1154
|
-
spotLightsBuffer, numSpotLights,
|
|
1155
|
-
bvhBuffer,
|
|
1156
|
-
triangleBuffer,
|
|
1157
|
-
materialBuffer,
|
|
1158
|
-
envTexture, environmentIntensity, envMatrix,
|
|
1159
|
-
envCDFBuffer,
|
|
1160
|
-
envTotalSum, envCompensationDelta, envResolution,
|
|
1161
|
-
enableEnvironmentLight,
|
|
1162
|
-
);
|
|
1163
|
-
|
|
1164
|
-
radiance.addAssign( regularizePathContribution( {
|
|
1165
|
-
contribution: directLight.mul( throughput ).mul( giScale ), pathLength: float( bounceIndex ), fireflyThreshold, frame: int( frame ),
|
|
1166
|
-
} ) );
|
|
1167
|
-
|
|
1168
|
-
// 2b. EMISSIVE TRIANGLE DIRECT LIGHTING
|
|
1169
|
-
If( enableEmissiveTriangleSampling.equal( int( 1 ) ).and( emissiveTriangleCount.greaterThan( int( 0 ) ) ), () => {
|
|
1170
|
-
|
|
1171
|
-
const traceShadowRayWrapped = Fn( ( [ origin, dir, maxDist ] ) => {
|
|
1172
|
-
|
|
1173
|
-
return traceShadowRay( origin, dir, maxDist, traverseBVHShadow, bvhBuffer, triangleBuffer, materialBuffer );
|
|
1174
|
-
|
|
1175
|
-
} );
|
|
1176
|
-
|
|
1177
|
-
If( lightBVHNodeCount.greaterThan( int( 0 ) ), () => {
|
|
1178
|
-
|
|
1179
|
-
// Use Light BVH for spatially-aware importance sampling
|
|
1180
|
-
const emissiveSample = EmissiveSample.wrap( sampleLightBVHTriangle(
|
|
1181
|
-
hitInfo.hitPoint, N,
|
|
1182
|
-
rngState,
|
|
1183
|
-
lightBVHBuffer,
|
|
1184
|
-
emissiveTriangleBuffer,
|
|
1185
|
-
emissiveVec4Offset,
|
|
1186
|
-
triangleBuffer,
|
|
1187
|
-
) );
|
|
1188
|
-
|
|
1189
|
-
// Skip for very rough diffuse surfaces on secondary bounces
|
|
1190
|
-
const skip = bounceIndex.greaterThan( int( 1 ) )
|
|
1191
|
-
.and( material.roughness.greaterThan( 0.9 ) )
|
|
1192
|
-
.and( material.metalness.lessThan( 0.1 ) );
|
|
1193
|
-
|
|
1194
|
-
If( skip.not().and( emissiveSample.valid ).and( emissiveSample.pdf.greaterThan( 0.0 ) ), () => {
|
|
1195
|
-
|
|
1196
|
-
const NoL = max( float( 0.0 ), dot( N, emissiveSample.direction ) );
|
|
1197
|
-
|
|
1198
|
-
If( NoL.greaterThan( 0.0 ), () => {
|
|
1199
|
-
|
|
1200
|
-
const rayOffset = calculateRayOffset( hitInfo.hitPoint, N, material );
|
|
1201
|
-
const rayOrigin = hitInfo.hitPoint.add( rayOffset );
|
|
1202
|
-
const shadowDist = emissiveSample.distance.sub( 0.001 );
|
|
1203
|
-
const visibility = traceShadowRayWrapped( rayOrigin, emissiveSample.direction, shadowDist );
|
|
1204
|
-
|
|
1205
|
-
If( visibility.greaterThan( 0.0 ), () => {
|
|
1206
|
-
|
|
1207
|
-
// Share H + dot products between BRDF eval and PDF eval.
|
|
1208
|
-
const emisDots = DotProducts.wrap( computeDotProducts( N, V, emissiveSample.direction ) );
|
|
1209
|
-
const brdfValue = evaluateMaterialResponseFromDots( material, emisDots );
|
|
1210
|
-
const brdfPdf = calculateMaterialPDFFromDots( material, emisDots );
|
|
1211
|
-
const misWeight = select(
|
|
1212
|
-
brdfPdf.greaterThan( 0.0 ),
|
|
1213
|
-
powerHeuristic( { pdf1: emissiveSample.pdf, pdf2: brdfPdf } ),
|
|
1214
|
-
float( 1.0 )
|
|
1215
|
-
);
|
|
1216
|
-
|
|
1217
|
-
const emissiveLight = emissiveSample.emission
|
|
1218
|
-
.mul( brdfValue ).mul( NoL )
|
|
1219
|
-
.div( emissiveSample.pdf )
|
|
1220
|
-
.mul( visibility ).mul( emissiveBoost ).mul( misWeight );
|
|
1221
|
-
|
|
1222
|
-
radiance.addAssign( regularizePathContribution( {
|
|
1223
|
-
contribution: emissiveLight.mul( throughput ).mul( giScale ), pathLength: float( bounceIndex ), fireflyThreshold, frame: int( frame ),
|
|
1224
|
-
} ) );
|
|
1225
|
-
|
|
1226
|
-
} );
|
|
1227
|
-
|
|
1228
|
-
} );
|
|
1229
|
-
|
|
1230
|
-
} );
|
|
1231
|
-
|
|
1232
|
-
} ).Else( () => {
|
|
1233
|
-
|
|
1234
|
-
// Fallback: flat CDF importance sampling
|
|
1235
|
-
const emissiveLight = calculateEmissiveTriangleContribution(
|
|
1236
|
-
hitInfo.hitPoint, N, V, material,
|
|
1237
|
-
bounceIndex, rngState,
|
|
1238
|
-
emissiveBoost,
|
|
1239
|
-
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
1240
|
-
triangleBuffer,
|
|
1241
|
-
traceShadowRayWrapped,
|
|
1242
|
-
calculateRayOffset,
|
|
1243
|
-
);
|
|
1244
|
-
|
|
1245
|
-
radiance.addAssign( regularizePathContribution( {
|
|
1246
|
-
contribution: emissiveLight.mul( throughput ).mul( giScale ), pathLength: float( bounceIndex ), fireflyThreshold, frame: int( frame ),
|
|
1247
|
-
} ) );
|
|
1248
|
-
|
|
1249
|
-
} );
|
|
1250
|
-
|
|
1251
|
-
} );
|
|
1252
|
-
|
|
1253
|
-
// Classification was already synced at the top of the bounce loop, so
|
|
1254
|
-
// psCachedClassification is current here regardless of which BRDF sample
|
|
1255
|
-
// path ran above.
|
|
1256
|
-
const samplingInfo = ImportanceSamplingInfo.wrap( getImportanceSamplingInfo(
|
|
1257
|
-
material, bounceIndex, psCachedClassification,
|
|
1258
|
-
) );
|
|
1259
|
-
|
|
1260
|
-
// 3. INDIRECT LIGHTING
|
|
1261
|
-
const indirectResult = IndirectLightingResult.wrap( calculateIndirectLighting(
|
|
1262
|
-
V, N, material,
|
|
1263
|
-
brdfDir, brdfPdf, brdfValue,
|
|
1264
|
-
rngState,
|
|
1265
|
-
samplingInfo,
|
|
1266
|
-
) );
|
|
1267
|
-
throughput.mulAssign( indirectResult.throughput );
|
|
1268
|
-
|
|
1269
|
-
// Prepare for next bounce
|
|
1270
|
-
rayOrigin.assign( hitInfo.hitPoint.add( N.mul( 0.001 ) ) );
|
|
1271
|
-
rayDirection.assign( indirectResult.direction );
|
|
1272
|
-
prevBouncePdf.assign( indirectResult.combinedPdf );
|
|
1273
|
-
|
|
1274
|
-
stateIsPrimaryRay.assign( tslBool( false ) );
|
|
1275
|
-
|
|
1276
|
-
// Determine ray type
|
|
1277
|
-
If( material.metalness.greaterThan( 0.7 ).and( material.roughness.lessThan( 0.3 ) ), () => {
|
|
1278
|
-
|
|
1279
|
-
stateRayType.assign( int( RAY_TYPE_REFLECTION ) );
|
|
1280
|
-
|
|
1281
|
-
} ).ElseIf( material.transmission.greaterThan( 0.5 ), () => {
|
|
1282
|
-
|
|
1283
|
-
stateRayType.assign( int( RAY_TYPE_TRANSMISSION ) );
|
|
1284
|
-
|
|
1285
|
-
} ).Else( () => {
|
|
1286
|
-
|
|
1287
|
-
stateRayType.assign( int( RAY_TYPE_DIFFUSE ) );
|
|
1288
|
-
|
|
1289
|
-
} );
|
|
1290
|
-
|
|
1291
|
-
// firstHitPoint / firstHitDistance reflect the actual primary-ray hit
|
|
1292
|
-
// (used for depth + temporal reprojection — must not skip transparent surfaces)
|
|
1293
|
-
If( bounceIndex.equal( int( 0 ) ).and( hitInfo.didHit ), () => {
|
|
1294
|
-
|
|
1295
|
-
firstHitPoint.assign( hitInfo.hitPoint );
|
|
1296
|
-
firstHitDistance.assign( hitInfo.dst );
|
|
1297
|
-
|
|
1298
|
-
} );
|
|
1299
|
-
|
|
1300
|
-
// objectNormal / objectColor / objectID feed OIDN's aux inputs. Overwrite
|
|
1301
|
-
// at each bounce until we land on a non-specular surface, then lock.
|
|
1302
|
-
// Falls back to the last specular hit if the path never hits diffuse.
|
|
1303
|
-
If( auxLocked.not().and( hitInfo.didHit ), () => {
|
|
1304
|
-
|
|
1305
|
-
objectNormal.assign( N );
|
|
1306
|
-
objectColor.assign( material.color.xyz );
|
|
1307
|
-
objectID.assign( float( hitInfo.materialIndex ) );
|
|
1308
|
-
|
|
1309
|
-
const isMirror = material.metalness.greaterThan( 0.7 ).and( material.roughness.lessThan( 0.3 ) );
|
|
1310
|
-
const isTransmissive = material.transmission.greaterThan( 0.5 );
|
|
1311
|
-
If( isMirror.or( isTransmissive ).not(), () => {
|
|
1312
|
-
|
|
1313
|
-
auxLocked.assign( tslBool( true ) );
|
|
1314
|
-
|
|
1315
|
-
} );
|
|
1316
|
-
|
|
1317
|
-
} );
|
|
1318
|
-
|
|
1319
|
-
// 4. RUSSIAN ROULETTE
|
|
1320
|
-
const rrSurvivalProb = handleRussianRoulette(
|
|
1321
|
-
bounceIndex, throughput, material, hitInfo.materialIndex,
|
|
1322
|
-
rayDirection, rngState,
|
|
1323
|
-
psClassificationCached, psLastMaterialIndex, psCachedClassification,
|
|
1324
|
-
enableEnvironmentLight, useEnvMapIS,
|
|
1325
|
-
);
|
|
1326
|
-
If( rrSurvivalProb.lessThanEqual( 0.0 ), () => {
|
|
1327
|
-
|
|
1328
|
-
Break();
|
|
1329
|
-
|
|
1330
|
-
} );
|
|
1331
|
-
// Apply throughput compensation
|
|
1332
|
-
throughput.divAssign( rrSurvivalProb );
|
|
1333
|
-
|
|
1334
|
-
// Increment effective bounces
|
|
1335
|
-
effectiveBounces.addAssign( 1 );
|
|
1336
|
-
|
|
1337
|
-
// Reset per-bounce caches so next iteration recomputes for its own material
|
|
1338
|
-
psWeightsComputed.assign( tslBool( false ) );
|
|
1339
|
-
psMaterialCacheCached.assign( tslBool( false ) );
|
|
1340
|
-
|
|
1341
|
-
} );
|
|
1342
|
-
|
|
1343
|
-
return TraceResult( {
|
|
1344
|
-
radiance: vec4( radiance, alpha ),
|
|
1345
|
-
objectNormal,
|
|
1346
|
-
objectColor,
|
|
1347
|
-
objectID,
|
|
1348
|
-
firstHitPoint,
|
|
1349
|
-
firstHitDistance,
|
|
1350
|
-
} );
|
|
1351
|
-
|
|
1352
|
-
} );
|