@gnsx/three 0.186.6 → 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.6",
3
+ "version": "0.186.7",
4
4
  "description": "JavaScript 3D library",
5
5
  "type": "module",
6
6
  "main": "./build/three.cjs",
@@ -26,11 +26,14 @@ 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_SHADER_COMPLEXITY, DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS, debugDrawAccumulates, 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
30
  import { mrt } from '../../nodes/core/MRTNode.js';
31
31
  import { DEBUG_VIEW_BUFFER, assignBufferDefaults, bufferVisualizationOutput } from '../../nodes/display/BufferDebug.js';
32
32
  import { DEBUG_VIEW_DETAIL_LIGHTING, DEBUG_VIEW_LIGHTING_ONLY, applyLightingDebug, lightingOnlyNormal } from '../../nodes/display/LightingDebug.js';
33
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';
34
37
 
35
38
  /**
36
39
  * Replaces the color a material writes with a debug output. With MRT, only the `output`
@@ -574,6 +577,18 @@ class NodeMaterial extends Material {
574
577
 
575
578
  applyDrawCallDebug( builder, this );
576
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 );
591
+
577
592
  }
578
593
  // !WITH_GENESYS
579
594
 
@@ -657,10 +672,10 @@ class NodeMaterial extends Material {
657
672
  builder.shaderComplexityBase = this.getShaderComplexity();
658
673
  resultNode = replaceColorOutput( resultNode, shaderComplexityOutput );
659
674
 
660
- } else if ( renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS ) {
675
+ } else if ( renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_OVERDRAW ) {
661
676
 
662
677
  builder.shaderComplexityBase = this.getShaderComplexity();
663
- resultNode = replaceColorOutput( resultNode, shaderComplexityAndQuadsOutput );
678
+ resultNode = replaceColorOutput( resultNode, shaderComplexityAndOverdrawOutput );
664
679
 
665
680
  } else {
666
681
 
@@ -1032,7 +1047,7 @@ class NodeMaterial extends Material {
1032
1047
  setupNormal( builder ) {
1033
1048
 
1034
1049
  // WITH_GENESYS
1035
- 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 ) {
1036
1051
 
1037
1052
  return lightingOnlyNormal();
1038
1053
 
@@ -1237,7 +1252,7 @@ class NodeMaterial extends Material {
1237
1252
  // WITH_GENESYS
1238
1253
  // Lighting only and draw call substitute a plain lit material, so emissive is dropped.
1239
1254
  // Detail lighting keeps it.
1240
- 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 ) {
1241
1256
 
1242
1257
  return outgoingLightNode;
1243
1258
 
@@ -15,7 +15,8 @@ import { DEBUG_VIEW_BUFFER } from './BufferDebug.js';
15
15
  export const DEBUG_VIEW_NONE = 'none';
16
16
 
17
17
  /**
18
- * 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.
19
20
  *
20
21
  * @type {string}
21
22
  */
@@ -34,16 +35,16 @@ export const DEBUG_VIEW_LIGHTING_COMPLEXITY = 'lightingComplexity';
34
35
  *
35
36
  * @type {string}
36
37
  */
37
- export const DEBUG_VIEW_QUAD_OVERDRAW = 'quadOverdraw';
38
+ export const DEBUG_VIEW_OVERDRAW = 'overdraw';
38
39
 
39
40
  /**
40
41
  * Shader cost multiplied by the number of fragments that shaded the pixel, then the shader-complexity ramp.
41
- * 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.
42
43
  * WebGPU cannot count wasted lanes in a 2×2 quad, so the count is fragments that pass the depth test.
43
44
  *
44
45
  * @type {string}
45
46
  */
46
- export const DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS = 'shaderComplexityAndQuads';
47
+ export const DEBUG_VIEW_SHADER_COMPLEXITY_AND_OVERDRAW = 'shaderComplexityAndOverdraw';
47
48
 
48
49
  /**
49
50
  * Proxy budget that fills the shader-complexity ramp.
@@ -63,7 +64,7 @@ export const DEFAULT_SHADER_COMPLEXITY_BUDGET = 800;
63
64
  export const DEFAULT_QUAD_OVERDRAW_BUDGET = 10;
64
65
 
65
66
  /**
66
- * 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}.
67
68
  * Each fragment adds `1 / scale`, so an 8-bit target can hold this many layers.
68
69
  * The output pass multiplies that channel back to a count. Half-float targets hold the same count exactly.
69
70
  *
@@ -221,7 +222,7 @@ export const colorizeQuadOverdraw = ( cost ) => {
221
222
  */
222
223
  export function debugViewAccumulates( view ) {
223
224
 
224
- 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;
225
226
 
226
227
  }
227
228
 
@@ -244,6 +245,22 @@ export function debugDrawAccumulates( renderer, material, object ) {
244
245
 
245
246
  }
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
+
247
264
  /**
248
265
  * Views that output data or a heatmap, so tone mapping would distort them.
249
266
  *
@@ -375,7 +392,7 @@ export function shaderComplexityOutput( resultNode ) {
375
392
  * @param {Node} resultNode - The material's shaded output.
376
393
  * @return {Node<vec4>} Cost ratio in red, one scaled fragment in green.
377
394
  */
378
- export function shaderComplexityAndQuadsOutput( resultNode ) {
395
+ export function shaderComplexityAndOverdrawOutput( resultNode ) {
379
396
 
380
397
  const shaded = vec4( resultNode ).toVar();
381
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
+ }
@@ -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
+ }
@@ -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;
@@ -30,7 +30,7 @@ import { Matrix4 } from '../../math/Matrix4.js';
30
30
  import { Vector2 } from '../../math/Vector2.js';
31
31
  import { Vector4 } from '../../math/Vector4.js';
32
32
  import { RenderTarget } from '../../core/RenderTarget.js';
33
- import { DoubleSide, BackSide, FrontSide, SRGBColorSpace, NoToneMapping, LinearFilter, HalfFloatType, RGBAFormat, PCFShadowMap, PCFSoftShadowMap, VSMShadowMap, RenderObjectRefreshType } from '../../constants.js';
33
+ import { DoubleSide, BackSide, FrontSide, EqualDepth, SRGBColorSpace, NoToneMapping, LinearFilter, HalfFloatType, RGBAFormat, PCFShadowMap, PCFSoftShadowMap, VSMShadowMap, RenderObjectRefreshType } from '../../constants.js';
34
34
 
35
35
  import { float, vec3, vec4, Fn } from '../../nodes/tsl/TSLCore.js';
36
36
  // WITH_GENESYS
@@ -38,6 +38,7 @@ import { detachRendererReference } from '../../nodes/accessors/RendererReference
38
38
  import { toneMappingExposure } from '../../nodes/display/ToneMappingNode.js';
39
39
  import { ProfilerService } from '../../profiler/ProfilerService.js';
40
40
  import { DEBUG_VIEW_NONE, DEFAULT_QUAD_OVERDRAW_BUDGET, DEFAULT_SHADER_COMPLEXITY_BUDGET, debugViewAccumulates, debugViewSkipsToneMapping } from '../../nodes/display/ComplexityDebug.js';
41
+ import { DEBUG_VIEW_DOUBLE_SIDE, meshCanHideOwnBack } from '../../nodes/display/DoubleSideDebug.js';
41
42
  import { DEFAULT_BUFFER } from '../../nodes/display/BufferDebug.js';
42
43
  // !WITH_GENESYS
43
44
  import { reference } from '../../nodes/accessors/ReferenceNode.js';
@@ -745,7 +746,7 @@ class Renderer {
745
746
  * @property {?Function} onNodeBuilderCreated - A callback function that is executed after a node builder has been created and before it is built.
746
747
  * @property {?Function} onShaderError - A callback function that is executed when a shader error happens. Only supported with WebGL 2 right now.
747
748
  * @property {Function} getShaderAsync - Allows the get the raw shader code for the given scene, camera and 3D object.
748
- * @property {string} view - Debug view. `shaderComplexity`, `lightingComplexity`, `quadOverdraw`, and `shaderComplexityAndQuads` replace the shaded color with a heatmap. `shaderComplexityAndQuads` multiplies the shader cost by the overdraw count. `bufferVisualization` shows one material channel. `lightingOnly` and `detailLighting` light a flat gray surface. `drawCall` lights each submitted draw with its own diffuse color.
749
+ * @property {string} view - Debug view. `shaderComplexity`, `lightingComplexity`, `overdraw`, and `shaderComplexityAndOverdraw` replace the shaded color with a heatmap. `shaderComplexityAndOverdraw` multiplies the shader cost by the overdraw count. `bufferVisualization` shows one material channel. `lightingOnly` and `detailLighting` light a flat gray surface. `drawCall` lights each submitted draw with its own diffuse color. `frontBackFace` draws both windings and tints the side facing the camera. `shadowCaster` lights a mesh green when it casts shadows and gray when it does not. `doubleSide` lights a single-sided surface gray, a visible double-sided back face blue, and a hidden double-sided back face red.
749
750
  * @property {string} buffer - Channel drawn by `bufferVisualization`: `baseColor`, `worldNormal`, `roughness`, `metallic`, `ambientOcclusion`, or `emissive`.
750
751
  * @property {number} shaderComplexityBudget - Proxy budget that fills the shader-complexity ramp.
751
752
  * @property {number} quadOverdrawBudget - Overlapping fragments that fill the quad-overdraw ramp.
@@ -768,6 +769,8 @@ class Renderer {
768
769
  buffer: DEFAULT_BUFFER,
769
770
  shaderComplexityBudget: DEFAULT_SHADER_COMPLEXITY_BUDGET,
770
771
  quadOverdrawBudget: DEFAULT_QUAD_OVERDRAW_BUDGET,
772
+ doubleSideHidden: false,
773
+ doubleSideBack: false,
771
774
  // !WITH_GENESYS
772
775
  getShaderAsync: async ( scene, camera, object ) => {
773
776
 
@@ -1988,6 +1991,10 @@ class Renderer {
1988
1991
  if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode );
1989
1992
  if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode );
1990
1993
 
1994
+ // WITH_GENESYS
1995
+ this._renderDoubleSideHidden( opaqueObjects, transparentObjects, camera, sceneRef, lightsNode );
1996
+ // !WITH_GENESYS
1997
+
1991
1998
  // finish render pass
1992
1999
 
1993
2000
  this.backend.finishRender( renderContext );
@@ -3558,6 +3565,82 @@ class Renderer {
3558
3565
 
3559
3566
  }
3560
3567
 
3568
+ // WITH_GENESYS
3569
+ /**
3570
+ * Paints a double-sided mesh red where its own front face is the nearest surface and the
3571
+ * mesh has faces pointing the other way. A different mesh in front keeps its own color.
3572
+ * A flat card has no opposing faces, so it stays gray or blue. A shadow pass is left alone.
3573
+ *
3574
+ * @private
3575
+ * @param {Array<Object>} opaqueObjects - The opaque render list.
3576
+ * @param {Array<Object>} transparentObjects - The transparent render list.
3577
+ * @param {Camera} camera - The camera.
3578
+ * @param {Scene} scene - The scene.
3579
+ * @param {LightsNode} lightsNode - The current lights node.
3580
+ */
3581
+ _renderDoubleSideHidden( opaqueObjects, transparentObjects, camera, scene, lightsNode ) {
3582
+
3583
+ if ( this.debug.view !== DEBUG_VIEW_DOUBLE_SIDE ) return;
3584
+
3585
+ if ( scene.overrideMaterial !== null && scene.overrideMaterial.isShadowPassMaterial === true ) return;
3586
+
3587
+ const list = [];
3588
+ const saved = new Map();
3589
+
3590
+ const collect = ( objects ) => {
3591
+
3592
+ for ( let i = 0, l = objects.length; i < l; i ++ ) {
3593
+
3594
+ const entry = objects[ i ];
3595
+ const material = entry.material;
3596
+ const geometry = entry.object.geometry;
3597
+
3598
+ if ( entry.object.isMesh !== true || material.side !== DoubleSide || material.isShadowPassMaterial === true ) continue;
3599
+
3600
+ if ( geometry === undefined || meshCanHideOwnBack( geometry ) !== true ) continue;
3601
+
3602
+ list.push( entry );
3603
+
3604
+ if ( saved.has( material ) ) continue;
3605
+
3606
+ saved.set( material, {
3607
+ side: material.side,
3608
+ depthFunc: material.depthFunc,
3609
+ depthWrite: material.depthWrite
3610
+ } );
3611
+
3612
+ }
3613
+
3614
+ };
3615
+
3616
+ collect( opaqueObjects );
3617
+ collect( transparentObjects );
3618
+
3619
+ if ( list.length === 0 ) return;
3620
+
3621
+ for ( const material of saved.keys() ) {
3622
+
3623
+ material.side = FrontSide;
3624
+ material.depthFunc = EqualDepth;
3625
+ material.depthWrite = false;
3626
+
3627
+ }
3628
+
3629
+ this.debug.doubleSideHidden = true;
3630
+ this._renderObjects( list, camera, scene, lightsNode, 'doubleSideHidden' );
3631
+ this.debug.doubleSideHidden = false;
3632
+
3633
+ for ( const [ material, state ] of saved ) {
3634
+
3635
+ material.side = state.side;
3636
+ material.depthFunc = state.depthFunc;
3637
+ material.depthWrite = state.depthWrite;
3638
+
3639
+ }
3640
+
3641
+ }
3642
+ // !WITH_GENESYS
3643
+
3561
3644
  /**
3562
3645
  * Renders the transparent objects from the given render lists.
3563
3646
  *