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
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
import { Matrix3 } from 'three/webgpu';
|
|
2
|
-
import { texture, sampler, uniform,
|
|
1
|
+
import { Matrix3, StorageBufferAttribute } from 'three/webgpu';
|
|
2
|
+
import { texture, sampler, uniform, storage, PI } from 'three/tsl';
|
|
3
3
|
import { EquirectHdrInfoUniform } from '../uniforms/EquirectHdrInfoUniform.js';
|
|
4
4
|
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
5
|
-
import {
|
|
6
|
-
import { equirectDirectionToUvFn, equirectUvToDirectionFn
|
|
7
|
-
|
|
8
|
-
// totalWeight includes sin( theta ), which cancels in the solid-angle conversion.
|
|
9
|
-
const equirectLuminancePdfFn = wgslFn( /* wgsl */ `
|
|
10
|
-
|
|
11
|
-
fn equirectLuminancePdf( luminance: f32, totalWeight: f32, resolution: vec2u ) -> f32 {
|
|
12
|
-
|
|
13
|
-
return f32( resolution.x * resolution.y ) * luminance / ( 2.0 * PI * PI * totalWeight );
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
`, [ constants ] );
|
|
5
|
+
import { environmentSampleStruct } from './nodes/structs.wgsl.js';
|
|
6
|
+
import { equirectDirectionToUvFn, equirectUvToDirectionFn } from './nodes/sampling.wgsl.js';
|
|
18
7
|
|
|
19
8
|
export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
20
9
|
|
|
@@ -22,13 +11,10 @@ export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
|
22
11
|
|
|
23
12
|
super();
|
|
24
13
|
|
|
25
|
-
// environment map
|
|
14
|
+
// environment map with a sampler, and the importance sampling CDFs for the default 1x1 map
|
|
26
15
|
this.mapNode = texture( this.map );
|
|
27
16
|
this.mapSampler = sampler( this.mapNode );
|
|
28
|
-
this.
|
|
29
|
-
this.marginalSampler = sampler( this.marginalNode );
|
|
30
|
-
this.conditionalNode = texture( this.conditionalWeights );
|
|
31
|
-
this.conditionalSampler = sampler( this.conditionalNode );
|
|
17
|
+
this.cdfNode = storage( new StorageBufferAttribute( new Float32Array( [ 0, 1, 0, 1 ] ), 1 ), 'float' ).toReadOnly();
|
|
32
18
|
|
|
33
19
|
// scalar parameters that assemble into the EnvironmentInfo struct in the shader
|
|
34
20
|
this.rotationNode = uniform( new Matrix3() );
|
|
@@ -53,17 +39,35 @@ export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
|
53
39
|
|
|
54
40
|
const {
|
|
55
41
|
mapNode,
|
|
56
|
-
|
|
57
|
-
conditionalNode,
|
|
42
|
+
cdfNode,
|
|
58
43
|
totalSumNode,
|
|
59
44
|
} = this;
|
|
60
45
|
|
|
61
46
|
// refresh values in place on the existing nodes so no rebuild is required
|
|
62
47
|
mapNode.value = this.map;
|
|
63
|
-
marginalNode.value = this.marginalWeights;
|
|
64
|
-
conditionalNode.value = this.conditionalWeights;
|
|
65
48
|
totalSumNode.value = this.totalSum;
|
|
66
49
|
|
|
50
|
+
// pack the row CDF and then each row's pixel CDF into one buffer, each led by a zero so a
|
|
51
|
+
// pixel's range is two neighboring entries
|
|
52
|
+
const { width, height } = this.map.image;
|
|
53
|
+
const { cdfMarginal, cdfConditional } = this;
|
|
54
|
+
const cdf = new Float32Array( height + 1 + height * ( width + 1 ) );
|
|
55
|
+
cdf.set( cdfMarginal, 1 );
|
|
56
|
+
for ( let y = 0; y < height; y ++ ) {
|
|
57
|
+
|
|
58
|
+
cdf.set( cdfConditional.subarray( y * width, y * width + width ), height + 1 + y * ( width + 1 ) + 1 );
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
cdfNode.value = new StorageBufferAttribute( cdf, 1 );
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
dispose() {
|
|
67
|
+
|
|
68
|
+
super.dispose();
|
|
69
|
+
this.cdfNode.value.dispose();
|
|
70
|
+
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
_initFns() {
|
|
@@ -71,15 +75,60 @@ export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
|
71
75
|
const {
|
|
72
76
|
mapNode,
|
|
73
77
|
mapSampler,
|
|
74
|
-
|
|
75
|
-
marginalSampler,
|
|
76
|
-
conditionalNode,
|
|
77
|
-
conditionalSampler,
|
|
78
|
+
cdfNode,
|
|
78
79
|
totalSumNode,
|
|
79
80
|
rotationNode,
|
|
80
81
|
intensityNode,
|
|
81
82
|
} = this;
|
|
82
83
|
|
|
84
|
+
// Binary searches the "count" pixel ranges from "offset" for the one holding "r", writing where
|
|
85
|
+
// "r" falls within it to "fraction". See:
|
|
86
|
+
// https://pbr-book.org/3ed-2018/Monte_Carlo_Integration/2D_Sampling_with_Multidimensional_Transformations#Piecewise-Constant2DDistributions
|
|
87
|
+
// https://github.com/blender/cycles/blob/main/src/kernel/light/background.h
|
|
88
|
+
const searchCdfFn = wgslTagFn/* wgsl */`
|
|
89
|
+
fn searchEnvCdf( r: f32, offset: u32, count: u32, fraction: ptr<function, f32> ) -> u32 {
|
|
90
|
+
|
|
91
|
+
var lower = 0u;
|
|
92
|
+
var upper = count - 1u;
|
|
93
|
+
while ( lower < upper ) {
|
|
94
|
+
|
|
95
|
+
let mid = ( lower + upper ) >> 1u;
|
|
96
|
+
if ( ${ cdfNode }[ offset + mid + 1u ] <= r ) {
|
|
97
|
+
|
|
98
|
+
lower = mid + 1u;
|
|
99
|
+
|
|
100
|
+
} else {
|
|
101
|
+
|
|
102
|
+
upper = mid;
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let start = ${ cdfNode }[ offset + lower ];
|
|
109
|
+
let end = ${ cdfNode }[ offset + lower + 1u ];
|
|
110
|
+
*fraction = select( 0.5, saturate( ( r - start ) / ( end - start ) ), end > start );
|
|
111
|
+
return lower;
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
// the solid angle pdf of a direction at "v" in the given pixel, which is sampled uniformly over its area
|
|
117
|
+
const getTexelPdfFn = wgslTagFn/* wgsl */`
|
|
118
|
+
fn getEnvTexelPdf( texel: vec2u, v: f32 ) -> f32 {
|
|
119
|
+
|
|
120
|
+
let resolution = textureDimensions( ${ mapNode } );
|
|
121
|
+
let rowOffset = resolution.y + 1u + texel.y * ( resolution.x + 1u );
|
|
122
|
+
let rowPdf = ${ cdfNode }[ texel.y + 1u ] - ${ cdfNode }[ texel.y ];
|
|
123
|
+
let pixelPdf = ${ cdfNode }[ rowOffset + texel.x + 1u ] - ${ cdfNode }[ rowOffset + texel.x ];
|
|
124
|
+
|
|
125
|
+
// the equirect projection stretches each pixel by 1 / sin( theta ) over the sphere
|
|
126
|
+
let sinTheta = sin( ${ PI } * v );
|
|
127
|
+
return select( 0.0, rowPdf * pixelPdf * f32( resolution.x * resolution.y ) / ( 2.0 * ${ PI } * ${ PI } * sinTheta ), sinTheta > 0.0 );
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
`;
|
|
131
|
+
|
|
83
132
|
this.sampleColor = wgslTagFn/* wgsl */`
|
|
84
133
|
fn sampleEnv( direction: vec3f ) -> vec4f {
|
|
85
134
|
|
|
@@ -97,31 +146,34 @@ export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
|
97
146
|
|
|
98
147
|
var result: ${ environmentSampleStruct };
|
|
99
148
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
let
|
|
103
|
-
|
|
104
|
-
|
|
149
|
+
// search the CDFs: marginal picks the row (v), conditional picks the column (u), and the
|
|
150
|
+
// fractions place the sample within the pixel
|
|
151
|
+
let resolution = textureDimensions( ${ mapNode } );
|
|
152
|
+
var rowFraction = 0.0;
|
|
153
|
+
var columnFraction = 0.0;
|
|
154
|
+
let row = ${ searchCdfFn }( r.x, 0u, resolution.y, &rowFraction );
|
|
155
|
+
let column = ${ searchCdfFn }( r.y, resolution.y + 1u + row * ( resolution.x + 1u ), resolution.x, &columnFraction );
|
|
156
|
+
let texel = vec2u( column, row );
|
|
157
|
+
let uv = ( vec2f( texel ) + vec2f( columnFraction, rowFraction ) ) / vec2f( resolution );
|
|
105
158
|
|
|
106
159
|
let direction = ${ equirectUvToDirectionFn }( uv );
|
|
107
160
|
let color = textureSampleLevel( ${ mapNode }, ${ mapSampler }, uv, 0 ).rgb;
|
|
108
161
|
|
|
109
162
|
result.direction = transpose( ${ rotationNode } ) * direction;
|
|
110
163
|
result.color = color * ${ intensityNode };
|
|
164
|
+
result.pdf = ${ getTexelPdfFn }( texel, uv.y );
|
|
111
165
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
let lum = ${ luminanceFn }( color );
|
|
115
|
-
let resolution = textureDimensions( ${ mapNode } );
|
|
116
|
-
result.pdf = ${ equirectLuminancePdfFn }( lum, totalSum, resolution );
|
|
117
|
-
|
|
118
|
-
} else {
|
|
166
|
+
return result;
|
|
119
167
|
|
|
120
|
-
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
121
170
|
|
|
122
|
-
|
|
171
|
+
// the environment's average irradiance, for weighing it against the lights
|
|
172
|
+
this.getWeight = wgslTagFn/* wgsl */`
|
|
173
|
+
fn getEnvWeight() -> f32 {
|
|
123
174
|
|
|
124
|
-
|
|
175
|
+
let resolution = textureDimensions( ${ mapNode } );
|
|
176
|
+
return ${ intensityNode } * ${ PI } * ${ PI } * ${ totalSumNode } / ( 2.0 * f32( resolution.x * resolution.y ) );
|
|
125
177
|
|
|
126
178
|
}
|
|
127
179
|
`;
|
|
@@ -137,11 +189,10 @@ export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
|
137
189
|
|
|
138
190
|
let rotatedDir = ${ rotationNode } * direction;
|
|
139
191
|
let mapUv = ${ equirectDirectionToUvFn }( rotatedDir );
|
|
140
|
-
let color = textureSampleLevel( ${ mapNode }, ${ mapSampler }, mapUv, 0 ).rgb;
|
|
141
|
-
let lum = ${ luminanceFn }( color );
|
|
142
192
|
let resolution = textureDimensions( ${ mapNode } );
|
|
193
|
+
let texel = min( vec2u( mapUv * vec2f( resolution ) ), resolution - 1u );
|
|
143
194
|
|
|
144
|
-
return ${
|
|
195
|
+
return ${ getTexelPdfFn }( texel, mapUv.y );
|
|
145
196
|
|
|
146
197
|
}
|
|
147
198
|
`;
|
|
@@ -5,6 +5,7 @@ import { AtlasTexture } from './AtlasTexture.js';
|
|
|
5
5
|
import { LightsInfoUniformStruct } from '../uniforms/LightsInfoUniformStruct.js';
|
|
6
6
|
import { lightStruct, lightRecordStruct } from './nodes/structs.wgsl.js';
|
|
7
7
|
import { sampleTexelFunc } from './nodes/utils.wgsl.js';
|
|
8
|
+
import { luminanceFn } from './nodes/sampling.wgsl.js';
|
|
8
9
|
import {
|
|
9
10
|
RECT_AREA_LIGHT_TYPE,
|
|
10
11
|
CIRC_AREA_LIGHT_TYPE,
|
|
@@ -17,6 +18,8 @@ import {
|
|
|
17
18
|
randomAreaLightSampleFn,
|
|
18
19
|
randomSpotLightSampleFn,
|
|
19
20
|
getSpotAttenuationFn,
|
|
21
|
+
getDistanceAttenuationFn,
|
|
22
|
+
getSpotLightNearestFn,
|
|
20
23
|
} from './nodes/lights.wgsl.js';
|
|
21
24
|
|
|
22
25
|
export class LightsInfoNode extends LightsInfoUniformStruct {
|
|
@@ -33,7 +36,7 @@ export class LightsInfoNode extends LightsInfoUniformStruct {
|
|
|
33
36
|
// ies profiles packed into an atlas alongside their placement rects
|
|
34
37
|
this.iesAtlas = new AtlasTexture( { type: HalfFloatType } );
|
|
35
38
|
this.iesProfilesNode = texture( this.iesAtlas.texture );
|
|
36
|
-
this.iesInfoNode = uniformArray( this.iesAtlas.textureInfo, 'uvec4' );
|
|
39
|
+
this.iesInfoNode = uniformArray( this.iesAtlas.textureInfo, 'uvec4' ).setName( 'iesInfo' );
|
|
37
40
|
|
|
38
41
|
this._initFns();
|
|
39
42
|
|
|
@@ -65,12 +68,13 @@ export class LightsInfoNode extends LightsInfoUniformStruct {
|
|
|
65
68
|
const src = this.tex.image.data;
|
|
66
69
|
this.buffer.array.set( src.subarray( 0, count * stride ) );
|
|
67
70
|
|
|
68
|
-
// rewrite the int fields ( lightType, iesProfile ) as i32 bits
|
|
71
|
+
// rewrite the int fields ( lightType, visibleToCameraRays, iesProfile ) as i32 bits
|
|
69
72
|
const intView = new Int32Array( this.buffer.array.buffer );
|
|
70
73
|
for ( let i = 0; i < count; i ++ ) {
|
|
71
74
|
|
|
72
75
|
const base = i * stride;
|
|
73
76
|
intView[ base + 3 ] = Math.round( src[ base + 3 ] );
|
|
77
|
+
intView[ base + 11 ] = Math.round( src[ base + 11 ] );
|
|
74
78
|
intView[ base + 21 ] = Math.round( src[ base + 21 ] );
|
|
75
79
|
|
|
76
80
|
}
|
|
@@ -84,12 +88,146 @@ export class LightsInfoNode extends LightsInfoUniformStruct {
|
|
|
84
88
|
|
|
85
89
|
_initFns() {
|
|
86
90
|
|
|
87
|
-
const { bufferNode, iesProfilesNode, iesInfoNode } = this;
|
|
91
|
+
const { bufferNode, countNode, iesProfilesNode, iesInfoNode } = this;
|
|
88
92
|
|
|
89
93
|
// profiles are sampled out of an atlas so filtering must resolve tile-relative wrapping
|
|
90
94
|
const sampleIesTexelFn = sampleTexelFunc( iesInfoNode, iesProfilesNode, 'sampleIesTexel' );
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
this.isLightVisibleToCamera = wgslTagFn/* wgsl */`
|
|
97
|
+
fn isLightVisibleToCamera( index: u32 ) -> bool {
|
|
98
|
+
|
|
99
|
+
return ${ bufferNode }[ index ].visibleToCameraRays != 0;
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
// A cheap estimate of how much light reaches a position from a light, ignoring occlusion and
|
|
105
|
+
// the surface orientation, used to choose which light to sample.
|
|
106
|
+
// TODO: weigh lights below the surface's horizon to zero using its normal, skipped for
|
|
107
|
+
// transmissive surfaces. Forward hit MIS would then need the previous vertex's normal too.
|
|
108
|
+
this.getLightWeight = wgslTagFn/* wgsl */`
|
|
109
|
+
fn getLightWeight( index: u32, position: vec3f ) -> f32 {
|
|
110
|
+
|
|
111
|
+
let light = ${ bufferNode }[ index ];
|
|
112
|
+
let power = ${ luminanceFn }( light.color * light.intensity );
|
|
113
|
+
if ( light.lightType == ${ SPOT_LIGHT_TYPE } ) {
|
|
114
|
+
|
|
115
|
+
// the most any point on the spot's disc lights the position. Ies profiles are not
|
|
116
|
+
// evaluated here
|
|
117
|
+
let nearest = ${ getSpotLightNearestFn }( light, position );
|
|
118
|
+
var attenuation = 1.0;
|
|
119
|
+
if ( light.iesProfile < 0 ) {
|
|
120
|
+
|
|
121
|
+
attenuation = ${ getSpotAttenuationFn }( light.coneCos, light.penumbraCos, nearest.x );
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return power * attenuation * ${ getDistanceAttenuationFn }( nearest.y, light.distance, light.decay );
|
|
126
|
+
|
|
127
|
+
} else if ( light.lightType == ${ POINT_LIGHT_TYPE } ) {
|
|
128
|
+
|
|
129
|
+
// the point light's world position is packed into the u slot
|
|
130
|
+
let dist = length( light.u - position );
|
|
131
|
+
return power * ${ getDistanceAttenuationFn }( dist, light.distance, light.decay );
|
|
132
|
+
|
|
133
|
+
} else if ( light.lightType == ${ DIR_LIGHT_TYPE } ) {
|
|
134
|
+
|
|
135
|
+
return power;
|
|
136
|
+
|
|
137
|
+
} else {
|
|
138
|
+
|
|
139
|
+
let toLight = light.position - position;
|
|
140
|
+
let distSq = dot( toLight, toLight );
|
|
141
|
+
if ( distSq == 0.0 ) {
|
|
142
|
+
|
|
143
|
+
return 0.0;
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// area lights only emit from their front face, and the distance is held above the size
|
|
148
|
+
// of the light so positions on or near it do not dominate
|
|
149
|
+
let normal = normalize( cross( light.u, light.v ) );
|
|
150
|
+
let cosTheta = dot( toLight, normal ) * inverseSqrt( distSq );
|
|
151
|
+
return power * light.area * max( cosTheta, 0.0 ) / max( distSq, light.area );
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
`;
|
|
157
|
+
|
|
158
|
+
this.getLightsWeight = wgslTagFn/* wgsl */`
|
|
159
|
+
fn getLightsWeight( position: vec3f ) -> f32 {
|
|
160
|
+
|
|
161
|
+
var total = 0.0;
|
|
162
|
+
for ( var i = 0u; i < ${ countNode }; i ++ ) {
|
|
163
|
+
|
|
164
|
+
total += ${ this.getLightWeight }( i, position );
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return total;
|
|
169
|
+
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
|
|
173
|
+
// Chooses a light, or the environment when the returned index equals the light count, in
|
|
174
|
+
// proportion to the estimated weights. The pdf is zero when nothing can light the position.
|
|
175
|
+
this.selectLight = wgslTagFn/* wgsl */`
|
|
176
|
+
fn selectLight( position: vec3f, envWeight: f32, r: f32, selectionPdf: ptr<function, f32> ) -> u32 {
|
|
177
|
+
|
|
178
|
+
let count = ${ countNode };
|
|
179
|
+
let totalWeight = ${ this.getLightsWeight }( position ) + envWeight;
|
|
180
|
+
if ( totalWeight <= 0.0 ) {
|
|
181
|
+
|
|
182
|
+
*selectionPdf = 0.0;
|
|
183
|
+
return 0u;
|
|
184
|
+
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let targetWeight = r * totalWeight;
|
|
188
|
+
var accumulated = 0.0;
|
|
189
|
+
var lastWeighted = 0u;
|
|
190
|
+
var index = count;
|
|
191
|
+
for ( var i = 0u; i < count; i ++ ) {
|
|
192
|
+
|
|
193
|
+
let lightWeight = ${ this.getLightWeight }( i, position );
|
|
194
|
+
accumulated += lightWeight;
|
|
195
|
+
if ( lightWeight > 0.0 ) {
|
|
196
|
+
|
|
197
|
+
lastWeighted = i;
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if ( targetWeight < accumulated ) {
|
|
202
|
+
|
|
203
|
+
index = i;
|
|
204
|
+
break;
|
|
205
|
+
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// rounding can carry the target past the last light when there is no environment
|
|
211
|
+
if ( index == count && envWeight <= 0.0 ) {
|
|
212
|
+
|
|
213
|
+
index = lastWeighted;
|
|
214
|
+
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
var weight = envWeight;
|
|
218
|
+
if ( index < count ) {
|
|
219
|
+
|
|
220
|
+
weight = ${ this.getLightWeight }( index, position );
|
|
221
|
+
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
*selectionPdf = weight / totalWeight;
|
|
225
|
+
return index;
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
`;
|
|
229
|
+
|
|
230
|
+
// sample the light at the given index
|
|
93
231
|
this.randomLightSample = wgslTagFn/* wgsl */`
|
|
94
232
|
fn randomLightSample( lightIndex: u32, rayOrigin: vec3f, ruv: vec2f ) -> ${ lightRecordStruct } {
|
|
95
233
|
|
|
@@ -69,7 +69,7 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
69
69
|
|
|
70
70
|
setBVHData( bvhData ) {
|
|
71
71
|
|
|
72
|
-
this.kernel.bvhData = bvhData;
|
|
72
|
+
this.kernel.context.bvhData = bvhData;
|
|
73
73
|
this.rebuild();
|
|
74
74
|
|
|
75
75
|
}
|
|
@@ -179,6 +179,7 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
179
179
|
kernel,
|
|
180
180
|
maxBounces,
|
|
181
181
|
maxTransparentBounces,
|
|
182
|
+
maxSubsurfaceBounces,
|
|
182
183
|
|
|
183
184
|
outputTarget,
|
|
184
185
|
sampleCountTarget,
|
|
@@ -193,6 +194,10 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
193
194
|
|
|
194
195
|
kernel.maxBounces = maxBounces;
|
|
195
196
|
kernel.maxTransparentBounces = maxTransparentBounces;
|
|
197
|
+
kernel.maxSubsurfaceBounces = maxSubsurfaceBounces;
|
|
198
|
+
|
|
199
|
+
// compile the kernel before its first dispatch
|
|
200
|
+
yield* this.compileKernels( [ kernel ] );
|
|
196
201
|
|
|
197
202
|
// number of tile cycles that have finished, ie. the sample count every pixel has reached
|
|
198
203
|
let completedSamples = 0;
|
|
@@ -201,6 +206,8 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
201
206
|
|
|
202
207
|
// skip the tile loop entirely once every pixel is finished
|
|
203
208
|
kernel.maxSamples = this.maxSamples;
|
|
209
|
+
kernel.auxTarget = this.auxTarget ?? this._auxPlaceholder;
|
|
210
|
+
kernel.accumulateAux = this.auxTarget ? 1 : 0;
|
|
204
211
|
if ( this.maxSamples !== 0 && completedSamples >= this.maxSamples ) {
|
|
205
212
|
|
|
206
213
|
yield;
|
|
@@ -210,7 +217,7 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
210
217
|
|
|
211
218
|
// split the frame into a near square grid of tiles no larger than the budget
|
|
212
219
|
this.getSize( _size );
|
|
213
|
-
const budgetTiles = Math.max( 1, Math.ceil( _size.x * _size.y / this.frameBudget ) );
|
|
220
|
+
const budgetTiles = lowResMode ? 1 : Math.max( 1, Math.ceil( _size.x * _size.y / this.frameBudget ) );
|
|
214
221
|
tiles.x = Math.ceil( Math.sqrt( budgetTiles ) );
|
|
215
222
|
tiles.y = Math.ceil( budgetTiles / tiles.x );
|
|
216
223
|
|
|
@@ -245,7 +252,7 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
245
252
|
for ( let y = 0; y < tiles.y; y ++ ) {
|
|
246
253
|
|
|
247
254
|
kernel.offset.set( x, y ).multiply( tileSize );
|
|
248
|
-
|
|
255
|
+
this.dispatch( kernel, dispatchSize );
|
|
249
256
|
|
|
250
257
|
// in low res mode the tiles past the first fall outside the target and do no work
|
|
251
258
|
completedTiles = Math.min( completedTiles + 1, tileCount );
|
|
@@ -271,6 +278,7 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
271
278
|
// TODO: dispose of all buffers
|
|
272
279
|
this.envInfo.dispose();
|
|
273
280
|
this.lightsInfo.dispose();
|
|
281
|
+
this.kernel.dispose();
|
|
274
282
|
|
|
275
283
|
}
|
|
276
284
|
|