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,331 @@
1
+ import { Color, SRGBColorSpace, Vector2 } from 'three';
2
+ import { MeshBasicNodeMaterial, StorageBufferAttribute } from 'three/webgpu';
3
+ import { globalId, storage, uniform, uv, varying, wgslFn } from 'three/tsl';
4
+ import { wgslTagFn, wgslTagCode } from 'three-mesh-bvh/webgpu';
5
+
6
+ // pastel palette evenly spaced along the color wheel, looped when there are more graphs
7
+ const GRAPH_COLOR_COUNT = 10;
8
+
9
+ function getGraphColor( i ) {
10
+
11
+ const hue = ( i % GRAPH_COLOR_COUNT ) / GRAPH_COLOR_COUNT;
12
+ return new Color().setHSL( hue, 0.65, 0.7, SRGBColorSpace );
13
+
14
+ }
15
+
16
+ // anti-aliased background with x / y axes and 1-unit markers
17
+ const getBackgroundFunc = wgslFn( /* wgsl */`
18
+ fn getBackground( point: vec2f, steepness: f32 ) -> vec3f {
19
+
20
+ let pw = fwidth( point );
21
+ let halfWidth = pw * 0.5;
22
+
23
+ // x, y axes
24
+ var distToZero = smoothstep(
25
+ - halfWidth * 0.5,
26
+ halfWidth * 0.5,
27
+ abs( point ) - pw
28
+ );
29
+
30
+ // 1 unit markers
31
+ let modAxis = fract( abs( point + vec2f( 0.5 ) ) ) - 0.5;
32
+ var distToAxis = smoothstep(
33
+ - halfWidth,
34
+ halfWidth,
35
+ abs( modAxis ) - pw * 0.5
36
+ );
37
+
38
+ // if we're at a chart boundary then remove the artifacts
39
+ if ( abs( pw.y ) > steepness * 0.5 ) {
40
+
41
+ distToZero.y = 1.0;
42
+ distToAxis.y = 1.0;
43
+
44
+ }
45
+
46
+ // mix colors into a background color
47
+ let axisIntensity = 1.0 - min( distToZero.x, distToZero.y );
48
+ let markerIntensity = 1.0 - min( distToAxis.x, distToAxis.y );
49
+
50
+ let markerColor = mix( vec3f( 0.005 ), vec3f( 0.05 ), markerIntensity );
51
+ return mix( markerColor, vec3f( 0.2 ), axisIntensity );
52
+
53
+ }
54
+ ` );
55
+
56
+ // Fullscreen material that plots an arbitrary number of WGSL functions of the form
57
+ // "fn( x: f32 ) -> f32" provided via "setGraphs", either overlaid or in stacked sections.
58
+ export class GraphMaterial extends MeshBasicNodeMaterial {
59
+
60
+ get dim() {
61
+
62
+ return this._dim.value > 0.5;
63
+
64
+ }
65
+
66
+ set dim( v ) {
67
+
68
+ this._dim.value = v ? 1 : 0;
69
+
70
+ }
71
+
72
+ get overlay() {
73
+
74
+ return this._overlay.value > 0.5;
75
+
76
+ }
77
+
78
+ set overlay( v ) {
79
+
80
+ this._overlay.value = v ? 1 : 0;
81
+
82
+ }
83
+
84
+ get thickness() {
85
+
86
+ return this._thickness.value;
87
+
88
+ }
89
+
90
+ set thickness( v ) {
91
+
92
+ this._thickness.value = v;
93
+
94
+ }
95
+
96
+ get xRange() {
97
+
98
+ return this._xRange.value;
99
+
100
+ }
101
+
102
+ get yRange() {
103
+
104
+ return this._yRange.value;
105
+
106
+ }
107
+
108
+ get graphCount() {
109
+
110
+ return this._graphs.length;
111
+
112
+ }
113
+
114
+ // the display names of the plotted graphs, in slot order
115
+ get graphNames() {
116
+
117
+ return this._graphs.map( entry => {
118
+
119
+ return entry.name || entry.code.match( /fn ([^(]+)/ )[ 1 ];
120
+
121
+ } );
122
+
123
+ }
124
+
125
+ // the mouse position in graph space - set to a distant point to hide the markers
126
+ get mousePoint() {
127
+
128
+ return this._mousePoint.value;
129
+
130
+ }
131
+
132
+ constructor( options = {} ) {
133
+
134
+ const { graphs = [], ...params } = options;
135
+
136
+ super();
137
+
138
+ this.depthWrite = false;
139
+ this.depthTest = false;
140
+
141
+ // uniforms - created once and shared across shader rebuilds
142
+ this._xRange = uniform( new Vector2( - 2, 2 ) );
143
+ this._yRange = uniform( new Vector2( - 2, 2 ) );
144
+ this._thickness = uniform( window.devicePixelRatio );
145
+ this._dim = uniform( 1 );
146
+ this._overlay = uniform( 1 );
147
+ this._displayMask = uniform( 0xffffffff, 'uint' );
148
+ this._mousePoint = uniform( new Vector2( 1e10, 1e10 ) );
149
+ this._graphs = [];
150
+
151
+ this.setGraphs( graphs );
152
+ this.setValues( params );
153
+
154
+ }
155
+
156
+ // toggles the visibility of the graph at the given index
157
+ setGraphVisible( index, visible ) {
158
+
159
+ const mask = this._displayMask;
160
+ if ( visible ) {
161
+
162
+ mask.value = ( mask.value | ( 1 << index ) ) >>> 0;
163
+
164
+ } else {
165
+
166
+ mask.value = ( mask.value & ~ ( 1 << index ) ) >>> 0;
167
+
168
+ }
169
+
170
+ }
171
+
172
+ getGraphVisible( index ) {
173
+
174
+ return ( ( this._displayMask.value >> index ) & 1 ) === 1;
175
+
176
+ }
177
+
178
+ getGraphColor( index ) {
179
+
180
+ return getGraphColor( index );
181
+
182
+ }
183
+
184
+ // Replaces the set of plotted functions - each an "fn( x: f32 ) -> f32" wgsl function node -
185
+ // and rebuilds the shader to display them.
186
+ setGraphs( graphs ) {
187
+
188
+ const { _xRange, _yRange, _thickness, _dim, _overlay, _displayMask, _mousePoint } = this;
189
+
190
+ this._graphs = [ ...graphs ];
191
+
192
+ // unroll one evaluation block per graph, accumulated into a single code chunk
193
+ let graphBody = '';
194
+ graphs.forEach( ( graph, i ) => {
195
+
196
+ const color = getGraphColor( i );
197
+ graphBody = wgslTagCode/* wgsl */`
198
+ ${ graphBody }
199
+
200
+ {
201
+ let visible = ( ( displayMask >> ${ i }u ) & 1u ) == 1u;
202
+ if ( visible && ( sectionCount < 1.5 || section == ${ i } ) ) {
203
+
204
+ let delta = ${ graph }( point.x ) - point.y;
205
+ var halfDdf = fwidth( delta ) * 0.5;
206
+ if ( pointYWidth > yWidth * 0.5 ) {
207
+
208
+ halfDdf = 0.0;
209
+
210
+ }
211
+
212
+ var graph = smoothstep( - halfDdf, halfDdf, abs( delta ) - ${ _thickness } * halfDdf );
213
+ if ( dimmed ) {
214
+
215
+ graph = mix( 1.0, graph, 0.1 );
216
+
217
+ }
218
+
219
+ color = mix( vec3f( ${ color.r }, ${ color.g }, ${ color.b } ), color, graph );
220
+
221
+ // circle marker at the mouse line's intersection with the curve
222
+ let intersection = vec2f( mousePoint.x, ${ graph }( mousePoint.x ) );
223
+ let circleDist = length( ( point - intersection ) / pw );
224
+ let circle = smoothstep( 4.0, 5.0, circleDist );
225
+ color = mix( vec3f( ${ color.r }, ${ color.g }, ${ color.b } ), color, circle );
226
+
227
+ }
228
+
229
+ }
230
+ `;
231
+
232
+ } );
233
+
234
+ // the section math assumes at least one graph
235
+ const count = Math.max( graphs.length, 1 );
236
+ const graphShader = wgslTagFn/* wgsl */`
237
+ fn graphShader( vUv: vec2f ) -> vec4f {
238
+
239
+ var sectionCount = ${ count }.0;
240
+ if ( ${ _overlay } > 0.5 ) {
241
+
242
+ sectionCount = 1.0;
243
+
244
+ }
245
+
246
+ let yWidth = abs( ${ _yRange }.y - ${ _yRange }.x );
247
+
248
+ // separate into sections
249
+ let scaledY = sectionCount * vUv.y;
250
+ let sectionY = fract( scaledY );
251
+ let section = clamp( i32( sectionCount - floor( scaledY ) - 1.0 ), 0, ${ count - 1 } );
252
+
253
+ // the graph-space point for this pixel
254
+ let point = vec2f(
255
+ mix( ${ _xRange }.x, ${ _xRange }.y, vUv.x ),
256
+ mix( ${ _yRange }.x, ${ _yRange }.y, sectionY )
257
+ );
258
+
259
+ let pw = fwidth( point );
260
+ let pointYWidth = pw.y;
261
+ let dimmed = ${ _dim } > 0.5 && ( point.x < 0.0 || point.y < 0.0 );
262
+ let displayMask = ${ _displayMask };
263
+ let mousePoint = ${ _mousePoint };
264
+
265
+ var color = ${ getBackgroundFunc }( point, yWidth );
266
+
267
+ // vertical marker line at the mouse position
268
+ let mouseLine = smoothstep( - pw.x * 0.5, pw.x * 0.5, abs( point.x - mousePoint.x ) - pw.x );
269
+ color = mix( vec3f( 0.35 ), color, mouseLine );
270
+
271
+ ${ graphBody }
272
+
273
+ return vec4f( color, 1.0 );
274
+
275
+ }
276
+ `;
277
+
278
+ // NOTE: varyings cannot be referenced directly and must be passed as arguments
279
+ this.colorNode = graphShader( varying( uv() ) );
280
+ this.needsUpdate = true;
281
+
282
+ // compute kernel that evaluates every graph at the mouse x so the values can be read back.
283
+ // The buffer is padded to a minimum length of 2 to avoid TSL treating it as a scalar.
284
+ const valuesAttribute = new StorageBufferAttribute( new Float32Array( Math.max( count, 2 ) ), 1 );
285
+ const valuesStorage = storage( valuesAttribute, 'float' );
286
+
287
+ let valuesBody = '';
288
+ graphs.forEach( ( graph, i ) => {
289
+
290
+ valuesBody = wgslTagCode/* wgsl */`
291
+ ${ valuesBody }
292
+ ${ valuesStorage }[ ${ i } ] = ${ graph }( x );
293
+ `;
294
+
295
+ } );
296
+
297
+ const valuesFn = wgslTagFn/* wgsl */`
298
+ fn computeGraphValues( globalId: vec3u ) -> void {
299
+
300
+ let x = ${ _mousePoint }.x;
301
+ ${ valuesBody }
302
+
303
+ }
304
+ `;
305
+
306
+ this._valuesAttribute?.dispose();
307
+ this._valuesAttribute = valuesAttribute;
308
+ this._valuesKernel = valuesFn( { globalId } ).computeKernel( [ 1, 1, 1 ] );
309
+
310
+ }
311
+
312
+ dispose() {
313
+
314
+ super.dispose();
315
+ this._valuesAttribute.dispose();
316
+
317
+ }
318
+
319
+ // Evaluates every graph function at the current "mousePoint.x" on the gpu and resolves to a
320
+ // Float32Array of the resulting values.
321
+ async readGraphValues( renderer ) {
322
+
323
+ const count = this._graphs.length;
324
+ renderer.compute( this._valuesKernel, [ 1, 1, 1 ] );
325
+
326
+ const buffer = await renderer.getArrayBufferAsync( this._valuesAttribute );
327
+ return new Float32Array( buffer ).slice( 0, count );
328
+
329
+ }
330
+
331
+ }
@@ -0,0 +1,89 @@
1
+ import { wgslTagFn } from 'three-mesh-bvh/webgpu';
2
+ import { rand2, RNG_INDEX_SCATTER_DIRECTION } from '../nodes/random.wgsl.js';
3
+ import { diffuseDirectionFunc } from '../nodes/sampling.wgsl.js';
4
+
5
+ /**
6
+ * Defines a material sampled by the pathtracer
7
+ */
8
+ export class PathtracingMaterial {
9
+
10
+ /**
11
+ *
12
+ * Called once per material
13
+ * Adds ability to initialize state
14
+ *
15
+ */
16
+ init( /* renderer */ ) {
17
+
18
+ }
19
+
20
+ /**
21
+ *
22
+ * Must return a bsdf sampling function node with signature
23
+ * ( worldView: vec3f, surface: Surface ) -> ScatterRecord
24
+ *
25
+ */
26
+ getBsdfNode() {
27
+
28
+ return wgslTagFn/* wgsl */`
29
+
30
+ fn bsdfSample( worldWo: vec3f, surf: SurfaceRecord ) -> ScatterRecord {
31
+
32
+ var record: ScatterRecord;
33
+
34
+ let wo = normalize( surf.normalInvBasis * worldWo );
35
+ let wi = ${ diffuseDirectionFunc }( wo, ${ rand2 }( ${ RNG_INDEX_SCATTER_DIRECTION } ) );
36
+ record.color = surf.color * max( wi.z, 0.0 );
37
+ record.pdf = max( wi.z, 0.0 ) / PI;
38
+ record.direction = normalize( surf.normalBasis * wi );
39
+
40
+ return record;
41
+
42
+ }
43
+
44
+ `;
45
+
46
+ }
47
+
48
+ /**
49
+ *
50
+ * Must return a bsdf evaluation function node with signature
51
+ * ( worldView: vec3f, worldLight: vec3f, surface: Surface ) -> ScatterRecord
52
+ * returning the bsdf value and sampling pdf for a given light direction.
53
+ * Used by next event estimation to weight a chosen light direction.
54
+ *
55
+ */
56
+ getBsdfEvalPdfNode() {
57
+
58
+ return wgslTagFn/* wgsl */`
59
+
60
+ fn bsdfEvalPdf( worldWo: vec3f, worldWi: vec3f, surf: SurfaceRecord ) -> ScatterRecord {
61
+
62
+ var record: ScatterRecord;
63
+ record.direction = worldWi;
64
+
65
+ let wo = normalize( surf.normalInvBasis * worldWo );
66
+ let wi = normalize( surf.normalInvBasis * worldWi );
67
+ record.color = surf.color * max( wi.z, 0.0 );
68
+ record.pdf = max( wi.z, 0.0 ) / PI;
69
+
70
+ return record;
71
+
72
+ }
73
+
74
+ `;
75
+
76
+ }
77
+
78
+ getData() {
79
+
80
+ return {
81
+
82
+ bsdfSample: this.getBsdfNode(),
83
+ bsdfEvalPdf: this.getBsdfEvalPdfNode(),
84
+
85
+ };
86
+
87
+ }
88
+
89
+ }
@@ -0,0 +1,150 @@
1
+ import { MeshBasicNodeMaterial, NoToneMapping, StorageTexture } from 'three/webgpu';
2
+ import { uv, varying, texture, vec4, toneMapping, uniform, wgslFn } from 'three/tsl';
3
+ import { wgslTagFn } from 'three-mesh-bvh/webgpu';
4
+
5
+ // TODO: we could fall back to hardware-based filtering if available but it has to be specifically
6
+ // requested and available on the renderer which we don't have access to immediately. It's possible this
7
+ // can be detected during build time via a custom node to adjust the sample approach used?
8
+ const sampleTexelFn = wgslFn( /* wgsl */`
9
+ fn sampleTexel( tex: texture_2d<f32>, coord: vec2f ) -> vec4f {
10
+
11
+ // Manual bilinear filtering using textureLoad to support filterable float32 textures
12
+ // on all devices
13
+ let size = vec2f( textureDimensions( tex, 0 ) );
14
+
15
+ // the path tracer stores its color rows top down
16
+ let pxCoord = vec2f( coord.x, 1.0 - coord.y ) * size - 0.5;
17
+ let px = vec2i( floor( pxCoord ) );
18
+ let fr = fract( pxCoord );
19
+
20
+ // get the four sibling samples
21
+ let s00 = textureLoad( tex, clamp( px, vec2i( 0 ), vec2i( size ) - 1 ), 0 );
22
+ let s10 = textureLoad( tex, clamp( px + vec2i( 1, 0 ), vec2i( 0 ), vec2i( size ) - 1 ), 0 );
23
+ let s01 = textureLoad( tex, clamp( px + vec2i( 0, 1 ), vec2i( 0 ), vec2i( size ) - 1 ), 0 );
24
+ let s11 = textureLoad( tex, clamp( px + vec2i( 1, 1 ), vec2i( 0 ), vec2i( size ) - 1 ), 0 );
25
+
26
+ // interpolate on the x axis
27
+ let y0 = mix( s00, s10, fr.x );
28
+ let y1 = mix( s01, s11, fr.x );
29
+
30
+ // then y
31
+ return mix( y0, y1, fr.y );
32
+
33
+ }
34
+ ` );
35
+
36
+ // Material to apply tone mapping _before_ applying alpha blending
37
+ export class RenderToScreenNodeMaterial extends MeshBasicNodeMaterial {
38
+
39
+ get texture() {
40
+
41
+ return this._texNode.value;
42
+
43
+ }
44
+
45
+ set texture( v ) {
46
+
47
+ this._texNode.value = v;
48
+
49
+ }
50
+
51
+ get fromTexture() {
52
+
53
+ return this._fromTexNode.value;
54
+
55
+ }
56
+
57
+ set fromTexture( v ) {
58
+
59
+ this._fromTexNode.value = v;
60
+
61
+ }
62
+
63
+ get toneMapping() {
64
+
65
+ return this._toneMapping.toneMapping;
66
+
67
+ }
68
+
69
+ set toneMapping( v ) {
70
+
71
+ this._toneMapping.setToneMapping( v );
72
+
73
+ }
74
+
75
+ get transition() {
76
+
77
+ return this._transitionUniform.value;
78
+
79
+ }
80
+
81
+ set transition( v ) {
82
+
83
+ this._transitionUniform.value = v;
84
+
85
+ }
86
+
87
+ get exposure() {
88
+
89
+ return this._toneMapping.exposureNode.value;
90
+
91
+ }
92
+
93
+ set exposure( v ) {
94
+
95
+ this._toneMapping.exposureNode.value = v;
96
+
97
+ }
98
+
99
+ constructor( params ) {
100
+
101
+ super();
102
+
103
+ const texNode = texture( new StorageTexture() );
104
+ this._texNode = texNode;
105
+
106
+ const fromTexNode = texture( new StorageTexture() );
107
+ this._fromTexNode = fromTexNode;
108
+
109
+ const texUV = varying( uv() );
110
+
111
+ const transitionUniform = uniform( 1.0 );
112
+ this._transitionUniform = transitionUniform;
113
+
114
+ // NOTE: varyings cannot be referenced directly and must be passed as arguments
115
+ const getFadedColorFn = wgslTagFn/* wgsl */`
116
+ fn fade( uv: vec2f ) -> vec4f {
117
+
118
+ if ( ${ transitionUniform } <= 0.0 ) {
119
+
120
+ return ${ sampleTexelFn }( ${ fromTexNode }, uv );
121
+
122
+ } else if ( ${ transitionUniform } >= 1.0 ) {
123
+
124
+ return ${ sampleTexelFn }( ${ texNode }, uv );
125
+
126
+ } else {
127
+
128
+ let col0 = ${ sampleTexelFn }( ${ fromTexNode }, uv );
129
+ let col1 = ${ sampleTexelFn }( ${ texNode }, uv );
130
+ return mix( col0, col1, ${ transitionUniform } );
131
+
132
+ }
133
+
134
+ }
135
+ `;
136
+
137
+ const toneMappingNode = toneMapping( NoToneMapping, 1.0, getFadedColorFn( texUV ) );
138
+ this._toneMapping = toneMappingNode;
139
+
140
+ // apply alpha _after_ applying tone mapping
141
+ // NOTE: alpha is being multiplied twice here to accommodate some odd blending in three.js
142
+ // See mrdoob/three.js#33104. It's possible this should be removed or rethought once fixed.
143
+ this.transparent = true;
144
+ this.colorNode = vec4( toneMappingNode.rgb, toneMappingNode.a );
145
+
146
+ this.setValues( params );
147
+
148
+ }
149
+
150
+ }
@@ -0,0 +1,41 @@
1
+ import { MeshBasicNodeMaterial, DataTexture, NoBlending } from 'three/webgpu';
2
+ import { uv, varying, texture, uniform, wgslFn } from 'three/tsl';
3
+
4
+ export class AtlasDebugMaterial extends MeshBasicNodeMaterial {
5
+
6
+ set texture( v ) {
7
+
8
+ this._texNode.value = v;
9
+
10
+ }
11
+
12
+ set layer( v ) {
13
+
14
+ this._layerUniform.value = v;
15
+
16
+ }
17
+
18
+ constructor() {
19
+
20
+ super();
21
+
22
+ this.blending = NoBlending;
23
+
24
+ this._texNode = texture( new DataTexture() );
25
+ this._layerUniform = uniform( 0 );
26
+
27
+ // Shows one layer of the atlas array texture.
28
+ this.colorNode = wgslFn( /* wgsl */ `
29
+ fn atlasDebug( tex: texture_2d_array<f32>, coord: vec2f, layer: f32 ) -> vec4f {
30
+
31
+ // flip y so texel origin shows at the top-left
32
+ let dims = vec2f( textureDimensions( tex, 0 ).xy );
33
+ let texel = vec2i( vec2f( coord.x, 1.0 - coord.y ) * dims );
34
+ return textureLoad( tex, texel, i32( layer ), 0 );
35
+
36
+ }
37
+ ` )( this._texNode, varying( uv() ), this._layerUniform );
38
+
39
+ }
40
+
41
+ }
@@ -0,0 +1,61 @@
1
+ import { MeshBasicNodeMaterial, NoBlending, StorageTexture } from 'three/webgpu';
2
+ import { uv, varying, texture, uniform } from 'three/tsl';
3
+ import { wgslTagFn } from 'three-mesh-bvh/webgpu';
4
+ import { heatColorFn } from '../../nodes/debugBounds.wgsl.js';
5
+ import { SAMPLE_COUNT_MASK } from '../../constants.js';
6
+
7
+ // Shows how many samples each pixel has accumulated
8
+ export class SampleDensityMaterial extends MeshBasicNodeMaterial {
9
+
10
+ set texture( v ) {
11
+
12
+ this._texNode.value = v;
13
+
14
+ }
15
+
16
+ set minCount( v ) {
17
+
18
+ this._minUniform.value = v;
19
+
20
+ }
21
+
22
+ set maxCount( v ) {
23
+
24
+ this._maxUniform.value = v;
25
+
26
+ }
27
+
28
+ constructor() {
29
+
30
+ super();
31
+
32
+ this.blending = NoBlending;
33
+
34
+ this._texNode = texture( new StorageTexture( 1, 1 ) );
35
+ this._minUniform = uniform( 0 );
36
+ this._maxUniform = uniform( 1 );
37
+
38
+ const sampleDensity = wgslTagFn/* wgsl */`
39
+ fn sampleDensity( tex: texture_2d<u32>, coord: vec2f, minCount: f32, maxCount: f32 ) -> vec4f {
40
+
41
+ let dims = vec2f( textureDimensions( tex, 0 ) );
42
+ let texel = vec2i( coord * dims );
43
+
44
+ // the high bits hold the ray state flags rather than part of the count
45
+ let samples = textureLoad( tex, texel, 0 ).r & ${ SAMPLE_COUNT_MASK }u;
46
+
47
+ // stretched across the recorded range rather than from zero, otherwise a converged
48
+ // image with counts bunched near the max reads as uniformly hot
49
+ let range = max( maxCount - minCount, 1.0 );
50
+ let t = clamp( ( f32( samples ) - minCount ) / range, 0.0, 1.0 );
51
+
52
+ return vec4f( ${ heatColorFn }( t ), 1.0 );
53
+
54
+ }
55
+ `;
56
+
57
+ this.colorNode = sampleDensity( this._texNode, varying( uv() ), this._minUniform, this._maxUniform );
58
+
59
+ }
60
+
61
+ }