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