rayzee 5.7.0 → 5.7.1
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/dist/rayzee.es.js +2270 -2276
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +2 -2
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/PathTracerApp.js +15 -11
- package/src/RenderSettings.js +10 -8
- package/src/Stages/Compositor.js +101 -0
- package/src/Stages/NormalDepth.js +37 -20
- package/src/managers/DenoisingManager.js +8 -18
- package/src/Stages/Display.js +0 -120
package/package.json
CHANGED
package/src/PathTracerApp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WebGPURenderer, RectAreaLightNode } from 'three/webgpu';
|
|
1
|
+
import { WebGPURenderer, RectAreaLightNode, LinearSRGBColorSpace, SRGBColorSpace } from 'three/webgpu';
|
|
2
2
|
import { texture as _tslTexture, cubeTexture as _tslCubeTexture } from 'three/tsl';
|
|
3
3
|
import {
|
|
4
4
|
ACESFilmicToneMapping, Scene, EventDispatcher, TimestampQuery
|
|
@@ -16,7 +16,7 @@ import { AdaptiveSampling } from './Stages/AdaptiveSampling.js';
|
|
|
16
16
|
import { EdgeFilter } from './Stages/EdgeFilter.js';
|
|
17
17
|
import { AutoExposure } from './Stages/AutoExposure.js';
|
|
18
18
|
import { SSRC } from './Stages/SSRC.js';
|
|
19
|
-
import {
|
|
19
|
+
import { Compositor } from './Stages/Compositor.js';
|
|
20
20
|
import { RenderPipeline } from './Pipeline/RenderPipeline.js';
|
|
21
21
|
import { CompletionTracker } from './Pipeline/CompletionTracker.js';
|
|
22
22
|
import { ENGINE_DEFAULTS as DEFAULT_STATE, FINAL_RENDER_CONFIG, PREVIEW_RENDER_CONFIG } from './EngineDefaults.js';
|
|
@@ -286,7 +286,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
286
286
|
if ( this._needsDisplayRefresh ) {
|
|
287
287
|
|
|
288
288
|
this._needsDisplayRefresh = false;
|
|
289
|
-
this.stages.
|
|
289
|
+
this.stages.compositor.render( this.pipeline.context );
|
|
290
290
|
this._renderHelperOverlay();
|
|
291
291
|
|
|
292
292
|
}
|
|
@@ -775,7 +775,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
775
775
|
// Apply all settings to stages in one shot
|
|
776
776
|
timer.start( 'Apply settings' );
|
|
777
777
|
this.settings.applyAll();
|
|
778
|
-
this.stages.
|
|
778
|
+
this.stages.compositor.setTransparentBackground( this.settings.get( 'transparentBackground' ) );
|
|
779
779
|
timer.end( 'Apply settings' );
|
|
780
780
|
|
|
781
781
|
timer.print();
|
|
@@ -1006,10 +1006,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1006
1006
|
|
|
1007
1007
|
if ( usePostProcess ) return dm.denoiserCanvas;
|
|
1008
1008
|
|
|
1009
|
-
// Re-render
|
|
1010
|
-
if ( this.stages.
|
|
1009
|
+
// Re-render compositor stage so the WebGPU canvas has valid content
|
|
1010
|
+
if ( this.stages.compositor && this.pipeline?.context ) {
|
|
1011
1011
|
|
|
1012
|
-
this.stages.
|
|
1012
|
+
this.stages.compositor.render( this.pipeline.context );
|
|
1013
1013
|
|
|
1014
1014
|
}
|
|
1015
1015
|
|
|
@@ -1208,6 +1208,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1208
1208
|
|
|
1209
1209
|
RectAreaLightNode.setLTC( RectAreaLightTexturesLib.init() );
|
|
1210
1210
|
|
|
1211
|
+
this.renderer.workingColorSpace = SRGBColorSpace;
|
|
1212
|
+
this.renderer.outputColorSpace = SRGBColorSpace;
|
|
1211
1213
|
this.renderer.toneMapping = ACESFilmicToneMapping;
|
|
1212
1214
|
this.renderer.toneMappingExposure = 1.0;
|
|
1213
1215
|
this.renderer.setPixelRatio( 1.0 );
|
|
@@ -1261,7 +1263,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1261
1263
|
this.pipeline.addStage( this.stages.adaptiveSampling );
|
|
1262
1264
|
this.pipeline.addStage( this.stages.edgeFilter );
|
|
1263
1265
|
this.pipeline.addStage( this.stages.autoExposure );
|
|
1264
|
-
this.pipeline.addStage( this.stages.
|
|
1266
|
+
this.pipeline.addStage( this.stages.compositor );
|
|
1265
1267
|
|
|
1266
1268
|
const initRenderW = this.canvas.clientWidth || 1;
|
|
1267
1269
|
const initRenderH = this.canvas.clientHeight || 1;
|
|
@@ -1367,10 +1369,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1367
1369
|
// Bind settings to pipeline stages
|
|
1368
1370
|
this.settings.bind( {
|
|
1369
1371
|
stages: this.stages,
|
|
1372
|
+
renderer: this.renderer,
|
|
1370
1373
|
resetCallback: () => this.reset(),
|
|
1371
1374
|
reconcileCompletion: () => this._reconcileCompletion(),
|
|
1372
1375
|
} );
|
|
1373
1376
|
|
|
1377
|
+
this.renderer.toneMappingExposure = this.settings.get( 'exposure' ) ?? 1.0;
|
|
1378
|
+
|
|
1374
1379
|
// Resize handling
|
|
1375
1380
|
this.onResize();
|
|
1376
1381
|
this.resizeHandler = () => this.onResize();
|
|
@@ -1474,8 +1479,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1474
1479
|
this.stages.edgeFilter = new EdgeFilter( this.renderer, { enabled: false } );
|
|
1475
1480
|
this.stages.autoExposure = new AutoExposure( this.renderer, { enabled: DEFAULT_STATE.autoExposure ?? false } );
|
|
1476
1481
|
|
|
1477
|
-
this.stages.
|
|
1478
|
-
exposure: ( DEFAULT_STATE.autoExposure ) ? 1.0 : ( this.settings.get( 'exposure' ) ?? 1.0 ),
|
|
1482
|
+
this.stages.compositor = new Compositor( this.renderer, {
|
|
1479
1483
|
saturation: this.settings.get( 'saturation' ) ?? DEFAULT_STATE.saturation,
|
|
1480
1484
|
} );
|
|
1481
1485
|
|
|
@@ -1497,7 +1501,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1497
1501
|
edgeFilter: this.stages.edgeFilter,
|
|
1498
1502
|
ssrc: this.stages.ssrc,
|
|
1499
1503
|
autoExposure: this.stages.autoExposure,
|
|
1500
|
-
|
|
1504
|
+
compositor: this.stages.compositor,
|
|
1501
1505
|
},
|
|
1502
1506
|
pipeline: this.pipeline,
|
|
1503
1507
|
getExposure: () => this.settings.get( 'exposure' ) ?? 1.0,
|
package/src/RenderSettings.js
CHANGED
|
@@ -104,16 +104,16 @@ export class RenderSettings extends EventDispatcher {
|
|
|
104
104
|
* Wires internal references. Called by PathTracerApp after init().
|
|
105
105
|
*
|
|
106
106
|
* @param {Object} params
|
|
107
|
-
* @param {Object} params.stages - Pipeline stages { pathTracer,
|
|
107
|
+
* @param {Object} params.stages - Pipeline stages { pathTracer, compositor, autoExposure, ... }
|
|
108
108
|
* @param {Function} params.resetCallback - Called to reset accumulation
|
|
109
109
|
* @param {Function} [params.reconcileCompletion] - Called when completion limits change
|
|
110
110
|
*/
|
|
111
|
-
bind( { stages, resetCallback, reconcileCompletion } ) {
|
|
111
|
+
bind( { stages, renderer, resetCallback, reconcileCompletion } ) {
|
|
112
112
|
|
|
113
113
|
this._pathTracer = stages.pathTracer;
|
|
114
114
|
this._resetCallback = resetCallback;
|
|
115
115
|
this._delegates = {};
|
|
116
|
-
this._handlers = this._buildHandlers( stages, reconcileCompletion );
|
|
116
|
+
this._handlers = this._buildHandlers( stages, renderer, reconcileCompletion );
|
|
117
117
|
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -121,22 +121,24 @@ export class RenderSettings extends EventDispatcher {
|
|
|
121
121
|
* Builds handler functions for multi-stage settings that can't
|
|
122
122
|
* be routed with a simple uniform forward.
|
|
123
123
|
*/
|
|
124
|
-
_buildHandlers( stages, reconcileCompletion ) {
|
|
124
|
+
_buildHandlers( stages, renderer, reconcileCompletion ) {
|
|
125
125
|
|
|
126
126
|
return {
|
|
127
127
|
|
|
128
128
|
handleTransparentBackground: ( value ) => {
|
|
129
129
|
|
|
130
130
|
stages.pathTracer?.setUniform( 'transparentBackground', value );
|
|
131
|
-
stages.
|
|
131
|
+
stages.compositor?.setTransparentBackground( value );
|
|
132
132
|
|
|
133
133
|
},
|
|
134
134
|
|
|
135
135
|
handleExposure: ( value ) => {
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
// Three.js applies toneMappingExposure inside the tone-mapping branch,
|
|
138
|
+
// so this has no effect when renderer.toneMapping === NoToneMapping.
|
|
139
|
+
if ( ! stages.autoExposure?.enabled && renderer ) {
|
|
138
140
|
|
|
139
|
-
|
|
141
|
+
renderer.toneMappingExposure = value;
|
|
140
142
|
|
|
141
143
|
}
|
|
142
144
|
|
|
@@ -144,7 +146,7 @@ export class RenderSettings extends EventDispatcher {
|
|
|
144
146
|
|
|
145
147
|
handleSaturation: ( value ) => {
|
|
146
148
|
|
|
147
|
-
stages.
|
|
149
|
+
stages.compositor?.setSaturation( value );
|
|
148
150
|
|
|
149
151
|
},
|
|
150
152
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { vec4, vec3, uv, uniform, select, dot, mix } from 'three/tsl';
|
|
2
|
+
import { MeshBasicNodeMaterial, QuadMesh, TextureNode } from 'three/webgpu';
|
|
3
|
+
import { NoBlending } from 'three';
|
|
4
|
+
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
5
|
+
import { REC709_LUMINANCE_COEFFICIENTS } from '../TSL/Common.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Compositor — Terminal pipeline stage.
|
|
9
|
+
*
|
|
10
|
+
* Selects the latest upstream texture via a priority fallback chain, applies
|
|
11
|
+
* a saturation grade, sets alpha, and hands the linear HDR result to the
|
|
12
|
+
* renderer's output pass (tone mapping + sRGB gamma happen there).
|
|
13
|
+
*
|
|
14
|
+
* Exposure is not applied here — `renderer.toneMappingExposure` owns it,
|
|
15
|
+
* and Three.js applies it inside the tone-mapping branch of the output pass
|
|
16
|
+
* (so it has no effect when `renderer.toneMapping === NoToneMapping`).
|
|
17
|
+
*/
|
|
18
|
+
export class Compositor extends RenderStage {
|
|
19
|
+
|
|
20
|
+
constructor( renderer, options = {} ) {
|
|
21
|
+
|
|
22
|
+
super( 'Compositor', {
|
|
23
|
+
...options,
|
|
24
|
+
executionMode: StageExecutionMode.ALWAYS
|
|
25
|
+
} );
|
|
26
|
+
|
|
27
|
+
this.renderer = renderer;
|
|
28
|
+
|
|
29
|
+
// 1.0 = neutral; >1 boosts to compensate for ACES/AgX desaturation.
|
|
30
|
+
this.saturation = uniform( options.saturation ?? 1.0 );
|
|
31
|
+
|
|
32
|
+
this._transparentBackground = uniform( 0, 'int' );
|
|
33
|
+
|
|
34
|
+
// TextureNode reused across frames — only `.value` mutates, shader doesn't recompile.
|
|
35
|
+
this._sourceTexNode = new TextureNode();
|
|
36
|
+
|
|
37
|
+
const texSample = this._sourceTexNode.sample( uv() );
|
|
38
|
+
|
|
39
|
+
const luma = dot( texSample.xyz, REC709_LUMINANCE_COEFFICIENTS );
|
|
40
|
+
const gradedColor = mix( vec3( luma ), texSample.xyz, this.saturation );
|
|
41
|
+
|
|
42
|
+
const outputAlpha = select( this._transparentBackground, texSample.w, 1.0 );
|
|
43
|
+
|
|
44
|
+
this.compositorMaterial = new MeshBasicNodeMaterial();
|
|
45
|
+
this.compositorMaterial.colorNode = vec4( gradedColor, outputAlpha );
|
|
46
|
+
this.compositorMaterial.blending = NoBlending;
|
|
47
|
+
|
|
48
|
+
this.compositorQuad = new QuadMesh( this.compositorMaterial );
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Later stages in the chain take priority; `pathtracer:color` is the
|
|
54
|
+
* baseline fallback that is always present.
|
|
55
|
+
*/
|
|
56
|
+
_resolveSourceTexture( context ) {
|
|
57
|
+
|
|
58
|
+
return context.getTexture( 'bloom:output' )
|
|
59
|
+
|| context.getTexture( 'edgeFiltering:output' )
|
|
60
|
+
|| context.getTexture( 'asvgf:output' )
|
|
61
|
+
|| context.getTexture( 'ssrc:output' )
|
|
62
|
+
|| context.getTexture( 'pathtracer:color' );
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
render( context ) {
|
|
67
|
+
|
|
68
|
+
if ( ! this.enabled ) return;
|
|
69
|
+
|
|
70
|
+
const sourceTexture = this._resolveSourceTexture( context );
|
|
71
|
+
if ( ! sourceTexture ) return;
|
|
72
|
+
|
|
73
|
+
this._sourceTexNode.value = sourceTexture;
|
|
74
|
+
|
|
75
|
+
this.renderer.setRenderTarget( null );
|
|
76
|
+
this.compositorQuad.render( this.renderer );
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setSaturation( value ) {
|
|
81
|
+
|
|
82
|
+
this.saturation.value = value;
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setTransparentBackground( enabled ) {
|
|
87
|
+
|
|
88
|
+
this._transparentBackground.value = enabled ? 1 : 0;
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
dispose() {
|
|
93
|
+
|
|
94
|
+
this._sourceTexNode?.dispose();
|
|
95
|
+
this.compositorMaterial?.dispose();
|
|
96
|
+
// QuadMesh extends Mesh — no dispose method; material already released above.
|
|
97
|
+
this.compositorQuad = null;
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
}
|
|
@@ -95,6 +95,14 @@ export class NormalDepth extends RenderStage {
|
|
|
95
95
|
this._bvhStorageNode = null;
|
|
96
96
|
this._matStorageNode = null;
|
|
97
97
|
|
|
98
|
+
// Last-seen attribute identities. PathTracer replaces these in-place
|
|
99
|
+
// across model load / BVH rebuild; the compute's bind group is locked
|
|
100
|
+
// to whatever buffer was bound at pipeline compile time, so we rebuild
|
|
101
|
+
// when any of them swaps to a new object.
|
|
102
|
+
this._lastTriAttr = null;
|
|
103
|
+
this._lastBvhAttr = null;
|
|
104
|
+
this._lastMatAttr = null;
|
|
105
|
+
|
|
98
106
|
// Compute node — built once when storage buffers are ready
|
|
99
107
|
this._computeNode = null;
|
|
100
108
|
this._computeBuilt = false;
|
|
@@ -138,50 +146,59 @@ export class NormalDepth extends RenderStage {
|
|
|
138
146
|
const pt = this.pathTracer;
|
|
139
147
|
if ( ! pt ) return false;
|
|
140
148
|
|
|
141
|
-
|
|
149
|
+
const matStorageAttr = pt.materialData.materialStorageAttr;
|
|
150
|
+
|
|
151
|
+
// Detect attribute identity swap (PathTracer.setTriangleData /
|
|
152
|
+
// setBVHData replace the attribute object on growth). The compute
|
|
153
|
+
// node's bind group is locked to the buffer bound at compile time —
|
|
154
|
+
// updating the storage node's .value alone leaves the GPU binding
|
|
155
|
+
// pointing at the now-discarded buffer, so every traversal misses.
|
|
156
|
+
const triSwapped = pt.triangleStorageAttr && pt.triangleStorageAttr !== this._lastTriAttr;
|
|
157
|
+
const bvhSwapped = pt.bvhStorageAttr && pt.bvhStorageAttr !== this._lastBvhAttr;
|
|
158
|
+
const matSwapped = matStorageAttr && matStorageAttr !== this._lastMatAttr;
|
|
159
|
+
|
|
160
|
+
if ( triSwapped || bvhSwapped || matSwapped ) {
|
|
161
|
+
|
|
162
|
+
// Drop compute + storage nodes so they get rebuilt against the
|
|
163
|
+
// current buffers. Cheap: this only happens on model load.
|
|
164
|
+
this._computeNode?.dispose?.();
|
|
165
|
+
this._computeNode = null;
|
|
166
|
+
this._computeBuilt = false;
|
|
167
|
+
this._triStorageNode = null;
|
|
168
|
+
this._bvhStorageNode = null;
|
|
169
|
+
this._matStorageNode = null;
|
|
170
|
+
this._dirty = true;
|
|
171
|
+
|
|
172
|
+
}
|
|
173
|
+
|
|
142
174
|
if ( pt.triangleStorageAttr && ! this._triStorageNode ) {
|
|
143
175
|
|
|
144
176
|
this._triStorageNode = storage(
|
|
145
177
|
pt.triangleStorageAttr, 'vec4', pt.triangleStorageAttr.count
|
|
146
178
|
).toReadOnly();
|
|
147
179
|
|
|
148
|
-
} else if ( pt.triangleStorageAttr && this._triStorageNode ) {
|
|
149
|
-
|
|
150
|
-
// Data changed (new model loaded) — update in-place
|
|
151
|
-
this._triStorageNode.value = pt.triangleStorageAttr;
|
|
152
|
-
this._triStorageNode.bufferCount = pt.triangleStorageAttr.count;
|
|
153
|
-
|
|
154
180
|
}
|
|
155
181
|
|
|
156
|
-
// BVH storage
|
|
157
182
|
if ( pt.bvhStorageAttr && ! this._bvhStorageNode ) {
|
|
158
183
|
|
|
159
184
|
this._bvhStorageNode = storage(
|
|
160
185
|
pt.bvhStorageAttr, 'vec4', pt.bvhStorageAttr.count
|
|
161
186
|
).toReadOnly();
|
|
162
187
|
|
|
163
|
-
} else if ( pt.bvhStorageAttr && this._bvhStorageNode ) {
|
|
164
|
-
|
|
165
|
-
this._bvhStorageNode.value = pt.bvhStorageAttr;
|
|
166
|
-
this._bvhStorageNode.bufferCount = pt.bvhStorageAttr.count;
|
|
167
|
-
|
|
168
188
|
}
|
|
169
189
|
|
|
170
|
-
// Material storage
|
|
171
|
-
const matStorageAttr = pt.materialData.materialStorageAttr;
|
|
172
190
|
if ( matStorageAttr && ! this._matStorageNode ) {
|
|
173
191
|
|
|
174
192
|
this._matStorageNode = storage(
|
|
175
193
|
matStorageAttr, 'vec4', matStorageAttr.count
|
|
176
194
|
).toReadOnly();
|
|
177
195
|
|
|
178
|
-
} else if ( matStorageAttr && this._matStorageNode ) {
|
|
179
|
-
|
|
180
|
-
this._matStorageNode.value = matStorageAttr;
|
|
181
|
-
this._matStorageNode.bufferCount = matStorageAttr.count;
|
|
182
|
-
|
|
183
196
|
}
|
|
184
197
|
|
|
198
|
+
this._lastTriAttr = pt.triangleStorageAttr || this._lastTriAttr;
|
|
199
|
+
this._lastBvhAttr = pt.bvhStorageAttr || this._lastBvhAttr;
|
|
200
|
+
this._lastMatAttr = matStorageAttr || this._lastMatAttr;
|
|
201
|
+
|
|
185
202
|
return !! ( this._triStorageNode && this._bvhStorageNode && this._matStorageNode );
|
|
186
203
|
|
|
187
204
|
}
|
|
@@ -41,7 +41,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
41
41
|
this.pipeline = pipeline;
|
|
42
42
|
|
|
43
43
|
// Stage references — only used internally for orchestration
|
|
44
|
-
this._stages = stages; // { pathTracer, asvgf, variance, bilateralFilter, adaptiveSampling, edgeFilter, ssrc, autoExposure,
|
|
44
|
+
this._stages = stages; // { pathTracer, asvgf, variance, bilateralFilter, adaptiveSampling, edgeFilter, ssrc, autoExposure, compositor }
|
|
45
45
|
|
|
46
46
|
this._getExposure = getExposure;
|
|
47
47
|
this._getSaturation = getSaturation;
|
|
@@ -295,9 +295,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
/**
|
|
298
|
-
* Enables/disables auto-exposure with proper exposure stacking management.
|
|
299
298
|
* @param {boolean} enabled
|
|
300
|
-
* @param {number} manualExposure -
|
|
299
|
+
* @param {number} manualExposure - Restored to renderer.toneMappingExposure when disabling.
|
|
301
300
|
*/
|
|
302
301
|
setAutoExposureEnabled( enabled, manualExposure ) {
|
|
303
302
|
|
|
@@ -306,19 +305,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
306
305
|
|
|
307
306
|
s.autoExposure.enabled = enabled;
|
|
308
307
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// Neutralize Display manual exposure to avoid stacking
|
|
312
|
-
s.display?.setExposure( 1.0 );
|
|
313
|
-
|
|
314
|
-
} else {
|
|
308
|
+
// AutoExposure overwrites renderer.toneMappingExposure each frame; restore manual on disable.
|
|
309
|
+
if ( ! enabled && this.renderer ) {
|
|
315
310
|
|
|
316
|
-
|
|
317
|
-
if ( s.display && this.renderer ) {
|
|
318
|
-
|
|
319
|
-
this.renderer.toneMappingExposure = 1.0;
|
|
320
|
-
|
|
321
|
-
}
|
|
311
|
+
this.renderer.toneMappingExposure = manualExposure;
|
|
322
312
|
|
|
323
313
|
}
|
|
324
314
|
|
|
@@ -417,10 +407,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
417
407
|
|
|
418
408
|
} else {
|
|
419
409
|
|
|
420
|
-
// Re-render
|
|
421
|
-
if ( this.upscaler?.enabled && this._stages.
|
|
410
|
+
// Re-render compositor stage so WebGPU canvas has valid content
|
|
411
|
+
if ( this.upscaler?.enabled && this._stages.compositor && context ) {
|
|
422
412
|
|
|
423
|
-
this._stages.
|
|
413
|
+
this._stages.compositor.render( context );
|
|
424
414
|
|
|
425
415
|
}
|
|
426
416
|
|
package/src/Stages/Display.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { vec4, vec3, uv, uniform, select, dot, mix } from 'three/tsl';
|
|
2
|
-
import { MeshBasicNodeMaterial, QuadMesh, TextureNode } from 'three/webgpu';
|
|
3
|
-
import { NoBlending } from 'three';
|
|
4
|
-
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
5
|
-
import { REC709_LUMINANCE_COEFFICIENTS } from '../TSL/Common.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Display — Terminal pipeline stage for WebGPU.
|
|
9
|
-
*
|
|
10
|
-
* Reads the final colour texture from the pipeline context (using a
|
|
11
|
-
* priority fallback chain), applies exposure, and renders to screen.
|
|
12
|
-
*
|
|
13
|
-
* When new post-processing stages are added between PathTracer and
|
|
14
|
-
* Display, the fallback chain automatically picks up the latest output
|
|
15
|
-
* without any wiring changes.
|
|
16
|
-
*/
|
|
17
|
-
export class Display extends RenderStage {
|
|
18
|
-
|
|
19
|
-
constructor( renderer, options = {} ) {
|
|
20
|
-
|
|
21
|
-
super( 'Display', {
|
|
22
|
-
...options,
|
|
23
|
-
executionMode: StageExecutionMode.ALWAYS
|
|
24
|
-
} );
|
|
25
|
-
|
|
26
|
-
this.renderer = renderer;
|
|
27
|
-
|
|
28
|
-
// Exposure uniform — linear multiplier (consistent with auto-exposure)
|
|
29
|
-
this.exposure = uniform( options.exposure ?? 1.0 );
|
|
30
|
-
|
|
31
|
-
// Pre-tonemapping saturation — compensates for ACES/AgX desaturation (1.0 = neutral)
|
|
32
|
-
this.saturation = uniform( options.saturation ?? 1.0 );
|
|
33
|
-
|
|
34
|
-
// Transparent background toggle
|
|
35
|
-
this._transparentBackground = uniform( 0, 'int' );
|
|
36
|
-
|
|
37
|
-
// Updatable texture node — swap .value each frame, no shader recompile
|
|
38
|
-
this._displayTexNode = new TextureNode();
|
|
39
|
-
|
|
40
|
-
const texSample = this._displayTexNode.sample( uv() );
|
|
41
|
-
|
|
42
|
-
// Build material once (TSL compiles on first render)
|
|
43
|
-
const exposed = texSample.xyz.mul( this.exposure );
|
|
44
|
-
|
|
45
|
-
// Saturation adjustment (before tonemapping): mix between luminance and color
|
|
46
|
-
const luma = dot( exposed, REC709_LUMINANCE_COEFFICIENTS );
|
|
47
|
-
let displayShader = mix( vec3( luma ), exposed, this.saturation );
|
|
48
|
-
|
|
49
|
-
// Alpha: pass through source alpha when transparent, otherwise 1.0
|
|
50
|
-
const outputAlpha = select( this._transparentBackground, texSample.w, 1.0 );
|
|
51
|
-
|
|
52
|
-
this.displayMaterial = new MeshBasicNodeMaterial();
|
|
53
|
-
this.displayMaterial.colorNode = vec4( displayShader, outputAlpha );
|
|
54
|
-
this.displayMaterial.blending = NoBlending;
|
|
55
|
-
this.displayMaterial.toneMapped = true;
|
|
56
|
-
|
|
57
|
-
this.displayQuad = new QuadMesh( this.displayMaterial );
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Resolve the best available output texture from the pipeline context.
|
|
63
|
-
* Later stages in the chain take priority; pathtracer:color is the
|
|
64
|
-
* baseline fallback that is always present.
|
|
65
|
-
*/
|
|
66
|
-
_resolveDisplayTexture( context ) {
|
|
67
|
-
|
|
68
|
-
return context.getTexture( 'bloom:output' )
|
|
69
|
-
|| context.getTexture( 'edgeFiltering:output' )
|
|
70
|
-
|| context.getTexture( 'asvgf:output' )
|
|
71
|
-
|| context.getTexture( 'ssrc:output' )
|
|
72
|
-
|| context.getTexture( 'pathtracer:color' );
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
render( context ) {
|
|
77
|
-
|
|
78
|
-
if ( ! this.enabled ) return;
|
|
79
|
-
|
|
80
|
-
const displayTexture = this._resolveDisplayTexture( context );
|
|
81
|
-
|
|
82
|
-
if ( ! displayTexture ) return;
|
|
83
|
-
|
|
84
|
-
// Swap texture reference (no shader recompilation)
|
|
85
|
-
this._displayTexNode.value = displayTexture;
|
|
86
|
-
|
|
87
|
-
// Render to screen
|
|
88
|
-
this.renderer.setRenderTarget( null );
|
|
89
|
-
this.displayQuad.render( this.renderer );
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
setExposure( value ) {
|
|
94
|
-
|
|
95
|
-
this.exposure.value = value;
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
setSaturation( value ) {
|
|
100
|
-
|
|
101
|
-
this.saturation.value = value;
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
setTransparentBackground( enabled ) {
|
|
106
|
-
|
|
107
|
-
this._transparentBackground.value = enabled ? 1 : 0;
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
dispose() {
|
|
112
|
-
|
|
113
|
-
this._displayTexNode?.dispose();
|
|
114
|
-
this.displayMaterial?.dispose();
|
|
115
|
-
// QuadMesh extends Mesh — no dispose method; material already disposed.
|
|
116
|
-
this.displayQuad = null;
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
}
|