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.
- package/README.md +53 -530
- package/build/index.module.js +262 -50
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +261 -48
- package/build/index.umd.cjs.map +1 -1
- package/package.json +29 -11
- package/src/core/WebGLPathTracer.js +8 -0
- package/src/core/utils/sceneUpdateUtils.js +8 -12
- package/src/detectors/PrecisionMaterial.js +9 -9
- package/src/index.js +1 -0
- package/src/objects/PhysicalSpotLight.js +2 -0
- package/src/shader/bsdf/bsdf_functions.glsl.js +0 -1
- package/src/textures/BlueNoiseTexture.js +6 -6
- package/src/textures/ProceduralEquirectTexture.js +8 -7
- package/src/textures/turquinMetal.png +0 -0
- package/src/uniforms/EquirectHdrInfoUniform.js +9 -4
- package/src/uniforms/FloatAttributeTextureArray.js +11 -11
- package/src/uniforms/MaterialsTexture.js +9 -9
- package/src/webgpu/API.md +762 -0
- package/src/webgpu/AtlasTexture.js +471 -0
- package/src/webgpu/BlurredEnvMapGenerator.js +129 -0
- package/src/webgpu/EquirectBackgroundInfo.js +97 -0
- package/src/webgpu/EquirectHdrInfoNode.js +151 -0
- package/src/webgpu/LightsInfoNode.js +224 -0
- package/src/webgpu/MegaKernelPathTracer.js +277 -0
- package/src/webgpu/PathTracerBackend.js +216 -0
- package/src/webgpu/TurquinTexture.js +169 -0
- package/src/webgpu/WaveFrontPathTracer.js +586 -0
- package/src/webgpu/WebGPUPathTracer.js +1302 -0
- package/src/webgpu/compute/ComputeKernel.js +92 -0
- package/src/webgpu/compute/CopyBufferKernel.js +40 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +470 -0
- package/src/webgpu/compute/SampleDebugKernel.js +51 -0
- package/src/webgpu/compute/TallySampleCountsKernel.js +111 -0
- package/src/webgpu/compute/ZeroOutBufferKernel.js +35 -0
- package/src/webgpu/compute/ZeroOutKernel.js +31 -0
- package/src/webgpu/compute/wavefront/LogicKernel.js +349 -0
- package/src/webgpu/compute/wavefront/MaterialKernel.js +371 -0
- package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +72 -0
- package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +38 -0
- package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +66 -0
- package/src/webgpu/compute/wavefront/TraceRayKernel.js +71 -0
- package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +67 -0
- package/src/webgpu/compute/wavefront/structs.js +155 -0
- package/src/webgpu/constants.js +62 -0
- package/src/webgpu/denoise/OIDNDenoiser.js +431 -0
- package/src/webgpu/index.d.ts +176 -0
- package/src/webgpu/index.js +11 -0
- package/src/webgpu/materials/GltfCompliantMaterial.js +608 -0
- package/src/webgpu/materials/GraphMaterial.js +331 -0
- package/src/webgpu/materials/PathtracingMaterial.js +89 -0
- package/src/webgpu/materials/RenderToScreenMaterial.js +150 -0
- package/src/webgpu/materials/debug/AtlasDebugMaterial.js +41 -0
- package/src/webgpu/materials/debug/SampleDensityMaterial.js +61 -0
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +1037 -0
- package/src/webgpu/nodes/debugBounds.wgsl.js +228 -0
- package/src/webgpu/nodes/eon.wgsl.js +262 -0
- package/src/webgpu/nodes/ggx.wgsl.js +190 -0
- package/src/webgpu/nodes/lights.wgsl.js +203 -0
- package/src/webgpu/nodes/material.wgsl.js +990 -0
- package/src/webgpu/nodes/rand/bluedither.wgsl.js +92 -0
- package/src/webgpu/nodes/rand/pcg.wgsl.js +81 -0
- package/src/webgpu/nodes/rand/sobol.wgsl.js +287 -0
- package/src/webgpu/nodes/random.wgsl.js +20 -0
- package/src/webgpu/nodes/reset.wgsl.js +21 -0
- package/src/webgpu/nodes/sampling.wgsl.js +193 -0
- package/src/webgpu/nodes/sheen.wgsl.js +148 -0
- package/src/webgpu/nodes/structs.wgsl.js +272 -0
- package/src/webgpu/nodes/utils.wgsl.js +366 -0
- package/src/webgpu/shims/ArrayCameraShim.js +74 -0
- package/src/webgpu/shims/EquirectCameraShim.js +37 -0
- package/src/webgpu/shims/PhysicalCameraShim.js +151 -0
- 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
|
+
` );
|