@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.
@@ -4,6 +4,9 @@ import { vec4, renderOutput, context } from '../../nodes/TSL.js';
4
4
  import { NoToneMapping } from '../../constants.js';
5
5
  import QuadMesh from '../../renderers/common/QuadMesh.js';
6
6
  import { warnOnce } from '../../utils.js';
7
+ // WITH_GENESYS
8
+ import { debugViewAccumulates, debugViewSkipsToneMapping } from '../../nodes/display/ComplexityDebug.js';
9
+ // !WITH_GENESYS
7
10
 
8
11
  /**
9
12
  * This module is responsible to manage the rendering pipeline setups in apps.
@@ -139,7 +142,12 @@ class RenderPipeline {
139
142
  const outputColorSpace = renderer.outputColorSpace;
140
143
 
141
144
  renderer.toneMapping = NoToneMapping;
142
- renderer.outputColorSpace = ColorManagement.workingColorSpace;
145
+
146
+ // WITH_GENESYS
147
+ // Accumulating views colorize in the renderer's output pass, which must still encode the color space.
148
+ if ( debugViewAccumulates( renderer.debug.view ) !== true ) renderer.outputColorSpace = ColorManagement.workingColorSpace;
149
+ // !WITH_GENESYS
150
+ // renderer.outputColorSpace = ColorManagement.workingColorSpace;
143
151
 
144
152
  //
145
153
 
@@ -215,19 +223,40 @@ class RenderPipeline {
215
223
  */
216
224
  _update() {
217
225
 
218
- if ( this._toneMapping !== this.renderer.toneMapping ) {
226
+ // WITH_GENESYS
227
+ // Debug views drawn without tone mapping must match the direct path. An accumulating
228
+ // view copies the raw ratio here, and the renderer's output pass colorizes it.
229
+ const view = this.renderer.debug.view;
230
+ const toneMapping = debugViewSkipsToneMapping( view ) ? NoToneMapping : this.renderer.toneMapping;
231
+ const outputColorSpace = debugViewAccumulates( view ) ? ColorManagement.workingColorSpace : this.renderer.outputColorSpace;
232
+
233
+ if ( this._toneMapping !== toneMapping ) {
219
234
 
220
- this._toneMapping = this.renderer.toneMapping;
235
+ this._toneMapping = toneMapping;
221
236
  this.needsUpdate = true;
222
237
 
223
238
  }
224
239
 
225
- if ( this._outputColorSpace !== this.renderer.outputColorSpace ) {
240
+ if ( this._outputColorSpace !== outputColorSpace ) {
226
241
 
227
- this._outputColorSpace = this.renderer.outputColorSpace;
242
+ this._outputColorSpace = outputColorSpace;
228
243
  this.needsUpdate = true;
229
244
 
230
245
  }
246
+ // !WITH_GENESYS
247
+ // if ( this._toneMapping !== this.renderer.toneMapping ) {
248
+ //
249
+ // this._toneMapping = this.renderer.toneMapping;
250
+ // this.needsUpdate = true;
251
+ //
252
+ // }
253
+ //
254
+ // if ( this._outputColorSpace !== this.renderer.outputColorSpace ) {
255
+ //
256
+ // this._outputColorSpace = this.renderer.outputColorSpace;
257
+ // this.needsUpdate = true;
258
+ //
259
+ // }
231
260
 
232
261
  if ( this.needsUpdate === true ) {
233
262
 
@@ -30,15 +30,16 @@ 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
37
37
  import { detachRendererReference } from '../../nodes/accessors/RendererReferenceNode.js';
38
38
  import { toneMappingExposure } from '../../nodes/display/ToneMappingNode.js';
39
39
  import { ProfilerService } from '../../profiler/ProfilerService.js';
40
- import { DEBUG_VIEW_LIGHTING_COMPLEXITY, DEBUG_VIEW_NONE, DEBUG_VIEW_QUAD_OVERDRAW, DEBUG_VIEW_SHADER_COMPLEXITY, DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS, DEFAULT_QUAD_OVERDRAW_BUDGET, DEFAULT_SHADER_COMPLEXITY_BUDGET, debugViewAccumulates } from '../../nodes/display/ComplexityDebug.js';
41
- import { DEBUG_VIEW_BUFFER, DEFAULT_BUFFER } from '../../nodes/display/BufferDebug.js';
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';
42
+ import { DEFAULT_BUFFER } from '../../nodes/display/BufferDebug.js';
42
43
  // !WITH_GENESYS
43
44
  import { reference } from '../../nodes/accessors/ReferenceNode.js';
44
45
  import { highpModelNormalViewMatrix, highpModelViewMatrix } from '../../nodes/accessors/ModelNode.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 );
@@ -2705,7 +2712,8 @@ class Renderer {
2705
2712
 
2706
2713
  // WITH_GENESYS
2707
2714
  // Accumulating views write a scalar into an intermediate target, then the output pass colorizes it.
2708
- return useToneMapping || useColorSpace || debugViewAccumulates( this.debug.view );
2715
+ // Only screen output needs one. An off-screen target, such as a scene pass, already is the target.
2716
+ return useToneMapping || useColorSpace || ( debugViewAccumulates( this.debug.view ) && this.isOutputTarget );
2709
2717
  // !WITH_GENESYS
2710
2718
  // return useToneMapping || useColorSpace;
2711
2719
 
@@ -2759,9 +2767,7 @@ class Renderer {
2759
2767
  get currentToneMapping() {
2760
2768
 
2761
2769
  // WITH_GENESYS
2762
- const view = this.debug.view;
2763
-
2764
- if ( view === DEBUG_VIEW_SHADER_COMPLEXITY || view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS || view === DEBUG_VIEW_LIGHTING_COMPLEXITY || view === DEBUG_VIEW_QUAD_OVERDRAW || view === DEBUG_VIEW_BUFFER ) {
2770
+ if ( debugViewSkipsToneMapping( this.debug.view ) ) {
2765
2771
 
2766
2772
  return NoToneMapping;
2767
2773
 
@@ -3559,6 +3565,82 @@ class Renderer {
3559
3565
 
3560
3566
  }
3561
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
+
3562
3644
  /**
3563
3645
  * Renders the transparent objects from the given render lists.
3564
3646
  *
@@ -10,7 +10,7 @@ import { builtin } from '../../../nodes/accessors/BuiltinNode.js';
10
10
  import { CubeUVReflectionMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping, NoToneMapping } from '../../../constants.js';
11
11
  // WITH_GENESYS
12
12
  import { vec4 } from '../../../nodes/tsl/TSLCore.js';
13
- import { colorizeQuadOverdraw, colorizeShaderComplexity, DEBUG_VIEW_QUAD_OVERDRAW, DEBUG_VIEW_SHADER_COMPLEXITY, DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS, SHADER_COMPLEXITY_QUAD_COUNT_SCALE } from '../../../nodes/display/ComplexityDebug.js';
13
+ import { colorizeQuadOverdraw, colorizeShaderComplexity, DEBUG_VIEW_OVERDRAW, DEBUG_VIEW_SHADER_COMPLEXITY, DEBUG_VIEW_SHADER_COMPLEXITY_AND_OVERDRAW, SHADER_COMPLEXITY_QUAD_COUNT_SCALE } from '../../../nodes/display/ComplexityDebug.js';
14
14
  // !WITH_GENESYS
15
15
  import { hashArray } from '../../../nodes/core/NodeUtils.js';
16
16
  import { error } from '../../../utils.js';
@@ -1035,9 +1035,9 @@ class NodeManager extends DataMap {
1035
1035
  }
1036
1036
 
1037
1037
  // WITH_GENESYS
1038
- if ( renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY || renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS ) {
1038
+ if ( renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY || renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_OVERDRAW ) {
1039
1039
 
1040
- const complexity = renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_QUADS
1040
+ const complexity = renderer.debug.view === DEBUG_VIEW_SHADER_COMPLEXITY_AND_OVERDRAW
1041
1041
  ? sampled.r.mul( sampled.g ).mul( SHADER_COMPLEXITY_QUAD_COUNT_SCALE )
1042
1042
  : sampled.r;
1043
1043
 
@@ -1045,7 +1045,7 @@ class NodeManager extends DataMap {
1045
1045
 
1046
1046
  }
1047
1047
 
1048
- if ( renderer.debug.view === DEBUG_VIEW_QUAD_OVERDRAW ) {
1048
+ if ( renderer.debug.view === DEBUG_VIEW_OVERDRAW ) {
1049
1049
 
1050
1050
  return vec4( colorizeQuadOverdraw( sampled.r ), 1 ).renderOutput( NoToneMapping, renderer.currentColorSpace );
1051
1051
 
@@ -1124,11 +1124,17 @@ class WebGLBackend extends Backend {
1124
1124
 
1125
1125
  const frontFaceCW = ( object.isMesh && object.matrixWorld.determinantAffine() < 0 );
1126
1126
 
1127
- state.setMaterial( material, frontFaceCW, hardwareClippingPlanes );
1127
+ // WITH_GENESYS
1128
+ state.setMaterial( material, frontFaceCW, hardwareClippingPlanes, object );
1129
+ // !WITH_GENESYS
1130
+ // state.setMaterial( material, frontFaceCW, hardwareClippingPlanes );
1128
1131
 
1129
1132
  if ( context.mrt !== null && context.textures !== null ) {
1130
1133
 
1131
- state.setMRTBlending( context.textures, context.mrt, material );
1134
+ // WITH_GENESYS
1135
+ state.setMRTBlending( context.textures, context.mrt, material, object );
1136
+ // !WITH_GENESYS
1137
+ // state.setMRTBlending( context.textures, context.mrt, material );
1132
1138
 
1133
1139
  }
1134
1140
 
@@ -12,7 +12,8 @@ import { Color } from '../../../math/Color.js';
12
12
  import { Vector4 } from '../../../math/Vector4.js';
13
13
  import { error, ReversedDepthFuncs, warnOnce } from '../../../utils.js';
14
14
  // WITH_GENESYS
15
- import { debugViewAccumulates } from '../../../nodes/display/ComplexityDebug.js';
15
+ import { debugDrawAccumulates, debugDrawReplacesCost } from '../../../nodes/display/ComplexityDebug.js';
16
+ import { DEBUG_VIEW_FRONT_BACK_FACE } from '../../../nodes/display/FrontBackFaceDebug.js';
16
17
  // !WITH_GENESYS
17
18
 
18
19
  let equationToGL, factorToGL;
@@ -288,7 +289,7 @@ class WebGLState {
288
289
 
289
290
  }
290
291
 
291
- setMRTBlending( textures, mrt, material ) {
292
+ setMRTBlending( textures, mrt, material, object = null ) {
292
293
 
293
294
  const gl = this.gl;
294
295
  const drawBuffersIndexedExt = this.backend.drawBuffersIndexedExt;
@@ -301,6 +302,24 @@ class WebGLState {
301
302
 
302
303
  }
303
304
 
305
+ // WITH_GENESYS
306
+ // Every attachment matches the single-target path. Opaque material blending would replace a sum that should add.
307
+ if ( debugDrawAccumulates( this.backend.renderer, material, object ) ) {
308
+
309
+ const dst = debugDrawReplacesCost( this.backend.renderer.debug.view, material ) ? gl.ZERO : gl.ONE;
310
+
311
+ for ( let i = 0; i < textures.length; i ++ ) {
312
+
313
+ drawBuffersIndexedExt.blendEquationSeparateiOES( i, gl.FUNC_ADD, gl.FUNC_ADD );
314
+ drawBuffersIndexedExt.blendFuncSeparateiOES( i, gl.ONE, dst, gl.ONE, gl.ZERO );
315
+
316
+ }
317
+
318
+ return;
319
+
320
+ }
321
+ // !WITH_GENESYS
322
+
304
323
  for ( let i = 0; i < textures.length; i ++ ) {
305
324
 
306
325
  const texture = textures[ i ];
@@ -923,14 +942,22 @@ class WebGLState {
923
942
  * @param {Material} material - The material to configure the state for.
924
943
  * @param {number} frontFaceCW - Whether the front faces are counter-clockwise or not.
925
944
  * @param {number} hardwareClippingPlanes - The number of hardware clipping planes.
945
+ * @param {?Object3D} [object=null] - The object being drawn. Used by the accumulating debug views.
926
946
  */
927
- setMaterial( material, frontFaceCW, hardwareClippingPlanes ) {
947
+ setMaterial( material, frontFaceCW, hardwareClippingPlanes, object = null ) {
928
948
 
929
949
  const { gl } = this;
930
950
 
931
- material.side === DoubleSide
951
+ // WITH_GENESYS
952
+ const drawBothSides = material.side === DoubleSide || ( this.backend.renderer.debug.view === DEBUG_VIEW_FRONT_BACK_FACE && material.isShadowPassMaterial !== true );
953
+
954
+ drawBothSides
932
955
  ? this.disable( gl.CULL_FACE )
933
956
  : this.enable( gl.CULL_FACE );
957
+ // !WITH_GENESYS
958
+ // material.side === DoubleSide
959
+ // ? this.disable( gl.CULL_FACE )
960
+ // : this.enable( gl.CULL_FACE );
934
961
 
935
962
  let flipSided = ( material.side === BackSide );
936
963
  if ( frontFaceCW ) flipSided = ! flipSided;
@@ -938,10 +965,13 @@ class WebGLState {
938
965
  this.setFlipSided( flipSided );
939
966
 
940
967
  // WITH_GENESYS
941
- // WebGL has no cached render pipeline. One+One is applied on each draw instead.
942
- if ( debugViewAccumulates( this.backend.renderer.debug.view ) && material.isShadowPassMaterial !== true && this.backend.renderer.isOutputTarget !== true ) {
968
+ // WebGL has no cached render pipeline. The accumulate blend is applied on each draw instead.
969
+ // Alpha is replaced so it stays coverage, as in a shaded pass, for later compositing.
970
+ if ( debugDrawAccumulates( this.backend.renderer, material, object ) ) {
971
+
972
+ const dstFactor = debugDrawReplacesCost( this.backend.renderer.debug.view, material ) ? ZeroFactor : OneFactor;
943
973
 
944
- this.setBlending( AdditiveBlending, AddEquation, OneFactor, OneFactor, AddEquation, OneFactor, OneFactor, material.blendColor, material.blendAlpha, true );
974
+ this.setBlending( CustomBlending, AddEquation, OneFactor, dstFactor, AddEquation, OneFactor, ZeroFactor, material.blendColor, material.blendAlpha, true );
945
975
 
946
976
  } else if ( material.blending === NormalBlending && material.transparent === false ) {
947
977
 
@@ -16,7 +16,8 @@ import {
16
16
 
17
17
  import { error, ReversedDepthFuncs, warn, warnOnce } from '../../../utils.js';
18
18
  // WITH_GENESYS
19
- import { debugViewAccumulates } from '../../../nodes/display/ComplexityDebug.js';
19
+ import { debugDrawAccumulates, debugDrawReplacesCost } from '../../../nodes/display/ComplexityDebug.js';
20
+ import { DEBUG_VIEW_FRONT_BACK_FACE } from '../../../nodes/display/FrontBackFaceDebug.js';
20
21
  // !WITH_GENESYS
21
22
 
22
23
  import GPUComputePipelineDescriptor from '../descriptors/GPUComputePipelineDescriptor.js';
@@ -110,18 +111,22 @@ class WebGPUPipelineUtils {
110
111
  }
111
112
 
112
113
  // WITH_GENESYS
113
- // Accumulate shader cost with One+One. Opaque materials normally disable blending.
114
- if ( debugViewAccumulates( backend.renderer.debug.view ) && material.isShadowPassMaterial !== true && backend.renderer.isOutputTarget !== true ) {
114
+ // Quad overdraw and translucent shader complexity add. Opaque shader complexity replaces
115
+ // the stored cost, so the nearest surface wins without a depth pre-pass.
116
+ // Alpha is replaced so it stays coverage, as in a shaded pass, for later compositing.
117
+ if ( debugDrawAccumulates( backend.renderer, material, object ) ) {
118
+
119
+ const replaceCost = debugDrawReplacesCost( backend.renderer.debug.view, material );
115
120
 
116
121
  materialBlending = {
117
122
  color: {
118
123
  srcFactor: GPUBlendFactor.One,
119
- dstFactor: GPUBlendFactor.One,
124
+ dstFactor: replaceCost ? GPUBlendFactor.Zero : GPUBlendFactor.One,
120
125
  operation: GPUBlendOperation.Add
121
126
  },
122
127
  alpha: {
123
128
  srcFactor: GPUBlendFactor.One,
124
- dstFactor: GPUBlendFactor.One,
129
+ dstFactor: GPUBlendFactor.Zero,
125
130
  operation: GPUBlendOperation.Add
126
131
  }
127
132
  };
@@ -942,7 +947,12 @@ class WebGPUPipelineUtils {
942
947
 
943
948
  //
944
949
 
945
- descriptor.cullMode = ( material.side === DoubleSide ) ? GPUCullMode.None : GPUCullMode.Back;
950
+ // WITH_GENESYS
951
+ // Front/back face draws both windings. Depth still hides a face behind the shell.
952
+ const drawBothSides = material.side === DoubleSide || ( this.backend.renderer.debug.view === DEBUG_VIEW_FRONT_BACK_FACE && material.isShadowPassMaterial !== true );
953
+ descriptor.cullMode = drawBothSides ? GPUCullMode.None : GPUCullMode.Back;
954
+ // !WITH_GENESYS
955
+ // descriptor.cullMode = ( material.side === DoubleSide ) ? GPUCullMode.None : GPUCullMode.Back;
946
956
 
947
957
  return descriptor;
948
958