rayzee 6.5.0 → 7.1.0
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 +24 -5
- package/dist/rayzee.es.js +7624 -7063
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +157 -236
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +26 -9
- package/src/PathTracerApp.js +118 -26
- package/src/Pipeline/PipelineContext.js +1 -2
- package/src/Pipeline/RenderPipeline.js +1 -1
- package/src/Pipeline/RenderStage.js +1 -1
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/GeometryExtractor.js +6 -0
- package/src/Processor/KernelManager.js +277 -0
- package/src/Processor/PackedRayBuffer.js +291 -0
- package/src/Processor/QueueManager.js +173 -0
- package/src/Processor/SceneProcessor.js +1 -0
- package/src/Processor/ShaderBuilder.js +11 -317
- package/src/Processor/StorageTexturePool.js +29 -15
- package/src/Processor/VRAMTracker.js +169 -0
- package/src/Processor/utils.js +11 -110
- package/src/RenderSettings.js +0 -3
- package/src/Stages/ASVGF.js +151 -78
- package/src/Stages/BilateralFilter.js +34 -10
- package/src/Stages/EdgeFilter.js +2 -3
- package/src/Stages/MotionVector.js +16 -9
- package/src/Stages/NormalDepth.js +17 -5
- package/src/Stages/PathTracer.js +671 -1456
- package/src/Stages/PathTracerStage.js +1451 -0
- package/src/Stages/SSRC.js +32 -15
- package/src/Stages/Variance.js +35 -12
- package/src/TSL/CompactKernel.js +110 -0
- package/src/TSL/DebugKernel.js +98 -0
- package/src/TSL/Environment.js +13 -11
- package/src/TSL/ExtendKernel.js +75 -0
- package/src/TSL/FinalWriteKernel.js +121 -0
- package/src/TSL/GenerateKernel.js +111 -0
- package/src/TSL/LightsSampling.js +2 -2
- package/src/TSL/PathTracerCore.js +43 -1039
- package/src/TSL/ShadeKernel.js +876 -0
- package/src/TSL/patches.js +81 -4
- package/src/index.js +3 -0
- package/src/managers/CameraManager.js +1 -1
- package/src/managers/DenoisingManager.js +40 -75
- package/src/managers/EnvironmentManager.js +30 -39
- package/src/managers/OverlayManager.js +7 -22
- package/src/managers/UniformManager.js +0 -3
- package/src/managers/helpers/TileHelper.js +2 -2
- package/src/Stages/AdaptiveSampling.js +0 -483
- package/src/TSL/PathTracer.js +0 -384
- package/src/managers/TileManager.js +0 -298
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueueManager.js — wavefront ray queues: active indices (ping-pong), sorted indices, atomic counters.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { storage } from 'three/tsl';
|
|
6
|
+
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
7
|
+
|
|
8
|
+
/** Counter indices — must match ResetCounters kernel */
|
|
9
|
+
export const COUNTER = {
|
|
10
|
+
ACTIVE_RAY_COUNT: 0,
|
|
11
|
+
// rays entering current bounce; snapshotted before ACTIVE_RAY_COUNT reset so over-sized dispatch is safe.
|
|
12
|
+
ENTERING_COUNT: 1,
|
|
13
|
+
COUNT: 2,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Ray flag bits packed into rayBounceFlags (uint) */
|
|
17
|
+
export const RAY_FLAG = {
|
|
18
|
+
BOUNCE_MASK: 0xFF, // bits 0-7: bounce count (0-255)
|
|
19
|
+
ACTIVE: 1 << 8, // bit 8: ray is alive
|
|
20
|
+
SPECULAR: 1 << 9, // bit 9: last bounce was specular
|
|
21
|
+
INSIDE_MEDIUM: 1 << 10, // bit 10: ray is inside a transmissive medium
|
|
22
|
+
// bits 11-15: ray type
|
|
23
|
+
RAY_TYPE_SHIFT: 11,
|
|
24
|
+
RAY_TYPE_MASK: 0x1F << 11,
|
|
25
|
+
// bits 16-31: spare per-ray state carried across bounces
|
|
26
|
+
HAS_HIT_OPAQUE: 1 << 16, // bit 16: ray chain has hit non-transmissive geometry (transparent-bg alpha; megakernel hasHitOpaqueSurface)
|
|
27
|
+
AUX_LOCKED: 1 << 17, // bit 17: OIDN aux (normal/albedo) locked onto first non-specular hit (megakernel auxLocked)
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export class QueueManager {
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {number} maxRays - Maximum number of rays (typically width * height)
|
|
34
|
+
*/
|
|
35
|
+
constructor( maxRays = 0 ) {
|
|
36
|
+
|
|
37
|
+
this.capacity = 0;
|
|
38
|
+
this.counters = null;
|
|
39
|
+
// A/B alternate: one read by current bounce, other written by compaction
|
|
40
|
+
this.activeIndices = null;
|
|
41
|
+
this.activeIndicesRO = null;
|
|
42
|
+
this.pingPong = 0; // 0 = read A / write B, 1 = read B / write A
|
|
43
|
+
|
|
44
|
+
if ( maxRays > 0 ) {
|
|
45
|
+
|
|
46
|
+
this.allocate( maxRays );
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// capacity must match RayBufferPool.allocatedCapacity
|
|
53
|
+
allocate( capacity ) {
|
|
54
|
+
|
|
55
|
+
this.dispose();
|
|
56
|
+
this.capacity = capacity;
|
|
57
|
+
|
|
58
|
+
// explicit attribute (not attributeArray) so it can be referenced for async readback
|
|
59
|
+
this._countersAttr = new StorageInstancedBufferAttribute( new Uint32Array( COUNTER.COUNT ), 1 );
|
|
60
|
+
this.counters = storage( this._countersAttr, 'uint' ).toAtomic();
|
|
61
|
+
|
|
62
|
+
// per-bounce ACTIVE_RAY_COUNT snapshots; read back async to size/skip late bounces next frame
|
|
63
|
+
this.MAX_BOUNCE_SNAPSHOTS = 32;
|
|
64
|
+
this._bounceCountsAttr = new StorageInstancedBufferAttribute(
|
|
65
|
+
new Uint32Array( this.MAX_BOUNCE_SNAPSHOTS ), 1,
|
|
66
|
+
);
|
|
67
|
+
this.bounceCounts = storage( this._bounceCountsAttr, 'uint' );
|
|
68
|
+
|
|
69
|
+
const attrA = new StorageInstancedBufferAttribute( new Uint32Array( capacity ), 1 );
|
|
70
|
+
const attrB = new StorageInstancedBufferAttribute( new Uint32Array( capacity ), 1 );
|
|
71
|
+
this._attrA = attrA;
|
|
72
|
+
this._attrB = attrB;
|
|
73
|
+
|
|
74
|
+
this.activeIndices = {
|
|
75
|
+
a: storage( attrA, 'uint' ),
|
|
76
|
+
b: storage( attrB, 'uint' ),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// RO reuses the same attribute so RW/RO share one GPU buffer
|
|
80
|
+
this.activeIndicesRO = {
|
|
81
|
+
a: storage( attrA, 'uint' ).toReadOnly(),
|
|
82
|
+
b: storage( attrB, 'uint' ).toReadOnly(),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
this.pingPong = 0;
|
|
86
|
+
|
|
87
|
+
const totalBytes = (
|
|
88
|
+
COUNTER.COUNT * 4 +
|
|
89
|
+
capacity * 4 * 2
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
console.log(
|
|
93
|
+
`QueueManager: Allocated capacity=${capacity}, ` +
|
|
94
|
+
`total=${( totalBytes / ( 1024 * 1024 ) ).toFixed( 1 )} MB`
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// returns true if reallocation occurred
|
|
100
|
+
resize( capacity ) {
|
|
101
|
+
|
|
102
|
+
if ( capacity <= this.capacity && this.capacity > 0 ) return false;
|
|
103
|
+
this.allocate( capacity );
|
|
104
|
+
return true;
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getCounters() {
|
|
109
|
+
|
|
110
|
+
return this.counters;
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getActiveReadRO() {
|
|
115
|
+
|
|
116
|
+
return this.pingPong === 0 ? this.activeIndicesRO.a : this.activeIndicesRO.b;
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// RW version for compaction input
|
|
121
|
+
getActiveRead() {
|
|
122
|
+
|
|
123
|
+
return this.pingPong === 0 ? this.activeIndices.a : this.activeIndices.b;
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getActiveWrite() {
|
|
128
|
+
|
|
129
|
+
return this.pingPong === 0 ? this.activeIndices.b : this.activeIndices.a;
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// raw attribute for `renderer.getArrayBufferAsync(...)` readback
|
|
134
|
+
getCountersAttribute() {
|
|
135
|
+
|
|
136
|
+
return this._countersAttr;
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getBounceCounts() {
|
|
141
|
+
|
|
142
|
+
return this.bounceCounts;
|
|
143
|
+
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getBounceCountsAttribute() {
|
|
147
|
+
|
|
148
|
+
return this._bounceCountsAttr;
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
swap() {
|
|
153
|
+
|
|
154
|
+
this.pingPong = 1 - this.pingPong;
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
resetPingPong() {
|
|
159
|
+
|
|
160
|
+
this.pingPong = 0;
|
|
161
|
+
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
dispose() {
|
|
165
|
+
|
|
166
|
+
this.counters = null;
|
|
167
|
+
this.activeIndices = null;
|
|
168
|
+
this.activeIndicesRO = null;
|
|
169
|
+
this.capacity = 0;
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
}
|
|
@@ -297,6 +297,7 @@ export class SceneProcessor {
|
|
|
297
297
|
// Store other extracted data
|
|
298
298
|
this.materials = extractedData.materials;
|
|
299
299
|
this.materialCount = this.materials.length; // Store material count for feature scanning
|
|
300
|
+
this.materialTriangleCounts = extractedData.materialTriangleCounts; // Per-material tri count for sort-bin remap
|
|
300
301
|
this.meshes = extractedData.meshes;
|
|
301
302
|
this.meshTriangleRanges = extractedData.meshTriangleRanges; // Per-mesh { start, count } for TLAS/BLAS
|
|
302
303
|
this.maps = extractedData.maps;
|
|
@@ -1,110 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ShaderBuilder.js
|
|
3
|
-
* Owns TSL shader graph construction and compute node management
|
|
4
|
-
* for the path tracing pipeline.
|
|
2
|
+
* ShaderBuilder.js — shared scene texture-node factory for the path tracer.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Texture nodes are created once and updated in-place via .value mutation
|
|
11
|
-
* to preserve compiled shader graph references.
|
|
4
|
+
* Creates the texture/storage nodes the wavefront kernels read (environment, material map
|
|
5
|
+
* arrays, previous-frame MRT, adaptive-sampling, gobo/IES) and configures the module-level
|
|
6
|
+
* shadow/alpha/gobo/IES shader state. Nodes are created once and updated in-place via
|
|
7
|
+
* .value mutation to preserve compiled shader-graph references.
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
|
-
import {
|
|
15
|
-
localId, workgroupId } from 'three/tsl';
|
|
16
|
-
import { TextureNode } from 'three/webgpu';
|
|
10
|
+
import { texture } from 'three/tsl';
|
|
17
11
|
import { LinearFilter, DataArrayTexture } from 'three';
|
|
18
|
-
import { pathTracerMain } from '../TSL/PathTracer.js';
|
|
19
12
|
import { setShadowAlbedoMaps, setAlphaShadowsUniform } from '../TSL/LightsDirect.js';
|
|
20
13
|
import { setGoboMapsTexture, setIESProfilesTexture } from '../TSL/LightsCore.js';
|
|
21
|
-
import { BuildTimer } from './BuildTimer.js';
|
|
22
|
-
|
|
23
|
-
const WG_SIZE = 8;
|
|
24
14
|
|
|
25
15
|
export class ShaderBuilder {
|
|
26
16
|
|
|
27
17
|
constructor() {
|
|
28
18
|
|
|
29
|
-
// Single compute node (no dual ping-pong — copy approach)
|
|
30
|
-
this.computeNode = null;
|
|
31
|
-
|
|
32
19
|
// Previous-frame texture nodes (sample from MRT RenderTarget)
|
|
33
20
|
this.prevColorTexNode = null;
|
|
34
21
|
this.prevNormalDepthTexNode = null;
|
|
35
22
|
this.prevAlbedoTexNode = null;
|
|
36
23
|
|
|
37
|
-
// Adaptive sampling texture (updated per-frame from context)
|
|
38
|
-
this.adaptiveSamplingTexNode = null;
|
|
39
|
-
|
|
40
|
-
// Tile offset uniforms — pixel origin of the active tile region
|
|
41
|
-
// Dispatch covers only tile-sized workgroups; offset maps them to image space
|
|
42
|
-
this.tileOffsetX = uniform( 0, 'int' );
|
|
43
|
-
this.tileOffsetY = uniform( 0, 'int' );
|
|
44
|
-
|
|
45
|
-
// Render dimensions for edge-workgroup bounds checking
|
|
46
|
-
this.renderWidth = uniform( 1920, 'int' );
|
|
47
|
-
this.renderHeight = uniform( 1080, 'int' );
|
|
48
|
-
|
|
49
|
-
// Dispatch dimensions
|
|
50
|
-
this._dispatchX = 0;
|
|
51
|
-
this._dispatchY = 0;
|
|
52
|
-
|
|
53
|
-
// Reused per-frame dispatchSize array — avoids GC pressure from
|
|
54
|
-
// allocating [x,y,z] on every setFullScreenDispatch/setTileDispatch call.
|
|
55
|
-
// WebGPUBackend only reads indices 0..2 of this array during compute dispatch.
|
|
56
|
-
this._dispatchSize = [ 0, 0, 1 ];
|
|
57
|
-
|
|
58
24
|
// Scene texture nodes cache (for in-place updates on model change)
|
|
59
25
|
this._sceneTextureNodes = null;
|
|
60
26
|
|
|
61
|
-
// Whether the GPU compute pipeline has been compiled (via a real dispatch).
|
|
62
|
-
// Reset on setupCompute() rebuilds and on dispose().
|
|
63
|
-
this._compiled = false;
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Creates the full compute shader graph from scratch.
|
|
69
|
-
*
|
|
70
|
-
* @param {Object} config
|
|
71
|
-
* @param {Object} config.stage - PathTracer instance
|
|
72
|
-
* @param {Object} config.storageTextures - StorageTexturePool
|
|
73
|
-
*/
|
|
74
|
-
setupCompute( config ) {
|
|
75
|
-
|
|
76
|
-
const { stage, storageTextures } = config;
|
|
77
|
-
|
|
78
|
-
const timer = new BuildTimer( 'setupCompute' );
|
|
79
|
-
|
|
80
|
-
timer.start( 'Create texture nodes' );
|
|
81
|
-
const textureNodes = this._createTextureNodes( stage, storageTextures );
|
|
82
|
-
timer.end( 'Create texture nodes' );
|
|
83
|
-
|
|
84
|
-
timer.start( 'Build compute node (TSL)' );
|
|
85
|
-
|
|
86
|
-
const width = storageTextures.renderWidth;
|
|
87
|
-
const height = storageTextures.renderHeight;
|
|
88
|
-
this._dispatchX = Math.ceil( width / WG_SIZE );
|
|
89
|
-
this._dispatchY = Math.ceil( height / WG_SIZE );
|
|
90
|
-
|
|
91
|
-
this.renderWidth.value = width;
|
|
92
|
-
this.renderHeight.value = height;
|
|
93
|
-
|
|
94
|
-
const writeTex = storageTextures.getWriteTextures();
|
|
95
|
-
|
|
96
|
-
this.computeNode = this._buildComputeNode(
|
|
97
|
-
stage, textureNodes,
|
|
98
|
-
writeTex.color, writeTex.normalDepth, writeTex.albedo
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
// New compute node → needs a fresh GPU pipeline compile
|
|
102
|
-
this._compiled = false;
|
|
103
|
-
|
|
104
|
-
timer.end( 'Build compute node (TSL)' );
|
|
105
|
-
|
|
106
|
-
timer.print();
|
|
107
|
-
|
|
108
27
|
}
|
|
109
28
|
|
|
110
29
|
updateSceneTextures( stage ) {
|
|
@@ -165,98 +84,10 @@ export class ShaderBuilder {
|
|
|
165
84
|
|
|
166
85
|
}
|
|
167
86
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if ( this.computeNode ) {
|
|
174
|
-
|
|
175
|
-
this._dispatchSize[ 0 ] = this._dispatchX;
|
|
176
|
-
this._dispatchSize[ 1 ] = this._dispatchY;
|
|
177
|
-
this.computeNode.dispatchSize = this._dispatchSize;
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
this.renderWidth.value = width;
|
|
182
|
-
this.renderHeight.value = height;
|
|
183
|
-
|
|
184
|
-
// Reset tile offset (full-screen)
|
|
185
|
-
this.tileOffsetX.value = 0;
|
|
186
|
-
this.tileOffsetY.value = 0;
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Set dispatch to cover only the active tile region.
|
|
192
|
-
* Adjusts dispatch count and tile offset so threads map directly to tile pixels.
|
|
193
|
-
* @param {number} offsetX - Tile origin X in pixels
|
|
194
|
-
* @param {number} offsetY - Tile origin Y in pixels
|
|
195
|
-
* @param {number} tileWidth - Tile width in pixels
|
|
196
|
-
* @param {number} tileHeight - Tile height in pixels
|
|
197
|
-
*/
|
|
198
|
-
setTileDispatch( offsetX, offsetY, tileWidth, tileHeight ) {
|
|
199
|
-
|
|
200
|
-
this.tileOffsetX.value = offsetX;
|
|
201
|
-
this.tileOffsetY.value = offsetY;
|
|
202
|
-
|
|
203
|
-
const dispatchX = Math.ceil( tileWidth / WG_SIZE );
|
|
204
|
-
const dispatchY = Math.ceil( tileHeight / WG_SIZE );
|
|
205
|
-
|
|
206
|
-
if ( this.computeNode ) {
|
|
207
|
-
|
|
208
|
-
this._dispatchSize[ 0 ] = dispatchX;
|
|
209
|
-
this._dispatchSize[ 1 ] = dispatchY;
|
|
210
|
-
this.computeNode.dispatchSize = this._dispatchSize;
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Reset dispatch to full-screen (no tiling).
|
|
218
|
-
*/
|
|
219
|
-
setFullScreenDispatch() {
|
|
220
|
-
|
|
221
|
-
this.tileOffsetX.value = 0;
|
|
222
|
-
this.tileOffsetY.value = 0;
|
|
223
|
-
|
|
224
|
-
if ( this.computeNode ) {
|
|
225
|
-
|
|
226
|
-
this._dispatchSize[ 0 ] = this._dispatchX;
|
|
227
|
-
this._dispatchSize[ 1 ] = this._dispatchY;
|
|
228
|
-
this.computeNode.dispatchSize = this._dispatchSize;
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Front-load GPU compute pipeline creation via a single dispatch.
|
|
236
|
-
*
|
|
237
|
-
* Three.js WebGPU has no `createComputePipelineAsync` path — compute
|
|
238
|
-
* pipelines always compile synchronously on first `renderer.compute(node)`.
|
|
239
|
-
* Calling this at build time (while a "Compiling shaders…" status is
|
|
240
|
-
* already visible) moves the stall off the first animate frame.
|
|
241
|
-
*
|
|
242
|
-
* The dispatch writes to ping-pong storage textures whose contents are
|
|
243
|
-
* discarded by the subsequent `reset()` (frame counter back to 0 →
|
|
244
|
-
* `hasPreviousAccumulated = 0` → prev textures are not read).
|
|
245
|
-
*
|
|
246
|
-
* @param {object} renderer - WebGPURenderer
|
|
247
|
-
*/
|
|
248
|
-
forceCompile( renderer ) {
|
|
249
|
-
|
|
250
|
-
if ( this._compiled || ! this.computeNode || ! renderer ) return;
|
|
251
|
-
|
|
252
|
-
this._compiled = true;
|
|
253
|
-
renderer.compute( this.computeNode );
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// ===== PRIVATE =====
|
|
258
|
-
|
|
259
|
-
_createTextureNodes( stage, storageTextures ) {
|
|
87
|
+
// Creates the shared scene texture nodes (env, material maps, prev-frame, adaptive, gobo, IES)
|
|
88
|
+
// + configures the module-level shadow/alpha/gobo/IES shader state read by the wavefront kernels.
|
|
89
|
+
// Call from setupMaterial before the kernels are built.
|
|
90
|
+
createSceneTextureNodes( stage, storageTextures ) {
|
|
260
91
|
|
|
261
92
|
const triStorage = stage.triangleStorageNode;
|
|
262
93
|
const bvhStorage = stage.bvhStorageNode;
|
|
@@ -270,14 +101,6 @@ export class ShaderBuilder {
|
|
|
270
101
|
|
|
271
102
|
const envTex = texture( stage.environment.environmentTexture );
|
|
272
103
|
|
|
273
|
-
// Adaptive sampling texture
|
|
274
|
-
const adaptiveSamplingTex = new TextureNode();
|
|
275
|
-
this.adaptiveSamplingTexNode = adaptiveSamplingTex;
|
|
276
|
-
|
|
277
|
-
// Environment importance sampling CDF — packed storage buffer
|
|
278
|
-
// Layout: [marginal (envResolution.y floats) | conditional (envResolution.x * envResolution.y floats)]
|
|
279
|
-
const envCDFStorage = stage.environment.envCDFStorageNode;
|
|
280
|
-
|
|
281
104
|
// Previous-frame texture nodes — initialized from readTarget textures
|
|
282
105
|
const readTextures = storageTextures.getReadTextures();
|
|
283
106
|
this.prevColorTexNode = texture( readTextures.color );
|
|
@@ -319,7 +142,7 @@ export class ShaderBuilder {
|
|
|
319
142
|
|
|
320
143
|
const result = {
|
|
321
144
|
triStorage, bvhStorage, matStorage, lightBufferStorage,
|
|
322
|
-
envTex,
|
|
145
|
+
envTex,
|
|
323
146
|
albedoMapsTex, normalMapsTex, bumpMapsTex,
|
|
324
147
|
metalnessMapsTex, roughnessMapsTex, emissiveMapsTex, displacementMapsTex,
|
|
325
148
|
goboMapsTex, iesProfilesTex,
|
|
@@ -330,141 +153,12 @@ export class ShaderBuilder {
|
|
|
330
153
|
|
|
331
154
|
}
|
|
332
155
|
|
|
333
|
-
/**
|
|
334
|
-
* Build a single compute node.
|
|
335
|
-
* Previous-frame reads use texture() nodes bound to MRT RenderTarget textures.
|
|
336
|
-
*/
|
|
337
|
-
_buildComputeNode( stage, textureNodes,
|
|
338
|
-
writeColorTex, writeNDTex, writeAlbedoTex ) {
|
|
339
|
-
|
|
340
|
-
const {
|
|
341
|
-
triStorage, bvhStorage, matStorage, lightBufferStorage,
|
|
342
|
-
envTex, adaptiveSamplingTex, envCDFStorage,
|
|
343
|
-
albedoMapsTex, normalMapsTex, bumpMapsTex,
|
|
344
|
-
metalnessMapsTex, roughnessMapsTex, emissiveMapsTex, displacementMapsTex,
|
|
345
|
-
} = textureNodes;
|
|
346
|
-
|
|
347
|
-
const tileOffsetX = this.tileOffsetX;
|
|
348
|
-
const tileOffsetY = this.tileOffsetY;
|
|
349
|
-
const renderWidth = this.renderWidth;
|
|
350
|
-
const renderHeight = this.renderHeight;
|
|
351
|
-
|
|
352
|
-
const prevColorTexNode = this.prevColorTexNode;
|
|
353
|
-
const prevNormalDepthTexNode = this.prevNormalDepthTexNode;
|
|
354
|
-
const prevAlbedoTexNode = this.prevAlbedoTexNode;
|
|
355
|
-
|
|
356
|
-
const computeFn = Fn( () => {
|
|
357
|
-
|
|
358
|
-
// Map thread to image-space pixel via tile offset
|
|
359
|
-
const gx = tileOffsetX.add( int( workgroupId.x ).mul( WG_SIZE ) ).add( int( localId.x ) );
|
|
360
|
-
const gy = tileOffsetY.add( int( workgroupId.y ).mul( WG_SIZE ) ).add( int( localId.y ) );
|
|
361
|
-
|
|
362
|
-
// Bounds check only needed for edge workgroups that overshoot render dimensions
|
|
363
|
-
If( gx.lessThan( renderWidth ).and( gy.lessThan( renderHeight ) ), () => {
|
|
364
|
-
|
|
365
|
-
const pixelCoord = vec2( float( gx ).add( 0.5 ), float( gy ).add( 0.5 ) );
|
|
366
|
-
|
|
367
|
-
pathTracerMain( {
|
|
368
|
-
pixelCoord,
|
|
369
|
-
writeColorTex, writeNDTex, writeAlbedoTex,
|
|
370
|
-
// Previous-frame textures from MRT RenderTarget (sampled via texture())
|
|
371
|
-
prevAccumTexture: prevColorTexNode,
|
|
372
|
-
prevNormalDepthTexture: prevNormalDepthTexNode,
|
|
373
|
-
prevAlbedoTexture: prevAlbedoTexNode,
|
|
374
|
-
resolution: stage.resolution,
|
|
375
|
-
frame: stage.frame,
|
|
376
|
-
samplesPerPixel: stage.samplesPerPixel,
|
|
377
|
-
visMode: stage.visMode,
|
|
378
|
-
cameraWorldMatrix: stage.cameraWorldMatrix,
|
|
379
|
-
cameraProjectionMatrixInverse: stage.cameraProjectionMatrixInverse,
|
|
380
|
-
cameraViewMatrix: stage.cameraViewMatrix,
|
|
381
|
-
cameraProjectionMatrix: stage.cameraProjectionMatrix,
|
|
382
|
-
bvhBuffer: bvhStorage,
|
|
383
|
-
triangleBuffer: triStorage,
|
|
384
|
-
materialBuffer: matStorage,
|
|
385
|
-
albedoMaps: albedoMapsTex,
|
|
386
|
-
normalMaps: normalMapsTex,
|
|
387
|
-
bumpMaps: bumpMapsTex,
|
|
388
|
-
metalnessMaps: metalnessMapsTex,
|
|
389
|
-
roughnessMaps: roughnessMapsTex,
|
|
390
|
-
emissiveMaps: emissiveMapsTex,
|
|
391
|
-
displacementMaps: displacementMapsTex,
|
|
392
|
-
directionalLightsBuffer: stage.directionalLightsBufferNode,
|
|
393
|
-
numDirectionalLights: stage.numDirectionalLights,
|
|
394
|
-
areaLightsBuffer: stage.areaLightsBufferNode,
|
|
395
|
-
numAreaLights: stage.numAreaLights,
|
|
396
|
-
pointLightsBuffer: stage.pointLightsBufferNode,
|
|
397
|
-
numPointLights: stage.numPointLights,
|
|
398
|
-
spotLightsBuffer: stage.spotLightsBufferNode,
|
|
399
|
-
numSpotLights: stage.numSpotLights,
|
|
400
|
-
envTexture: envTex,
|
|
401
|
-
environmentIntensity: stage.environmentIntensity,
|
|
402
|
-
envMatrix: stage.environmentMatrix,
|
|
403
|
-
envCDFBuffer: envCDFStorage,
|
|
404
|
-
envTotalSum: stage.envTotalSum,
|
|
405
|
-
envCompensationDelta: stage.envCompensationDelta,
|
|
406
|
-
envResolution: stage.envResolution,
|
|
407
|
-
enableEnvironmentLight: stage.enableEnvironment,
|
|
408
|
-
useEnvMapIS: stage.useEnvMapIS,
|
|
409
|
-
groundProjectionEnabled: stage.groundProjectionEnabled,
|
|
410
|
-
groundProjectionRadius: stage.groundProjectionRadius,
|
|
411
|
-
groundProjectionHeight: stage.groundProjectionHeight,
|
|
412
|
-
maxBounceCount: stage.maxBounces,
|
|
413
|
-
transmissiveBounces: stage.transmissiveBounces,
|
|
414
|
-
maxSubsurfaceSteps: stage.maxSubsurfaceSteps,
|
|
415
|
-
showBackground: stage.showBackground,
|
|
416
|
-
transparentBackground: stage.transparentBackground,
|
|
417
|
-
backgroundIntensity: stage.backgroundIntensity,
|
|
418
|
-
fireflyThreshold: stage.fireflyThreshold,
|
|
419
|
-
globalIlluminationIntensity: stage.globalIlluminationIntensity,
|
|
420
|
-
enableEmissiveTriangleSampling: stage.enableEmissiveTriangleSampling,
|
|
421
|
-
emissiveTriangleBuffer: lightBufferStorage,
|
|
422
|
-
emissiveTriangleCount: stage.emissiveTriangleCount,
|
|
423
|
-
emissiveTotalPower: stage.emissiveTotalPower,
|
|
424
|
-
emissiveBoost: stage.emissiveBoost,
|
|
425
|
-
emissiveVec4Offset: stage.emissiveVec4Offset,
|
|
426
|
-
lightBVHBuffer: lightBufferStorage,
|
|
427
|
-
lightBVHNodeCount: stage.lightBVHNodeCount,
|
|
428
|
-
debugVisScale: stage.debugVisScale,
|
|
429
|
-
enableAccumulation: stage.enableAccumulation,
|
|
430
|
-
hasPreviousAccumulated: stage.hasPreviousAccumulated,
|
|
431
|
-
accumulationAlpha: stage.accumulationAlpha,
|
|
432
|
-
cameraIsMoving: stage.cameraIsMoving,
|
|
433
|
-
useAdaptiveSampling: stage.useAdaptiveSampling,
|
|
434
|
-
adaptiveSamplingTexture: adaptiveSamplingTex,
|
|
435
|
-
adaptiveSamplingMin: stage.adaptiveSamplingMin,
|
|
436
|
-
adaptiveSamplingMax: stage.adaptiveSamplingMax,
|
|
437
|
-
enableDOF: stage.enableDOF,
|
|
438
|
-
focalLength: stage.focalLength,
|
|
439
|
-
aperture: stage.aperture,
|
|
440
|
-
focusDistance: stage.focusDistance,
|
|
441
|
-
sceneScale: stage.sceneScale,
|
|
442
|
-
apertureScale: stage.apertureScale,
|
|
443
|
-
anamorphicRatio: stage.anamorphicRatio,
|
|
444
|
-
} );
|
|
445
|
-
|
|
446
|
-
} );
|
|
447
|
-
|
|
448
|
-
} );
|
|
449
|
-
|
|
450
|
-
return computeFn().compute(
|
|
451
|
-
[ this._dispatchX, this._dispatchY, 1 ],
|
|
452
|
-
[ WG_SIZE, WG_SIZE, 1 ]
|
|
453
|
-
);
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
|
|
457
156
|
dispose() {
|
|
458
157
|
|
|
459
|
-
this.computeNode?.dispose();
|
|
460
|
-
|
|
461
|
-
this.computeNode = null;
|
|
462
158
|
this.prevColorTexNode = null;
|
|
463
159
|
this.prevNormalDepthTexNode = null;
|
|
464
160
|
this.prevAlbedoTexNode = null;
|
|
465
|
-
this.adaptiveSamplingTexNode = null;
|
|
466
161
|
this._sceneTextureNodes = null;
|
|
467
|
-
this._compiled = false;
|
|
468
162
|
|
|
469
163
|
}
|
|
470
164
|
|
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { StorageTexture, RenderTarget } from 'three/webgpu';
|
|
16
|
-
import { RGBAFormat, FloatType, LinearFilter, NearestFilter } from 'three';
|
|
16
|
+
import { RGBAFormat, FloatType, LinearFilter, NearestFilter, Box2, Vector2 } from 'three';
|
|
17
|
+
import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
17
18
|
|
|
18
|
-
function createWriteStorageTex(
|
|
19
|
+
function createWriteStorageTex() {
|
|
19
20
|
|
|
20
|
-
const tex = new StorageTexture(
|
|
21
|
+
const tex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
21
22
|
tex.type = FloatType;
|
|
22
23
|
tex.format = RGBAFormat;
|
|
23
24
|
tex.minFilter = LinearFilter;
|
|
@@ -44,6 +45,9 @@ export class StorageTexturePool {
|
|
|
44
45
|
this.renderWidth = 0;
|
|
45
46
|
this.renderHeight = 0;
|
|
46
47
|
|
|
48
|
+
// Reused srcRegion for copies out of the over-allocated StorageTextures.
|
|
49
|
+
this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
|
|
50
|
+
|
|
47
51
|
if ( width > 0 && height > 0 ) {
|
|
48
52
|
|
|
49
53
|
this.create( width, height );
|
|
@@ -59,10 +63,10 @@ export class StorageTexturePool {
|
|
|
59
63
|
this.renderWidth = width;
|
|
60
64
|
this.renderHeight = height;
|
|
61
65
|
|
|
62
|
-
// Write-only StorageTextures
|
|
63
|
-
this.writeColor = createWriteStorageTex(
|
|
64
|
-
this.writeNormalDepth = createWriteStorageTex(
|
|
65
|
-
this.writeAlbedo = createWriteStorageTex(
|
|
66
|
+
// Write-only StorageTextures allocated at max — never resized (see resize crash fix).
|
|
67
|
+
this.writeColor = createWriteStorageTex();
|
|
68
|
+
this.writeNormalDepth = createWriteStorageTex();
|
|
69
|
+
this.writeAlbedo = createWriteStorageTex();
|
|
66
70
|
|
|
67
71
|
// Readable MRT RenderTarget (3 color attachments, no depth/stencil)
|
|
68
72
|
this.readTarget = new RenderTarget( width, height, {
|
|
@@ -85,13 +89,22 @@ export class StorageTexturePool {
|
|
|
85
89
|
|
|
86
90
|
ensureSize( width, height ) {
|
|
87
91
|
|
|
88
|
-
if (
|
|
92
|
+
if ( ! this.writeColor ) {
|
|
89
93
|
|
|
90
94
|
this.create( width, height );
|
|
91
95
|
return true;
|
|
92
96
|
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
if ( this.renderWidth !== width || this.renderHeight !== height ) {
|
|
100
|
+
|
|
101
|
+
// Resize only the readTarget — never dispose/recreate the write StorageTextures
|
|
102
|
+
// (that destroys the texture the compute bind group holds → submit crash).
|
|
103
|
+
this.setSize( width, height );
|
|
104
|
+
return true;
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
95
108
|
return false;
|
|
96
109
|
|
|
97
110
|
}
|
|
@@ -131,9 +144,13 @@ export class StorageTexturePool {
|
|
|
131
144
|
*/
|
|
132
145
|
copyToReadTargets( renderer ) {
|
|
133
146
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
// Source write StorageTextures are over-allocated at the max size; the Box2 region
|
|
148
|
+
// restricts the copy to the active render size so source and destination extents match.
|
|
149
|
+
this._srcRegion.max.set( this.renderWidth, this.renderHeight );
|
|
150
|
+
|
|
151
|
+
renderer.copyTextureToTexture( this.writeColor, this.readTarget.textures[ 0 ], this._srcRegion );
|
|
152
|
+
renderer.copyTextureToTexture( this.writeNormalDepth, this.readTarget.textures[ 1 ], this._srcRegion );
|
|
153
|
+
renderer.copyTextureToTexture( this.writeAlbedo, this.readTarget.textures[ 2 ], this._srcRegion );
|
|
137
154
|
|
|
138
155
|
}
|
|
139
156
|
|
|
@@ -163,10 +180,7 @@ export class StorageTexturePool {
|
|
|
163
180
|
this.renderWidth = width;
|
|
164
181
|
this.renderHeight = height;
|
|
165
182
|
|
|
166
|
-
|
|
167
|
-
this.writeNormalDepth?.setSize( width, height );
|
|
168
|
-
this.writeAlbedo?.setSize( width, height );
|
|
169
|
-
|
|
183
|
+
// Write StorageTextures stay at max allocation — never resized (see resize crash fix).
|
|
170
184
|
if ( this.readTarget ) {
|
|
171
185
|
|
|
172
186
|
this.readTarget.setSize( width, height );
|