Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGPURenderer: implement ClippingGroup object #28237

Merged
merged 15 commits into from
Nov 4, 2024
72 changes: 37 additions & 35 deletions examples/webgpu_clipping.html
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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();
Expand All @@ -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;

},

Expand All @@ -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 );
Expand Down
1 change: 1 addition & 0 deletions src/Three.WebGPU.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
4 changes: 2 additions & 2 deletions src/materials/nodes/NodeMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
89 changes: 50 additions & 39 deletions src/nodes/accessors/ClippingNode.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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() );

}

Expand All @@ -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 ) );
Expand Down
6 changes: 3 additions & 3 deletions src/nodes/display/ToonOutlinePassNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -43,15 +43,15 @@ 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 );

}

}

// default

renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode );
renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext );

} );

Expand Down
2 changes: 1 addition & 1 deletion src/renderers/common/Background.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
Loading