From ffd2dff5eee24534db9d196ff0f7cf5247eaba0b Mon Sep 17 00:00:00 2001 From: aardgoose Date: Wed, 5 Jun 2024 14:50:41 +0100 Subject: [PATCH] hardware clipping --- examples/jsm/nodes/accessors/BuiltinNode.js | 28 +++++++++++++++++ examples/jsm/nodes/accessors/ClippingNode.js | 30 ++++++++++++++++++ examples/jsm/nodes/materials/NodeMaterial.js | 29 +++++++++++++++-- .../jsm/renderers/common/ClippingContext.js | 1 + examples/jsm/renderers/common/RenderObject.js | 6 ++++ examples/jsm/renderers/webgl/WebGLBackend.js | 4 +-- .../renderers/webgl/nodes/GLSLNodeBuilder.js | 31 +++++++++++++++++++ .../jsm/renderers/webgl/utils/WebGLState.js | 28 ++++++++++++++++- .../renderers/webgpu/nodes/WGSLNodeBuilder.js | 24 +++++++++++++- 9 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 examples/jsm/nodes/accessors/BuiltinNode.js diff --git a/examples/jsm/nodes/accessors/BuiltinNode.js b/examples/jsm/nodes/accessors/BuiltinNode.js new file mode 100644 index 00000000000000..f7f3367369b882 --- /dev/null +++ b/examples/jsm/nodes/accessors/BuiltinNode.js @@ -0,0 +1,28 @@ +import Node, { addNodeClass } from '../core/Node.js'; +import { nodeProxy } from '../shadernode/ShaderNode.js'; + +class BuiltinNode extends Node { + + constructor( name ) { + + super( 'float' ); + + this.name = name; + + this.isBuiltinNode = true; + + } + + generate( /* builder */ ) { + + return this.name; + + } + +} + +export default BuiltinNode; + +export const builtin = nodeProxy( BuiltinNode ); + +addNodeClass( 'BuiltinhNode', BuiltinNode ); diff --git a/examples/jsm/nodes/accessors/ClippingNode.js b/examples/jsm/nodes/accessors/ClippingNode.js index b77ce29fc084d7..94fddee90adca3 100644 --- a/examples/jsm/nodes/accessors/ClippingNode.js +++ b/examples/jsm/nodes/accessors/ClippingNode.js @@ -7,6 +7,7 @@ import { tslFn } from '../shadernode/ShaderNode.js'; import { loop } from '../utils/LoopNode.js'; import { smoothstep } from '../math/MathNode.js'; import { uniforms } from './UniformsNode.js'; +import { builtin } from './BuiltinNode.js'; class ClippingNode extends Node { @@ -32,6 +33,10 @@ class ClippingNode extends Node { return this.setupAlphaToCoverage( clippingContext.planes, numClippingPlanes, numUnionClippingPlanes ); + } else if ( this.scope === ClippingNode.HARDWARE ) { + + return this.setupHardwareClipping( clippingContext.planes, numUnionClippingPlanes, builder ); + } else { return this.setupDefault( clippingContext.planes, numClippingPlanes, numUnionClippingPlanes ); @@ -133,13 +138,38 @@ class ClippingNode extends Node { } + setupHardwareClipping( planes, numUnionClippingPlanes, builder ) { + + return tslFn( () => { + + const clippingPlanes = uniforms( planes ); + let plane; + + const hw_clip_distances = builtin( builder.getClipDistance() ); + + for ( let i = 0; i < numUnionClippingPlanes; i ++ ) { + + plane = clippingPlanes.element( i ); + + const distance = positionView.dot( plane.xyz ).sub( plane.w ).negate(); + hw_clip_distances.element( i ).assign( distance ); + + } + + } )(); + + } + } ClippingNode.ALPHA_TO_COVERAGE = 'alphaToCoverage'; ClippingNode.DEFAULT = 'default'; +ClippingNode.HARDWARE = 'hardware'; export default ClippingNode; export const clipping = () => nodeObject( new ClippingNode() ); export const clippingAlpha = () => nodeObject( new ClippingNode( ClippingNode.ALPHA_TO_COVERAGE ) ); + +export const hardwareClipping = () => nodeObject( new ClippingNode( ClippingNode.HARDWARE ) ); diff --git a/examples/jsm/nodes/materials/NodeMaterial.js b/examples/jsm/nodes/materials/NodeMaterial.js index 72ded5b3062437..82606c6669b8fb 100644 --- a/examples/jsm/nodes/materials/NodeMaterial.js +++ b/examples/jsm/nodes/materials/NodeMaterial.js @@ -22,7 +22,7 @@ import EnvironmentNode from '../lighting/EnvironmentNode.js'; import IrradianceNode from '../lighting/IrradianceNode.js'; import { depthPixel } from '../display/ViewportDepthNode.js'; import { cameraLogDepth } from '../accessors/CameraNode.js'; -import { clipping, clippingAlpha } from '../accessors/ClippingNode.js'; +import { clipping, clippingAlpha, hardwareClipping } from '../accessors/ClippingNode.js'; import { faceDirection } from '../display/FrontFacingNode.js'; const NodeMaterials = new Map(); @@ -42,6 +42,7 @@ class NodeMaterial extends Material { this.fog = true; this.lights = true; this.normals = true; + this.hardwareClipping = false; this.lightsNode = null; this.envNode = null; @@ -146,7 +147,7 @@ class NodeMaterial extends Material { setupClipping( builder ) { - if ( builder.clippingContext === null ) return null; + if ( builder.clippingContext === null || this.hardwareClipping === true ) return null; const { globalClippingCount, localClippingCount } = builder.clippingContext; @@ -171,6 +172,28 @@ class NodeMaterial extends Material { } + setupHardwareClipping( builder ) { + + if ( builder.clippingContext === null ) return null; + + const { localClipIntersection } = builder.clippingContext; + + if ( ! localClipIntersection && ! this.alphaToCoverage && builder.enableHardwareClipping() ) { + + builder.stack.add( hardwareClipping() ); + + this.hardwareClipping = true; + + } else { + + this.hardwareClipping = false; + + } + + return; + + } + setupDepth( builder ) { const { renderer } = builder; @@ -244,6 +267,8 @@ class NodeMaterial extends Material { } + this.setupHardwareClipping( builder ); + const mvp = modelViewProjection(); builder.context.vertex = builder.removeStack(); diff --git a/examples/jsm/renderers/common/ClippingContext.js b/examples/jsm/renderers/common/ClippingContext.js index a0fd4349b3f642..4931248ce8a676 100644 --- a/examples/jsm/renderers/common/ClippingContext.js +++ b/examples/jsm/renderers/common/ClippingContext.js @@ -20,6 +20,7 @@ class ClippingContext { this.parentVersion = 0; this.viewNormalMatrix = new Matrix3(); + this.hardwareClippingPlanes = 0; } diff --git a/examples/jsm/renderers/common/RenderObject.js b/examples/jsm/renderers/common/RenderObject.js index 141799c5640580..cc6981754b848c 100644 --- a/examples/jsm/renderers/common/RenderObject.js +++ b/examples/jsm/renderers/common/RenderObject.js @@ -121,6 +121,12 @@ export default class RenderObject { } + get hardwareClippingPlanes() { + + return this.clippingContext.hardwareClippingPlanes; + + } + getNodeBuilderState() { return this._nodeBuilderState || ( this._nodeBuilderState = this._nodes.getForRender( this ) ); diff --git a/examples/jsm/renderers/webgl/WebGLBackend.js b/examples/jsm/renderers/webgl/WebGLBackend.js index 0c11cbff8dcea2..34af8de19d580e 100644 --- a/examples/jsm/renderers/webgl/WebGLBackend.js +++ b/examples/jsm/renderers/webgl/WebGLBackend.js @@ -569,7 +569,7 @@ class WebGLBackend extends Backend { draw( renderObject, info ) { - const { object, pipeline, material, context } = renderObject; + const { object, pipeline, material, context, hardwareClippingPlanes } = renderObject; const { programGPU } = this.get( pipeline ); const { gl, state } = this; @@ -582,7 +582,7 @@ class WebGLBackend extends Backend { const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); - state.setMaterial( material, frontFaceCW ); + state.setMaterial( material, frontFaceCW, hardwareClippingPlanes ); gl.useProgram( programGPU ); diff --git a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js index aa60d4b484c852..66b42481ab2132 100644 --- a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js @@ -549,6 +549,12 @@ ${ flowData.code } } + getClipDistance() { + + return 'gl_ClipDistance'; + + } + isAvailable( name ) { return supports[ name ] === true; @@ -561,6 +567,29 @@ ${ flowData.code } } + enableHardwareClipping() { + + const renderer = this.renderer; + const { localClippingCount, globalClippingCount } = this.clippingContext; + const planeCount = localClippingCount + globalClippingCount; + + if ( planeCount === 0 ) return false; + + const gl = renderer.getContext(); + const ext = gl.getExtension( 'WEBGL_clip_cull_distance' ); + + if ( ext && planeCount <= gl.getParameter( ext.MAX_CLIP_DISTANCES_WEBGL ) ) { + + this.clippingContext.hardwareClippingPlanes = planeCount; + + return true; + + } + + return false; + + } + registerTransform( varyingName, attributeNode ) { this.transforms.push( { varyingName, attributeNode } ); @@ -602,6 +631,8 @@ ${vars} ${ this.getSignature() } +${ this.hardwareClippingPlanes !== 0 ? '#extension GL_ANGLE_clip_cull_distance : enable' : '' } + // precision ${ defaultPrecisions } diff --git a/examples/jsm/renderers/webgl/utils/WebGLState.js b/examples/jsm/renderers/webgl/utils/WebGLState.js index 51a2e0d572f0fb..6597048e3127ea 100644 --- a/examples/jsm/renderers/webgl/utils/WebGLState.js +++ b/examples/jsm/renderers/webgl/utils/WebGLState.js @@ -41,6 +41,7 @@ class WebGLState { this.currentStencilZPass = null; this.currentStencilMask = null; this.currentLineWidth = null; + this.currentClippingPlanes = 0; this.currentBoundFramebuffers = {}; this.currentDrawbuffers = new WeakMap(); @@ -477,7 +478,7 @@ class WebGLState { } - setMaterial( material, frontFaceCW ) { + setMaterial( material, frontFaceCW, hardwareClippingPlanes ) { const { gl } = this; @@ -515,6 +516,31 @@ class WebGLState { ? this.enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) : this.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); + + if ( hardwareClippingPlanes > 0 ) { + + if ( this.currentClippingPlanes !== hardwareClippingPlanes ) { + + const CLIP_DISTANCE0_WEBGL = 0x3000; + + for ( let i = 0; i < 8; i ++ ) { + + if ( i < hardwareClippingPlanes ) { + + this.enable( CLIP_DISTANCE0_WEBGL + i ); + + } else { + + this.disable( CLIP_DISTANCE0_WEBGL + i ); + + } + + } + + } + + } + } setPolygonOffset( polygonOffset, factor, units ) { diff --git a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js index 37d3cefe97b104..b96ab835e8980b 100644 --- a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -594,12 +594,34 @@ ${ flowData.code } } + getClipDistance() { + + return 'hw_clip_distances'; + + } + isFlipY() { return false; } + enableHardwareClipping() { + + const { localClippingCount, globalClippingCount } = this.clippingContext; + const planeCount = localClippingCount + globalClippingCount; + + if ( planeCount > 0 && planeCount <= 8 && this.renderer.backend.hasFeature( 'clip-distances' ) ) { + + this.getBuiltin( 'clip_distances', 'hw_clip_distances', `array`, 'vertex' ); + return true; + + } + + return false; + + } + getBuiltins( shaderStage ) { const snippets = []; @@ -689,7 +711,7 @@ ${ flowData.code } snippets.push( snippet ); - snippets.push( `\nvar output : ${ name };\n\n`); + snippets.push( `\nvar output : ${ name };\n\n` ); }