three-gpu-pathtracer 0.0.23 → 0.0.25
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 -528
- package/build/index.module.js +319 -66
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +318 -64
- package/build/index.umd.cjs.map +1 -1
- package/package.json +35 -18
- package/src/core/PathTracingSceneGenerator.js +21 -2
- package/src/core/WebGLPathTracer.js +16 -7
- package/src/core/utils/mergeGeometries.js +14 -1
- package/src/core/utils/sceneUpdateUtils.js +8 -12
- package/src/detectors/PrecisionMaterial.js +9 -9
- package/src/index.d.ts +2 -1
- package/src/index.js +1 -0
- package/src/materials/pathtracing/PhysicalPathTracingMaterial.js +2 -1
- package/src/materials/pathtracing/glsl/attenuate_hit_function.glsl.js +2 -1
- package/src/materials/pathtracing/glsl/camera_util_functions.glsl.js +1 -1
- package/src/materials/pathtracing/glsl/get_surface_record_function.glsl.js +2 -1
- package/src/materials/surface/AmbientOcclusionMaterial.js +2 -0
- package/src/objects/PhysicalSpotLight.js +2 -0
- package/src/shader/bsdf/bsdf_functions.glsl.js +0 -1
- package/src/shader/bvh/inside_fog_volume_function.glsl.js +1 -1
- package/src/shader/structs/material_struct.glsl.js +3 -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 +13 -10
- 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,111 @@
|
|
|
1
|
+
import { StorageBufferAttribute, StorageTexture, StructTypeNode } from 'three/webgpu';
|
|
2
|
+
import { storage, textureStore, globalId } from 'three/tsl';
|
|
3
|
+
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
4
|
+
import { ComputeKernel } from './ComputeKernel.js';
|
|
5
|
+
import { SAMPLE_COUNT_MASK, SAMPLE_DISPATCHED_FLAG } from '../constants.js';
|
|
6
|
+
|
|
7
|
+
// field order of the sample counter buffer, matching the struct below
|
|
8
|
+
export const SAMPLE_COUNTER_MIN = 0;
|
|
9
|
+
export const SAMPLE_COUNTER_MAX = 1;
|
|
10
|
+
export const SAMPLE_COUNTER_TOTAL_LO = 2;
|
|
11
|
+
export const SAMPLE_COUNTER_TOTAL_HI = 3;
|
|
12
|
+
export const SAMPLE_COUNTER_PIXEL_COUNT = 4;
|
|
13
|
+
export const SAMPLE_COUNTER_LENGTH = 5;
|
|
14
|
+
|
|
15
|
+
export const U32_RANGE = 4294967296;
|
|
16
|
+
|
|
17
|
+
// Reduction target for the sample counts. The total outgrows 32 bits, so it is split across
|
|
18
|
+
// "totalLo" and "totalHi" and reassembled on read.
|
|
19
|
+
export const sampleCountersStruct = new StructTypeNode( {
|
|
20
|
+
minSamples: { type: 'uint', atomic: true },
|
|
21
|
+
maxSamples: { type: 'uint', atomic: true },
|
|
22
|
+
totalLo: { type: 'uint', atomic: true },
|
|
23
|
+
totalHi: { type: 'uint', atomic: true },
|
|
24
|
+
pixelCount: { type: 'uint', atomic: true },
|
|
25
|
+
}, 'SampleCounters' );
|
|
26
|
+
|
|
27
|
+
// Clears the counters ahead of the tally. "minSamples" starts at the u32 max so the first
|
|
28
|
+
// "atomicMin" replaces it.
|
|
29
|
+
export class PrimeSampleCountersKernel extends ComputeKernel {
|
|
30
|
+
|
|
31
|
+
constructor() {
|
|
32
|
+
|
|
33
|
+
const params = {
|
|
34
|
+
counters: storage( new StorageBufferAttribute( 1, 1 ), sampleCountersStruct ),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const fn = wgslTagFn/* wgsl */`
|
|
38
|
+
fn compute() -> void {
|
|
39
|
+
|
|
40
|
+
atomicStore( &${ params.counters }.minSamples, 0xFFFFFFFFu );
|
|
41
|
+
atomicStore( &${ params.counters }.maxSamples, 0u );
|
|
42
|
+
atomicStore( &${ params.counters }.totalLo, 0u );
|
|
43
|
+
atomicStore( &${ params.counters }.totalHi, 0u );
|
|
44
|
+
atomicStore( &${ params.counters }.pixelCount, 0u );
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
super( fn( params ) );
|
|
50
|
+
|
|
51
|
+
this.defineUniformAccessors( params );
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Reduces the sample count target to a min, max, and total so the CPU reads back a few values
|
|
58
|
+
// instead of the whole texture.
|
|
59
|
+
export class TallySampleCountsKernel extends ComputeKernel {
|
|
60
|
+
|
|
61
|
+
constructor() {
|
|
62
|
+
|
|
63
|
+
const params = {
|
|
64
|
+
globalId: globalId,
|
|
65
|
+
counters: storage( new StorageBufferAttribute( 1, 1 ), sampleCountersStruct ),
|
|
66
|
+
sampleCountTarget: textureStore( new StorageTexture( 1, 1 ) ).toReadOnly(),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const fn = wgslTagFn/* wgsl */`
|
|
70
|
+
fn compute( globalId: vec3u ) -> void {
|
|
71
|
+
|
|
72
|
+
let targetDimensions = textureDimensions( ${ params.sampleCountTarget } );
|
|
73
|
+
if ( globalId.x >= targetDimensions.x || globalId.y >= targetDimensions.y ) {
|
|
74
|
+
|
|
75
|
+
return;
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// pixels without a camera ray aren't part of the image, so skip rather than count zero
|
|
80
|
+
let combinedField = textureLoad( ${ params.sampleCountTarget }, globalId.xy ).r;
|
|
81
|
+
if ( ( combinedField & ${ SAMPLE_DISPATCHED_FLAG }u ) == 0u ) {
|
|
82
|
+
|
|
83
|
+
return;
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let samples = combinedField & ${ SAMPLE_COUNT_MASK }u;
|
|
88
|
+
|
|
89
|
+
atomicMin( &${ params.counters }.minSamples, samples );
|
|
90
|
+
atomicMax( &${ params.counters }.maxSamples, samples );
|
|
91
|
+
atomicAdd( &${ params.counters }.pixelCount, 1u );
|
|
92
|
+
|
|
93
|
+
// the sum overflowed if it came out smaller than the value already there, so carry
|
|
94
|
+
// the wrap into "totalHi"
|
|
95
|
+
let previousTotal = atomicAdd( &${ params.counters }.totalLo, samples );
|
|
96
|
+
if ( previousTotal + samples < previousTotal ) {
|
|
97
|
+
|
|
98
|
+
atomicAdd( &${ params.counters }.totalHi, 1u );
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
super( fn( params ) );
|
|
106
|
+
|
|
107
|
+
this.defineUniformAccessors( params );
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { IndirectStorageBufferAttribute } from 'three/webgpu';
|
|
2
|
+
import { ComputeKernel } from './ComputeKernel.js';
|
|
3
|
+
import { storage, globalId } from 'three/tsl';
|
|
4
|
+
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
5
|
+
|
|
6
|
+
export class ZeroOutBufferKernel extends ComputeKernel {
|
|
7
|
+
|
|
8
|
+
constructor( options = {} ) {
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
type = 'u32',
|
|
12
|
+
} = options;
|
|
13
|
+
|
|
14
|
+
const params = {
|
|
15
|
+
globalId: globalId,
|
|
16
|
+
outputTarget: storage( new IndirectStorageBufferAttribute( 1, 1 ), type ),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const fn = wgslTagFn/* wgsl */`
|
|
20
|
+
fn compute( globalId: vec3u ) -> void {
|
|
21
|
+
|
|
22
|
+
${ params.outputTarget }[ globalId.x ] = 0;
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
super( fn( params ) );
|
|
28
|
+
|
|
29
|
+
this.defineUniformAccessors( {
|
|
30
|
+
target: params.outputTarget,
|
|
31
|
+
} );
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { StorageTexture } from 'three/webgpu';
|
|
2
|
+
import { ComputeKernel } from './ComputeKernel.js';
|
|
3
|
+
import { textureStore, globalId } from 'three/tsl';
|
|
4
|
+
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
5
|
+
|
|
6
|
+
export class ZeroOutKernel extends ComputeKernel {
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
|
|
10
|
+
const params = {
|
|
11
|
+
globalId: globalId,
|
|
12
|
+
outputTarget: textureStore( new StorageTexture( 1, 1 ) ).toWriteOnly(),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const fn = wgslTagFn/* wgsl */`
|
|
16
|
+
fn compute( globalId: vec3u ) -> void {
|
|
17
|
+
|
|
18
|
+
textureStore( ${ params.outputTarget }, globalId.xy, vec4( 0, 0, 0, 1 ) );
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
super( fn( params ) );
|
|
24
|
+
|
|
25
|
+
this.defineUniformAccessors( {
|
|
26
|
+
target: params.outputTarget,
|
|
27
|
+
} );
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { StorageBufferAttribute, StorageTexture } from 'three/webgpu';
|
|
2
|
+
import { ComputeKernel } from '../ComputeKernel.js';
|
|
3
|
+
import { uniform, storage, textureStore, globalId } from 'three/tsl';
|
|
4
|
+
import { proxy, proxyFn, wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
5
|
+
import { misHeuristicFn, weightedAlphaBlendFn } from '../../nodes/sampling.wgsl.js';
|
|
6
|
+
import { clampPathContributionFunc, isTerminatingScatterFunc } from '../../nodes/utils.wgsl.js';
|
|
7
|
+
import { TRANSMISSIVE_BACKGROUND_ENVIRONMENT, TRANSMISSIVE_BACKGROUND_OVERLAY, TRANSMISSIVE_BACKGROUND_TRANSPARENT } from '../../constants.js';
|
|
8
|
+
import {
|
|
9
|
+
rngInit, rand2, rand3,
|
|
10
|
+
RNG_INDEX_BACKGROUND_SAMPLE,
|
|
11
|
+
RNG_INDEX_DIRECT_LIGHT_SAMPLE,
|
|
12
|
+
} from '../../nodes/random.wgsl.js';
|
|
13
|
+
import { ENVIRONMENT_LIGHT_TYPE, LIGHT_FAR_DISTANCE, isMISWeightLightFn } from '../../nodes/lights.wgsl.js';
|
|
14
|
+
import { lightRecordStruct, scatterRecordStruct } from '../../nodes/structs.wgsl.js';
|
|
15
|
+
import { rayDataStruct, intersectionResultStruct } from './structs.js';
|
|
16
|
+
import { SAMPLE_COUNT_MASK, SAMPLE_DISPATCHED_FLAG } from '../../constants.js';
|
|
17
|
+
|
|
18
|
+
// Path logic over the persistent slot pool: resolves the previous frame's shadow and bounce trace
|
|
19
|
+
// results, accumulates emission and NEE / forward MIS contributions, terminates finished paths into
|
|
20
|
+
// the output, and samples the next NEE light for MaterialKernel. Touches no BVH data.
|
|
21
|
+
export class LogicKernel extends ComputeKernel {
|
|
22
|
+
|
|
23
|
+
constructor( ) {
|
|
24
|
+
|
|
25
|
+
const params = {
|
|
26
|
+
envInfo: { value: null },
|
|
27
|
+
backgroundInfo: { value: null },
|
|
28
|
+
lightsInfo: { value: null },
|
|
29
|
+
|
|
30
|
+
// targets
|
|
31
|
+
prevOutputTarget: textureStore( new StorageTexture( 1, 1 ) ).toReadOnly(),
|
|
32
|
+
outputTarget: textureStore( new StorageTexture( 1, 1 ) ).toWriteOnly(),
|
|
33
|
+
sampleCountTarget: textureStore( new StorageTexture( 1, 1 ) ).toReadWrite(),
|
|
34
|
+
|
|
35
|
+
// settings
|
|
36
|
+
misEnabled: uniform( 1, 'uint' ),
|
|
37
|
+
rayCount: uniform( 0, 'uint' ),
|
|
38
|
+
maxBounces: uniform( 5, 'uint' ),
|
|
39
|
+
clampDirect: uniform( 0 ),
|
|
40
|
+
clampIndirect: uniform( 10 ),
|
|
41
|
+
transmissiveBackground: uniform( TRANSMISSIVE_BACKGROUND_OVERLAY ),
|
|
42
|
+
|
|
43
|
+
rayDataStorage: storage( new StorageBufferAttribute( 1, 1 ), rayDataStruct ),
|
|
44
|
+
rayIntersectionsStorage: storage( new StorageBufferAttribute( 1, 1 ), intersectionResultStruct ),
|
|
45
|
+
shadowRayIntersectionsStorage: storage( new StorageBufferAttribute( 1, 1 ), intersectionResultStruct ),
|
|
46
|
+
|
|
47
|
+
globalId: globalId,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// environment + background resources pulled off their providers (embedded functions)
|
|
51
|
+
const envTotalSumNode = proxy( 'envInfo.value.totalSumNode', params );
|
|
52
|
+
const envIntensityNode = proxy( 'envInfo.value.intensityNode', params );
|
|
53
|
+
const sampleEnvColor = proxy( 'envInfo.value.sampleColor', params );
|
|
54
|
+
const sampleEnvDir = proxy( 'envInfo.value.sampleDir', params );
|
|
55
|
+
const getEnvDirPdf = proxy( 'envInfo.value.getDirPdf', params );
|
|
56
|
+
const sampleBackground = proxy( 'backgroundInfo.value.sampleColor', params );
|
|
57
|
+
|
|
58
|
+
// analytic scene lights pulled off the lightsInfo provider (LightsInfoNode)
|
|
59
|
+
const lightsCountNode = proxy( 'lightsInfo.value.countNode', params );
|
|
60
|
+
const randomLightSampleFn = proxyFn( 'lightsInfo.value.randomLightSample', params );
|
|
61
|
+
const intersectLightAtIndexFn = proxyFn( 'lightsInfo.value.intersectLightAtIndex', params );
|
|
62
|
+
|
|
63
|
+
const fn = wgslTagFn/* wgsl */`
|
|
64
|
+
|
|
65
|
+
fn compute(
|
|
66
|
+
// settings
|
|
67
|
+
misEnabled: u32,
|
|
68
|
+
rayCount: u32,
|
|
69
|
+
maxBounces: u32,
|
|
70
|
+
clampDirect: f32,
|
|
71
|
+
clampIndirect: f32,
|
|
72
|
+
transmissiveBackground: u32,
|
|
73
|
+
|
|
74
|
+
globalId: vec3u
|
|
75
|
+
) -> void {
|
|
76
|
+
|
|
77
|
+
let rayDataStorage = &${ params.rayDataStorage };
|
|
78
|
+
let rayIntersectionsStorage = &${ params.rayIntersectionsStorage };
|
|
79
|
+
let shadowRayIntersectionsStorage = &${ params.shadowRayIntersectionsStorage };
|
|
80
|
+
|
|
81
|
+
// bound by "rayCount" rather than the pool length. See MaterialKernel
|
|
82
|
+
let index = globalId.x;
|
|
83
|
+
if ( index >= rayCount ) {
|
|
84
|
+
|
|
85
|
+
return;
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// skip slots that have never spawned a ray
|
|
90
|
+
let input = ( *rayDataStorage )[ index ];
|
|
91
|
+
if ( input.rayIntersectionIndex < 0 ) {
|
|
92
|
+
|
|
93
|
+
return;
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let indexUV = vec2u( input.pixelIndex >> 16, input.pixelIndex & 0xFFFF );
|
|
98
|
+
${ rngInit }( indexUV, input.seed, input.currentBounce + input.alphaDepth );
|
|
99
|
+
|
|
100
|
+
// one-sample NEE selection normalization (lights + env), matched with the megakernel
|
|
101
|
+
let envActive = ${ envTotalSumNode } > 0.0 && ${ envIntensityNode } > 0.0;
|
|
102
|
+
let lightsCount = ${ lightsCountNode };
|
|
103
|
+
var lightsDenom = f32( lightsCount );
|
|
104
|
+
if ( envActive ) {
|
|
105
|
+
|
|
106
|
+
lightsDenom += 1.0;
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var resultColor = input.resultColor;
|
|
111
|
+
var throughputColor = input.throughputColor;
|
|
112
|
+
|
|
113
|
+
// resolve the previous surface's NEE shadow ray (pre-scatter throughput). The index
|
|
114
|
+
// is negative when no shadow ray was enqueued last frame
|
|
115
|
+
if ( input.shadowRayIntersectionIndex >= 0 && input.lightPdf > 0.0 ) {
|
|
116
|
+
|
|
117
|
+
let shadowHit = ( *shadowRayIntersectionsStorage )[ u32( input.shadowRayIntersectionIndex ) ];
|
|
118
|
+
let occluded = shadowHit.objectIndex >= 0;
|
|
119
|
+
if ( ! occluded ) {
|
|
120
|
+
|
|
121
|
+
// env + area lights are also bsdf-sampled, so MIS-weight them; punctual take full weight
|
|
122
|
+
let misWeight = select( 1.0, ${ misHeuristicFn }( input.lightPdf, input.lightBsdfPdf ), ${ isMISWeightLightFn }( input.lightType ) );
|
|
123
|
+
let directLight = throughputColor * input.lightEmission * input.lightBsdf * misWeight / input.lightPdf;
|
|
124
|
+
let contribution = ${ clampPathContributionFunc }( directLight, input.currentBounce, clampDirect, clampIndirect );
|
|
125
|
+
resultColor += vec4f( contribution, 0.0 );
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// emission gathered at the previous surface (pre-scatter throughput). Found light clamps
|
|
132
|
+
// at the depth of the segment that found it, as in Cycles, so one less than the current
|
|
133
|
+
let emission = ${ clampPathContributionFunc }( throughputColor * input.emission, max( input.currentBounce, 1u ) - 1u, clampDirect, clampIndirect );
|
|
134
|
+
resultColor += vec4f( emission, 0.0 );
|
|
135
|
+
|
|
136
|
+
// reconstruct the scatter record staged by MaterialKernel
|
|
137
|
+
var scatterRec: ${ scatterRecordStruct };
|
|
138
|
+
scatterRec.color = input.scatterColor;
|
|
139
|
+
scatterRec.pdf = input.scatterPdf;
|
|
140
|
+
|
|
141
|
+
// the bounce limit, russian roulette, and terminating scatter checks all ran in
|
|
142
|
+
// MaterialKernel, which stages a zeroed pdf and skips the bounce trace when they fire
|
|
143
|
+
var isTerminated = all( throughputColor == vec3f( 0.0 ) ) || input.currentBounce >= maxBounces || ${ isTerminatingScatterFunc }( scatterRec );
|
|
144
|
+
|
|
145
|
+
if ( ! isTerminated ) {
|
|
146
|
+
|
|
147
|
+
// apply the scatter across the traced segment
|
|
148
|
+
throughputColor *= scatterRec.color / scatterRec.pdf;
|
|
149
|
+
|
|
150
|
+
let hitResult = ( *rayIntersectionsStorage )[ u32( input.rayIntersectionIndex ) ];
|
|
151
|
+
let didHit = hitResult.objectIndex >= 0;
|
|
152
|
+
let surfaceDist = select( ${ LIGHT_FAR_DISTANCE }, hitResult.dist, didHit );
|
|
153
|
+
|
|
154
|
+
// forward hits: a bsdf-sampled segment that lands on a area light. MIS-weighted
|
|
155
|
+
// only when NEE is also sampling the lights. The camera segment is skipped.
|
|
156
|
+
if ( input.currentBounce > 0u ) {
|
|
157
|
+
|
|
158
|
+
for ( var li = 0u; li < lightsCount; li ++ ) {
|
|
159
|
+
|
|
160
|
+
var lightRec: ${ lightRecordStruct };
|
|
161
|
+
if ( ${ intersectLightAtIndexFn }( input.origin, input.direction, li, &lightRec ) && lightRec.dist < surfaceDist ) {
|
|
162
|
+
|
|
163
|
+
var misWeight = 1.0;
|
|
164
|
+
if ( misEnabled != 0u ) {
|
|
165
|
+
|
|
166
|
+
let lightPdf = lightRec.pdf / lightsDenom;
|
|
167
|
+
misWeight = ${ misHeuristicFn }( input.scatterPdf, lightPdf );
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let lightHit = ${ clampPathContributionFunc }( lightRec.emission * throughputColor * misWeight, input.currentBounce, clampDirect, clampIndirect );
|
|
172
|
+
resultColor += vec4f( lightHit, 0.0 );
|
|
173
|
+
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if ( didHit ) {
|
|
181
|
+
|
|
182
|
+
// stage the hit for MaterialKernel
|
|
183
|
+
rayDataStorage[ index ].barycoord = hitResult.barycoord;
|
|
184
|
+
rayDataStorage[ index ].normal = hitResult.normal;
|
|
185
|
+
rayDataStorage[ index ].side = hitResult.side;
|
|
186
|
+
rayDataStorage[ index ].indices = hitResult.indices;
|
|
187
|
+
rayDataStorage[ index ].objectIndex = hitResult.objectIndex;
|
|
188
|
+
rayDataStorage[ index ].dist = hitResult.dist;
|
|
189
|
+
|
|
190
|
+
// next event estimation: pick one light or the environment with a single sample.
|
|
191
|
+
// MaterialKernel evaluates the bsdf and enqueues the shadow ray.
|
|
192
|
+
// TODO: importance-sample the selection by light intensity and solid angle
|
|
193
|
+
var lightPdf = 0.0;
|
|
194
|
+
if ( misEnabled != 0u && lightsDenom > 0.0 ) {
|
|
195
|
+
|
|
196
|
+
let ruv = ${ rand3 }( ${ RNG_INDEX_DIRECT_LIGHT_SAMPLE } );
|
|
197
|
+
let lightIndex = min( u32( ruv.x * lightsDenom ), u32( lightsDenom ) - 1u );
|
|
198
|
+
var lightRec: ${ lightRecordStruct };
|
|
199
|
+
if ( envActive && lightIndex == lightsCount ) {
|
|
200
|
+
|
|
201
|
+
// the environment, sampled from its CDF, as a light of kind ENVIRONMENT
|
|
202
|
+
let envSample = ${ sampleEnvDir }( ruv.yz );
|
|
203
|
+
lightRec.direction = envSample.direction;
|
|
204
|
+
lightRec.emission = envSample.color;
|
|
205
|
+
lightRec.pdf = envSample.pdf;
|
|
206
|
+
lightRec.dist = ${ LIGHT_FAR_DISTANCE };
|
|
207
|
+
lightRec.lightType = ${ ENVIRONMENT_LIGHT_TYPE };
|
|
208
|
+
|
|
209
|
+
} else {
|
|
210
|
+
|
|
211
|
+
lightRec = ${ randomLightSampleFn }( lightIndex, hitResult.position, ruv.yz );
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
lightPdf = lightRec.pdf / lightsDenom;
|
|
216
|
+
rayDataStorage[ index ].lightDirection = lightRec.direction;
|
|
217
|
+
rayDataStorage[ index ].lightEmission = lightRec.emission;
|
|
218
|
+
rayDataStorage[ index ].lightDist = lightRec.dist;
|
|
219
|
+
rayDataStorage[ index ].lightType = lightRec.lightType;
|
|
220
|
+
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
rayDataStorage[ index ].lightPdf = lightPdf;
|
|
224
|
+
|
|
225
|
+
} else {
|
|
226
|
+
|
|
227
|
+
// the segment escaped the scene: gather the environment for opaque paths, or
|
|
228
|
+
// the background for camera segments and fully transmissive paths
|
|
229
|
+
if ( input.currentBounce > 0u && input.isFullyTransmissive == 0u ) {
|
|
230
|
+
|
|
231
|
+
var misWeight = 1.0;
|
|
232
|
+
if ( misEnabled != 0u && envActive ) {
|
|
233
|
+
|
|
234
|
+
// match the env pdf scaling used by the NEE selection so the two estimators balance
|
|
235
|
+
let envPdf = ${ getEnvDirPdf }( input.direction ) / lightsDenom;
|
|
236
|
+
misWeight = ${ misHeuristicFn }( input.scatterPdf, envPdf );
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let environment = ${ sampleEnvColor }( input.direction ).rgb * throughputColor * misWeight;
|
|
241
|
+
let contribution = ${ clampPathContributionFunc }( environment, input.currentBounce, clampDirect, clampIndirect );
|
|
242
|
+
resultColor += vec4f( contribution, 0.0 );
|
|
243
|
+
|
|
244
|
+
} else {
|
|
245
|
+
|
|
246
|
+
// hit the background
|
|
247
|
+
// support multiple transparent background blending techniques
|
|
248
|
+
let rng = ${ rand2 }( ${ RNG_INDEX_BACKGROUND_SAMPLE } );
|
|
249
|
+
let bg = ${ sampleBackground }( input.direction, rng );
|
|
250
|
+
if ( input.currentBounce == 0u ) {
|
|
251
|
+
|
|
252
|
+
// sample the background directly if this is the primary ray
|
|
253
|
+
let background = ${ clampPathContributionFunc }( bg.a * bg.rgb, input.currentBounce, clampDirect, clampIndirect );
|
|
254
|
+
resultColor = vec4f( background, bg.a );
|
|
255
|
+
|
|
256
|
+
} else {
|
|
257
|
+
|
|
258
|
+
// transmissive ray handling
|
|
259
|
+
let env = ${ sampleEnvColor }( input.direction );
|
|
260
|
+
let avg = saturate( dot( throughputColor, vec3f( 1.0 / 3.0 ) ) );
|
|
261
|
+
let transparency = ( 1.0 - bg.a ) * avg;
|
|
262
|
+
|
|
263
|
+
var misWeight = 1.0;
|
|
264
|
+
if ( misEnabled != 0u && envActive ) {
|
|
265
|
+
|
|
266
|
+
let envPdf = ${ getEnvDirPdf }( input.direction );
|
|
267
|
+
misWeight = ${ misHeuristicFn }( input.scatterPdf, envPdf );
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if ( transmissiveBackground == ${ TRANSMISSIVE_BACKGROUND_ENVIRONMENT }u ) {
|
|
272
|
+
|
|
273
|
+
// display the env map through transmissive surfaces
|
|
274
|
+
let background = ${ clampPathContributionFunc }( env.rgb * throughputColor * misWeight, input.currentBounce, clampDirect, clampIndirect );
|
|
275
|
+
resultColor = vec4f(
|
|
276
|
+
resultColor.rgb + background,
|
|
277
|
+
1.0,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
} else if ( transmissiveBackground == ${ TRANSMISSIVE_BACKGROUND_TRANSPARENT }u ) {
|
|
281
|
+
|
|
282
|
+
// fade the background by the throughput color average
|
|
283
|
+
let background = ${ clampPathContributionFunc }( bg.a * bg.rgb * throughputColor * misWeight, input.currentBounce, clampDirect, clampIndirect );
|
|
284
|
+
resultColor = vec4f(
|
|
285
|
+
resultColor.rgb + background,
|
|
286
|
+
1.0 - transparency,
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
} else {
|
|
290
|
+
|
|
291
|
+
// fade the background by the throughput color average, mixing in env lighting
|
|
292
|
+
var light = mix( env.rgb, bg.rgb, bg.a ) * misWeight;
|
|
293
|
+
let background = ${ clampPathContributionFunc }( light * throughputColor, input.currentBounce, clampDirect, clampIndirect );
|
|
294
|
+
resultColor = vec4f(
|
|
295
|
+
resultColor.rgb + background,
|
|
296
|
+
1.0 - transparency,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
isTerminated = true;
|
|
306
|
+
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// the color rows are stored top down to match a rasterized render target
|
|
312
|
+
let colorIndex = vec2u( indexUV.x, textureDimensions( ${ params.outputTarget } ).y - 1u - indexUV.y );
|
|
313
|
+
let storedSamples = textureLoad( ${ params.sampleCountTarget }, indexUV ).r & ${ SAMPLE_COUNT_MASK }u;
|
|
314
|
+
|
|
315
|
+
if ( isTerminated ) {
|
|
316
|
+
|
|
317
|
+
// blend the finished sample into the output and free the slot for a new camera ray
|
|
318
|
+
let sampleCount = storedSamples + 1;
|
|
319
|
+
let prevColor = textureLoad( ${ params.prevOutputTarget }, colorIndex );
|
|
320
|
+
let blendedColor = ${ weightedAlphaBlendFn }( prevColor, resultColor, 1.0 / f32( sampleCount ) );
|
|
321
|
+
textureStore( ${ params.sampleCountTarget }, indexUV, vec4( ${ SAMPLE_DISPATCHED_FLAG }u | sampleCount ) );
|
|
322
|
+
textureStore( ${ params.outputTarget }, colorIndex, blendedColor );
|
|
323
|
+
|
|
324
|
+
rayDataStorage[ index ].objectIndex = - 1;
|
|
325
|
+
|
|
326
|
+
} else {
|
|
327
|
+
|
|
328
|
+
// show the path so far, which the first sample overwrites
|
|
329
|
+
if ( storedSamples == 0u ) {
|
|
330
|
+
|
|
331
|
+
textureStore( ${ params.outputTarget }, colorIndex, resultColor );
|
|
332
|
+
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
rayDataStorage[ index ].resultColor = resultColor;
|
|
336
|
+
rayDataStorage[ index ].throughputColor = throughputColor;
|
|
337
|
+
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
}
|
|
341
|
+
`;
|
|
342
|
+
|
|
343
|
+
super( fn( params ) );
|
|
344
|
+
|
|
345
|
+
this.defineUniformAccessors( params );
|
|
346
|
+
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
}
|