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

WebGPURenderer: implement ClippingGroup object (#28237)

* clipping groups

Replace renderer and material clipping planes with nestable clipping
groups

* update to upstream

* rework cache key

* use right cache key

* fixup

* fix cache key use

* clean up TSL

* remove unused import

* Update ToonOutlinePassNode.js

Add missing `clippingContext`.

* Update Renderer.js

Add default parameter to renderObject().

* Update three.webgpu.js

Revert changes to build files.

---------

Co-authored-by: aardgoose <angus.sawyer@email.com>
Co-authored-by: Michael Herzog <michael.herzog@human-interactive.org>
aardgoose 1 год назад
Родитель
Сommit
d7da5e02a0

+ 37 - 35
examples/webgpu_clipping.html

@@ -74,11 +74,23 @@
 				dirLight.shadow.mapSize.height = 1024;
 				scene.add( dirLight );
 
-				// ***** Clipping planes: *****
+				// Clipping planes
 
-				const localPlane = new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0.8 );
-				const localPlane2 = new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0.1 );
 				const globalPlane = new THREE.Plane( new THREE.Vector3( - 1, 0, 0 ), 0.1 );
+				const localPlane1 = new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0.8 );
+				const localPlane2 = new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0.1 );
+
+				// Clipping Groups
+
+				const globalClippingGroup = new THREE.ClippingGroup();
+				globalClippingGroup.clippingPlanes = [ globalPlane ];
+
+				const knotClippingGroup = new THREE.ClippingGroup();
+				knotClippingGroup.clippingPlanes = [ localPlane1, localPlane2 ];
+				knotClippingGroup.clipIntersection = true;
+
+				scene.add( globalClippingGroup );
+				globalClippingGroup.add( knotClippingGroup );
 
 				// Geometry
 
@@ -88,27 +100,23 @@
 					side: THREE.DoubleSide,
 
 					// ***** Clipping setup (material): *****
-					clippingPlanes: [ localPlane, localPlane2 ],
-					clipShadows: true,
-					alphaToCoverage: true,
-					clipIntersection: true
-
+					alphaToCoverage: true
 				} );
 
 				const geometry = new THREE.TorusKnotGeometry( 0.4, 0.08, 95, 20 );
 
 				object = new THREE.Mesh( geometry, material );
 				object.castShadow = true;
-				scene.add( object );
+				knotClippingGroup.add( object );
 
 				const ground = new THREE.Mesh(
 					new THREE.PlaneGeometry( 9, 9, 1, 1 ),
-					new THREE.MeshPhongNodeMaterial( { color: 0xa0adaf, shininess: 150 } )
+					new THREE.MeshPhongNodeMaterial( { color: 0xa0adaf, shininess: 150, alphaToCoverage: true } )
 				);
 
 				ground.rotation.x = - Math.PI / 2; // rotates X/Y to X/Z
 				ground.receiveShadow = true;
-				scene.add( ground );
+				globalClippingGroup.add( ground );
 
 				// Stats
 
@@ -125,14 +133,8 @@
 				window.addEventListener( 'resize', onWindowResize );
 				document.body.appendChild( renderer.domElement );
 
-				// ***** Clipping setup (renderer): *****
-				const globalPlanes = [ globalPlane ];
-				const Empty = Object.freeze( [] );
-
-				renderer.clippingPlanes = Empty; // GUI sets it to globalPlanes
-				renderer.localClippingEnabled = true;
-
 				// Controls
+
 				const controls = new OrbitControls( camera, renderer.domElement );
 				controls.target.set( 0, 1, 0 );
 				controls.update();
@@ -143,67 +145,67 @@
 					props = {
 						alphaToCoverage: true,
 					},
-					folderLocal = gui.addFolder( 'Local Clipping' ),
-					propsLocal = {
+					folderKnot = gui.addFolder( 'Knot Clipping Group' ),
+					propsKnot = {
 
 						get 'Enabled'() {
 
-							return renderer.localClippingEnabled;
+							return knotClippingGroup.enabled;
 
 						},
 						set 'Enabled'( v ) {
 
-							renderer.localClippingEnabled = v;
+							knotClippingGroup.enabled = v;
 
 						},
 
 						get 'Shadows'() {
 
-							return material.clipShadows;
+							return knotClippingGroup.clipShadows;
 
 						},
 						set 'Shadows'( v ) {
 
-							material.clipShadows = v;
+							knotClippingGroup.clipShadows = v;
 
 						},
 
 						get 'Intersection'() {
 
-							return material.clipIntersection;
+							return knotClippingGroup.clipIntersection;
 
 						},
 
 						set 'Intersection'( v ) {
 
-							material.clipIntersection = v;
+							knotClippingGroup.clipIntersection = v;
 
 						},
 
 						get 'Plane'() {
 
-							return localPlane.constant;
+							return localPlane1.constant;
 
 						},
 						set 'Plane'( v ) {
 
-							localPlane.constant = v;
+							localPlane1.constant = v;
 
 						}
 
 					},
 
-					folderGlobal = gui.addFolder( 'Global Clipping' ),
+					folderGlobal = gui.addFolder( 'Global Clipping Group' ),
 					propsGlobal = {
 
 						get 'Enabled'() {
 
-							return renderer.clippingPlanes !== Empty;
+							return globalClippingGroup.enabled;
 
 						},
 						set 'Enabled'( v ) {
 
-							renderer.clippingPlanes = v ? globalPlanes : Empty;
+							globalClippingGroup.enabled = v;
 
 						},
 
@@ -230,10 +232,10 @@
 
 				} );
 
-				folderLocal.add( propsLocal, 'Enabled' );
-				folderLocal.add( propsLocal, 'Shadows' );
-				folderLocal.add( propsLocal, 'Intersection' );
-				folderLocal.add( propsLocal, 'Plane', 0.3, 1.25 );
+				folderKnot.add( propsKnot, 'Enabled' );
+				folderKnot.add( propsKnot, 'Shadows' );
+				folderKnot.add( propsKnot, 'Intersection' );
+				folderKnot.add( propsKnot, 'Plane', 0.3, 1.25 );
 
 				folderGlobal.add( propsGlobal, 'Enabled' );
 				folderGlobal.add( propsGlobal, 'Plane', - 0.4, 3 );

+ 1 - 0
src/Three.WebGPU.js

@@ -164,6 +164,7 @@ export * from './Three.Legacy.js';
 
 export * from './materials/nodes/NodeMaterials.js';
 export { default as WebGPURenderer } from './renderers/webgpu/WebGPURenderer.js';
+export { default as ClippingGroup } from './renderers/common/ClippingGroup.js';
 export { default as Lighting } from './renderers/common/Lighting.js';
 export { default as BundleGroup } from './renderers/common/BundleGroup.js';
 export { default as QuadMesh } from './renderers/common/QuadMesh.js';

+ 2 - 2
src/materials/nodes/NodeMaterial.js

@@ -216,11 +216,11 @@ class NodeMaterial extends Material {
 
 		if ( builder.clippingContext === null ) return null;
 
-		const { globalClippingCount, localClippingCount } = builder.clippingContext;
+		const { unionPlanes, intersectionPlanes } = builder.clippingContext;
 
 		let result = null;
 
-		if ( globalClippingCount || localClippingCount ) {
+		if ( unionPlanes.length > 0 || intersectionPlanes.length > 0 ) {
 
 			const samples = builder.renderer.samples;
 

+ 50 - 39
src/nodes/accessors/ClippingNode.js

@@ -1,9 +1,8 @@
 
 import Node from '../core/Node.js';
-import { nodeObject } from '../tsl/TSLBase.js';
+import { nodeObject, Fn, bool, float } from '../tsl/TSLBase.js';
 import { positionView } from './Position.js';
-import { diffuseColor, property } from '../core/PropertyNode.js';
-import { Fn } from '../tsl/TSLBase.js';
+import { diffuseColor } from '../core/PropertyNode.js';
 import { Loop } from '../utils/LoopNode.js';
 import { smoothstep } from '../math/MathNode.js';
 import { uniformArray } from './UniformArrayNode.js';
@@ -29,69 +28,72 @@ class ClippingNode extends Node {
 		super.setup( builder );
 
 		const clippingContext = builder.clippingContext;
-		const { localClipIntersection, localClippingCount, globalClippingCount } = clippingContext;
+		const { intersectionPlanes, unionPlanes } = clippingContext;
 
-		const numClippingPlanes = globalClippingCount + localClippingCount;
-		const numUnionClippingPlanes = localClipIntersection ? numClippingPlanes - localClippingCount : numClippingPlanes;
 
 		if ( this.scope === ClippingNode.ALPHA_TO_COVERAGE ) {
 
-			return this.setupAlphaToCoverage( clippingContext.planes, numClippingPlanes, numUnionClippingPlanes );
+			return this.setupAlphaToCoverage( intersectionPlanes, unionPlanes );
 
 		} else {
 
-			return this.setupDefault( clippingContext.planes, numClippingPlanes, numUnionClippingPlanes );
+			return this.setupDefault( intersectionPlanes, unionPlanes );
 
 		}
 
 	}
 
-	setupAlphaToCoverage( planes, numClippingPlanes, numUnionClippingPlanes ) {
+	setupAlphaToCoverage( intersectionPlanes, unionPlanes ) {
 
 		return Fn( () => {
 
-			const clippingPlanes = uniformArray( planes );
+			const distanceToPlane = float().toVar( 'distanceToPlane' );
+			const distanceGradient = float().toVar( 'distanceToGradient' );
 
-			const distanceToPlane = property( 'float', 'distanceToPlane' );
-			const distanceGradient = property( 'float', 'distanceToGradient' );
+			const clipOpacity = float( 1 ).toVar( 'clipOpacity' );
 
-			const clipOpacity = property( 'float', 'clipOpacity' );
+			const numUnionPlanes = unionPlanes.length;
 
-			clipOpacity.assign( 1 );
+			if ( numUnionPlanes > 0 ) {
 
-			let plane;
+				const clippingPlanes = uniformArray( unionPlanes );
 
-			Loop( numUnionClippingPlanes, ( { i } ) => {
+				let plane;
 
-				plane = clippingPlanes.element( i );
+				Loop( numUnionPlanes, ( { i } ) => {
 
-				distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
-				distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
+					plane = clippingPlanes.element( i );
+
+					distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
+					distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
 
-				clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) );
+					clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) );
 
-				clipOpacity.equal( 0.0 ).discard();
+				} );
+
+			}
 
-			} );
+			const numIntersectionPlanes = intersectionPlanes.length;
 
-			if ( numUnionClippingPlanes < numClippingPlanes ) {
+			if ( numIntersectionPlanes > 0 ) {
 
-				const unionClipOpacity = property( 'float', 'unionclipOpacity' );
+				const clippingPlanes = uniformArray( intersectionPlanes );
+				const intersectionClipOpacity = float( 1 ).toVar( 'intersectionClipOpacity' );
 
-				unionClipOpacity.assign( 1 );
+				let plane;
 
-				Loop( { start: numUnionClippingPlanes, end: numClippingPlanes }, ( { i } ) => {
+				Loop( numIntersectionPlanes, ( { i } ) => {
 
 					plane = clippingPlanes.element( i );
 
 					distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
 					distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
 
-					unionClipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ).oneMinus() );
+					intersectionClipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ).oneMinus() );
 
 				} );
 
-				clipOpacity.mulAssign( unionClipOpacity.oneMinus() );
+				clipOpacity.mulAssign( intersectionClipOpacity.oneMinus() );
 
 			}
 
@@ -103,28 +105,37 @@ class ClippingNode extends Node {
 
 	}
 
-	setupDefault( planes, numClippingPlanes, numUnionClippingPlanes ) {
+	setupDefault( intersectionPlanes, unionPlanes ) {
 
 		return Fn( () => {
 
-			const clippingPlanes = uniformArray( planes );
+			const numUnionPlanes = unionPlanes.length;
 
-			let plane;
+			if ( numUnionPlanes > 0 ) {
 
-			Loop( numUnionClippingPlanes, ( { i } ) => {
+				const clippingPlanes = uniformArray( unionPlanes );
 
-				plane = clippingPlanes.element( i );
-				positionView.dot( plane.xyz ).greaterThan( plane.w ).discard();
+				let plane;
+
+				Loop( numUnionPlanes, ( { i } ) => {
+
+					plane = clippingPlanes.element( i );
+					positionView.dot( plane.xyz ).greaterThan( plane.w ).discard();
+
+				} );
+
+			}
 
-			} );
+			const numIntersectionPlanes = intersectionPlanes.length;
 
-			if ( numUnionClippingPlanes < numClippingPlanes ) {
+			if ( numIntersectionPlanes > 0 ) {
 
-				const clipped = property( 'bool', 'clipped' );
+				const clippingPlanes = uniformArray( intersectionPlanes );
+				const clipped = bool( true ).toVar( 'clipped' );
 
-				clipped.assign( true );
+				let plane;
 
-				Loop( { start: numUnionClippingPlanes, end: numClippingPlanes }, ( { i } ) => {
+				Loop( numIntersectionPlanes, ( { i } ) => {
 
 					plane = clippingPlanes.element( i );
 					clipped.assign( positionView.dot( plane.xyz ).greaterThan( plane.w ).and( clipped ) );

+ 3 - 3
src/nodes/display/ToonOutlinePassNode.js

@@ -34,7 +34,7 @@ class ToonOutlinePassNode extends PassNode {
 
 		const currentRenderObjectFunction = renderer.getRenderObjectFunction();
 
-		renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode ) => {
+		renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode, clippingContext ) => {
 
 			// only render outline for supported materials
 
@@ -43,7 +43,7 @@ class ToonOutlinePassNode extends PassNode {
 				if ( material.wireframe === false ) {
 
 					const outlineMaterial = this._getOutlineMaterial( material );
-					renderer.renderObject( object, scene, camera, geometry, outlineMaterial, group, lightsNode );
+					renderer.renderObject( object, scene, camera, geometry, outlineMaterial, group, lightsNode, clippingContext );
 
 				}
 
@@ -51,7 +51,7 @@ class ToonOutlinePassNode extends PassNode {
 
 			// default
 
-			renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode );
+			renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext );
 
 		} );
 

+ 1 - 1
src/renderers/common/Background.js

@@ -99,7 +99,7 @@ class Background extends DataMap {
 
 			}
 
-			renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null );
+			renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null, null );
 
 		} else {
 

+ 73 - 87
src/renderers/common/ClippingContext.js

@@ -1,40 +1,53 @@
 import { Matrix3 } from '../../math/Matrix3.js';
 import { Plane } from '../../math/Plane.js';
 import { Vector4 } from '../../math/Vector4.js';
-import { hash } from '../../nodes/core/NodeUtils.js';
 
 const _plane = /*@__PURE__*/ new Plane();
 
 class ClippingContext {
 
-	constructor() {
+	constructor( parentContext = null ) {
 
 		this.version = 0;
 
-		this.globalClippingCount = 0;
+		this.clipIntersection = null;
+		this.cacheKey = '';
 
-		this.localClippingCount = 0;
-		this.localClippingEnabled = false;
-		this.localClipIntersection = false;
 
-		this.planes = [];
+		if ( parentContext === null ) {
 
-		this.parentVersion = 0;
-		this.viewNormalMatrix = new Matrix3();
-		this.cacheKey = 0;
+			this.intersectionPlanes = [];
+			this.unionPlanes = [];
+
+			this.viewNormalMatrix = new Matrix3();
+			this.clippingGroupContexts = new WeakMap();
+
+			this.shadowPass = false;
+
+		} else {
+
+			this.viewNormalMatrix = parentContext.viewNormalMatrix;
+			this.clippingGroupContexts = parentContext.clippingGroupContexts;
+
+			this.shadowPass = parentContext.shadowPass;
+
+			this.viewMatrix = parentContext.viewMatrix;
+
+		}
+
+		this.parentVersion = null;
 
 	}
 
-	projectPlanes( source, offset ) {
+	projectPlanes( source, destination, offset ) {
 
 		const l = source.length;
-		const planes = this.planes;
 
 		for ( let i = 0; i < l; i ++ ) {
 
 			_plane.copy( source[ i ] ).applyMatrix4( this.viewMatrix, this.viewNormalMatrix );
 
-			const v = planes[ offset + i ];
+			const v = destination[ offset + i ];
 			const normal = _plane.normal;
 
 			v.x = - normal.x;
@@ -46,129 +59,102 @@ class ClippingContext {
 
 	}
 
-	updateGlobal( renderer, camera ) {
+	updateGlobal( scene, camera ) {
 
-		const rendererClippingPlanes = renderer.clippingPlanes;
+		this.shadowPass = ( scene.overrideMaterial !== null && scene.overrideMaterial.isShadowNodeMaterial );
 		this.viewMatrix = camera.matrixWorldInverse;
 
 		this.viewNormalMatrix.getNormalMatrix( this.viewMatrix );
 
-		let update = false;
-
-		if ( Array.isArray( rendererClippingPlanes ) && rendererClippingPlanes.length !== 0 ) {
+	}
 
-			const l = rendererClippingPlanes.length;
+	update( parentContext, clippingGroup ) {
 
-			if ( l !== this.globalClippingCount ) {
+		let update = false;
 
-				const planes = [];
+		if ( parentContext.version !== this.parentVersion ) {
 
-				for ( let i = 0; i < l; i ++ ) {
+			this.intersectionPlanes = Array.from( parentContext.intersectionPlanes );
+			this.unionPlanes = Array.from( parentContext.unionPlanes );
+			this.parentVersion = parentContext.version;
 
-					planes.push( new Vector4() );
+		}
 
-				}
+		if ( this.clipIntersection !== clippingGroup.clipIntersection ) {
 
-				this.globalClippingCount = l;
-				this.planes = planes;
+			this.clipIntersection = clippingGroup.clipIntersection;
 
-				update = true;
+			if ( this.clipIntersection ) {
 
-			}
+				this.unionPlanes.length = parentContext.unionPlanes.length;
 
-			this.projectPlanes( rendererClippingPlanes, 0 );
+			} else {
 
-		} else if ( this.globalClippingCount !== 0 ) {
+				this.intersectionPlanes.length = parentContext.intersectionPlanes.length;
 
-			this.globalClippingCount = 0;
-			this.planes = [];
-			update = true;
+			}
 
 		}
 
-		if ( renderer.localClippingEnabled !== this.localClippingEnabled ) {
+		const srcClippingPlanes = clippingGroup.clippingPlanes;
+		const l = srcClippingPlanes.length;
 
-			this.localClippingEnabled = renderer.localClippingEnabled;
-			update = true;
+		let dstClippingPlanes;
+		let offset;
 
-		}
+		if ( this.clipIntersection ) {
 
-		if ( update ) {
+			dstClippingPlanes = this.intersectionPlanes;
+			offset = parentContext.intersectionPlanes.length;
 
-			this.version ++;
-			this.cacheKey = hash( this.globalClippingCount, this.localClippingEnabled === true ? 1 : 0 );
+		} else {
+
+			dstClippingPlanes = this.unionPlanes;
+			offset = parentContext.unionPlanes.length;
 
 		}
 
-	}
+		if ( dstClippingPlanes.length !== offset + l ) {
 
-	update( parent, material ) {
+			dstClippingPlanes.length = offset + l;
 
-		let update = false;
+			for ( let i = 0; i < l; i ++ ) {
 
-		if ( this !== parent && parent.version !== this.parentVersion ) {
+				dstClippingPlanes[ offset + i ] = new Vector4();
 
-			this.globalClippingCount = material.isShadowNodeMaterial ? 0 : parent.globalClippingCount;
-			this.localClippingEnabled = parent.localClippingEnabled;
-			this.planes = Array.from( parent.planes );
-			this.parentVersion = parent.version;
-			this.viewMatrix = parent.viewMatrix;
-			this.viewNormalMatrix = parent.viewNormalMatrix;
+			}
 
 			update = true;
 
 		}
 
-		if ( this.localClippingEnabled ) {
-
-			const localClippingPlanes = material.clippingPlanes;
-
-			if ( ( Array.isArray( localClippingPlanes ) && localClippingPlanes.length !== 0 ) ) {
-
-				const l = localClippingPlanes.length;
-				const planes = this.planes;
-				const offset = this.globalClippingCount;
-
-				if ( update || l !== this.localClippingCount ) {
-
-					planes.length = offset + l;
-
-					for ( let i = 0; i < l; i ++ ) {
-
-						planes[ offset + i ] = new Vector4();
-
-					}
+		this.projectPlanes( srcClippingPlanes, dstClippingPlanes, offset );
 
-					this.localClippingCount = l;
-					update = true;
-
-				}
+		if ( update ) {
 
-				this.projectPlanes( localClippingPlanes, offset );
+			this.version ++;
+			this.cacheKey = `${ this.intersectionPlanes.length }:${ this.unionPlanes.length }`;
 
+		}
 
-			} else if ( this.localClippingCount !== 0 ) {
+	}
 
-				this.localClippingCount = 0;
-				update = true;
+	getGroupContext( clippingGroup ) {
 
-			}
+		if ( this.shadowPass && ! clippingGroup.clipShadows ) return this;
 
-			if ( this.localClipIntersection !== material.clipIntersection ) {
+		let context = this.clippingGroupContexts.get( clippingGroup );
 
-				this.localClipIntersection = material.clipIntersection;
-				update = true;
+		if ( context === undefined ) {
 
-			}
+			context = new ClippingContext( this );
+			this.clippingGroupContexts.set( clippingGroup, context );
 
 		}
 
-		if ( update ) {
+		context.update( this, clippingGroup );
 
-			this.version += parent.version;
-			this.cacheKey = hash( parent.cacheKey, this.localClippingCount, this.localClipIntersection === true ? 1 : 0 );
-
-		}
+		return context;
 
 	}
 

+ 19 - 0
src/renderers/common/ClippingGroup.js

@@ -0,0 +1,19 @@
+import { Group } from '../../objects/Group.js';
+
+class ClippingGroup extends Group {
+
+	constructor() {
+
+		super();
+
+		this.isClippingGroup = true;
+		this.clippingPlanes = [];
+		this.enabled = true;
+		this.clipIntersection = false;
+		this.clipShadows = false;
+
+	}
+
+}
+
+export default ClippingGroup;

+ 9 - 6
src/renderers/common/RenderList.js

@@ -95,7 +95,7 @@ class RenderList {
 
 	}
 
-	getNextRenderItem( object, geometry, material, groupOrder, z, group ) {
+	getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext ) {
 
 		let renderItem = this.renderItems[ this.renderItemsIndex ];
 
@@ -109,7 +109,8 @@ class RenderList {
 				groupOrder: groupOrder,
 				renderOrder: object.renderOrder,
 				z: z,
-				group: group
+				group: group,
+				clippingContext: clippingContext
 			};
 
 			this.renderItems[ this.renderItemsIndex ] = renderItem;
@@ -124,6 +125,7 @@ class RenderList {
 			renderItem.renderOrder = object.renderOrder;
 			renderItem.z = z;
 			renderItem.group = group;
+			renderItem.clippingContext = clippingContext;
 
 		}
 
@@ -133,9 +135,9 @@ class RenderList {
 
 	}
 
-	push( object, geometry, material, groupOrder, z, group ) {
+	push( object, geometry, material, groupOrder, z, group, clippingContext ) {
 
-		const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group );
+		const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext );
 
 		if ( object.occlusionTest === true ) this.occlusionQueryCount ++;
 
@@ -153,9 +155,9 @@ class RenderList {
 
 	}
 
-	unshift( object, geometry, material, groupOrder, z, group ) {
+	unshift( object, geometry, material, groupOrder, z, group, clippingContext ) {
 
-		const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group );
+		const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext );
 
 		if ( material.transparent === true || material.transmission > 0 ) {
 
@@ -213,6 +215,7 @@ class RenderList {
 			renderItem.renderOrder = null;
 			renderItem.z = null;
 			renderItem.group = null;
+			renderItem.clippingContext = null;
 
 		}
 

+ 7 - 28
src/renderers/common/RenderObject.js

@@ -1,5 +1,4 @@
 import { hashString } from '../../nodes/core/NodeUtils.js';
-import ClippingContext from './ClippingContext.js';
 
 let _id = 0;
 
@@ -39,7 +38,7 @@ function getKeys( obj ) {
 
 export default class RenderObject {
 
-	constructor( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext ) {
+	constructor( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext ) {
 
 		this._nodes = nodes;
 		this._geometries = geometries;
@@ -66,9 +65,8 @@ export default class RenderObject {
 
 		this.bundle = null;
 
-		this.updateClipping( renderContext.clippingContext );
-
-		this.clippingContextVersion = this.clippingContext.version;
+		this.clippingContext = clippingContext;
+		this.clippingContextCacheKey = clippingContext !== null ? clippingContext.cacheKey : '';
 
 		this.initialNodesCacheKey = this.getDynamicCacheKey();
 		this.initialCacheKey = this.getCacheKey();
@@ -93,34 +91,15 @@ export default class RenderObject {
 
 	updateClipping( parent ) {
 
-		const material = this.material;
-
-		let clippingContext = this.clippingContext;
-
-		if ( Array.isArray( material.clippingPlanes ) ) {
-
-			if ( clippingContext === parent || ! clippingContext ) {
-
-				clippingContext = new ClippingContext();
-				this.clippingContext = clippingContext;
-
-			}
-
-			clippingContext.update( parent, material );
-
-		} else if ( this.clippingContext !== parent ) {
-
-			this.clippingContext = parent;
-
-		}
+		this.clippingContext = parent;
 
 	}
 
 	get clippingNeedsUpdate() {
 
-		if ( this.clippingContext.version === this.clippingContextVersion ) return false;
+		if ( this.clippingContext === null || this.clippingContext.cacheKey === this.clippingContextCacheKey ) return false;
 
-		this.clippingContextVersion = this.clippingContext.version;
+		this.clippingContextCacheKey = this.clippingContext.cacheKey;
 
 		return true;
 
@@ -347,7 +326,7 @@ export default class RenderObject {
 
 		}
 
-		cacheKey += this.clippingContext.cacheKey + ',';
+		cacheKey += this.clippingContextCacheKey + ',';
 
 		if ( object.geometry ) {
 

+ 6 - 6
src/renderers/common/RenderObjects.js

@@ -18,7 +18,7 @@ class RenderObjects {
 
 	}
 
-	get( object, material, scene, camera, lightsNode, renderContext, passId ) {
+	get( object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ) {
 
 		const chainMap = this.getChainMap( passId );
 
@@ -32,13 +32,13 @@ class RenderObjects {
 
 		if ( renderObject === undefined ) {
 
-			renderObject = this.createRenderObject( this.nodes, this.geometries, this.renderer, object, material, scene, camera, lightsNode, renderContext, passId );
+			renderObject = this.createRenderObject( this.nodes, this.geometries, this.renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext, passId );
 
 			chainMap.set( chainArray, renderObject );
 
 		} else {
 
-			renderObject.updateClipping( renderContext.clippingContext );
+			renderObject.updateClipping( clippingContext );
 
 			if ( renderObject.version !== material.version || renderObject.needsUpdate ) {
 
@@ -46,7 +46,7 @@ class RenderObjects {
 
 					renderObject.dispose();
 
-					renderObject = this.get( object, material, scene, camera, lightsNode, renderContext, passId );
+					renderObject = this.get( object, material, scene, camera, lightsNode, renderContext, clippingContext, passId );
 
 				} else {
 
@@ -74,11 +74,11 @@ class RenderObjects {
 
 	}
 
-	createRenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, passId ) {
+	createRenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ) {
 
 		const chainMap = this.getChainMap( passId );
 
-		const renderObject = new RenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext );
+		const renderObject = new RenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext );
 
 		renderObject.onDispose = () => {
 

+ 22 - 48
src/renderers/common/Renderer.js

@@ -78,8 +78,6 @@ class Renderer {
 		this.depth = depth;
 		this.stencil = stencil;
 
-		this.clippingPlanes = [];
-
 		this.info = new Info();
 
 		this.nodes = {
@@ -312,7 +310,7 @@ class Renderer {
 		renderContext.stencil = this.stencil;
 
 		if ( ! renderContext.clippingContext ) renderContext.clippingContext = new ClippingContext();
-		renderContext.clippingContext.updateGlobal( this, camera );
+		renderContext.clippingContext.updateGlobal( sceneRef, camera );
 
 		//
 
@@ -323,7 +321,7 @@ class Renderer {
 		const renderList = this._renderLists.get( scene, camera );
 		renderList.begin();
 
-		this._projectObject( scene, camera, 0, renderList );
+		this._projectObject( scene, camera, 0, renderList, renderContext.clippingContext );
 
 		// include lights from target scene
 		if ( targetScene !== scene ) {
@@ -678,7 +676,7 @@ class Renderer {
 		renderContext.scissorValue.height >>= activeMipmapLevel;
 
 		if ( ! renderContext.clippingContext ) renderContext.clippingContext = new ClippingContext();
-		renderContext.clippingContext.updateGlobal( this, camera );
+		renderContext.clippingContext.updateGlobal( sceneRef, camera );
 
 		//
 
@@ -692,7 +690,7 @@ class Renderer {
 		const renderList = this._renderLists.get( scene, camera );
 		renderList.begin();
 
-		this._projectObject( scene, camera, 0, renderList );
+		this._projectObject( scene, camera, 0, renderList, renderContext.clippingContext );
 
 		renderList.finish();
 
@@ -1381,7 +1379,7 @@ class Renderer {
 
 	}
 
-	_projectObject( object, camera, groupOrder, renderList ) {
+	_projectObject( object, camera, groupOrder, renderList, clippingContext ) {
 
 		if ( object.visible === false ) return;
 
@@ -1393,6 +1391,8 @@ class Renderer {
 
 				groupOrder = object.renderOrder;
 
+				if ( object.isClippingGroup && object.enabled ) clippingContext = clippingContext.getGroupContext( object );
+
 			} else if ( object.isLOD ) {
 
 				if ( object.autoUpdate === true ) object.update( camera );
@@ -1415,7 +1415,7 @@ class Renderer {
 
 					if ( material.visible ) {
 
-						renderList.push( object, geometry, material, groupOrder, _vector4.z, null );
+						renderList.push( object, geometry, material, groupOrder, _vector4.z, null, clippingContext );
 
 					}
 
@@ -1453,7 +1453,7 @@ class Renderer {
 
 							if ( groupMaterial && groupMaterial.visible ) {
 
-								renderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group );
+								renderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group, clippingContext );
 
 							}
 
@@ -1461,7 +1461,7 @@ class Renderer {
 
 					} else if ( material.visible ) {
 
-						renderList.push( object, geometry, material, groupOrder, _vector4.z, null );
+						renderList.push( object, geometry, material, groupOrder, _vector4.z, null, clippingContext );
 
 					}
 
@@ -1494,7 +1494,7 @@ class Renderer {
 
 		for ( let i = 0, l = children.length; i < l; i ++ ) {
 
-			this._projectObject( children[ i ], camera, groupOrder, renderList );
+			this._projectObject( children[ i ], camera, groupOrder, renderList, clippingContext );
 
 		}
 
@@ -1561,7 +1561,7 @@ class Renderer {
 			// @TODO: Add support for multiple materials per object. This will require to extract
 			// the material from the renderItem object and pass it with its group data to renderObject().
 
-			const { object, geometry, material, group } = renderItem;
+			const { object, geometry, material, group, clippingContext } = renderItem;
 
 			if ( camera.isArrayCamera ) {
 
@@ -1584,7 +1584,7 @@ class Renderer {
 
 						this.backend.updateViewport( this._currentRenderContext );
 
-						this._currentRenderObjectFunction( object, scene, camera2, geometry, material, group, lightsNode, passId );
+						this._currentRenderObjectFunction( object, scene, camera2, geometry, material, group, lightsNode, clippingContext, passId );
 
 					}
 
@@ -1592,7 +1592,7 @@ class Renderer {
 
 			} else {
 
-				this._currentRenderObjectFunction( object, scene, camera, geometry, material, group, lightsNode, passId );
+				this._currentRenderObjectFunction( object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId );
 
 			}
 
@@ -1600,7 +1600,7 @@ class Renderer {
 
 	}
 
-	renderObject( object, scene, camera, geometry, material, group, lightsNode, passId = null ) {
+	renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext = null, passId = null ) {
 
 		let overridePositionNode;
 		let overrideFragmentNode;
@@ -1642,32 +1642,6 @@ class Renderer {
 
 				}
 
-				if ( this.localClippingEnabled ) {
-
-					if ( material.clipShadows ) {
-
-						if ( overrideMaterial.clippingPlanes !== material.clippingPlanes ) {
-
-							overrideMaterial.clippingPlanes = material.clippingPlanes;
-							overrideMaterial.needsUpdate = true;
-
-						}
-
-						if ( overrideMaterial.clipIntersection !== material.clipIntersection ) {
-
-							overrideMaterial.clipIntersection = material.clipIntersection;
-
-						}
-
-					} else if ( Array.isArray( overrideMaterial.clippingPlanes ) ) {
-
-						overrideMaterial.clippingPlanes = null;
-						overrideMaterial.needsUpdate = true;
-
-					}
-
-				}
-
 			}
 
 			material = overrideMaterial;
@@ -1679,16 +1653,16 @@ class Renderer {
 		if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) {
 
 			material.side = BackSide;
-			this._handleObjectFunction( object, material, scene, camera, lightsNode, group, 'backSide' ); // create backSide pass id
+			this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, 'backSide' ); // create backSide pass id
 
 			material.side = FrontSide;
-			this._handleObjectFunction( object, material, scene, camera, lightsNode, group, passId ); // use default pass id
+			this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, passId ); // use default pass id
 
 			material.side = DoubleSide;
 
 		} else {
 
-			this._handleObjectFunction( object, material, scene, camera, lightsNode, group, passId );
+			this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, passId );
 
 		}
 
@@ -1718,9 +1692,9 @@ class Renderer {
 
 	}
 
-	_renderObjectDirect( object, material, scene, camera, lightsNode, group, passId ) {
+	_renderObjectDirect( object, material, scene, camera, lightsNode, group, clippingContext, passId ) {
 
-		const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, passId );
+		const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId );
 		renderObject.drawRange = object.geometry.drawRange;
 		renderObject.group = group;
 
@@ -1759,9 +1733,9 @@ class Renderer {
 
 	}
 
-	_createObjectPipeline( object, material, scene, camera, lightsNode, passId ) {
+	_createObjectPipeline( object, material, scene, camera, lightsNode, clippingContext, passId ) {
 
-		const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, passId );
+		const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId );
 
 		//
 

+ 3 - 3
src/renderers/webgpu/WebGPUBackend.js

@@ -1061,7 +1061,7 @@ class WebGPUBackend extends Backend {
 			data.sampleCount !== sampleCount || data.colorSpace !== colorSpace ||
 			data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat ||
 			data.primitiveTopology !== primitiveTopology ||
-			data.clippingContextCacheKey !== renderObject.clippingContext.cacheKey
+			data.clippingContextCacheKey !== renderObject.clippingContextCacheKey
 		) {
 
 			data.material = material; data.materialVersion = material.version;
@@ -1079,7 +1079,7 @@ class WebGPUBackend extends Backend {
 			data.colorFormat = colorFormat;
 			data.depthStencilFormat = depthStencilFormat;
 			data.primitiveTopology = primitiveTopology;
-			data.clippingContextCacheKey = renderObject.clippingContext.cacheKey;
+			data.clippingContextCacheKey = renderObject.clippingContextCacheKey;
 
 			needsUpdate = true;
 
@@ -1110,7 +1110,7 @@ class WebGPUBackend extends Backend {
 			utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ),
 			utils.getPrimitiveTopology( object, material ),
 			renderObject.getGeometryCacheKey(),
-			renderObject.clippingContext.cacheKey
+			renderObject.clippingContextCacheKey
 		].join();
 
 	}

粤ICP备19079148号