rayzee 5.4.0 → 5.4.2
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 +2821 -2784
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +54 -54
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/PathTracerApp.js +1 -2
- package/src/Processor/AssetLoader.js +40 -18
- package/src/Processor/EquirectHDRInfo.js +38 -29
- package/src/Processor/GeometryExtractor.js +19 -2
- package/src/Processor/InstanceTable.js +16 -0
- package/src/Processor/SceneProcessor.js +22 -33
- package/src/Processor/ShaderBuilder.js +14 -17
- package/src/Processor/TLASBuilder.js +9 -4
- package/src/Stages/PathTracer.js +100 -43
- package/src/TSL/BVHTraversal.js +34 -74
- package/src/TSL/Clearcoat.js +1 -1
- package/src/TSL/Displacement.js +1 -1
- package/src/TSL/EmissiveSampling.js +17 -13
- package/src/TSL/Environment.js +12 -9
- package/src/TSL/LightBVHSampling.js +3 -2
- package/src/TSL/LightsCore.js +1 -1
- package/src/TSL/LightsDirect.js +14 -1
- package/src/TSL/LightsIndirect.js +0 -1
- package/src/TSL/LightsSampling.js +2 -2
- package/src/TSL/MaterialTransmission.js +1 -1
- package/src/TSL/PathTracer.js +4 -4
- package/src/TSL/PathTracerCore.js +6 -6
- package/src/TSL/Struct.js +1 -1
- package/src/TSL/TextureSampling.js +1 -1
- package/src/TSL/patches.js +145 -0
- package/src/index.js +1 -1
- package/src/managers/EnvironmentManager.js +32 -56
- package/src/managers/MaterialDataManager.js +93 -3
- package/src/managers/UniformManager.js +3 -0
- package/src/TSL/structProxy.js +0 -87
- package/src/TSL/wgslGlobalVarsPatch.js +0 -60
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
fract,
|
|
31
31
|
} from 'three/tsl';
|
|
32
32
|
|
|
33
|
-
import { struct } from './
|
|
33
|
+
import { struct } from './patches.js';
|
|
34
34
|
import { Ray, RayTracingMaterial, RenderState, HitInfo, DotProducts, DirectionSample } from './Struct.js';
|
|
35
35
|
import { PI, EPSILON, MIN_ROUGHNESS, MIN_CLEARCOAT_ROUGHNESS, computeDotProducts } from './Common.js';
|
|
36
36
|
import { iorToFresnel0, fresnelSchlickFloat } from './Fresnel.js';
|
package/src/TSL/PathTracer.js
CHANGED
|
@@ -137,14 +137,14 @@ export const pathTracerMain = ( params ) => {
|
|
|
137
137
|
pointLightsBuffer, numPointLights,
|
|
138
138
|
spotLightsBuffer, numSpotLights,
|
|
139
139
|
envTexture, environmentIntensity, envMatrix,
|
|
140
|
-
|
|
140
|
+
envCDFBuffer,
|
|
141
141
|
envTotalSum, envResolution,
|
|
142
142
|
enableEnvironmentLight, useEnvMapIS,
|
|
143
143
|
maxBounceCount, transmissiveBounces,
|
|
144
144
|
showBackground, transparentBackground, backgroundIntensity,
|
|
145
145
|
fireflyThreshold, globalIlluminationIntensity,
|
|
146
146
|
totalTriangleCount, enableEmissiveTriangleSampling,
|
|
147
|
-
emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
147
|
+
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
148
148
|
lightBVHBuffer, lightBVHNodeCount,
|
|
149
149
|
debugVisScale,
|
|
150
150
|
enableAccumulation, hasPreviousAccumulated,
|
|
@@ -284,14 +284,14 @@ export const pathTracerMain = ( params ) => {
|
|
|
284
284
|
pointLightsBuffer, numPointLights,
|
|
285
285
|
spotLightsBuffer, numSpotLights,
|
|
286
286
|
envTexture, environmentIntensity, envMatrix,
|
|
287
|
-
|
|
287
|
+
envCDFBuffer,
|
|
288
288
|
envTotalSum, envResolution,
|
|
289
289
|
enableEnvironmentLight, useEnvMapIS,
|
|
290
290
|
maxBounceCount, transmissiveBounces,
|
|
291
291
|
backgroundIntensity, showBackground, transparentBackground,
|
|
292
292
|
fireflyThreshold, globalIlluminationIntensity,
|
|
293
293
|
totalTriangleCount, enableEmissiveTriangleSampling,
|
|
294
|
-
emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
294
|
+
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
295
295
|
lightBVHBuffer, lightBVHNodeCount,
|
|
296
296
|
pixelCoord, resolution, frame,
|
|
297
297
|
) );
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
sampler,
|
|
42
42
|
} from 'three/tsl';
|
|
43
43
|
|
|
44
|
-
import { struct } from './
|
|
44
|
+
import { struct } from './patches.js';
|
|
45
45
|
|
|
46
46
|
import {
|
|
47
47
|
PI_INV,
|
|
@@ -590,7 +590,7 @@ export const Trace = Fn( ( [
|
|
|
590
590
|
spotLightsBuffer, numSpotLights,
|
|
591
591
|
// Environment
|
|
592
592
|
envTexture, environmentIntensity, envMatrix,
|
|
593
|
-
|
|
593
|
+
envCDFBuffer,
|
|
594
594
|
envTotalSum, envResolution,
|
|
595
595
|
enableEnvironmentLight, useEnvMapIS,
|
|
596
596
|
// Rendering parameters
|
|
@@ -598,7 +598,7 @@ export const Trace = Fn( ( [
|
|
|
598
598
|
backgroundIntensity, showBackground, transparentBackground,
|
|
599
599
|
fireflyThreshold, globalIlluminationIntensity,
|
|
600
600
|
totalTriangleCount, enableEmissiveTriangleSampling,
|
|
601
|
-
emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
601
|
+
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
602
602
|
lightBVHBuffer, lightBVHNodeCount,
|
|
603
603
|
// Per-pixel info
|
|
604
604
|
pixelCoord, resolution, frame,
|
|
@@ -1013,7 +1013,7 @@ export const Trace = Fn( ( [
|
|
|
1013
1013
|
triangleBuffer,
|
|
1014
1014
|
materialBuffer,
|
|
1015
1015
|
envTexture, environmentIntensity, envMatrix,
|
|
1016
|
-
|
|
1016
|
+
envCDFBuffer,
|
|
1017
1017
|
envTotalSum, envResolution,
|
|
1018
1018
|
enableEnvironmentLight,
|
|
1019
1019
|
);
|
|
@@ -1040,6 +1040,7 @@ export const Trace = Fn( ( [
|
|
|
1040
1040
|
rngState,
|
|
1041
1041
|
lightBVHBuffer,
|
|
1042
1042
|
emissiveTriangleBuffer,
|
|
1043
|
+
emissiveVec4Offset,
|
|
1043
1044
|
triangleBuffer,
|
|
1044
1045
|
) );
|
|
1045
1046
|
|
|
@@ -1091,7 +1092,7 @@ export const Trace = Fn( ( [
|
|
|
1091
1092
|
hitInfo.hitPoint, N, V, material,
|
|
1092
1093
|
totalTriangleCount, bounceIndex, rngState,
|
|
1093
1094
|
emissiveBoost,
|
|
1094
|
-
emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
|
|
1095
|
+
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
1095
1096
|
triangleBuffer,
|
|
1096
1097
|
traceShadowRayWrapped,
|
|
1097
1098
|
evaluateMaterialResponse,
|
|
@@ -1132,7 +1133,6 @@ export const Trace = Fn( ( [
|
|
|
1132
1133
|
rngState,
|
|
1133
1134
|
samplingInfo,
|
|
1134
1135
|
envTexture, environmentIntensity, envMatrix,
|
|
1135
|
-
envMarginalWeights, envConditionalWeights,
|
|
1136
1136
|
envTotalSum, envResolution,
|
|
1137
1137
|
enableEnvironmentLight, useEnvMapIS,
|
|
1138
1138
|
) );
|
package/src/TSL/Struct.js
CHANGED
|
@@ -281,7 +281,7 @@ export const processBump = Fn( ( [ bumpMaps, currentNormal, material, uvCache ]
|
|
|
281
281
|
|
|
282
282
|
const result = currentNormal.toVar();
|
|
283
283
|
|
|
284
|
-
If( material.bumpMapIndex.greaterThanEqual( int( 0 ) ), () => {
|
|
284
|
+
If( material.bumpMapIndex.greaterThanEqual( int( 0 ) ).and( material.bumpScale.greaterThan( 0.0 ) ), () => {
|
|
285
285
|
|
|
286
286
|
// Approximate texel size
|
|
287
287
|
const texelSize = vec2( 1.0 / 1024.0 ).toVar();
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rayzee patches for Three.js / TSL.
|
|
3
|
+
*
|
|
4
|
+
* Side-effect on import: installs `WebGPUBackend.createNodeBuilder` override
|
|
5
|
+
* (restores r183 function-scoped `var` emission for compute shaders — prevents
|
|
6
|
+
* a register-allocation regression in the path tracer's hot loop).
|
|
7
|
+
*
|
|
8
|
+
* Export: `struct()` — drop-in replacement for TSL's `struct()` returning
|
|
9
|
+
* a proxy factory that supports GLSL-style dot-notation field access.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { WebGPUBackend } from 'three/webgpu';
|
|
13
|
+
import { struct as _struct } from 'three/tsl';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// 1. WGSL global-variable promotion patch (compute-only)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Three.js r184 introduced `WGSLNodeBuilder.allowGlobalVariables = true`, which
|
|
19
|
+
// emits `.toVar()` declarations at WGSL module scope as `var<private> name : T`
|
|
20
|
+
// instead of function-local `var name : T` inside `fn main()` (as r183 did).
|
|
21
|
+
//
|
|
22
|
+
// For compute shaders with hundreds of `.toVar()` calls in loops (e.g. the BVH
|
|
23
|
+
// traversal + BRDF path tracer), `var<private>` increases GPU register pressure
|
|
24
|
+
// because the Dawn/Chromium WGSL compiler cannot aggressively register-allocate
|
|
25
|
+
// variables with a stable per-invocation memory address. We measured a ~8% fps
|
|
26
|
+
// regression (120 → 110) on the path tracer after upgrading r183 → r184 that
|
|
27
|
+
// traced entirely to GPU execution, not CPU.
|
|
28
|
+
//
|
|
29
|
+
// `allowGlobalVariables` is ONLY consumed by the compute template
|
|
30
|
+
// (`_getWGSLComputeCode`). The vertex/fragment templates always emit
|
|
31
|
+
// `shaderData.vars` at module scope and REQUIRE `allowGlobalVariables=true`
|
|
32
|
+
// (emitting function-local `var` at module scope is invalid WGSL and crashes
|
|
33
|
+
// pipeline creation with "Invalid ShaderModule"). We install a per-instance
|
|
34
|
+
// accessor that returns `false` only when the builder is for a compute node
|
|
35
|
+
// (material === null) and `true` otherwise, so render pipelines keep r184
|
|
36
|
+
// behavior untouched.
|
|
37
|
+
//
|
|
38
|
+
// Relevant upstream lines:
|
|
39
|
+
// - `node_modules/three/src/renderers/webgpu/nodes/WGSLNodeBuilder.js:247`
|
|
40
|
+
// (`this.allowGlobalVariables = true`)
|
|
41
|
+
// - `...WGSLNodeBuilder.js:2458` (module-scope vars block)
|
|
42
|
+
// - `...WGSLNodeBuilder.js:2467` (function-body vars block)
|
|
43
|
+
//
|
|
44
|
+
// Revisit if upstream adds an official opt-out or fixes register pressure.
|
|
45
|
+
|
|
46
|
+
const _origCreateNodeBuilder = WebGPUBackend.prototype.createNodeBuilder;
|
|
47
|
+
|
|
48
|
+
WebGPUBackend.prototype.createNodeBuilder = function ( object, renderer ) {
|
|
49
|
+
|
|
50
|
+
const builder = _origCreateNodeBuilder.call( this, object, renderer );
|
|
51
|
+
|
|
52
|
+
Object.defineProperty( builder, 'allowGlobalVariables', {
|
|
53
|
+
get() {
|
|
54
|
+
|
|
55
|
+
return this.material !== null;
|
|
56
|
+
|
|
57
|
+
},
|
|
58
|
+
set() { /* ignore — the value is derived from material presence */ },
|
|
59
|
+
configurable: true,
|
|
60
|
+
} );
|
|
61
|
+
|
|
62
|
+
return builder;
|
|
63
|
+
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// 2. TSL struct proxy — enables GLSL-style dot-notation field access
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// TSL structs require `.get('fieldName')` for member access, but GLSL-style
|
|
70
|
+
// dot notation (`.fieldName`) is more natural and matches ported code.
|
|
71
|
+
//
|
|
72
|
+
// This wraps TSL's `struct()` so that:
|
|
73
|
+
// - Direct construction: `MyStruct({...}).toVar('x')` → `.fieldName` works
|
|
74
|
+
// - Fn return values: `MyStruct.wrap(someFn(...))` → `.fieldName` works
|
|
75
|
+
//
|
|
76
|
+
// Property access for known struct member names is redirected to `.get('name')`.
|
|
77
|
+
// Swizzle properties (x, y, z, w, etc.), Node methods (.add, .assign, etc.), and
|
|
78
|
+
// other standard properties pass through to the underlying node unmodified.
|
|
79
|
+
|
|
80
|
+
function createStructProxy( node, memberSet ) {
|
|
81
|
+
|
|
82
|
+
return new Proxy( node, {
|
|
83
|
+
|
|
84
|
+
get( target, prop, receiver ) {
|
|
85
|
+
|
|
86
|
+
// Intercept known struct member names
|
|
87
|
+
if ( typeof prop === 'string' && memberSet.has( prop ) ) {
|
|
88
|
+
|
|
89
|
+
return target.get( prop );
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const val = Reflect.get( target, prop, receiver );
|
|
94
|
+
|
|
95
|
+
// Intercept .toVar() to proxy-wrap the result
|
|
96
|
+
if ( prop === 'toVar' && typeof val === 'function' ) {
|
|
97
|
+
|
|
98
|
+
return ( ...args ) => createStructProxy( val.apply( target, args ), memberSet );
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return val;
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
} );
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Drop-in replacement for TSL's `struct()` that returns a proxy-enhanced factory.
|
|
112
|
+
*
|
|
113
|
+
* The returned factory:
|
|
114
|
+
* - Creates struct nodes where `.toVar()` results support dot-notation field access
|
|
115
|
+
* - Has `.wrap(node)` method to proxy-wrap Fn return values for field access
|
|
116
|
+
* - Has `.layout` and `.isStruct` matching the original TSL struct API
|
|
117
|
+
*
|
|
118
|
+
* @param {Object} members - Struct member layout (e.g., { didHit: 'bool', dst: 'float' })
|
|
119
|
+
* @param {string|null} name - Optional struct name
|
|
120
|
+
* @returns {Function} Enhanced struct factory
|
|
121
|
+
*/
|
|
122
|
+
export function struct( members, name = null ) {
|
|
123
|
+
|
|
124
|
+
const factory = _struct( members, name );
|
|
125
|
+
const memberSet = new Set( Object.keys( members ) );
|
|
126
|
+
|
|
127
|
+
const wrappedFactory = ( ...args ) => {
|
|
128
|
+
|
|
129
|
+
const node = factory( ...args );
|
|
130
|
+
return createStructProxy( node, memberSet );
|
|
131
|
+
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
wrappedFactory.layout = factory.layout;
|
|
135
|
+
wrappedFactory.isStruct = true;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Wrap an existing node (e.g., Fn return value) with struct field access proxy.
|
|
139
|
+
* Usage: `const hit = HitInfo.wrap(traverseBVH(...).toVar('hit'));`
|
|
140
|
+
*/
|
|
141
|
+
wrappedFactory.wrap = ( node ) => createStructProxy( node, memberSet );
|
|
142
|
+
|
|
143
|
+
return wrappedFactory;
|
|
144
|
+
|
|
145
|
+
}
|
package/src/index.js
CHANGED
|
@@ -43,11 +43,11 @@ export class EnvironmentManager {
|
|
|
43
43
|
this.environmentTexture = this._envPlaceholder;
|
|
44
44
|
this.envTexSize = new Vector2();
|
|
45
45
|
|
|
46
|
-
// CDF storage
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.
|
|
50
|
-
this.
|
|
46
|
+
// CDF storage buffer (marginal + conditional packed into one buffer).
|
|
47
|
+
// Layout: [ marginal (envResolution.y floats) | conditional (envResolution.x * envResolution.y floats) ]
|
|
48
|
+
// Conditional offset is the marginal length, which equals envResolution.y at runtime.
|
|
49
|
+
this.envCDFStorageAttr = null;
|
|
50
|
+
this.envCDFStorageNode = null;
|
|
51
51
|
this._initCDFStorageBuffers();
|
|
52
52
|
|
|
53
53
|
// Environment rotation
|
|
@@ -177,74 +177,52 @@ export class EnvironmentManager {
|
|
|
177
177
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
// ===== CDF STORAGE
|
|
180
|
+
// ===== CDF STORAGE BUFFER =====
|
|
181
181
|
|
|
182
182
|
/**
|
|
183
|
-
* Initialize CDF storage
|
|
184
|
-
* Must be called before shader compilation so the
|
|
183
|
+
* Initialize the packed CDF storage buffer with placeholder data.
|
|
184
|
+
* Must be called before shader compilation so the node exists in the graph.
|
|
185
|
+
*
|
|
186
|
+
* Layout: [ marginal (size = envResolution.y) | conditional (size = envResolution.x * envResolution.y) ]
|
|
187
|
+
* Placeholder shape is a 1x2 env map: marginal=[0,1], conditional=[0,0,1,1].
|
|
185
188
|
* @private
|
|
186
189
|
*/
|
|
187
190
|
_initCDFStorageBuffers() {
|
|
188
191
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
this.
|
|
192
|
-
this.envMarginalStorageNode = storage( this.envMarginalStorageAttr, 'float', 2 ).toReadOnly();
|
|
193
|
-
|
|
194
|
-
// Conditional: 1 float per entry, default placeholder
|
|
195
|
-
const conditionalPlaceholder = new Float32Array( [ 0, 0, 1, 1 ] );
|
|
196
|
-
this.envConditionalStorageAttr = new StorageInstancedBufferAttribute( conditionalPlaceholder, 1 );
|
|
197
|
-
this.envConditionalStorageNode = storage( this.envConditionalStorageAttr, 'float', 4 ).toReadOnly();
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Update marginal CDF storage buffer from Float32Array.
|
|
203
|
-
*/
|
|
204
|
-
setEnvMarginalData( floatData ) {
|
|
205
|
-
|
|
206
|
-
if ( ! floatData ) return;
|
|
207
|
-
|
|
208
|
-
this.envMarginalStorageAttr = new StorageInstancedBufferAttribute( floatData, 1 );
|
|
209
|
-
this.envMarginalStorageNode.value = this.envMarginalStorageAttr;
|
|
210
|
-
this.envMarginalStorageNode.bufferCount = floatData.length;
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Update conditional CDF storage buffer from Float32Array.
|
|
216
|
-
*/
|
|
217
|
-
setEnvConditionalData( floatData ) {
|
|
218
|
-
|
|
219
|
-
if ( ! floatData ) return;
|
|
220
|
-
|
|
221
|
-
this.envConditionalStorageAttr = new StorageInstancedBufferAttribute( floatData, 1 );
|
|
222
|
-
this.envConditionalStorageNode.value = this.envConditionalStorageAttr;
|
|
223
|
-
this.envConditionalStorageNode.bufferCount = floatData.length;
|
|
192
|
+
const placeholder = new Float32Array( [ 0, 1, 0, 0, 1, 1 ] );
|
|
193
|
+
this.envCDFStorageAttr = new StorageInstancedBufferAttribute( placeholder, 1 );
|
|
194
|
+
this.envCDFStorageNode = storage( this.envCDFStorageAttr, 'float', placeholder.length ).toReadOnly();
|
|
224
195
|
|
|
225
196
|
}
|
|
226
197
|
|
|
227
198
|
/**
|
|
228
|
-
* Update
|
|
199
|
+
* Update the packed CDF storage buffer from equirectHdrInfo.
|
|
200
|
+
* Concatenates marginal + conditional into one buffer.
|
|
229
201
|
* @private
|
|
230
202
|
*/
|
|
231
203
|
_updateCDFStorageBuffers() {
|
|
232
204
|
|
|
233
|
-
|
|
234
|
-
|
|
205
|
+
const marginal = this.equirectHdrInfo.marginalData;
|
|
206
|
+
const conditional = this.equirectHdrInfo.conditionalData;
|
|
207
|
+
if ( ! marginal || ! conditional ) return;
|
|
208
|
+
|
|
209
|
+
const combined = new Float32Array( marginal.length + conditional.length );
|
|
210
|
+
combined.set( marginal, 0 );
|
|
211
|
+
combined.set( conditional, marginal.length );
|
|
212
|
+
|
|
213
|
+
this.envCDFStorageAttr = new StorageInstancedBufferAttribute( combined, 1 );
|
|
214
|
+
this.envCDFStorageNode.value = this.envCDFStorageAttr;
|
|
215
|
+
this.envCDFStorageNode.bufferCount = combined.length;
|
|
235
216
|
|
|
236
217
|
}
|
|
237
218
|
|
|
238
219
|
/**
|
|
239
|
-
* Get CDF storage
|
|
240
|
-
* @returns {{
|
|
220
|
+
* Get the packed CDF storage node for shader graph.
|
|
221
|
+
* @returns {{ cdfNode: StorageNode }}
|
|
241
222
|
*/
|
|
242
223
|
getCDFStorageNodes() {
|
|
243
224
|
|
|
244
|
-
return {
|
|
245
|
-
marginalNode: this.envMarginalStorageNode,
|
|
246
|
-
conditionalNode: this.envConditionalStorageNode,
|
|
247
|
-
};
|
|
225
|
+
return { cdfNode: this.envCDFStorageNode };
|
|
248
226
|
|
|
249
227
|
}
|
|
250
228
|
|
|
@@ -559,10 +537,8 @@ export class EnvironmentManager {
|
|
|
559
537
|
|
|
560
538
|
this.proceduralSkyRenderer = null;
|
|
561
539
|
this.simpleSkyRenderer = null;
|
|
562
|
-
this.
|
|
563
|
-
this.
|
|
564
|
-
this.envConditionalStorageAttr = null;
|
|
565
|
-
this.envConditionalStorageNode = null;
|
|
540
|
+
this.envCDFStorageAttr = null;
|
|
541
|
+
this.envCDFStorageNode = null;
|
|
566
542
|
this._envPlaceholder?.dispose();
|
|
567
543
|
this._envPlaceholder = null;
|
|
568
544
|
this.environmentTexture = null;
|
|
@@ -9,9 +9,16 @@
|
|
|
9
9
|
|
|
10
10
|
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
11
11
|
import { storage } from 'three/tsl';
|
|
12
|
-
import {
|
|
12
|
+
import { MATERIAL_DATA_LAYOUT as M, TRIANGLE_DATA_LAYOUT as T } from '../EngineDefaults.js';
|
|
13
13
|
|
|
14
14
|
const PIXELS_PER_MATERIAL = M.SLOTS_PER_MATERIAL;
|
|
15
|
+
// Per-triangle float offsets used by _patchTriangleSideForMaterial / _patchTriangleBlockerForMaterial.
|
|
16
|
+
const TRI_MAT_IDX_OFFSET = T.UV_C_MAT_OFFSET + 2; // uvData2.z in shader
|
|
17
|
+
const TRI_SIDE_OFFSET = T.NORMAL_C_OFFSET + 3; // normalCData.w in shader
|
|
18
|
+
const TRI_BLOCKER_OFFSET = T.NORMAL_A_OFFSET + 3; // nA.w in shader (opaque-blocker fast path)
|
|
19
|
+
|
|
20
|
+
// Material properties that affect the shadow-ray opaque-blocker flag.
|
|
21
|
+
const BLOCKER_PROPS = new Set( [ 'transmission', 'transparent', 'opacity', 'alphaMode' ] );
|
|
15
22
|
|
|
16
23
|
export class MaterialDataManager {
|
|
17
24
|
|
|
@@ -41,7 +48,7 @@ export class MaterialDataManager {
|
|
|
41
48
|
|
|
42
49
|
/**
|
|
43
50
|
* Optional callbacks set by the owning stage.
|
|
44
|
-
* @type {{ onReset?: Function, onFeaturesChanged?: Function }}
|
|
51
|
+
* @type {{ onReset?: Function, onFeaturesChanged?: Function, getTriangleData?: Function, onTriangleDataChanged?: Function }}
|
|
45
52
|
*/
|
|
46
53
|
this.callbacks = {};
|
|
47
54
|
|
|
@@ -275,7 +282,11 @@ export class MaterialDataManager {
|
|
|
275
282
|
case 'clearcoat': data[ stride + M.CLEARCOAT ] = value; break;
|
|
276
283
|
case 'clearcoatRoughness': data[ stride + M.CLEARCOAT_ROUGHNESS ] = value; break;
|
|
277
284
|
case 'opacity': data[ stride + M.OPACITY ] = value; break;
|
|
278
|
-
case 'side': data[ stride + M.SIDE ] = value;
|
|
285
|
+
case 'side': data[ stride + M.SIDE ] = value;
|
|
286
|
+
// Side is also mirrored into per-triangle data (NORMAL_C.w) so BVH
|
|
287
|
+
// traversal can do side culling without reading the material buffer.
|
|
288
|
+
this._patchTriangleSideForMaterial( materialIndex, value );
|
|
289
|
+
break;
|
|
279
290
|
case 'transparent': data[ stride + M.TRANSPARENT ] = value; break;
|
|
280
291
|
case 'alphaTest': data[ stride + M.ALPHA_TEST ] = value; break;
|
|
281
292
|
case 'alphaMode': data[ stride + M.ALPHA_MODE ] = value; break;
|
|
@@ -304,6 +315,13 @@ export class MaterialDataManager {
|
|
|
304
315
|
|
|
305
316
|
this.materialStorageAttr.needsUpdate = true;
|
|
306
317
|
|
|
318
|
+
// Recompute triangle-data opaque-blocker flag when any input to it changes.
|
|
319
|
+
if ( BLOCKER_PROPS.has( property ) ) {
|
|
320
|
+
|
|
321
|
+
this._recomputeOpaqueBlockerForMaterial( materialIndex );
|
|
322
|
+
|
|
323
|
+
}
|
|
324
|
+
|
|
307
325
|
const featureProperties = [ 'transmission', 'clearcoat', 'sheen', 'iridescence', 'dispersion', 'transparent', 'opacity', 'alphaTest' ];
|
|
308
326
|
if ( featureProperties.includes( property ) ) {
|
|
309
327
|
|
|
@@ -414,6 +432,10 @@ export class MaterialDataManager {
|
|
|
414
432
|
data[ stride + M.CLEARCOAT_ROUGHNESS ] = materialData.clearcoatRoughness ?? 0;
|
|
415
433
|
data[ stride + M.OPACITY ] = materialData.opacity ?? 1;
|
|
416
434
|
data[ stride + M.SIDE ] = materialData.side ?? 0;
|
|
435
|
+
// Mirror side into per-triangle data so BVH traversal avoids a material-buffer read.
|
|
436
|
+
this._patchTriangleSideForMaterial( materialIndex, materialData.side ?? 0 );
|
|
437
|
+
// Recompute shadow-ray opaque-blocker flag (reads alphaMode/transparent/transmission/opacity from buffer).
|
|
438
|
+
this._recomputeOpaqueBlockerForMaterial( materialIndex );
|
|
417
439
|
data[ stride + M.TRANSPARENT ] = materialData.transparent ?? 0;
|
|
418
440
|
data[ stride + M.ALPHA_TEST ] = materialData.alphaTest ?? 0;
|
|
419
441
|
data[ stride + M.ALPHA_MODE ] = materialData.alphaMode ?? 0;
|
|
@@ -657,6 +679,74 @@ export class MaterialDataManager {
|
|
|
657
679
|
|
|
658
680
|
}
|
|
659
681
|
|
|
682
|
+
/**
|
|
683
|
+
* Rewrite the per-triangle `side` flag (NORMAL_C.w) for every triangle whose
|
|
684
|
+
* materialIndex matches. Linear over triangles because there's no reverse
|
|
685
|
+
* index — side edits are a rare UI action so the scan cost is acceptable.
|
|
686
|
+
* @private
|
|
687
|
+
*/
|
|
688
|
+
/**
|
|
689
|
+
* Re-derive the shadow-ray opaque-blocker flag for a material from its
|
|
690
|
+
* current buffer values and patch NORMAL_A.w on every matching triangle.
|
|
691
|
+
* Kept in sync with the blocker definition in GeometryExtractor.
|
|
692
|
+
* @private
|
|
693
|
+
*/
|
|
694
|
+
_recomputeOpaqueBlockerForMaterial( materialIndex ) {
|
|
695
|
+
|
|
696
|
+
const matBuf = this.materialStorageAttr?.array;
|
|
697
|
+
if ( ! matBuf ) return;
|
|
698
|
+
|
|
699
|
+
const matStride = materialIndex * M.FLOATS_PER_MATERIAL;
|
|
700
|
+
const alphaMode = matBuf[ matStride + M.ALPHA_MODE ] | 0;
|
|
701
|
+
const transparent = matBuf[ matStride + M.TRANSPARENT ] | 0;
|
|
702
|
+
const transmission = matBuf[ matStride + M.TRANSMISSION ] || 0;
|
|
703
|
+
const opacity = matBuf[ matStride + M.OPACITY ] ?? 1;
|
|
704
|
+
const isOpaqueBlocker = ( alphaMode === 0 && transparent === 0 && transmission === 0 && opacity >= 1 ) ? 1.0 : 0.0;
|
|
705
|
+
|
|
706
|
+
this._patchTriangleFlagForMaterial( materialIndex, TRI_BLOCKER_OFFSET, isOpaqueBlocker );
|
|
707
|
+
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Generic helper: patch a single per-triangle float at `triOffset` for every
|
|
712
|
+
* triangle whose materialIndex matches, then fire onTriangleDataChanged.
|
|
713
|
+
* @private
|
|
714
|
+
*/
|
|
715
|
+
_patchTriangleFlagForMaterial( materialIndex, triOffset, value ) {
|
|
716
|
+
|
|
717
|
+
const triInfo = this.callbacks.getTriangleData?.();
|
|
718
|
+
const triData = triInfo?.array;
|
|
719
|
+
const triCount = triInfo?.count | 0;
|
|
720
|
+
if ( ! triData || triCount === 0 ) return;
|
|
721
|
+
|
|
722
|
+
const stride = T.FLOATS_PER_TRIANGLE;
|
|
723
|
+
let patched = 0;
|
|
724
|
+
for ( let i = 0; i < triCount; i ++ ) {
|
|
725
|
+
|
|
726
|
+
const base = i * stride;
|
|
727
|
+
if ( triData[ base + TRI_MAT_IDX_OFFSET ] === materialIndex ) {
|
|
728
|
+
|
|
729
|
+
triData[ base + triOffset ] = value;
|
|
730
|
+
patched ++;
|
|
731
|
+
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if ( patched > 0 && this.callbacks.onTriangleDataChanged ) {
|
|
737
|
+
|
|
738
|
+
this.callbacks.onTriangleDataChanged();
|
|
739
|
+
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
_patchTriangleSideForMaterial( materialIndex, sideValue ) {
|
|
745
|
+
|
|
746
|
+
this._patchTriangleFlagForMaterial( materialIndex, TRI_SIDE_OFFSET, sideValue );
|
|
747
|
+
|
|
748
|
+
}
|
|
749
|
+
|
|
660
750
|
// ===== DISPOSAL =====
|
|
661
751
|
|
|
662
752
|
dispose() {
|
|
@@ -237,6 +237,9 @@ export class UniformManager {
|
|
|
237
237
|
u( 'emissiveTriangleCount', 0, 'int' );
|
|
238
238
|
u( 'emissiveTotalPower', 0.0, 'float' );
|
|
239
239
|
u( 'lightBVHNodeCount', 0, 'int' );
|
|
240
|
+
// Offset (in vec4 elements) within the packed light buffer where emissive
|
|
241
|
+
// triangle data starts. Equals lightBVHNodeCount * LBVH_STRIDE; computed on upload.
|
|
242
|
+
u( 'emissiveVec4Offset', 0, 'int' );
|
|
240
243
|
|
|
241
244
|
// Render mode
|
|
242
245
|
u( 'renderMode', DEFAULT_STATE.renderMode, 'int' );
|
package/src/TSL/structProxy.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Proxy-enhanced struct factory for TSL.
|
|
3
|
-
*
|
|
4
|
-
* TSL structs require `.get('fieldName')` for member access, but GLSL-style
|
|
5
|
-
* dot notation (`.fieldName`) is more natural and matches the ported code.
|
|
6
|
-
*
|
|
7
|
-
* This utility wraps TSL's `struct()` so that:
|
|
8
|
-
* - Direct construction: `MyStruct({...}).toVar('x')` → `.fieldName` works automatically
|
|
9
|
-
* - Fn return values: `MyStruct.wrap(someFn(...))` → `.fieldName` works automatically
|
|
10
|
-
*
|
|
11
|
-
* Internally, property access for known struct member names is redirected to `.get('name')`.
|
|
12
|
-
* Swizzle properties (x, y, z, w, etc.), Node methods (.add, .assign, etc.), and other
|
|
13
|
-
* standard properties pass through to the underlying node unmodified.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { struct as _struct } from 'three/tsl';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Creates a Proxy around a TSL node that redirects struct member access to `.get('name')`.
|
|
20
|
-
* Also intercepts `.toVar()` to ensure the resulting VarNode is also proxy-wrapped.
|
|
21
|
-
*/
|
|
22
|
-
function createStructProxy( node, memberSet ) {
|
|
23
|
-
|
|
24
|
-
return new Proxy( node, {
|
|
25
|
-
|
|
26
|
-
get( target, prop, receiver ) {
|
|
27
|
-
|
|
28
|
-
// Intercept known struct member names
|
|
29
|
-
if ( typeof prop === 'string' && memberSet.has( prop ) ) {
|
|
30
|
-
|
|
31
|
-
return target.get( prop );
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const val = Reflect.get( target, prop, receiver );
|
|
36
|
-
|
|
37
|
-
// Intercept .toVar() to proxy-wrap the result
|
|
38
|
-
if ( prop === 'toVar' && typeof val === 'function' ) {
|
|
39
|
-
|
|
40
|
-
return ( ...args ) => createStructProxy( val.apply( target, args ), memberSet );
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return val;
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
} );
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Drop-in replacement for TSL's `struct()` that returns a proxy-enhanced factory.
|
|
54
|
-
*
|
|
55
|
-
* The returned factory:
|
|
56
|
-
* - Creates struct nodes where `.toVar()` results support dot-notation field access
|
|
57
|
-
* - Has `.wrap(node)` method to proxy-wrap Fn return values for field access
|
|
58
|
-
* - Has `.layout` and `.isStruct` matching the original TSL struct API
|
|
59
|
-
*
|
|
60
|
-
* @param {Object} members - Struct member layout (e.g., { didHit: 'bool', dst: 'float' })
|
|
61
|
-
* @param {string|null} name - Optional struct name
|
|
62
|
-
* @returns {Function} Enhanced struct factory
|
|
63
|
-
*/
|
|
64
|
-
export function struct( members, name = null ) {
|
|
65
|
-
|
|
66
|
-
const factory = _struct( members, name );
|
|
67
|
-
const memberSet = new Set( Object.keys( members ) );
|
|
68
|
-
|
|
69
|
-
const wrappedFactory = ( ...args ) => {
|
|
70
|
-
|
|
71
|
-
const node = factory( ...args );
|
|
72
|
-
return createStructProxy( node, memberSet );
|
|
73
|
-
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
wrappedFactory.layout = factory.layout;
|
|
77
|
-
wrappedFactory.isStruct = true;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Wrap an existing node (e.g., Fn return value) with struct field access proxy.
|
|
81
|
-
* Usage: `const hit = HitInfo.wrap(traverseBVH(...).toVar('hit'));`
|
|
82
|
-
*/
|
|
83
|
-
wrappedFactory.wrap = ( node ) => createStructProxy( node, memberSet );
|
|
84
|
-
|
|
85
|
-
return wrappedFactory;
|
|
86
|
-
|
|
87
|
-
}
|