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.
Files changed (73) hide show
  1. package/README.md +53 -530
  2. package/build/index.module.js +262 -50
  3. package/build/index.module.js.map +1 -1
  4. package/build/index.umd.cjs +261 -48
  5. package/build/index.umd.cjs.map +1 -1
  6. package/package.json +29 -11
  7. package/src/core/WebGLPathTracer.js +8 -0
  8. package/src/core/utils/sceneUpdateUtils.js +8 -12
  9. package/src/detectors/PrecisionMaterial.js +9 -9
  10. package/src/index.js +1 -0
  11. package/src/objects/PhysicalSpotLight.js +2 -0
  12. package/src/shader/bsdf/bsdf_functions.glsl.js +0 -1
  13. package/src/textures/BlueNoiseTexture.js +6 -6
  14. package/src/textures/ProceduralEquirectTexture.js +8 -7
  15. package/src/textures/turquinMetal.png +0 -0
  16. package/src/uniforms/EquirectHdrInfoUniform.js +9 -4
  17. package/src/uniforms/FloatAttributeTextureArray.js +11 -11
  18. package/src/uniforms/MaterialsTexture.js +9 -9
  19. package/src/webgpu/API.md +762 -0
  20. package/src/webgpu/AtlasTexture.js +471 -0
  21. package/src/webgpu/BlurredEnvMapGenerator.js +129 -0
  22. package/src/webgpu/EquirectBackgroundInfo.js +97 -0
  23. package/src/webgpu/EquirectHdrInfoNode.js +151 -0
  24. package/src/webgpu/LightsInfoNode.js +224 -0
  25. package/src/webgpu/MegaKernelPathTracer.js +277 -0
  26. package/src/webgpu/PathTracerBackend.js +216 -0
  27. package/src/webgpu/TurquinTexture.js +169 -0
  28. package/src/webgpu/WaveFrontPathTracer.js +586 -0
  29. package/src/webgpu/WebGPUPathTracer.js +1302 -0
  30. package/src/webgpu/compute/ComputeKernel.js +92 -0
  31. package/src/webgpu/compute/CopyBufferKernel.js +40 -0
  32. package/src/webgpu/compute/PathTracerMegaKernel.js +470 -0
  33. package/src/webgpu/compute/SampleDebugKernel.js +51 -0
  34. package/src/webgpu/compute/TallySampleCountsKernel.js +111 -0
  35. package/src/webgpu/compute/ZeroOutBufferKernel.js +35 -0
  36. package/src/webgpu/compute/ZeroOutKernel.js +31 -0
  37. package/src/webgpu/compute/wavefront/LogicKernel.js +349 -0
  38. package/src/webgpu/compute/wavefront/MaterialKernel.js +371 -0
  39. package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +72 -0
  40. package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +38 -0
  41. package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +66 -0
  42. package/src/webgpu/compute/wavefront/TraceRayKernel.js +71 -0
  43. package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +67 -0
  44. package/src/webgpu/compute/wavefront/structs.js +155 -0
  45. package/src/webgpu/constants.js +62 -0
  46. package/src/webgpu/denoise/OIDNDenoiser.js +431 -0
  47. package/src/webgpu/index.d.ts +176 -0
  48. package/src/webgpu/index.js +11 -0
  49. package/src/webgpu/materials/GltfCompliantMaterial.js +608 -0
  50. package/src/webgpu/materials/GraphMaterial.js +331 -0
  51. package/src/webgpu/materials/PathtracingMaterial.js +89 -0
  52. package/src/webgpu/materials/RenderToScreenMaterial.js +150 -0
  53. package/src/webgpu/materials/debug/AtlasDebugMaterial.js +41 -0
  54. package/src/webgpu/materials/debug/SampleDensityMaterial.js +61 -0
  55. package/src/webgpu/nodes/PathtracerBVHComputeData.js +1037 -0
  56. package/src/webgpu/nodes/debugBounds.wgsl.js +228 -0
  57. package/src/webgpu/nodes/eon.wgsl.js +262 -0
  58. package/src/webgpu/nodes/ggx.wgsl.js +190 -0
  59. package/src/webgpu/nodes/lights.wgsl.js +203 -0
  60. package/src/webgpu/nodes/material.wgsl.js +990 -0
  61. package/src/webgpu/nodes/rand/bluedither.wgsl.js +92 -0
  62. package/src/webgpu/nodes/rand/pcg.wgsl.js +81 -0
  63. package/src/webgpu/nodes/rand/sobol.wgsl.js +287 -0
  64. package/src/webgpu/nodes/random.wgsl.js +20 -0
  65. package/src/webgpu/nodes/reset.wgsl.js +21 -0
  66. package/src/webgpu/nodes/sampling.wgsl.js +193 -0
  67. package/src/webgpu/nodes/sheen.wgsl.js +148 -0
  68. package/src/webgpu/nodes/structs.wgsl.js +272 -0
  69. package/src/webgpu/nodes/utils.wgsl.js +366 -0
  70. package/src/webgpu/shims/ArrayCameraShim.js +74 -0
  71. package/src/webgpu/shims/EquirectCameraShim.js +37 -0
  72. package/src/webgpu/shims/PhysicalCameraShim.js +151 -0
  73. package/src/webgpu/upscale/FSRUpscaler.js +108 -0
@@ -0,0 +1,151 @@
1
+ import { Matrix3 } from 'three/webgpu';
2
+ import { texture, sampler, uniform, wgslFn } from 'three/tsl';
3
+ import { EquirectHdrInfoUniform } from '../uniforms/EquirectHdrInfoUniform.js';
4
+ import { wgslTagFn } from 'three-mesh-bvh/webgpu';
5
+ import { constants, environmentSampleStruct } from './nodes/structs.wgsl.js';
6
+ import { equirectDirectionToUvFn, equirectUvToDirectionFn, luminanceFn } from './nodes/sampling.wgsl.js';
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 ] );
18
+
19
+ export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
20
+
21
+ constructor() {
22
+
23
+ super();
24
+
25
+ // environment map + importance-sampling CDF textures, each with a sampler
26
+ this.mapNode = texture( this.map );
27
+ this.mapSampler = sampler( this.mapNode );
28
+ this.marginalNode = texture( this.marginalWeights );
29
+ this.marginalSampler = sampler( this.marginalNode );
30
+ this.conditionalNode = texture( this.conditionalWeights );
31
+ this.conditionalSampler = sampler( this.conditionalNode );
32
+
33
+ // scalar parameters that assemble into the EnvironmentInfo struct in the shader
34
+ this.rotationNode = uniform( new Matrix3() );
35
+ this.intensityNode = uniform( 1 );
36
+ this.totalSumNode = uniform( this.totalSum );
37
+
38
+ this._initFns();
39
+
40
+ }
41
+
42
+ getPixelWeight( r, g, b, row, height ) {
43
+
44
+ // weight the pixel contribution by its spherical solid angle.
45
+ const theta = Math.PI * ( row + 0.5 ) / height;
46
+ return super.getPixelWeight( r, g, b ) * Math.sin( theta );
47
+
48
+ }
49
+
50
+ updateFrom( envMap ) {
51
+
52
+ super.updateFrom( envMap );
53
+
54
+ const {
55
+ mapNode,
56
+ marginalNode,
57
+ conditionalNode,
58
+ totalSumNode,
59
+ } = this;
60
+
61
+ // refresh values in place on the existing nodes so no rebuild is required
62
+ mapNode.value = this.map;
63
+ marginalNode.value = this.marginalWeights;
64
+ conditionalNode.value = this.conditionalWeights;
65
+ totalSumNode.value = this.totalSum;
66
+
67
+ }
68
+
69
+ _initFns() {
70
+
71
+ const {
72
+ mapNode,
73
+ mapSampler,
74
+ marginalNode,
75
+ marginalSampler,
76
+ conditionalNode,
77
+ conditionalSampler,
78
+ totalSumNode,
79
+ rotationNode,
80
+ intensityNode,
81
+ } = this;
82
+
83
+ this.sampleColor = wgslTagFn/* wgsl */`
84
+ fn sampleEnv( direction: vec3f ) -> vec4f {
85
+
86
+ let sampleDir = ${ rotationNode } * direction;
87
+ let mapUv = ${ equirectDirectionToUvFn }( sampleDir );
88
+ let col = textureSampleLevel( ${ mapNode }, ${ mapSampler }, mapUv, 0 );
89
+
90
+ return vec4f( ${ intensityNode } * col.rgb, col.a );
91
+
92
+ }
93
+ `;
94
+
95
+ this.sampleDir = wgslTagFn/* wgsl */`
96
+ fn sampleEnvDir( r: vec2f ) -> ${ environmentSampleStruct } {
97
+
98
+ var result: ${ environmentSampleStruct };
99
+
100
+ // walk the CDF: marginal picks the row (v), conditional picks the column (u)
101
+ let v = textureSampleLevel( ${ marginalNode }, ${ marginalSampler }, vec2f( r.x, 0.0 ), 0 ).x;
102
+ let u = textureSampleLevel( ${ conditionalNode }, ${ conditionalSampler }, vec2f( r.y, v ), 0 ).x;
103
+ let uv = vec2f( u, v );
104
+ let totalSum = ${ totalSumNode };
105
+
106
+ let direction = ${ equirectUvToDirectionFn }( uv );
107
+ let color = textureSampleLevel( ${ mapNode }, ${ mapSampler }, uv, 0 ).rgb;
108
+
109
+ result.direction = transpose( ${ rotationNode } ) * direction;
110
+ result.color = color * ${ intensityNode };
111
+
112
+ if ( totalSum != 0.0 ) {
113
+
114
+ let lum = ${ luminanceFn }( color );
115
+ let resolution = textureDimensions( ${ mapNode } );
116
+ result.pdf = ${ equirectLuminancePdfFn }( lum, totalSum, resolution );
117
+
118
+ } else {
119
+
120
+ result.pdf = 0.0;
121
+
122
+ }
123
+
124
+ return result;
125
+
126
+ }
127
+ `;
128
+
129
+ this.getDirPdf = wgslTagFn/* wgsl */`
130
+ fn getEnvDirPdf( direction: vec3f ) -> f32 {
131
+
132
+ if ( ${ totalSumNode } == 0.0 ) {
133
+
134
+ return 0.0;
135
+
136
+ }
137
+
138
+ let rotatedDir = ${ rotationNode } * direction;
139
+ let mapUv = ${ equirectDirectionToUvFn }( rotatedDir );
140
+ let color = textureSampleLevel( ${ mapNode }, ${ mapSampler }, mapUv, 0 ).rgb;
141
+ let lum = ${ luminanceFn }( color );
142
+ let resolution = textureDimensions( ${ mapNode } );
143
+
144
+ return ${ equirectLuminancePdfFn }( lum, ${ totalSumNode }, resolution );
145
+
146
+ }
147
+ `;
148
+
149
+ }
150
+
151
+ }
@@ -0,0 +1,224 @@
1
+ import { storage, uniform, uniformArray, texture } from 'three/tsl';
2
+ import { StorageBufferAttribute, HalfFloatType } from 'three/webgpu';
3
+ import { wgslTagFn } from 'three-mesh-bvh/webgpu';
4
+ import { AtlasTexture } from './AtlasTexture.js';
5
+ import { LightsInfoUniformStruct } from '../uniforms/LightsInfoUniformStruct.js';
6
+ import { lightStruct, lightRecordStruct } from './nodes/structs.wgsl.js';
7
+ import { sampleTexelFunc } from './nodes/utils.wgsl.js';
8
+ import {
9
+ RECT_AREA_LIGHT_TYPE,
10
+ CIRC_AREA_LIGHT_TYPE,
11
+ SPOT_LIGHT_TYPE,
12
+ DIR_LIGHT_TYPE,
13
+ POINT_LIGHT_TYPE,
14
+ LIGHT_FAR_DISTANCE,
15
+ intersectsRectangleFn,
16
+ intersectsCircleFn,
17
+ randomAreaLightSampleFn,
18
+ randomSpotLightSampleFn,
19
+ getSpotAttenuationFn,
20
+ } from './nodes/lights.wgsl.js';
21
+
22
+ export class LightsInfoNode extends LightsInfoUniformStruct {
23
+
24
+ constructor() {
25
+
26
+ super();
27
+
28
+ // lights packed into a storage buffer of Light structs
29
+ this.countNode = uniform( this.count, 'uint' );
30
+ this.buffer = new StorageBufferAttribute( new Float32Array( 2 * lightStruct.getLength() ), lightStruct.getLength() );
31
+ this.bufferNode = storage( this.buffer, lightStruct ).toReadOnly().setName( 'lights' );
32
+
33
+ // ies profiles packed into an atlas alongside their placement rects
34
+ this.iesAtlas = new AtlasTexture( { type: HalfFloatType } );
35
+ this.iesProfilesNode = texture( this.iesAtlas.texture );
36
+ this.iesInfoNode = uniformArray( this.iesAtlas.textureInfo, 'uvec4' );
37
+
38
+ this._initFns();
39
+
40
+ }
41
+
42
+ updateFrom( renderer, lights ) {
43
+
44
+ // the unique set of ies textures referenced by the lights' "iesMap" fields
45
+ const iesTextures = Array.from( new Set( lights.map( l => l.iesMap ).filter( t => t ) ) );
46
+ const changed = super.updateFrom( lights, iesTextures );
47
+
48
+ this.iesAtlas.setTextures( renderer, iesTextures );
49
+ this.iesProfilesNode.value = this.iesAtlas.texture;
50
+
51
+ const stride = lightStruct.getLength();
52
+ const count = this.count;
53
+ const capacity = Math.max( count, 2 );
54
+
55
+ // resize the buffer to the exact light count, keeping the same binding node
56
+ if ( this.buffer.array.length !== capacity * stride ) {
57
+
58
+ this.buffer.dispose();
59
+ this.buffer = new StorageBufferAttribute( new Float32Array( capacity * stride ), stride );
60
+ this.bufferNode.value = this.buffer;
61
+
62
+ }
63
+
64
+ // the texture's packed float layout already matches lightStruct's std layout, so copy it in
65
+ const src = this.tex.image.data;
66
+ this.buffer.array.set( src.subarray( 0, count * stride ) );
67
+
68
+ // rewrite the int fields ( lightType, iesProfile ) as i32 bits
69
+ const intView = new Int32Array( this.buffer.array.buffer );
70
+ for ( let i = 0; i < count; i ++ ) {
71
+
72
+ const base = i * stride;
73
+ intView[ base + 3 ] = Math.round( src[ base + 3 ] );
74
+ intView[ base + 21 ] = Math.round( src[ base + 21 ] );
75
+
76
+ }
77
+
78
+ this.buffer.needsUpdate = true;
79
+ this.countNode.value = this.count;
80
+
81
+ return changed;
82
+
83
+ }
84
+
85
+ _initFns() {
86
+
87
+ const { bufferNode, iesProfilesNode, iesInfoNode } = this;
88
+
89
+ // profiles are sampled out of an atlas so filtering must resolve tile-relative wrapping
90
+ const sampleIesTexelFn = sampleTexelFunc( iesInfoNode, iesProfilesNode, 'sampleIesTexel' );
91
+
92
+ // uniformly pick a light and sample it
93
+ this.randomLightSample = wgslTagFn/* wgsl */`
94
+ fn randomLightSample( lightIndex: u32, rayOrigin: vec3f, ruv: vec2f ) -> ${ lightRecordStruct } {
95
+
96
+ let light = ${ bufferNode }[ lightIndex ];
97
+
98
+ var result: ${ lightRecordStruct };
99
+ if ( light.lightType == ${ SPOT_LIGHT_TYPE } ) {
100
+
101
+ result = ${ randomSpotLightSampleFn }( light, rayOrigin, ruv );
102
+
103
+ let spotNormal = normalize( cross( light.u, light.v ) );
104
+ let cosTheta = dot( result.direction, spotNormal );
105
+ var spotAttenuation: f32;
106
+ if ( light.iesProfile >= 0 ) {
107
+
108
+ // half a texel of the profile's tile in the atlas
109
+ let profileInfo = ${ iesInfoNode }[ u32( light.iesProfile ) ];
110
+ let texelInset = 0.5 / vec2f( vec2u( profileInfo.y & 0xFFFFu, profileInfo.y >> 16u ) );
111
+
112
+ // tilt spans [ 0, 180 ]deg inset to the texel centers, twist covers [ 0, 359 ]deg and wraps
113
+ let tiltAngle = mix( texelInset.x, 1.0 - texelInset.x, acos( cosTheta ) / PI );
114
+ let twistAngle = ( atan2( dot( result.direction, light.v ), dot( result.direction, light.u ) ) + PI ) / ( 2.0 * PI ) + texelInset.y;
115
+ let packedProfile = ( 1 << 26 ) | light.iesProfile;
116
+ spotAttenuation = ${ sampleIesTexelFn }( vec2f( tiltAngle, twistAngle ), packedProfile, 0.0 ).r;
117
+
118
+ } else {
119
+
120
+ spotAttenuation = ${ getSpotAttenuationFn }( light.coneCos, light.penumbraCos, cosTheta );
121
+
122
+ }
123
+
124
+ result.emission *= spotAttenuation;
125
+
126
+ } else if ( light.lightType == ${ POINT_LIGHT_TYPE } ) {
127
+
128
+ // the point light's world position is packed into the u slot
129
+ let lightRay = light.u - rayOrigin;
130
+ let lightDist = length( lightRay );
131
+ let cutoffDistance = light.distance;
132
+ var distanceFalloff = 1.0 / max( pow( lightDist, light.decay ), 0.01 );
133
+ if ( cutoffDistance > 0.0 ) {
134
+
135
+ let window = clamp( 1.0 - pow( lightDist / cutoffDistance, 4.0 ), 0.0, 1.0 );
136
+ distanceFalloff *= window * window;
137
+
138
+ }
139
+
140
+ result.direction = normalize( lightRay );
141
+ result.dist = lightDist;
142
+ result.pdf = 1.0;
143
+ result.emission = light.color * light.intensity * distanceFalloff;
144
+ result.lightType = light.lightType;
145
+
146
+ } else if ( light.lightType == ${ DIR_LIGHT_TYPE } ) {
147
+
148
+ // the directional light's direction is packed into the u slot
149
+ result.dist = ${ LIGHT_FAR_DISTANCE };
150
+ result.direction = light.u;
151
+ result.pdf = 1.0;
152
+ result.emission = light.color * light.intensity;
153
+ result.lightType = light.lightType;
154
+
155
+ } else {
156
+
157
+ result = ${ randomAreaLightSampleFn }( light, rayOrigin, ruv );
158
+
159
+ }
160
+
161
+ return result;
162
+
163
+ }
164
+ `;
165
+
166
+ // forward intersection of a ray with a single area light ( rect / circ only ), used for MIS
167
+ // TODO: support hitting the spot light disk here and move spot lights into the
168
+ // MIS-weighted set so they appear in sharp reflections
169
+ this.intersectLightAtIndex = wgslTagFn/* wgsl */`
170
+ fn intersectLightAtIndex( rayOrigin: vec3f, rayDirection: vec3f, index: u32, lightRec: ptr<function, ${ lightRecordStruct }> ) -> bool {
171
+
172
+ let light = ${ bufferNode }[ index ];
173
+
174
+ var u = light.u;
175
+ var v = light.v;
176
+ let normal = normalize( cross( u, v ) );
177
+
178
+ // only front-facing area lights can be hit
179
+ if ( dot( normal, rayDirection ) > 0.0 ) {
180
+
181
+ u *= 1.0 / dot( u, u );
182
+ v *= 1.0 / dot( v, v );
183
+
184
+ var dist = - 1.0;
185
+ if ( light.lightType == ${ RECT_AREA_LIGHT_TYPE } ) {
186
+
187
+ dist = ${ intersectsRectangleFn }( light.position, normal, u, v, rayOrigin, rayDirection );
188
+
189
+ } else if ( light.lightType == ${ CIRC_AREA_LIGHT_TYPE } ) {
190
+
191
+ dist = ${ intersectsCircleFn }( light.position, normal, u, v, rayOrigin, rayDirection );
192
+
193
+ }
194
+
195
+ if ( dist > 0.0 ) {
196
+
197
+ let cosTheta = dot( rayDirection, normal );
198
+ lightRec.dist = dist;
199
+ lightRec.pdf = ( dist * dist ) / ( light.area * cosTheta );
200
+ lightRec.emission = light.color * light.intensity;
201
+ lightRec.direction = rayDirection;
202
+ lightRec.lightType = light.lightType;
203
+ return true;
204
+
205
+ }
206
+
207
+ }
208
+
209
+ return false;
210
+
211
+ }
212
+ `;
213
+
214
+ }
215
+
216
+ dispose() {
217
+
218
+ this.tex.dispose();
219
+ this.buffer.dispose();
220
+ this.iesAtlas.dispose();
221
+
222
+ }
223
+
224
+ }
@@ -0,0 +1,277 @@
1
+ import { Matrix4, Vector2 } from 'three/webgpu';
2
+ import { PathTracerMegaKernel } from './compute/PathTracerMegaKernel.js';
3
+ import { EquirectHdrInfoNode } from './EquirectHdrInfoNode.js';
4
+ import { EquirectBackgroundInfo } from './EquirectBackgroundInfo.js';
5
+ import { LightsInfoNode } from './LightsInfoNode.js';
6
+ import { FILTER_GLOSSY_DISABLED } from './nodes/material.wgsl.js';
7
+ import { PathTracerBackend } from './PathTracerBackend.js';
8
+
9
+ const _size = new Vector2();
10
+
11
+ export class MegaKernelPathTracer extends PathTracerBackend {
12
+
13
+ constructor( renderer ) {
14
+
15
+ super( renderer );
16
+
17
+ // options
18
+ this.envInfo = new EquirectHdrInfoNode();
19
+
20
+ // every pixel in a tile finishes its sample in one dispatch, so the counts follow from how
21
+ // far through the tile cycle the render is and need no GPU work to measure
22
+ this._completedSamples = 0;
23
+ this._tileProgress = 0;
24
+
25
+ // kernels
26
+ this.kernel = new PathTracerMegaKernel( ).setWorkgroupSize( 8, 8, 1 );
27
+ this.kernel.envInfo = this.envInfo;
28
+
29
+ this.backgroundInfo = new EquirectBackgroundInfo();
30
+ this.kernel.backgroundInfo = this.backgroundInfo;
31
+
32
+ this.lightsInfo = new LightsInfoNode();
33
+ this.kernel.lightsInfo = this.lightsInfo;
34
+
35
+ }
36
+
37
+ resetSeed() {
38
+
39
+ this.kernel.seed = 0;
40
+
41
+ }
42
+
43
+ reset() {
44
+
45
+ super.reset();
46
+ this._completedSamples = 0;
47
+ this._tileProgress = 0;
48
+
49
+ }
50
+
51
+ async getSampleCountsAsync() {
52
+
53
+ if ( this.lowResMode ) {
54
+
55
+ return { min: 0, max: 0, avg: 0 };
56
+
57
+ }
58
+
59
+ const completed = this._completedSamples;
60
+ const progress = this._tileProgress;
61
+
62
+ return {
63
+ min: progress === 1 ? completed + 1 : completed,
64
+ max: completed + 1,
65
+ avg: completed + progress,
66
+ };
67
+
68
+ }
69
+
70
+ setBVHData( bvhData ) {
71
+
72
+ this.kernel.bvhData = bvhData;
73
+ this.rebuild();
74
+
75
+ }
76
+
77
+ rebuild() {
78
+
79
+ super.rebuild();
80
+ this.kernel.needsUpdate = true;
81
+ this.reset();
82
+
83
+ }
84
+
85
+ setRandom( random ) {
86
+
87
+ this.kernel.context.random = random;
88
+ this.kernel.needsUpdate = true;
89
+ this.reset();
90
+
91
+ }
92
+
93
+ setMaterial( material ) {
94
+
95
+ this.kernel.material = material.getData();
96
+ this.kernel.needsUpdate = true;
97
+ this.reset();
98
+
99
+ }
100
+
101
+ setFilterGlossy( value ) {
102
+
103
+ // the kernel takes the inverted threshold, mirroring Cycles
104
+ this.kernel.filterGlossy = value === 0 ? FILTER_GLOSSY_DISABLED : 1 / value;
105
+ this.reset();
106
+
107
+ }
108
+
109
+ setClamping( direct, indirect ) {
110
+
111
+ this.kernel.clampDirect = direct;
112
+ this.kernel.clampIndirect = indirect;
113
+ this.reset();
114
+
115
+ }
116
+
117
+ setEnvironment( envMap ) {
118
+
119
+ this.envInfo.updateFrom( envMap );
120
+
121
+ }
122
+
123
+ setLights( lights ) {
124
+
125
+ this.lightsInfo.updateFrom( this.renderer, lights );
126
+ this.reset();
127
+
128
+ }
129
+
130
+ setMultipleImportanceSampling( enabled ) {
131
+
132
+ this.kernel.misEnabled = enabled ? 1 : 0;
133
+ this.reset();
134
+
135
+ }
136
+
137
+ setEnvironmentParams( envMapIntensity, envMapRotation ) {
138
+
139
+ const { envInfo } = this;
140
+ const rotationMatrix = new Matrix4().makeRotationFromEuler( envMapRotation ).invert();
141
+ envInfo.rotationNode.value.setFromMatrix4( rotationMatrix );
142
+ envInfo.intensityNode.value = envMapIntensity;
143
+
144
+ }
145
+
146
+ setBackground( background ) {
147
+
148
+ const { backgroundInfo } = this;
149
+ backgroundInfo.dispose();
150
+ backgroundInfo.map = background;
151
+
152
+ }
153
+
154
+ setBackgroundParams(
155
+ backgroundIntensity,
156
+ backgroundRotation,
157
+ backgroundBlurriness,
158
+ ) {
159
+
160
+ const { backgroundInfo } = this;
161
+ const rotationMatrix = new Matrix4().makeRotationFromEuler( backgroundRotation ).invert();
162
+ backgroundInfo.rotationNode.value.copy( rotationMatrix );
163
+ backgroundInfo.intensity = backgroundIntensity;
164
+ backgroundInfo.blur = backgroundBlurriness;
165
+
166
+ }
167
+
168
+ setTransmissiveBackground( value ) {
169
+
170
+ this.kernel.transmissiveBackground = value;
171
+ this.reset();
172
+
173
+ }
174
+
175
+ *createRenderTask() {
176
+
177
+ const {
178
+ renderer,
179
+ kernel,
180
+ maxBounces,
181
+ maxTransparentBounces,
182
+
183
+ outputTarget,
184
+ sampleCountTarget,
185
+ lowResMode,
186
+ } = this;
187
+
188
+ const tiles = new Vector2();
189
+
190
+ // init parameters
191
+ kernel.outputTarget = outputTarget;
192
+ kernel.sampleCountTarget = sampleCountTarget;
193
+
194
+ kernel.maxBounces = maxBounces;
195
+ kernel.maxTransparentBounces = maxTransparentBounces;
196
+
197
+ // number of tile cycles that have finished, ie. the sample count every pixel has reached
198
+ let completedSamples = 0;
199
+
200
+ while ( true ) {
201
+
202
+ // skip the tile loop entirely once every pixel is finished
203
+ kernel.maxSamples = this.maxSamples;
204
+ if ( this.maxSamples !== 0 && completedSamples >= this.maxSamples ) {
205
+
206
+ yield;
207
+ continue;
208
+
209
+ }
210
+
211
+ // split the frame into a near square grid of tiles no larger than the budget
212
+ this.getSize( _size );
213
+ const budgetTiles = Math.max( 1, Math.ceil( _size.x * _size.y / this.frameBudget ) );
214
+ tiles.x = Math.ceil( Math.sqrt( budgetTiles ) );
215
+ tiles.y = Math.ceil( budgetTiles / tiles.x );
216
+
217
+ // every pixel in a tile finishes its sample in a single dispatch, so the sample counts
218
+ // can be derived from how far through the tile cycle we are without reading anything back
219
+ const tileCount = this.lowResMode ? 1 : tiles.x * tiles.y;
220
+ let completedTiles = 0;
221
+
222
+ const tileSize = kernel.tileSize;
223
+ if ( lowResMode ) {
224
+
225
+ this.getSize( tileSize );
226
+
227
+ } else {
228
+
229
+ this.getSize( tileSize ).divide( tiles ).ceil();
230
+
231
+ }
232
+
233
+ const dispatchSize = kernel.getDispatchSize( tileSize.x, tileSize.y );
234
+ kernel.seed += 1;
235
+
236
+ // Swap targets to support devices without <rgba32float, read_write> textures
237
+ // Copy latest data to a new outputTarget to keep the appearance
238
+ renderer.copyTextureToTexture( this.outputTarget, this.prevOutputTarget );
239
+ [ this.outputTarget, this.prevOutputTarget ] = [ this.prevOutputTarget, this.outputTarget ];
240
+ kernel.prevOutputTarget = this.prevOutputTarget;
241
+ kernel.outputTarget = this.outputTarget;
242
+
243
+ for ( let x = 0; x < tiles.x; x ++ ) {
244
+
245
+ for ( let y = 0; y < tiles.y; y ++ ) {
246
+
247
+ kernel.offset.set( x, y ).multiply( tileSize );
248
+ renderer.compute( kernel.kernel, dispatchSize );
249
+
250
+ // in low res mode the tiles past the first fall outside the target and do no work
251
+ completedTiles = Math.min( completedTiles + 1, tileCount );
252
+ this._completedSamples = completedSamples;
253
+ this._tileProgress = completedTiles / tileCount;
254
+
255
+ yield;
256
+
257
+ }
258
+
259
+ }
260
+
261
+ completedSamples ++;
262
+
263
+ }
264
+
265
+ }
266
+
267
+ dispose() {
268
+
269
+ super.dispose();
270
+
271
+ // TODO: dispose of all buffers
272
+ this.envInfo.dispose();
273
+ this.lightsInfo.dispose();
274
+
275
+ }
276
+
277
+ }