Skip to content

Commit 5ea08b1

Browse files
author
aardgoose
committed
clipping groups
Replace renderer and material clipping planes with nestable clipping groups
1 parent a60450e commit 5ea08b1

11 files changed

+215
-223
lines changed

examples/webgpu_clipping.html

+37-35
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,23 @@
7474
dirLight.shadow.mapSize.height = 1024;
7575
scene.add( dirLight );
7676

77-
// ***** Clipping planes: *****
77+
// Clipping planes
7878

79-
const localPlane = new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0.8 );
80-
const localPlane2 = new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0.1 );
8179
const globalPlane = new THREE.Plane( new THREE.Vector3( - 1, 0, 0 ), 0.1 );
80+
const localPlane1 = new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0.8 );
81+
const localPlane2 = new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0.1 );
82+
83+
// Clipping Groups
84+
85+
const globalClippingGroup = new THREE.ClippingGroup();
86+
globalClippingGroup.clippingPlanes = [ globalPlane ];
87+
88+
const knotClippingGroup = new THREE.ClippingGroup();
89+
knotClippingGroup.clippingPlanes = [ localPlane1, localPlane2 ];
90+
knotClippingGroup.clipIntersection = true;
91+
92+
scene.add( globalClippingGroup );
93+
globalClippingGroup.add( knotClippingGroup );
8294

8395
// Geometry
8496

@@ -88,27 +100,23 @@
88100
side: THREE.DoubleSide,
89101

90102
// ***** Clipping setup (material): *****
91-
clippingPlanes: [ localPlane, localPlane2 ],
92-
clipShadows: true,
93-
alphaToCoverage: true,
94-
clipIntersection: true
95-
103+
alphaToCoverage: true
96104
} );
97105

98106
const geometry = new THREE.TorusKnotGeometry( 0.4, 0.08, 95, 20 );
99107

100108
object = new THREE.Mesh( geometry, material );
101109
object.castShadow = true;
102-
scene.add( object );
110+
knotClippingGroup.add( object );
103111

104112
const ground = new THREE.Mesh(
105113
new THREE.PlaneGeometry( 9, 9, 1, 1 ),
106-
new THREE.MeshPhongNodeMaterial( { color: 0xa0adaf, shininess: 150 } )
114+
new THREE.MeshPhongNodeMaterial( { color: 0xa0adaf, shininess: 150, alphaToCoverage: true } )
107115
);
108116

109117
ground.rotation.x = - Math.PI / 2; // rotates X/Y to X/Z
110118
ground.receiveShadow = true;
111-
scene.add( ground );
119+
globalClippingGroup.add( ground );
112120

113121
// Stats
114122

@@ -125,14 +133,8 @@
125133
window.addEventListener( 'resize', onWindowResize );
126134
document.body.appendChild( renderer.domElement );
127135

128-
// ***** Clipping setup (renderer): *****
129-
const globalPlanes = [ globalPlane ];
130-
const Empty = Object.freeze( [] );
131-
132-
renderer.clippingPlanes = Empty; // GUI sets it to globalPlanes
133-
renderer.localClippingEnabled = true;
134-
135136
// Controls
137+
136138
const controls = new OrbitControls( camera, renderer.domElement );
137139
controls.target.set( 0, 1, 0 );
138140
controls.update();
@@ -143,67 +145,67 @@
143145
props = {
144146
alphaToCoverage: true,
145147
},
146-
folderLocal = gui.addFolder( 'Local Clipping' ),
147-
propsLocal = {
148+
folderKnot = gui.addFolder( 'Knot Clipping Group' ),
149+
propsKnot = {
148150

149151
get 'Enabled'() {
150152

151-
return renderer.localClippingEnabled;
153+
return knotClippingGroup.enabled;
152154

153155
},
154156
set 'Enabled'( v ) {
155157

156-
renderer.localClippingEnabled = v;
158+
knotClippingGroup.enabled = v;
157159

158160
},
159161

160162
get 'Shadows'() {
161163

162-
return material.clipShadows;
164+
return knotClippingGroup.clipShadows;
163165

164166
},
165167
set 'Shadows'( v ) {
166168

167-
material.clipShadows = v;
169+
knotClippingGroup.clipShadows = v;
168170

169171
},
170172

171173
get 'Intersection'() {
172174

173-
return material.clipIntersection;
175+
return knotClippingGroup.clipIntersection;
174176

175177
},
176178

177179
set 'Intersection'( v ) {
178180

179-
material.clipIntersection = v;
181+
knotClippingGroup.clipIntersection = v;
180182

181183
},
182184

183185
get 'Plane'() {
184186

185-
return localPlane.constant;
187+
return localPlane1.constant;
186188

187189
},
188190
set 'Plane'( v ) {
189191

190-
localPlane.constant = v;
192+
localPlane1.constant = v;
191193

192194
}
193195

194196
},
195197

196-
folderGlobal = gui.addFolder( 'Global Clipping' ),
198+
folderGlobal = gui.addFolder( 'Global Clipping Group' ),
197199
propsGlobal = {
198200

199201
get 'Enabled'() {
200202

201-
return renderer.clippingPlanes !== Empty;
203+
return globalClippingGroup.enabled;
202204

203205
},
204206
set 'Enabled'( v ) {
205207

206-
renderer.clippingPlanes = v ? globalPlanes : Empty;
208+
globalClippingGroup.enabled = v;
207209

208210
},
209211

@@ -230,10 +232,10 @@
230232

231233
} );
232234

233-
folderLocal.add( propsLocal, 'Enabled' );
234-
folderLocal.add( propsLocal, 'Shadows' );
235-
folderLocal.add( propsLocal, 'Intersection' );
236-
folderLocal.add( propsLocal, 'Plane', 0.3, 1.25 );
235+
folderKnot.add( propsKnot, 'Enabled' );
236+
folderKnot.add( propsKnot, 'Shadows' );
237+
folderKnot.add( propsKnot, 'Intersection' );
238+
folderKnot.add( propsKnot, 'Plane', 0.3, 1.25 );
237239

238240
folderGlobal.add( propsGlobal, 'Enabled' );
239241
folderGlobal.add( propsGlobal, 'Plane', - 0.4, 3 );

src/Three.WebGPU.js

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ export * from './constants.js';
161161
export * from './Three.Legacy.js';
162162

163163
export { default as WebGPURenderer } from './renderers/webgpu/WebGPURenderer.js';
164+
export { default as ClippingGroup } from './renderers/common/ClippingGroup.js';
164165
export { default as QuadMesh } from './renderers/common/QuadMesh.js';
165166
export { default as PMREMGenerator } from './renderers/common/extras/PMREMGenerator.js';
166167
export { default as PostProcessing } from './renderers/common/PostProcessing.js';

src/nodes/accessors/ClippingNode.js

+47-27
Original file line numberDiff line numberDiff line change
@@ -23,58 +23,67 @@ class ClippingNode extends Node {
2323
super.setup( builder );
2424

2525
const clippingContext = builder.clippingContext;
26-
const { localClipIntersection, localClippingCount, globalClippingCount } = clippingContext;
26+
const { intersectionPlanes, unionPlanes } = clippingContext;
2727

28-
const numClippingPlanes = globalClippingCount + localClippingCount;
29-
const numUnionClippingPlanes = localClipIntersection ? numClippingPlanes - localClippingCount : numClippingPlanes;
3028

3129
if ( this.scope === ClippingNode.ALPHA_TO_COVERAGE ) {
3230

33-
return this.setupAlphaToCoverage( clippingContext.planes, numClippingPlanes, numUnionClippingPlanes );
31+
return this.setupAlphaToCoverage( intersectionPlanes, unionPlanes );
3432

3533
} else {
3634

37-
return this.setupDefault( clippingContext.planes, numClippingPlanes, numUnionClippingPlanes );
35+
return this.setupDefault( intersectionPlanes, unionPlanes );
3836

3937
}
4038

4139
}
4240

43-
setupAlphaToCoverage( planes, numClippingPlanes, numUnionClippingPlanes ) {
41+
setupAlphaToCoverage( intersectionPlanes, unionPlanes ) {
4442

4543
return tslFn( () => {
4644

47-
const clippingPlanes = uniforms( planes );
48-
4945
const distanceToPlane = property( 'float', 'distanceToPlane' );
5046
const distanceGradient = property( 'float', 'distanceToGradient' );
5147

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

5450
clipOpacity.assign( 1 );
5551

56-
let plane;
52+
const numUnionPlanes = unionPlanes.length;
53+
54+
if ( numUnionPlanes > 0 ) {
55+
56+
const clippingPlanes = uniforms( unionPlanes );
57+
58+
let plane;
59+
60+
loop( numUnionPlanes, ( { i } ) => {
5761

58-
loop( numUnionClippingPlanes, ( { i } ) => {
62+
plane = clippingPlanes.element( i );
5963

60-
plane = clippingPlanes.element( i );
64+
distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
65+
distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
6166

62-
distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
63-
distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
67+
clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) );
6468

65-
clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) );
69+
clipOpacity.equal( 0.0 ).discard();
70+
71+
} );
6672

67-
clipOpacity.equal( 0.0 ).discard();
73+
}
6874

69-
} );
75+
const numIntersectionPlanes = intersectionPlanes.length;
7076

71-
if ( numUnionClippingPlanes < numClippingPlanes ) {
77+
if ( numIntersectionPlanes > 0 ) {
7278

79+
const clippingPlanes = uniforms( intersectionPlanes );
7380
const unionClipOpacity = property( 'float', 'unionclipOpacity' );
7481

82+
let plane;
83+
7584
unionClipOpacity.assign( 1 );
7685

77-
loop( { start: numUnionClippingPlanes, end: numClippingPlanes }, ( { i } ) => {
86+
loop( numIntersectionPlanes, ( { i } ) => {
7887

7988
plane = clippingPlanes.element( i );
8089

@@ -97,28 +106,39 @@ class ClippingNode extends Node {
97106

98107
}
99108

100-
setupDefault( planes, numClippingPlanes, numUnionClippingPlanes ) {
109+
setupDefault( intersectionPlanes, unionPlanes ) {
101110

102111
return tslFn( () => {
103112

104-
const clippingPlanes = uniforms( planes );
113+
const numUnionPlanes = unionPlanes.length;
114+
115+
if ( numUnionPlanes > 0 ) {
116+
117+
const clippingPlanes = uniforms( unionPlanes );
118+
119+
let plane;
105120

106-
let plane;
121+
loop( numUnionPlanes, ( { i } ) => {
107122

108-
loop( numUnionClippingPlanes, ( { i } ) => {
123+
plane = clippingPlanes.element( i );
124+
positionView.dot( plane.xyz ).greaterThan( plane.w ).discard();
109125

110-
plane = clippingPlanes.element( i );
111-
positionView.dot( plane.xyz ).greaterThan( plane.w ).discard();
126+
} );
112127

113-
} );
128+
}
114129

115-
if ( numUnionClippingPlanes < numClippingPlanes ) {
130+
const numIntersectionPlanes = intersectionPlanes.length;
116131

132+
if ( numIntersectionPlanes > 0 ) {
133+
134+
const clippingPlanes = uniforms( intersectionPlanes );
117135
const clipped = property( 'bool', 'clipped' );
118136

137+
let plane;
138+
119139
clipped.assign( true );
120140

121-
loop( { start: numUnionClippingPlanes, end: numClippingPlanes }, ( { i } ) => {
141+
loop( numIntersectionPlanes, ( { i } ) => {
122142

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

src/nodes/materials/NodeMaterial.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,11 @@ class NodeMaterial extends Material {
177177

178178
if ( builder.clippingContext === null ) return null;
179179

180-
const { globalClippingCount, localClippingCount } = builder.clippingContext;
180+
const { unionPlanes, intersectionPlanes } = builder.clippingContext;
181181

182182
let result = null;
183183

184-
if ( globalClippingCount || localClippingCount ) {
184+
if ( unionPlanes.length > 0 || intersectionPlanes.length > 0 ) {
185185

186186
if ( this.alphaToCoverage ) {
187187

src/renderers/common/Background.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class Background extends DataMap {
9595

9696
}
9797

98-
renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null );
98+
renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null, null );
9999

100100
} else {
101101

0 commit comments

Comments
 (0)