Просмотр исходного кода

WebGPURenderer: hardware clipping support. (#28578)

* hardware clipping

* misc fixes

lint

---------

Co-authored-by: aardgoose <angus.sawyer@email.com>
aardgoose 1 год назад
Родитель
Сommit
8bc792bca1

+ 26 - 1
src/materials/nodes/NodeMaterial.js

@@ -20,7 +20,7 @@ import { lightingContext } from '../../nodes/lighting/LightingContextNode.js';
 import IrradianceNode from '../../nodes/lighting/IrradianceNode.js';
 import { depth, perspectiveDepthToLogarithmicDepth, viewZToOrthographicDepth } from '../../nodes/display/ViewportDepthNode.js';
 import { cameraFar, cameraNear } from '../../nodes/accessors/Camera.js';
-import { clipping, clippingAlpha } from '../../nodes/accessors/ClippingNode.js';
+import { clipping, clippingAlpha, hardwareClipping } from '../../nodes/accessors/ClippingNode.js';
 import NodeMaterialObserver from './manager/NodeMaterialObserver.js';
 import getAlphaHashThreshold from '../../nodes/functions/material/getAlphaHashThreshold.js';
 
@@ -50,6 +50,7 @@ class NodeMaterial extends Material {
 
 		this.fog = true;
 		this.lights = false;
+		this.hardwareClipping = false;
 
 		this.lightsNode = null;
 		this.envNode = null;
@@ -241,6 +242,28 @@ class NodeMaterial extends Material {
 
 	}
 
+	setupHardwareClipping( builder ) {
+
+		this.hardwareClipping = false;
+
+		if ( builder.clippingContext === null ) return;
+
+		const candidateCount = builder.clippingContext.unionPlanes.length;
+
+		// 8 planes supported by WebGL ANGLE_clip_cull_distance and WebGPU clip-distances
+
+		if ( candidateCount > 0 && candidateCount <= 8 && builder.isAvailable( 'clipDistance' ) ) {
+
+			builder.stack.add( hardwareClipping() );
+
+			this.hardwareClipping = true;
+
+		}
+
+		return;
+
+	}
+
 	setupDepth( builder ) {
 
 		const { renderer, camera } = builder;
@@ -330,6 +353,8 @@ class NodeMaterial extends Material {
 
 		}
 
+		this.setupHardwareClipping( builder );
+
 		const mvp = modelViewProjection();
 
 		builder.context.vertex = builder.removeStack();

+ 26 - 0
src/nodes/accessors/BuiltinNode.js

@@ -0,0 +1,26 @@
+import Node from '../core/Node.js';
+import { nodeProxy } from '../tsl/TSLBase.js';
+
+class BuiltinNode extends Node {
+
+	constructor( name ) {
+
+		super( 'float' );
+
+		this.name = name;
+
+		this.isBuiltinNode = true;
+
+	}
+
+	generate( /* builder */ ) {
+
+		return this.name;
+
+	}
+
+}
+
+export default BuiltinNode;
+
+export const builtin = nodeProxy( BuiltinNode );

+ 38 - 15
src/nodes/accessors/ClippingNode.js

@@ -6,6 +6,7 @@ import { diffuseColor } from '../core/PropertyNode.js';
 import { Loop } from '../utils/LoopNode.js';
 import { smoothstep } from '../math/MathNode.js';
 import { uniformArray } from './UniformArrayNode.js';
+import { builtin } from './BuiltinNode.js';
 
 class ClippingNode extends Node {
 
@@ -30,11 +31,16 @@ class ClippingNode extends Node {
 		const clippingContext = builder.clippingContext;
 		const { intersectionPlanes, unionPlanes } = clippingContext;
 
+		this.hardwareClipping = builder.material.hardwareClipping;
 
 		if ( this.scope === ClippingNode.ALPHA_TO_COVERAGE ) {
 
 			return this.setupAlphaToCoverage( intersectionPlanes, unionPlanes );
 
+		} else if ( this.scope === ClippingNode.HARDWARE ) {
+
+			return this.setupHardwareClipping( unionPlanes, builder );
+
 		} else {
 
 			return this.setupDefault( intersectionPlanes, unionPlanes );
@@ -54,15 +60,13 @@ class ClippingNode extends Node {
 
 			const numUnionPlanes = unionPlanes.length;
 
-			if ( numUnionPlanes > 0 ) {
+			if ( ! this.hardwareClipping && numUnionPlanes > 0 ) {
 
 				const clippingPlanes = uniformArray( unionPlanes );
 
-				let plane;
-
 				Loop( numUnionPlanes, ( { i } ) => {
 
-					plane = clippingPlanes.element( i );
+					const plane = clippingPlanes.element( i );
 
 					distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
 					distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
@@ -80,11 +84,9 @@ class ClippingNode extends Node {
 				const clippingPlanes = uniformArray( intersectionPlanes );
 				const intersectionClipOpacity = float( 1 ).toVar( 'intersectionClipOpacity' );
 
-				let plane;
-
 				Loop( numIntersectionPlanes, ( { i } ) => {
 
-					plane = clippingPlanes.element( i );
+					const plane = clippingPlanes.element( i );
 
 					distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
 					distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
@@ -111,15 +113,13 @@ class ClippingNode extends Node {
 
 			const numUnionPlanes = unionPlanes.length;
 
-			if ( numUnionPlanes > 0 ) {
+			if ( ! this.hardwareClipping && numUnionPlanes > 0 ) {
 
 				const clippingPlanes = uniformArray( unionPlanes );
 
-				let plane;
-
 				Loop( numUnionPlanes, ( { i } ) => {
 
-					plane = clippingPlanes.element( i );
+					const plane = clippingPlanes.element( i );
 					positionView.dot( plane.xyz ).greaterThan( plane.w ).discard();
 
 				} );
@@ -133,11 +133,9 @@ class ClippingNode extends Node {
 				const clippingPlanes = uniformArray( intersectionPlanes );
 				const clipped = bool( true ).toVar( 'clipped' );
 
-				let plane;
-
 				Loop( numIntersectionPlanes, ( { i } ) => {
 
-					plane = clippingPlanes.element( i );
+					const plane = clippingPlanes.element( i );
 					clipped.assign( positionView.dot( plane.xyz ).greaterThan( plane.w ).and( clipped ) );
 
 				} );
@@ -150,13 +148,38 @@ class ClippingNode extends Node {
 
 	}
 
+	setupHardwareClipping( unionPlanes, builder ) {
+
+		const numUnionPlanes = unionPlanes.length;
+
+		builder.enableHardwareClipping( numUnionPlanes );
+
+		return Fn( () => {
+
+			const clippingPlanes = uniformArray( unionPlanes );
+			const hw_clip_distances = builtin( builder.getClipDistance() );
+
+			Loop( numUnionPlanes, ( { i } ) => {
+
+				const plane = clippingPlanes.element( i );
+
+				const distance = positionView.dot( plane.xyz ).sub( plane.w ).negate();
+				hw_clip_distances.element( i ).assign( distance );
+
+			} );
+
+		} )();
+
+	}
+
 }
 
 ClippingNode.ALPHA_TO_COVERAGE = 'alphaToCoverage';
 ClippingNode.DEFAULT = 'default';
+ClippingNode.HARDWARE = 'hardware';
 
 export default ClippingNode;
 
 export const clipping = () => nodeObject( new ClippingNode() );
-
 export const clippingAlpha = () => nodeObject( new ClippingNode( ClippingNode.ALPHA_TO_COVERAGE ) );
+export const hardwareClipping = () => nodeObject( new ClippingNode( ClippingNode.HARDWARE ) );

+ 6 - 0
src/renderers/common/ClippingContext.js

@@ -158,6 +158,12 @@ class ClippingContext {
 
 	}
 
+	get unionClippingCount() {
+
+		return this.unionPlanes.length;
+
+	}
+
 }
 
 export default ClippingContext;

+ 6 - 0
src/renderers/common/RenderObject.js

@@ -105,6 +105,12 @@ export default class RenderObject {
 
 	}
 
+	get hardwareClippingPlanes() {
+
+		return this.material.hardwareClipping === true ? this.clippingContext.unionClippingCount : 0;
+
+	}
+
 	getNodeBuilderState() {
 
 		return this._nodeBuilderState || ( this._nodeBuilderState = this._nodes.getForRender( this ) );

+ 2 - 2
src/renderers/webgl-fallback/WebGLBackend.js

@@ -641,7 +641,7 @@ class WebGLBackend extends Backend {
 
 	draw( renderObject/*, info*/ ) {
 
-		const { object, pipeline, material, context } = renderObject;
+		const { object, pipeline, material, context, hardwareClippingPlanes } = renderObject;
 		const { programGPU } = this.get( pipeline );
 
 		const { gl, state } = this;
@@ -658,7 +658,7 @@ class WebGLBackend extends Backend {
 
 		const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );
 
-		state.setMaterial( material, frontFaceCW );
+		state.setMaterial( material, frontFaceCW, hardwareClippingPlanes );
 
 		state.useProgram( programGPU );
 

+ 40 - 7
src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js

@@ -55,6 +55,7 @@ class GLSLNodeBuilder extends NodeBuilder {
 		this.uniformGroups = {};
 		this.transforms = [];
 		this.extensions = {};
+		this.builtins = { vertex: [], fragment: [], compute: [] };
 
 		this.useComparisonMethod = true;
 
@@ -595,6 +596,12 @@ ${ flowData.code }
 
 		}
 
+		for ( const builtin of this.builtins[ shaderStage ] ) {
+
+			snippet += `${builtin};\n`;
+
+		}
+
 		return snippet;
 
 	}
@@ -701,24 +708,42 @@ ${ flowData.code }
 
 	}
 
+	getClipDistance() {
+
+		return 'gl_ClipDistance';
+
+	}
+
 	isAvailable( name ) {
 
 		let result = supports[ name ];
 
 		if ( result === undefined ) {
 
-			if ( name === 'float32Filterable' ) {
+			let extensionName;
 
-				const extensions = this.renderer.backend.extensions;
+			result = false;
 
-				if ( extensions.has( 'OES_texture_float_linear' ) ) {
+			switch ( name ) {
 
-					extensions.get( 'OES_texture_float_linear' );
-					result = true;
+				case 'float32Filterable':
+					extensionName = 'OES_texture_float_linear';
+					break;
 
-				} else {
+				case 'clipDistance':
+					extensionName = 'WEBGL_clip_cull_distance';
+					break;
 
-					result = false;
+			}
+
+			if ( extensionName !== undefined ) {
+
+				const extensions = this.renderer.backend.extensions;
+
+				if ( extensions.has( extensionName ) ) {
+
+					extensions.get( extensionName );
+					result = true;
 
 				}
 
@@ -738,6 +763,14 @@ ${ flowData.code }
 
 	}
 
+	enableHardwareClipping( planeCount ) {
+
+		this.enableExtension( 'GL_ANGLE_clip_cull_distance', 'require' );
+
+		this.builtins[ 'vertex' ].push( `out float gl_ClipDistance[ ${ planeCount } ]` );
+
+	}
+
 	registerTransform( varyingName, attributeNode ) {
 
 		this.transforms.push( { varyingName, attributeNode } );

+ 26 - 1
src/renderers/webgl-fallback/utils/WebGLState.js

@@ -41,6 +41,7 @@ class WebGLState {
 		this.currentStencilZPass = null;
 		this.currentStencilMask = null;
 		this.currentLineWidth = null;
+		this.currentClippingPlanes = 0;
 
 		this.currentBoundFramebuffers = {};
 		this.currentDrawbuffers = new WeakMap();
@@ -478,7 +479,7 @@ class WebGLState {
 
 	}
 
-	setMaterial( material, frontFaceCW ) {
+	setMaterial( material, frontFaceCW, hardwareClippingPlanes ) {
 
 		const { gl } = this;
 
@@ -516,6 +517,30 @@ class WebGLState {
 			? this.enable( gl.SAMPLE_ALPHA_TO_COVERAGE )
 			: this.disable( gl.SAMPLE_ALPHA_TO_COVERAGE );
 
+		if ( hardwareClippingPlanes > 0 ) {
+
+			if ( this.currentClippingPlanes !== hardwareClippingPlanes ) {
+
+				const CLIP_DISTANCE0_WEBGL = 0x3000;
+
+				for ( let i = 0; i < 8; i ++ ) {
+
+					if ( i < hardwareClippingPlanes ) {
+
+						this.enable( CLIP_DISTANCE0_WEBGL + i );
+
+					} else {
+
+						this.disable( CLIP_DISTANCE0_WEBGL + i );
+
+					}
+
+				}
+
+			}
+
+		}
+
 	}
 
 	setPolygonOffset( polygonOffset, factor, units ) {

+ 17 - 0
src/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -692,6 +692,12 @@ ${ flowData.code }
 
 	}
 
+	getClipDistance() {
+
+		return 'varyings.hw_clip_distances';
+
+	}
+
 	isFlipY() {
 
 		return false;
@@ -754,6 +760,13 @@ ${ flowData.code }
 
 	}
 
+	enableHardwareClipping( planeCount ) {
+
+		this.enableClipDistances();
+		this.getBuiltin( 'clip_distances', 'hw_clip_distances', `array<f32, ${ planeCount } >`, 'vertex' );
+
+	}
+
 	getBuiltins( shaderStage ) {
 
 		const snippets = [];
@@ -1239,6 +1252,10 @@ ${ flowData.code }
 
 				result = this.renderer.hasFeature( 'float32-filterable' );
 
+			} else if ( name === 'clipDistance' ) {
+
+				result = this.renderer.hasFeature( 'clip-distances' );
+
 			}
 
 			supports[ name ] = result;

粤ICP备19079148号