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.
- package/README.md +4 -2
- package/build/index.module.js +23 -5
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +22 -4
- package/build/index.umd.cjs.map +1 -1
- package/package.json +8 -8
- package/src/core/PathTracingSceneGenerator.js +1 -1
- package/src/core/utils/sceneUpdateUtils.js +2 -1
- package/src/uniforms/EquirectHdrInfoUniform.js +4 -2
- package/src/uniforms/LightsInfoUniformStruct.js +16 -1
- package/src/webgpu/API.md +167 -19
- package/src/webgpu/AtlasTexture.js +32 -12
- package/src/webgpu/EquirectHdrInfoNode.js +97 -46
- package/src/webgpu/LightsInfoNode.js +142 -4
- package/src/webgpu/MegaKernelPathTracer.js +11 -3
- package/src/webgpu/PathTracerBackend.js +174 -21
- package/src/webgpu/TurquinTexture.js +2 -0
- package/src/webgpu/WaveFrontPathTracer.js +169 -71
- package/src/webgpu/WebGPUPathTracer.js +637 -51
- package/src/webgpu/compute/ComputeKernel.js +18 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +253 -71
- package/src/webgpu/compute/TallySampleCountsKernel.js +10 -10
- package/src/webgpu/compute/UnpackAuxKernel.js +48 -0
- package/src/webgpu/compute/ZeroOutKernel.js +3 -1
- package/src/webgpu/compute/targets.js +55 -0
- package/src/webgpu/compute/wavefront/LogicKernel.js +136 -55
- package/src/webgpu/compute/wavefront/MaterialKernel.js +153 -66
- package/src/webgpu/compute/wavefront/MediumKernel.js +154 -0
- package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +5 -5
- package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +5 -5
- package/src/webgpu/compute/wavefront/QueueMediumWalksKernel.js +52 -0
- package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +6 -6
- package/src/webgpu/compute/wavefront/TraceRayKernel.js +11 -9
- package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +18 -23
- package/src/webgpu/compute/wavefront/structs.js +63 -35
- package/src/webgpu/constants.js +2 -2
- package/src/webgpu/denoise/OIDNDenoiser.js +81 -78
- package/src/webgpu/index.d.ts +79 -6
- package/src/webgpu/materials/GltfCompliantMaterial.js +20 -6
- package/src/webgpu/materials/debug/AuxDebugMaterial.js +54 -0
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +109 -17
- package/src/webgpu/nodes/aux.wgsl.js +61 -0
- package/src/webgpu/nodes/eon.wgsl.js +1 -1
- package/src/webgpu/nodes/lights.wgsl.js +21 -1
- package/src/webgpu/nodes/material.wgsl.js +2 -1
- package/src/webgpu/nodes/medium.wgsl.js +165 -0
- package/src/webgpu/nodes/random.wgsl.js +2 -0
- package/src/webgpu/nodes/sampling.wgsl.js +44 -3
- package/src/webgpu/nodes/structs.wgsl.js +8 -1
- 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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
|
104
|
+
this._upscaler?.dispose();
|
|
104
105
|
this._upscaler = null;
|
|
105
106
|
|
|
106
107
|
}
|