three-gpu-pathtracer 0.0.26 → 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 +4 -2
- package/build/index.module.js +23 -5
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +22 -4
- package/build/index.umd.cjs.map +1 -1
- package/package.json +8 -8
- package/src/core/PathTracingSceneGenerator.js +1 -1
- package/src/core/utils/sceneUpdateUtils.js +2 -1
- package/src/uniforms/EquirectHdrInfoUniform.js +4 -2
- package/src/uniforms/LightsInfoUniformStruct.js +16 -1
- package/src/webgpu/API.md +167 -19
- package/src/webgpu/AtlasTexture.js +32 -12
- package/src/webgpu/EquirectHdrInfoNode.js +97 -46
- package/src/webgpu/LightsInfoNode.js +142 -4
- package/src/webgpu/MegaKernelPathTracer.js +11 -3
- package/src/webgpu/PathTracerBackend.js +174 -21
- package/src/webgpu/TurquinTexture.js +2 -0
- package/src/webgpu/WaveFrontPathTracer.js +169 -71
- package/src/webgpu/WebGPUPathTracer.js +637 -51
- package/src/webgpu/compute/ComputeKernel.js +18 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +253 -71
- 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 +136 -55
- 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 -23
- package/src/webgpu/compute/wavefront/structs.js +63 -35
- package/src/webgpu/constants.js +2 -2
- package/src/webgpu/denoise/OIDNDenoiser.js +81 -78
- package/src/webgpu/index.d.ts +79 -6
- package/src/webgpu/materials/GltfCompliantMaterial.js +20 -6
- package/src/webgpu/materials/debug/AuxDebugMaterial.js +54 -0
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +109 -17
- package/src/webgpu/nodes/aux.wgsl.js +61 -0
- package/src/webgpu/nodes/eon.wgsl.js +1 -1
- package/src/webgpu/nodes/lights.wgsl.js +21 -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 +8 -1
- package/src/webgpu/upscale/FSRUpscaler.js +3 -2
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ColorManagement, FloatType, HalfFloatType, RGBAFormat } from 'three';
|
|
2
|
+
import { RedIntegerFormat, StorageArrayTexture, StorageTexture, UnsignedIntType } from 'three/webgpu';
|
|
3
|
+
import { AUX_LAYERS } from '../nodes/aux.wgsl.js';
|
|
4
|
+
|
|
5
|
+
// The storage textures the backends accumulate into. The kernels are created with these too, so
|
|
6
|
+
// their pipelines are compiled against the formats they are dispatched with.
|
|
7
|
+
|
|
8
|
+
// the color accumulation, in the working color space
|
|
9
|
+
export function createOutputTarget( name = '' ) {
|
|
10
|
+
|
|
11
|
+
const target = new StorageTexture( 1, 1 );
|
|
12
|
+
target.format = RGBAFormat;
|
|
13
|
+
target.type = FloatType;
|
|
14
|
+
target.colorSpace = ColorManagement.workingColorSpace;
|
|
15
|
+
target.name = name;
|
|
16
|
+
target.generateMipmaps = false;
|
|
17
|
+
return target;
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// the per pixel sample count and its flags
|
|
22
|
+
export function createSampleCountTarget( name = '' ) {
|
|
23
|
+
|
|
24
|
+
const target = new StorageTexture( 1, 1 );
|
|
25
|
+
target.format = RedIntegerFormat;
|
|
26
|
+
target.type = UnsignedIntType;
|
|
27
|
+
target.name = name;
|
|
28
|
+
target.generateMipmaps = false;
|
|
29
|
+
return target;
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// the denoiser's first hit albedo and normal, packed as halves into the layers. See "aux.wgsl.js"
|
|
34
|
+
export function createAuxTarget( name = '' ) {
|
|
35
|
+
|
|
36
|
+
const target = new StorageArrayTexture( 1, 1, AUX_LAYERS );
|
|
37
|
+
target.format = RedIntegerFormat;
|
|
38
|
+
target.type = UnsignedIntType;
|
|
39
|
+
target.name = name;
|
|
40
|
+
target.generateMipmaps = false;
|
|
41
|
+
return target;
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// an albedo or normal image unpacked from the auxiliary target, for the denoiser
|
|
46
|
+
export function createAuxImageTarget( name = '' ) {
|
|
47
|
+
|
|
48
|
+
const target = new StorageTexture( 1, 1 );
|
|
49
|
+
target.format = RGBAFormat;
|
|
50
|
+
target.type = HalfFloatType;
|
|
51
|
+
target.name = name;
|
|
52
|
+
target.generateMipmaps = false;
|
|
53
|
+
return target;
|
|
54
|
+
|
|
55
|
+
}
|
|
@@ -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
|
};
|
|
@@ -53,12 +59,17 @@ export class LogicKernel extends ComputeKernel {
|
|
|
53
59
|
const sampleEnvColor = proxy( 'envInfo.value.sampleColor', params );
|
|
54
60
|
const sampleEnvDir = proxy( 'envInfo.value.sampleDir', params );
|
|
55
61
|
const getEnvDirPdf = proxy( 'envInfo.value.getDirPdf', params );
|
|
62
|
+
const getEnvWeight = proxy( 'envInfo.value.getWeight', params );
|
|
56
63
|
const sampleBackground = proxy( 'backgroundInfo.value.sampleColor', params );
|
|
57
64
|
|
|
58
65
|
// analytic scene lights pulled off the lightsInfo provider (LightsInfoNode)
|
|
59
66
|
const lightsCountNode = proxy( 'lightsInfo.value.countNode', params );
|
|
60
67
|
const randomLightSampleFn = proxyFn( 'lightsInfo.value.randomLightSample', params );
|
|
61
68
|
const intersectLightAtIndexFn = proxyFn( 'lightsInfo.value.intersectLightAtIndex', params );
|
|
69
|
+
const getLightWeightFn = proxyFn( 'lightsInfo.value.getLightWeight', params );
|
|
70
|
+
const getLightsWeightFn = proxyFn( 'lightsInfo.value.getLightsWeight', params );
|
|
71
|
+
const selectLightFn = proxyFn( 'lightsInfo.value.selectLight', params );
|
|
72
|
+
const isLightVisibleToCameraFn = proxyFn( 'lightsInfo.value.isLightVisibleToCamera', params );
|
|
62
73
|
|
|
63
74
|
const fn = wgslTagFn/* wgsl */`
|
|
64
75
|
|
|
@@ -70,6 +81,7 @@ export class LogicKernel extends ComputeKernel {
|
|
|
70
81
|
clampDirect: f32,
|
|
71
82
|
clampIndirect: f32,
|
|
72
83
|
transmissiveBackground: u32,
|
|
84
|
+
accumulateAux: u32,
|
|
73
85
|
|
|
74
86
|
globalId: vec3u
|
|
75
87
|
) -> void {
|
|
@@ -87,7 +99,8 @@ export class LogicKernel extends ComputeKernel {
|
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
// skip slots that have never spawned a ray
|
|
90
|
-
|
|
102
|
+
// whole structs are read from the buffer, not the pointer alias, which Safari fails to compile
|
|
103
|
+
let input = ${ params.rayDataStorage }[ index ];
|
|
91
104
|
if ( input.rayIntersectionIndex < 0 ) {
|
|
92
105
|
|
|
93
106
|
return;
|
|
@@ -95,32 +108,40 @@ export class LogicKernel extends ComputeKernel {
|
|
|
95
108
|
}
|
|
96
109
|
|
|
97
110
|
let indexUV = vec2u( input.pixelIndex >> 16, input.pixelIndex & 0xFFFF );
|
|
98
|
-
${ rngInit }( indexUV, input.seed, input.currentBounce + input.alphaDepth );
|
|
111
|
+
${ rngInit }( indexUV, input.seed, input.currentBounce + input.alphaDepth + input.subsurfaceBounces );
|
|
99
112
|
|
|
100
|
-
// one-sample NEE
|
|
113
|
+
// one-sample NEE chooses between the analytic lights and the environment, matched with
|
|
114
|
+
// the megakernel
|
|
101
115
|
let envActive = ${ envTotalSumNode } > 0.0 && ${ envIntensityNode } > 0.0;
|
|
102
116
|
let lightsCount = ${ lightsCountNode };
|
|
103
|
-
var
|
|
117
|
+
var envWeight = 0.0;
|
|
104
118
|
if ( envActive ) {
|
|
105
119
|
|
|
106
|
-
|
|
120
|
+
envWeight = ${ getEnvWeight }();
|
|
107
121
|
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
var resultColor = input.resultColor;
|
|
111
125
|
var throughputColor = input.throughputColor;
|
|
112
126
|
|
|
127
|
+
// added last, since a camera ray miss overwrites the pixel with the background
|
|
128
|
+
var lightHits = vec3f( 0.0 );
|
|
129
|
+
var cameraHitLight = false;
|
|
130
|
+
|
|
113
131
|
// resolve the previous surface's NEE shadow ray (pre-scatter throughput). The index
|
|
114
132
|
// is negative when no shadow ray was enqueued last frame
|
|
115
133
|
if ( input.shadowRayIntersectionIndex >= 0 && input.lightPdf > 0.0 ) {
|
|
116
134
|
|
|
117
|
-
let shadowHit =
|
|
135
|
+
let shadowHit = ${ params.shadowRayIntersectionsStorage }[ u32( input.shadowRayIntersectionIndex ) ];
|
|
118
136
|
let occluded = shadowHit.objectIndex >= 0;
|
|
119
137
|
if ( ! occluded ) {
|
|
120
138
|
|
|
121
|
-
// env + area lights are also bsdf
|
|
122
|
-
|
|
123
|
-
|
|
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;
|
|
124
145
|
let contribution = ${ clampPathContributionFunc }( directLight, input.currentBounce, clampDirect, clampIndirect );
|
|
125
146
|
resultColor += vec4f( contribution, 0.0 );
|
|
126
147
|
|
|
@@ -142,37 +163,64 @@ export class LogicKernel extends ComputeKernel {
|
|
|
142
163
|
// MaterialKernel, which stages a zeroed pdf and skips the bounce trace when they fire
|
|
143
164
|
var isTerminated = all( throughputColor == vec3f( 0.0 ) ) || input.currentBounce >= maxBounces || ${ isTerminatingScatterFunc }( scatterRec );
|
|
144
165
|
|
|
166
|
+
// apply the scatter across the traced segment, including the weight of a medium walk
|
|
145
167
|
if ( ! isTerminated ) {
|
|
146
168
|
|
|
147
|
-
// apply the scatter across the traced segment
|
|
148
169
|
throughputColor *= scatterRec.color / scatterRec.pdf;
|
|
149
170
|
|
|
150
|
-
|
|
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 ) ];
|
|
151
187
|
let didHit = hitResult.objectIndex >= 0;
|
|
152
188
|
let surfaceDist = select( ${ LIGHT_FAR_DISTANCE }, hitResult.dist, didHit );
|
|
153
189
|
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
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;
|
|
193
|
+
var lightTotalWeight = 0.0;
|
|
194
|
+
if ( isMISWeighted ) {
|
|
157
195
|
|
|
158
|
-
|
|
196
|
+
lightTotalWeight = ${ getLightsWeightFn }( input.origin ) + envWeight;
|
|
159
197
|
|
|
160
|
-
|
|
161
|
-
if ( ${ intersectLightAtIndexFn }( input.origin, input.direction, li, &lightRec ) && lightRec.dist < surfaceDist ) {
|
|
198
|
+
}
|
|
162
199
|
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
202
|
+
for ( var li = 0u; li < lightsCount; li ++ ) {
|
|
165
203
|
|
|
166
|
-
|
|
167
|
-
misWeight = ${ misHeuristicFn }( input.scatterPdf, lightPdf );
|
|
204
|
+
if ( input.currentBounce == 0u && ! ${ isLightVisibleToCameraFn }( li ) ) {
|
|
168
205
|
|
|
169
|
-
|
|
206
|
+
continue;
|
|
170
207
|
|
|
171
|
-
|
|
172
|
-
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
var lightRec: ${ lightRecordStruct };
|
|
211
|
+
if ( ${ intersectLightAtIndexFn }( input.origin, input.direction, li, &lightRec ) && lightRec.dist > input.rayStart && lightRec.dist < input.rayStart + surfaceDist ) {
|
|
212
|
+
|
|
213
|
+
var misWeight = 1.0;
|
|
214
|
+
if ( isMISWeighted ) {
|
|
215
|
+
|
|
216
|
+
let selectionPdf = ${ getLightWeightFn }( li, input.origin ) / lightTotalWeight;
|
|
217
|
+
misWeight = ${ misHeuristicFn }( input.scatterPdf, lightRec.pdf * selectionPdf );
|
|
173
218
|
|
|
174
219
|
}
|
|
175
220
|
|
|
221
|
+
lightHits += ${ clampPathContributionFunc }( lightRec.emission * throughputColor * misWeight, input.currentBounce, clampDirect, clampIndirect );
|
|
222
|
+
cameraHitLight = cameraHitLight || input.currentBounce == 0u;
|
|
223
|
+
|
|
176
224
|
}
|
|
177
225
|
|
|
178
226
|
}
|
|
@@ -187,36 +235,40 @@ export class LogicKernel extends ComputeKernel {
|
|
|
187
235
|
rayDataStorage[ index ].objectIndex = hitResult.objectIndex;
|
|
188
236
|
rayDataStorage[ index ].dist = hitResult.dist;
|
|
189
237
|
|
|
190
|
-
// next event estimation:
|
|
191
|
-
// MaterialKernel evaluates the bsdf and enqueues the shadow ray.
|
|
192
|
-
// TODO: importance-sample the selection by light intensity and solid angle
|
|
238
|
+
// next event estimation: choose one light or the environment by its estimated
|
|
239
|
+
// contribution. MaterialKernel evaluates the bsdf and enqueues the shadow ray.
|
|
193
240
|
var lightPdf = 0.0;
|
|
194
|
-
if ( misEnabled != 0u
|
|
241
|
+
if ( misEnabled != 0u ) {
|
|
195
242
|
|
|
196
243
|
let ruv = ${ rand3 }( ${ RNG_INDEX_DIRECT_LIGHT_SAMPLE } );
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
// the environment, sampled from its CDF, as a light of kind ENVIRONMENT
|
|
202
|
-
let envSample = ${ sampleEnvDir }( ruv.yz );
|
|
203
|
-
lightRec.direction = envSample.direction;
|
|
204
|
-
lightRec.emission = envSample.color;
|
|
205
|
-
lightRec.pdf = envSample.pdf;
|
|
206
|
-
lightRec.dist = ${ LIGHT_FAR_DISTANCE };
|
|
207
|
-
lightRec.lightType = ${ ENVIRONMENT_LIGHT_TYPE };
|
|
244
|
+
var selectionPdf = 0.0;
|
|
245
|
+
let lightIndex = ${ selectLightFn }( hitResult.position, envWeight, ruv.x, &selectionPdf );
|
|
246
|
+
if ( selectionPdf > 0.0 ) {
|
|
208
247
|
|
|
209
|
-
|
|
248
|
+
var lightRec: ${ lightRecordStruct };
|
|
249
|
+
if ( envActive && lightIndex == lightsCount ) {
|
|
210
250
|
|
|
211
|
-
|
|
251
|
+
// the environment, sampled from its CDF, as a light of kind ENVIRONMENT
|
|
252
|
+
let envSample = ${ sampleEnvDir }( ruv.yz );
|
|
253
|
+
lightRec.direction = envSample.direction;
|
|
254
|
+
lightRec.emission = envSample.color;
|
|
255
|
+
lightRec.pdf = envSample.pdf;
|
|
256
|
+
lightRec.dist = ${ LIGHT_FAR_DISTANCE };
|
|
257
|
+
lightRec.lightType = ${ ENVIRONMENT_LIGHT_TYPE };
|
|
212
258
|
|
|
213
|
-
|
|
259
|
+
} else {
|
|
214
260
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
261
|
+
lightRec = ${ randomLightSampleFn }( lightIndex, hitResult.position, ruv.yz );
|
|
262
|
+
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
lightPdf = lightRec.pdf * selectionPdf;
|
|
266
|
+
rayDataStorage[ index ].lightDirection = lightRec.direction;
|
|
267
|
+
rayDataStorage[ index ].lightEmission = lightRec.emission;
|
|
268
|
+
rayDataStorage[ index ].lightDist = lightRec.dist;
|
|
269
|
+
rayDataStorage[ index ].lightType = lightRec.lightType;
|
|
270
|
+
|
|
271
|
+
}
|
|
220
272
|
|
|
221
273
|
}
|
|
222
274
|
|
|
@@ -229,10 +281,10 @@ export class LogicKernel extends ComputeKernel {
|
|
|
229
281
|
if ( input.currentBounce > 0u && input.isFullyTransmissive == 0u ) {
|
|
230
282
|
|
|
231
283
|
var misWeight = 1.0;
|
|
232
|
-
if (
|
|
284
|
+
if ( isMISWeighted && envActive ) {
|
|
233
285
|
|
|
234
286
|
// match the env pdf scaling used by the NEE selection so the two estimators balance
|
|
235
|
-
let envPdf = ${ getEnvDirPdf }( input.direction ) /
|
|
287
|
+
let envPdf = ${ getEnvDirPdf }( input.direction ) * envWeight / lightTotalWeight;
|
|
236
288
|
misWeight = ${ misHeuristicFn }( input.scatterPdf, envPdf );
|
|
237
289
|
|
|
238
290
|
}
|
|
@@ -308,6 +360,14 @@ export class LogicKernel extends ComputeKernel {
|
|
|
308
360
|
|
|
309
361
|
}
|
|
310
362
|
|
|
363
|
+
// visible lights are opaque against a transparent background
|
|
364
|
+
resultColor += vec4f( lightHits, 0.0 );
|
|
365
|
+
if ( cameraHitLight ) {
|
|
366
|
+
|
|
367
|
+
resultColor.a = 1.0;
|
|
368
|
+
|
|
369
|
+
}
|
|
370
|
+
|
|
311
371
|
// the color rows are stored top down to match a rasterized render target
|
|
312
372
|
let colorIndex = vec2u( indexUV.x, textureDimensions( ${ params.outputTarget } ).y - 1u - indexUV.y );
|
|
313
373
|
let storedSamples = textureLoad( ${ params.sampleCountTarget }, indexUV ).r & ${ SAMPLE_COUNT_MASK }u;
|
|
@@ -321,6 +381,27 @@ export class LogicKernel extends ComputeKernel {
|
|
|
321
381
|
textureStore( ${ params.sampleCountTarget }, indexUV, vec4( ${ SAMPLE_DISPATCHED_FLAG }u | sampleCount ) );
|
|
322
382
|
textureStore( ${ params.outputTarget }, colorIndex, blendedColor );
|
|
323
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
|
+
|
|
324
405
|
rayDataStorage[ index ].objectIndex = - 1;
|
|
325
406
|
|
|
326
407
|
} else {
|