rayzee 7.3.0 → 7.4.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 +2 -3
- package/dist/rayzee.es.js +1234 -1209
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +58 -44
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/EngineDefaults.js +9 -7
- package/src/PathTracerApp.js +4 -1
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/EmissiveTriangleBuilder.js +4 -3
- package/src/Processor/PackedRayBuffer.js +3 -4
- package/src/Processor/QueueManager.js +1 -0
- package/src/RenderSettings.js +13 -2
- package/src/Stages/PathTracer.js +31 -52
- package/src/Stages/PathTracerStage.js +0 -13
- package/src/TSL/DebugKernel.js +2 -3
- package/src/TSL/FinalWriteKernel.js +28 -29
- package/src/TSL/GenerateKernel.js +11 -16
- package/src/TSL/ShadeKernel.js +161 -55
- package/src/managers/DenoisingManager.js +7 -0
- package/src/managers/UniformManager.js +4 -2
- package/src/managers/VideoRenderManager.js +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rayzee",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Real-time WebGPU path tracing engine built on Three.js",
|
|
6
6
|
"main": "dist/rayzee.umd.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"prepublishOnly": "npm run build"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"three": ">=0.
|
|
37
|
+
"three": ">=0.185.0"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
40
|
"oidn-web": ">=0.3.0",
|
package/src/EngineDefaults.js
CHANGED
|
@@ -19,6 +19,13 @@ export const ENGINE_DEFAULTS = {
|
|
|
19
19
|
useImportanceSampledEnvironment: true,
|
|
20
20
|
environmentIntensity: 1,
|
|
21
21
|
backgroundIntensity: 1,
|
|
22
|
+
// Solid backdrop color shown on camera-ray misses in 'color' background mode
|
|
23
|
+
// (showBackground=false, transparentBackground=false). Black = legacy hidden-backdrop look.
|
|
24
|
+
backgroundColor: '#000000',
|
|
25
|
+
// Backdrop blur (env background only). 0 = sharp/off (no cost). Cone-jitter blur of the
|
|
26
|
+
// primary-ray env lookup; lighting/reflections stay sharp. Samples = taps/frame (noise vs cost).
|
|
27
|
+
backgroundBlurriness: 0,
|
|
28
|
+
backgroundBlurSamples: 8,
|
|
22
29
|
environmentRotation: 270.0,
|
|
23
30
|
groundProjectionEnabled: false,
|
|
24
31
|
groundProjectionRadius: 100,
|
|
@@ -64,16 +71,11 @@ export const ENGINE_DEFAULTS = {
|
|
|
64
71
|
afScreenPoint: { x: 0.5, y: 0.5 },
|
|
65
72
|
afSmoothingFactor: 0.15,
|
|
66
73
|
|
|
67
|
-
// Multi-sample pool: S=samplesPerPixel rays/pixel/frame, FinalWrite averages them; interactive-only (renderMode 0, ≤ cap), else S=1.
|
|
68
|
-
// Pixel cap (768²) bounds pool memory; covers the 512² default, excludes ≥768².
|
|
69
|
-
wavefrontMultiSampleMaxPixels: 589824,
|
|
70
|
-
|
|
71
74
|
enablePathTracer: true,
|
|
72
75
|
enableAccumulation: true,
|
|
73
76
|
pauseRendering: false,
|
|
74
77
|
maxSamples: 60,
|
|
75
78
|
bounces: 3,
|
|
76
|
-
samplesPerPixel: 1,
|
|
77
79
|
transmissiveBounces: 5,
|
|
78
80
|
maxSubsurfaceSteps: 8, // interactive default: low cap (bounded random-walk SSS)
|
|
79
81
|
samplingTechnique: 3,
|
|
@@ -487,7 +489,7 @@ export const DEFAULT_TEXTURE_MATRIX = [ 0, 0, 1, 1, 0, 0, 0, 1 ];
|
|
|
487
489
|
// 'interactive' — low-sample, bounded bounces, no offline denoising, controls enabled.
|
|
488
490
|
// 'production' — high-sample, deep bounces, OIDN enabled, controls disabled.
|
|
489
491
|
export const PRODUCTION_RENDER_CONFIG = {
|
|
490
|
-
maxSamples: 30, bounces: 20, transmissiveBounces: 8, maxSubsurfaceSteps: 64,
|
|
492
|
+
maxSamples: 30, bounces: 20, transmissiveBounces: 8, maxSubsurfaceSteps: 64,
|
|
491
493
|
renderMode: 1, enableAlphaShadows: true,
|
|
492
494
|
enableOIDN: true, oidnQuality: 'balance',
|
|
493
495
|
interactionModeEnabled: false,
|
|
@@ -495,7 +497,7 @@ export const PRODUCTION_RENDER_CONFIG = {
|
|
|
495
497
|
|
|
496
498
|
export const INTERACTIVE_RENDER_CONFIG = {
|
|
497
499
|
maxSamples: ENGINE_DEFAULTS.maxSamples, bounces: ENGINE_DEFAULTS.bounces,
|
|
498
|
-
|
|
500
|
+
renderMode: ENGINE_DEFAULTS.renderMode, enableAlphaShadows: ENGINE_DEFAULTS.enableAlphaShadows,
|
|
499
501
|
transmissiveBounces: ENGINE_DEFAULTS.transmissiveBounces,
|
|
500
502
|
maxSubsurfaceSteps: ENGINE_DEFAULTS.maxSubsurfaceSteps,
|
|
501
503
|
enableOIDN: false, oidnQuality: 'fast',
|
package/src/PathTracerApp.js
CHANGED
|
@@ -959,7 +959,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
959
959
|
this.settings.setMany( {
|
|
960
960
|
maxSamples: config.maxSamples,
|
|
961
961
|
maxBounces: config.bounces,
|
|
962
|
-
samplesPerPixel: config.samplesPerPixel,
|
|
963
962
|
transmissiveBounces: config.transmissiveBounces,
|
|
964
963
|
maxSubsurfaceSteps: config.maxSubsurfaceSteps,
|
|
965
964
|
}, { silent: true } );
|
|
@@ -978,6 +977,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
978
977
|
|
|
979
978
|
}
|
|
980
979
|
|
|
980
|
+
// OIDN toggled directly above (bypassing setOIDNEnabled) — re-sync so the wavefront produces the
|
|
981
|
+
// aux MRT when OIDN is on and skips it otherwise. Runs before the reset below so kernels rebuild once.
|
|
982
|
+
this.denoisingManager?._syncGBufferStages?.();
|
|
983
|
+
|
|
981
984
|
this.denoisingManager?.upscaler?.abort();
|
|
982
985
|
|
|
983
986
|
if ( options.canvasWidth && options.canvasHeight ) {
|
|
@@ -24,7 +24,6 @@ export class CameraOptimizer {
|
|
|
24
24
|
// Enhanced interaction mode settings for reduced quality during interaction
|
|
25
25
|
this.interactionQualitySettings = {
|
|
26
26
|
maxBounceCount: 1,
|
|
27
|
-
numRaysPerPixel: 1,
|
|
28
27
|
useEnvMapIS: false,
|
|
29
28
|
// pixelRatio: 0.25,
|
|
30
29
|
enableAccumulation: false,
|
|
@@ -304,28 +303,24 @@ export class CameraOptimizer {
|
|
|
304
303
|
const presets = {
|
|
305
304
|
'ultra-low': {
|
|
306
305
|
maxBounceCount: 1,
|
|
307
|
-
numRaysPerPixel: 1,
|
|
308
306
|
useEnvMapIS: false,
|
|
309
307
|
pixelRatio: 0.125,
|
|
310
308
|
enableAccumulation: false
|
|
311
309
|
},
|
|
312
310
|
'low': {
|
|
313
311
|
maxBounceCount: 1,
|
|
314
|
-
numRaysPerPixel: 1,
|
|
315
312
|
useEnvMapIS: false,
|
|
316
313
|
pixelRatio: 0.25,
|
|
317
314
|
enableAccumulation: false
|
|
318
315
|
},
|
|
319
316
|
'medium': {
|
|
320
317
|
maxBounceCount: 2,
|
|
321
|
-
numRaysPerPixel: 1,
|
|
322
318
|
useEnvMapIS: true,
|
|
323
319
|
pixelRatio: 0.5,
|
|
324
320
|
enableAccumulation: false
|
|
325
321
|
},
|
|
326
322
|
'high': {
|
|
327
323
|
maxBounceCount: 3,
|
|
328
|
-
numRaysPerPixel: 1,
|
|
329
324
|
useEnvMapIS: true,
|
|
330
325
|
pixelRatio: 0.75,
|
|
331
326
|
enableAccumulation: true
|
|
@@ -443,8 +443,9 @@ export class EmissiveTriangleBuilder {
|
|
|
443
443
|
// If not emissive before and not now, nothing to do
|
|
444
444
|
if ( ! isNowEmissive ) return false;
|
|
445
445
|
|
|
446
|
-
// Fast path: just update power + CDF for affected entries
|
|
447
|
-
|
|
446
|
+
// Fast path: just update power + CDF for affected entries.
|
|
447
|
+
// Rec.709 luma — must match the build path + shader MIS weighting.
|
|
448
|
+
const luma = 0.2126 * emissive.r + 0.7152 * emissive.g + 0.0722 * emissive.b;
|
|
448
449
|
|
|
449
450
|
this.totalEmissivePower = 0;
|
|
450
451
|
for ( let i = 0; i < this.emissiveCount; i ++ ) {
|
|
@@ -452,7 +453,7 @@ export class EmissiveTriangleBuilder {
|
|
|
452
453
|
const tri = this.emissiveTriangles[ i ];
|
|
453
454
|
if ( tri.materialIndex === materialIndex ) {
|
|
454
455
|
|
|
455
|
-
tri.power =
|
|
456
|
+
tri.power = luma * emissiveIntensity * tri.area;
|
|
456
457
|
tri.emissive = { r: emissive.r, g: emissive.g, b: emissive.b };
|
|
457
458
|
tri.emissiveIntensity = emissiveIntensity;
|
|
458
459
|
this.emissivePowerArray[ i ] = tri.power;
|
|
@@ -20,7 +20,7 @@ export const HIT_STRIDE = 2;
|
|
|
20
20
|
export const GBUFFER_STRIDE = 2;
|
|
21
21
|
|
|
22
22
|
export const RAY = {
|
|
23
|
-
ORIGIN_META: 0, // vec4(origin.xyz, uintBitsToFloat(perRayBounces | sssSteps<<8)); pixelIndex
|
|
23
|
+
ORIGIN_META: 0, // vec4(origin.xyz, uintBitsToFloat(perRayBounces | sssSteps<<8)); pixelIndex == rayID
|
|
24
24
|
DIR_FLAGS: 1, // vec4(direction.xyz, uintBitsToFloat(bounceFlags))
|
|
25
25
|
THROUGHPUT_PDF: 2, // vec4(throughput.xyz, pdf)
|
|
26
26
|
RADIANCE_ALPHA: 3, // vec4(radiance.xyz, alpha)
|
|
@@ -195,8 +195,8 @@ export const gbDecodeNormalDepth = ( packed ) => {
|
|
|
195
195
|
export const gbDecodeAlbedo = ( packed ) =>
|
|
196
196
|
vec3( unpackUnorm2x16( packed.z ), unpackUnorm2x16( packed.w ).x );
|
|
197
197
|
|
|
198
|
-
// .w packs per-ray bounce state: perRayBounces (bits 0-7) | sssSteps (bits 8-15). pixelIndex
|
|
199
|
-
//
|
|
198
|
+
// .w packs per-ray bounce state: perRayBounces (bits 0-7) | sssSteps (bits 8-15). pixelIndex is
|
|
199
|
+
// NOT stored — it equals rayID (one ray per pixel).
|
|
200
200
|
export const writeRayOriginMeta = ( buf, id, origin, bounces, sssSteps ) =>
|
|
201
201
|
buf.element( soa( id, RAY.ORIGIN_META ) )
|
|
202
202
|
.assign( vec4( origin, uintBitsToFloat(
|
|
@@ -273,7 +273,6 @@ export const writeMediumSigmaA = ( buf, id, sigmaA ) =>
|
|
|
273
273
|
// Per-ray bounce state packed into ORIGIN_META.w (written by writeRayOriginMeta alongside the origin):
|
|
274
274
|
// perRayBounces = bits 0-7 (camera-bounce depth; the loop index can't track it once free bounces decouple it)
|
|
275
275
|
// sssSteps = bits 8-15 (SSS random-walk step counter)
|
|
276
|
-
// sampleIndex (the multi-sample sub-sample 0..S-1) is derived in-kernel from rayID, not stored.
|
|
277
276
|
export const readPathBounces = ( buf, id ) =>
|
|
278
277
|
int( floatBitsToUint( buf.element( soa( id, RAY.ORIGIN_META ) ).w ).bitAnd( 0xFF ) );
|
|
279
278
|
export const readSssSteps = ( buf, id ) =>
|
|
@@ -25,6 +25,7 @@ export const RAY_FLAG = {
|
|
|
25
25
|
// bits 16-31: spare per-ray state carried across bounces
|
|
26
26
|
HAS_HIT_OPAQUE: 1 << 16, // bit 16: ray chain has hit non-transmissive geometry (transparent-bg alpha; megakernel hasHitOpaqueSurface)
|
|
27
27
|
AUX_LOCKED: 1 << 17, // bit 17: OIDN aux (normal/albedo) locked onto first non-specular hit (megakernel auxLocked)
|
|
28
|
+
REDIRECTED: 1 << 18, // bit 18: ray has been redirected (refraction/reflection/SSS/opaque scatter) since the camera, so env it reaches is transported light (sharp), NOT the direct backdrop. NOT set by pure alpha/transparent passthrough (direction unchanged) → env through cutout holes is still the backdrop (blur/intensity/show/color/ground-projection). Set via bitOr only (positive mask) so it never disturbs ACTIVE/bounce bits.
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
export class QueueManager {
|
package/src/RenderSettings.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventDispatcher } from 'three';
|
|
1
|
+
import { EventDispatcher, Color } from 'three';
|
|
2
2
|
import { ENGINE_DEFAULTS } from './EngineDefaults.js';
|
|
3
3
|
import { EngineEvents } from './EngineEvents.js';
|
|
4
4
|
|
|
@@ -16,11 +16,12 @@ const SETTING_ROUTES = {
|
|
|
16
16
|
// ── Simple PathTracer uniforms ──────────────────────────
|
|
17
17
|
|
|
18
18
|
maxBounces: { uniform: 'maxBounces', reset: true },
|
|
19
|
-
samplesPerPixel: { uniform: 'samplesPerPixel', reset: true },
|
|
20
19
|
transmissiveBounces: { uniform: 'transmissiveBounces', reset: true },
|
|
21
20
|
maxSubsurfaceSteps: { uniform: 'maxSubsurfaceSteps', reset: true },
|
|
22
21
|
environmentIntensity: { uniform: 'environmentIntensity', reset: true },
|
|
23
22
|
backgroundIntensity: { uniform: 'backgroundIntensity', reset: true },
|
|
23
|
+
backgroundBlurriness: { uniform: 'backgroundBlurriness', reset: true },
|
|
24
|
+
backgroundBlurSamples: { uniform: 'backgroundBlurSamples', reset: true },
|
|
24
25
|
showBackground: { uniform: 'showBackground', reset: true },
|
|
25
26
|
enableEnvironment: { uniform: 'enableEnvironment', reset: true },
|
|
26
27
|
groundProjectionEnabled: { uniform: 'groundProjectionEnabled', reset: true },
|
|
@@ -49,6 +50,7 @@ const SETTING_ROUTES = {
|
|
|
49
50
|
interactionModeEnabled: { handler: 'handleInteractionModeEnabled', reset: false },
|
|
50
51
|
maxSamples: { handler: 'handleMaxSamples', reset: false },
|
|
51
52
|
transparentBackground: { handler: 'handleTransparentBackground' },
|
|
53
|
+
backgroundColor: { handler: 'handleBackgroundColor', reset: true },
|
|
52
54
|
exposure: { handler: 'handleExposure' },
|
|
53
55
|
saturation: { handler: 'handleSaturation' },
|
|
54
56
|
renderLimitMode: { handler: 'handleRenderLimitMode' },
|
|
@@ -136,6 +138,15 @@ export class RenderSettings extends EventDispatcher {
|
|
|
136
138
|
|
|
137
139
|
},
|
|
138
140
|
|
|
141
|
+
handleBackgroundColor: ( value ) => {
|
|
142
|
+
|
|
143
|
+
// Accept a hex string ('#rrggbb') or a Color; THREE.Color converts sRGB → linear working
|
|
144
|
+
// space, which is what the shader adds to radiance.
|
|
145
|
+
const c = value?.isColor ? value : new Color( value );
|
|
146
|
+
stages.pathTracer?.setUniform( 'backgroundColor', c );
|
|
147
|
+
|
|
148
|
+
},
|
|
149
|
+
|
|
139
150
|
handleExposure: ( value ) => {
|
|
140
151
|
|
|
141
152
|
// Three.js applies toneMappingExposure inside the tone-mapping branch,
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -17,7 +17,6 @@ import { buildShadeKernel, SHADE_WG_SIZE } from '../TSL/ShadeKernel.js';
|
|
|
17
17
|
import { buildCompactKernel, buildCompactSubgroupKernel, COMPACT_WG_SIZE } from '../TSL/CompactKernel.js';
|
|
18
18
|
import { buildFinalWriteKernel, FINALWRITE_WG_SIZE } from '../TSL/FinalWriteKernel.js';
|
|
19
19
|
import { buildDebugKernel, DEBUG_WG_SIZE } from '../TSL/DebugKernel.js';
|
|
20
|
-
import { ENGINE_DEFAULTS } from '../EngineDefaults.js';
|
|
21
20
|
import {
|
|
22
21
|
Fn, uint, atomicStore, atomicLoad, instanceIndex, If, Return,
|
|
23
22
|
} from 'three/tsl';
|
|
@@ -35,16 +34,18 @@ export class PathTracer extends PathTracerStage {
|
|
|
35
34
|
this._gBufferAttr = null; // per-pixel first-hit MRT (ND + albedo); see _buildWavefrontKernels
|
|
36
35
|
this._wavefrontReady = false;
|
|
37
36
|
|
|
37
|
+
// Aux MRT (normalDepth + albedo) feeds only the denoiser/OIDN. When no denoiser is active the
|
|
38
|
+
// wavefront skips those writes (Generate/Shade G-buffer + FinalWrite stores). Gated by a live
|
|
39
|
+
// uniform — NOT baked — so DenoisingManager can toggle it without a (UI-freezing) kernel rebuild.
|
|
40
|
+
this._auxGBufferEnabled = false;
|
|
41
|
+
this._auxGBufferUniform = uniform( 0, 'uint' );
|
|
42
|
+
|
|
38
43
|
// CPU sizes per-bounce kernels from last frame's survivor curve; kernels bound on ENTERING_COUNT so over-sizing is safe. (indirect dispatch not viable — three.js doesn't sync compute-written indirect buffers across submissions)
|
|
39
44
|
this._useDynamicDispatch = true;
|
|
40
45
|
|
|
41
46
|
// Flag-gated off: perf-neutral vs atomic-append and adds a 'subgroups' feature dependency.
|
|
42
47
|
this._useSubgroupCompact = false;
|
|
43
48
|
|
|
44
|
-
// Multi-sample pool: S=samplesPerPixel primary rays/pixel/frame (interactive-only, ≤ the pixel cap; else S=1). FinalWrite averages the S slots. Baked into kernels; _ensureSamplesPerPass() rebuilds on change.
|
|
45
|
-
this._multiSampleMaxPixels = ENGINE_DEFAULTS.wavefrontMultiSampleMaxPixels ?? 589824; // 768²
|
|
46
|
-
this._samplesPerPass = 1;
|
|
47
|
-
|
|
48
49
|
this._lastBounceCounts = null;
|
|
49
50
|
// maxBounces the curve was measured at; the curve is ignored once this no longer matches (-1 = none).
|
|
50
51
|
this._lastBounceCountsBudget = - 1;
|
|
@@ -164,19 +165,15 @@ export class PathTracer extends PathTracerStage {
|
|
|
164
165
|
const renderMode = this.renderMode.value;
|
|
165
166
|
|
|
166
167
|
let originalMaxBounces = null;
|
|
167
|
-
let originalSamplesPerPixel = null;
|
|
168
168
|
|
|
169
169
|
if ( renderMode === 1 && frameValue === 0 ) {
|
|
170
170
|
|
|
171
171
|
originalMaxBounces = this.maxBounces.value;
|
|
172
|
-
originalSamplesPerPixel = this.samplesPerPixel.value;
|
|
173
172
|
this.maxBounces.value = 1;
|
|
174
|
-
this.samplesPerPixel.value = 1;
|
|
175
173
|
|
|
176
174
|
}
|
|
177
175
|
|
|
178
176
|
this._handleResize();
|
|
179
|
-
this._ensureSamplesPerPass();
|
|
180
177
|
this.manageASVGFForRenderMode( renderMode );
|
|
181
178
|
|
|
182
179
|
// Full-frame render is always a complete cycle (PER_CYCLE stages gate on this).
|
|
@@ -218,7 +215,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
218
215
|
if ( ! this.cameraOptimizer?.isInInteractionMode() ) this.frameCount ++;
|
|
219
216
|
|
|
220
217
|
if ( originalMaxBounces !== null ) this.maxBounces.value = originalMaxBounces;
|
|
221
|
-
if ( originalSamplesPerPixel !== null ) this.samplesPerPixel.value = originalSamplesPerPixel;
|
|
222
218
|
|
|
223
219
|
this.performanceMonitor?.end();
|
|
224
220
|
return;
|
|
@@ -336,7 +332,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
336
332
|
if ( ! this.cameraOptimizer?.isInInteractionMode() ) this.frameCount ++;
|
|
337
333
|
|
|
338
334
|
if ( originalMaxBounces !== null ) this.maxBounces.value = originalMaxBounces;
|
|
339
|
-
if ( originalSamplesPerPixel !== null ) this.samplesPerPixel.value = originalSamplesPerPixel;
|
|
340
335
|
|
|
341
336
|
this.performanceMonitor?.end();
|
|
342
337
|
|
|
@@ -354,27 +349,16 @@ export class PathTracer extends PathTracerStage {
|
|
|
354
349
|
|
|
355
350
|
}
|
|
356
351
|
|
|
357
|
-
//
|
|
358
|
-
|
|
352
|
+
// Aux MRT (normalDepth/albedo) is needed only by the denoiser/OIDN; DenoisingManager calls this to
|
|
353
|
+
// turn the wavefront's aux writes on/off. It's a live uniform, so toggling is just a value flip +
|
|
354
|
+
// accumulation reset — no kernel rebuild, no UI freeze.
|
|
355
|
+
setAuxGBufferEnabled( enabled ) {
|
|
359
356
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
// S is baked at build but samplesPerPixel/mode can change without a resize; rebuild when the implied S differs.
|
|
367
|
-
_ensureSamplesPerPass() {
|
|
368
|
-
|
|
369
|
-
if ( ! this._wavefrontReady ) return;
|
|
370
|
-
const w = this.storageTextures.renderWidth;
|
|
371
|
-
const h = this.storageTextures.renderHeight;
|
|
372
|
-
if ( this._resolveSamplesPerPass( w, h ) !== this._samplesPerPass ) {
|
|
373
|
-
|
|
374
|
-
this._wavefrontReady = false;
|
|
375
|
-
this._buildWavefrontKernels();
|
|
376
|
-
|
|
377
|
-
}
|
|
357
|
+
enabled = !! enabled;
|
|
358
|
+
if ( this._auxGBufferEnabled === enabled ) return;
|
|
359
|
+
this._auxGBufferEnabled = enabled;
|
|
360
|
+
this._auxGBufferUniform.value = enabled ? 1 : 0;
|
|
361
|
+
this.reset();
|
|
378
362
|
|
|
379
363
|
}
|
|
380
364
|
|
|
@@ -464,12 +448,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
464
448
|
this._readbackFrameCounter = 0;
|
|
465
449
|
this._readbackGeneration ++;
|
|
466
450
|
|
|
467
|
-
// Recompile only when buffers reallocate (capacity grows)
|
|
468
|
-
const
|
|
469
|
-
const neededCap = PackedRayBuffer.requiredCapacity( newW * newH * newS );
|
|
451
|
+
// Recompile only when buffers reallocate (capacity grows); otherwise resize uniforms in place.
|
|
452
|
+
const neededCap = PackedRayBuffer.requiredCapacity( newW * newH );
|
|
470
453
|
const mustRebuild = ! this._packedBuffers
|
|
471
|
-
|| neededCap > this._packedBuffers.capacity
|
|
472
|
-
|| newS !== this._samplesPerPass;
|
|
454
|
+
|| neededCap > this._packedBuffers.capacity;
|
|
473
455
|
|
|
474
456
|
if ( mustRebuild ) {
|
|
475
457
|
|
|
@@ -485,10 +467,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
485
467
|
|
|
486
468
|
}
|
|
487
469
|
|
|
488
|
-
// Same-capacity
|
|
470
|
+
// Same-capacity resize: update render-size uniforms + early-exit threshold, no recompile.
|
|
489
471
|
_resizeWavefrontInPlace( w, h ) {
|
|
490
472
|
|
|
491
|
-
const maxRays = w * h
|
|
473
|
+
const maxRays = w * h;
|
|
492
474
|
this._wfRenderWidth.value = w;
|
|
493
475
|
this._wfRenderHeight.value = h;
|
|
494
476
|
this._wfMaxRayCount.value = maxRays;
|
|
@@ -509,11 +491,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
509
491
|
|
|
510
492
|
const w = this.storageTextures.renderWidth;
|
|
511
493
|
const h = this.storageTextures.renderHeight;
|
|
512
|
-
|
|
513
|
-
this._samplesPerPass = this._resolveSamplesPerPass( w, h );
|
|
514
|
-
const S = this._samplesPerPass | 0;
|
|
515
|
-
const maxRaysPerSample = w * h;
|
|
516
|
-
const maxRays = maxRaysPerSample * S;
|
|
494
|
+
const maxRays = w * h;
|
|
517
495
|
|
|
518
496
|
if ( this._bounceEarlyExitThreshold !== - 1 ) {
|
|
519
497
|
|
|
@@ -534,9 +512,9 @@ export class PathTracer extends PathTracerStage {
|
|
|
534
512
|
// Per-pixel G-buffer (first-hit MRT: ND + albedo), 1 uvec4/pixel — half-precision packed (pack2x16).
|
|
535
513
|
// uint (not f32) buffer: packed lanes can hit the NaN exponent range (e.g. snorm 1.0 → 0x7FFF), which a
|
|
536
514
|
// GPU may canonicalize through f32 storage; u32 stores the bits verbatim. Separate from RAY — it's
|
|
537
|
-
// per-pixel
|
|
515
|
+
// per-pixel, written by Generate/Shade bounce-0 and read only by FinalWrite.
|
|
538
516
|
// 1.25× margin (same as the per-ray buffers) so it survives the in-place-resize range.
|
|
539
|
-
const gBufferVec4s = PackedRayBuffer.requiredCapacity(
|
|
517
|
+
const gBufferVec4s = PackedRayBuffer.requiredCapacity( maxRays ) * GBUFFER_STRIDE;
|
|
540
518
|
this._gBufferAttr = new StorageInstancedBufferAttribute( new Uint32Array( gBufferVec4s * 4 ), 4 );
|
|
541
519
|
const gBufferRW = storage( this._gBufferAttr, 'uvec4' );
|
|
542
520
|
const gBufferRO = storage( this._gBufferAttr, 'uvec4' ).toReadOnly();
|
|
@@ -639,14 +617,13 @@ export class PathTracer extends PathTracerStage {
|
|
|
639
617
|
anamorphicRatio: this.anamorphicRatio,
|
|
640
618
|
renderWidth: this._wfRenderWidth,
|
|
641
619
|
renderHeight: this._wfRenderHeight,
|
|
642
|
-
samplesPerPass: S,
|
|
643
620
|
transmissiveBounces: this.transmissiveBounces,
|
|
644
621
|
transparentBackground: this.transparentBackground,
|
|
622
|
+
auxGBufferEnabled: this._auxGBufferUniform,
|
|
645
623
|
} );
|
|
646
624
|
this._kernelManager.register( 'generate',
|
|
647
625
|
genFn().compute(
|
|
648
|
-
|
|
649
|
-
[ Math.ceil( w / GENERATE_WG_SIZE ), Math.ceil( ( h * S ) / GENERATE_WG_SIZE ), 1 ],
|
|
626
|
+
[ Math.ceil( w / GENERATE_WG_SIZE ), Math.ceil( h / GENERATE_WG_SIZE ), 1 ],
|
|
650
627
|
[ GENERATE_WG_SIZE, GENERATE_WG_SIZE, 1 ]
|
|
651
628
|
)
|
|
652
629
|
);
|
|
@@ -743,6 +720,9 @@ export class PathTracer extends PathTracerStage {
|
|
|
743
720
|
maxSubsurfaceSteps: this.maxSubsurfaceSteps,
|
|
744
721
|
transparentBackground: this.transparentBackground,
|
|
745
722
|
backgroundIntensity: this.backgroundIntensity,
|
|
723
|
+
backgroundColor: this.backgroundColor,
|
|
724
|
+
backgroundBlurriness: this.backgroundBlurriness,
|
|
725
|
+
backgroundBlurSamples: this.backgroundBlurSamples,
|
|
746
726
|
showBackground: this.showBackground,
|
|
747
727
|
globalIlluminationIntensity: this.globalIlluminationIntensity,
|
|
748
728
|
cameraProjectionMatrix: this.cameraProjectionMatrix,
|
|
@@ -760,6 +740,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
760
740
|
reverseMapVec4Offset: this.reverseMapVec4Offset,
|
|
761
741
|
currentBounce: this._wfCurrentBounce,
|
|
762
742
|
maxRayCount: this._wfMaxRayCount,
|
|
743
|
+
auxGBufferEnabled: this._auxGBufferUniform,
|
|
763
744
|
} );
|
|
764
745
|
this._kernelManager.register( 'shade',
|
|
765
746
|
shadeFn().compute(
|
|
@@ -833,8 +814,8 @@ export class PathTracer extends PathTracerStage {
|
|
|
833
814
|
prevAlbedoTexture: prevAlbedo,
|
|
834
815
|
renderWidth: this._wfRenderWidth,
|
|
835
816
|
renderHeight: this._wfRenderHeight,
|
|
836
|
-
samplesPerPass: S,
|
|
837
817
|
visMode: this.visMode,
|
|
818
|
+
auxGBufferEnabled: this._auxGBufferUniform,
|
|
838
819
|
} );
|
|
839
820
|
this._kernelManager.register( 'finalWrite',
|
|
840
821
|
// Per-pixel (w×h) — kernel averages the S sample-slots internally.
|
|
@@ -873,7 +854,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
873
854
|
enableEnvironmentLight: this.enableEnvironment,
|
|
874
855
|
visMode: this.visMode,
|
|
875
856
|
debugVisScale: this.debugVisScale,
|
|
876
|
-
samplesPerPass: this._samplesPerPass,
|
|
877
857
|
albedoMaps: freshAlbedoMaps,
|
|
878
858
|
normalMaps: freshNormalMaps,
|
|
879
859
|
bumpMaps: freshBumpMaps,
|
|
@@ -898,11 +878,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
898
878
|
|
|
899
879
|
const w = this._wfRenderWidth.value;
|
|
900
880
|
const h = this._wfRenderHeight.value;
|
|
901
|
-
const S = this._samplesPerPass | 0;
|
|
902
881
|
|
|
903
882
|
this._kernelManager.setDispatchCount( 'generate', [
|
|
904
883
|
Math.ceil( w / GENERATE_WG_SIZE ),
|
|
905
|
-
Math.ceil(
|
|
884
|
+
Math.ceil( h / GENERATE_WG_SIZE ), 1
|
|
906
885
|
] );
|
|
907
886
|
this._kernelManager.setDispatchCount( 'finalWrite', [
|
|
908
887
|
Math.ceil( w / FINALWRITE_WG_SIZE ),
|
|
@@ -279,18 +279,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
279
279
|
|
|
280
280
|
}
|
|
281
281
|
},
|
|
282
|
-
numRaysPerPixel: {
|
|
283
|
-
get value() {
|
|
284
|
-
|
|
285
|
-
return self.samplesPerPixel.value;
|
|
286
|
-
|
|
287
|
-
},
|
|
288
|
-
set value( v ) {
|
|
289
|
-
|
|
290
|
-
self.samplesPerPixel.value = v;
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
282
|
useEnvMapIS: {
|
|
295
283
|
get value() {
|
|
296
284
|
|
|
@@ -346,7 +334,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
346
334
|
enabled: DEFAULT_STATE.interactionModeEnabled,
|
|
347
335
|
qualitySettings: {
|
|
348
336
|
maxBounceCount: 1,
|
|
349
|
-
numRaysPerPixel: 1,
|
|
350
337
|
useEnvMapIS: false,
|
|
351
338
|
enableAccumulation: false,
|
|
352
339
|
enableEmissiveTriangleSampling: false,
|
package/src/TSL/DebugKernel.js
CHANGED
|
@@ -32,7 +32,6 @@ export function buildDebugKernel( params ) {
|
|
|
32
32
|
visMode, debugVisScale,
|
|
33
33
|
albedoMaps, normalMaps, bumpMaps, metalnessMaps, roughnessMaps, emissiveMaps,
|
|
34
34
|
frame,
|
|
35
|
-
samplesPerPass = 1,
|
|
36
35
|
} = params;
|
|
37
36
|
|
|
38
37
|
const computeFn = Fn( () => {
|
|
@@ -61,8 +60,8 @@ export function buildDebugKernel( params ) {
|
|
|
61
60
|
// Mode 9: visualize the stratified AA-jitter pattern (R,G = jitter).
|
|
62
61
|
If( visMode.equal( int( 9 ) ), () => {
|
|
63
62
|
|
|
64
|
-
//
|
|
65
|
-
const jitter = getStratifiedSample( pixelCoord, int( 0 ), int(
|
|
63
|
+
// One ray per pixel — plain per-frame jitter (totalRays = 1 → random, no stratified lattice).
|
|
64
|
+
const jitter = getStratifiedSample( pixelCoord, int( 0 ), int( 1 ), seed, resolution, frame );
|
|
66
65
|
color.assign( vec4( jitter, 1.0, 1.0 ) );
|
|
67
66
|
|
|
68
67
|
} ).Else( () => {
|
|
@@ -34,12 +34,13 @@ export function buildFinalWriteKernel( params ) {
|
|
|
34
34
|
transparentBackground,
|
|
35
35
|
prevAccumTexture, prevNormalDepthTexture, prevAlbedoTexture,
|
|
36
36
|
renderWidth, renderHeight,
|
|
37
|
-
// Multi-sample: average S sample-slots per pixel (slot = pixel + k*w*h, w*h from the resolution uniform).
|
|
38
|
-
samplesPerPass = 1,
|
|
39
37
|
visMode,
|
|
38
|
+
// Aux MRT (normalDepth + albedo) feeds only the denoiser/OIDN. Gated by a live uniform (1 = denoiser
|
|
39
|
+
// on): when off, skip the G-buffer decode, the prev-frame aux mix, and the two aux stores.
|
|
40
|
+
auxGBufferEnabled,
|
|
40
41
|
} = params;
|
|
41
42
|
|
|
42
|
-
const
|
|
43
|
+
const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
|
|
43
44
|
|
|
44
45
|
const computeFn = Fn( () => {
|
|
45
46
|
|
|
@@ -51,31 +52,21 @@ export function buildFinalWriteKernel( params ) {
|
|
|
51
52
|
const pixelIndex = gy.mul( int( resolution.x ) ).add( gx );
|
|
52
53
|
const rayID = uint( pixelIndex );
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
if ( S <= 1 ) return readRayRadiance( rayBufferRO, rayID );
|
|
58
|
-
const acc = readRayRadiance( rayBufferRO, rayID ).toVar();
|
|
59
|
-
const mrps = uint( resolution.x ).mul( uint( resolution.y ) ).toVar(); // w*h from the resolution uniform, not baked
|
|
60
|
-
for ( let k = 1; k < S; k ++ ) {
|
|
61
|
-
|
|
62
|
-
acc.addAssign( readRayRadiance( rayBufferRO, rayID.add( uint( k ).mul( mrps ) ) ) );
|
|
63
|
-
|
|
64
|
-
}
|
|
55
|
+
const sampleColor = readRayRadiance( rayBufferRO, rayID );
|
|
56
|
+
const finalColor = sampleColor.xyz.toVar();
|
|
57
|
+
const outputAlpha = select( transparentBackground, sampleColor.w, float( 1.0 ) ).toVar();
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
// MRT comes from the per-pixel G-buffer (rayID == pixelIndex). Half-packed: decode.
|
|
60
|
+
// auxOn gates the decode + stores so a no-denoiser frame does no G-buffer read and no aux writes.
|
|
61
|
+
const finalNormalDepth = vec4( 0.0 ).toVar();
|
|
62
|
+
const finalAlbedo = vec4( 0.0 ).xyz.toVar();
|
|
63
|
+
If( auxOn, () => {
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const normalDepth = gbDecodeNormalDepth( gbuf );
|
|
73
|
-
const albedoID = vec4( gbDecodeAlbedo( gbuf ), 0.0 );
|
|
65
|
+
const gbuf = readGBuffer( gBufferRO, rayID );
|
|
66
|
+
finalNormalDepth.assign( gbDecodeNormalDepth( gbuf ) );
|
|
67
|
+
finalAlbedo.assign( vec4( gbDecodeAlbedo( gbuf ), 0.0 ).xyz );
|
|
74
68
|
|
|
75
|
-
|
|
76
|
-
const finalNormalDepth = normalDepth.toVar();
|
|
77
|
-
const finalAlbedo = albedoID.xyz.toVar();
|
|
78
|
-
const outputAlpha = select( transparentBackground, sampleColor.w, float( 1.0 ) ).toVar();
|
|
69
|
+
} );
|
|
79
70
|
|
|
80
71
|
const pixelCoord = vec2( float( gx ).add( 0.5 ), float( gy ).add( 0.5 ) );
|
|
81
72
|
const prevUV = pixelCoord.div( resolution );
|
|
@@ -87,8 +78,12 @@ export function buildFinalWriteKernel( params ) {
|
|
|
87
78
|
const prevAccumSample = texture( prevAccumTexture, prevUV, 0 ).toVar();
|
|
88
79
|
|
|
89
80
|
finalColor.assign( mix( prevAccumSample.xyz, sampleColor.xyz, accumulationAlpha ) );
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
If( auxOn, () => {
|
|
82
|
+
|
|
83
|
+
finalNormalDepth.assign( mix( texture( prevNormalDepthTexture, prevUV, 0 ), finalNormalDepth, accumulationAlpha ) );
|
|
84
|
+
finalAlbedo.assign( mix( texture( prevAlbedoTexture, prevUV, 0 ).xyz, finalAlbedo, accumulationAlpha ) );
|
|
85
|
+
|
|
86
|
+
} );
|
|
92
87
|
|
|
93
88
|
If( transparentBackground, () => {
|
|
94
89
|
|
|
@@ -107,8 +102,12 @@ export function buildFinalWriteKernel( params ) {
|
|
|
107
102
|
|
|
108
103
|
const uintCoord = uvec2( uint( gx ), uint( gy ) );
|
|
109
104
|
textureStore( writeColorTex, uintCoord, vec4( finalColor, outputAlpha ) ).toWriteOnly();
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
If( auxOn, () => {
|
|
106
|
+
|
|
107
|
+
textureStore( writeNDTex, uintCoord, finalNormalDepth ).toWriteOnly();
|
|
108
|
+
textureStore( writeAlbedoTex, uintCoord, vec4( finalAlbedo, 1.0 ) ).toWriteOnly();
|
|
109
|
+
|
|
110
|
+
} );
|
|
112
111
|
|
|
113
112
|
} );
|
|
114
113
|
|
|
@@ -33,39 +33,32 @@ export function buildGenerateKernel( params ) {
|
|
|
33
33
|
cameraWorldMatrix, cameraProjectionMatrixInverse,
|
|
34
34
|
enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
|
|
35
35
|
renderWidth, renderHeight,
|
|
36
|
-
// Multi-sample: S primary rays/pixel/frame; S>1 dispatch covers h*S rows, ray lands in slot subSample*(w*h) + pixelIndex.
|
|
37
|
-
samplesPerPass = 1,
|
|
38
36
|
transmissiveBounces, // per-ray refraction budget (megakernel parity: PathTracerCore.js:606)
|
|
39
37
|
transparentBackground, // alpha inits to 1 here (megakernel parity: PathTracerCore.js:554) — env-escape-without-opaque zeroes it in Shade
|
|
38
|
+
auxGBufferEnabled, // live uniform: 1 = init the per-pixel G-buffer (denoiser on), 0 = skip it
|
|
40
39
|
} = params;
|
|
41
40
|
|
|
42
|
-
const
|
|
41
|
+
const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
|
|
43
42
|
|
|
44
43
|
const computeFn = Fn( () => {
|
|
45
44
|
|
|
46
45
|
const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( localId.x ) );
|
|
47
|
-
const
|
|
46
|
+
const gy = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
const gy = S > 1 ? gyRaw.sub( subSample.mul( renderHeight ) ).toVar() : gyRaw;
|
|
51
|
-
const yBound = S > 1 ? renderHeight.mul( int( S ) ) : renderHeight;
|
|
52
|
-
|
|
53
|
-
If( gx.lessThan( renderWidth ).and( gyRaw.lessThan( yBound ) ), () => {
|
|
48
|
+
If( gx.lessThan( renderWidth ).and( gy.lessThan( renderHeight ) ), () => {
|
|
54
49
|
|
|
55
50
|
const pixelCoord = vec2( float( gx ).add( 0.5 ), float( gy ).add( 0.5 ) );
|
|
56
51
|
const pixelIndex = gy.mul( int( resolution.x ) ).add( gx );
|
|
57
|
-
//
|
|
58
|
-
const rayID =
|
|
59
|
-
? uint( pixelIndex ).add( uint( subSample ).mul( uint( resolution.x ).mul( uint( resolution.y ) ) ) )
|
|
60
|
-
: uint( pixelIndex );
|
|
52
|
+
// One ray per pixel: rayID is the pixel index.
|
|
53
|
+
const rayID = uint( pixelIndex );
|
|
61
54
|
|
|
62
55
|
const screenPosition = pixelCoord.div( resolution ).mul( 2.0 ).sub( 1.0 ).toVar();
|
|
63
56
|
screenPosition.y.assign( screenPosition.y.negate() );
|
|
64
57
|
|
|
65
|
-
const baseSeed = getDecorrelatedSeed( { pixelCoord, rayIndex:
|
|
58
|
+
const baseSeed = getDecorrelatedSeed( { pixelCoord, rayIndex: int( 0 ), frame } ).toVar();
|
|
66
59
|
const seed = pcgHash( { state: baseSeed } ).toVar();
|
|
67
60
|
|
|
68
|
-
const stratifiedJitter = getStratifiedSample( pixelCoord,
|
|
61
|
+
const stratifiedJitter = getStratifiedSample( pixelCoord, int( 0 ), int( 1 ), seed, resolution, frame ).toVar();
|
|
69
62
|
|
|
70
63
|
const jitterScale = vec2( 2.0 ).div( resolution );
|
|
71
64
|
const jitter = stratifiedJitter.sub( 0.5 ).mul( jitterScale );
|
|
@@ -78,6 +71,8 @@ export function buildGenerateKernel( params ) {
|
|
|
78
71
|
) );
|
|
79
72
|
|
|
80
73
|
writeRayOriginMeta( rayBufferRW, rayID, ray.origin, int( 0 ), int( 0 ) );
|
|
74
|
+
// A fresh camera ray is NOT redirected — REDIRECTED stays clear so it sees the direct backdrop;
|
|
75
|
+
// ShadeKernel sets REDIRECTED on the first direction-changing interaction (see RAY_FLAG.REDIRECTED).
|
|
81
76
|
writeRayDirFlags( rayBufferRW, rayID, ray.direction, uint( RAY_FLAG.ACTIVE ) );
|
|
82
77
|
// pdf inits to 0 = prevBouncePdf (megakernel parity PathTracerCore.js:556). The bounce>0 env/emissive
|
|
83
78
|
// MIS gate skips until an opaque scatter writes a real combinedPdf; free bounces preserve it.
|
|
@@ -87,7 +82,7 @@ export function buildGenerateKernel( params ) {
|
|
|
87
82
|
// keeps alpha 1 → solid. Non-transparent mode is inert (FinalWrite forces alpha 1).
|
|
88
83
|
writeRayRadiance( rayBufferRW, rayID, vec4( vec3( 0.0 ), select( transparentBackground, float( 1.0 ), float( 0.0 ) ) ) );
|
|
89
84
|
|
|
90
|
-
If(
|
|
85
|
+
If( auxOn, () => {
|
|
91
86
|
|
|
92
87
|
// default: normal +Z, depth 1 (far), black albedo (background/miss)
|
|
93
88
|
writeGBuffer( gBufferRW, uint( pixelIndex ), vec3( 0.0, 0.0, 1.0 ), float( 1.0 ), vec3( 0.0 ) );
|