@gnsx/three 0.186.3 → 0.186.5

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.
Files changed (34) hide show
  1. package/build/three.webgpu.js +1326 -18
  2. package/build/three.webgpu.nodes.js +1326 -18
  3. package/examples/jsm/inspector/tabs/Settings.js +97 -0
  4. package/package.json +1 -1
  5. package/src/materials/nodes/MeshBasicNodeMaterial.js +13 -0
  6. package/src/materials/nodes/MeshLambertNodeMaterial.js +13 -0
  7. package/src/materials/nodes/MeshMatcapNodeMaterial.js +13 -0
  8. package/src/materials/nodes/MeshPhongNodeMaterial.js +13 -0
  9. package/src/materials/nodes/MeshPhysicalNodeMaterial.js +25 -0
  10. package/src/materials/nodes/MeshSSSNodeMaterial.js +15 -0
  11. package/src/materials/nodes/MeshStandardNodeMaterial.js +13 -0
  12. package/src/materials/nodes/MeshToonNodeMaterial.js +13 -0
  13. package/src/materials/nodes/NodeMaterial.js +106 -1
  14. package/src/materials/nodes/ShadowNodeMaterial.js +13 -0
  15. package/src/materials/nodes/VolumeNodeMaterial.js +13 -0
  16. package/src/nodes/core/NodeBuilder.js +74 -0
  17. package/src/nodes/display/BufferDebug.js +143 -0
  18. package/src/nodes/display/ComplexityDebug.js +400 -0
  19. package/src/nodes/display/DrawCallDebug.js +118 -0
  20. package/src/nodes/display/LightingDebug.js +108 -0
  21. package/src/nodes/lighting/LightsNode.js +71 -2
  22. package/src/nodes/lighting/PointLightNode.js +4 -1
  23. package/src/nodes/lighting/RectAreaLightNode.js +14 -0
  24. package/src/nodes/lighting/SpotLightNode.js +10 -2
  25. package/src/renderers/common/Background.js +20 -1
  26. package/src/renderers/common/RenderObject.js +5 -0
  27. package/src/renderers/common/Renderer.js +27 -1
  28. package/src/renderers/common/nodes/NodeManager.js +32 -7
  29. package/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +8 -0
  30. package/src/renderers/webgl-fallback/utils/WebGLState.js +22 -3
  31. package/src/renderers/webgpu/WebGPUBackend.js +14 -2
  32. package/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +16 -0
  33. package/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +23 -0
  34. package/build/three.cjs +0 -17
@@ -0,0 +1,143 @@
1
+ // WITH_GENESYS
2
+ // Buffer visualization. Shows one material channel in place of the shaded color.
3
+ // !WITH_GENESYS
4
+
5
+ import { diffuseColor, ambientOcclusion, emissive, metalness, roughness } from '../core/PropertyNode.js';
6
+ import { normalWorld } from '../accessors/Normal.js';
7
+ import { float, vec3, vec4 } from '../tsl/TSLCore.js';
8
+
9
+ /**
10
+ * Buffer visualization. The visible surface wins, and one material channel replaces the shaded color.
11
+ *
12
+ * @type {string}
13
+ */
14
+ export const DEBUG_VIEW_BUFFER = 'bufferVisualization';
15
+
16
+ /**
17
+ * Albedo before lighting, including maps and vertex colors.
18
+ *
19
+ * @type {string}
20
+ */
21
+ export const BUFFER_BASE_COLOR = 'baseColor';
22
+
23
+ /**
24
+ * Shading normal in world space, packed to RGB with `n * 0.5 + 0.5`.
25
+ *
26
+ * @type {string}
27
+ */
28
+ export const BUFFER_WORLD_NORMAL = 'worldNormal';
29
+
30
+ /**
31
+ * Scalar roughness used by the lighting model. Black is smooth.
32
+ *
33
+ * @type {string}
34
+ */
35
+ export const BUFFER_ROUGHNESS = 'roughness';
36
+
37
+ /**
38
+ * Scalar metalness. White is metal. Unreal calls this buffer Metallic.
39
+ *
40
+ * @type {string}
41
+ */
42
+ export const BUFFER_METALLIC = 'metallic';
43
+
44
+ /**
45
+ * Material ambient occlusion. White means no occlusion map.
46
+ *
47
+ * @type {string}
48
+ */
49
+ export const BUFFER_AMBIENT_OCCLUSION = 'ambientOcclusion';
50
+
51
+ /**
52
+ * Emissive color before it is added to the lit result.
53
+ *
54
+ * @type {string}
55
+ */
56
+ export const BUFFER_EMISSIVE = 'emissive';
57
+
58
+ /**
59
+ * Channel drawn when buffer visualization is first enabled.
60
+ *
61
+ * @type {string}
62
+ */
63
+ export const DEFAULT_BUFFER = BUFFER_BASE_COLOR;
64
+
65
+ /**
66
+ * Writes a defined value for channels a material may never assign.
67
+ * Standard and physical materials overwrite roughness and metalness in `setupVariants`,
68
+ * and emissive is overwritten when the material has one.
69
+ *
70
+ * @param {string} buffer - `renderer.debug.buffer`.
71
+ */
72
+ export function assignBufferDefaults( buffer ) {
73
+
74
+ if ( buffer === BUFFER_ROUGHNESS ) {
75
+
76
+ roughness.assign( float( 0 ) );
77
+
78
+ } else if ( buffer === BUFFER_METALLIC ) {
79
+
80
+ metalness.assign( float( 0 ) );
81
+
82
+ } else if ( buffer === BUFFER_EMISSIVE ) {
83
+
84
+ emissive.assign( vec3( 0 ) );
85
+
86
+ }
87
+
88
+ }
89
+
90
+ /**
91
+ * The color for one buffer channel. Scalar channels are copied into RGB.
92
+ *
93
+ * @param {string} buffer - `renderer.debug.buffer`.
94
+ * @return {Node<vec3>} The channel color.
95
+ */
96
+ export function bufferVisualizationColor( buffer ) {
97
+
98
+ if ( buffer === BUFFER_WORLD_NORMAL ) {
99
+
100
+ return normalWorld.mul( 0.5 ).add( 0.5 );
101
+
102
+ }
103
+
104
+ if ( buffer === BUFFER_ROUGHNESS ) {
105
+
106
+ return vec3( roughness );
107
+
108
+ }
109
+
110
+ if ( buffer === BUFFER_METALLIC ) {
111
+
112
+ return vec3( metalness );
113
+
114
+ }
115
+
116
+ if ( buffer === BUFFER_AMBIENT_OCCLUSION ) {
117
+
118
+ return vec3( ambientOcclusion );
119
+
120
+ }
121
+
122
+ if ( buffer === BUFFER_EMISSIVE ) {
123
+
124
+ return emissive;
125
+
126
+ }
127
+
128
+ return diffuseColor.rgb;
129
+
130
+ }
131
+
132
+ /**
133
+ * Replaces the shaded color with the selected buffer. Alpha stays the material's coverage
134
+ * so masked and transparent surfaces keep their shape.
135
+ *
136
+ * @param {string} buffer - `renderer.debug.buffer`.
137
+ * @return {Node<vec4>} The buffer color.
138
+ */
139
+ export function bufferVisualizationOutput( buffer ) {
140
+
141
+ return vec4( bufferVisualizationColor( buffer ), diffuseColor.a );
142
+
143
+ }
@@ -0,0 +1,400 @@
1
+ // WITH_GENESYS
2
+ // Shader and lighting complexity debug views. Costs are a material proxy, not ISA instruction counts.
3
+ // !WITH_GENESYS
4
+
5
+ import Node from '../core/Node.js';
6
+ import { float, vec3, vec4, If, Fn } from '../tsl/TSLCore.js';
7
+ import { fract, min, mix, round } from '../math/MathNode.js';
8
+
9
+ /**
10
+ * Shaded output. No complexity debug view.
11
+ *
12
+ * @type {string}
13
+ */
14
+ export const DEBUG_VIEW_NONE = 'none';
15
+
16
+ /**
17
+ * Material-cost heatmap. Overlapping draws accumulate, then a fullscreen pass applies the ramp.
18
+ *
19
+ * @type {string}
20
+ */
21
+ export const DEBUG_VIEW_SHADER_COMPLEXITY = 'shaderComplexity';
22
+
23
+ /**
24
+ * Direct-light count heatmap. The visible surface wins.
25
+ *
26
+ * @type {string}
27
+ */
28
+ export const DEBUG_VIEW_LIGHTING_COMPLEXITY = 'lightingComplexity';
29
+
30
+ /**
31
+ * Per-pixel overdraw heatmap. Each fragment that passes the depth test adds one.
32
+ * This is the portable stand-in for Unreal's 2×2 quad-coverage count.
33
+ *
34
+ * @type {string}
35
+ */
36
+ export const DEBUG_VIEW_QUAD_OVERDRAW = 'quadOverdraw';
37
+
38
+ /**
39
+ * Shader cost multiplied by the number of fragments that shaded the pixel, then the shader-complexity ramp.
40
+ * One opaque surface matches {@link DEBUG_VIEW_SHADER_COMPLEXITY}. Each extra overlapping fragment scales the summed cost.
41
+ * WebGPU cannot count wasted lanes in a 2×2 quad, so the count is fragments that pass the depth test.
42
+ *
43
+ * @type {string}
44
+ */
45
+ export const DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS = 'shaderComplexityAndQuads';
46
+
47
+ /**
48
+ * Proxy budget that fills the shader-complexity ramp.
49
+ * Unlit and basic stay green. Phong and standard move through yellow into red.
50
+ * Transmission plus several texture samples climbs toward white.
51
+ *
52
+ * @type {number}
53
+ */
54
+ export const DEFAULT_SHADER_COMPLEXITY_BUDGET = 800;
55
+
56
+ /**
57
+ * Overlapping fragments that fill the quad-overdraw ramp.
58
+ * Unreal's stair reaches white at ten layers (`1 / 16` stored, then scaled by 1.6).
59
+ *
60
+ * @type {number}
61
+ */
62
+ export const DEFAULT_QUAD_OVERDRAW_BUDGET = 10;
63
+
64
+ /**
65
+ * Storage scale for the overdraw count in {@link DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS}.
66
+ * Each fragment adds `1 / scale`, so an 8-bit target can hold this many layers.
67
+ * The output pass multiplies that channel back to a count. Half-float targets hold the same count exactly.
68
+ *
69
+ * @type {number}
70
+ */
71
+ export const SHADER_COMPLEXITY_QUAD_COUNT_SCALE = 16;
72
+
73
+ /**
74
+ * Added for each fragment texture sample. Lighting-model costs are returned
75
+ * directly by {@link NodeMaterial#getShaderComplexity}.
76
+ *
77
+ * @type {number}
78
+ */
79
+ export const SHADER_COMPLEXITY_TEXTURE_COST = 16;
80
+
81
+ /**
82
+ * Attenuation above this counts as a light that shades the pixel.
83
+ *
84
+ * @type {number}
85
+ */
86
+ export const LIGHTING_COMPLEXITY_EPSILON = 0.001;
87
+
88
+ /**
89
+ * Added on top of 1 when a received light casts a shadow.
90
+ * Applied even where the shadow map is black.
91
+ *
92
+ * @type {number}
93
+ */
94
+ export const LIGHTING_COMPLEXITY_SHADOW_WEIGHT = 0.5;
95
+
96
+ /**
97
+ * Unreal `ShaderComplexityColors` from `BaseEngine.ini`.
98
+ *
99
+ * @type {Array<Array<number>>}
100
+ */
101
+ const SHADER_COMPLEXITY_COLORS = [
102
+ [ 0.0, 1.0, 0.127 ],
103
+ [ 0.0, 1.0, 0.0 ],
104
+ [ 0.046, 0.52, 0.0 ],
105
+ [ 0.215, 0.215, 0.0 ],
106
+ [ 0.52, 0.046, 0.0 ],
107
+ [ 0.7, 0.0, 0.0 ],
108
+ [ 1.0, 0.0, 0.0 ],
109
+ [ 1.0, 0.0, 0.5 ],
110
+ [ 1.0, 0.9, 0.9 ]
111
+ ];
112
+
113
+ /**
114
+ * Unreal `QuadComplexityColors` from `BaseEngine.ini`.
115
+ * Sampled as a stair: one overlapping fragment is one stop.
116
+ *
117
+ * @type {Array<Array<number>>}
118
+ */
119
+ const QUAD_COMPLEXITY_COLORS = [
120
+ [ 0.0, 0.0, 0.0 ],
121
+ [ 0.0, 0.0, 0.4 ],
122
+ [ 0.0, 0.3, 1.0 ],
123
+ [ 0.0, 0.7, 0.4 ],
124
+ [ 0.0, 1.0, 0.0 ],
125
+ [ 0.8, 0.8, 0.0 ],
126
+ [ 1.0, 0.3, 0.0 ],
127
+ [ 0.7, 0.0, 0.0 ],
128
+ [ 0.5, 0.0, 0.5 ],
129
+ [ 0.7, 0.3, 0.7 ],
130
+ [ 1.0, 0.9, 0.9 ]
131
+ ];
132
+
133
+ /**
134
+ * Unreal `LightComplexityColors` from `BaseEngine.ini`.
135
+ *
136
+ * @type {Array<Array<number>>}
137
+ */
138
+ const LIGHT_COMPLEXITY_COLORS = [
139
+ [ 0.0, 0.0, 0.0 ],
140
+ [ 0.0, 0.0, 0.4 ],
141
+ [ 0.0, 0.3, 1.0 ],
142
+ [ 0.0, 0.7, 0.4 ],
143
+ [ 0.0, 1.0, 0.0 ],
144
+ [ 0.8, 0.8, 0.0 ],
145
+ [ 1.0, 0.3, 0.0 ],
146
+ [ 0.7, 0.0, 0.0 ],
147
+ [ 0.5, 0.0, 0.5 ],
148
+ [ 0.7, 0.3, 0.7 ],
149
+ [ 1.0, 0.9, 0.9 ]
150
+ ];
151
+
152
+ const rgb = ( colors, index ) => vec3( colors[ index ][ 0 ], colors[ index ][ 1 ], colors[ index ][ 2 ] );
153
+
154
+ /**
155
+ * Linear sample of a fixed RGB ramp. `cost` is in steps, and one step is one light.
156
+ *
157
+ * @param {Node<float>} cost - Complexity in ramp steps.
158
+ * @param {Array<Array<number>>} colors - Ramp stops.
159
+ * @return {Node<vec3>} The ramp color.
160
+ */
161
+ const colorizeLinear = ( cost, colors ) => {
162
+
163
+ const steps = colors.length - 1;
164
+ const expanded = cost.div( steps ).clamp( 0, 0.999 ).mul( steps );
165
+ const index = expanded.floor();
166
+ const frac = fract( expanded );
167
+
168
+ let color = rgb( colors, 0 );
169
+
170
+ for ( let i = 0; i < steps; i ++ ) {
171
+
172
+ const sample = mix( rgb( colors, i ), rgb( colors, i + 1 ), frac );
173
+
174
+ color = index.greaterThanEqual( float( i ) ).and( index.lessThan( float( i + 1 ) ) ).select( sample, color );
175
+
176
+ }
177
+
178
+ return color;
179
+
180
+ };
181
+
182
+ /**
183
+ * Maps a direct-light cost through Unreal's light-complexity ramp.
184
+ * Ten lights saturate at white.
185
+ *
186
+ * @param {Node<float>} cost - Accumulated light weight.
187
+ * @return {Node<vec3>} The ramp color.
188
+ */
189
+ export const colorizeLightComplexity = ( cost ) => colorizeLinear( cost, LIGHT_COMPLEXITY_COLORS );
190
+
191
+ /**
192
+ * Unreal's stair sample for quad overdraw (`CS_STAIR`).
193
+ * `cost` is the accumulated fragment count divided by the budget.
194
+ *
195
+ * @param {Node<float>} cost - Overdraw count divided by the budget.
196
+ * @return {Node<vec3>} The ramp color.
197
+ */
198
+ export const colorizeQuadOverdraw = ( cost ) => {
199
+
200
+ const steps = QUAD_COMPLEXITY_COLORS.length - 1;
201
+ const index = round( cost.clamp( 0, 1 ).mul( steps ) );
202
+
203
+ let color = rgb( QUAD_COMPLEXITY_COLORS, 0 );
204
+
205
+ for ( let i = 0; i <= steps; i ++ ) {
206
+
207
+ color = index.equal( float( i ) ).select( rgb( QUAD_COMPLEXITY_COLORS, i ), color );
208
+
209
+ }
210
+
211
+ return color;
212
+
213
+ };
214
+
215
+ /**
216
+ * Views that add a scalar per fragment, then colorize that sum in the output pass.
217
+ *
218
+ * @param {string} view - `renderer.debug.view`.
219
+ * @return {boolean} `true` when the view accumulates.
220
+ */
221
+ export function debugViewAccumulates( view ) {
222
+
223
+ return view === DEBUG_VIEW_SHADER_COMPLEXITY || view === DEBUG_VIEW_QUAD_OVERDRAW || view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS;
224
+
225
+ }
226
+
227
+ /**
228
+ * Unreal's nonlinear shader-complexity ramp (`ColorizeComplexity`).
229
+ * The first third of the normalized cost spreads across the first seven stops.
230
+ *
231
+ * @param {Node<float>} complexity - Accumulated cost divided by the budget.
232
+ * @return {Node<vec3>} The ramp color.
233
+ */
234
+ export const colorizeShaderComplexity = /*@__PURE__*/ Fn( ( [ complexity ] ) => {
235
+
236
+ const c = complexity.clamp( 0, 0.999 );
237
+ const expanded = min( float( SHADER_COMPLEXITY_COLORS.length - 2 ), c.mul( 18 ) );
238
+ const color = vec3( 0 ).toVar();
239
+ const frac = fract( expanded );
240
+
241
+ If( expanded.lessThan( 1 ), () => {
242
+
243
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 0 ), rgb( SHADER_COMPLEXITY_COLORS, 1 ), frac ) );
244
+
245
+ } ).ElseIf( expanded.lessThan( 2 ), () => {
246
+
247
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 1 ), rgb( SHADER_COMPLEXITY_COLORS, 2 ), frac ) );
248
+
249
+ } ).ElseIf( expanded.lessThan( 3 ), () => {
250
+
251
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 2 ), rgb( SHADER_COMPLEXITY_COLORS, 3 ), frac ) );
252
+
253
+ } ).ElseIf( expanded.lessThan( 4 ), () => {
254
+
255
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 3 ), rgb( SHADER_COMPLEXITY_COLORS, 4 ), frac ) );
256
+
257
+ } ).ElseIf( expanded.lessThan( 5 ), () => {
258
+
259
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 4 ), rgb( SHADER_COMPLEXITY_COLORS, 5 ), frac ) );
260
+
261
+ } ).ElseIf( expanded.lessThan( 6 ), () => {
262
+
263
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 5 ), rgb( SHADER_COMPLEXITY_COLORS, 6 ), frac ) );
264
+
265
+ } ).Else( () => {
266
+
267
+ // Nine stops, so the upper range uses the `count > 8` scale of 3.
268
+ const upper = c.sub( 0.33333333 ).mul( 3.0 );
269
+ const upperFrac = fract( upper );
270
+
271
+ If( upper.lessThanEqual( 1 ), () => {
272
+
273
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 6 ), rgb( SHADER_COMPLEXITY_COLORS, 7 ), upperFrac ) );
274
+
275
+ } ).Else( () => {
276
+
277
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 7 ), rgb( SHADER_COMPLEXITY_COLORS, 8 ), upperFrac ) );
278
+
279
+ } );
280
+
281
+ } );
282
+
283
+ return color;
284
+
285
+ } );
286
+
287
+ /**
288
+ * Reads the builder's shader-complexity proxy at code-generation time,
289
+ * after the real material graph has been generated and texture samples counted.
290
+ *
291
+ * @augments Node
292
+ */
293
+ class ShaderComplexityRatioNode extends Node {
294
+
295
+ static get type() {
296
+
297
+ return 'ShaderComplexityRatioNode';
298
+
299
+ }
300
+
301
+ constructor() {
302
+
303
+ super( 'float' );
304
+
305
+ }
306
+
307
+ generate( builder ) {
308
+
309
+ const budget = builder.renderer.debug.shaderComplexityBudget;
310
+ const safeBudget = ( typeof budget === 'number' && budget > 0 ) ? budget : 1;
311
+ const ratio = builder.getShaderComplexityCost() / safeBudget;
312
+
313
+ return ratio.toFixed( 6 );
314
+
315
+ }
316
+
317
+ }
318
+
319
+ /**
320
+ * Replaces the shaded color with `vec4(cost / budget, 0, 0, 1)`.
321
+ * The real graph is still generated so texture samples are counted first.
322
+ *
323
+ * @param {Node} resultNode - The material's shaded output.
324
+ * @return {Node<vec4>} The complexity ratio in the red channel.
325
+ */
326
+ export function shaderComplexityOutput( resultNode ) {
327
+
328
+ // Generate the shaded graph first so texture samples are counted, then
329
+ // replace the color. A bypass of a value node emits a bare expression
330
+ // statement, which WGSL rejects.
331
+ const shaded = vec4( resultNode ).toVar();
332
+
333
+ const ratio = new ShaderComplexityRatioNode();
334
+
335
+ return vec4( ratio, 0, 0, 1 ).add( shaded.mul( 0 ) );
336
+
337
+ }
338
+
339
+ /**
340
+ * Same cost as {@link shaderComplexityOutput}, plus one overdraw step in green.
341
+ * The output pass multiplies red by the accumulated green count.
342
+ *
343
+ * @param {Node} resultNode - The material's shaded output.
344
+ * @return {Node<vec4>} Cost ratio in red, one scaled fragment in green.
345
+ */
346
+ export function shaderComplexityAndQuadsOutput( resultNode ) {
347
+
348
+ const shaded = vec4( resultNode ).toVar();
349
+ const ratio = new ShaderComplexityRatioNode();
350
+ const step = float( 1 / SHADER_COMPLEXITY_QUAD_COUNT_SCALE );
351
+
352
+ return vec4( ratio, step, 0, 1 ).add( shaded.mul( 0 ) );
353
+
354
+ }
355
+
356
+ /**
357
+ * Reads `1 / quadOverdrawBudget` at code-generation time.
358
+ *
359
+ * @augments Node
360
+ */
361
+ class QuadOverdrawRatioNode extends Node {
362
+
363
+ static get type() {
364
+
365
+ return 'QuadOverdrawRatioNode';
366
+
367
+ }
368
+
369
+ constructor() {
370
+
371
+ super( 'float' );
372
+
373
+ }
374
+
375
+ generate( builder ) {
376
+
377
+ const budget = builder.renderer.debug.quadOverdrawBudget;
378
+ const safeBudget = ( typeof budget === 'number' && budget > 0 ) ? budget : 1;
379
+
380
+ return ( 1 / safeBudget ).toFixed( 6 );
381
+
382
+ }
383
+
384
+ }
385
+
386
+ /**
387
+ * Replaces the shaded color with `vec4(1 / budget, 0, 0, 1)`.
388
+ * The real graph is still generated so clipped fragments are not counted.
389
+ *
390
+ * @param {Node} resultNode - The material's shaded output.
391
+ * @return {Node<vec4>} One overdraw step in the red channel.
392
+ */
393
+ export function quadOverdrawOutput( resultNode ) {
394
+
395
+ const shaded = vec4( resultNode ).toVar();
396
+ const ratio = new QuadOverdrawRatioNode();
397
+
398
+ return vec4( ratio, 0, 0, 1 ).add( shaded.mul( 0 ) );
399
+
400
+ }
@@ -0,0 +1,118 @@
1
+ // WITH_GENESYS
2
+ // Draw call coloration. One stable diffuse color per submitted draw, then lit.
3
+ // !WITH_GENESYS
4
+
5
+ import { Color } from '../../math/Color.js';
6
+ import { diffuseColor, diffuseContribution, metalness, roughness, specularColor, specularColorBlended, specularF90, clearcoat, sheen, iridescence, anisotropy, transmission, retroreflectivity } from '../core/PropertyNode.js';
7
+ import { uniform } from '../core/UniformNode.js';
8
+ import { float, vec3 } from '../tsl/TSLCore.js';
9
+
10
+ /**
11
+ * Draw call coloration. Each submitted draw gets one diffuse color. An instanced mesh is one draw.
12
+ * The color is lit, so lights and shadows still show. Unlit materials stay flat.
13
+ *
14
+ * @type {string}
15
+ */
16
+ export const DEBUG_VIEW_DRAW_CALL = 'drawCall';
17
+
18
+ /**
19
+ * Golden-ratio step. Sequential object ids land on opposite sides of the hue wheel.
20
+ *
21
+ * @type {number}
22
+ */
23
+ const DRAW_CALL_HUE_STEP = 0.618033988749895;
24
+
25
+ /**
26
+ * Saturation of a draw color. High enough that neighboring hues stay apart after lighting.
27
+ *
28
+ * @type {number}
29
+ */
30
+ const DRAW_CALL_SATURATION = 0.85;
31
+
32
+ /**
33
+ * Value of a draw color. Lighting and shadows darken it from here.
34
+ *
35
+ * @type {number}
36
+ */
37
+ const DRAW_CALL_VALUE = 0.92;
38
+
39
+ /**
40
+ * Writes a saturated color for this draw into `color`.
41
+ * Hue walks the wheel by the golden ratio, so consecutive objects do not share a tint.
42
+ * Instances of one mesh share the object id, so they share the color.
43
+ *
44
+ * @param {Color} color - The uniform color, updated in place.
45
+ * @param {Object3D} object - The drawn object.
46
+ * @param {Material} material - The drawn material. A multi-material group is its own draw.
47
+ * @return {Color} `color`.
48
+ */
49
+ function writeDrawCallColor( color, object, material ) {
50
+
51
+ const hue = ( ( object.id + material.id * 64 ) * DRAW_CALL_HUE_STEP ) % 1;
52
+ const s = DRAW_CALL_SATURATION;
53
+ const v = DRAW_CALL_VALUE;
54
+ const sector = hue * 6;
55
+ const i = Math.floor( sector );
56
+ const f = sector - i;
57
+ const p = v * ( 1 - s );
58
+ const q = v * ( 1 - f * s );
59
+ const t = v * ( 1 - ( 1 - f ) * s );
60
+
61
+ switch ( i % 6 ) {
62
+
63
+ case 0: return color.setRGB( v, t, p );
64
+ case 1: return color.setRGB( q, v, p );
65
+ case 2: return color.setRGB( p, v, t );
66
+ case 3: return color.setRGB( p, q, v );
67
+ case 4: return color.setRGB( t, p, v );
68
+ default: return color.setRGB( v, p, q );
69
+
70
+ }
71
+
72
+ }
73
+
74
+ /**
75
+ * Replaces albedo with the draw color after the material has written its channels.
76
+ * Lit materials become a matte dielectric in that color. Unlit materials become flat.
77
+ * The color is an object uniform, so meshes that share a material still differ.
78
+ *
79
+ * @param {NodeBuilder} builder - The current node builder.
80
+ */
81
+ export function applyDrawCallDebug( builder ) {
82
+
83
+ const material = builder.material;
84
+ const colorNode = uniform( new Color() ).onObjectUpdate( ( { object, material: drawnMaterial }, self ) => {
85
+
86
+ if ( object === null || drawnMaterial === null ) return;
87
+
88
+ return writeDrawCallColor( self.value, object, drawnMaterial );
89
+
90
+ } );
91
+
92
+ diffuseColor.rgb.assign( colorNode );
93
+
94
+ if ( material.lights !== true ) return;
95
+
96
+ metalness.assign( float( 0 ) );
97
+ diffuseContribution.assign( colorNode );
98
+ roughness.assign( float( 1 ) );
99
+ specularColor.assign( vec3( 0 ) );
100
+ specularColorBlended.assign( vec3( 0 ) );
101
+ specularF90.assign( float( 0 ) );
102
+
103
+ // Ambient occlusion belonged to the original material.
104
+ builder.context.ambientOcclusion = null;
105
+
106
+ if ( material.useClearcoat === true ) clearcoat.assign( float( 0 ) );
107
+
108
+ if ( material.useSheen === true ) sheen.assign( vec3( 0 ) );
109
+
110
+ if ( material.useIridescence === true ) iridescence.assign( float( 0 ) );
111
+
112
+ if ( material.useAnisotropy === true ) anisotropy.assign( float( 0 ) );
113
+
114
+ if ( material.useTransmission === true ) transmission.assign( float( 0 ) );
115
+
116
+ if ( material.useRetroreflection === true ) retroreflectivity.assign( float( 0 ) );
117
+
118
+ }