@gnsx/three 0.186.3 → 0.186.4

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.
@@ -134,10 +134,46 @@ class Settings extends Parameters {
134
134
 
135
135
  } ).info( 'Shows how many times each pixel is shaded.' );
136
136
 
137
+ // WITH_GENESYS
138
+ // Values match Renderer.debug.view. Not stored with inspector settings.
139
+ const debugViewState = { view: 'none' };
140
+
141
+ this._debugViewState = debugViewState;
142
+ this._debugViewControl = modesGroup.add( debugViewState, 'view', {
143
+ 'Shaded': 'none',
144
+ 'Shader Complexity': 'shaderComplexity',
145
+ 'Lighting Complexity': 'lightingComplexity'
146
+ } ).name( 'Debug View' ).onChange( ( view ) => {
147
+
148
+ const renderer = this.inspector.getRenderer();
149
+
150
+ if ( renderer === null || renderer.debug === undefined ) return;
151
+
152
+ renderer.debug.view = view;
153
+ window.dispatchEvent( new Event( 'genesys-debug-view' ) );
154
+
155
+ } ).info( 'Shaded color, a shader-cost heatmap, or a direct-light count. Overdraw draws on top when both are enabled.' );
156
+ // !WITH_GENESYS
157
+
137
158
  }
138
159
 
139
160
  init() {
140
161
 
162
+ // WITH_GENESYS
163
+ const renderer = this.inspector.getRenderer();
164
+ const view = renderer !== null && renderer.debug !== undefined ? renderer.debug.view : null;
165
+
166
+ if ( view === 'none' || view === 'shaderComplexity' || view === 'lightingComplexity' ) {
167
+
168
+ if ( this._debugViewState.view !== view ) {
169
+
170
+ this._debugViewControl.setValue( view );
171
+
172
+ }
173
+
174
+ }
175
+ // !WITH_GENESYS
176
+
141
177
  const extensionsGroup = this.createGroup( 'Extensions' );
142
178
 
143
179
  const storageGroup = this.createGroup( 'Storage' );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnsx/three",
3
- "version": "0.186.3",
3
+ "version": "0.186.4",
4
4
  "description": "JavaScript 3D library",
5
5
  "type": "module",
6
6
  "main": "./build/three.cjs",
@@ -25,6 +25,9 @@ import { modelViewMatrix } from '../../nodes/accessors/ModelNode.js';
25
25
  import { vertexColor } from '../../nodes/accessors/VertexColorNode.js';
26
26
  import { premultiplyAlpha } from '../../nodes/display/PremultiplyAlphaFunctions.js';
27
27
  import { subBuild } from '../../nodes/core/SubBuildNode.js';
28
+ // WITH_GENESYS
29
+ import { DEBUG_VIEW_LIGHTING_COMPLEXITY, DEBUG_VIEW_SHADER_COMPLEXITY, getShaderComplexityBase, shaderComplexityOutput } from '../../nodes/display/ComplexityDebug.js';
30
+ // !WITH_GENESYS
28
31
 
29
32
  /**
30
33
  * Base class for all node materials.
@@ -597,6 +600,17 @@ class NodeMaterial extends Material {
597
600
 
598
601
  }
599
602
 
603
+ // WITH_GENESYS
604
+ // The screen output pass colorizes the accumulated ratio. Wrapping that pass
605
+ // would replace the heatmap with this material's own constant cost.
606
+ if ( renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY && this.isShadowPassMaterial !== true && renderer.isOutputTarget !== true ) {
607
+
608
+ builder.shaderComplexityBase = getShaderComplexityBase( this );
609
+ resultNode = shaderComplexityOutput( resultNode );
610
+
611
+ }
612
+ // !WITH_GENESYS
613
+
600
614
  builder.stack.outputNode = resultNode;
601
615
 
602
616
  builder.addFlow( 'fragment', builder.removeStack() );
@@ -1110,6 +1124,22 @@ class NodeMaterial extends Material {
1110
1124
 
1111
1125
  }
1112
1126
 
1127
+ // WITH_GENESYS
1128
+ if ( builder.renderer.debug.view === DEBUG_VIEW_LIGHTING_COMPLEXITY && this.isShadowPassMaterial !== true ) {
1129
+
1130
+ const hasLightLoop = lightsNode && ( materialLightings.length > 0 || lightsNode.getScope().hasLights );
1131
+
1132
+ if ( hasLightLoop !== true ) {
1133
+
1134
+ outgoingLightNode = vec3( 0 );
1135
+
1136
+ }
1137
+
1138
+ return outgoingLightNode;
1139
+
1140
+ }
1141
+ // !WITH_GENESYS
1142
+
1113
1143
  // EMISSIVE
1114
1144
 
1115
1145
  if ( ( emissiveNode && emissiveNode.isNode === true ) || ( material.emissive && material.emissive.isColor === true ) ) {
@@ -24,6 +24,9 @@ import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js';
24
24
  import BindGroup from '../../renderers/common/BindGroup.js';
25
25
 
26
26
  import { REVISION, IntType, UnsignedIntType, LinearFilter, LinearMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapLinearFilter, NormalBlending } from '../../constants.js';
27
+ // WITH_GENESYS
28
+ import { SHADER_COMPLEXITY_TEXTURE_COST } from '../display/ComplexityDebug.js';
29
+ // !WITH_GENESYS
27
30
  import { RenderTarget } from '../../core/RenderTarget.js';
28
31
  import { Color } from '../../math/Color.js';
29
32
  import { Vector2 } from '../../math/Vector2.js';
@@ -268,6 +271,31 @@ class NodeBuilder {
268
271
  */
269
272
  this.fragmentShader = null;
270
273
 
274
+ // WITH_GENESYS
275
+ /**
276
+ * Static material-feature cost for the shader-complexity debug view.
277
+ *
278
+ * @type {number}
279
+ * @default 0
280
+ */
281
+ this.shaderComplexityBase = 0;
282
+
283
+ /**
284
+ * Fragment texture samples counted while generating the shader.
285
+ *
286
+ * @type {number}
287
+ * @default 0
288
+ */
289
+ this.fragmentTextureSamples = 0;
290
+
291
+ /**
292
+ * Sample sites already counted, so a node that is generated twice is not double-counted.
293
+ *
294
+ * @type {Set<string>}
295
+ */
296
+ this.shaderComplexitySampleKeys = new Set();
297
+ // !WITH_GENESYS
298
+
271
299
  /**
272
300
  * The generated compute shader.
273
301
  *
@@ -1296,6 +1324,46 @@ class NodeBuilder {
1296
1324
 
1297
1325
  }
1298
1326
 
1327
+ // WITH_GENESYS
1328
+ /**
1329
+ * Counts one fragment texture sample toward the shader-complexity proxy.
1330
+ * Depth and cube textures are shadow maps and environment maps, and rect-area
1331
+ * LTC tables belong to the light loop, so they are not material cost.
1332
+ *
1333
+ * @param {?Texture} texture - The texture being sampled.
1334
+ * @param {string} [shaderStage=this.shaderStage] - The stage the sample is emitted for.
1335
+ * @param {string} [key=''] - Stable id for this sample site. Repeated generation of the same site counts once.
1336
+ */
1337
+ recordFragmentTextureSample( texture, shaderStage = this.shaderStage, key = '' ) {
1338
+
1339
+ if ( shaderStage !== 'fragment' ) return;
1340
+
1341
+ if ( texture && ( texture.isDepthTexture === true || texture.isCubeTexture === true || texture.isRectAreaLTC === true ) ) return;
1342
+
1343
+ if ( key !== '' ) {
1344
+
1345
+ if ( this.shaderComplexitySampleKeys.has( key ) ) return;
1346
+
1347
+ this.shaderComplexitySampleKeys.add( key );
1348
+
1349
+ }
1350
+
1351
+ this.fragmentTextureSamples ++;
1352
+
1353
+ }
1354
+
1355
+ /**
1356
+ * Returns the shader-complexity proxy for the material currently being built.
1357
+ *
1358
+ * @return {number} Base feature cost plus weighted fragment texture samples.
1359
+ */
1360
+ getShaderComplexityCost() {
1361
+
1362
+ return this.shaderComplexityBase + this.fragmentTextureSamples * SHADER_COMPLEXITY_TEXTURE_COST;
1363
+
1364
+ }
1365
+ // !WITH_GENESYS
1366
+
1299
1367
  /**
1300
1368
  * Generates a texture LOD shader string for the given texture data.
1301
1369
  *
@@ -3119,6 +3187,12 @@ class NodeBuilder {
3119
3187
  */
3120
3188
  prebuild() {
3121
3189
 
3190
+ // WITH_GENESYS
3191
+ this.shaderComplexityBase = 0;
3192
+ this.fragmentTextureSamples = 0;
3193
+ this.shaderComplexitySampleKeys.clear();
3194
+ // !WITH_GENESYS
3195
+
3122
3196
  const { renderer, material } = this;
3123
3197
 
3124
3198
  // < renderer.contextNode >
@@ -0,0 +1,278 @@
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 } 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
+ * Proxy budget that fills the shader-complexity ramp.
32
+ * A plain lit material stays in the green stops. Transmission plus several
33
+ * texture samples climbs toward red.
34
+ *
35
+ * @type {number}
36
+ */
37
+ export const DEFAULT_SHADER_COMPLEXITY_BUDGET = 800;
38
+
39
+ /** @type {number} */
40
+ export const SHADER_COMPLEXITY_BASE_UNLIT = 20;
41
+
42
+ /** @type {number} */
43
+ export const SHADER_COMPLEXITY_BASE_LIT = 40;
44
+
45
+ /** @type {number} */
46
+ export const SHADER_COMPLEXITY_TEXTURE_COST = 16;
47
+
48
+ /** @type {number} */
49
+ export const SHADER_COMPLEXITY_FEATURE_COST = 30;
50
+
51
+ /** @type {number} */
52
+ export const SHADER_COMPLEXITY_TRANSMISSION_COST = 60;
53
+
54
+ /**
55
+ * Attenuation above this counts as a light that shades the pixel.
56
+ *
57
+ * @type {number}
58
+ */
59
+ export const LIGHTING_COMPLEXITY_EPSILON = 0.001;
60
+
61
+ /**
62
+ * Added on top of 1 when a received light casts a shadow.
63
+ * Applied even where the shadow map is black.
64
+ *
65
+ * @type {number}
66
+ */
67
+ export const LIGHTING_COMPLEXITY_SHADOW_WEIGHT = 0.5;
68
+
69
+ /**
70
+ * Unreal `ShaderComplexityColors` from `BaseEngine.ini`.
71
+ *
72
+ * @type {Array<Array<number>>}
73
+ */
74
+ const SHADER_COMPLEXITY_COLORS = [
75
+ [ 0.0, 1.0, 0.127 ],
76
+ [ 0.0, 1.0, 0.0 ],
77
+ [ 0.046, 0.52, 0.0 ],
78
+ [ 0.215, 0.215, 0.0 ],
79
+ [ 0.52, 0.046, 0.0 ],
80
+ [ 0.7, 0.0, 0.0 ],
81
+ [ 1.0, 0.0, 0.0 ],
82
+ [ 1.0, 0.0, 0.5 ],
83
+ [ 1.0, 0.9, 0.9 ]
84
+ ];
85
+
86
+ /**
87
+ * Unreal `LightComplexityColors` from `BaseEngine.ini`.
88
+ *
89
+ * @type {Array<Array<number>>}
90
+ */
91
+ const LIGHT_COMPLEXITY_COLORS = [
92
+ [ 0.0, 0.0, 0.0 ],
93
+ [ 0.0, 0.0, 0.4 ],
94
+ [ 0.0, 0.3, 1.0 ],
95
+ [ 0.0, 0.7, 0.4 ],
96
+ [ 0.0, 1.0, 0.0 ],
97
+ [ 0.8, 0.8, 0.0 ],
98
+ [ 1.0, 0.3, 0.0 ],
99
+ [ 0.7, 0.0, 0.0 ],
100
+ [ 0.5, 0.0, 0.5 ],
101
+ [ 0.7, 0.3, 0.7 ],
102
+ [ 1.0, 0.9, 0.9 ]
103
+ ];
104
+
105
+ const rgb = ( colors, index ) => vec3( colors[ index ][ 0 ], colors[ index ][ 1 ], colors[ index ][ 2 ] );
106
+
107
+ /**
108
+ * Linear sample of a fixed RGB ramp. `cost` is in steps, and one step is one light.
109
+ *
110
+ * @param {Node<float>} cost - Complexity in ramp steps.
111
+ * @param {Array<Array<number>>} colors - Ramp stops.
112
+ * @return {Node<vec3>} The ramp color.
113
+ */
114
+ const colorizeLinear = ( cost, colors ) => {
115
+
116
+ const steps = colors.length - 1;
117
+ const expanded = cost.div( steps ).clamp( 0, 0.999 ).mul( steps );
118
+ const index = expanded.floor();
119
+ const frac = fract( expanded );
120
+
121
+ let color = rgb( colors, 0 );
122
+
123
+ for ( let i = 0; i < steps; i ++ ) {
124
+
125
+ const sample = mix( rgb( colors, i ), rgb( colors, i + 1 ), frac );
126
+
127
+ color = index.greaterThanEqual( float( i ) ).and( index.lessThan( float( i + 1 ) ) ).select( sample, color );
128
+
129
+ }
130
+
131
+ return color;
132
+
133
+ };
134
+
135
+ /**
136
+ * Maps a direct-light cost through Unreal's light-complexity ramp.
137
+ * Ten lights saturate at white.
138
+ *
139
+ * @param {Node<float>} cost - Accumulated light weight.
140
+ * @return {Node<vec3>} The ramp color.
141
+ */
142
+ export const colorizeLightComplexity = ( cost ) => colorizeLinear( cost, LIGHT_COMPLEXITY_COLORS );
143
+
144
+ /**
145
+ * Unreal's nonlinear shader-complexity ramp (`ColorizeComplexity`).
146
+ * The first third of the normalized cost spreads across the first seven stops.
147
+ *
148
+ * @param {Node<float>} complexity - Accumulated cost divided by the budget.
149
+ * @return {Node<vec3>} The ramp color.
150
+ */
151
+ export const colorizeShaderComplexity = /*@__PURE__*/ Fn( ( [ complexity ] ) => {
152
+
153
+ const c = complexity.clamp( 0, 0.999 );
154
+ const expanded = min( float( SHADER_COMPLEXITY_COLORS.length - 2 ), c.mul( 18 ) );
155
+ const color = vec3( 0 ).toVar();
156
+ const frac = fract( expanded );
157
+
158
+ If( expanded.lessThan( 1 ), () => {
159
+
160
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 0 ), rgb( SHADER_COMPLEXITY_COLORS, 1 ), frac ) );
161
+
162
+ } ).ElseIf( expanded.lessThan( 2 ), () => {
163
+
164
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 1 ), rgb( SHADER_COMPLEXITY_COLORS, 2 ), frac ) );
165
+
166
+ } ).ElseIf( expanded.lessThan( 3 ), () => {
167
+
168
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 2 ), rgb( SHADER_COMPLEXITY_COLORS, 3 ), frac ) );
169
+
170
+ } ).ElseIf( expanded.lessThan( 4 ), () => {
171
+
172
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 3 ), rgb( SHADER_COMPLEXITY_COLORS, 4 ), frac ) );
173
+
174
+ } ).ElseIf( expanded.lessThan( 5 ), () => {
175
+
176
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 4 ), rgb( SHADER_COMPLEXITY_COLORS, 5 ), frac ) );
177
+
178
+ } ).ElseIf( expanded.lessThan( 6 ), () => {
179
+
180
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 5 ), rgb( SHADER_COMPLEXITY_COLORS, 6 ), frac ) );
181
+
182
+ } ).Else( () => {
183
+
184
+ // Nine stops, so the upper range uses the `count > 8` scale of 3.
185
+ const upper = c.sub( 0.33333333 ).mul( 3.0 );
186
+ const upperFrac = fract( upper );
187
+
188
+ If( upper.lessThanEqual( 1 ), () => {
189
+
190
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 6 ), rgb( SHADER_COMPLEXITY_COLORS, 7 ), upperFrac ) );
191
+
192
+ } ).Else( () => {
193
+
194
+ color.assign( mix( rgb( SHADER_COMPLEXITY_COLORS, 7 ), rgb( SHADER_COMPLEXITY_COLORS, 8 ), upperFrac ) );
195
+
196
+ } );
197
+
198
+ } );
199
+
200
+ return color;
201
+
202
+ } );
203
+
204
+ /**
205
+ * Reads the builder's shader-complexity proxy at code-generation time,
206
+ * after the real material graph has been generated and texture samples counted.
207
+ *
208
+ * @augments Node
209
+ */
210
+ class ShaderComplexityRatioNode extends Node {
211
+
212
+ static get type() {
213
+
214
+ return 'ShaderComplexityRatioNode';
215
+
216
+ }
217
+
218
+ constructor() {
219
+
220
+ super( 'float' );
221
+
222
+ }
223
+
224
+ generate( builder ) {
225
+
226
+ const budget = builder.renderer.debug.shaderComplexityBudget;
227
+ const safeBudget = ( typeof budget === 'number' && budget > 0 ) ? budget : 1;
228
+ const ratio = builder.getShaderComplexityCost() / safeBudget;
229
+
230
+ return ratio.toFixed( 6 );
231
+
232
+ }
233
+
234
+ }
235
+
236
+ /**
237
+ * Static fragment proxy for a material. Texture samples are counted later, during generation.
238
+ *
239
+ * @param {NodeMaterial} material - The material being built.
240
+ * @return {number} The base cost, excluding texture samples.
241
+ */
242
+ export function getShaderComplexityBase( material ) {
243
+
244
+ let cost = material.lights === true ? SHADER_COMPLEXITY_BASE_LIT : SHADER_COMPLEXITY_BASE_UNLIT;
245
+
246
+ if ( material.useClearcoat === true ) cost += SHADER_COMPLEXITY_FEATURE_COST;
247
+
248
+ if ( material.useSheen === true ) cost += SHADER_COMPLEXITY_FEATURE_COST;
249
+
250
+ if ( material.useIridescence === true ) cost += SHADER_COMPLEXITY_FEATURE_COST;
251
+
252
+ if ( material.useAnisotropy === true ) cost += SHADER_COMPLEXITY_FEATURE_COST;
253
+
254
+ if ( material.useTransmission === true ) cost += SHADER_COMPLEXITY_TRANSMISSION_COST;
255
+
256
+ return cost;
257
+
258
+ }
259
+
260
+ /**
261
+ * Replaces the shaded color with `vec4(cost / budget, 0, 0, 1)`.
262
+ * The real graph is still generated so texture samples are counted first.
263
+ *
264
+ * @param {Node} resultNode - The material's shaded output.
265
+ * @return {Node<vec4>} The complexity ratio in the red channel.
266
+ */
267
+ export function shaderComplexityOutput( resultNode ) {
268
+
269
+ // Generate the shaded graph first so texture samples are counted, then
270
+ // replace the color. A bypass of a value node emits a bare expression
271
+ // statement, which WGSL rejects.
272
+ const shaded = vec4( resultNode ).toVar();
273
+
274
+ const ratio = new ShaderComplexityRatioNode();
275
+
276
+ return vec4( ratio, 0, 0, 1 ).add( shaded.mul( 0 ) );
277
+
278
+ }
@@ -1,8 +1,11 @@
1
1
  import Node from '../core/Node.js';
2
- import { property, vec3 } from '../tsl/TSLBase.js';
2
+ import { float, property, vec3 } from '../tsl/TSLBase.js';
3
3
  import { hashArray } from '../core/NodeUtils.js';
4
4
  import { warn } from '../../utils.js';
5
5
  // WITH_GENESYS
6
+ import { colorizeLightComplexity, DEBUG_VIEW_LIGHTING_COMPLEXITY, LIGHTING_COMPLEXITY_EPSILON, LIGHTING_COMPLEXITY_SHADOW_WEIGHT } from '../display/ComplexityDebug.js';
7
+ // !WITH_GENESYS
8
+ // WITH_GENESYS
6
9
  import LightProbeGridNode from './LightProbeGridNode.js';
7
10
  // !WITH_GENESYS
8
11
 
@@ -27,6 +30,10 @@ const totalSpecular = property( 'vec3', 'totalSpecular' );
27
30
  */
28
31
  const outgoingLight = property( 'vec3', 'outgoingLight' );
29
32
 
33
+ // WITH_GENESYS
34
+ const lightingComplexity = property( 'float', 'lightingComplexity' );
35
+ // !WITH_GENESYS
36
+
30
37
  /**
31
38
  * Sorts an array of lights in ascending order by their IDs.
32
39
  *
@@ -380,6 +387,10 @@ class LightsNode extends Node {
380
387
  reflectedLight
381
388
  }, builder );
382
389
 
390
+ // WITH_GENESYS
391
+ this._addLightingComplexity( builder, lightNode, lightData );
392
+ // !WITH_GENESYS
393
+
383
394
  }
384
395
 
385
396
  /**
@@ -399,7 +410,39 @@ class LightsNode extends Node {
399
410
  reflectedLight
400
411
  }, builder );
401
412
 
413
+ // WITH_GENESYS
414
+ this._addLightingComplexity( builder, lightNode, lightData );
415
+ // !WITH_GENESYS
416
+
417
+ }
418
+
419
+ // WITH_GENESYS
420
+ /**
421
+ * Adds this direct light's weight to the lighting-complexity accumulator.
422
+ * Point, spot, and rect-area lights count only where attenuation is in range.
423
+ * Directional lights have no falloff, so they always count. Shadow-casting
424
+ * lights add extra weight even where the shadow map is black.
425
+ *
426
+ * @private
427
+ * @param {NodeBuilder} builder - The current node builder.
428
+ * @param {LightingNode} lightNode - The light node being shaded.
429
+ * @param {Object} lightData - Direct light terms, including an optional attenuation node.
430
+ */
431
+ _addLightingComplexity( builder, lightNode, lightData ) {
432
+
433
+ if ( builder.renderer.debug.view !== DEBUG_VIEW_LIGHTING_COMPLEXITY ) return;
434
+
435
+ if ( builder.material && builder.material.isShadowPassMaterial === true ) return;
436
+
437
+ const attenuation = lightData.attenuation !== undefined ? lightData.attenuation : float( 1 );
438
+ const light = lightNode.light;
439
+ const castsShadow = light !== undefined && light !== null && light.castShadow === true && builder.object.receiveShadow === true;
440
+ const weight = castsShadow ? float( 1 + LIGHTING_COMPLEXITY_SHADOW_WEIGHT ) : float( 1 );
441
+
442
+ lightingComplexity.addAssign( attenuation.greaterThan( LIGHTING_COMPLEXITY_EPSILON ).select( weight, float( 0 ) ) );
443
+
402
444
  }
445
+ // !WITH_GENESYS
403
446
 
404
447
  /**
405
448
  * Setups the internal lights by building all respective
@@ -463,6 +506,14 @@ class LightsNode extends Node {
463
506
 
464
507
  properties.nodes = stack.nodes;
465
508
 
509
+ // WITH_GENESYS
510
+ if ( builder.renderer.debug.view === DEBUG_VIEW_LIGHTING_COMPLEXITY && ( builder.material === null || builder.material.isShadowPassMaterial !== true ) ) {
511
+
512
+ lightingComplexity.assign( 0 );
513
+
514
+ }
515
+ // !WITH_GENESYS
516
+
466
517
  lightingModel.start( builder );
467
518
 
468
519
  const { backdrop, backdropAlpha } = context;
@@ -487,10 +538,28 @@ class LightsNode extends Node {
487
538
  totalDiffuseNode.assign( totalDiffuse );
488
539
  totalSpecularNode.assign( directSpecular.add( indirectSpecular ) );
489
540
 
490
- outgoingLightNode.assign( totalDiffuseNode.add( totalSpecularNode ) );
541
+ // WITH_GENESYS
542
+ const lightingComplexityView = builder.renderer.debug.view === DEBUG_VIEW_LIGHTING_COMPLEXITY && ( builder.material === null || builder.material.isShadowPassMaterial !== true );
543
+
544
+ if ( lightingComplexityView !== true ) {
545
+
546
+ outgoingLightNode.assign( totalDiffuseNode.add( totalSpecularNode ) );
547
+
548
+ }
549
+ // !WITH_GENESYS
550
+ // outgoingLightNode.assign( totalDiffuseNode.add( totalSpecularNode ) );
491
551
 
492
552
  lightingModel.finish( builder );
493
553
 
554
+ // WITH_GENESYS
555
+ // After finish(), so clearcoat and sheen cannot draw over the heatmap.
556
+ if ( lightingComplexityView === true ) {
557
+
558
+ outgoingLightNode.assign( colorizeLightComplexity( lightingComplexity ) );
559
+
560
+ }
561
+ // !WITH_GENESYS
562
+
494
563
  outgoingLightNode = outgoingLightNode.bypass( builder.removeStack() );
495
564
 
496
565
  } else {
@@ -17,7 +17,10 @@ export const directPointLight = ( { color, lightVector, cutoffDistance, decayExp
17
17
 
18
18
  const lightColor = color.mul( attenuation );
19
19
 
20
- return { lightDirection, lightColor };
20
+ // WITH_GENESYS
21
+ return { lightDirection, lightColor, attenuation };
22
+ // !WITH_GENESYS
23
+ // return { lightDirection, lightColor };
21
24
 
22
25
  };
23
26
 
@@ -88,6 +88,20 @@ class RectAreaLightNode extends AnalyticLightNode {
88
88
 
89
89
  setupDirectRectArea( builder ) {
90
90
 
91
+ // WITH_GENESYS
92
+ // LTC lookups are part of the light loop, not the material's shader cost.
93
+ if ( _ltcLib !== null ) {
94
+
95
+ _ltcLib.LTC_FLOAT_1.isRectAreaLTC = true;
96
+ _ltcLib.LTC_FLOAT_2.isRectAreaLTC = true;
97
+
98
+ if ( _ltcLib.LTC_HALF_1 ) _ltcLib.LTC_HALF_1.isRectAreaLTC = true;
99
+
100
+ if ( _ltcLib.LTC_HALF_2 ) _ltcLib.LTC_HALF_2.isRectAreaLTC = true;
101
+
102
+ }
103
+ // !WITH_GENESYS
104
+
91
105
  let ltc_1, ltc_2;
92
106
 
93
107
  if ( builder.isAvailable( 'float32Filterable' ) ) {
@@ -135,7 +135,12 @@ class SpotLightNode extends AnalyticLightNode {
135
135
  decayExponent: decayExponentNode
136
136
  } );
137
137
 
138
- let lightColor = colorNode.mul( spotAttenuation ).mul( lightAttenuation );
138
+ // WITH_GENESYS
139
+ const attenuation = spotAttenuation.mul( lightAttenuation );
140
+
141
+ let lightColor = colorNode.mul( attenuation );
142
+ // !WITH_GENESYS
143
+ // let lightColor = colorNode.mul( spotAttenuation ).mul( lightAttenuation );
139
144
 
140
145
  let projected, lightCoord;
141
146
 
@@ -159,7 +164,10 @@ class SpotLightNode extends AnalyticLightNode {
159
164
 
160
165
  }
161
166
 
162
- return { lightColor, lightDirection };
167
+ // WITH_GENESYS
168
+ return { lightColor, lightDirection, attenuation };
169
+ // !WITH_GENESYS
170
+ // return { lightColor, lightDirection };
163
171
 
164
172
  }
165
173
 
@@ -191,7 +191,15 @@ class Background extends DataMap {
191
191
 
192
192
  }
193
193
 
194
- renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null, null );
194
+ // WITH_GENESYS
195
+ // The background is depth-tested off, so additive shader complexity would add it to every pixel.
196
+ if ( renderer.debug.view !== 'shaderComplexity' ) {
197
+
198
+ renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null, null );
199
+
200
+ }
201
+ // !WITH_GENESYS
202
+ // renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null, null );
195
203
 
196
204
  } else {
197
205
 
@@ -215,6 +223,14 @@ class Background extends DataMap {
215
223
 
216
224
  //
217
225
 
226
+ // WITH_GENESYS
227
+ if ( renderer.debug.view === 'shaderComplexity' ) {
228
+
229
+ _clearColor.set( 0, 0, 0, 1 );
230
+
231
+ }
232
+ // !WITH_GENESYS
233
+
218
234
  if ( renderer.autoClear === true || forceClear === true ) {
219
235
 
220
236
  const clearColorValue = renderContext.clearColorValue;