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.
Files changed (50) hide show
  1. package/README.md +4 -2
  2. package/build/index.module.js +23 -5
  3. package/build/index.module.js.map +1 -1
  4. package/build/index.umd.cjs +22 -4
  5. package/build/index.umd.cjs.map +1 -1
  6. package/package.json +8 -8
  7. package/src/core/PathTracingSceneGenerator.js +1 -1
  8. package/src/core/utils/sceneUpdateUtils.js +2 -1
  9. package/src/uniforms/EquirectHdrInfoUniform.js +4 -2
  10. package/src/uniforms/LightsInfoUniformStruct.js +16 -1
  11. package/src/webgpu/API.md +167 -19
  12. package/src/webgpu/AtlasTexture.js +32 -12
  13. package/src/webgpu/EquirectHdrInfoNode.js +97 -46
  14. package/src/webgpu/LightsInfoNode.js +142 -4
  15. package/src/webgpu/MegaKernelPathTracer.js +11 -3
  16. package/src/webgpu/PathTracerBackend.js +174 -21
  17. package/src/webgpu/TurquinTexture.js +2 -0
  18. package/src/webgpu/WaveFrontPathTracer.js +169 -71
  19. package/src/webgpu/WebGPUPathTracer.js +637 -51
  20. package/src/webgpu/compute/ComputeKernel.js +18 -0
  21. package/src/webgpu/compute/PathTracerMegaKernel.js +253 -71
  22. package/src/webgpu/compute/TallySampleCountsKernel.js +10 -10
  23. package/src/webgpu/compute/UnpackAuxKernel.js +48 -0
  24. package/src/webgpu/compute/ZeroOutKernel.js +3 -1
  25. package/src/webgpu/compute/targets.js +55 -0
  26. package/src/webgpu/compute/wavefront/LogicKernel.js +136 -55
  27. package/src/webgpu/compute/wavefront/MaterialKernel.js +153 -66
  28. package/src/webgpu/compute/wavefront/MediumKernel.js +154 -0
  29. package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +5 -5
  30. package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +5 -5
  31. package/src/webgpu/compute/wavefront/QueueMediumWalksKernel.js +52 -0
  32. package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +6 -6
  33. package/src/webgpu/compute/wavefront/TraceRayKernel.js +11 -9
  34. package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +18 -23
  35. package/src/webgpu/compute/wavefront/structs.js +63 -35
  36. package/src/webgpu/constants.js +2 -2
  37. package/src/webgpu/denoise/OIDNDenoiser.js +81 -78
  38. package/src/webgpu/index.d.ts +79 -6
  39. package/src/webgpu/materials/GltfCompliantMaterial.js +20 -6
  40. package/src/webgpu/materials/debug/AuxDebugMaterial.js +54 -0
  41. package/src/webgpu/nodes/PathtracerBVHComputeData.js +109 -17
  42. package/src/webgpu/nodes/aux.wgsl.js +61 -0
  43. package/src/webgpu/nodes/eon.wgsl.js +1 -1
  44. package/src/webgpu/nodes/lights.wgsl.js +21 -1
  45. package/src/webgpu/nodes/material.wgsl.js +2 -1
  46. package/src/webgpu/nodes/medium.wgsl.js +165 -0
  47. package/src/webgpu/nodes/random.wgsl.js +2 -0
  48. package/src/webgpu/nodes/sampling.wgsl.js +44 -3
  49. package/src/webgpu/nodes/structs.wgsl.js +8 -1
  50. package/src/webgpu/upscale/FSRUpscaler.js +3 -2
@@ -0,0 +1,165 @@
1
+ import { StructTypeNode } from 'three/webgpu';
2
+ import { wgslTagFn } from 'three-mesh-bvh/webgpu';
3
+ import { LIGHT_FAR_DISTANCE } from './lights.wgsl.js';
4
+ import { materialStruct } from './structs.wgsl.js';
5
+
6
+ // Random walk subsurface scattering, following Cycles' subsurface_random_walk.h
7
+ // https://github.com/blender/cycles/blob/a456b761034dda42c32eef9f4aae0fa5a5c9f604/src/kernel/integrator/subsurface_random_walk.h
8
+
9
+ // the smallest attenuation color used to derive a medium's extinction, keeping the log finite
10
+ const MEDIUM_MIN_ATTENUATION = 1e-6;
11
+
12
+ // the throughput below which a walk stops, matching Cycles' VOLUME_THROUGHPUT_EPSILON
13
+ export const MEDIUM_THROUGHPUT_EPSILON = 1e-6;
14
+
15
+ // the largest phase anisotropy used, short of the +/- 1 delta phase function the scatter albedo
16
+ // inversion and the Henyey-Greenstein pdf divide by zero at
17
+ const MEDIUM_MAX_ANISOTROPY = 0.999;
18
+
19
+ export const mediumStruct = new StructTypeNode( {
20
+ inside: 'bool',
21
+ object: 'uint',
22
+ extinction: 'vec3f',
23
+ albedo: 'vec3f',
24
+ anisotropy: 'float',
25
+ }, 'Medium' );
26
+
27
+ // a medium in a storage buffer, which can't hold bools, for walks that span passes
28
+ export const mediumStateStruct = new StructTypeNode( {
29
+ extinction: 'vec3f',
30
+ object: 'uint',
31
+
32
+ albedo: 'vec3f',
33
+ anisotropy: 'float',
34
+
35
+ inside: 'uint',
36
+ }, 'MediumState' );
37
+
38
+ export const storeMediumFn = wgslTagFn/* wgsl */`
39
+
40
+ fn storeMedium( medium: ${ mediumStruct } ) -> ${ mediumStateStruct } {
41
+
42
+ var state: ${ mediumStateStruct };
43
+ state.extinction = medium.extinction;
44
+ state.object = medium.object;
45
+ state.albedo = medium.albedo;
46
+ state.anisotropy = medium.anisotropy;
47
+ state.inside = select( 0u, 1u, medium.inside );
48
+ return state;
49
+
50
+ }
51
+
52
+ `;
53
+
54
+ export const loadMediumFn = wgslTagFn/* wgsl */`
55
+
56
+ fn loadMedium( state: ${ mediumStateStruct } ) -> ${ mediumStruct } {
57
+
58
+ var medium: ${ mediumStruct };
59
+ medium.inside = state.inside != 0u;
60
+ medium.object = state.object;
61
+ medium.extinction = state.extinction;
62
+ medium.albedo = state.albedo;
63
+ medium.anisotropy = state.anisotropy;
64
+ return medium;
65
+
66
+ }
67
+
68
+ `;
69
+
70
+ // The single scatter albedo of a volume from its perceived multi scatter albedo and phase anisotropy, using the
71
+ // inversion from "KHR_materials_scatter" (after Kulla and Conty 2017). A zero multi scatter albedo maps to exactly zero
72
+ // https://github.com/KhronosGroup/glTF/pull/2579
73
+ const singleScatterAlbedoFn = wgslTagFn/* wgsl */`
74
+
75
+ fn singleScatterAlbedo( multiscatterAlbedo: vec3f, anisotropy: f32 ) -> vec3f {
76
+
77
+ let a = multiscatterAlbedo;
78
+ let s = clamp( 4.09712 + 4.20863 * a - sqrt( 9.59217 + 41.6808 * a + 17.7126 * a * a ), vec3f( 0.0 ), vec3f( 1.0 ) );
79
+ let s2 = select( s * s, vec3f( 1.0 ), a <= vec3f( 0.0 ) );
80
+ return ( 1.0 - s2 ) / ( 1.0 - anisotropy * s2 );
81
+
82
+ }
83
+
84
+ `;
85
+
86
+ // The medium entered by refracting into a material, not "inside" if the material doesn't scatter
87
+ export const enterMediumFn = wgslTagFn/* wgsl */`
88
+
89
+ fn enterMedium( material: ${ materialStruct }, object: u32 ) -> ${ mediumStruct } {
90
+
91
+ let anisotropy = clamp( material.scatterAnisotropy, - ${ MEDIUM_MAX_ANISOTROPY }, ${ MEDIUM_MAX_ANISOTROPY } );
92
+
93
+ var medium: ${ mediumStruct };
94
+ medium.extinction = - log( max( material.attenuationColor, vec3f( ${ MEDIUM_MIN_ATTENUATION } ) ) ) / material.attenuationDistance;
95
+ medium.albedo = ${ singleScatterAlbedoFn }( material.multiscatterColor * material.scatterStrength, anisotropy );
96
+ medium.inside = any( medium.albedo > vec3f( 0.0 ) ) && any( medium.extinction > vec3f( 0.0 ) );
97
+ medium.object = object;
98
+ medium.anisotropy = anisotropy;
99
+ return medium;
100
+
101
+ }
102
+
103
+ `;
104
+
105
+ // The probability of sampling each color channel's distance, in proportion to throughput times albedo
106
+ // https://github.com/blender/cycles/blob/a456b761034dda42c32eef9f4aae0fa5a5c9f604/src/kernel/closure/volume.h#L157
107
+ export const mediumChannelPdfFn = wgslTagFn/* wgsl */`
108
+
109
+ fn mediumChannelPdf( throughput: vec3f, albedo: vec3f ) -> vec3f {
110
+
111
+ let weights = abs( throughput * albedo );
112
+ let weightSum = weights.r + weights.g + weights.b;
113
+ if ( weightSum > 0.0 ) {
114
+
115
+ return weights / weightSum;
116
+
117
+ }
118
+
119
+ return vec3f( 1.0 / 3.0 );
120
+
121
+ }
122
+
123
+ `;
124
+
125
+ // Samples the distance to the next scatter event using one color channel's extinction
126
+ export const sampleMediumDistanceFn = wgslTagFn/* wgsl */`
127
+
128
+ fn sampleMediumDistance( medium: ${ mediumStruct }, channelPdf: vec3f, u: vec2f ) -> f32 {
129
+
130
+ var channel = 2u;
131
+ if ( u.x < channelPdf.r ) {
132
+
133
+ channel = 0u;
134
+
135
+ } else if ( u.x < channelPdf.r + channelPdf.g ) {
136
+
137
+ channel = 1u;
138
+
139
+ }
140
+
141
+ // a channel with no extinction never scatters, and WGSL doesn't guarantee an infinite quotient
142
+ let extinction = medium.extinction[ channel ];
143
+ if ( extinction <= 0.0 ) {
144
+
145
+ return ${ LIGHT_FAR_DISTANCE };
146
+
147
+ }
148
+
149
+ return - log( 1.0 - u.y ) / extinction;
150
+
151
+ }
152
+
153
+ `;
154
+
155
+ // Per channel pdf of a segment: of scattering at "dist", or of passing it when it didn't scatter
156
+ export const mediumDistancePdfFn = wgslTagFn/* wgsl */`
157
+
158
+ fn mediumDistancePdf( medium: ${ mediumStruct }, dist: f32, scattered: bool ) -> vec3f {
159
+
160
+ let transmittance = exp( - medium.extinction * dist );
161
+ return select( transmittance, medium.extinction * transmittance, scattered );
162
+
163
+ }
164
+
165
+ `;
@@ -6,6 +6,8 @@ export const RNG_INDEX_APERTURE_SAMPLE = 4;
6
6
  export const RNG_INDEX_DIRECT_LIGHT_SAMPLE = 5;
7
7
  export const RNG_INDEX_RUSSIAN_ROULETTE = 6;
8
8
  export const RNG_INDEX_DISPERSION_WAVELENGTH = 7;
9
+ export const RNG_INDEX_MEDIUM_DISTANCE = 8;
10
+ export const RNG_INDEX_MEDIUM_DIRECTION = 9;
9
11
  export const RNG_INDEX_ALPHA_TEST = 50;
10
12
  import { contextProxyFn } from 'three-mesh-bvh/webgpu';
11
13
  import * as sobol from './rand/sobol.wgsl.js';
@@ -108,14 +108,55 @@ export const sampleHemisphereFn = wgslFn( /* wgsl */ `
108
108
 
109
109
  `, [ constants ] );
110
110
 
111
+ // below this anisotropy the Henyey-Greenstein phase function is sampled as isotropic, avoiding the
112
+ // division by zero in its inverted CDF
113
+ const HG_ISOTROPIC_EPSILON = 1e-3;
114
+
115
+ // Samples a new direction from the Henyey-Greenstein phase function around the propagation direction
116
+ // "d". Positive "g" scatters forward. The sample is exact, so the phase function over its pdf is one.
117
+ // https://github.com/blender/cycles/blob/a456b761034dda42c32eef9f4aae0fa5a5c9f604/src/kernel/closure/volume_util.h#L36
118
+ export const sampleHenyeyGreensteinFn = wgslFn( /* wgsl */ `
119
+
120
+ fn sampleHenyeyGreenstein( d: vec3f, g: f32, uv: vec2f ) -> vec3f {
121
+
122
+ var cosTheta = 1.0 - 2.0 * uv.x;
123
+ if ( abs( g ) > ${ HG_ISOTROPIC_EPSILON } ) {
124
+
125
+ let sqrTerm = ( 1.0 - g * g ) / ( 1.0 - g + 2.0 * g * uv.x );
126
+ cosTheta = clamp( ( 1.0 + g * g - sqrTerm * sqrTerm ) / ( 2.0 * g ), - 1.0, 1.0 );
127
+
128
+ }
129
+
130
+ // orthonormal basis around "d", from https://graphics.pixar.com/library/OrthonormalB/paper.pdf
131
+ let sign = select( sign( d.z ), 1.0, d.z == 0.0 );
132
+ let a = - 1.0 / ( sign + d.z );
133
+ let b = d.x * d.y * a;
134
+ let b1 = vec3( 1.0 + sign * d.x * d.x * a, sign * b, - sign * d.x );
135
+ let b2 = vec3( b, sign + d.y * d.y * a, - d.y );
136
+
137
+ let sinTheta = sqrt( max( 0.0, 1.0 - cosTheta * cosTheta ) );
138
+ let phi = 2.0 * PI * uv.y;
139
+ return normalize( sinTheta * cos( phi ) * b1 + sinTheta * sin( phi ) * b2 + cosTheta * d );
140
+
141
+ }
142
+
143
+ `, [ constants ] );
144
+
111
145
  // power heuristic for multiple importance sampling
112
146
  export const misHeuristicFn = wgslFn( /* wgsl */ `
113
147
 
114
148
  fn misHeuristic( a: f32, b: f32 ) -> f32 {
115
149
 
116
- let aa = a * a;
117
- let bb = b * b;
118
- return aa / ( aa + bb );
150
+ // computed as a ratio so squaring large pdfs can't overflow to Inf / Inf
151
+ if ( a >= b ) {
152
+
153
+ let r = b / a;
154
+ return 1.0 / ( 1.0 + r * r );
155
+
156
+ }
157
+
158
+ let r = a / b;
159
+ return r * r / ( 1.0 + r * r );
119
160
 
120
161
  }
121
162
 
@@ -156,7 +156,13 @@ export const materialStruct = new StructTypeNode( {
156
156
 
157
157
  // offset 264 floats
158
158
  anisotropyMapTransform: 'mat3',
159
- // total size = 276
159
+
160
+ // offset 276 floats
161
+ // the volume's scattering, from "KHR_materials_scatter"
162
+ multiscatterColor: 'vec3',
163
+ scatterStrength: 'float',
164
+ scatterAnisotropy: 'float',
165
+ // total size = 284
160
166
  }, 'Material' );
161
167
 
162
168
  export const surfaceRecordStruct = new StructTypeNode( {
@@ -234,6 +240,7 @@ export const lightStruct = new StructTypeNode( {
234
240
  color: 'vec3f',
235
241
  intensity: 'float',
236
242
  u: 'vec3f',
243
+ visibleToCameraRays: 'int',
237
244
  v: 'vec3f',
238
245
  area: 'float',
239
246
  radius: 'float',
@@ -1,5 +1,5 @@
1
- import { Vector2 } from 'three/webgpu';
2
1
  /** @import { Camera, Texture, WebGPURenderer } from 'three/webgpu' */
2
+ import { Vector2 } from 'three/webgpu';
3
3
 
4
4
  const _size = /*@__PURE__*/ new Vector2();
5
5
 
@@ -50,6 +50,7 @@ export class FSRUpscaler {
50
50
  init( renderer ) {
51
51
 
52
52
  this.renderer = renderer;
53
+ this._upscaler?.dispose();
53
54
  this._upscaler = new this.Upscaler( { renderer } );
54
55
  this._upscaler.init();
55
56
 
@@ -100,7 +101,7 @@ export class FSRUpscaler {
100
101
 
101
102
  dispose() {
102
103
 
103
- this._upscaler.dispose();
104
+ this._upscaler?.dispose();
104
105
  this._upscaler = null;
105
106
 
106
107
  }