Skip to content

Commit b77201a

Browse files
committed
Enhance front-face culling logic in WebGPUBackend and WebGPUPipelineUtils
- Introduced computeFrontFaceCW function to determine front-face orientation based on object, view, and projection matrix determinants. - Updated setPipelineAndBindings and draw functions to accept frontFaceCW parameter for pipeline selection. - Modified _getPrimitiveState and _buildRenderPipelineDescriptor to support front-face orientation configuration. - Added getRenderPipelineVariant method to retrieve or create a pipeline based on front-face orientation.
1 parent 2e3b4ca commit b77201a

File tree

2 files changed

+101
-17
lines changed

2 files changed

+101
-17
lines changed

src/renderers/webgpu/WebGPUBackend.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,16 @@ class WebGPUBackend extends Backend {
14351435
const { object, material, context, pipeline } = renderObject;
14361436
const bindings = renderObject.getBindings();
14371437
const renderContextData = this.get( context );
1438-
const pipelineGPU = this.get( pipeline ).pipeline;
1438+
1439+
const computeFrontFaceCW = ( cam ) => {
1440+
1441+
if ( object.isMesh !== true ) return false;
1442+
const objectFlipped = object.matrixWorld.determinant() < 0;
1443+
const viewFlipped = cam.matrixWorld.determinant() < 0;
1444+
const projectionFlipped = cam.projectionMatrix.determinant() > 0; // A standard projection's determinant is negative; a positive determinant will flip face culling
1445+
return ( ( objectFlipped ^ viewFlipped ^ projectionFlipped ) !== 0 );
1446+
1447+
};
14391448

14401449
const index = renderObject.getIndex();
14411450
const hasIndex = ( index !== null );
@@ -1446,9 +1455,10 @@ class WebGPUBackend extends Backend {
14461455

14471456
// pipeline
14481457

1449-
const setPipelineAndBindings = ( passEncoderGPU, currentSets ) => {
1458+
const setPipelineAndBindings = ( passEncoderGPU, currentSets, frontFaceCW ) => {
14501459

1451-
// pipeline
1460+
// oriented pipeline (select orientation variant lazily)
1461+
const pipelineGPU = this.pipelineUtils.getRenderPipelineVariant( renderObject, frontFaceCW === true );
14521462
this.pipelineUtils.setPipeline( passEncoderGPU, pipelineGPU );
14531463
currentSets.pipeline = pipelineGPU;
14541464

@@ -1516,9 +1526,9 @@ class WebGPUBackend extends Backend {
15161526
};
15171527

15181528
// Define draw function
1519-
const draw = ( passEncoderGPU, currentSets ) => {
1529+
const draw = ( passEncoderGPU, currentSets, frontFaceCW ) => {
15201530

1521-
setPipelineAndBindings( passEncoderGPU, currentSets );
1531+
setPipelineAndBindings( passEncoderGPU, currentSets, frontFaceCW );
15221532

15231533
if ( object.isBatchedMesh === true ) {
15241534

@@ -1671,7 +1681,8 @@ class WebGPUBackend extends Backend {
16711681

16721682
}
16731683

1674-
draw( pass, sets );
1684+
const frontFaceCW = computeFrontFaceCW( subCamera );
1685+
draw( pass, sets, frontFaceCW );
16751686

16761687

16771688
}
@@ -1709,7 +1720,8 @@ class WebGPUBackend extends Backend {
17091720

17101721
}
17111722

1712-
draw( renderContextData.currentPass, renderContextData.currentSets );
1723+
const frontFaceCW = computeFrontFaceCW( renderObject.camera );
1724+
draw( renderContextData.currentPass, renderContextData.currentSets, frontFaceCW );
17131725

17141726
}
17151727

src/renderers/webgpu/utils/WebGPUPipelineUtils.js

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,15 @@ class WebGPUPipelineUtils {
8181
}
8282

8383
/**
84-
* Creates a render pipeline for the given render object.
84+
* Builds a GPURenderPipelineDescriptor for the given render object.
8585
*
86+
* @private
8687
* @param {RenderObject} renderObject - The render object.
87-
* @param {Array<Promise>} promises - An array of compilation promises which are used in `compileAsync()`.
88+
* @param {Object} [options={}] - Optional configuration.
89+
* @param {?boolean} [options.frontFaceCW=false] - Controls the primitive front-face orientation; defaults to CCW.
90+
* @return {Object} The render pipeline descriptor ready for createRenderPipeline.
8891
*/
89-
createRenderPipeline( renderObject, promises ) {
92+
_buildRenderPipelineDescriptor( renderObject, { frontFaceCW = false } = {} ) {
9093

9194
const { object, material, geometry, pipeline } = renderObject;
9295
const { vertexProgram, fragmentProgram } = pipeline;
@@ -95,7 +98,6 @@ class WebGPUPipelineUtils {
9598
const device = backend.device;
9699
const utils = backend.utils;
97100

98-
const pipelineData = backend.get( pipeline );
99101

100102
// bind group layouts
101103

@@ -173,14 +175,15 @@ class WebGPUPipelineUtils {
173175
const vertexModule = backend.get( vertexProgram ).module;
174176
const fragmentModule = backend.get( fragmentProgram ).module;
175177

176-
const primitiveState = this._getPrimitiveState( object, geometry, material );
178+
const primitiveState = this._getPrimitiveState( object, geometry, material, frontFaceCW );
179+
177180
const depthCompare = this._getDepthCompare( material );
178181
const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );
179182

180183
const sampleCount = this._getSampleCount( renderObject.context );
181184

182185
const pipelineDescriptor = {
183-
label: `renderPipeline_${ material.name || material.type }_${ material.id }`,
186+
label: `renderPipeline_${ material.name || material.type }_${ material.id }${ frontFaceCW ? '_CW' : '' }`,
184187
vertex: Object.assign( {}, vertexModule, { buffers: vertexBuffers } ),
185188
fragment: Object.assign( {}, fragmentModule, { targets } ),
186189
primitive: primitiveState,
@@ -229,6 +232,26 @@ class WebGPUPipelineUtils {
229232

230233
}
231234

235+
return pipelineDescriptor;
236+
237+
}
238+
239+
/**
240+
* Creates a render pipeline for the given render object.
241+
*
242+
* @param {RenderObject} renderObject - The render object.
243+
* @param {Array<Promise>} promises - An array of compilation promises which are used in `compileAsync()`.
244+
*/
245+
createRenderPipeline( renderObject, promises ) {
246+
247+
const { pipeline } = renderObject;
248+
249+
const backend = this.backend;
250+
const device = backend.device;
251+
252+
const pipelineData = backend.get( pipeline );
253+
254+
const pipelineDescriptor = this._buildRenderPipelineDescriptor( renderObject );
232255

233256
if ( promises === null ) {
234257

@@ -253,6 +276,54 @@ class WebGPUPipelineUtils {
253276

254277
}
255278

279+
/**
280+
* Returns a render pipeline variant with the requested front-face orientation.
281+
* If necessary, a new pipeline is created lazily and cached.
282+
*
283+
* @param {RenderObject} renderObject - The render object.
284+
* @param {boolean} frontFaceCW - Whether the front face should be CW (true) or CCW (false).
285+
* @return {GPURenderPipeline} The pipeline for the requested orientation.
286+
*/
287+
getRenderPipelineVariant( renderObject, frontFaceCW ) {
288+
289+
const { material, pipeline } = renderObject;
290+
291+
const backend = this.backend;
292+
const device = backend.device;
293+
294+
const pipelineData = backend.get( pipeline );
295+
296+
console.assert( pipelineData.pipeline !== undefined, 'Pipeline not created' );
297+
298+
// DoubleSide does not cull, orientation is irrelevant
299+
if ( material.side === DoubleSide ) {
300+
301+
return pipelineData.pipeline;
302+
303+
}
304+
305+
// Default CCW pipeline already handled by createRenderPipeline()
306+
if ( frontFaceCW === false ) {
307+
308+
return pipelineData.pipeline;
309+
310+
}
311+
312+
// Need CW variant
313+
if ( pipelineData.pipelineCW !== undefined ) {
314+
315+
return pipelineData.pipelineCW;
316+
317+
}
318+
319+
// Build a CW-oriented pipeline by reusing the shared descriptor with frontFace override
320+
const pipelineDescriptor = this._buildRenderPipelineDescriptor( renderObject, { frontFaceCW: true } );
321+
pipelineData.pipelineCW = device.createRenderPipeline( pipelineDescriptor );
322+
323+
return pipelineData.pipelineCW;
324+
325+
}
326+
256327
/**
257328
* Creates GPU render bundle encoder for the given render context.
258329
*
@@ -665,9 +736,10 @@ class WebGPUPipelineUtils {
665736
* @param {Object3D} object - The 3D object.
666737
* @param {BufferGeometry} geometry - The geometry.
667738
* @param {Material} material - The material.
739+
* @param {?boolean} [frontFaceCW=false] - Primitive front-face orientation; false=CCW (default), true=CW.
668740
* @return {Object} The primitive state.
669741
*/
670-
_getPrimitiveState( object, geometry, material ) {
742+
_getPrimitiveState( object, geometry, material, frontFaceCW = false ) {
671743

672744
const descriptor = {};
673745
const utils = this.backend.utils;
@@ -683,17 +755,17 @@ class WebGPUPipelineUtils {
683755
switch ( material.side ) {
684756

685757
case FrontSide:
686-
descriptor.frontFace = GPUFrontFace.CCW;
758+
descriptor.frontFace = frontFaceCW ? GPUFrontFace.CW : GPUFrontFace.CCW;
687759
descriptor.cullMode = GPUCullMode.Back;
688760
break;
689761

690762
case BackSide:
691-
descriptor.frontFace = GPUFrontFace.CCW;
763+
descriptor.frontFace = frontFaceCW ? GPUFrontFace.CW : GPUFrontFace.CCW;
692764
descriptor.cullMode = GPUCullMode.Front;
693765
break;
694766

695767
case DoubleSide:
696-
descriptor.frontFace = GPUFrontFace.CCW;
768+
descriptor.frontFace = frontFaceCW ? GPUFrontFace.CW : GPUFrontFace.CCW;
697769
descriptor.cullMode = GPUCullMode.None;
698770
break;
699771

0 commit comments

Comments
 (0)