three-gpu-pathtracer 0.0.23 → 0.0.25

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 (83) hide show
  1. package/README.md +53 -528
  2. package/build/index.module.js +319 -66
  3. package/build/index.module.js.map +1 -1
  4. package/build/index.umd.cjs +318 -64
  5. package/build/index.umd.cjs.map +1 -1
  6. package/package.json +35 -18
  7. package/src/core/PathTracingSceneGenerator.js +21 -2
  8. package/src/core/WebGLPathTracer.js +16 -7
  9. package/src/core/utils/mergeGeometries.js +14 -1
  10. package/src/core/utils/sceneUpdateUtils.js +8 -12
  11. package/src/detectors/PrecisionMaterial.js +9 -9
  12. package/src/index.d.ts +2 -1
  13. package/src/index.js +1 -0
  14. package/src/materials/pathtracing/PhysicalPathTracingMaterial.js +2 -1
  15. package/src/materials/pathtracing/glsl/attenuate_hit_function.glsl.js +2 -1
  16. package/src/materials/pathtracing/glsl/camera_util_functions.glsl.js +1 -1
  17. package/src/materials/pathtracing/glsl/get_surface_record_function.glsl.js +2 -1
  18. package/src/materials/surface/AmbientOcclusionMaterial.js +2 -0
  19. package/src/objects/PhysicalSpotLight.js +2 -0
  20. package/src/shader/bsdf/bsdf_functions.glsl.js +0 -1
  21. package/src/shader/bvh/inside_fog_volume_function.glsl.js +1 -1
  22. package/src/shader/structs/material_struct.glsl.js +3 -1
  23. package/src/textures/BlueNoiseTexture.js +6 -6
  24. package/src/textures/ProceduralEquirectTexture.js +8 -7
  25. package/src/textures/turquinMetal.png +0 -0
  26. package/src/uniforms/EquirectHdrInfoUniform.js +9 -4
  27. package/src/uniforms/FloatAttributeTextureArray.js +11 -11
  28. package/src/uniforms/MaterialsTexture.js +13 -10
  29. package/src/webgpu/API.md +762 -0
  30. package/src/webgpu/AtlasTexture.js +471 -0
  31. package/src/webgpu/BlurredEnvMapGenerator.js +129 -0
  32. package/src/webgpu/EquirectBackgroundInfo.js +97 -0
  33. package/src/webgpu/EquirectHdrInfoNode.js +151 -0
  34. package/src/webgpu/LightsInfoNode.js +224 -0
  35. package/src/webgpu/MegaKernelPathTracer.js +277 -0
  36. package/src/webgpu/PathTracerBackend.js +216 -0
  37. package/src/webgpu/TurquinTexture.js +169 -0
  38. package/src/webgpu/WaveFrontPathTracer.js +586 -0
  39. package/src/webgpu/WebGPUPathTracer.js +1302 -0
  40. package/src/webgpu/compute/ComputeKernel.js +92 -0
  41. package/src/webgpu/compute/CopyBufferKernel.js +40 -0
  42. package/src/webgpu/compute/PathTracerMegaKernel.js +470 -0
  43. package/src/webgpu/compute/SampleDebugKernel.js +51 -0
  44. package/src/webgpu/compute/TallySampleCountsKernel.js +111 -0
  45. package/src/webgpu/compute/ZeroOutBufferKernel.js +35 -0
  46. package/src/webgpu/compute/ZeroOutKernel.js +31 -0
  47. package/src/webgpu/compute/wavefront/LogicKernel.js +349 -0
  48. package/src/webgpu/compute/wavefront/MaterialKernel.js +371 -0
  49. package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +72 -0
  50. package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +38 -0
  51. package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +66 -0
  52. package/src/webgpu/compute/wavefront/TraceRayKernel.js +71 -0
  53. package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +67 -0
  54. package/src/webgpu/compute/wavefront/structs.js +155 -0
  55. package/src/webgpu/constants.js +62 -0
  56. package/src/webgpu/denoise/OIDNDenoiser.js +431 -0
  57. package/src/webgpu/index.d.ts +176 -0
  58. package/src/webgpu/index.js +11 -0
  59. package/src/webgpu/materials/GltfCompliantMaterial.js +608 -0
  60. package/src/webgpu/materials/GraphMaterial.js +331 -0
  61. package/src/webgpu/materials/PathtracingMaterial.js +89 -0
  62. package/src/webgpu/materials/RenderToScreenMaterial.js +150 -0
  63. package/src/webgpu/materials/debug/AtlasDebugMaterial.js +41 -0
  64. package/src/webgpu/materials/debug/SampleDensityMaterial.js +61 -0
  65. package/src/webgpu/nodes/PathtracerBVHComputeData.js +1037 -0
  66. package/src/webgpu/nodes/debugBounds.wgsl.js +228 -0
  67. package/src/webgpu/nodes/eon.wgsl.js +262 -0
  68. package/src/webgpu/nodes/ggx.wgsl.js +190 -0
  69. package/src/webgpu/nodes/lights.wgsl.js +203 -0
  70. package/src/webgpu/nodes/material.wgsl.js +990 -0
  71. package/src/webgpu/nodes/rand/bluedither.wgsl.js +92 -0
  72. package/src/webgpu/nodes/rand/pcg.wgsl.js +81 -0
  73. package/src/webgpu/nodes/rand/sobol.wgsl.js +287 -0
  74. package/src/webgpu/nodes/random.wgsl.js +20 -0
  75. package/src/webgpu/nodes/reset.wgsl.js +21 -0
  76. package/src/webgpu/nodes/sampling.wgsl.js +193 -0
  77. package/src/webgpu/nodes/sheen.wgsl.js +148 -0
  78. package/src/webgpu/nodes/structs.wgsl.js +272 -0
  79. package/src/webgpu/nodes/utils.wgsl.js +366 -0
  80. package/src/webgpu/shims/ArrayCameraShim.js +74 -0
  81. package/src/webgpu/shims/EquirectCameraShim.js +37 -0
  82. package/src/webgpu/shims/PhysicalCameraShim.js +151 -0
  83. package/src/webgpu/upscale/FSRUpscaler.js +108 -0
@@ -0,0 +1,216 @@
1
+ import { ColorManagement, FloatType, RGBAFormat } from 'three';
2
+ import { RedIntegerFormat, StorageTexture, UnsignedIntType } from 'three/webgpu';
3
+ import { ZeroOutKernel } from './compute/ZeroOutKernel.js';
4
+
5
+ export class PathTracerBackend {
6
+
7
+ constructor( renderer ) {
8
+
9
+ this.renderer = renderer;
10
+ this.maxBounces = 15;
11
+
12
+ // how much work a single update dispatches, in path slots. Lower values keep frames
13
+ // responsive on slower hardware; the unit is resolution independent.
14
+ this.frameBudget = 250000;
15
+ this.maxTransparentBounces = 15;
16
+ this.lowResMode = false;
17
+
18
+ // stop taking samples once a pixel reaches this count. zero means no limit.
19
+ this.maxSamples = 0;
20
+
21
+ this._renderTask = null;
22
+
23
+ this.outputTarget = new StorageTexture( 1, 1, );
24
+ this.outputTarget.format = RGBAFormat;
25
+ this.outputTarget.type = FloatType;
26
+ this.outputTarget.colorSpace = ColorManagement.workingColorSpace;
27
+ this.outputTarget.name = 'Output #0';
28
+ this.outputTarget.generateMipmaps = false;
29
+
30
+ this.prevOutputTarget = new StorageTexture( 1, 1, );
31
+ this.prevOutputTarget.format = RGBAFormat;
32
+ this.prevOutputTarget.type = FloatType;
33
+ this.prevOutputTarget.colorSpace = ColorManagement.workingColorSpace;
34
+ this.prevOutputTarget.name = 'Output #1';
35
+ this.prevOutputTarget.generateMipmaps = false;
36
+
37
+ this.sampleCountTarget = new StorageTexture( 1, 1, );
38
+ this.sampleCountTarget.format = RedIntegerFormat;
39
+ this.sampleCountTarget.type = UnsignedIntType;
40
+ this.sampleCountTarget.name = 'Sample Count';
41
+ this.sampleCountTarget.generateMipmaps = false;
42
+
43
+ this.sampleCountClearKernel = new ZeroOutKernel().setWorkgroupSize( 8, 8, 1 );
44
+ this.outputTargetClearKernel = new ZeroOutKernel().setWorkgroupSize( 8, 8, 1 );
45
+
46
+ }
47
+
48
+ setRandom( random ) {
49
+
50
+ }
51
+
52
+ setBVHData( data ) {
53
+
54
+ }
55
+
56
+ setTextures( textures ) {
57
+
58
+ }
59
+
60
+ setMaterial( material ) {
61
+
62
+ }
63
+
64
+ setFilterGlossy( value ) {
65
+
66
+ }
67
+
68
+ setClamping( _direct, _indirect ) {
69
+
70
+ }
71
+
72
+ setEnvironment(
73
+ envMap,
74
+ envMapIntensity,
75
+ envMapRotation,
76
+ ) {
77
+
78
+ }
79
+
80
+ setLights( lights ) {
81
+
82
+ }
83
+
84
+ setBackground(
85
+ background,
86
+ backgroundIntensity,
87
+ backgroundRotation,
88
+ backgroundBlurriness,
89
+ ) {
90
+
91
+ }
92
+
93
+ // Rebuild the render task after a shader-generating function changes.
94
+ rebuild() {
95
+
96
+ this._renderTask = null;
97
+
98
+ }
99
+
100
+ setSize( w, h ) {
101
+
102
+ w = Math.ceil( w );
103
+ h = Math.ceil( h );
104
+
105
+ const { width, height } = this.outputTarget;
106
+ if ( width === w && height === h ) {
107
+
108
+ return false;
109
+
110
+ }
111
+
112
+ this.outputTarget.dispose();
113
+ this.prevOutputTarget.dispose();
114
+ this.sampleCountTarget.dispose();
115
+
116
+ this.outputTarget = this.outputTarget.clone();
117
+ this.prevOutputTarget = this.outputTarget.clone();
118
+ this.sampleCountTarget = this.sampleCountTarget.clone();
119
+
120
+ this.outputTarget.setSize( w, h );
121
+ this.prevOutputTarget.setSize( w, h );
122
+ this.sampleCountTarget.setSize( w, h );
123
+
124
+ this.reset();
125
+ return true;
126
+
127
+ }
128
+
129
+ getSize( target ) {
130
+
131
+ const { width, height } = this.outputTarget;
132
+ target.x = width;
133
+ target.y = height;
134
+
135
+ return target;
136
+
137
+ }
138
+
139
+ update() {
140
+
141
+ const { renderer } = this;
142
+ if ( ! renderer.initialized ) {
143
+
144
+ return;
145
+
146
+ }
147
+
148
+ if ( ! this._renderTask ) {
149
+
150
+ this._renderTask = this.createRenderTask();
151
+
152
+ }
153
+
154
+ this._renderTask.next();
155
+
156
+ }
157
+
158
+ *createRenderTask() {
159
+
160
+ }
161
+
162
+ // Measures the current per pixel sample counts. Backends that need GPU work to answer this do
163
+ // it here, so it only happens when asked for rather than every round.
164
+ async getSampleCountsAsync() {
165
+
166
+ return { min: 0, max: 0, avg: 0 };
167
+
168
+ }
169
+
170
+ resetSeed() {}
171
+
172
+ reset() {
173
+
174
+ const {
175
+ renderer,
176
+ outputTargetClearKernel,
177
+ sampleCountClearKernel,
178
+
179
+ outputTarget,
180
+ prevOutputTarget,
181
+ sampleCountTarget,
182
+ } = this;
183
+
184
+ if ( ! renderer.initialized ) {
185
+
186
+ return;
187
+
188
+ }
189
+
190
+ const { width, height } = outputTarget;
191
+ const dispatchSize = outputTargetClearKernel.getDispatchSize( width, height );
192
+
193
+ outputTargetClearKernel.target = outputTarget;
194
+ renderer.compute( outputTargetClearKernel.kernel, dispatchSize );
195
+
196
+ outputTargetClearKernel.target = prevOutputTarget;
197
+ renderer.compute( outputTargetClearKernel.kernel, dispatchSize );
198
+
199
+ sampleCountClearKernel.target = sampleCountTarget;
200
+ renderer.compute( sampleCountClearKernel.kernel, dispatchSize );
201
+
202
+ this._renderTask = null;
203
+
204
+ }
205
+
206
+ dispose() {
207
+
208
+ this.outputTarget.dispose();
209
+ this.prevOutputTarget.dispose();
210
+ this.sampleCountTarget.dispose();
211
+
212
+ this._renderTask = null;
213
+
214
+ }
215
+
216
+ }
@@ -0,0 +1,169 @@
1
+ import { Storage3DTexture, RGBAFormat, LinearFilter, HalfFloatType, MathUtils } from 'three/webgpu';
2
+ import { storageTexture3D, globalId, uniform, texture3D, sampler } from 'three/tsl';
3
+ import { turquinIntegralFn } from './nodes/material.wgsl.js';
4
+ import { ComputeKernel } from './compute/ComputeKernel.js';
5
+ import { wgslTagFn, proxyFn } from 'three-mesh-bvh/webgpu';
6
+
7
+ const RESOLUTION = 32;
8
+ const WORKGROUP_SIZE = 16;
9
+
10
+ // A 3D texture of the GGX single-scatter directional albedo E, used for Turquin multiscatter
11
+ // energy compensation (https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf).
12
+ export class TurquinTexture extends Storage3DTexture {
13
+
14
+ constructor() {
15
+
16
+ // layer 0: conductor
17
+ // layer 1 - 10: dielectric reflection over the ior range
18
+ // layer 11 - 20: transmissive total energy entering
19
+ // layer 21 - 30: transmissive total energy exiting
20
+ super( RESOLUTION, RESOLUTION, 31 );
21
+
22
+ // rgba16float is the one float format that is both storable and filterable everywhere
23
+ this.type = HalfFloatType;
24
+ this.format = RGBAFormat;
25
+ this.minFilter = LinearFilter;
26
+ this.magFilter = LinearFilter;
27
+
28
+ // TODO: turquin paper suggests 32 layers for dielectric
29
+ this.dielectricLayerCount = 10;
30
+ this.transmissiveLayerCount = 10;
31
+ this.minIoR = 1;
32
+ this.maxIoR = 2.5;
33
+
34
+ // construct the fns
35
+ this.sampleConductorFn = proxyFn( '_sampleConductorFn', this );
36
+ this.sampleDielectricFn = proxyFn( '_sampleDielectricFn', this );
37
+ this.sampleTransmissiveFn = proxyFn( '_sampleTransmissiveFn', this );
38
+
39
+ }
40
+
41
+ // bakes the albedo table into the texture. Must be called once the renderer is initialized.
42
+ generate( renderer ) {
43
+
44
+ const {
45
+ dielectricLayerCount,
46
+ transmissiveLayerCount,
47
+ minIoR,
48
+ maxIoR,
49
+ } = this;
50
+
51
+ const params = {
52
+ outputTarget: storageTexture3D( this ).toWriteOnly(),
53
+ globalId,
54
+ eta: uniform( 1 ),
55
+ layer: uniform( 0, 'uint' ),
56
+ includeFresnel: uniform( 0 ),
57
+ includeRefraction: uniform( 0 ),
58
+ };
59
+
60
+ const dispatch = [ RESOLUTION / WORKGROUP_SIZE, RESOLUTION / WORKGROUP_SIZE, 1 ];
61
+ const kernel = new ComputeKernel( turquinIntegralFn( params.outputTarget )( params ), { workgroupSize: [ WORKGROUP_SIZE, WORKGROUP_SIZE, 1 ] } );
62
+
63
+ // metallic
64
+ params.layer.value = 0;
65
+ params.eta.value = 1;
66
+ params.includeFresnel.value = 0;
67
+ params.includeRefraction.value = 0;
68
+ renderer.compute( kernel.kernel, dispatch );
69
+
70
+ // dielectric
71
+ for ( let i = 0; i < dielectricLayerCount; i ++ ) {
72
+
73
+ params.layer.value ++;
74
+ params.eta.value = 1 / MathUtils.mapLinear( i, 0, dielectricLayerCount - 1, minIoR, maxIoR );
75
+ params.includeFresnel.value = 1;
76
+ renderer.compute( kernel.kernel, dispatch );
77
+
78
+ }
79
+
80
+ // transmissive total energy - entering and exiting layers over the same ior range
81
+ params.includeFresnel.value = 1;
82
+ params.includeRefraction.value = 1;
83
+ for ( let i = 0; i < transmissiveLayerCount; i ++ ) {
84
+
85
+ const ior = MathUtils.mapLinear( i, 0, transmissiveLayerCount - 1, minIoR, maxIoR );
86
+
87
+ // entering - air incident
88
+ params.layer.value = 1 + dielectricLayerCount + i;
89
+ params.eta.value = 1 / ior;
90
+ renderer.compute( kernel.kernel, dispatch );
91
+
92
+ // exiting - volume incident, TIR side
93
+ params.layer.value = 1 + dielectricLayerCount + transmissiveLayerCount + i;
94
+ params.eta.value = ior;
95
+ renderer.compute( kernel.kernel, dispatch );
96
+
97
+ }
98
+
99
+ // fns
100
+ // function that maps the given value from range [a0, a1] to [b0, b1]
101
+ const mapLinearClampedFn = wgslTagFn/* wgsl */`
102
+ fn mapLinearClamped( v: f32, a0: f32, a1: f32, b0: f32, b1: f32 ) -> f32 {
103
+
104
+ let mapped = saturate( ( v - a0 ) / ( a1 - a0 ) );
105
+ return mix( b0, b1, mapped );
106
+
107
+ }
108
+ `;
109
+
110
+ // texture nodes
111
+ const textureNode = texture3D( this ).setName( 'turquinTexture' );
112
+ const samplerNode = sampler( this ).setName( 'turquinSampler' );
113
+
114
+ // conductor fetch fn
115
+ const layerDepth = 1.0 / ( 1 + dielectricLayerCount + 2 * transmissiveLayerCount );
116
+ this._sampleConductorFn = wgslTagFn/* wgsl */`
117
+ fn sampleConductor( cosTheta0: f32, roughness: f32 ) -> f32 {
118
+
119
+ let layer = 0.5;
120
+ let uvw = vec3f( cosTheta0, roughness, layer * ${ layerDepth } );
121
+ return textureSampleLevel( ${ textureNode }, ${ samplerNode }, uvw, 0 ).r;
122
+
123
+ }
124
+ `;
125
+
126
+ // dielectric fetch fn - ior is clamped at the valid range
127
+ this._sampleDielectricFn = wgslTagFn/* wgsl */`
128
+ fn sampleDielectric( cosTheta0: f32, roughness: f32, ior: f32 ) -> f32 {
129
+
130
+ let blockStart = 1.0;
131
+ let layer = ${ mapLinearClampedFn }(
132
+ ior,
133
+ f32( ${ minIoR } ), f32( ${ maxIoR } ),
134
+ 0.5, f32( ${ dielectricLayerCount } ) - 0.5,
135
+ ) + blockStart;
136
+
137
+ let uvw = vec3f( cosTheta0, roughness, layer * ${ layerDepth } );
138
+ return textureSampleLevel( ${ textureNode }, ${ samplerNode }, uvw, 0 ).r;
139
+
140
+ }
141
+ `;
142
+
143
+ // transmissive fetch fn - entering selects the air-incident block, otherwise the
144
+ // volume-incident (TIR) block. ior is clamped at the valid range
145
+ const enterBlockStart = 1 + dielectricLayerCount;
146
+ const exitBlockStart = 1 + dielectricLayerCount + transmissiveLayerCount;
147
+ this._sampleTransmissiveFn = wgslTagFn/* wgsl */`
148
+ fn sampleTransmissive( cosTheta0: f32, roughness: f32, ior: f32, entering: bool ) -> f32 {
149
+
150
+ let blockStart = select(
151
+ f32( ${ exitBlockStart } ),
152
+ f32( ${ enterBlockStart } ),
153
+ entering
154
+ );
155
+ let layer = ${ mapLinearClampedFn }(
156
+ ior,
157
+ f32( ${ minIoR } ), f32( ${ maxIoR } ),
158
+ 0.5, f32( ${ transmissiveLayerCount } ) - 0.5,
159
+ ) + blockStart;
160
+
161
+ let uvw = vec3f( cosTheta0, roughness, layer * ${ layerDepth } );
162
+ return textureSampleLevel( ${ textureNode }, ${ samplerNode }, uvw, 0 ).r;
163
+
164
+ }
165
+ `;
166
+
167
+ }
168
+
169
+ }