Skip to content

Commit

Permalink
WebGPURenderer: Support clipping (#27691)
Browse files Browse the repository at this point in the history
* clipping

* add ;

* remove testing code

* cleanup and adjust default settiing in example

* remove unused import

* cleanup ClippingNode

* combine methods

* rework to increase efficiency

* update screenshot

* fix asyncCompile

* fix against upstream

* cleanup

* simplify

* exclude tests

---------

Co-authored-by: aardgoose <angus.sawyer@email.com>
  • Loading branch information
aardgoose and aardgoose authored Feb 9, 2024
1 parent 9239009 commit 158d8a1
Show file tree
Hide file tree
Showing 13 changed files with 730 additions and 6 deletions.
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@
"webgpu_backdrop_water",
"webgpu_camera_logarithmicdepthbuffer",
"webgpu_clearcoat",
"webgpu_clipping",
"webgpu_compute_audio",
"webgpu_compute_particles",
"webgpu_compute_particles_rain",
Expand Down
144 changes: 144 additions & 0 deletions examples/jsm/nodes/accessors/ClippingNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@

import Node from '../core/Node.js';
import { nodeObject } from '../shadernode/ShaderNode.js';
import { positionView } from './PositionNode.js';
import { diffuseColor, property } from '../core/PropertyNode.js';
import { tslFn } from '../shadernode/ShaderNode.js';
import { loop } from '../utils/LoopNode.js';
import { smoothstep } from '../math/MathNode.js';
import { uniforms } from './UniformsNode.js';

class ClippingNode extends Node {

constructor( scope = ClippingNode.DEFAULT ) {

super();

this.scope = scope;

}

setup( builder ) {

super.setup( builder );

const clippingContext = builder.clippingContext;
const { localClipIntersection, localClippingCount, globalClippingCount } = 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 );

} else {

return this.setupDefault( clippingContext.planes, numClippingPlanes, numUnionClippingPlanes );

}

}

setupAlphaToCoverage( planes, numClippingPlanes, numUnionClippingPlanes ) {

return tslFn( () => {

const clippingPlanes = uniforms( planes );

const distanceToPlane = property( 'float', 'distanceToPlane' );
const distanceGradient = property( 'float', 'distanceToGradient' );

const clipOpacity = property( 'float', 'clipOpacity' );

clipOpacity.assign( 1 );

let plane;

loop( numUnionClippingPlanes, ( { i } ) => {

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.equal( 0.0 ).discard();

} );

if ( numUnionClippingPlanes < numClippingPlanes ) {

const unionClipOpacity = property( 'float', 'unionclipOpacity' );

unionClipOpacity.assign( 1 );

loop( { start: numUnionClippingPlanes, end: numClippingPlanes }, ( { 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() );

} );

clipOpacity.mulAssign( unionClipOpacity.oneMinus() );

}

diffuseColor.a.mulAssign( clipOpacity );

diffuseColor.a.equal( 0.0 ).discard();

} )();

}

setupDefault( planes, numClippingPlanes, numUnionClippingPlanes ) {

return tslFn( () => {

const clippingPlanes = uniforms( planes );

let plane;

loop( numUnionClippingPlanes, ( { i } ) => {

plane = clippingPlanes.element( i );
positionView.dot( plane.xyz ).greaterThan( plane.w ).discard();

} );

if ( numUnionClippingPlanes < numClippingPlanes ) {

const clipped = property( 'bool', 'clipped' );

clipped.assign( true );

loop( { start: numUnionClippingPlanes, end: numClippingPlanes }, ( { i } ) => {

plane = clippingPlanes.element( i );
clipped.assign( positionView.dot( plane.xyz ).greaterThan( plane.w ).and( clipped ) );

} );

clipped.discard();
}

} )();

}

}

ClippingNode.ALPHA_TO_COVERAGE = 'alphaToCoverage';
ClippingNode.DEFAULT = 'default';

export default ClippingNode;

export const clipping = () => nodeObject( new ClippingNode() );

export const clippingAlpha = () => nodeObject( new ClippingNode( ClippingNode.ALPHA_TO_COVERAGE ) );
2 changes: 2 additions & 0 deletions examples/jsm/nodes/core/NodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class NodeBuilder {
this.fogNode = null;
this.toneMappingNode = null;

this.clippingContext = null;

this.vertexShader = null;
this.fragmentShader = null;
this.computeShader = null;
Expand Down
30 changes: 30 additions & 0 deletions examples/jsm/nodes/materials/NodeMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { lightingContext } from '../lighting/LightingContextNode.js';
import EnvironmentNode from '../lighting/EnvironmentNode.js';
import { depthPixel } from '../display/ViewportDepthNode.js';
import { cameraLogDepth } from '../accessors/CameraNode.js';
import { clipping, clippingAlpha } from '../accessors/ClippingNode.js';

const NodeMaterials = new Map();

Expand Down Expand Up @@ -90,6 +91,8 @@ class NodeMaterial extends ShaderMaterial {

let resultNode;

const clippingNode = this.setupClipping( builder );

if ( this.fragmentNode === null ) {

if ( this.depthWrite === true ) this.setupDepth( builder );
Expand All @@ -101,6 +104,8 @@ class NodeMaterial extends ShaderMaterial {

const outgoingLightNode = this.setupLighting( builder );

if ( clippingNode !== null ) builder.stack.add( clippingNode );

resultNode = this.setupOutput( builder, vec4( outgoingLightNode, diffuseColor.a ) );

// OUTPUT NODE
Expand All @@ -123,6 +128,31 @@ class NodeMaterial extends ShaderMaterial {

}

setupClipping( builder ) {

const { globalClippingCount, localClippingCount } = builder.clippingContext;

let result = null;

if ( globalClippingCount || localClippingCount ) {

if ( this.alphaToCoverage ) {

// to be added to flow when the color/alpha value has been determined
result = clippingAlpha();

} else {

builder.stack.add( clipping() );

}

}

return result;

}

setupDepth( builder ) {

const { renderer } = builder;
Expand Down
165 changes: 165 additions & 0 deletions examples/jsm/renderers/common/ClippingContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { Matrix3, Plane, Vector4 } from 'three';

const _plane = new Plane();
const _viewNormalMatrix = new Matrix3();

let _clippingContextVersion = 0;

class ClippingContext {

constructor() {

this.version = ++ _clippingContextVersion;

this.globalClippingCount = 0;

this.localClippingCount = 0;
this.localClippingEnabled = false;
this.localClipIntersection = false;

this.planes = [];

this.parentVersion = 0;

}

projectPlanes( source, offset ) {

const l = source.length;
const planes = this.planes;

for ( let i = 0; i < l; i ++ ) {

_plane.copy( source[ i ] ).applyMatrix4( this.viewMatrix, _viewNormalMatrix );

const v = planes[ offset + i ];
const normal = _plane.normal;

v.x = - normal.x;
v.y = - normal.y;
v.z = - normal.z;
v.w = _plane.constant;

}

}

updateGlobal( renderer, camera ) {

const rendererClippingPlanes = renderer.clippingPlanes;
this.viewMatrix = camera.matrixWorldInverse;

_viewNormalMatrix.getNormalMatrix( this.viewMatrix );

let update = false;

if ( Array.isArray( rendererClippingPlanes ) && rendererClippingPlanes.length !== 0 ) {

const l = rendererClippingPlanes.length;

if ( l !== this.globalClippingCount ) {

const planes = [];

for ( let i = 0; i < l; i ++ ) {

planes.push( new Vector4() );

}

this.globalClippingCount = l;
this.planes = planes;

update = true;

}

this.projectPlanes( rendererClippingPlanes, 0 );

} else if ( this.globalClippingCount !== 0 ) {

this.globalClippingCount = 0;
this.planes = [];
update = true;

}

if ( renderer.localClippingEnabled !== this.localClippingEnabled ) {

this.localClippingEnabled = renderer.localClippingEnabled;
update = true;

}

if ( update ) this.version = _clippingContextVersion ++;

}

update( parent, material ) {

let update = false;

if ( this !== parent && parent.version !== this.parentVersion ) {

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;


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.localClippingCount = l;
update = true;

}

this.projectPlanes( localClippingPlanes, offset );


} else if ( this.localClippingCount !== 0 ) {

this.localClippingCount = 0;
update = true;

}

if ( this.localClipIntersection !== material.clipIntersection ) {

this.localClipIntersection = material.clipIntersection;
update = true;

}

}

if ( update ) this.version = _clippingContextVersion ++;

}

}

export default ClippingContext;
Loading

0 comments on commit 158d8a1

Please sign in to comment.