@gnsx/three 0.186.5 → 0.186.7

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.
@@ -143,14 +143,17 @@ class Settings extends Parameters {
143
143
  this._bufferState = bufferState;
144
144
  this._debugViewControl = modesGroup.add( debugViewState, 'view', {
145
145
  'Shaded': 'none',
146
+ 'Draw Call': 'drawCall',
146
147
  'Shader Complexity': 'shaderComplexity',
147
148
  'Lighting Complexity': 'lightingComplexity',
148
- 'Quad Overdraw': 'quadOverdraw',
149
- 'Shader Complexity & Quads': 'shaderComplexityAndQuads',
150
- 'Buffer Visualization': 'bufferVisualization',
149
+ 'Shadow Caster': 'shadowCaster',
150
+ 'Double Side': 'doubleSide',
151
+ 'Overdraw': 'overdraw',
152
+ 'Shader Complexity & Overdraw': 'shaderComplexityAndOverdraw',
151
153
  'Lighting Only': 'lightingOnly',
152
154
  'Detail Lighting': 'detailLighting',
153
- 'Draw Call': 'drawCall'
155
+ 'Front/Back Face': 'frontBackFace',
156
+ 'Buffer Visualization': 'bufferVisualization',
154
157
  } ).name( 'Debug View' ).onChange( ( view ) => {
155
158
 
156
159
  const renderer = this.inspector.getRenderer();
@@ -161,7 +164,7 @@ class Settings extends Parameters {
161
164
  this._syncBufferRow( view );
162
165
  window.dispatchEvent( new Event( 'genesys-debug-view' ) );
163
166
 
164
- } ).info( 'Shaded color, a cost heatmap, per-pixel overdraw, shader cost times overdraw, one material channel, lighting on a flat gray surface, or one lit color per draw. The Overdraw toggle above draws on top when both are enabled.' );
167
+ } ).info( 'Shaded color, a cost heatmap, per-pixel overdraw, shader cost times overdraw, one material channel, lighting on a flat gray surface, one lit color per draw, a tint for the front and back of each face, green and gray for objects that do and do not cast shadows, or red where a double-sided back face is hidden. The Overdraw toggle above draws on top when both are enabled.' );
165
168
 
166
169
  this._bufferControl = modesGroup.add( bufferState, 'buffer', {
167
170
  'Base Color': 'baseColor',
@@ -192,7 +195,7 @@ class Settings extends Parameters {
192
195
  const renderer = this.inspector.getRenderer();
193
196
  const view = renderer !== null && renderer.debug !== undefined ? renderer.debug.view : null;
194
197
 
195
- if ( view === 'none' || view === 'shaderComplexity' || view === 'lightingComplexity' || view === 'quadOverdraw' || view === 'shaderComplexityAndQuads' || view === 'bufferVisualization' || view === 'lightingOnly' || view === 'detailLighting' || view === 'drawCall' ) {
198
+ if ( view === 'none' || view === 'shaderComplexity' || view === 'lightingComplexity' || view === 'overdraw' || view === 'shaderComplexityAndOverdraw' || view === 'bufferVisualization' || view === 'lightingOnly' || view === 'detailLighting' || view === 'drawCall' || view === 'frontBackFace' || view === 'shadowCaster' || view === 'doubleSide' ) {
196
199
 
197
200
  if ( this._debugViewState.view !== view ) {
198
201
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnsx/three",
3
- "version": "0.186.5",
3
+ "version": "0.186.7",
4
4
  "description": "JavaScript 3D library",
5
5
  "type": "module",
6
6
  "main": "./build/three.cjs",
@@ -26,10 +26,33 @@ 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
28
  // WITH_GENESYS
29
- import { DEBUG_VIEW_LIGHTING_COMPLEXITY, DEBUG_VIEW_QUAD_OVERDRAW, DEBUG_VIEW_SHADER_COMPLEXITY, DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS, quadOverdrawOutput, shaderComplexityAndQuadsOutput, shaderComplexityOutput } from '../../nodes/display/ComplexityDebug.js';
29
+ import { DEBUG_VIEW_LIGHTING_COMPLEXITY, DEBUG_VIEW_SHADER_COMPLEXITY, DEBUG_VIEW_SHADER_COMPLEXITY_AND_OVERDRAW, debugDrawAccumulates, quadOverdrawOutput, shaderComplexityAndOverdrawOutput, shaderComplexityOutput } from '../../nodes/display/ComplexityDebug.js';
30
+ import { mrt } from '../../nodes/core/MRTNode.js';
30
31
  import { DEBUG_VIEW_BUFFER, assignBufferDefaults, bufferVisualizationOutput } from '../../nodes/display/BufferDebug.js';
31
32
  import { DEBUG_VIEW_DETAIL_LIGHTING, DEBUG_VIEW_LIGHTING_ONLY, applyLightingDebug, lightingOnlyNormal } from '../../nodes/display/LightingDebug.js';
32
33
  import { DEBUG_VIEW_DRAW_CALL, applyDrawCallDebug } from '../../nodes/display/DrawCallDebug.js';
34
+ import { DEBUG_VIEW_FRONT_BACK_FACE, applyFrontBackFaceDebug } from '../../nodes/display/FrontBackFaceDebug.js';
35
+ import { DEBUG_VIEW_SHADOW_CASTER, applyShadowCasterDebug } from '../../nodes/display/ShadowCasterDebug.js';
36
+ import { DEBUG_VIEW_DOUBLE_SIDE, applyDoubleSideDebug } from '../../nodes/display/DoubleSideDebug.js';
37
+
38
+ /**
39
+ * Replaces the color a material writes with a debug output. With MRT, only the `output`
40
+ * attachment is replaced so the other attachments keep their members. Converting the
41
+ * whole MRT struct to a color leaves WGSL with an empty output struct.
42
+ *
43
+ * @param {Node} resultNode - The material's fragment result, possibly an MRT node.
44
+ * @param {function(Node): Node} createOutput - Builds the debug color from the shaded color.
45
+ * @return {Node} The fragment result with the debug color in place.
46
+ */
47
+ function replaceColorOutput( resultNode, createOutput ) {
48
+
49
+ if ( resultNode.isMRTNode !== true ) return createOutput( resultNode );
50
+
51
+ if ( resultNode.has( 'output' ) !== true ) return resultNode;
52
+
53
+ return resultNode.merge( mrt( { output: createOutput( resultNode.get( 'output' ) ) } ) );
54
+
55
+ }
33
56
  // !WITH_GENESYS
34
57
 
35
58
  /**
@@ -548,11 +571,23 @@ class NodeMaterial extends Material {
548
571
  // for detail lighting and can be replaced for lighting only.
549
572
  if ( ( renderer.debug.view === DEBUG_VIEW_LIGHTING_ONLY || renderer.debug.view === DEBUG_VIEW_DETAIL_LIGHTING ) && this.isShadowPassMaterial !== true ) {
550
573
 
551
- applyLightingDebug( builder, renderer.debug.view );
574
+ applyLightingDebug( builder, this, renderer.debug.view );
552
575
 
553
576
  } else if ( renderer.debug.view === DEBUG_VIEW_DRAW_CALL && this.isShadowPassMaterial !== true ) {
554
577
 
555
- applyDrawCallDebug( builder );
578
+ applyDrawCallDebug( builder, this );
579
+
580
+ } else if ( renderer.debug.view === DEBUG_VIEW_FRONT_BACK_FACE && this.isShadowPassMaterial !== true ) {
581
+
582
+ applyFrontBackFaceDebug( builder, this );
583
+
584
+ } else if ( renderer.debug.view === DEBUG_VIEW_SHADOW_CASTER && this.isShadowPassMaterial !== true ) {
585
+
586
+ applyShadowCasterDebug( builder, this );
587
+
588
+ } else if ( renderer.debug.view === DEBUG_VIEW_DOUBLE_SIDE && this.isShadowPassMaterial !== true ) {
589
+
590
+ applyDoubleSideDebug( builder, this );
556
591
 
557
592
  }
558
593
  // !WITH_GENESYS
@@ -627,27 +662,32 @@ class NodeMaterial extends Material {
627
662
  }
628
663
 
629
664
  // WITH_GENESYS
630
- // The screen output pass colorizes the accumulated ratio. Wrapping that pass
631
- // would replace the heatmap with this material's own constant cost.
632
- if ( renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY && this.isShadowPassMaterial !== true && renderer.isOutputTarget !== true ) {
665
+ // The screen output pass colorizes the accumulated ratio. Wrapping that pass, or a
666
+ // post-processing quad that copies the scene target, would replace the heatmap with
667
+ // that quad's own constant cost.
668
+ if ( debugDrawAccumulates( renderer, this, builder.object ) ) {
633
669
 
634
- builder.shaderComplexityBase = this.getShaderComplexity();
635
- resultNode = shaderComplexityOutput( resultNode );
670
+ if ( renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY ) {
636
671
 
637
- } else if ( renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS && this.isShadowPassMaterial !== true && renderer.isOutputTarget !== true ) {
672
+ builder.shaderComplexityBase = this.getShaderComplexity();
673
+ resultNode = replaceColorOutput( resultNode, shaderComplexityOutput );
638
674
 
639
- builder.shaderComplexityBase = this.getShaderComplexity();
640
- resultNode = shaderComplexityAndQuadsOutput( resultNode );
675
+ } else if ( renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_OVERDRAW ) {
641
676
 
642
- } else if ( renderer.debug.view === DEBUG_VIEW_QUAD_OVERDRAW && this.isShadowPassMaterial !== true && renderer.isOutputTarget !== true ) {
677
+ builder.shaderComplexityBase = this.getShaderComplexity();
678
+ resultNode = replaceColorOutput( resultNode, shaderComplexityAndOverdrawOutput );
643
679
 
644
- resultNode = quadOverdrawOutput( resultNode );
680
+ } else {
681
+
682
+ resultNode = replaceColorOutput( resultNode, quadOverdrawOutput );
683
+
684
+ }
645
685
 
646
686
  } else if ( renderer.debug.view === DEBUG_VIEW_BUFFER && this.isShadowPassMaterial !== true && this.fragmentNode === null ) {
647
687
 
648
688
  // The fullscreen output pass sets fragmentNode. Scene materials do not, including
649
689
  // when the scene is drawn straight to the canvas.
650
- resultNode = bufferVisualizationOutput( renderer.debug.buffer );
690
+ resultNode = replaceColorOutput( resultNode, () => bufferVisualizationOutput( renderer.debug.buffer ) );
651
691
 
652
692
  }
653
693
  // !WITH_GENESYS
@@ -1007,7 +1047,7 @@ class NodeMaterial extends Material {
1007
1047
  setupNormal( builder ) {
1008
1048
 
1009
1049
  // WITH_GENESYS
1010
- if ( ( builder.renderer.debug.view === DEBUG_VIEW_LIGHTING_ONLY || builder.renderer.debug.view === DEBUG_VIEW_DRAW_CALL ) && this.isShadowPassMaterial !== true && this.fragmentNode === null ) {
1050
+ if ( ( builder.renderer.debug.view === DEBUG_VIEW_LIGHTING_ONLY || builder.renderer.debug.view === DEBUG_VIEW_DRAW_CALL || builder.renderer.debug.view === DEBUG_VIEW_FRONT_BACK_FACE || builder.renderer.debug.view === DEBUG_VIEW_SHADOW_CASTER || builder.renderer.debug.view === DEBUG_VIEW_DOUBLE_SIDE ) && this.isShadowPassMaterial !== true && this.fragmentNode === null ) {
1011
1051
 
1012
1052
  return lightingOnlyNormal();
1013
1053
 
@@ -1212,7 +1252,7 @@ class NodeMaterial extends Material {
1212
1252
  // WITH_GENESYS
1213
1253
  // Lighting only and draw call substitute a plain lit material, so emissive is dropped.
1214
1254
  // Detail lighting keeps it.
1215
- if ( ( builder.renderer.debug.view === DEBUG_VIEW_LIGHTING_ONLY || builder.renderer.debug.view === DEBUG_VIEW_DRAW_CALL ) && this.isShadowPassMaterial !== true ) {
1255
+ if ( ( builder.renderer.debug.view === DEBUG_VIEW_LIGHTING_ONLY || builder.renderer.debug.view === DEBUG_VIEW_DRAW_CALL || builder.renderer.debug.view === DEBUG_VIEW_FRONT_BACK_FACE || builder.renderer.debug.view === DEBUG_VIEW_SHADOW_CASTER || builder.renderer.debug.view === DEBUG_VIEW_DOUBLE_SIDE ) && this.isShadowPassMaterial !== true ) {
1216
1256
 
1217
1257
  return outgoingLightNode;
1218
1258
 
@@ -5,6 +5,7 @@
5
5
  import Node from '../core/Node.js';
6
6
  import { float, vec3, vec4, If, Fn } from '../tsl/TSLCore.js';
7
7
  import { fract, min, mix, round } from '../math/MathNode.js';
8
+ import { DEBUG_VIEW_BUFFER } from './BufferDebug.js';
8
9
 
9
10
  /**
10
11
  * Shaded output. No complexity debug view.
@@ -14,7 +15,8 @@ import { fract, min, mix, round } from '../math/MathNode.js';
14
15
  export const DEBUG_VIEW_NONE = 'none';
15
16
 
16
17
  /**
17
- * Material-cost heatmap. Overlapping draws accumulate, then a fullscreen pass applies the ramp.
18
+ * Material-cost heatmap. The nearest opaque surface wins, translucent draws add on top,
19
+ * then a fullscreen pass applies the ramp.
18
20
  *
19
21
  * @type {string}
20
22
  */
@@ -33,16 +35,16 @@ export const DEBUG_VIEW_LIGHTING_COMPLEXITY = 'lightingComplexity';
33
35
  *
34
36
  * @type {string}
35
37
  */
36
- export const DEBUG_VIEW_QUAD_OVERDRAW = 'quadOverdraw';
38
+ export const DEBUG_VIEW_OVERDRAW = 'overdraw';
37
39
 
38
40
  /**
39
41
  * 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.
42
+ * One opaque surface matches {@link DEBUG_VIEW_SHADER_COMPLEXITY}. Each extra translucent fragment scales the summed cost.
41
43
  * WebGPU cannot count wasted lanes in a 2×2 quad, so the count is fragments that pass the depth test.
42
44
  *
43
45
  * @type {string}
44
46
  */
45
- export const DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS = 'shaderComplexityAndQuads';
47
+ export const DEBUG_VIEW_SHADER_COMPLEXITY_AND_OVERDRAW = 'shaderComplexityAndOverdraw';
46
48
 
47
49
  /**
48
50
  * Proxy budget that fills the shader-complexity ramp.
@@ -62,7 +64,7 @@ export const DEFAULT_SHADER_COMPLEXITY_BUDGET = 800;
62
64
  export const DEFAULT_QUAD_OVERDRAW_BUDGET = 10;
63
65
 
64
66
  /**
65
- * Storage scale for the overdraw count in {@link DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS}.
67
+ * Storage scale for the overdraw count in {@link DEBUG_VIEW_SHADER_COMPLEXITY_AND_OVERDRAW}.
66
68
  * Each fragment adds `1 / scale`, so an 8-bit target can hold this many layers.
67
69
  * The output pass multiplies that channel back to a count. Half-float targets hold the same count exactly.
68
70
  *
@@ -220,7 +222,54 @@ export const colorizeQuadOverdraw = ( cost ) => {
220
222
  */
221
223
  export function debugViewAccumulates( view ) {
222
224
 
223
- return view === DEBUG_VIEW_SHADER_COMPLEXITY || view === DEBUG_VIEW_QUAD_OVERDRAW || view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS;
225
+ return view === DEBUG_VIEW_SHADER_COMPLEXITY || view === DEBUG_VIEW_OVERDRAW || view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_OVERDRAW;
226
+
227
+ }
228
+
229
+ /**
230
+ * Whether a draw adds its scalar to the accumulating target instead of shading normally.
231
+ * Shadow passes, draws to the screen, and fullscreen quads draw normally. A post-processing
232
+ * quad copies the accumulated scene target, so its own cost must not replace it.
233
+ *
234
+ * @param {Renderer} renderer - The renderer.
235
+ * @param {Material} material - The material being drawn.
236
+ * @param {?Object3D} object - The object being drawn.
237
+ * @return {boolean} `true` when the draw accumulates.
238
+ */
239
+ export function debugDrawAccumulates( renderer, material, object ) {
240
+
241
+ return debugViewAccumulates( renderer.debug.view ) &&
242
+ material.isShadowPassMaterial !== true &&
243
+ renderer.isOutputTarget !== true &&
244
+ ( object === null || object === undefined || object.isQuadMesh !== true );
245
+
246
+ }
247
+
248
+ /**
249
+ * Whether an accumulating draw replaces the stored cost instead of adding to it.
250
+ * Three.js has no depth pre-pass, so adding every opaque fragment counts hidden surfaces
251
+ * and changes as the camera reorders draws. Replacing, with the material's own depth test
252
+ * and depth write, leaves the nearest opaque surface. Translucent draws and quad overdraw still add.
253
+ *
254
+ * @param {string} view - `renderer.debug.view`.
255
+ * @param {Material} material - The material being drawn.
256
+ * @return {boolean} `true` when the draw replaces the stored cost.
257
+ */
258
+ export function debugDrawReplacesCost( view, material ) {
259
+
260
+ return ( view === DEBUG_VIEW_SHADER_COMPLEXITY || view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_OVERDRAW ) && material.transparent !== true;
261
+
262
+ }
263
+
264
+ /**
265
+ * Views that output data or a heatmap, so tone mapping would distort them.
266
+ *
267
+ * @param {string} view - `renderer.debug.view`.
268
+ * @return {boolean} `true` when the view is drawn without tone mapping.
269
+ */
270
+ export function debugViewSkipsToneMapping( view ) {
271
+
272
+ return debugViewAccumulates( view ) || view === DEBUG_VIEW_LIGHTING_COMPLEXITY || view === DEBUG_VIEW_BUFFER;
224
273
 
225
274
  }
226
275
 
@@ -343,7 +392,7 @@ export function shaderComplexityOutput( resultNode ) {
343
392
  * @param {Node} resultNode - The material's shaded output.
344
393
  * @return {Node<vec4>} Cost ratio in red, one scaled fragment in green.
345
394
  */
346
- export function shaderComplexityAndQuadsOutput( resultNode ) {
395
+ export function shaderComplexityAndOverdrawOutput( resultNode ) {
347
396
 
348
397
  const shaded = vec4( resultNode ).toVar();
349
398
  const ratio = new ShaderComplexityRatioNode();
@@ -0,0 +1,152 @@
1
+ // WITH_GENESYS
2
+ // Double sided. Gray is a single-sided surface or the front of a double-sided one.
3
+ // Blue is a double-sided back face you can see. Red is a double-sided back face hidden behind the front.
4
+ // !WITH_GENESYS
5
+
6
+ import Node from '../core/Node.js';
7
+ import { nodeImmutable } from '../tsl/TSLBase.js';
8
+ import { select } from '../math/ConditionalNode.js';
9
+ import { diffuseColor, diffuseContribution, metalness, roughness, specularColor, specularColorBlended, specularF90, clearcoat, sheen, iridescence, anisotropy, transmission, retroreflectivity } from '../core/PropertyNode.js';
10
+ import { float, vec3 } from '../tsl/TSLCore.js';
11
+ import { DoubleSide } from '../../constants.js';
12
+
13
+ /**
14
+ * Double sided. A single-sided material is gray. A double-sided front face is gray.
15
+ * A double-sided back face that is the nearest surface is blue. A later pass paints
16
+ * a hidden double-sided back face red over the front.
17
+ *
18
+ * @type {string}
19
+ */
20
+ export const DEBUG_VIEW_DOUBLE_SIDE = 'doubleSide';
21
+
22
+ /**
23
+ * Rasterizer front face, ignoring the material side shortcut in {@link frontFacing}.
24
+ */
25
+ class GeometricFrontFacingNode extends Node {
26
+
27
+ static get type() {
28
+
29
+ return 'GeometricFrontFacingNode';
30
+
31
+ }
32
+
33
+ constructor() {
34
+
35
+ super( 'bool' );
36
+
37
+ }
38
+
39
+ generate( builder ) {
40
+
41
+ if ( builder.shaderStage !== 'fragment' ) return 'true';
42
+
43
+ return builder.getFrontFacing();
44
+
45
+ }
46
+
47
+ }
48
+
49
+ const geometricFrontFacing = /*@__PURE__*/ nodeImmutable( GeometricFrontFacingNode );
50
+
51
+ const hideCache = new WeakMap();
52
+
53
+ /**
54
+ * A flat card or wall has normals that all point the same way, so its back face is the same
55
+ * surface. A box or a closed shell has normals that face each other, so one side can hide the other.
56
+ *
57
+ * @param {BufferGeometry} geometry - The mesh geometry.
58
+ * @return {boolean} `true` when some normals oppose the first one.
59
+ */
60
+ export function meshCanHideOwnBack( geometry ) {
61
+
62
+ const normal = geometry.getAttribute( 'normal' );
63
+ const cached = hideCache.get( geometry );
64
+
65
+ if ( cached !== undefined && cached.version === geometry.version ) return cached.hides;
66
+
67
+ let hides = false;
68
+
69
+ if ( normal !== undefined && normal.count > 1 ) {
70
+
71
+ const ax = normal.getX( 0 );
72
+ const ay = normal.getY( 0 );
73
+ const az = normal.getZ( 0 );
74
+ const step = Math.max( 1, Math.floor( normal.count / 64 ) );
75
+
76
+ for ( let i = step; i < normal.count; i += step ) {
77
+
78
+ if ( ax * normal.getX( i ) + ay * normal.getY( i ) + az * normal.getZ( i ) < 0.25 ) {
79
+
80
+ hides = true;
81
+ break;
82
+
83
+ }
84
+
85
+ }
86
+
87
+ }
88
+
89
+ hideCache.set( geometry, { version: geometry.version, hides } );
90
+
91
+ return hides;
92
+
93
+ }
94
+
95
+ const FRONT_COLOR = /*@__PURE__*/ vec3( 0.5, 0.5, 0.5 );
96
+ const BACK_COLOR = /*@__PURE__*/ vec3( 0.25, 0.45, 0.85 );
97
+ const HIDDEN_COLOR = /*@__PURE__*/ vec3( 0.85, 0.12, 0.1 );
98
+
99
+ /**
100
+ * Makes a lit material a matte dielectric in the debug color. An unlit material stays flat.
101
+ *
102
+ * @param {NodeBuilder} builder - The current node builder.
103
+ * @param {NodeMaterial} material - The node material being set up.
104
+ * @param {Node} colorNode - The albedo written for this fragment.
105
+ */
106
+ function assignLitColor( builder, material, colorNode ) {
107
+
108
+ diffuseColor.rgb.assign( colorNode );
109
+
110
+ if ( material.lights !== true ) return;
111
+
112
+ metalness.assign( float( 0 ) );
113
+ diffuseContribution.assign( colorNode );
114
+ roughness.assign( float( 1 ) );
115
+ specularColor.assign( vec3( 0 ) );
116
+ specularColorBlended.assign( vec3( 0 ) );
117
+ specularF90.assign( float( 0 ) );
118
+
119
+ builder.context.ambientOcclusion = null;
120
+
121
+ if ( material.useClearcoat === true ) clearcoat.assign( float( 0 ) );
122
+
123
+ if ( material.useSheen === true ) sheen.assign( vec3( 0 ) );
124
+
125
+ if ( material.useIridescence === true ) iridescence.assign( float( 0 ) );
126
+
127
+ if ( material.useAnisotropy === true ) anisotropy.assign( float( 0 ) );
128
+
129
+ if ( material.useTransmission === true ) transmission.assign( float( 0 ) );
130
+
131
+ if ( material.useRetroreflection === true ) retroreflectivity.assign( float( 0 ) );
132
+
133
+ }
134
+
135
+ /**
136
+ * Replaces albedo after the material has written its channels.
137
+ * The hidden pass forces red. The first pass uses gray, or blue on a visible double-sided back face.
138
+ *
139
+ * @param {NodeBuilder} builder - The current node builder.
140
+ * @param {NodeMaterial} material - The node material being set up. `builder.material` can be the
141
+ * source material of a converted one, such as a glTF `MeshStandardMaterial`.
142
+ */
143
+ export function applyDoubleSideDebug( builder, material ) {
144
+
145
+ const hidden = builder.renderer.debug.doubleSideHidden === true;
146
+ const visibleBack = builder.renderer.debug.doubleSideBack === true;
147
+ const doubleSided = material.side === DoubleSide;
148
+ const colorNode = hidden ? HIDDEN_COLOR : ( visibleBack ? BACK_COLOR : ( doubleSided ? select( geometricFrontFacing, FRONT_COLOR, BACK_COLOR ) : FRONT_COLOR ) );
149
+
150
+ assignLitColor( builder, material, colorNode );
151
+
152
+ }
@@ -77,10 +77,11 @@ function writeDrawCallColor( color, object, material ) {
77
77
  * The color is an object uniform, so meshes that share a material still differ.
78
78
  *
79
79
  * @param {NodeBuilder} builder - The current node builder.
80
+ * @param {NodeMaterial} material - The node material being set up. `builder.material` can be the
81
+ * source material of a converted one, such as a glTF `MeshStandardMaterial`.
80
82
  */
81
- export function applyDrawCallDebug( builder ) {
83
+ export function applyDrawCallDebug( builder, material ) {
82
84
 
83
- const material = builder.material;
84
85
  const colorNode = uniform( new Color() ).onObjectUpdate( ( { object, material: drawnMaterial }, self ) => {
85
86
 
86
87
  if ( object === null || drawnMaterial === null ) return;
@@ -0,0 +1,88 @@
1
+ // WITH_GENESYS
2
+ // Front/back face. Both windings are drawn. The side facing the camera uses one color, the other side another.
3
+ // !WITH_GENESYS
4
+
5
+ import Node from '../core/Node.js';
6
+ import { nodeImmutable } from '../tsl/TSLBase.js';
7
+ import { select } from '../math/ConditionalNode.js';
8
+ import { diffuseColor, diffuseContribution, metalness, roughness, specularColor, specularColorBlended, specularF90, clearcoat, sheen, iridescence, anisotropy, transmission, retroreflectivity } from '../core/PropertyNode.js';
9
+ import { float, vec3 } from '../tsl/TSLCore.js';
10
+
11
+ /**
12
+ * Front/back face. Culling is off. A fragment whose winding faces the camera is one color.
13
+ * The opposite winding is another. Depth still hides a face behind the shell.
14
+ *
15
+ * @type {string}
16
+ */
17
+ export const DEBUG_VIEW_FRONT_BACK_FACE = 'frontBackFace';
18
+
19
+ /**
20
+ * Rasterizer front face, ignoring the material side shortcut in {@link frontFacing}.
21
+ * `BackSide` materials otherwise report every fragment as back facing.
22
+ */
23
+ class GeometricFrontFacingNode extends Node {
24
+
25
+ static get type() {
26
+
27
+ return 'GeometricFrontFacingNode';
28
+
29
+ }
30
+
31
+ constructor() {
32
+
33
+ super( 'bool' );
34
+
35
+ }
36
+
37
+ generate( builder ) {
38
+
39
+ if ( builder.shaderStage !== 'fragment' ) return 'true';
40
+
41
+ return builder.getFrontFacing();
42
+
43
+ }
44
+
45
+ }
46
+
47
+ const geometricFrontFacing = /*@__PURE__*/ nodeImmutable( GeometricFrontFacingNode );
48
+
49
+ const FRONT_COLOR = /*@__PURE__*/ vec3( 0.72, 0.70, 0.66 );
50
+ const BACK_COLOR = /*@__PURE__*/ vec3( 0.25, 0.45, 0.85 );
51
+
52
+ /**
53
+ * Replaces albedo with the front or back color after the material has written its channels.
54
+ * Lit materials become a matte dielectric. Unlit materials become flat.
55
+ *
56
+ * @param {NodeBuilder} builder - The current node builder.
57
+ * @param {NodeMaterial} material - The node material being set up.
58
+ */
59
+ export function applyFrontBackFaceDebug( builder, material ) {
60
+
61
+ const colorNode = select( geometricFrontFacing, FRONT_COLOR, BACK_COLOR );
62
+
63
+ diffuseColor.rgb.assign( colorNode );
64
+
65
+ if ( material.lights !== true ) return;
66
+
67
+ metalness.assign( float( 0 ) );
68
+ diffuseContribution.assign( colorNode );
69
+ roughness.assign( float( 1 ) );
70
+ specularColor.assign( vec3( 0 ) );
71
+ specularColorBlended.assign( vec3( 0 ) );
72
+ specularF90.assign( float( 0 ) );
73
+
74
+ builder.context.ambientOcclusion = null;
75
+
76
+ if ( material.useClearcoat === true ) clearcoat.assign( float( 0 ) );
77
+
78
+ if ( material.useSheen === true ) sheen.assign( vec3( 0 ) );
79
+
80
+ if ( material.useIridescence === true ) iridescence.assign( float( 0 ) );
81
+
82
+ if ( material.useAnisotropy === true ) anisotropy.assign( float( 0 ) );
83
+
84
+ if ( material.useTransmission === true ) transmission.assign( float( 0 ) );
85
+
86
+ if ( material.useRetroreflection === true ) retroreflectivity.assign( float( 0 ) );
87
+
88
+ }
@@ -59,11 +59,12 @@ export const lightingOnlyNormal = /*@__PURE__*/ Fn( ( builder ) => {
59
59
  * Lit materials become a dielectric gray. Unlit materials become flat gray.
60
60
  *
61
61
  * @param {NodeBuilder} builder - The current node builder.
62
+ * @param {NodeMaterial} material - The node material being set up. `builder.material` can be the
63
+ * source material of a converted one, such as a glTF `MeshStandardMaterial`.
62
64
  * @param {string} view - `renderer.debug.view`.
63
65
  */
64
- export function applyLightingDebug( builder, view ) {
66
+ export function applyLightingDebug( builder, material, view ) {
65
67
 
66
- const material = builder.material;
67
68
  const gray = vec3( LIGHTING_ONLY_BRIGHTNESS );
68
69
 
69
70
  diffuseColor.rgb.assign( gray );
@@ -0,0 +1,69 @@
1
+ // WITH_GENESYS
2
+ // Shadow casters. Green when the object casts a shadow, gray when it does not.
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
+ * Shadow casters. A mesh that casts shadows is green. One that does not is gray.
12
+ * The color is lit, so form stays readable.
13
+ *
14
+ * Matches Unreal's simplified shadow-caster view (`GetCachedShadowCasterColorSimplified`):
15
+ * green `(0, 1, 0)` casts, gray `(0.5, 0.5, 0.5)` does not. Contact-shadow yellow is omitted;
16
+ * Three.js has no per-object contact-shadow flag.
17
+ *
18
+ * @type {string}
19
+ */
20
+ export const DEBUG_VIEW_SHADOW_CASTER = 'shadowCaster';
21
+
22
+ const CASTS_COLOR = new Color( 0, 1, 0 );
23
+ const NONE_COLOR = new Color( 0.5, 0.5, 0.5 );
24
+
25
+ /**
26
+ * Replaces albedo with the caster color after the material has written its channels.
27
+ * Lit materials become a matte dielectric. Unlit materials become flat.
28
+ * The color is an object uniform, so meshes that share a material still differ.
29
+ *
30
+ * @param {NodeBuilder} builder - The current node builder.
31
+ * @param {NodeMaterial} material - The node material being set up. `builder.material` can be the
32
+ * source material of a converted one, such as a glTF `MeshStandardMaterial`.
33
+ */
34
+ export function applyShadowCasterDebug( builder, material ) {
35
+
36
+ const colorNode = uniform( new Color() ).onObjectUpdate( ( { object }, self ) => {
37
+
38
+ if ( object === null ) return;
39
+
40
+ return self.value.copy( object.castShadow === true ? CASTS_COLOR : NONE_COLOR );
41
+
42
+ } );
43
+
44
+ diffuseColor.rgb.assign( colorNode );
45
+
46
+ if ( material.lights !== true ) return;
47
+
48
+ metalness.assign( float( 0 ) );
49
+ diffuseContribution.assign( colorNode );
50
+ roughness.assign( float( 1 ) );
51
+ specularColor.assign( vec3( 0 ) );
52
+ specularColorBlended.assign( vec3( 0 ) );
53
+ specularF90.assign( float( 0 ) );
54
+
55
+ builder.context.ambientOcclusion = null;
56
+
57
+ if ( material.useClearcoat === true ) clearcoat.assign( float( 0 ) );
58
+
59
+ if ( material.useSheen === true ) sheen.assign( vec3( 0 ) );
60
+
61
+ if ( material.useIridescence === true ) iridescence.assign( float( 0 ) );
62
+
63
+ if ( material.useAnisotropy === true ) anisotropy.assign( float( 0 ) );
64
+
65
+ if ( material.useTransmission === true ) transmission.assign( float( 0 ) );
66
+
67
+ if ( material.useRetroreflection === true ) retroreflectivity.assign( float( 0 ) );
68
+
69
+ }
@@ -227,9 +227,11 @@ class Background extends DataMap {
227
227
  //
228
228
 
229
229
  // WITH_GENESYS
230
+ // Accumulating views start from zero cost. Alpha keeps the shaded pass coverage, so an
231
+ // overlay pass such as a HUD stays transparent where it draws nothing when composited.
230
232
  if ( debugViewAccumulates( renderer.debug.view ) ) {
231
233
 
232
- _clearColor.set( 0, 0, 0, 1 );
234
+ _clearColor.set( 0, 0, 0, background !== null ? 1 : _clearColor.a );
233
235
 
234
236
  }
235
237
  // !WITH_GENESYS
@@ -952,7 +952,7 @@ class RenderObject {
952
952
 
953
953
  // WITH_GENESYS
954
954
  // hash() xors each argument as a number, so a view name has to be hashed first.
955
- cacheKey = hash( cacheKey, hashString( this.renderer.debug.view ), hashString( this.renderer.debug.buffer ), this.renderer.debug.shaderComplexityBudget, this.renderer.debug.quadOverdrawBudget );
955
+ cacheKey = hash( cacheKey, hashString( this.renderer.debug.view ), hashString( this.renderer.debug.buffer ), this.renderer.debug.shaderComplexityBudget, this.renderer.debug.quadOverdrawBudget, this.renderer.debug.doubleSideHidden === true ? 1 : 0, this.renderer.debug.doubleSideBack === true ? 1 : 0 );
956
956
  // !WITH_GENESYS
957
957
 
958
958
  return cacheKey;