three-gpu-pathtracer 0.0.24 → 0.0.26
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 +53 -530
- package/build/index.module.js +262 -50
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +261 -48
- package/build/index.umd.cjs.map +1 -1
- package/package.json +29 -11
- package/src/core/WebGLPathTracer.js +8 -0
- package/src/core/utils/sceneUpdateUtils.js +8 -12
- package/src/detectors/PrecisionMaterial.js +9 -9
- package/src/index.js +1 -0
- package/src/objects/PhysicalSpotLight.js +2 -0
- package/src/shader/bsdf/bsdf_functions.glsl.js +0 -1
- package/src/textures/BlueNoiseTexture.js +6 -6
- package/src/textures/ProceduralEquirectTexture.js +8 -7
- package/src/textures/turquinMetal.png +0 -0
- package/src/uniforms/EquirectHdrInfoUniform.js +9 -4
- package/src/uniforms/FloatAttributeTextureArray.js +11 -11
- package/src/uniforms/MaterialsTexture.js +9 -9
- package/src/webgpu/API.md +762 -0
- package/src/webgpu/AtlasTexture.js +471 -0
- package/src/webgpu/BlurredEnvMapGenerator.js +129 -0
- package/src/webgpu/EquirectBackgroundInfo.js +97 -0
- package/src/webgpu/EquirectHdrInfoNode.js +151 -0
- package/src/webgpu/LightsInfoNode.js +224 -0
- package/src/webgpu/MegaKernelPathTracer.js +277 -0
- package/src/webgpu/PathTracerBackend.js +216 -0
- package/src/webgpu/TurquinTexture.js +169 -0
- package/src/webgpu/WaveFrontPathTracer.js +586 -0
- package/src/webgpu/WebGPUPathTracer.js +1302 -0
- package/src/webgpu/compute/ComputeKernel.js +92 -0
- package/src/webgpu/compute/CopyBufferKernel.js +40 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +470 -0
- package/src/webgpu/compute/SampleDebugKernel.js +51 -0
- package/src/webgpu/compute/TallySampleCountsKernel.js +111 -0
- package/src/webgpu/compute/ZeroOutBufferKernel.js +35 -0
- package/src/webgpu/compute/ZeroOutKernel.js +31 -0
- package/src/webgpu/compute/wavefront/LogicKernel.js +349 -0
- package/src/webgpu/compute/wavefront/MaterialKernel.js +371 -0
- package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +72 -0
- package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +38 -0
- package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +66 -0
- package/src/webgpu/compute/wavefront/TraceRayKernel.js +71 -0
- package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +67 -0
- package/src/webgpu/compute/wavefront/structs.js +155 -0
- package/src/webgpu/constants.js +62 -0
- package/src/webgpu/denoise/OIDNDenoiser.js +431 -0
- package/src/webgpu/index.d.ts +176 -0
- package/src/webgpu/index.js +11 -0
- package/src/webgpu/materials/GltfCompliantMaterial.js +608 -0
- package/src/webgpu/materials/GraphMaterial.js +331 -0
- package/src/webgpu/materials/PathtracingMaterial.js +89 -0
- package/src/webgpu/materials/RenderToScreenMaterial.js +150 -0
- package/src/webgpu/materials/debug/AtlasDebugMaterial.js +41 -0
- package/src/webgpu/materials/debug/SampleDensityMaterial.js +61 -0
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +1037 -0
- package/src/webgpu/nodes/debugBounds.wgsl.js +228 -0
- package/src/webgpu/nodes/eon.wgsl.js +262 -0
- package/src/webgpu/nodes/ggx.wgsl.js +190 -0
- package/src/webgpu/nodes/lights.wgsl.js +203 -0
- package/src/webgpu/nodes/material.wgsl.js +990 -0
- package/src/webgpu/nodes/rand/bluedither.wgsl.js +92 -0
- package/src/webgpu/nodes/rand/pcg.wgsl.js +81 -0
- package/src/webgpu/nodes/rand/sobol.wgsl.js +287 -0
- package/src/webgpu/nodes/random.wgsl.js +20 -0
- package/src/webgpu/nodes/reset.wgsl.js +21 -0
- package/src/webgpu/nodes/sampling.wgsl.js +193 -0
- package/src/webgpu/nodes/sheen.wgsl.js +148 -0
- package/src/webgpu/nodes/structs.wgsl.js +272 -0
- package/src/webgpu/nodes/utils.wgsl.js +366 -0
- package/src/webgpu/shims/ArrayCameraShim.js +74 -0
- package/src/webgpu/shims/EquirectCameraShim.js +37 -0
- package/src/webgpu/shims/PhysicalCameraShim.js +151 -0
- package/src/webgpu/upscale/FSRUpscaler.js +108 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { Vector2 } from 'three';
|
|
2
|
+
import { StorageBufferAttribute, StorageTexture } from 'three/webgpu';
|
|
3
|
+
import { ComputeKernel } from '../ComputeKernel.js';
|
|
4
|
+
import { uniform, storage, textureStore, globalId } from 'three/tsl';
|
|
5
|
+
import { proxy, proxyFn, rayStruct, wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
6
|
+
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 { rayDataStruct, rayQueueAtomicStruct, pixelQueueStruct } from './structs.js';
|
|
8
|
+
import { SAMPLE_ACTIVE_FLAG, SAMPLE_COUNT_MASK, SAMPLE_DISPATCHED_FLAG } from '../../constants.js';
|
|
9
|
+
import { applyDispersionFunc, dispersionColorWeightFunc, DISPERSION_MIN_WAVELENGTH, DISPERSION_MAX_WAVELENGTH, transmissionAttenuationFunc } from '../../nodes/material.wgsl.js';
|
|
10
|
+
import { isTerminatingScatterFunc, offsetRayOriginFunc } from '../../nodes/utils.wgsl.js';
|
|
11
|
+
import { LIGHT_EPSILON } from '../../nodes/lights.wgsl.js';
|
|
12
|
+
|
|
13
|
+
// Pure material evaluation and ray generation: terminated slots pull a recycled pixel and emit a
|
|
14
|
+
// fresh camera ray; live slots evaluate the surface staged by LogicKernel, sample the bsdf, and
|
|
15
|
+
// enqueue the next bounce ray plus the NEE shadow ray toward the light LogicKernel selected.
|
|
16
|
+
export class MaterialKernel extends ComputeKernel {
|
|
17
|
+
|
|
18
|
+
constructor( ) {
|
|
19
|
+
|
|
20
|
+
const params = {
|
|
21
|
+
bvhData: { value: null },
|
|
22
|
+
material: { value: null },
|
|
23
|
+
|
|
24
|
+
seed: uniform( 0, 'uint' ),
|
|
25
|
+
targetDimensions: uniform( new Vector2() ),
|
|
26
|
+
maxSamples: uniform( 0, 'uint' ),
|
|
27
|
+
rayCount: uniform( 0, 'uint' ),
|
|
28
|
+
filterGlossy: uniform( 1 ),
|
|
29
|
+
maxTransparentBounces: uniform( 5, 'uint' ),
|
|
30
|
+
maxBounces: uniform( 5, 'uint' ),
|
|
31
|
+
|
|
32
|
+
sampleCountTarget: textureStore( new StorageTexture( 1, 1 ) ).toReadWrite(),
|
|
33
|
+
|
|
34
|
+
rayDataStorage: storage( new StorageBufferAttribute( 1, 1 ), rayDataStruct ),
|
|
35
|
+
rayQueue: storage( new StorageBufferAttribute( 1, 1 ), rayQueueAtomicStruct ),
|
|
36
|
+
shadowRayQueue: storage( new StorageBufferAttribute( 1, 1 ), rayQueueAtomicStruct ),
|
|
37
|
+
pixelQueue: storage( new StorageBufferAttribute( 1, 1 ), pixelQueueStruct ),
|
|
38
|
+
|
|
39
|
+
globalId: globalId,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getCameraRayFn = proxyFn( 'bvhData.value.fns.getCameraRay', params );
|
|
43
|
+
const sampleTrianglePointFn = proxyFn( 'bvhData.value.fns.sampleTrianglePoint', params );
|
|
44
|
+
const getSurfaceRecordFn = proxyFn( 'bvhData.value.fns.getSurfaceRecord', params );
|
|
45
|
+
const bsdfSampleFn = proxyFn( 'material.value.bsdfSample', params );
|
|
46
|
+
const bsdfEvalPdfFn = proxyFn( 'material.value.bsdfEvalPdf', params );
|
|
47
|
+
|
|
48
|
+
const fn = wgslTagFn/* wgsl */`
|
|
49
|
+
|
|
50
|
+
fn compute(
|
|
51
|
+
seed: u32,
|
|
52
|
+
targetDimensions: vec2u,
|
|
53
|
+
maxSamples: u32,
|
|
54
|
+
rayCount: u32,
|
|
55
|
+
filterGlossy: f32,
|
|
56
|
+
maxTransparentBounces: u32,
|
|
57
|
+
maxBounces: u32,
|
|
58
|
+
|
|
59
|
+
globalId: vec3u
|
|
60
|
+
) -> void {
|
|
61
|
+
|
|
62
|
+
let rayDataStorage = &${ params.rayDataStorage };
|
|
63
|
+
let rayQueue = &${ params.rayQueue };
|
|
64
|
+
let shadowRayQueue = &${ params.shadowRayQueue };
|
|
65
|
+
let pixelQueue = &${ params.pixelQueue };
|
|
66
|
+
|
|
67
|
+
let materials = &${ proxy( 'bvhData.value.storage.materials', params ) };
|
|
68
|
+
let transforms = &${ proxy( 'bvhData.value.storage.transforms', params ) };
|
|
69
|
+
|
|
70
|
+
// bound by "rayCount" rather than the pool length. The dispatch rounds up to the
|
|
71
|
+
// workgroup size and those extra slots hold a zeroed pixel index
|
|
72
|
+
let index = globalId.x;
|
|
73
|
+
if ( index >= rayCount ) {
|
|
74
|
+
|
|
75
|
+
return;
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let input = ( *rayDataStorage )[ index ];
|
|
80
|
+
if ( input.objectIndex < 0 ) {
|
|
81
|
+
|
|
82
|
+
// the slot's path has terminated: recycle the pixel through the overflow queue and
|
|
83
|
+
// generate a fresh camera ray
|
|
84
|
+
var pixelIndex = input.pixelIndex;
|
|
85
|
+
let elementCount = atomicLoad( &pixelQueue.elementCount );
|
|
86
|
+
if ( elementCount > 0u ) {
|
|
87
|
+
|
|
88
|
+
// TODO: If we've pulled off a pixel that's already finished we currently just
|
|
89
|
+
// write a no-op ray, wasting a frame. It may be better to iterate over a few
|
|
90
|
+
// points in the queue to see if we can find one we can use.
|
|
91
|
+
let queueIndex = atomicAdd( &pixelQueue.current, 1u ) % elementCount;
|
|
92
|
+
pixelIndex = atomicExchange( &pixelQueue.elements[ queueIndex ], pixelIndex );
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let indexUV = vec2u( pixelIndex >> 16, pixelIndex & 0xFFFF );
|
|
97
|
+
|
|
98
|
+
// skip the pixel if it has hit the sample limit
|
|
99
|
+
let combinedField = textureLoad( ${ params.sampleCountTarget }, indexUV ).r;
|
|
100
|
+
let samples = ( ${ SAMPLE_COUNT_MASK }u & combinedField );
|
|
101
|
+
let isComplete = maxSamples != 0u && samples >= maxSamples;
|
|
102
|
+
|
|
103
|
+
if ( isComplete ) {
|
|
104
|
+
|
|
105
|
+
rayDataStorage[ index ].pixelIndex = pixelIndex;
|
|
106
|
+
rayDataStorage[ index ].rayIntersectionIndex = - 1;
|
|
107
|
+
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
108
|
+
return;
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
${ rngInit }( indexUV, seed + samples, 0 );
|
|
113
|
+
|
|
114
|
+
let uv = vec2f( indexUV ) / vec2f( targetDimensions );
|
|
115
|
+
let jitteredUv = uv + ${ rand2 }( ${ RNG_INDEX_RAY_JITTER } ) / vec2f( targetDimensions );
|
|
116
|
+
var ray: ${ rayStruct };
|
|
117
|
+
if ( ! ${ getCameraRayFn }( jitteredUv, vec2f( targetDimensions ), &ray ) ) {
|
|
118
|
+
|
|
119
|
+
// the camera declined the pixel, so leave the slot dormant for this round
|
|
120
|
+
// TODO: same as above - this work is a bit wasteful and leaves slots empty for
|
|
121
|
+
// a frame. Is it possible to quickly skip rays outside the mask or are finished?
|
|
122
|
+
rayDataStorage[ index ].pixelIndex = pixelIndex;
|
|
123
|
+
rayDataStorage[ index ].rayIntersectionIndex = - 1;
|
|
124
|
+
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
125
|
+
return;
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ray.direction = normalize( ray.direction );
|
|
130
|
+
|
|
131
|
+
let rayIndex = atomicAdd( &rayQueue.length, 1u );
|
|
132
|
+
rayQueue.elements[ rayIndex ].origin = ray.origin;
|
|
133
|
+
rayQueue.elements[ rayIndex ].direction = ray.direction;
|
|
134
|
+
rayQueue.elements[ rayIndex ].pixelIndex = pixelIndex;
|
|
135
|
+
rayQueue.elements[ rayIndex ].currentBounce = 0u;
|
|
136
|
+
rayQueue.elements[ rayIndex ].seed = seed + samples;
|
|
137
|
+
rayQueue.elements[ rayIndex ].alphaDepth = 0u;
|
|
138
|
+
rayQueue.elements[ rayIndex ].maxDist = ray.maxDist;
|
|
139
|
+
|
|
140
|
+
rayDataStorage[ index ].origin = ray.origin;
|
|
141
|
+
rayDataStorage[ index ].direction = ray.direction;
|
|
142
|
+
rayDataStorage[ index ].pixelIndex = pixelIndex;
|
|
143
|
+
rayDataStorage[ index ].seed = seed + samples;
|
|
144
|
+
rayDataStorage[ index ].currentBounce = 0u;
|
|
145
|
+
rayDataStorage[ index ].throughputColor = vec3f( 1.0 );
|
|
146
|
+
rayDataStorage[ index ].resultColor = vec4f( 0.0, 0.0, 0.0, 1.0 );
|
|
147
|
+
rayDataStorage[ index ].scatterColor = vec3f( 1.0 );
|
|
148
|
+
rayDataStorage[ index ].scatterPdf = 1.0;
|
|
149
|
+
rayDataStorage[ index ].minPdf = 1.0;
|
|
150
|
+
rayDataStorage[ index ].isFullyTransmissive = 1u;
|
|
151
|
+
rayDataStorage[ index ].emission = vec3f( 0.0 );
|
|
152
|
+
rayDataStorage[ index ].lightPdf = 0.0;
|
|
153
|
+
rayDataStorage[ index ].alphaDepth = 0u;
|
|
154
|
+
rayDataStorage[ index ].maxDist = ray.maxDist;
|
|
155
|
+
rayDataStorage[ index ].rayIntersectionIndex = i32( rayIndex );
|
|
156
|
+
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
157
|
+
rayDataStorage[ index ].dispersionWavelength = - mix( ${ DISPERSION_MIN_WAVELENGTH }.0, ${ DISPERSION_MAX_WAVELENGTH }.0, ${ rand1 }( ${ RNG_INDEX_DISPERSION_WAVELENGTH } ) );
|
|
158
|
+
|
|
159
|
+
// write the active params & dispatched flag
|
|
160
|
+
textureStore( ${ params.sampleCountTarget }, indexUV, vec4( ${ SAMPLE_ACTIVE_FLAG }u | ${ SAMPLE_DISPATCHED_FLAG }u | samples ) );
|
|
161
|
+
|
|
162
|
+
} else {
|
|
163
|
+
|
|
164
|
+
// evaluate the surface staged by LogicKernel
|
|
165
|
+
let indexUV = vec2u( input.pixelIndex >> 16, input.pixelIndex & 0xFFFF );
|
|
166
|
+
${ rngInit }( indexUV, input.seed, input.currentBounce + input.alphaDepth );
|
|
167
|
+
|
|
168
|
+
let objectInfo = transforms[ u32( input.objectIndex ) ];
|
|
169
|
+
var materialInfo = ( *materials )[ objectInfo.materialIndex ];
|
|
170
|
+
|
|
171
|
+
// a matte surface hit by the camera ray renders as a fully transparent
|
|
172
|
+
let isMatte = materialInfo.matte != 0 && input.currentBounce == 0u;
|
|
173
|
+
if ( isMatte ) {
|
|
174
|
+
|
|
175
|
+
rayDataStorage[ index ].resultColor = vec4f( 0.0 );
|
|
176
|
+
rayDataStorage[ index ].throughputColor = vec3f( 0.0 );
|
|
177
|
+
rayDataStorage[ index ].emission = vec3f( 0.0 );
|
|
178
|
+
rayDataStorage[ index ].lightPdf = 0.0;
|
|
179
|
+
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
180
|
+
return;
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// apply per-object colors
|
|
185
|
+
materialInfo.color *= objectInfo.color.rgb;
|
|
186
|
+
materialInfo.opacity *= objectInfo.color.a;
|
|
187
|
+
|
|
188
|
+
var vertexData = ${ sampleTrianglePointFn }( input.barycoord, input.indices );
|
|
189
|
+
vertexData.normal = normalize( transpose( objectInfo.inverseMatrixWorld ) * vertexData.normal );
|
|
190
|
+
vertexData.tangent = vec4f( ( objectInfo.matrixWorld * vec4f( vertexData.tangent.xyz, 0.0 ) ).xyz, vertexData.tangent.w );
|
|
191
|
+
vertexData.position = objectInfo.matrixWorld * vertexData.position;
|
|
192
|
+
|
|
193
|
+
let view = - input.direction;
|
|
194
|
+
|
|
195
|
+
// blur glossy surfaces after low-probability bounces to suppress fireflies,
|
|
196
|
+
// from the Cycles "filter glossy" approach in integrator/surface_shader.h
|
|
197
|
+
let blurRoughness = sqrt( clamp( 1.0 - filterGlossy * input.minPdf, 0.0, 1.0 ) ) * 0.5;
|
|
198
|
+
|
|
199
|
+
var surface = ${ getSurfaceRecordFn }( materialInfo, vertexData, input.side, input.normal, view, blurRoughness );
|
|
200
|
+
|
|
201
|
+
// Stochastically pass through partially transparent surfaces by re-enqueueing
|
|
202
|
+
// the ray at the hit point, advancing the alpha depth but not the bounce count.
|
|
203
|
+
let passesThrough = ${ rand1 }( ${ RNG_INDEX_ALPHA_TEST } ) > surface.opacity;
|
|
204
|
+
if ( passesThrough ) {
|
|
205
|
+
|
|
206
|
+
// out of transparent bounces, so stop rather than shading a surface that
|
|
207
|
+
// should be invisible. A zeroed throughput terminates in LogicKernel.
|
|
208
|
+
if ( input.alphaDepth >= maxTransparentBounces ) {
|
|
209
|
+
|
|
210
|
+
rayDataStorage[ index ].throughputColor = vec3f( 0.0 );
|
|
211
|
+
rayDataStorage[ index ].emission = vec3f( 0.0 );
|
|
212
|
+
rayDataStorage[ index ].lightPdf = 0.0;
|
|
213
|
+
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
214
|
+
return;
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let alphaIndex = atomicAdd( &rayQueue.length, 1u );
|
|
219
|
+
rayQueue.elements[ alphaIndex ].origin = ${ offsetRayOriginFunc }( vertexData.position.xyz, input.direction, input.normal );
|
|
220
|
+
rayQueue.elements[ alphaIndex ].direction = input.direction;
|
|
221
|
+
rayQueue.elements[ alphaIndex ].pixelIndex = input.pixelIndex;
|
|
222
|
+
rayQueue.elements[ alphaIndex ].currentBounce = input.currentBounce;
|
|
223
|
+
rayQueue.elements[ alphaIndex ].seed = input.seed;
|
|
224
|
+
rayQueue.elements[ alphaIndex ].alphaDepth = input.alphaDepth + 1u;
|
|
225
|
+
// the origin advanced to the hit point, so the remaining budget shrinks by the
|
|
226
|
+
// distance already traced
|
|
227
|
+
rayQueue.elements[ alphaIndex ].maxDist = max( input.maxDist - input.dist, 0.0 );
|
|
228
|
+
|
|
229
|
+
// the surface is skipped, so no scatter or emission is staged for LogicKernel.
|
|
230
|
+
// "pdf" is left alone so the previous scatter still weights the forward MIS,
|
|
231
|
+
// and "bsdf" matches it so applying the scatter leaves the throughput as is.
|
|
232
|
+
rayDataStorage[ index ].alphaDepth = input.alphaDepth + 1u;
|
|
233
|
+
rayDataStorage[ index ].emission = vec3f( 0.0 );
|
|
234
|
+
rayDataStorage[ index ].scatterColor = vec3f( input.scatterPdf );
|
|
235
|
+
rayDataStorage[ index ].lightPdf = 0.0;
|
|
236
|
+
rayDataStorage[ index ].origin = rayQueue.elements[ alphaIndex ].origin;
|
|
237
|
+
rayDataStorage[ index ].rayIntersectionIndex = i32( alphaIndex );
|
|
238
|
+
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
239
|
+
return;
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// apply the hero wavelength to dispersive surfaces, folding the spectral weight
|
|
244
|
+
// into the throughput at the path's first dispersive interaction
|
|
245
|
+
var throughputColor = input.throughputColor;
|
|
246
|
+
let isDispersive = materialInfo.dispersion > 0.0 && surface.ior > 1.0 && surface.transmission > 0.0 && ! surface.thinWall;
|
|
247
|
+
if ( isDispersive ) {
|
|
248
|
+
|
|
249
|
+
let wavelength = abs( input.dispersionWavelength );
|
|
250
|
+
${ applyDispersionFunc }( &surface, materialInfo.dispersion, wavelength );
|
|
251
|
+
if ( input.dispersionWavelength < 0.0 ) {
|
|
252
|
+
|
|
253
|
+
rayDataStorage[ index ].dispersionWavelength = wavelength;
|
|
254
|
+
throughputColor *= ${ dispersionColorWeightFunc }( wavelength );
|
|
255
|
+
rayDataStorage[ index ].throughputColor = throughputColor;
|
|
256
|
+
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 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
|
+
if ( input.side < 0.0 && materialInfo.transmission > 0.0 ) {
|
|
264
|
+
|
|
265
|
+
throughputColor *= ${ transmissionAttenuationFunc }( input.dist, materialInfo.attenuationColor, materialInfo.attenuationDistance );
|
|
266
|
+
rayDataStorage[ index ].throughputColor = throughputColor;
|
|
267
|
+
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// sample the next bounce direction and stage the scatter state for LogicKernel
|
|
271
|
+
var scatterRec = ${ bsdfSampleFn }( view, surface );
|
|
272
|
+
let newBounce = input.currentBounce + 1u;
|
|
273
|
+
|
|
274
|
+
// decide termination now so finished paths skip the bounce trace entirely - a
|
|
275
|
+
// zeroed pdf reads as a terminating scatter in LogicKernel, which still resolves
|
|
276
|
+
// the surface's emission and NEE before freeing the slot
|
|
277
|
+
var isTerminated = newBounce >= maxBounces || all( scatterRec.color == vec3f( 0.0 ) ) || ${ isTerminatingScatterFunc }( scatterRec );
|
|
278
|
+
|
|
279
|
+
// russian roulette early out:
|
|
280
|
+
// Matches Cycles path_state_continuation_probability in integrator/path_state.h
|
|
281
|
+
if ( ! isTerminated && newBounce >= 3u ) {
|
|
282
|
+
|
|
283
|
+
let rrThroughput = throughputColor * scatterRec.color / scatterRec.pdf;
|
|
284
|
+
let rrProb = saturate( sqrt( max( max( rrThroughput.r, rrThroughput.g ), rrThroughput.b ) ) );
|
|
285
|
+
isTerminated = rrProb <= 0.0 || ${ rand1 }( ${ RNG_INDEX_RUSSIAN_ROULETTE } ) > rrProb;
|
|
286
|
+
if ( ! isTerminated ) {
|
|
287
|
+
|
|
288
|
+
// fold the survival boost into the scatter color so LogicKernel's
|
|
289
|
+
// throughput update applies it without a separate division
|
|
290
|
+
scatterRec.color /= rrProb;
|
|
291
|
+
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Write the ray storage content here since things like the emissive value is read
|
|
297
|
+
// in the logic kernel on the subsequent frame.
|
|
298
|
+
rayDataStorage[ index ].scatterColor = scatterRec.color;
|
|
299
|
+
rayDataStorage[ index ].scatterPdf = select( scatterRec.pdf, 0.0, isTerminated );
|
|
300
|
+
rayDataStorage[ index ].minPdf = min( input.minPdf, scatterRec.pdf );
|
|
301
|
+
rayDataStorage[ index ].isFullyTransmissive = input.isFullyTransmissive & select( 0u, 1u, scatterRec.isTransmissive );
|
|
302
|
+
rayDataStorage[ index ].emission = surface.emission;
|
|
303
|
+
rayDataStorage[ index ].currentBounce = newBounce;
|
|
304
|
+
|
|
305
|
+
// 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
|
+
let rayIndex = atomicAdd( &rayQueue.length, 1u );
|
|
310
|
+
rayQueue.elements[ rayIndex ].origin = ${ offsetRayOriginFunc }( vertexData.position.xyz, scatterRec.direction, input.normal );
|
|
311
|
+
rayQueue.elements[ rayIndex ].direction = scatterRec.direction;
|
|
312
|
+
rayQueue.elements[ rayIndex ].pixelIndex = input.pixelIndex;
|
|
313
|
+
rayQueue.elements[ rayIndex ].currentBounce = newBounce;
|
|
314
|
+
rayQueue.elements[ rayIndex ].seed = input.seed;
|
|
315
|
+
rayQueue.elements[ rayIndex ].alphaDepth = input.alphaDepth;
|
|
316
|
+
rayQueue.elements[ rayIndex ].maxDist = 0.0;
|
|
317
|
+
rayDataStorage[ index ].rayIntersectionIndex = i32( rayIndex );
|
|
318
|
+
|
|
319
|
+
rayDataStorage[ index ].origin = rayQueue.elements[ rayIndex ].origin;
|
|
320
|
+
rayDataStorage[ index ].direction = scatterRec.direction;
|
|
321
|
+
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// evaluate the bsdf toward the light LogicKernel selected and enqueue the shadow ray.
|
|
325
|
+
// the light pdf will be 0 if NEE is disabled.
|
|
326
|
+
var lightPdf = input.lightPdf;
|
|
327
|
+
if ( lightPdf > 0.0 ) {
|
|
328
|
+
|
|
329
|
+
let evalRec = ${ bsdfEvalPdfFn }( view, input.lightDirection, surface );
|
|
330
|
+
if ( evalRec.pdf > 0.0 ) {
|
|
331
|
+
|
|
332
|
+
rayDataStorage[ index ].lightBsdf = evalRec.color;
|
|
333
|
+
rayDataStorage[ index ].lightBsdfPdf = evalRec.pdf;
|
|
334
|
+
|
|
335
|
+
let shadowIndex = atomicAdd( &shadowRayQueue.length, 1u );
|
|
336
|
+
shadowRayQueue.elements[ shadowIndex ].origin = ${ offsetRayOriginFunc }( vertexData.position.xyz, input.lightDirection, input.normal );
|
|
337
|
+
shadowRayQueue.elements[ shadowIndex ].direction = input.lightDirection;
|
|
338
|
+
shadowRayQueue.elements[ shadowIndex ].pixelIndex = input.pixelIndex;
|
|
339
|
+
shadowRayQueue.elements[ shadowIndex ].currentBounce = input.currentBounce;
|
|
340
|
+
shadowRayQueue.elements[ shadowIndex ].seed = input.seed;
|
|
341
|
+
shadowRayQueue.elements[ shadowIndex ].alphaDepth = input.alphaDepth;
|
|
342
|
+
shadowRayQueue.elements[ shadowIndex ].maxDist = input.lightDist - ${ LIGHT_EPSILON };
|
|
343
|
+
rayDataStorage[ index ].shadowRayIntersectionIndex = i32( shadowIndex );
|
|
344
|
+
|
|
345
|
+
} else {
|
|
346
|
+
|
|
347
|
+
lightPdf = 0.0;
|
|
348
|
+
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if ( lightPdf <= 0.0 ) {
|
|
354
|
+
|
|
355
|
+
rayDataStorage[ index ].lightPdf = 0.0;
|
|
356
|
+
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
357
|
+
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
}
|
|
363
|
+
`;
|
|
364
|
+
|
|
365
|
+
super( fn( params ) );
|
|
366
|
+
|
|
367
|
+
this.defineUniformAccessors( params );
|
|
368
|
+
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Vector2 } from 'three';
|
|
2
|
+
import { StorageBufferAttribute } from 'three/webgpu';
|
|
3
|
+
import { uniform, storage, globalId } from 'three/tsl';
|
|
4
|
+
import { ComputeKernel } from '../ComputeKernel.js';
|
|
5
|
+
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
6
|
+
import { rayDataStruct, pixelQueueNonAtomicStruct } from './structs.js';
|
|
7
|
+
|
|
8
|
+
// Runs once per reset: assigns each of the first rayData-pool-count pixels to a path slot and parks
|
|
9
|
+
// the overflow pixel indices in the pixel queue. Slots are initialized so LogicKernel skips them and
|
|
10
|
+
// MaterialKernel immediately generates fresh camera rays. Later budget changes are handled by
|
|
11
|
+
// resizing the pool in the render loop, with ResetSlotsKernel returning retired slots' pixels.
|
|
12
|
+
export class PopulatePixelIndicesKernel extends ComputeKernel {
|
|
13
|
+
|
|
14
|
+
constructor( ) {
|
|
15
|
+
|
|
16
|
+
const params = {
|
|
17
|
+
rayDataStorage: storage( new StorageBufferAttribute( 1, 1 ), rayDataStruct ),
|
|
18
|
+
pixelQueue: storage( new StorageBufferAttribute( 1, 1 ), pixelQueueNonAtomicStruct ),
|
|
19
|
+
targetDimensions: uniform( new Vector2() ),
|
|
20
|
+
frameBudget: uniform( 0, 'uint' ),
|
|
21
|
+
globalId: globalId,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const fn = wgslTagFn/* wgsl */`
|
|
25
|
+
fn compute( targetDimensions: vec2u, frameBudget: u32, globalId: vec3u ) -> void {
|
|
26
|
+
|
|
27
|
+
let rayDataStorage = &${ params.rayDataStorage };
|
|
28
|
+
let pixelQueue = &${ params.pixelQueue };
|
|
29
|
+
|
|
30
|
+
if ( globalId.x >= targetDimensions.x || globalId.y >= targetDimensions.y ) {
|
|
31
|
+
|
|
32
|
+
return;
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let rayCount = min( frameBudget, arrayLength( rayDataStorage ) );
|
|
37
|
+
let pixelCount = targetDimensions.x * targetDimensions.y;
|
|
38
|
+
if ( globalId.x == 0u && globalId.y == 0u ) {
|
|
39
|
+
|
|
40
|
+
pixelQueue.current = 0u;
|
|
41
|
+
pixelQueue.elementCount = select( 0u, pixelCount - rayCount, pixelCount >= rayCount );
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let pixelHash = ( globalId.x << 16 ) | globalId.y;
|
|
46
|
+
let pixelIndex = globalId.x + globalId.y * targetDimensions.x;
|
|
47
|
+
if ( pixelIndex < rayCount ) {
|
|
48
|
+
|
|
49
|
+
rayDataStorage[ pixelIndex ].pixelIndex = pixelHash;
|
|
50
|
+
rayDataStorage[ pixelIndex ].resultColor = vec4f( 0.0 );
|
|
51
|
+
rayDataStorage[ pixelIndex ].throughputColor = vec3f( 0.0 );
|
|
52
|
+
rayDataStorage[ pixelIndex ].objectIndex = - 1;
|
|
53
|
+
rayDataStorage[ pixelIndex ].alphaDepth = 0u;
|
|
54
|
+
rayDataStorage[ pixelIndex ].rayIntersectionIndex = - 1;
|
|
55
|
+
rayDataStorage[ pixelIndex ].shadowRayIntersectionIndex = - 1;
|
|
56
|
+
|
|
57
|
+
} else {
|
|
58
|
+
|
|
59
|
+
pixelQueue.elements[ pixelIndex - rayCount ] = pixelHash;
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
super( fn( params ) );
|
|
67
|
+
|
|
68
|
+
this.defineUniformAccessors( params );
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { IndirectStorageBufferAttribute, StorageBufferAttribute } from 'three/webgpu';
|
|
2
|
+
import { storage } from 'three/tsl';
|
|
3
|
+
import { ComputeKernel } from '../ComputeKernel.js';
|
|
4
|
+
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
5
|
+
import { rayQueueAtomicStruct } from './structs.js';
|
|
6
|
+
|
|
7
|
+
// Converts a queue's atomic length counter into indirect dispatch arguments so the trace kernels can
|
|
8
|
+
// be dispatched with exactly as many threads as there are queued rays.
|
|
9
|
+
export class QueueLengthToDispatchKernel extends ComputeKernel {
|
|
10
|
+
|
|
11
|
+
constructor( queueStruct = rayQueueAtomicStruct, workgroupSize = 64 ) {
|
|
12
|
+
|
|
13
|
+
const params = {
|
|
14
|
+
queue: storage( new StorageBufferAttribute( 1, 1 ), queueStruct ),
|
|
15
|
+
outputDispatch: storage( new IndirectStorageBufferAttribute( 3, 1 ), 'u32' ).setName( 'outputDispatch' ),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const fn = wgslTagFn/* wgsl */`
|
|
19
|
+
fn compute() -> void {
|
|
20
|
+
|
|
21
|
+
let queue = &${ params.queue };
|
|
22
|
+
let outputDispatch = &${ params.outputDispatch };
|
|
23
|
+
|
|
24
|
+
let queueLength = atomicLoad( &queue.length );
|
|
25
|
+
outputDispatch[ 0 ] = ( queueLength + ${ workgroupSize - 1 }u ) / ${ workgroupSize }u;
|
|
26
|
+
outputDispatch[ 1 ] = 1u;
|
|
27
|
+
outputDispatch[ 2 ] = 1u;
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
super( fn( params ) );
|
|
33
|
+
|
|
34
|
+
this.defineUniformAccessors( params );
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { StorageBufferAttribute } from 'three/webgpu';
|
|
2
|
+
import { uniform, storage, globalId } from 'three/tsl';
|
|
3
|
+
import { ComputeKernel } from '../ComputeKernel.js';
|
|
4
|
+
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
5
|
+
import { rayDataStruct, pixelQueueStruct } from './structs.js';
|
|
6
|
+
|
|
7
|
+
// Moves pixels between a range of path slots and the queue when the pool is resized. Slots added
|
|
8
|
+
// by a grow take a pixel from the queue tail and start idle, as MaterialKernel expects. Slots
|
|
9
|
+
// dropped by a shrink push their pixel onto the tail before the pool is discarded. Runs between
|
|
10
|
+
// frames, so nothing else touches the queue meanwhile.
|
|
11
|
+
export class ResetSlotsKernel extends ComputeKernel {
|
|
12
|
+
|
|
13
|
+
constructor( ) {
|
|
14
|
+
|
|
15
|
+
const params = {
|
|
16
|
+
rayDataStorage: storage( new StorageBufferAttribute( 1, 1 ), rayDataStruct ),
|
|
17
|
+
pixelQueue: storage( new StorageBufferAttribute( 1, 1 ), pixelQueueStruct ),
|
|
18
|
+
start: uniform( 0, 'uint' ),
|
|
19
|
+
end: uniform( 0, 'uint' ),
|
|
20
|
+
addingSlots: uniform( 0, 'uint' ),
|
|
21
|
+
globalId: globalId,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const fn = wgslTagFn/* wgsl */`
|
|
25
|
+
fn compute( start: u32, end: u32, addingSlots: u32, globalId: vec3u ) -> void {
|
|
26
|
+
|
|
27
|
+
let rayDataStorage = &${ params.rayDataStorage };
|
|
28
|
+
let pixelQueue = &${ params.pixelQueue };
|
|
29
|
+
|
|
30
|
+
let index = start + globalId.x;
|
|
31
|
+
if ( index >= end ) {
|
|
32
|
+
|
|
33
|
+
return;
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if ( addingSlots == 0u ) {
|
|
38
|
+
|
|
39
|
+
// slots being removed hand their pixel back before the pool is discarded
|
|
40
|
+
let queueIndex = atomicAdd( &pixelQueue.elementCount, 1u );
|
|
41
|
+
atomicStore( &pixelQueue.elements[ queueIndex ], rayDataStorage[ index ].pixelIndex );
|
|
42
|
+
|
|
43
|
+
} else {
|
|
44
|
+
|
|
45
|
+
// the pool never exceeds the pixel count, so the queue holds enough for every new slot
|
|
46
|
+
let previousCount = atomicSub( &pixelQueue.elementCount, 1u );
|
|
47
|
+
rayDataStorage[ index ].pixelIndex = atomicLoad( &pixelQueue.elements[ previousCount - 1u ] );
|
|
48
|
+
rayDataStorage[ index ].resultColor = vec4f( 0.0 );
|
|
49
|
+
rayDataStorage[ index ].throughputColor = vec3f( 0.0 );
|
|
50
|
+
rayDataStorage[ index ].objectIndex = - 1;
|
|
51
|
+
rayDataStorage[ index ].alphaDepth = 0u;
|
|
52
|
+
rayDataStorage[ index ].rayIntersectionIndex = - 1;
|
|
53
|
+
rayDataStorage[ index ].shadowRayIntersectionIndex = - 1;
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
super( fn( params ) );
|
|
61
|
+
|
|
62
|
+
this.defineUniformAccessors( params );
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { StorageBufferAttribute } from 'three/webgpu';
|
|
2
|
+
import { ComputeKernel } from '../ComputeKernel.js';
|
|
3
|
+
import { storage, globalId } from 'three/tsl';
|
|
4
|
+
import { proxy, wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
5
|
+
import { rngInit } from '../../nodes/random.wgsl.js';
|
|
6
|
+
import { rayQueueStruct, intersectionResultStruct } from './structs.js';
|
|
7
|
+
|
|
8
|
+
// Pure BVH traversal over the queued bounce rays: one thread per queued ray, writing a compact
|
|
9
|
+
// intersection result at the ray's queue index for LogicKernel to consume next frame.
|
|
10
|
+
export class TraceRayKernel extends ComputeKernel {
|
|
11
|
+
|
|
12
|
+
constructor( ) {
|
|
13
|
+
|
|
14
|
+
const params = {
|
|
15
|
+
bvhData: { value: null },
|
|
16
|
+
|
|
17
|
+
rayQueue: storage( new StorageBufferAttribute( 1, 1 ), rayQueueStruct ),
|
|
18
|
+
rayIntersectionsStorage: storage( new StorageBufferAttribute( 1, 1 ), intersectionResultStruct ),
|
|
19
|
+
|
|
20
|
+
globalId: globalId,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const raycastOutput = proxy( 'bvhData.value.fns.raycastFirstHit.outputType', params );
|
|
24
|
+
const raycastFirstHitFn = proxy( 'bvhData.value.fns.raycastFirstHit', params );
|
|
25
|
+
|
|
26
|
+
const fn = wgslTagFn /* wgsl */`
|
|
27
|
+
|
|
28
|
+
fn compute( globalId: vec3u ) -> void {
|
|
29
|
+
|
|
30
|
+
let rayQueue = &${ params.rayQueue };
|
|
31
|
+
let rayIntersectionsStorage = &${ params.rayIntersectionsStorage };
|
|
32
|
+
|
|
33
|
+
let index = globalId.x;
|
|
34
|
+
if ( index >= rayQueue.length ) {
|
|
35
|
+
|
|
36
|
+
return;
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let queuedRay = ( *rayQueue ).elements[ index ];
|
|
41
|
+
let indexUV = vec2u( queuedRay.pixelIndex >> 16, queuedRay.pixelIndex & 0xFFFF );
|
|
42
|
+
${ rngInit }( indexUV, queuedRay.seed, queuedRay.currentBounce + queuedRay.alphaDepth );
|
|
43
|
+
|
|
44
|
+
let ray = Ray( queuedRay.origin, queuedRay.direction, queuedRay.maxDist );
|
|
45
|
+
var hitResult: ${ raycastOutput };
|
|
46
|
+
if ( ${ raycastFirstHitFn }( ray, &hitResult ) ) {
|
|
47
|
+
|
|
48
|
+
rayIntersectionsStorage[ index ].barycoord = hitResult.barycoord;
|
|
49
|
+
rayIntersectionsStorage[ index ].objectIndex = i32( hitResult.objectIndex );
|
|
50
|
+
rayIntersectionsStorage[ index ].position = ray.origin + ray.direction * hitResult.dist;
|
|
51
|
+
rayIntersectionsStorage[ index ].dist = hitResult.dist;
|
|
52
|
+
rayIntersectionsStorage[ index ].normal = hitResult.normal.xyz;
|
|
53
|
+
rayIntersectionsStorage[ index ].side = hitResult.side;
|
|
54
|
+
rayIntersectionsStorage[ index ].indices = hitResult.indices.xyz;
|
|
55
|
+
|
|
56
|
+
} else {
|
|
57
|
+
|
|
58
|
+
rayIntersectionsStorage[ index ].objectIndex = - 1;
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
super( fn( params ) );
|
|
66
|
+
|
|
67
|
+
this.defineUniformAccessors( params );
|
|
68
|
+
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { StorageBufferAttribute } from 'three/webgpu';
|
|
2
|
+
import { ComputeKernel } from '../ComputeKernel.js';
|
|
3
|
+
import { storage, globalId } from 'three/tsl';
|
|
4
|
+
import { proxy, wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
5
|
+
import { rngInit } from '../../nodes/random.wgsl.js';
|
|
6
|
+
import { rayQueueStruct, intersectionResultStruct } from './structs.js';
|
|
7
|
+
|
|
8
|
+
// Pure BVH traversal over the queued shadow rays. Uses the same first-hit traversal as the bounce
|
|
9
|
+
// rays ( no dedicated any-hit traversal exists yet ); LogicKernel decides occlusion by comparing the
|
|
10
|
+
// hit distance against the light distance.
|
|
11
|
+
export class TraceShadowRayKernel extends ComputeKernel {
|
|
12
|
+
|
|
13
|
+
constructor( ) {
|
|
14
|
+
|
|
15
|
+
const params = {
|
|
16
|
+
bvhData: { value: null },
|
|
17
|
+
|
|
18
|
+
shadowRayQueue: storage( new StorageBufferAttribute( 1, 1 ), rayQueueStruct ),
|
|
19
|
+
shadowRayIntersectionsStorage: storage( new StorageBufferAttribute( 1, 1 ), intersectionResultStruct ),
|
|
20
|
+
|
|
21
|
+
globalId: globalId,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const raycastOutput = proxy( 'bvhData.value.fns.raycastFirstHit.outputType', params );
|
|
25
|
+
const raycastFirstHitFn = proxy( 'bvhData.value.fns.raycastFirstHit', params );
|
|
26
|
+
|
|
27
|
+
const fn = wgslTagFn /* wgsl */`
|
|
28
|
+
|
|
29
|
+
fn compute( globalId: vec3u ) -> void {
|
|
30
|
+
|
|
31
|
+
let shadowRayQueue = &${ params.shadowRayQueue };
|
|
32
|
+
let shadowRayIntersectionsStorage = &${ params.shadowRayIntersectionsStorage };
|
|
33
|
+
|
|
34
|
+
let index = globalId.x;
|
|
35
|
+
if ( index >= shadowRayQueue.length ) {
|
|
36
|
+
|
|
37
|
+
return;
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let queuedRay = ( *shadowRayQueue ).elements[ index ];
|
|
42
|
+
let indexUV = vec2u( queuedRay.pixelIndex >> 16, queuedRay.pixelIndex & 0xFFFF );
|
|
43
|
+
${ rngInit }( indexUV, queuedRay.seed, queuedRay.currentBounce + queuedRay.alphaDepth );
|
|
44
|
+
|
|
45
|
+
let ray = Ray( queuedRay.origin, queuedRay.direction, queuedRay.maxDist );
|
|
46
|
+
var hitResult: ${ raycastOutput };
|
|
47
|
+
if ( ${ raycastFirstHitFn }( ray, &hitResult ) ) {
|
|
48
|
+
|
|
49
|
+
shadowRayIntersectionsStorage[ index ].objectIndex = i32( hitResult.objectIndex );
|
|
50
|
+
shadowRayIntersectionsStorage[ index ].dist = hitResult.dist;
|
|
51
|
+
|
|
52
|
+
} else {
|
|
53
|
+
|
|
54
|
+
shadowRayIntersectionsStorage[ index ].objectIndex = - 1;
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
super( fn( params ) );
|
|
62
|
+
|
|
63
|
+
this.defineUniformAccessors( params );
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
}
|