rayzee 11.5.0 → 11.7.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 +14 -14
- package/dist/{ArchiveImporter-29Nl1567.js → ArchiveImporter-DYcRXeKE.js} +2 -2
- package/dist/{BidirectionalIntegrator-CJay8tXK.js → BidirectionalIntegrator-BvjRpbTP.js} +289 -289
- package/dist/{EngineEvents-Ci8dVWCP.js → EngineEvents-CLckhYbL.js} +1 -0
- package/dist/{NeuralRetouch-KqGVPV7u.js → NeuralRetouch-ClaMiKYg.js} +1 -1
- package/dist/{NeuralSuperRes-BHxhYzlD.js → NeuralSuperRes-WNEXun9s.js} +2 -2
- package/dist/{PathTracer-Cldt0mXi.js → PathTracer-B9wZPPVL.js} +3197 -3167
- package/dist/{PhysicalSky-CUWrTcmT.js → PhysicalSky-DcjItqKi.js} +1 -1
- package/dist/{RayzeeRenderer-Dd-Dit0v.js → RayzeeRenderer-p-NeE6E2.js} +405 -437
- package/dist/addons/archives.es.js +1 -1
- package/dist/addons/bidirectional.es.js +1 -1
- package/dist/addons/physical-sky.es.js +1 -1
- package/dist/rayzee-core.es.js +5 -5
- package/dist/rayzee.es.js +1003 -1686
- package/dist/rayzee.umd.js +104 -143
- package/dist/{sceneCachePolicy--ta6moJu.js → sceneCachePolicy-B9w3BnaP.js} +2 -2
- package/package.json +2 -2
- package/src/EngineEvents.js +2 -0
- package/src/Passes/OIDNDenoiser.js +106 -100
- package/src/PathTracerApp.js +8 -19
- package/src/Pipeline/BindingAudit.js +2 -3
- package/src/Pipeline/PipelineContext.js +0 -1
- package/src/Pipeline/RenderPipeline.js +1 -2
- package/src/Pipeline/RenderStage.js +1 -1
- package/src/Processor/PackedRayBuffer.js +2 -2
- package/src/Processor/StorageTexturePool.js +1 -1
- package/src/Processor/TextureReadback.js +18 -1
- package/src/RayzeeRenderer.js +5 -5
- package/src/Stages/DenoiserSettings.js +5 -81
- package/src/Stages/MotionVector.js +21 -215
- package/src/Stages/NormalDepth.js +5 -5
- package/src/Stages/PathTracer.js +3 -11
- package/src/Stages/PathTracerStage.js +4 -17
- package/src/TSL/FinalWriteKernel.js +2 -2
- package/src/TSL/ShadeKernel.js +3 -3
- package/src/index.js +1 -1
- package/src/managers/DenoisingManager.js +93 -187
- package/src/Stages/ASVGF.js +0 -932
- package/src/Stages/BilateralFilter.js +0 -486
- package/src/Stages/EdgeFilter.js +0 -545
- package/src/Stages/Variance.js +0 -443
package/src/RayzeeRenderer.js
CHANGED
|
@@ -15,7 +15,7 @@ import { usesTextureCoordinates } from './Processor/GeometryExtractor.js';
|
|
|
15
15
|
import { MAX_STORAGE_TEXTURE_SIZE, MAX_RESERVABLE_RENDER_SIZE, setReservedRenderSize } from './Processor/StorageTexturePool.js';
|
|
16
16
|
import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples, disposeObjectFromMemory, disposeRenderer } from './Processor/utils.js';
|
|
17
17
|
import { BuildTimer } from './Processor/BuildTimer.js';
|
|
18
|
-
import { TextureReadback } from './Processor/TextureReadback.js';
|
|
18
|
+
import { TextureReadback, readPixels } from './Processor/TextureReadback.js';
|
|
19
19
|
import { createLogger, fmt } from './utils/Logger.js';
|
|
20
20
|
import { EngineEvents, LEGACY_EVENT_NAMES } from './EngineEvents.js';
|
|
21
21
|
import { IssueLog, ISSUE_CODES, EngineIssueError } from './EngineIssues.js';
|
|
@@ -2730,7 +2730,7 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
2730
2730
|
* accumulation
|
|
2731
2731
|
* @returns {Promise<{data: Float32Array|Uint8ClampedArray, width: number, height: number, colorSpace: string,
|
|
2732
2732
|
* source: string, toneMappedOn?: 'gpu'|'cpu'}>} `source` is what was read: `'accumulation'`, or the
|
|
2733
|
-
* denoiser that published it (`'oidn'`, `'
|
|
2733
|
+
* denoiser that published it (`'oidn'`, `'nrd'`).
|
|
2734
2734
|
* `toneMappedOn` is set for `'srgb'` only.
|
|
2735
2735
|
*/
|
|
2736
2736
|
async renderToBuffer( { colorSpace = 'srgb', preserveAlpha = false, source = 'accumulation' } = {} ) {
|
|
@@ -2766,14 +2766,14 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
2766
2766
|
const fn = ! gpu && tone.gain ? await tone.gain.cpu() : null;
|
|
2767
2767
|
const data = gpu ?? toneMapToRGBA8( shown
|
|
2768
2768
|
? await this._readTexture( shown.texture, width, height )
|
|
2769
|
-
: await this.renderer
|
|
2769
|
+
: await readPixels( this.renderer, target, width, height ), { ...tone, pixelGain: fn && { fn, width, height } } );
|
|
2770
2770
|
return { data, width, height, colorSpace, source: shown?.source ?? 'accumulation', toneMappedOn: gpu ? 'gpu' : 'cpu' };
|
|
2771
2771
|
|
|
2772
2772
|
}
|
|
2773
2773
|
|
|
2774
2774
|
const { data: linear, source: read } = source === 'display'
|
|
2775
2775
|
? await this._readDisplaySource( target, width, height )
|
|
2776
|
-
: { data: await this.renderer
|
|
2776
|
+
: { data: await readPixels( this.renderer, target, width, height ), source: 'accumulation' };
|
|
2777
2777
|
|
|
2778
2778
|
// `colorSpace` stays 'linear' — callers branch on it. `workingSpace` is the new, additive
|
|
2779
2779
|
// answer to "linear in what primaries", which only means something once a config is loaded.
|
|
@@ -3450,7 +3450,7 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
3450
3450
|
async _readDisplaySource( accumulation, width, height ) {
|
|
3451
3451
|
|
|
3452
3452
|
const shown = this._displaySource( accumulation );
|
|
3453
|
-
if ( ! shown ) return { data: await this.renderer
|
|
3453
|
+
if ( ! shown ) return { data: await readPixels( this.renderer, accumulation, width, height ), source: 'accumulation' };
|
|
3454
3454
|
return { data: await this._readTexture( shown.texture, width, height ), source: shown.source };
|
|
3455
3455
|
|
|
3456
3456
|
}
|
|
@@ -4,57 +4,12 @@
|
|
|
4
4
|
export const GBUFFER_MISS_DEPTH = 65504.0;
|
|
5
5
|
export const GBUFFER_MISS_THRESHOLD = 6e4;
|
|
6
6
|
|
|
7
|
-
// Albedo demodulation safety floor.
|
|
8
|
-
//
|
|
9
|
-
// only round-trip exactly when both sides agree.
|
|
7
|
+
// Albedo demodulation safety floor. Demod (`color / safeAlbedo`) and remod (`lighting * safeAlbedo`)
|
|
8
|
+
// only round-trip exactly when both sides use the same value.
|
|
10
9
|
export const ALBEDO_EPS = 0.01;
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// permissive. The adaptive temporal gradient (gradientStrength > 0) is always
|
|
15
|
-
// on: it measures real change in units of noise σ (gradientSigmaScale), so a
|
|
16
|
-
// static scene reads ~0 (no convergence penalty) and only moving lights / anim
|
|
17
|
-
// / disocclusion drop history. See ASVGF._buildGradientCompute.
|
|
18
|
-
low: {
|
|
19
|
-
temporalAlpha: 0.1,
|
|
20
|
-
gradientStrength: 0.8,
|
|
21
|
-
gradientSigmaScale: 2.5,
|
|
22
|
-
gradientNoiseFloor: 0.05,
|
|
23
|
-
atrousIterations: 3,
|
|
24
|
-
phiColor: 1.0,
|
|
25
|
-
phiNormal: 64.0,
|
|
26
|
-
phiDepth: 0.1,
|
|
27
|
-
phiLuminance: 6.0,
|
|
28
|
-
maxAccumFrames: 16,
|
|
29
|
-
varianceBoost: 0.5
|
|
30
|
-
},
|
|
31
|
-
medium: {
|
|
32
|
-
temporalAlpha: 0.03,
|
|
33
|
-
gradientStrength: 1.0,
|
|
34
|
-
gradientSigmaScale: 2.5,
|
|
35
|
-
gradientNoiseFloor: 0.05,
|
|
36
|
-
atrousIterations: 4,
|
|
37
|
-
phiColor: 0.5,
|
|
38
|
-
phiNormal: 128.0,
|
|
39
|
-
phiDepth: 0.05,
|
|
40
|
-
phiLuminance: 4.0,
|
|
41
|
-
maxAccumFrames: 64,
|
|
42
|
-
varianceBoost: 1.0
|
|
43
|
-
},
|
|
44
|
-
high: {
|
|
45
|
-
temporalAlpha: 0.0,
|
|
46
|
-
gradientStrength: 1.0,
|
|
47
|
-
gradientSigmaScale: 2.5,
|
|
48
|
-
gradientNoiseFloor: 0.05,
|
|
49
|
-
atrousIterations: 6,
|
|
50
|
-
phiColor: 0.3,
|
|
51
|
-
phiNormal: 256.0,
|
|
52
|
-
phiDepth: 0.02,
|
|
53
|
-
phiLuminance: 2.0,
|
|
54
|
-
maxAccumFrames: 128,
|
|
55
|
-
varianceBoost: 1.5
|
|
56
|
-
}
|
|
57
|
-
};
|
|
11
|
+
// The live view's denoisers, for setDenoiserStrategy.
|
|
12
|
+
export const DENOISER_STRATEGIES = Object.freeze( [ 'none', 'nrd', 'oidn' ] );
|
|
58
13
|
|
|
59
14
|
// normHitDist = hitDist / (A + B·viewZ). A constant, not a uniform: the Shade write and the NRD
|
|
60
15
|
// decode must agree, and the normalization is a pure round trip.
|
|
@@ -122,21 +77,9 @@ export const NRD_QUALITY_PRESETS = {
|
|
|
122
77
|
},
|
|
123
78
|
};
|
|
124
79
|
|
|
125
|
-
const ASVGF_MEDIUM = ASVGF_QUALITY_PRESETS.medium;
|
|
126
|
-
|
|
127
80
|
// What the live denoisers, the final OIDN pass and the AI upscaler start with; a host's own state seeds from these.
|
|
128
81
|
export const DENOISER_DEFAULTS = {
|
|
129
|
-
denoiserStrategy: 'none', // the live view's denoiser
|
|
130
|
-
|
|
131
|
-
// EdgeAware denoiser (spatial-only SVGF à-trous). filterStrength: final blend
|
|
132
|
-
// (0 = raw, 1 = filtered). edgeAtrousIterations: à-trous passes (step 1,2,4,8,16).
|
|
133
|
-
// edgePhiLuminance: variance-scaled luminance edge-stop. edgePhiNormal: normal cone
|
|
134
|
-
// exponent. edgePhiDepth: RELATIVE depth tolerance (fraction of ray distance).
|
|
135
|
-
filterStrength: 1.0,
|
|
136
|
-
edgeAtrousIterations: 5,
|
|
137
|
-
edgePhiLuminance: 4.0,
|
|
138
|
-
edgePhiNormal: 64.0,
|
|
139
|
-
edgePhiDepth: 0.1,
|
|
82
|
+
denoiserStrategy: 'none', // the live view's denoiser, one of DENOISER_STRATEGIES
|
|
140
83
|
|
|
141
84
|
enableOIDN: false,
|
|
142
85
|
oidnQuality: 'fast',
|
|
@@ -157,25 +100,6 @@ export const DENOISER_DEFAULTS = {
|
|
|
157
100
|
upscalerQuality: 'fast',
|
|
158
101
|
upscalerHdr: true,
|
|
159
102
|
|
|
160
|
-
// ASVGF runs its quality preset whenever it is switched on, so its values are that preset's.
|
|
161
|
-
enableASVGF: false,
|
|
162
|
-
asvgfQualityPreset: 'medium',
|
|
163
|
-
asvgfTemporalAlpha: ASVGF_MEDIUM.temporalAlpha,
|
|
164
|
-
asvgfAtrousIterations: ASVGF_MEDIUM.atrousIterations,
|
|
165
|
-
asvgfPhiColor: ASVGF_MEDIUM.phiColor,
|
|
166
|
-
asvgfPhiNormal: ASVGF_MEDIUM.phiNormal,
|
|
167
|
-
asvgfPhiDepth: ASVGF_MEDIUM.phiDepth,
|
|
168
|
-
asvgfPhiLuminance: ASVGF_MEDIUM.phiLuminance,
|
|
169
|
-
asvgfVarianceBoost: ASVGF_MEDIUM.varianceBoost,
|
|
170
|
-
asvgfMaxAccumFrames: ASVGF_MEDIUM.maxAccumFrames,
|
|
171
|
-
// Must be > 0: the gradient also rejects fireflies before the EMA smears them across
|
|
172
|
-
// ~1/alpha frames. At 0 the denoiser measures ~3x worse than no denoiser at 1 spp.
|
|
173
|
-
asvgfGradientStrength: ASVGF_MEDIUM.gradientStrength,
|
|
174
|
-
asvgfGradientSigmaScale: ASVGF_MEDIUM.gradientSigmaScale,
|
|
175
|
-
asvgfGradientNoiseFloor: ASVGF_MEDIUM.gradientNoiseFloor,
|
|
176
|
-
asvgfDebugMode: 0,
|
|
177
|
-
showAsvgfHeatmap: false,
|
|
178
|
-
|
|
179
103
|
// NRD (ReBLUR port) real-time denoiser — see NRD_DEFAULTS / NRD_QUALITY_PRESETS.
|
|
180
104
|
nrdQualityPreset: 'medium',
|
|
181
105
|
nrdDebugMode: 0,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fn, vec2,
|
|
1
|
+
import { Fn, vec2, vec4, float, int, uint, ivec2, uvec2, uniform, If,
|
|
2
2
|
textureLoad, textureStore, workgroupId, localId } from 'three/tsl';
|
|
3
3
|
import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
|
|
4
4
|
import { HalfFloatType, RGBAFormat, NearestFilter, Matrix4, Box2, Vector2 } from 'three';
|
|
@@ -9,14 +9,13 @@ import { cameraRayPoint, cameraRayUniforms } from '../TSL/CameraRay.js';
|
|
|
9
9
|
/**
|
|
10
10
|
* WebGPU Motion Vector Stage (Compute Shader)
|
|
11
11
|
*
|
|
12
|
-
* Computes per-pixel screen-space
|
|
13
|
-
*
|
|
14
|
-
* to the previous frame.
|
|
12
|
+
* Computes per-pixel screen-space motion vectors by reconstructing world
|
|
13
|
+
* positions from linear ray depth and reprojecting to the previous frame.
|
|
15
14
|
*
|
|
16
15
|
* Architecture (copy approach — proven working in PathTracer):
|
|
17
|
-
* 1.
|
|
16
|
+
* 1. A compute shader writes to a StorageTexture via textureStore
|
|
18
17
|
* 2. After dispatch, copyTextureToTexture transfers StorageTexture → RenderTarget
|
|
19
|
-
* 3. RenderTarget
|
|
18
|
+
* 3. The RenderTarget texture is published to context (NOT the StorageTexture —
|
|
20
19
|
* cross-dispatch reads from StorageTexture return zeros in Three.js WebGPU)
|
|
21
20
|
*
|
|
22
21
|
* Algorithm:
|
|
@@ -26,9 +25,8 @@ import { cameraRayPoint, cameraRayUniforms } from '../TSL/CameraRay.js';
|
|
|
26
25
|
* 4. Project to previous frame: prevVP * worldPos
|
|
27
26
|
* 5. Motion = currentUV - prevUV
|
|
28
27
|
*
|
|
29
|
-
* Output
|
|
28
|
+
* Output format (RGBA Float → copied to RGBA HalfFloat RenderTarget):
|
|
30
29
|
* screenSpace — xy=motion (UV-space), z=depth, w=validity
|
|
31
|
-
* worldSpace — xyz=world velocity, w=validity
|
|
32
30
|
*
|
|
33
31
|
* Critical design decisions:
|
|
34
32
|
* - matricesInitialized is NOT reset on pipeline:reset.
|
|
@@ -40,13 +38,8 @@ import { cameraRayPoint, cameraRayUniforms } from '../TSL/CameraRay.js';
|
|
|
40
38
|
*
|
|
41
39
|
* Execution: ALWAYS — motion vectors are needed every frame.
|
|
42
40
|
*
|
|
43
|
-
* Events listened:
|
|
44
|
-
* pipeline:reset — reset frame counter (but NOT matrices)
|
|
45
|
-
*
|
|
46
41
|
* Textures published:
|
|
47
42
|
* motionVector:screenSpace (from RenderTarget, not StorageTexture)
|
|
48
|
-
* motionVector:worldSpace (from RenderTarget, not StorageTexture)
|
|
49
|
-
* motionVector:motion (alias for screenSpace)
|
|
50
43
|
*
|
|
51
44
|
* Textures read:
|
|
52
45
|
* pathtracer:normalDepth — linear depth for world position reconstruction
|
|
@@ -72,8 +65,6 @@ export class MotionVector extends RenderStage {
|
|
|
72
65
|
this.currentViewProjectionMatrix = new Matrix4();
|
|
73
66
|
|
|
74
67
|
this.matricesInitialized = false;
|
|
75
|
-
this.isFirstFrame = true;
|
|
76
|
-
this.frameCount = 0;
|
|
77
68
|
|
|
78
69
|
// Camera uniforms for world position reconstruction
|
|
79
70
|
// Synced from PathTracer each frame (same source as NormalDepth)
|
|
@@ -81,9 +72,6 @@ export class MotionVector extends RenderStage {
|
|
|
81
72
|
this.cameraProjectionMatrixInverse = uniform( new Matrix4(), 'mat4' );
|
|
82
73
|
this.cameraRay = cameraRayUniforms();
|
|
83
74
|
this.prevVP = uniform( new Matrix4(), 'mat4' );
|
|
84
|
-
this.isFirstFrameU = uniform( 1.0 ); // 1.0 = true, 0.0 = false
|
|
85
|
-
this.deltaTime = uniform( 1.0 / 60.0 );
|
|
86
|
-
this.velocityScale = uniform( 1.0 );
|
|
87
75
|
|
|
88
76
|
// Resolution uniforms (for compute pixel coords)
|
|
89
77
|
this.resolutionWidth = uniform( width );
|
|
@@ -92,7 +80,7 @@ export class MotionVector extends RenderStage {
|
|
|
92
80
|
// Input texture node (swappable — no shader recompile)
|
|
93
81
|
this._normalDepthTexNode = new TextureNode();
|
|
94
82
|
|
|
95
|
-
// Write-only
|
|
83
|
+
// Write-only StorageTexture (compute output).
|
|
96
84
|
// Pre-allocate at max — StorageTexture.setSize() destroys the GPU texture
|
|
97
85
|
// while the compute bind group keeps the stale view (three.js #33061).
|
|
98
86
|
this._screenSpaceStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
@@ -101,35 +89,24 @@ export class MotionVector extends RenderStage {
|
|
|
101
89
|
this._screenSpaceStorageTex.minFilter = NearestFilter;
|
|
102
90
|
this._screenSpaceStorageTex.magFilter = NearestFilter;
|
|
103
91
|
|
|
104
|
-
|
|
105
|
-
this._worldSpaceStorageTex.type = HalfFloatType;
|
|
106
|
-
this._worldSpaceStorageTex.format = RGBAFormat;
|
|
107
|
-
this._worldSpaceStorageTex.minFilter = NearestFilter;
|
|
108
|
-
this._worldSpaceStorageTex.magFilter = NearestFilter;
|
|
109
|
-
|
|
110
|
-
// Reused per-copy: copy only the active region out of the over-alloc texs.
|
|
92
|
+
// Reused per-copy: copy only the active region out of the over-alloc tex.
|
|
111
93
|
this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
|
|
112
94
|
|
|
113
|
-
// Readable
|
|
114
|
-
|
|
95
|
+
// Readable RenderTarget (copy destination — published to context)
|
|
96
|
+
this.screenSpaceTarget = new RenderTarget( width, height, {
|
|
115
97
|
type: HalfFloatType,
|
|
116
98
|
format: RGBAFormat,
|
|
117
99
|
minFilter: NearestFilter,
|
|
118
100
|
magFilter: NearestFilter,
|
|
119
101
|
depthBuffer: false,
|
|
120
102
|
stencilBuffer: false
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
this.screenSpaceTarget = new RenderTarget( width, height, rtOpts );
|
|
124
|
-
this.worldSpaceTarget = new RenderTarget( width, height, rtOpts );
|
|
103
|
+
} );
|
|
125
104
|
|
|
126
|
-
// Dispatch dimensions (
|
|
105
|
+
// Dispatch dimensions (16x16 workgroups)
|
|
127
106
|
this._dispatchX = Math.ceil( width / 16 );
|
|
128
107
|
this._dispatchY = Math.ceil( height / 16 );
|
|
129
108
|
|
|
130
|
-
// Build compute nodes
|
|
131
109
|
this._buildScreenSpaceCompute();
|
|
132
|
-
this._buildWorldSpaceCompute();
|
|
133
110
|
|
|
134
111
|
}
|
|
135
112
|
|
|
@@ -222,95 +199,6 @@ export class MotionVector extends RenderStage {
|
|
|
222
199
|
|
|
223
200
|
}
|
|
224
201
|
|
|
225
|
-
/**
|
|
226
|
-
* World-space velocity compute shader.
|
|
227
|
-
*
|
|
228
|
-
* Approximates world-space velocity from UV displacement,
|
|
229
|
-
* scaled by deltaTime and velocityScale.
|
|
230
|
-
*/
|
|
231
|
-
_buildWorldSpaceCompute() {
|
|
232
|
-
|
|
233
|
-
const normalDepthTex = this._normalDepthTexNode;
|
|
234
|
-
const camWorldMat = this.cameraWorldMatrix;
|
|
235
|
-
const camProjInvMat = this.cameraProjectionMatrixInverse;
|
|
236
|
-
const prevVP = this.prevVP;
|
|
237
|
-
const isFirstFrameU = this.isFirstFrameU;
|
|
238
|
-
const deltaTime = this.deltaTime;
|
|
239
|
-
const velocityScale = this.velocityScale;
|
|
240
|
-
const resW = this.resolutionWidth;
|
|
241
|
-
const resH = this.resolutionHeight;
|
|
242
|
-
const outputTex = this._worldSpaceStorageTex;
|
|
243
|
-
|
|
244
|
-
const WG_SIZE = 16;
|
|
245
|
-
|
|
246
|
-
const computeFn = Fn( ( [ cwm, cpi ] ) => {
|
|
247
|
-
|
|
248
|
-
const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( localId.x ) );
|
|
249
|
-
const gy = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
|
|
250
|
-
|
|
251
|
-
If( gx.lessThan( int( resW ) ).and( gy.lessThan( int( resH ) ) ), () => {
|
|
252
|
-
|
|
253
|
-
const nd = textureLoad( normalDepthTex, ivec2( gx, gy ) );
|
|
254
|
-
const linearDepth = nd.w;
|
|
255
|
-
|
|
256
|
-
const result = vec4( 0.0, 0.0, 0.0, 0.0 ).toVar();
|
|
257
|
-
|
|
258
|
-
// Skip first frame and sky (depth >= 65504 sentinel)
|
|
259
|
-
If( isFirstFrameU.lessThan( 0.5 ).and( linearDepth.lessThan( float( 6e4 ) ) ), () => {
|
|
260
|
-
|
|
261
|
-
// Current pixel UV
|
|
262
|
-
const currentUV = vec2(
|
|
263
|
-
float( gx ).add( 0.5 ).div( resW ),
|
|
264
|
-
float( gy ).add( 0.5 ).div( resH )
|
|
265
|
-
);
|
|
266
|
-
const worldPos = cameraRayPoint( currentUV, linearDepth, cwm, cpi, this.cameraRay );
|
|
267
|
-
|
|
268
|
-
// Project to previous frame
|
|
269
|
-
const prevClip = prevVP.mul( vec4( worldPos, 1.0 ) );
|
|
270
|
-
// Flip prevUV.y to match WebGPU convention
|
|
271
|
-
const prevNDC = prevClip.xy.div( prevClip.w );
|
|
272
|
-
const prevUV = vec2(
|
|
273
|
-
prevNDC.x.mul( 0.5 ).add( 0.5 ),
|
|
274
|
-
prevNDC.y.mul( - 0.5 ).add( 0.5 )
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
const valid = prevUV.x.greaterThanEqual( 0.0 )
|
|
278
|
-
.and( prevUV.x.lessThanEqual( 1.0 ) )
|
|
279
|
-
.and( prevUV.y.greaterThanEqual( 0.0 ) )
|
|
280
|
-
.and( prevUV.y.lessThanEqual( 1.0 ) );
|
|
281
|
-
|
|
282
|
-
// World-space velocity approximation from UV displacement
|
|
283
|
-
const motionUV = currentUV.sub( prevUV );
|
|
284
|
-
const worldVelocity = vec3(
|
|
285
|
-
motionUV.x.div( deltaTime ).mul( velocityScale ),
|
|
286
|
-
motionUV.y.div( deltaTime ).mul( velocityScale ),
|
|
287
|
-
0.0
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
result.assign( valid.select(
|
|
291
|
-
vec4( worldVelocity, 1.0 ),
|
|
292
|
-
vec4( 0.0, 0.0, 0.0, 0.5 )
|
|
293
|
-
) );
|
|
294
|
-
|
|
295
|
-
} );
|
|
296
|
-
|
|
297
|
-
textureStore(
|
|
298
|
-
outputTex,
|
|
299
|
-
uvec2( uint( gx ), uint( gy ) ),
|
|
300
|
-
result
|
|
301
|
-
).toWriteOnly();
|
|
302
|
-
|
|
303
|
-
} );
|
|
304
|
-
|
|
305
|
-
} );
|
|
306
|
-
|
|
307
|
-
this._worldSpaceComputeNode = computeFn( camWorldMat, camProjInvMat ).compute(
|
|
308
|
-
[ this._dispatchX, this._dispatchY, 1 ],
|
|
309
|
-
[ WG_SIZE, WG_SIZE, 1 ]
|
|
310
|
-
);
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
|
|
314
202
|
// ──────────────────────────────────────────────────
|
|
315
203
|
// Camera matrix management
|
|
316
204
|
// ──────────────────────────────────────────────────
|
|
@@ -387,16 +275,6 @@ export class MotionVector extends RenderStage {
|
|
|
387
275
|
// Pipeline lifecycle
|
|
388
276
|
// ──────────────────────────────────────────────────
|
|
389
277
|
|
|
390
|
-
setupEventListeners() {
|
|
391
|
-
|
|
392
|
-
this.on( 'pipeline:reset', () => {
|
|
393
|
-
|
|
394
|
-
this.reset();
|
|
395
|
-
|
|
396
|
-
} );
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
|
|
400
278
|
render( context ) {
|
|
401
279
|
|
|
402
280
|
if ( ! this.enabled ) return;
|
|
@@ -408,11 +286,6 @@ export class MotionVector extends RenderStage {
|
|
|
408
286
|
// Update camera matrices (CPU-side) — synced from PathTracer
|
|
409
287
|
this._updateCameraMatrices();
|
|
410
288
|
|
|
411
|
-
// Update isFirstFrame uniform
|
|
412
|
-
this.isFirstFrameU.value = this.isFirstFrame ? 1.0 : 0.0;
|
|
413
|
-
|
|
414
|
-
this.frameCount ++;
|
|
415
|
-
|
|
416
289
|
// Auto-size to match normalDepth
|
|
417
290
|
const img = normalDepthTex.image;
|
|
418
291
|
if ( img && img.width > 0 && img.height > 0 ) {
|
|
@@ -429,67 +302,31 @@ export class MotionVector extends RenderStage {
|
|
|
429
302
|
// Swap input texture (no shader recompile, just swap value)
|
|
430
303
|
this._normalDepthTexNode.value = normalDepthTex;
|
|
431
304
|
|
|
432
|
-
// Dispatch screen-space motion vector compute
|
|
433
305
|
this.renderer.compute( this._screenSpaceComputeNode );
|
|
434
306
|
|
|
435
|
-
//
|
|
436
|
-
this.renderer.compute( this._worldSpaceComputeNode );
|
|
437
|
-
|
|
438
|
-
// Copy StorageTextures → RenderTargets (cross-dispatch reads from
|
|
307
|
+
// Copy StorageTexture → RenderTarget (cross-dispatch reads from
|
|
439
308
|
// StorageTexture return zeros — must use RenderTarget for downstream stages).
|
|
440
|
-
// srcRegion = active size;
|
|
309
|
+
// srcRegion = active size; the StorageTexture is over-allocated at 2048.
|
|
441
310
|
this._srcRegion.max.set( this.screenSpaceTarget.width, this.screenSpaceTarget.height );
|
|
442
311
|
this.renderer.copyTextureToTexture( this._screenSpaceStorageTex, this.screenSpaceTarget.texture, this._srcRegion );
|
|
443
|
-
this.renderer.copyTextureToTexture( this._worldSpaceStorageTex, this.worldSpaceTarget.texture, this._srcRegion );
|
|
444
312
|
|
|
445
|
-
// Publish RenderTarget textures to context
|
|
446
313
|
context.setTexture( 'motionVector:screenSpace', this.screenSpaceTarget.texture );
|
|
447
|
-
context.setTexture( 'motionVector:worldSpace', this.worldSpaceTarget.texture );
|
|
448
|
-
context.setTexture( 'motionVector:motion', this.screenSpaceTarget.texture );
|
|
449
|
-
|
|
450
|
-
// Emit
|
|
451
|
-
this.emit( 'motionvector:computed', {
|
|
452
|
-
frame: this.frameCount,
|
|
453
|
-
isFirstFrame: this.isFirstFrame
|
|
454
|
-
} );
|
|
455
|
-
|
|
456
|
-
this.isFirstFrame = false;
|
|
457
314
|
|
|
458
315
|
}
|
|
459
316
|
|
|
460
|
-
|
|
461
|
-
* Reset — intentionally does NOT reset matricesInitialized.
|
|
462
|
-
* Motion vectors must track camera motion across pipeline resets.
|
|
463
|
-
*/
|
|
464
|
-
// Free the 2048² StorageTextures when disabled (no consumer); three.js re-creates them on the next
|
|
317
|
+
// Free the 2048² StorageTexture when disabled (no consumer); three.js re-creates it on the next
|
|
465
318
|
// dispatch after re-enable. _syncGBufferStages additionally reseeds camera history on re-enable.
|
|
466
319
|
releaseGPUMemory() {
|
|
467
320
|
|
|
468
321
|
this._screenSpaceStorageTex?.dispose();
|
|
469
|
-
this._worldSpaceStorageTex?.dispose();
|
|
470
|
-
this.reset();
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
reset() {
|
|
475
|
-
|
|
476
|
-
if ( ! this.matricesInitialized ) {
|
|
477
|
-
|
|
478
|
-
this.isFirstFrame = true;
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
this.frameCount = 0;
|
|
483
322
|
|
|
484
323
|
}
|
|
485
324
|
|
|
486
325
|
setSize( width, height ) {
|
|
487
326
|
|
|
488
|
-
//
|
|
327
|
+
// The StorageTexture stays at its max allocation (see constructor).
|
|
489
328
|
this.screenSpaceTarget.setSize( width, height );
|
|
490
329
|
this.screenSpaceTarget.texture.needsUpdate = true;
|
|
491
|
-
this.worldSpaceTarget.setSize( width, height );
|
|
492
|
-
this.worldSpaceTarget.texture.needsUpdate = true;
|
|
493
330
|
this.resolutionWidth.value = width;
|
|
494
331
|
this.resolutionHeight.value = height;
|
|
495
332
|
|
|
@@ -502,57 +339,26 @@ export class MotionVector extends RenderStage {
|
|
|
502
339
|
|
|
503
340
|
}
|
|
504
341
|
|
|
505
|
-
if ( this._worldSpaceComputeNode ) {
|
|
506
|
-
|
|
507
|
-
this._worldSpaceComputeNode.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
|
|
511
342
|
}
|
|
512
343
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
this.velocityScale.value = scale;
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
setDeltaTime( dt ) {
|
|
520
|
-
|
|
521
|
-
this.deltaTime.value = dt;
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Reserved-storage change (e.g. 4K toggle): recreate the MAX-preallocated StorageTextures at the new live
|
|
526
|
-
// reserved size + rebuild the compute nodes in place. Same stage object, so refs/wiring stay valid.
|
|
344
|
+
// Reserved-storage change (e.g. 4K toggle): recreate the MAX-preallocated StorageTexture at the new live
|
|
345
|
+
// reserved size + rebuild the compute node in place. Same stage object, so refs/wiring stay valid.
|
|
527
346
|
reallocateReservedStorage() {
|
|
528
347
|
|
|
529
348
|
this._screenSpaceComputeNode?.dispose();
|
|
530
|
-
this._worldSpaceComputeNode?.dispose();
|
|
531
349
|
this._screenSpaceStorageTex?.dispose();
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const t = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
536
|
-
t.type = HalfFloatType; t.format = RGBAFormat; t.minFilter = NearestFilter; t.magFilter = NearestFilter;
|
|
537
|
-
return t;
|
|
538
|
-
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
this._screenSpaceStorageTex = mk();
|
|
542
|
-
this._worldSpaceStorageTex = mk();
|
|
350
|
+
const t = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
351
|
+
t.type = HalfFloatType; t.format = RGBAFormat; t.minFilter = NearestFilter; t.magFilter = NearestFilter;
|
|
352
|
+
this._screenSpaceStorageTex = t;
|
|
543
353
|
this._buildScreenSpaceCompute();
|
|
544
|
-
this._buildWorldSpaceCompute();
|
|
545
354
|
|
|
546
355
|
}
|
|
547
356
|
|
|
548
357
|
dispose() {
|
|
549
358
|
|
|
550
359
|
this._screenSpaceComputeNode?.dispose();
|
|
551
|
-
this._worldSpaceComputeNode?.dispose();
|
|
552
360
|
this._screenSpaceStorageTex?.dispose();
|
|
553
|
-
this._worldSpaceStorageTex?.dispose();
|
|
554
361
|
this.screenSpaceTarget?.dispose();
|
|
555
|
-
this.worldSpaceTarget?.dispose();
|
|
556
362
|
this._normalDepthTexNode?.dispose();
|
|
557
363
|
|
|
558
364
|
}
|
|
@@ -13,7 +13,7 @@ import { computeUVCache, processNormal, processBump, processMetalnessRoughness,
|
|
|
13
13
|
import { withSceneResources } from '../TSL/SceneResources.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* NormalDepth — primary-ray G-buffer for
|
|
16
|
+
* NormalDepth — primary-ray G-buffer for the denoisers' edge tests (NRD, the OIDN motion history).
|
|
17
17
|
*
|
|
18
18
|
* RGB = geometric world normal · 0.5 + 0.5, A = linear ray distance (sky=65504, the
|
|
19
19
|
* max HalfFloat value — a finite sentinel so miss−miss depth diffs stay 0, not Inf−Inf=NaN).
|
|
@@ -70,7 +70,7 @@ export class NormalDepth extends RenderStage {
|
|
|
70
70
|
this._outputStorageTex.magFilter = NearestFilter;
|
|
71
71
|
|
|
72
72
|
// Shading-normal output (geometric normal perturbed by normal/bump map).
|
|
73
|
-
// Single buffer —
|
|
73
|
+
// Single buffer — its readers (NRD, the OIDN history) use only the current frame.
|
|
74
74
|
this._shadingStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
75
75
|
this._shadingStorageTex.type = HalfFloatType;
|
|
76
76
|
this._shadingStorageTex.format = RGBAFormat;
|
|
@@ -137,7 +137,7 @@ export class NormalDepth extends RenderStage {
|
|
|
137
137
|
// _hasHistory is deliberately NOT cleared, mirroring MotionVector.reset(): the
|
|
138
138
|
// prev-frame G-buffer tracks camera motion, not accumulation, and a camera move
|
|
139
139
|
// resets accumulation every frame of a drag. Clearing it made render() overwrite
|
|
140
|
-
// prevRT with the current frame, so
|
|
140
|
+
// prevRT with the current frame, so the OIDN history could never see a disocclusion.
|
|
141
141
|
this._dirty = true;
|
|
142
142
|
|
|
143
143
|
} );
|
|
@@ -374,7 +374,7 @@ export class NormalDepth extends RenderStage {
|
|
|
374
374
|
this.renderer.copyTextureToTexture( this._shadingStorageTex, this._shadingRT.texture, this._srcRegion );
|
|
375
375
|
if ( this._leafRT ) this.renderer.copyTextureToTexture( this._leafStorageTex, this._leafRT.texture, this._srcRegion );
|
|
376
376
|
|
|
377
|
-
// First dispatch: seed prev from current so
|
|
377
|
+
// First dispatch: seed prev from current so the history doesn't see false
|
|
378
378
|
// disocclusion on frame 1.
|
|
379
379
|
if ( ! this._hasHistory ) {
|
|
380
380
|
|
|
@@ -393,7 +393,7 @@ export class NormalDepth extends RenderStage {
|
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
// Free the 2048² StorageTextures when disabled (no consumer); three.js re-creates them on the next
|
|
396
|
-
// dispatch after re-enable, and reset() re-arms the dirty/history fast-path. See
|
|
396
|
+
// dispatch after re-enable, and reset() re-arms the dirty/history fast-path. See NRD.releaseGPUMemory.
|
|
397
397
|
releaseGPUMemory() {
|
|
398
398
|
|
|
399
399
|
this._outputStorageTex?.dispose();
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '../TSL/SortGlobalKernels.js';
|
|
29
29
|
import { ENGINE_DEFAULTS } from '../EngineDefaults.js';
|
|
30
30
|
import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
|
|
31
|
+
import { readPixels } from '../Processor/TextureReadback.js';
|
|
31
32
|
import { ISSUE_CODES } from '../EngineIssues.js';
|
|
32
33
|
import { createLogger, fmt } from '../utils/Logger.js';
|
|
33
34
|
import { deviceMemoryGB } from '../Processor/HostMemory.js';
|
|
@@ -76,7 +77,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
76
77
|
this._auxGBufferUniform = uniform( 0, 'uint' );
|
|
77
78
|
// Clean-aux mode: temporally accumulate + renormalize the aux NORMAL (like albedo) so a clean-aux
|
|
78
79
|
// OIDN model (calb_cnrm/high, alb_nrm/balanced) gets the prefiltered-ish normal it expects instead
|
|
79
|
-
// of the point-sampled per-frame value that leaks noise. Off
|
|
80
|
+
// of the point-sampled per-frame value that leaks noise. Off otherwise: the fast model wants the bump normal.
|
|
80
81
|
this._cleanAuxNormalEnabled = false;
|
|
81
82
|
this._cleanAuxNormalUniform = uniform( 0, 'uint' );
|
|
82
83
|
// Aux MRT accumulates on an epoch of its own: enabling the denoiser at colour frame N must not leave
|
|
@@ -758,16 +759,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
758
759
|
const height = pool.renderHeight;
|
|
759
760
|
const pixels = width * height;
|
|
760
761
|
|
|
761
|
-
const readAttachment =
|
|
762
|
-
|
|
763
|
-
const padded = await this.renderer.readRenderTargetPixelsAsync( pool.readTarget, 0, 0, width, height, index );
|
|
764
|
-
const stride = Math.ceil( width * 16 / 256 ) * 64;
|
|
765
|
-
if ( stride === width * 4 ) return new Float32Array( padded.buffer, padded.byteOffset, pixels * 4 ).slice();
|
|
766
|
-
const tight = new Float32Array( pixels * 4 );
|
|
767
|
-
for ( let y = 0; y < height; y ++ ) tight.set( padded.subarray( y * stride, y * stride + width * 4 ), y * width * 4 );
|
|
768
|
-
return tight;
|
|
769
|
-
|
|
770
|
-
};
|
|
762
|
+
const readAttachment = index => readPixels( this.renderer, pool.readTarget, width, height, index );
|
|
771
763
|
|
|
772
764
|
const readBuffer = async ( attr, Type ) => attr ? new Type( await this.renderer.getArrayBufferAsync( attr, null, 0, pixels * 4 ) ) : null;
|
|
773
765
|
const aux = this._auxGBufferEnabled && this._auxSamples > 0;
|
|
@@ -185,9 +185,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
185
185
|
this.renderModeChangeDelay = 50;
|
|
186
186
|
this.pendingRenderMode = null;
|
|
187
187
|
|
|
188
|
-
// Track interaction mode state for accumulation
|
|
189
|
-
this.lastInteractionModeState = false;
|
|
190
|
-
|
|
191
188
|
// Track changes for event emission
|
|
192
189
|
this.cameraChanged = false;
|
|
193
190
|
// When the view was last moved, for viewIsChanging.
|
|
@@ -372,12 +369,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
372
369
|
},
|
|
373
370
|
onEnter: () => this.emit( 'pathtracer:interactionStart' ),
|
|
374
371
|
onExit: () => this.emit( 'pathtracer:interactionEnd' ),
|
|
375
|
-
onReset: () =>
|
|
376
|
-
|
|
377
|
-
this.reset();
|
|
378
|
-
this.emit( 'pathtracer:viewpointChanged' );
|
|
379
|
-
|
|
380
|
-
}
|
|
372
|
+
onReset: () => this.reset(),
|
|
381
373
|
} );
|
|
382
374
|
|
|
383
375
|
}
|
|
@@ -561,8 +553,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
561
553
|
|
|
562
554
|
// lastRenderMode is deliberately NOT invalidated here — clearing it made
|
|
563
555
|
// resetHistoryOnRenderModeChange see a phantom mode change 50 ms after every reset, wiping
|
|
564
|
-
//
|
|
565
|
-
this.lastInteractionModeState = false;
|
|
556
|
+
// the denoisers' history. A real mode change is still caught by the renderMode comparison.
|
|
566
557
|
|
|
567
558
|
// Only deterministic mode and lockstep readbacks rewind the seed axis; see _pinSeedToFrame.
|
|
568
559
|
if ( this._pinSeedToFrame || this._lockstep ) this._seedTick = 0;
|
|
@@ -1472,7 +1463,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
1472
1463
|
_updateAccumulationUniforms( frameValue ) {
|
|
1473
1464
|
|
|
1474
1465
|
const currentInteractionMode = this.cameraOptimizer?.isInInteractionMode() ?? false;
|
|
1475
|
-
this.lastInteractionModeState = currentInteractionMode;
|
|
1476
1466
|
|
|
1477
1467
|
if ( this.accumulationEnabled ) {
|
|
1478
1468
|
|
|
@@ -1512,7 +1502,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
1512
1502
|
// Not the same as the context's own `accumulatedFrames`, which counts pipeline renders — this
|
|
1513
1503
|
// freezes when the frame retires or the camera moves, which is what a denoiser needs.
|
|
1514
1504
|
context.setState( 'pathtracer:samples', this.frameCount );
|
|
1515
|
-
context.setState( 'interactionMode', this.cameraOptimizer?.isInInteractionMode() ?? false );
|
|
1516
1505
|
context.setState( 'renderMode', this.renderMode.value );
|
|
1517
1506
|
|
|
1518
1507
|
}
|
|
@@ -1557,7 +1546,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
1557
1546
|
|
|
1558
1547
|
}
|
|
1559
1548
|
|
|
1560
|
-
// =====
|
|
1549
|
+
// ===== DENOISER HISTORY =====
|
|
1561
1550
|
|
|
1562
1551
|
resetHistoryOnRenderModeChange( renderMode ) {
|
|
1563
1552
|
|
|
@@ -1576,9 +1565,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
1576
1565
|
if ( this.pendingRenderMode !== null && this.pendingRenderMode !== this.lastRenderMode ) {
|
|
1577
1566
|
|
|
1578
1567
|
this.lastRenderMode = this.pendingRenderMode;
|
|
1579
|
-
// History from the previous mode is not comparable.
|
|
1580
|
-
// touched here — it is owned by ASVGF_QUALITY_PRESETS, and overwriting it
|
|
1581
|
-
// with a hardcoded value meant `medium` and `high` never took effect.
|
|
1568
|
+
// History from the previous mode is not comparable.
|
|
1582
1569
|
this.emit( 'pipeline:historyReset' );
|
|
1583
1570
|
|
|
1584
1571
|
}
|