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,92 @@
1
+ import { texture, vec4 } from 'three/tsl';
2
+ import { wgslTagFn } from 'three-mesh-bvh/webgpu';
3
+ import { rand4, rngInit, rngNextBounce } from './sobol.wgsl.js';
4
+ import { BlueNoiseTexture } from '../../../textures/BlueNoiseTexture.js';
5
+
6
+ // Based in part on the "stratified" random sample implement from the WebGLPathTracer which
7
+ // is in turn based on "hoverinc/ray-tracing-renderer" sampling strategy.
8
+ // This random sampling strategy uses the exact sample stratified sobol sequence for every
9
+ // pixel on screen, with a fractional blue-noise offset applied. This means that pixels
10
+ // along the blue noise stride sample the same directions leading to a blue noise pattern
11
+ // emerging in screen space.
12
+ //
13
+ // References
14
+ // - "Blue-Noise Dithered Sampling", Georgiev & Fajardo, SIGGRAPH 2016 Talks
15
+ // https://www.arnoldrenderer.com/research/dither_abstract.pdf
16
+ // - Blue noise discussion:
17
+ // https://developer.nvidia.com/blog/rendering-in-real-time-with-spatiotemporal-blue-noise-textures-part-2/
18
+
19
+ // constants
20
+ const BN_SIZE = 64;
21
+
22
+ // construct nodes
23
+ const blueNoiseTex = new BlueNoiseTexture( BN_SIZE, 1 );
24
+ const blueNoiseTexNode = texture( blueNoiseTex );
25
+ const pixelSeed = vec4( 0 ).toVar( 'blueDitherSeed' );
26
+
27
+ // When dithering, the sobol sampler is seeded with a constant pixel so every pixel
28
+ // uses the same sequence, which is modified with the per-pixel blue noise sample.
29
+ const blueDitherInitFunc = wgslTagFn/* wgsl */`
30
+ fn blueDitherInitialize( pixel: vec2u, pathIndex: u32, bounceIndex: u32 ) -> void {
31
+
32
+ let coord = vec2i( pixel % vec2u( ${ BN_SIZE }u ) );
33
+ ${ pixelSeed } = textureLoad( ${ blueNoiseTexNode }, coord, 0 );
34
+ ${ rngInit }( vec2u( 0 ), pathIndex, bounceIndex );
35
+
36
+ }
37
+ `;
38
+
39
+ const blueDitherNextBounceFunc = wgslTagFn/* wgsl */`
40
+ fn blueDitherNextBounce() -> void {
41
+
42
+ ${ rngNextBounce }();
43
+
44
+ }
45
+ `;
46
+
47
+ // The per-pixel scalar is rotated by the golden ratio per dimension. Apply a per-dimension
48
+ // offset to reduce the corelation of the sequence.
49
+ const blueDitherRand4Func = wgslTagFn/* wgsl */`
50
+ fn blueDitherRand4( effect: u32 ) -> vec4f {
51
+
52
+ // Get the scrambled sobol stratified sample
53
+ let stratifiedSample = ${ rand4 }( effect );
54
+
55
+ // offset it by the blue noise seed and dimension rotation
56
+ return fract( stratifiedSample + ${ pixelSeed }.r );
57
+
58
+ }
59
+ `;
60
+
61
+ const blueDitherRand3Func = wgslTagFn/* wgsl */`
62
+ fn blueDitherRand3( effect: u32 ) -> vec3f {
63
+
64
+ return ${ blueDitherRand4Func }( effect ).xyz;
65
+
66
+ }
67
+ `;
68
+
69
+ const blueDitherRand2Func = wgslTagFn/* wgsl */`
70
+ fn blueDitherRand2( effect: u32 ) -> vec2f {
71
+
72
+ return ${ blueDitherRand4Func }( effect ).xy;
73
+
74
+ }
75
+ `;
76
+
77
+ const blueDitherRand1Func = wgslTagFn/* wgsl */`
78
+ fn blueDitherRand1( effect: u32 ) -> f32 {
79
+
80
+ return ${ blueDitherRand4Func }( effect ).x;
81
+
82
+ }
83
+ `;
84
+
85
+ export {
86
+ blueDitherNextBounceFunc as rngNextBounce,
87
+ blueDitherInitFunc as rngInit,
88
+ blueDitherRand1Func as rand1,
89
+ blueDitherRand2Func as rand2,
90
+ blueDitherRand3Func as rand3,
91
+ blueDitherRand4Func as rand4,
92
+ };
@@ -0,0 +1,81 @@
1
+ import { wgsl, wgslFn } from 'three/tsl';
2
+
3
+ const pcgStateStruct = wgsl( /* wgsl */`
4
+
5
+ struct PcgState {
6
+ s0: vec4u,
7
+ s1: vec4u,
8
+ };
9
+
10
+ var<private> g_state: PcgState;
11
+ ` );
12
+
13
+ const pcgInitFunc = wgslFn( /* wgsl */`
14
+ fn pcgInitialize( pixel: vec2u, pathIndex: u32, bounceIndex: u32 ) -> void {
15
+
16
+ // white noise seed
17
+ g_state.s0 = vec4u( pixel | vec2( bounceIndex << 16 ), pathIndex, pixel.x + pixel.y);
18
+
19
+ // blue noise seed
20
+ g_state.s1 = vec4u( pathIndex, pathIndex * 15843, pathIndex * 31 + 4566, pathIndex * 2345 + 58585 );
21
+
22
+ }
23
+ `, [ pcgStateStruct ] );
24
+
25
+ const pcg4d = wgslFn( /* wgsl */ `
26
+ fn pcg4d( v: ptr<private, vec4u> ) -> void {
27
+
28
+ *v = *v * 1664525u + 1013904223u;
29
+ *v = *v + v.yzxy * v.wxyz;
30
+ *v = *v ^ ( *v >> vec4u( 16u ) );
31
+ *v = *v + v.yzxy * v.wxyz;
32
+ }
33
+ ` );
34
+
35
+ const pcgRandFunc = wgslFn( /*wgsl*/`
36
+ fn pcgRand( _id: u32 ) -> f32 {
37
+
38
+ pcg4d( &g_state.s0 );
39
+ return abs( f32( g_state.s0.x ) / f32( 0xffffffffu ) );
40
+
41
+ }
42
+ `, [ pcg4d, pcgStateStruct ] );
43
+
44
+ const pcgRand2Func = wgslFn( /*wgsl*/`
45
+ fn pcgRand2( _id: u32 ) -> vec2f {
46
+
47
+ pcg4d( &g_state.s0 );
48
+ return abs( vec2f( g_state.s0.xy ) / f32( 0xffffffffu ) );
49
+
50
+ }
51
+ `, [ pcg4d, pcgStateStruct ] );
52
+
53
+ const pcgRand3Func = wgslFn( /*wgsl*/`
54
+ fn pcgRand3( _id: u32 ) -> vec3f {
55
+
56
+ pcg4d( &g_state.s0 );
57
+ return abs( vec3f( g_state.s0.xyz ) / f32( 0xffffffffu ) );
58
+
59
+ }
60
+ `, [ pcg4d, pcgStateStruct ] );
61
+
62
+ const pcgRand4Func = wgslFn( /*wgsl*/`
63
+ fn pcgRand4( _id: u32 ) -> vec4f {
64
+
65
+ pcg4d( &g_state.s0 );
66
+ return abs( vec4f( g_state.s0 ) / f32( 0xffffffffu ) );
67
+
68
+ }
69
+ `, [ pcg4d, pcgStateStruct ] );
70
+
71
+ const pcgNextBounceFunc = wgslFn( /* wgsl */ 'fn noop() -> void {}' );
72
+
73
+ // PCG definitions
74
+ export {
75
+ pcgInitFunc as rngInit,
76
+ pcgNextBounceFunc as rngNextBounce,
77
+ pcgRandFunc as rand1,
78
+ pcgRand2Func as rand2,
79
+ pcgRand3Func as rand3,
80
+ pcgRand4Func as rand4,
81
+ };
@@ -0,0 +1,287 @@
1
+ import { uint, float, wgsl, wgslFn } from 'three/tsl';
2
+ import { wgslTagFn } from 'three-mesh-bvh/webgpu';
3
+
4
+ // References
5
+ // - https://jcgt.org/published/0009/04/01/
6
+ // - Code from https://www.shadertoy.com/view/WtGyDm
7
+ // Ported from WebGL version at sobol.glsl.js
8
+
9
+ const SOBOL_MAX_POINTS = uint( 256 * 256 );
10
+ const SOBOL_FACTOR = float( 1.0 / 16777216.0 );
11
+
12
+ const sobolConstants = wgsl( /* wgsl */ `
13
+
14
+ const SOBOL_DIRECTIONS_1 = array<u32, 32>(
15
+ 0x80000000u, 0xc0000000u, 0xa0000000u, 0xf0000000u,
16
+ 0x88000000u, 0xcc000000u, 0xaa000000u, 0xff000000u,
17
+ 0x80800000u, 0xc0c00000u, 0xa0a00000u, 0xf0f00000u,
18
+ 0x88880000u, 0xcccc0000u, 0xaaaa0000u, 0xffff0000u,
19
+ 0x80008000u, 0xc000c000u, 0xa000a000u, 0xf000f000u,
20
+ 0x88008800u, 0xcc00cc00u, 0xaa00aa00u, 0xff00ff00u,
21
+ 0x80808080u, 0xc0c0c0c0u, 0xa0a0a0a0u, 0xf0f0f0f0u,
22
+ 0x88888888u, 0xccccccccu, 0xaaaaaaaau, 0xffffffffu
23
+ );
24
+
25
+ const SOBOL_DIRECTIONS_2 = array<u32, 32>(
26
+ 0x80000000u, 0xc0000000u, 0x60000000u, 0x90000000u,
27
+ 0xe8000000u, 0x5c000000u, 0x8e000000u, 0xc5000000u,
28
+ 0x68800000u, 0x9cc00000u, 0xee600000u, 0x55900000u,
29
+ 0x80680000u, 0xc09c0000u, 0x60ee0000u, 0x90550000u,
30
+ 0xe8808000u, 0x5cc0c000u, 0x8e606000u, 0xc5909000u,
31
+ 0x6868e800u, 0x9c9c5c00u, 0xeeee8e00u, 0x5555c500u,
32
+ 0x8000e880u, 0xc0005cc0u, 0x60008e60u, 0x9000c590u,
33
+ 0xe8006868u, 0x5c009c9cu, 0x8e00eeeeu, 0xc5005555u
34
+ );
35
+
36
+ const SOBOL_DIRECTIONS_3 = array<u32, 32>(
37
+ 0x80000000u, 0xc0000000u, 0x20000000u, 0x50000000u,
38
+ 0xf8000000u, 0x74000000u, 0xa2000000u, 0x93000000u,
39
+ 0xd8800000u, 0x25400000u, 0x59e00000u, 0xe6d00000u,
40
+ 0x78080000u, 0xb40c0000u, 0x82020000u, 0xc3050000u,
41
+ 0x208f8000u, 0x51474000u, 0xfbea2000u, 0x75d93000u,
42
+ 0xa0858800u, 0x914e5400u, 0xdbe79e00u, 0x25db6d00u,
43
+ 0x58800080u, 0xe54000c0u, 0x79e00020u, 0xb6d00050u,
44
+ 0x800800f8u, 0xc00c0074u, 0x200200a2u, 0x50050093u
45
+ );
46
+
47
+ const SOBOL_DIRECTIONS_4 = array<u32, 32>(
48
+ 0x80000000u, 0x40000000u, 0x20000000u, 0xb0000000u,
49
+ 0xf8000000u, 0xdc000000u, 0x7a000000u, 0x9d000000u,
50
+ 0x5a800000u, 0x2fc00000u, 0xa1600000u, 0xf0b00000u,
51
+ 0xda880000u, 0x6fc40000u, 0x81620000u, 0x40bb0000u,
52
+ 0x22878000u, 0xb3c9c000u, 0xfb65a000u, 0xddb2d000u,
53
+ 0x78022800u, 0x9c0b3c00u, 0x5a0fb600u, 0x2d0ddb00u,
54
+ 0xa2878080u, 0xf3c9c040u, 0xdb65a020u, 0x6db2d0b0u,
55
+ 0x800228f8u, 0x400b3cdcu, 0x200fb67au, 0xb00ddb9du
56
+ );
57
+
58
+ ` );
59
+
60
+ const sobolPixelIndex = uint( 0 ).toVar( 'sobolPixelIndex' );
61
+ const sobolPathIndex = uint( 0 ).toVar( 'sobolPathIndex' );
62
+ const sobolBounceIndex = uint( 0 ).toVar( 'sobolBounceIndex' );
63
+
64
+ const getMaskedSobolFunc = wgslFn( /* wgsl */ `
65
+ fn getMaskedSobol( index: u32, directions: array<u32, 32> ) -> u32 {
66
+
67
+ var X = 0u;
68
+ for ( var bit = 0u; bit < 32u; bit ++ ) {
69
+
70
+ let mask = ( index >> bit ) & 1u;
71
+ X ^= mask * directions[ bit ];
72
+
73
+ }
74
+ return X;
75
+
76
+ }
77
+ ` );
78
+
79
+ // functions to generate multi-dimensions variables of the same functions
80
+ // to support 1, 2, 3, and 4 dimensional sobol sampling.
81
+ const sobolScrambleNodesGenerator = ( dim = 1 ) => {
82
+
83
+ if ( dim <= 0 ) {
84
+
85
+ return;
86
+
87
+ }
88
+
89
+ const type = dim > 1 ? `vec${dim}u` : 'u32';
90
+
91
+ const sobolReverseBitsFunc = wgslFn( /* wgsl */ `
92
+ fn sobolReverseBits_${type}( in: ${type} ) -> ${type} {
93
+
94
+ var x = in;
95
+ x = ( ( ( x & ${type}( 0xaaaaaaaau ) ) >> ${type}( 1 ) ) | ( ( x & ${type}( 0x55555555u ) ) << ${type}( 1 ) ) );
96
+ x = ( ( ( x & ${type}( 0xccccccccu ) ) >> ${type}( 2 ) ) | ( ( x & ${type}( 0x33333333u ) ) << ${type}( 2 ) ) );
97
+ x = ( ( ( x & ${type}( 0xf0f0f0f0u ) ) >> ${type}( 4 ) ) | ( ( x & ${type}( 0x0f0f0f0fu ) ) << ${type}( 4 ) ) );
98
+ x = ( ( ( x & ${type}( 0xff00ff00u ) ) >> ${type}( 8 ) ) | ( ( x & ${type}( 0x00ff00ffu ) ) << ${type}( 8 ) ) );
99
+ return ( ( x >> ${type}( 16 ) ) | ( x << ${type}( 16 ) ) );
100
+
101
+ }
102
+ ` );
103
+
104
+ const sobolHashCombineFunc = wgslFn( /* wgsl */ `
105
+ fn sobolHashCombine_${type}( seed: u32, v: ${type} ) -> ${type} {
106
+
107
+ return ${type}( seed ) ^ ( v + ${ type }( ( seed << 6 ) + ( seed >> 2 ) ) );
108
+
109
+ }
110
+ ` );
111
+
112
+ const sobolLaineKarrasPermutationFunc = wgslFn( /* wgsl */ `
113
+ fn sobolLaineKarrasPermutation_${type}( in: ${type}, seed: ${type} ) -> ${type} {
114
+
115
+ var x = in;
116
+ x += seed;
117
+ x ^= x * 0x6c50b47cu;
118
+ x ^= x * 0xb82f1e52u;
119
+ x ^= x * 0xc7afe638u;
120
+ x ^= x * 0x8d22f6e6u;
121
+ return x;
122
+
123
+ }
124
+ ` );
125
+
126
+ const nestedUniformScrambleBase2Func = wgslTagFn/* wgsl */ `
127
+ fn nestedUniformScrambleBase2_${type}( x: ${type}, seed: ${type} ) -> ${type} {
128
+
129
+ var res = ${ sobolLaineKarrasPermutationFunc }( x, seed );
130
+ res = ${ sobolReverseBitsFunc }( res );
131
+ return res;
132
+
133
+ }
134
+ `;
135
+
136
+ return {
137
+ reverseBits: sobolReverseBitsFunc,
138
+ hashCombine: sobolHashCombineFunc,
139
+ lkPermutation: sobolLaineKarrasPermutationFunc,
140
+ scramble: nestedUniformScrambleBase2Func,
141
+ };
142
+
143
+ };
144
+
145
+ // 0th node is intentionally empty to make access pattern more intuitive:
146
+ // sobolNodes[ i ] are nodes that are needed to sample i-dimensional vectors
147
+ // 1-dimensional vector = f32
148
+ const sobolNodes = Array.from( { length: 5 }, ( _, i ) => sobolScrambleNodesGenerator( i ) );
149
+
150
+ const generateSobolPointFunc = wgslTagFn/* wgsl */`
151
+ ${ [ sobolConstants ] }
152
+ fn generateSobolPoint( id: u32 ) -> vec4f {
153
+
154
+ var index = id;
155
+ if ( index >= ${ SOBOL_MAX_POINTS } ) {
156
+
157
+ index = index % ${ SOBOL_MAX_POINTS };
158
+ // return vec4( 0.0 );
159
+
160
+ }
161
+
162
+ // NOTE: this sobol "direction" is also available but we can't write out 5 components
163
+ // uint x = index & 0x00ffffffu;
164
+ let x = ${ sobolNodes[ 1 ].reverseBits }( ${ getMaskedSobolFunc }( index, SOBOL_DIRECTIONS_1 ) ) & 0x00ffffffu;
165
+ let y = ${ sobolNodes[ 1 ].reverseBits }( ${ getMaskedSobolFunc }( index, SOBOL_DIRECTIONS_2 ) ) & 0x00ffffffu;
166
+ let z = ${ sobolNodes[ 1 ].reverseBits }( ${ getMaskedSobolFunc }( index, SOBOL_DIRECTIONS_3 ) ) & 0x00ffffffu;
167
+ let w = ${ sobolNodes[ 1 ].reverseBits }( ${ getMaskedSobolFunc }( index, SOBOL_DIRECTIONS_4 ) ) & 0x00ffffffu;
168
+
169
+ return vec4( f32( x ), f32( y ), f32( z ), f32( w ) ) * ${ SOBOL_FACTOR };
170
+
171
+ }
172
+ `;
173
+
174
+ const sobolHashFunc = wgslFn( /* wgsl */ `
175
+ fn sobolHash( in: u32 ) -> u32 {
176
+
177
+ var x = in;
178
+ // finalizer from murmurhash3
179
+ x ^= x >> 16;
180
+ x *= 0x85ebca6bu;
181
+ x ^= x >> 13;
182
+ x *= 0xc2b2ae35u;
183
+ x ^= x >> 16;
184
+ return x;
185
+
186
+ }
187
+ ` );
188
+
189
+ const sobolGetSeedFunc = wgslTagFn/* wgsl */`
190
+ fn sobolGetSeed( effect: u32 ) -> u32 {
191
+
192
+ return ${ sobolHashFunc }(
193
+ ${ sobolNodes[ 1 ].hashCombine }(
194
+ ${ sobolNodes[ 1 ].hashCombine }(
195
+ ${ sobolHashFunc }( ${ sobolBounceIndex } ),
196
+ ${ sobolPixelIndex }
197
+ ),
198
+ effect
199
+ )
200
+ );
201
+
202
+ }
203
+ `;
204
+
205
+ const sobolGenerator = ( dim = 1, sobolPointFunc = generateSobolPointFunc ) => {
206
+
207
+ if ( dim <= 0 ) {
208
+
209
+ return;
210
+
211
+ }
212
+
213
+ const utype = dim > 1 ? `vec${ dim }u` : 'u32';
214
+ const ftype = dim > 1 ? `vec${ dim }f` : 'f32';
215
+
216
+ let components = '.r';
217
+ let combineValues = '1u';
218
+ if ( dim === 2 ) {
219
+
220
+ components = '.rg';
221
+ combineValues = 'vec2( 1u, 2u )';
222
+
223
+ } else if ( dim === 3 ) {
224
+
225
+ components = '.rgb';
226
+ combineValues = 'vec3( 1u, 2u, 3u )';
227
+
228
+ } else if ( dim === 4 ) {
229
+
230
+ components = '';
231
+ combineValues = 'vec4( 1u, 2u, 3u, 4u )';
232
+
233
+ }
234
+
235
+ return wgslTagFn/* wgsl */`
236
+ fn sobol${ dim }( effect: u32 ) -> ${ ftype } {
237
+
238
+ let seed = ${ sobolGetSeedFunc }( effect );
239
+ let index = ${ sobolPathIndex };
240
+
241
+ let shuffle_seed = ${ sobolNodes[ 1 ].hashCombine }( seed, 0u );
242
+ let shuffled_index = ${ sobolNodes[ 1 ].scramble }( ${ sobolNodes[ 1 ].reverseBits }( index ), shuffle_seed );
243
+ let sobol_pt = ${ sobolPointFunc }( shuffled_index )${ components };
244
+ var result = ${ utype }( sobol_pt * 16777216.0 );
245
+
246
+ let seed2 = ${ sobolNodes[ dim ].hashCombine }( seed, ${ combineValues } );
247
+ result = ${ sobolNodes[ dim ].scramble }( result, seed2 );
248
+
249
+ return ${ SOBOL_FACTOR } * ${ ftype }( result >> ${utype}( 8 ) );
250
+
251
+ }
252
+ `;
253
+
254
+ };
255
+
256
+ const sobolRand1Func = sobolGenerator( 1 );
257
+ const sobolRand2Func = sobolGenerator( 2 );
258
+ const sobolRand3Func = sobolGenerator( 3 );
259
+ const sobolRand4Func = sobolGenerator( 4 );
260
+
261
+ const sobolInitFunc = wgslTagFn/* wgsl */`
262
+ fn sobolInit( pixel: vec2u, pathIndex: u32, bounceIndex: u32 ) -> void {
263
+
264
+ ${ sobolPixelIndex } = ( pixel.x << 16 ) | pixel.y;
265
+ ${ sobolPathIndex } = pathIndex;
266
+ ${ sobolBounceIndex } = bounceIndex;
267
+
268
+ }
269
+ `;
270
+
271
+ const sobolNextBounceFunc = wgslTagFn/* wgsl */`
272
+ fn sobolNextBounce() -> void {
273
+
274
+ ${ sobolBounceIndex }++;
275
+
276
+ }
277
+ `;
278
+
279
+ // Sobol definitions
280
+ export {
281
+ sobolInitFunc as rngInit,
282
+ sobolNextBounceFunc as rngNextBounce,
283
+ sobolRand1Func as rand1,
284
+ sobolRand2Func as rand2,
285
+ sobolRand3Func as rand3,
286
+ sobolRand4Func as rand4,
287
+ };
@@ -0,0 +1,20 @@
1
+ export const RNG_INDEX_RAY_JITTER = 0;
2
+ export const RNG_INDEX_BACKGROUND_SAMPLE = 1;
3
+ export const RNG_INDEX_SCATTER_TYPE = 2;
4
+ export const RNG_INDEX_SCATTER_DIRECTION = 3;
5
+ export const RNG_INDEX_APERTURE_SAMPLE = 4;
6
+ export const RNG_INDEX_DIRECT_LIGHT_SAMPLE = 5;
7
+ export const RNG_INDEX_RUSSIAN_ROULETTE = 6;
8
+ export const RNG_INDEX_DISPERSION_WAVELENGTH = 7;
9
+ export const RNG_INDEX_ALPHA_TEST = 50;
10
+ import { contextProxyFn } from 'three-mesh-bvh/webgpu';
11
+ import * as sobol from './rand/sobol.wgsl.js';
12
+
13
+ // Wrap random functions in context-proxies that will retrieve the functions from
14
+ // the build context if present otherwise fallback to sobol.
15
+ export const rngInit = contextProxyFn( 'random.rngInit', sobol.rngInit );
16
+ export const rngNextBounce = contextProxyFn( 'random.rngNextBounce', sobol.rngNextBounce );
17
+ export const rand1 = contextProxyFn( 'random.rand1', sobol.rand1 );
18
+ export const rand2 = contextProxyFn( 'random.rand2', sobol.rand2 );
19
+ export const rand3 = contextProxyFn( 'random.rand3', sobol.rand3 );
20
+ export const rand4 = contextProxyFn( 'random.rand4', sobol.rand4 );
@@ -0,0 +1,21 @@
1
+ import { wgslFn } from 'three/tsl';
2
+
3
+ export const resetResultFn = wgslFn( /* wgsl */ `
4
+
5
+ fn resetBuffers(
6
+ resultBuffer: ptr<storage, array<vec4f>, read_write>,
7
+ sample_count_buffer: ptr<storage, array<u32>, read_write>,
8
+ dimensions: vec2u,
9
+
10
+ globalId: vec2u,
11
+ ) -> void {
12
+
13
+ let offset = globalId.x + globalId.y * dimensions.x;
14
+ sample_count_buffer[offset] = 0;
15
+ resultBuffer[offset] = vec4f(0.0);
16
+
17
+ }
18
+
19
+ ` );
20
+
21
+ export default resetResultFn;
@@ -0,0 +1,193 @@
1
+ import { wgslFn } from 'three/tsl';
2
+ import { constants, lobeWeightsStruct } from './structs.wgsl.js';
3
+ import { disneyFresnelFunc, iorToF0Func, schlickFresnelFunc } from './utils.wgsl.js';
4
+
5
+ /*
6
+ wi : incident vector or light vector (pointing toward the light)
7
+ wo : outgoing vector or view vector (pointing towards the camera)
8
+ wh : computed half vector from wo and wi
9
+ Vectors above are assumed to be in tangent space. i.e. +z is along macronormal of the surface
10
+ eta : Greek character used to denote the "ratio of ior"
11
+ */
12
+
13
+ export const sampleSphereFunc = wgslFn( /* wgsl */ `
14
+
15
+ fn sampleSphere( uv: vec2f ) -> vec3f {
16
+
17
+ let u = ( uv.x - 0.5 ) * 2.0;
18
+ let t = uv.y * PI * 2.0;
19
+ let f = sqrt( 1.0 - u * u );
20
+
21
+ return vec3f( f * cos( t ), f * sin( t ), u );
22
+
23
+ }
24
+
25
+ `, [ constants ] );
26
+
27
+ // TODO: Investigate sampling directly in tagent space?
28
+ // See 16.6.1 in https://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.9.pdf
29
+ export const diffuseDirectionFunc = wgslFn( /* wgsl */ `
30
+
31
+ fn diffuseDirection( wo: vec3f, uv: vec2f ) -> vec3f {
32
+
33
+ var lightDirection = sampleSphere( uv );
34
+ lightDirection.z += 1.0;
35
+ lightDirection = normalize( lightDirection );
36
+
37
+ return lightDirection;
38
+
39
+ }
40
+
41
+ `, [ sampleSphereFunc ] );
42
+
43
+ export const getLobeWeightsFunc = wgslFn( /* wgsl */ `
44
+
45
+ fn getLobeWeights( wo: vec3f, wi: vec3f, woClearcoat: vec3f, wh: vec3f, clearcoatIor: f32, surf: SurfaceRecord ) -> LobeWeights {
46
+
47
+ var weights: LobeWeights;
48
+ var remaining = 1.0;
49
+
50
+ let clearcoatF0 = iorToF0( clearcoatIor );
51
+ let clearcoatFresnel = schlickFresnel( saturate( woClearcoat.z ), clearcoatF0 );
52
+ weights.clearcoat = surf.clearcoat * clearcoatFresnel;
53
+ remaining -= weights.clearcoat;
54
+
55
+ // this specular lobe only handles the opaque portion of the specular
56
+ let fEstimate = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
57
+ weights.specular = remaining * ( surf.metalness + ( 1.0 - surf.metalness ) * ( 1.0 - surf.transmission ) * fEstimate );
58
+ remaining -= weights.specular;
59
+
60
+ // the transmission lobe covers both the reflected and refracted halves of the transmissive portion
61
+ weights.transmission = remaining * surf.transmission;
62
+ remaining -= weights.transmission;
63
+
64
+ weights.diffuse = remaining;
65
+
66
+ return weights;
67
+
68
+ }
69
+
70
+ `, [ disneyFresnelFunc, schlickFresnelFunc, iorToF0Func, lobeWeightsStruct, constants ] );
71
+
72
+ export const equirectDirectionToUvFn = wgslFn( /* wgsl */`
73
+
74
+ fn equirectDirectionToUv(direction: vec3f) -> vec2f {
75
+
76
+ // from Spherical.setFromCartesianCoords
77
+ var uv = vec2f( atan2( direction.z, direction.x ), acos( direction.y ) );
78
+ uv /= vec2f( 2.0 * PI, PI );
79
+
80
+ // apply adjustments to get values in range [0, 1] and y right side up
81
+ uv.x += 0.5;
82
+ uv.y = 1.0 - uv.y;
83
+ return uv;
84
+
85
+ }
86
+
87
+ ` );
88
+
89
+ export const sampleHemisphereFn = wgslFn( /* wgsl */ `
90
+
91
+ fn sampleHemisphere( n: vec3f, uv: vec2f ) -> vec3f {
92
+
93
+ // https://www.rorydriscoll.com/2009/01/07/better-sampling/
94
+ // https://graphics.pixar.com/library/OrthonormalB/paper.pdf
95
+ let sign = select( sign( n.z ), 1.0, n.z == 0.0 );
96
+ let a = - 1.0 / ( sign + n.z );
97
+ let b = n.x * n.y * a;
98
+ let b1 = vec3( 1.0 + sign * n.x * n.x * a, sign * b, - sign * n.x );
99
+ let b2 = vec3( b, sign + n.y * n.y * a, - n.y );
100
+
101
+ let r = sqrt( uv.x );
102
+ let theta = 2.0 * PI * uv.y;
103
+ let x = r * cos( theta );
104
+ let y = r * sin( theta );
105
+ return x * b1 + y * b2 + sqrt( 1.0 - uv.x ) * n;
106
+
107
+ }
108
+
109
+ `, [ constants ] );
110
+
111
+ // power heuristic for multiple importance sampling
112
+ export const misHeuristicFn = wgslFn( /* wgsl */ `
113
+
114
+ fn misHeuristic( a: f32, b: f32 ) -> f32 {
115
+
116
+ let aa = a * a;
117
+ let bb = b * b;
118
+ return aa / ( aa + bb );
119
+
120
+ }
121
+
122
+ ` );
123
+
124
+ export const luminanceFn = wgslFn( /* wgsl */ `
125
+
126
+ fn luminance( color: vec3f ) -> f32 {
127
+
128
+ return dot( color, vec3f( 0.2126, 0.7152, 0.0722 ) );
129
+
130
+ }
131
+
132
+ ` );
133
+
134
+ // inverse of equirectDirectionToUv: map an equirect uv back to a direction
135
+ export const equirectUvToDirectionFn = wgslFn( /* wgsl */ `
136
+
137
+ fn equirectUvToDirection( uvIn: vec2f ) -> vec3f {
138
+
139
+ // undo the adjustments applied in equirectDirectionToUv
140
+ var uv = uvIn;
141
+ uv.x -= 0.5;
142
+ uv.y = 1.0 - uv.y;
143
+
144
+ let theta = uv.x * 2.0 * PI;
145
+ let phi = uv.y * PI;
146
+ let sinPhi = sin( phi );
147
+
148
+ return vec3f( sinPhi * cos( theta ), cos( phi ), sinPhi * sin( theta ) );
149
+
150
+ }
151
+
152
+ `, [ constants ] );
153
+
154
+ // solid-angle pdf factor for the equirect parameterization ( accounts for pole compression )
155
+ export const equirectDirectionPdfFn = wgslFn( /* wgsl */ `
156
+
157
+ fn equirectDirectionPdf( direction: vec3f ) -> f32 {
158
+
159
+ let uv = equirectDirectionToUv( direction );
160
+ let theta = uv.y * PI;
161
+ let sinTheta = sin( theta );
162
+ if ( sinTheta == 0.0 ) {
163
+
164
+ return 0.0;
165
+
166
+ }
167
+
168
+ return 1.0 / ( 2.0 * PI * PI * sinTheta );
169
+
170
+ }
171
+
172
+ `, [ constants, equirectDirectionToUvFn ] );
173
+
174
+ export const weightedAlphaBlendFn = wgslFn( /* wgsl */`
175
+
176
+ fn weightedAlphaBlend( prevColor: vec4f, newColor: vec4f, weight: f32 ) -> vec4f {
177
+
178
+ let invWeight = 1.0 - weight;
179
+ let totalAlpha = prevColor.a * invWeight + newColor.a * weight;
180
+ var blendedColor = vec4f( 0 );
181
+ if ( totalAlpha != 0.0 ) {
182
+
183
+ let prevContrib = prevColor.rgb * invWeight * prevColor.a / totalAlpha;
184
+ let resContrib = newColor.rgb * weight * newColor.a / totalAlpha;
185
+ blendedColor = vec4f( prevContrib + resContrib, totalAlpha );
186
+
187
+ }
188
+
189
+ return blendedColor;
190
+
191
+ }
192
+
193
+ ` );