diff --git a/examples/webgpu_clipping.html b/examples/webgpu_clipping.html index c037d5c7838606..184ac22a597886 100644 --- a/examples/webgpu_clipping.html +++ b/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 ); diff --git a/src/Three.WebGPU.js b/src/Three.WebGPU.js index 8613be85887b03..eb77205de000ed 100644 --- a/src/Three.WebGPU.js +++ b/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'; diff --git a/src/materials/nodes/NodeMaterial.js b/src/materials/nodes/NodeMaterial.js index 5179d5864f1c85..fdec68f77f26df 100644 --- a/src/materials/nodes/NodeMaterial.js +++ b/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; diff --git a/src/nodes/accessors/ClippingNode.js b/src/nodes/accessors/ClippingNode.js index 96e00141815af3..28bc7ab1c63a8c 100644 --- a/src/nodes/accessors/ClippingNode.js +++ b/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 ) ); diff --git a/src/nodes/display/ToonOutlinePassNode.js b/src/nodes/display/ToonOutlinePassNode.js index 9d497acab33609..00586208f4d8fd 100644 --- a/src/nodes/display/ToonOutlinePassNode.js +++ b/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 ); } ); diff --git a/src/renderers/common/Background.js b/src/renderers/common/Background.js index a47ddb3b370187..1a1ed200538f31 100644 --- a/src/renderers/common/Background.js +++ b/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 { diff --git a/src/renderers/common/ClippingContext.js b/src/renderers/common/ClippingContext.js index a4624ff044ba98..07243d56984b62 100644 --- a/src/renderers/common/ClippingContext.js +++ b/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; } diff --git a/src/renderers/common/ClippingGroup.js b/src/renderers/common/ClippingGroup.js new file mode 100644 index 00000000000000..a54dc1195f07bf --- /dev/null +++ b/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; diff --git a/src/renderers/common/RenderList.js b/src/renderers/common/RenderList.js index 76614893f55ac0..d819ee90bbb4c2 100644 --- a/src/renderers/common/RenderList.js +++ b/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; } diff --git a/src/renderers/common/RenderObject.js b/src/renderers/common/RenderObject.js index 987f59181039e3..2f25db1c9a5ca8 100644 --- a/src/renderers/common/RenderObject.js +++ b/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 ) { diff --git a/src/renderers/common/RenderObjects.js b/src/renderers/common/RenderObjects.js index 432c82375171c5..26dffcece2979a 100644 --- a/src/renderers/common/RenderObjects.js +++ b/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 = () => { diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js index 87b7a5d77b2cb5..2a700e1bb6fccd 100644 --- a/src/renderers/common/Renderer.js +++ b/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 ); // diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 9d695aae17a193..2e06a4448093f2 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/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(); }