three-gpu-pathtracer 0.0.27 → 0.0.28
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 +0 -1
- package/build/index.module.js +5 -3
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +4 -2
- package/build/index.umd.cjs.map +1 -1
- package/package.json +7 -7
- package/src/core/PathTracingSceneGenerator.js +1 -1
- package/src/uniforms/EquirectHdrInfoUniform.js +4 -2
- package/src/webgpu/API.md +167 -19
- package/src/webgpu/AtlasTexture.js +2 -3
- package/src/webgpu/EquirectHdrInfoNode.js +89 -48
- package/src/webgpu/LightsInfoNode.js +1 -1
- package/src/webgpu/MegaKernelPathTracer.js +10 -3
- package/src/webgpu/PathTracerBackend.js +172 -21
- package/src/webgpu/WaveFrontPathTracer.js +156 -71
- package/src/webgpu/WebGPUPathTracer.js +628 -48
- package/src/webgpu/compute/ComputeKernel.js +8 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +187 -36
- package/src/webgpu/compute/TallySampleCountsKernel.js +10 -10
- package/src/webgpu/compute/UnpackAuxKernel.js +48 -0
- package/src/webgpu/compute/ZeroOutKernel.js +3 -1
- package/src/webgpu/compute/targets.js +55 -0
- package/src/webgpu/compute/wavefront/LogicKernel.js +67 -20
- package/src/webgpu/compute/wavefront/MaterialKernel.js +153 -66
- package/src/webgpu/compute/wavefront/MediumKernel.js +154 -0
- package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +5 -5
- package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +5 -5
- package/src/webgpu/compute/wavefront/QueueMediumWalksKernel.js +52 -0
- package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +6 -6
- package/src/webgpu/compute/wavefront/TraceRayKernel.js +11 -9
- package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +18 -25
- package/src/webgpu/compute/wavefront/structs.js +63 -37
- package/src/webgpu/constants.js +2 -2
- package/src/webgpu/denoise/OIDNDenoiser.js +81 -78
- package/src/webgpu/index.d.ts +41 -6
- package/src/webgpu/materials/GltfCompliantMaterial.js +20 -6
- package/src/webgpu/materials/debug/AuxDebugMaterial.js +54 -0
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +35 -13
- package/src/webgpu/nodes/aux.wgsl.js +61 -0
- package/src/webgpu/nodes/eon.wgsl.js +1 -1
- package/src/webgpu/nodes/material.wgsl.js +2 -1
- package/src/webgpu/nodes/medium.wgsl.js +165 -0
- package/src/webgpu/nodes/random.wgsl.js +2 -0
- package/src/webgpu/nodes/sampling.wgsl.js +44 -3
- package/src/webgpu/nodes/structs.wgsl.js +7 -1
- package/src/webgpu/upscale/FSRUpscaler.js +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { StorageBufferAttribute
|
|
1
|
+
import { StorageBufferAttribute } from 'three/webgpu';
|
|
2
2
|
import { ComputeKernel } from '../ComputeKernel.js';
|
|
3
|
+
import { createAuxTarget, createOutputTarget, createSampleCountTarget } from '../targets.js';
|
|
4
|
+
import { blendAuxFn, AUX_LAYERS } from '../../nodes/aux.wgsl.js';
|
|
3
5
|
import { uniform, storage, textureStore, globalId } from 'three/tsl';
|
|
4
6
|
import { proxy, proxyFn, wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
5
7
|
import { misHeuristicFn, weightedAlphaBlendFn } from '../../nodes/sampling.wgsl.js';
|
|
@@ -12,7 +14,7 @@ import {
|
|
|
12
14
|
} from '../../nodes/random.wgsl.js';
|
|
13
15
|
import { ENVIRONMENT_LIGHT_TYPE, LIGHT_FAR_DISTANCE, isMISWeightLightFn } from '../../nodes/lights.wgsl.js';
|
|
14
16
|
import { lightRecordStruct, scatterRecordStruct } from '../../nodes/structs.wgsl.js';
|
|
15
|
-
import { rayDataStruct, intersectionResultStruct } from './structs.js';
|
|
17
|
+
import { rayDataStruct, intersectionResultStruct, shadowTraceResultStruct } from './structs.js';
|
|
16
18
|
import { SAMPLE_COUNT_MASK, SAMPLE_DISPATCHED_FLAG } from '../../constants.js';
|
|
17
19
|
|
|
18
20
|
// Path logic over the persistent slot pool: resolves the previous frame's shadow and bounce trace
|
|
@@ -28,9 +30,12 @@ export class LogicKernel extends ComputeKernel {
|
|
|
28
30
|
lightsInfo: { value: null },
|
|
29
31
|
|
|
30
32
|
// targets
|
|
31
|
-
prevOutputTarget: textureStore(
|
|
32
|
-
outputTarget: textureStore(
|
|
33
|
-
sampleCountTarget: textureStore(
|
|
33
|
+
prevOutputTarget: textureStore( createOutputTarget() ).toReadOnly(),
|
|
34
|
+
outputTarget: textureStore( createOutputTarget() ).toWriteOnly(),
|
|
35
|
+
sampleCountTarget: textureStore( createSampleCountTarget() ).toReadWrite(),
|
|
36
|
+
|
|
37
|
+
// the per pixel first hit albedo and normal, as running means like the color
|
|
38
|
+
auxTarget: textureStore( createAuxTarget() ).toReadWrite(),
|
|
34
39
|
|
|
35
40
|
// settings
|
|
36
41
|
misEnabled: uniform( 1, 'uint' ),
|
|
@@ -39,10 +44,11 @@ export class LogicKernel extends ComputeKernel {
|
|
|
39
44
|
clampDirect: uniform( 0 ),
|
|
40
45
|
clampIndirect: uniform( 10 ),
|
|
41
46
|
transmissiveBackground: uniform( TRANSMISSIVE_BACKGROUND_OVERLAY ),
|
|
47
|
+
accumulateAux: uniform( 0, 'uint' ),
|
|
42
48
|
|
|
43
49
|
rayDataStorage: storage( new StorageBufferAttribute( 1, 1 ), rayDataStruct ),
|
|
44
50
|
rayIntersectionsStorage: storage( new StorageBufferAttribute( 1, 1 ), intersectionResultStruct ),
|
|
45
|
-
shadowRayIntersectionsStorage: storage( new StorageBufferAttribute( 1, 1 ),
|
|
51
|
+
shadowRayIntersectionsStorage: storage( new StorageBufferAttribute( 1, 1 ), shadowTraceResultStruct ),
|
|
46
52
|
|
|
47
53
|
globalId: globalId,
|
|
48
54
|
};
|
|
@@ -75,6 +81,7 @@ export class LogicKernel extends ComputeKernel {
|
|
|
75
81
|
clampDirect: f32,
|
|
76
82
|
clampIndirect: f32,
|
|
77
83
|
transmissiveBackground: u32,
|
|
84
|
+
accumulateAux: u32,
|
|
78
85
|
|
|
79
86
|
globalId: vec3u
|
|
80
87
|
) -> void {
|
|
@@ -92,7 +99,8 @@ export class LogicKernel extends ComputeKernel {
|
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
// skip slots that have never spawned a ray
|
|
95
|
-
|
|
102
|
+
// whole structs are read from the buffer, not the pointer alias, which Safari fails to compile
|
|
103
|
+
let input = ${ params.rayDataStorage }[ index ];
|
|
96
104
|
if ( input.rayIntersectionIndex < 0 ) {
|
|
97
105
|
|
|
98
106
|
return;
|
|
@@ -100,7 +108,7 @@ export class LogicKernel extends ComputeKernel {
|
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
let indexUV = vec2u( input.pixelIndex >> 16, input.pixelIndex & 0xFFFF );
|
|
103
|
-
${ rngInit }( indexUV, input.seed, input.currentBounce + input.alphaDepth );
|
|
111
|
+
${ rngInit }( indexUV, input.seed, input.currentBounce + input.alphaDepth + input.subsurfaceBounces );
|
|
104
112
|
|
|
105
113
|
// one-sample NEE chooses between the analytic lights and the environment, matched with
|
|
106
114
|
// the megakernel
|
|
@@ -124,15 +132,16 @@ export class LogicKernel extends ComputeKernel {
|
|
|
124
132
|
// is negative when no shadow ray was enqueued last frame
|
|
125
133
|
if ( input.shadowRayIntersectionIndex >= 0 && input.lightPdf > 0.0 ) {
|
|
126
134
|
|
|
127
|
-
let shadowHit =
|
|
135
|
+
let shadowHit = ${ params.shadowRayIntersectionsStorage }[ u32( input.shadowRayIntersectionIndex ) ];
|
|
128
136
|
let occluded = shadowHit.objectIndex >= 0;
|
|
129
137
|
if ( ! occluded ) {
|
|
130
138
|
|
|
131
|
-
// env + area lights are also bsdf
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
let
|
|
135
|
-
let
|
|
139
|
+
// env + area lights are also found by bsdf rays, so MIS-weight them. Punctual lights, and lights
|
|
140
|
+
// past the bounce limit, take full weight. Bsdf rays stopped by faces the shadow ray skipped can't
|
|
141
|
+
// find the light, so their share of the weight moves to the light sample
|
|
142
|
+
let isMISWeighted = ${ isMISWeightLightFn }( input.lightType ) && input.currentBounce < maxBounces;
|
|
143
|
+
let misWeight = select( 1.0, mix( ${ misHeuristicFn }( input.lightPdf, input.lightBsdfPdf ), 1.0, shadowHit.skippedOcclusion ), isMISWeighted );
|
|
144
|
+
let directLight = throughputColor * input.lightEmission * input.lightBsdf * misWeight * ( 1.0 - shadowHit.occlusion ) / input.lightPdf;
|
|
136
145
|
let contribution = ${ clampPathContributionFunc }( directLight, input.currentBounce, clampDirect, clampIndirect );
|
|
137
146
|
resultColor += vec4f( contribution, 0.0 );
|
|
138
147
|
|
|
@@ -154,17 +163,33 @@ export class LogicKernel extends ComputeKernel {
|
|
|
154
163
|
// MaterialKernel, which stages a zeroed pdf and skips the bounce trace when they fire
|
|
155
164
|
var isTerminated = all( throughputColor == vec3f( 0.0 ) ) || input.currentBounce >= maxBounces || ${ isTerminatingScatterFunc }( scatterRec );
|
|
156
165
|
|
|
166
|
+
// apply the scatter across the traced segment, including the weight of a medium walk
|
|
157
167
|
if ( ! isTerminated ) {
|
|
158
168
|
|
|
159
|
-
// apply the scatter across the traced segment
|
|
160
169
|
throughputColor *= scatterRec.color / scatterRec.pdf;
|
|
161
170
|
|
|
162
|
-
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let isWalking = ! isTerminated && input.walkPending != 0u;
|
|
174
|
+
if ( isWalking ) {
|
|
175
|
+
|
|
176
|
+
// the walk hasn't reached a surface yet, so there's no hit to stage. Clear what was
|
|
177
|
+
// resolved and let MediumKernel accumulate the walk's weight
|
|
178
|
+
rayDataStorage[ index ].emission = vec3f( 0.0 );
|
|
179
|
+
rayDataStorage[ index ].lightPdf = 0.0;
|
|
180
|
+
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
181
|
+
rayDataStorage[ index ].scatterColor = vec3f( 1.0 );
|
|
182
|
+
rayDataStorage[ index ].scatterPdf = 1.0;
|
|
183
|
+
|
|
184
|
+
} else if ( ! isTerminated ) {
|
|
185
|
+
|
|
186
|
+
let hitResult = ${ params.rayIntersectionsStorage }[ u32( input.rayIntersectionIndex ) ];
|
|
163
187
|
let didHit = hitResult.objectIndex >= 0;
|
|
164
188
|
let surfaceDist = select( ${ LIGHT_FAR_DISTANCE }, hitResult.dist, didHit );
|
|
165
189
|
|
|
166
|
-
// NEE's total weight at the vertex this segment left, for MIS. Camera segments have none
|
|
167
|
-
|
|
190
|
+
// NEE's total weight at the vertex this segment left, for MIS. Camera segments have none,
|
|
191
|
+
// and neither do scatter events in a medium
|
|
192
|
+
let isMISWeighted = misEnabled != 0u && input.currentBounce > 0u && input.mediumScattered == 0u;
|
|
168
193
|
var lightTotalWeight = 0.0;
|
|
169
194
|
if ( isMISWeighted ) {
|
|
170
195
|
|
|
@@ -172,7 +197,8 @@ export class LogicKernel extends ComputeKernel {
|
|
|
172
197
|
|
|
173
198
|
}
|
|
174
199
|
|
|
175
|
-
// forward hits on area lights
|
|
200
|
+
// forward hits on area lights along the traced segment, which starts "rayStart" along the ray.
|
|
201
|
+
// Camera rays only see visibleToCameraRays lights, at full weight
|
|
176
202
|
for ( var li = 0u; li < lightsCount; li ++ ) {
|
|
177
203
|
|
|
178
204
|
if ( input.currentBounce == 0u && ! ${ isLightVisibleToCameraFn }( li ) ) {
|
|
@@ -182,7 +208,7 @@ export class LogicKernel extends ComputeKernel {
|
|
|
182
208
|
}
|
|
183
209
|
|
|
184
210
|
var lightRec: ${ lightRecordStruct };
|
|
185
|
-
if ( ${ intersectLightAtIndexFn }( input.origin, input.direction, li, &lightRec ) && lightRec.dist < surfaceDist ) {
|
|
211
|
+
if ( ${ intersectLightAtIndexFn }( input.origin, input.direction, li, &lightRec ) && lightRec.dist > input.rayStart && lightRec.dist < input.rayStart + surfaceDist ) {
|
|
186
212
|
|
|
187
213
|
var misWeight = 1.0;
|
|
188
214
|
if ( isMISWeighted ) {
|
|
@@ -355,6 +381,27 @@ export class LogicKernel extends ComputeKernel {
|
|
|
355
381
|
textureStore( ${ params.sampleCountTarget }, indexUV, vec4( ${ SAMPLE_DISPATCHED_FLAG }u | sampleCount ) );
|
|
356
382
|
textureStore( ${ params.outputTarget }, colorIndex, blendedColor );
|
|
357
383
|
|
|
384
|
+
// the auxiliary images are blended the same way, in the same row order
|
|
385
|
+
if ( accumulateAux != 0u ) {
|
|
386
|
+
|
|
387
|
+
var auxPacked: vec3u;
|
|
388
|
+
for ( var layer = 0u; layer < ${ AUX_LAYERS }u; layer ++ ) {
|
|
389
|
+
|
|
390
|
+
auxPacked[ layer ] = textureLoad( ${ params.auxTarget }, colorIndex, layer ).r;
|
|
391
|
+
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
let albedo = unpack4x8unorm( input.auxAlbedo ).rgb;
|
|
395
|
+
let normal = unpack4x8snorm( input.auxNormal ).xyz;
|
|
396
|
+
auxPacked = ${ blendAuxFn }( auxPacked, albedo, normal, 1.0 / f32( sampleCount ) );
|
|
397
|
+
for ( var layer = 0u; layer < ${ AUX_LAYERS }u; layer ++ ) {
|
|
398
|
+
|
|
399
|
+
textureStore( ${ params.auxTarget }, colorIndex, layer, vec4u( auxPacked[ layer ] ) );
|
|
400
|
+
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
}
|
|
404
|
+
|
|
358
405
|
rayDataStorage[ index ].objectIndex = - 1;
|
|
359
406
|
|
|
360
407
|
} else {
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { Vector2 } from 'three';
|
|
2
|
-
import { StorageBufferAttribute
|
|
2
|
+
import { StorageBufferAttribute } from 'three/webgpu';
|
|
3
3
|
import { ComputeKernel } from '../ComputeKernel.js';
|
|
4
|
+
import { createSampleCountTarget } from '../targets.js';
|
|
4
5
|
import { uniform, storage, textureStore, globalId } from 'three/tsl';
|
|
5
|
-
import {
|
|
6
|
+
import { proxyFn, contextProxy, contextProxyFn, rayStruct, wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
6
7
|
import { rngInit, rand1, rand2, RNG_INDEX_RAY_JITTER, RNG_INDEX_ALPHA_TEST, RNG_INDEX_RUSSIAN_ROULETTE, RNG_INDEX_DISPERSION_WAVELENGTH } from '../../nodes/random.wgsl.js';
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
rayDataStruct, traceQueuedRayStruct,
|
|
10
|
+
RAY_QUEUE_LENGTH, SHADOW_RAY_QUEUE_LENGTH, PIXEL_QUEUE_CURRENT, PIXEL_QUEUE_ELEMENT_COUNT, PIXEL_QUEUE_HEADER,
|
|
11
|
+
} from './structs.js';
|
|
12
|
+
import { enterMediumFn, loadMediumFn, storeMediumFn } from '../../nodes/medium.wgsl.js';
|
|
8
13
|
import { SAMPLE_ACTIVE_FLAG, SAMPLE_COUNT_MASK, SAMPLE_DISPATCHED_FLAG } from '../../constants.js';
|
|
9
14
|
import { applyDispersionFunc, dispersionColorWeightFunc, DISPERSION_MIN_WAVELENGTH, DISPERSION_MAX_WAVELENGTH, transmissionAttenuationFunc } from '../../nodes/material.wgsl.js';
|
|
10
15
|
import { isTerminatingScatterFunc, offsetRayOriginFunc } from '../../nodes/utils.wgsl.js';
|
|
11
16
|
import { LIGHT_EPSILON } from '../../nodes/lights.wgsl.js';
|
|
17
|
+
import { isAuxSurfaceFn } from '../../nodes/aux.wgsl.js';
|
|
12
18
|
|
|
13
19
|
// Pure material evaluation and ray generation: terminated slots pull a recycled pixel and emit a
|
|
14
20
|
// fresh camera ray; live slots evaluate the surface staged by LogicKernel, sample the bsdf, and
|
|
@@ -18,7 +24,6 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
18
24
|
constructor( ) {
|
|
19
25
|
|
|
20
26
|
const params = {
|
|
21
|
-
bvhData: { value: null },
|
|
22
27
|
material: { value: null },
|
|
23
28
|
|
|
24
29
|
seed: uniform( 0, 'uint' ),
|
|
@@ -29,21 +34,24 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
29
34
|
maxTransparentBounces: uniform( 5, 'uint' ),
|
|
30
35
|
maxBounces: uniform( 5, 'uint' ),
|
|
31
36
|
|
|
32
|
-
sampleCountTarget: textureStore(
|
|
37
|
+
sampleCountTarget: textureStore( createSampleCountTarget() ).toReadWrite(),
|
|
33
38
|
|
|
34
39
|
rayDataStorage: storage( new StorageBufferAttribute( 1, 1 ), rayDataStruct ),
|
|
35
|
-
rayQueue: storage( new StorageBufferAttribute( 1, 1 ),
|
|
36
|
-
shadowRayQueue: storage( new StorageBufferAttribute( 1, 1 ),
|
|
37
|
-
|
|
40
|
+
rayQueue: storage( new StorageBufferAttribute( 1, 1 ), traceQueuedRayStruct ),
|
|
41
|
+
shadowRayQueue: storage( new StorageBufferAttribute( 1, 1 ), traceQueuedRayStruct ),
|
|
42
|
+
queueCounters: storage( new StorageBufferAttribute( 1, 1 ), 'uint' ).toAtomic(),
|
|
43
|
+
pixelQueue: storage( new StorageBufferAttribute( 1, 1 ), 'uint' ).toAtomic(),
|
|
38
44
|
|
|
39
45
|
globalId: globalId,
|
|
40
46
|
};
|
|
41
47
|
|
|
42
|
-
const getCameraRayFn =
|
|
43
|
-
const sampleTrianglePointFn =
|
|
44
|
-
const getSurfaceRecordFn =
|
|
48
|
+
const getCameraRayFn = contextProxyFn( 'bvhData.fns.getCameraRay' );
|
|
49
|
+
const sampleTrianglePointFn = contextProxyFn( 'bvhData.fns.sampleTrianglePoint' );
|
|
50
|
+
const getSurfaceRecordFn = contextProxyFn( 'bvhData.fns.getSurfaceRecord' );
|
|
45
51
|
const bsdfSampleFn = proxyFn( 'material.value.bsdfSample', params );
|
|
46
52
|
const bsdfEvalPdfFn = proxyFn( 'material.value.bsdfEvalPdf', params );
|
|
53
|
+
const materialsStorage = contextProxy( 'bvhData.storage.materials' );
|
|
54
|
+
const transformsStorage = contextProxy( 'bvhData.storage.transforms' );
|
|
47
55
|
|
|
48
56
|
const fn = wgslTagFn/* wgsl */`
|
|
49
57
|
|
|
@@ -62,11 +70,9 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
62
70
|
let rayDataStorage = &${ params.rayDataStorage };
|
|
63
71
|
let rayQueue = &${ params.rayQueue };
|
|
64
72
|
let shadowRayQueue = &${ params.shadowRayQueue };
|
|
73
|
+
let queueCounters = &${ params.queueCounters };
|
|
65
74
|
let pixelQueue = &${ params.pixelQueue };
|
|
66
75
|
|
|
67
|
-
let materials = &${ proxy( 'bvhData.value.storage.materials', params ) };
|
|
68
|
-
let transforms = &${ proxy( 'bvhData.value.storage.transforms', params ) };
|
|
69
|
-
|
|
70
76
|
// bound by "rayCount" rather than the pool length. The dispatch rounds up to the
|
|
71
77
|
// workgroup size and those extra slots hold a zeroed pixel index
|
|
72
78
|
let index = globalId.x;
|
|
@@ -76,20 +82,21 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
76
82
|
|
|
77
83
|
}
|
|
78
84
|
|
|
79
|
-
|
|
85
|
+
// whole structs are read from the buffer, not the pointer alias, which Safari fails to compile
|
|
86
|
+
let input = ${ params.rayDataStorage }[ index ];
|
|
80
87
|
if ( input.objectIndex < 0 ) {
|
|
81
88
|
|
|
82
89
|
// the slot's path has terminated: recycle the pixel through the overflow queue and
|
|
83
90
|
// generate a fresh camera ray
|
|
84
91
|
var pixelIndex = input.pixelIndex;
|
|
85
|
-
let elementCount = atomicLoad( &pixelQueue
|
|
92
|
+
let elementCount = atomicLoad( &pixelQueue[ ${ PIXEL_QUEUE_ELEMENT_COUNT }u ] );
|
|
86
93
|
if ( elementCount > 0u ) {
|
|
87
94
|
|
|
88
95
|
// TODO: If we've pulled off a pixel that's already finished we currently just
|
|
89
96
|
// write a no-op ray, wasting a frame. It may be better to iterate over a few
|
|
90
97
|
// points in the queue to see if we can find one we can use.
|
|
91
|
-
let queueIndex = atomicAdd( &pixelQueue
|
|
92
|
-
pixelIndex = atomicExchange( &pixelQueue
|
|
98
|
+
let queueIndex = atomicAdd( &pixelQueue[ ${ PIXEL_QUEUE_CURRENT }u ], 1u ) % elementCount;
|
|
99
|
+
pixelIndex = atomicExchange( &pixelQueue[ ${ PIXEL_QUEUE_HEADER }u + queueIndex ], pixelIndex );
|
|
93
100
|
|
|
94
101
|
}
|
|
95
102
|
|
|
@@ -116,9 +123,16 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
116
123
|
var ray: ${ rayStruct };
|
|
117
124
|
if ( ! ${ getCameraRayFn }( jitteredUv, vec2f( targetDimensions ), &ray ) ) {
|
|
118
125
|
|
|
119
|
-
// the camera declined the pixel, so leave the slot dormant for this round
|
|
126
|
+
// the camera declined the pixel, so leave the slot dormant for this round. With a
|
|
127
|
+
// sample cap the pixel counts as finished, since it will never take a sample
|
|
120
128
|
// TODO: same as above - this work is a bit wasteful and leaves slots empty for
|
|
121
129
|
// a frame. Is it possible to quickly skip rays outside the mask or are finished?
|
|
130
|
+
if ( maxSamples != 0u ) {
|
|
131
|
+
|
|
132
|
+
textureStore( ${ params.sampleCountTarget }, indexUV, vec4( ( combinedField & ~${ SAMPLE_COUNT_MASK }u ) | maxSamples ) );
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
122
136
|
rayDataStorage[ index ].pixelIndex = pixelIndex;
|
|
123
137
|
rayDataStorage[ index ].rayIntersectionIndex = - 1;
|
|
124
138
|
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
@@ -128,14 +142,13 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
128
142
|
|
|
129
143
|
ray.direction = normalize( ray.direction );
|
|
130
144
|
|
|
131
|
-
let rayIndex = atomicAdd( &
|
|
132
|
-
rayQueue
|
|
133
|
-
rayQueue
|
|
134
|
-
rayQueue
|
|
135
|
-
rayQueue
|
|
136
|
-
rayQueue
|
|
137
|
-
rayQueue
|
|
138
|
-
rayQueue.elements[ rayIndex ].maxDist = ray.maxDist;
|
|
145
|
+
let rayIndex = atomicAdd( &queueCounters[ ${ RAY_QUEUE_LENGTH }u ], 1u );
|
|
146
|
+
rayQueue[ rayIndex ].origin = ray.origin;
|
|
147
|
+
rayQueue[ rayIndex ].direction = ray.direction;
|
|
148
|
+
rayQueue[ rayIndex ].pixelIndex = pixelIndex;
|
|
149
|
+
rayQueue[ rayIndex ].rngIndex = 0u;
|
|
150
|
+
rayQueue[ rayIndex ].seed = seed + samples;
|
|
151
|
+
rayQueue[ rayIndex ].maxDist = ray.maxDist;
|
|
139
152
|
|
|
140
153
|
rayDataStorage[ index ].origin = ray.origin;
|
|
141
154
|
rayDataStorage[ index ].direction = ray.direction;
|
|
@@ -151,22 +164,33 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
151
164
|
rayDataStorage[ index ].emission = vec3f( 0.0 );
|
|
152
165
|
rayDataStorage[ index ].lightPdf = 0.0;
|
|
153
166
|
rayDataStorage[ index ].alphaDepth = 0u;
|
|
167
|
+
rayDataStorage[ index ].rayStart = 0.0;
|
|
154
168
|
rayDataStorage[ index ].maxDist = ray.maxDist;
|
|
155
169
|
rayDataStorage[ index ].rayIntersectionIndex = i32( rayIndex );
|
|
156
170
|
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
157
171
|
rayDataStorage[ index ].dispersionWavelength = - mix( ${ DISPERSION_MIN_WAVELENGTH }.0, ${ DISPERSION_MAX_WAVELENGTH }.0, ${ rand1 }( ${ RNG_INDEX_DISPERSION_WAVELENGTH } ) );
|
|
172
|
+
rayDataStorage[ index ].auxAlbedo = 0u;
|
|
173
|
+
rayDataStorage[ index ].auxNormal = 0u;
|
|
174
|
+
rayDataStorage[ index ].subsurfaceBounces = 0u;
|
|
175
|
+
rayDataStorage[ index ].walkPending = 0u;
|
|
176
|
+
rayDataStorage[ index ].mediumScattered = 0u;
|
|
177
|
+
rayDataStorage[ index ].medium.inside = 0u;
|
|
158
178
|
|
|
159
179
|
// write the active params & dispatched flag
|
|
160
180
|
textureStore( ${ params.sampleCountTarget }, indexUV, vec4( ${ SAMPLE_ACTIVE_FLAG }u | ${ SAMPLE_DISPATCHED_FLAG }u | samples ) );
|
|
161
181
|
|
|
162
|
-
} else {
|
|
182
|
+
} else if ( input.walkPending == 0u ) {
|
|
163
183
|
|
|
164
|
-
// evaluate the surface staged by LogicKernel
|
|
184
|
+
// evaluate the surface staged by LogicKernel. Paths still walking a medium have none
|
|
165
185
|
let indexUV = vec2u( input.pixelIndex >> 16, input.pixelIndex & 0xFFFF );
|
|
166
|
-
${ rngInit }( indexUV, input.seed, input.currentBounce + input.alphaDepth );
|
|
186
|
+
${ rngInit }( indexUV, input.seed, input.currentBounce + input.alphaDepth + input.subsurfaceBounces );
|
|
167
187
|
|
|
168
|
-
|
|
169
|
-
var
|
|
188
|
+
// the medium the path is in, which the bsdf sample may enter or exit
|
|
189
|
+
var medium = ${ loadMediumFn }( input.medium );
|
|
190
|
+
let segmentInMedium = medium.inside;
|
|
191
|
+
|
|
192
|
+
let objectInfo = ${ transformsStorage }[ u32( input.objectIndex ) ];
|
|
193
|
+
var materialInfo = ${ materialsStorage }[ objectInfo.materialIndex ];
|
|
170
194
|
|
|
171
195
|
// a matte surface hit by the camera ray renders as a fully transparent
|
|
172
196
|
let isMatte = materialInfo.matte != 0 && input.currentBounce == 0u;
|
|
@@ -215,16 +239,31 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
215
239
|
|
|
216
240
|
}
|
|
217
241
|
|
|
218
|
-
let
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
242
|
+
let alphaOrigin = ${ offsetRayOriginFunc }( vertexData.position.xyz, input.direction, input.normal );
|
|
243
|
+
if ( segmentInMedium ) {
|
|
244
|
+
|
|
245
|
+
// inside a medium, MediumKernel walks on past the surface, from the surface
|
|
246
|
+
rayDataStorage[ index ].walkPending = 1u;
|
|
247
|
+
rayDataStorage[ index ].origin = alphaOrigin;
|
|
248
|
+
rayDataStorage[ index ].rayStart = 0.0;
|
|
249
|
+
|
|
250
|
+
} else {
|
|
251
|
+
|
|
252
|
+
let alphaIndex = atomicAdd( &queueCounters[ ${ RAY_QUEUE_LENGTH }u ], 1u );
|
|
253
|
+
rayQueue[ alphaIndex ].origin = alphaOrigin;
|
|
254
|
+
rayQueue[ alphaIndex ].direction = input.direction;
|
|
255
|
+
rayQueue[ alphaIndex ].pixelIndex = input.pixelIndex;
|
|
256
|
+
rayQueue[ alphaIndex ].rngIndex = input.currentBounce + input.alphaDepth + 1u + input.subsurfaceBounces;
|
|
257
|
+
rayQueue[ alphaIndex ].seed = input.seed;
|
|
258
|
+
// the origin advanced to the hit point, so the remaining budget shrinks by the
|
|
259
|
+
// distance already traced
|
|
260
|
+
rayQueue[ alphaIndex ].maxDist = max( input.maxDist - input.dist, 0.0 );
|
|
261
|
+
rayDataStorage[ index ].rayIntersectionIndex = i32( alphaIndex );
|
|
262
|
+
|
|
263
|
+
// the ray keeps its origin, and only the queued traversal starts past the surface
|
|
264
|
+
rayDataStorage[ index ].rayStart = input.rayStart + input.dist;
|
|
265
|
+
|
|
266
|
+
}
|
|
228
267
|
|
|
229
268
|
// the surface is skipped, so no scatter or emission is staged for LogicKernel.
|
|
230
269
|
// "pdf" is left alone so the previous scatter still weights the forward MIS,
|
|
@@ -233,8 +272,6 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
233
272
|
rayDataStorage[ index ].emission = vec3f( 0.0 );
|
|
234
273
|
rayDataStorage[ index ].scatterColor = vec3f( input.scatterPdf );
|
|
235
274
|
rayDataStorage[ index ].lightPdf = 0.0;
|
|
236
|
-
rayDataStorage[ index ].origin = rayQueue.elements[ alphaIndex ].origin;
|
|
237
|
-
rayDataStorage[ index ].rayIntersectionIndex = i32( alphaIndex );
|
|
238
275
|
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
239
276
|
return;
|
|
240
277
|
|
|
@@ -259,8 +296,9 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
259
296
|
}
|
|
260
297
|
|
|
261
298
|
// attenuate the light transmitted through the volume when exiting a backface so
|
|
262
|
-
// the surface's emission and NEE resolve against the attenuated throughput
|
|
263
|
-
|
|
299
|
+
// the surface's emission and NEE resolve against the attenuated throughput. MediumKernel
|
|
300
|
+
// already applied a scattering medium's transmittance
|
|
301
|
+
if ( input.side < 0.0 && materialInfo.transmission > 0.0 && ! segmentInMedium ) {
|
|
264
302
|
|
|
265
303
|
throughputColor *= ${ transmissionAttenuationFunc }( input.dist, materialInfo.attenuationColor, materialInfo.attenuationDistance );
|
|
266
304
|
rayDataStorage[ index ].throughputColor = throughputColor;
|
|
@@ -271,6 +309,20 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
271
309
|
var scatterRec = ${ bsdfSampleFn }( view, surface );
|
|
272
310
|
let newBounce = input.currentBounce + 1u;
|
|
273
311
|
|
|
312
|
+
// The first surface the path scatters off fills the denoiser's auxiliary images. Until one is
|
|
313
|
+
// found the last mirror or transmission hit stands in, for paths that reach the environment.
|
|
314
|
+
// The albedo is tinted by the throughput of the surfaces looked past to reach it. The packed
|
|
315
|
+
// albedo's alpha marks the surface found
|
|
316
|
+
let auxFound = unpack4x8unorm( input.auxAlbedo ).a != 0.0;
|
|
317
|
+
if ( ! auxFound ) {
|
|
318
|
+
|
|
319
|
+
let isAuxSurface = ${ isAuxSurfaceFn }( surface, scatterRec );
|
|
320
|
+
let auxAlbedo = saturate( throughputColor * surface.color );
|
|
321
|
+
rayDataStorage[ index ].auxAlbedo = pack4x8unorm( vec4f( auxAlbedo, select( 0.0, 1.0, isAuxSurface ) ) );
|
|
322
|
+
rayDataStorage[ index ].auxNormal = pack4x8snorm( vec4f( surface.normal, 0.0 ) );
|
|
323
|
+
|
|
324
|
+
}
|
|
325
|
+
|
|
274
326
|
// decide termination now so finished paths skip the bounce trace entirely - a
|
|
275
327
|
// zeroed pdf reads as a terminating scatter in LogicKernel, which still resolves
|
|
276
328
|
// the surface's emission and NEE before freeing the slot
|
|
@@ -301,23 +353,59 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
301
353
|
rayDataStorage[ index ].isFullyTransmissive = input.isFullyTransmissive & select( 0u, 1u, scatterRec.isTransmissive );
|
|
302
354
|
rayDataStorage[ index ].emission = surface.emission;
|
|
303
355
|
rayDataStorage[ index ].currentBounce = newBounce;
|
|
356
|
+
rayDataStorage[ index ].mediumScattered = 0u;
|
|
357
|
+
|
|
358
|
+
// As in "KHR_materials_scatter", the surface bsdf is the medium's boundary: refracting out
|
|
359
|
+
// ends the walk, reflecting continues it
|
|
360
|
+
if ( segmentInMedium && u32( input.objectIndex ) == medium.object && input.side < 0.0 && scatterRec.isTransmissive ) {
|
|
361
|
+
|
|
362
|
+
medium.inside = false;
|
|
363
|
+
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// refracting into a front face of a scattering material starts a random walk inside it
|
|
367
|
+
if ( scatterRec.isTransmissive && ! surface.thinWall && input.side > 0.0 ) {
|
|
368
|
+
|
|
369
|
+
let entered = ${ enterMediumFn }( materialInfo, u32( input.objectIndex ) );
|
|
370
|
+
if ( entered.inside ) {
|
|
371
|
+
|
|
372
|
+
medium = entered;
|
|
373
|
+
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// keep the medium the path left or entered
|
|
379
|
+
if ( segmentInMedium || medium.inside ) {
|
|
380
|
+
|
|
381
|
+
// a whole struct is written to the buffer, not through the pointer alias, which Safari fails to compile
|
|
382
|
+
${ params.rayDataStorage }[ index ].medium = ${ storeMediumFn }( medium );
|
|
383
|
+
|
|
384
|
+
}
|
|
304
385
|
|
|
305
386
|
// the NEE shadow ray below still resolves the surface's direct light, so only
|
|
306
|
-
// the bounce segment is skipped for finished paths
|
|
307
|
-
if ( ! isTerminated ) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
387
|
+
// the bounce segment is skipped for finished paths. Inside a medium, MediumKernel walks it
|
|
388
|
+
if ( ! isTerminated && medium.inside ) {
|
|
389
|
+
|
|
390
|
+
rayDataStorage[ index ].walkPending = 1u;
|
|
391
|
+
rayDataStorage[ index ].origin = ${ offsetRayOriginFunc }( vertexData.position.xyz, scatterRec.direction, input.normal );
|
|
392
|
+
rayDataStorage[ index ].direction = scatterRec.direction;
|
|
393
|
+
rayDataStorage[ index ].rayStart = 0.0;
|
|
394
|
+
|
|
395
|
+
} else if ( ! isTerminated ) {
|
|
396
|
+
|
|
397
|
+
let rayIndex = atomicAdd( &queueCounters[ ${ RAY_QUEUE_LENGTH }u ], 1u );
|
|
398
|
+
rayQueue[ rayIndex ].origin = ${ offsetRayOriginFunc }( vertexData.position.xyz, scatterRec.direction, input.normal );
|
|
399
|
+
rayQueue[ rayIndex ].direction = scatterRec.direction;
|
|
400
|
+
rayQueue[ rayIndex ].pixelIndex = input.pixelIndex;
|
|
401
|
+
rayQueue[ rayIndex ].rngIndex = newBounce + input.alphaDepth + input.subsurfaceBounces;
|
|
402
|
+
rayQueue[ rayIndex ].seed = input.seed;
|
|
403
|
+
rayQueue[ rayIndex ].maxDist = 0.0;
|
|
317
404
|
rayDataStorage[ index ].rayIntersectionIndex = i32( rayIndex );
|
|
318
405
|
|
|
319
|
-
rayDataStorage[ index ].origin = rayQueue
|
|
406
|
+
rayDataStorage[ index ].origin = rayQueue[ rayIndex ].origin;
|
|
320
407
|
rayDataStorage[ index ].direction = scatterRec.direction;
|
|
408
|
+
rayDataStorage[ index ].rayStart = 0.0;
|
|
321
409
|
|
|
322
410
|
}
|
|
323
411
|
|
|
@@ -332,14 +420,13 @@ export class MaterialKernel extends ComputeKernel {
|
|
|
332
420
|
rayDataStorage[ index ].lightBsdf = evalRec.color;
|
|
333
421
|
rayDataStorage[ index ].lightBsdfPdf = evalRec.pdf;
|
|
334
422
|
|
|
335
|
-
let shadowIndex = atomicAdd( &
|
|
336
|
-
shadowRayQueue
|
|
337
|
-
shadowRayQueue
|
|
338
|
-
shadowRayQueue
|
|
339
|
-
shadowRayQueue
|
|
340
|
-
shadowRayQueue
|
|
341
|
-
shadowRayQueue
|
|
342
|
-
shadowRayQueue.elements[ shadowIndex ].maxDist = input.lightDist * ( 1.0 - ${ LIGHT_EPSILON } );
|
|
423
|
+
let shadowIndex = atomicAdd( &queueCounters[ ${ SHADOW_RAY_QUEUE_LENGTH }u ], 1u );
|
|
424
|
+
shadowRayQueue[ shadowIndex ].origin = ${ offsetRayOriginFunc }( vertexData.position.xyz, input.lightDirection, input.normal );
|
|
425
|
+
shadowRayQueue[ shadowIndex ].direction = input.lightDirection;
|
|
426
|
+
shadowRayQueue[ shadowIndex ].pixelIndex = input.pixelIndex;
|
|
427
|
+
shadowRayQueue[ shadowIndex ].rngIndex = input.currentBounce + input.alphaDepth + input.subsurfaceBounces;
|
|
428
|
+
shadowRayQueue[ shadowIndex ].seed = input.seed;
|
|
429
|
+
shadowRayQueue[ shadowIndex ].maxDist = input.lightDist * ( 1.0 - ${ LIGHT_EPSILON } );
|
|
343
430
|
rayDataStorage[ index ].shadowRayIntersectionIndex = i32( shadowIndex );
|
|
344
431
|
|
|
345
432
|
} else {
|