Skip to content

Commit d7da5e0

Browse files
aardgooseaardgooseMugen87
authored
WebGPURenderer: implement ClippingGroup object (#28237)
* clipping groups Replace renderer and material clipping planes with nestable clipping groups * update to upstream * rework cache key * use right cache key * fixup * fix cache key use * clean up TSL * remove unused import * Update ToonOutlinePassNode.js Add missing `clippingContext`. * Update Renderer.js Add default parameter to renderObject(). * Update three.webgpu.js Revert changes to build files. --------- Co-authored-by: aardgoose <angus.sawyer@email.com> Co-authored-by: Michael Herzog <michael.herzog@human-interactive.org>
1 parent 60a60d1 commit d7da5e0

13 files changed

+233
-258
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
@@ -164,6 +164,7 @@ export * from './Three.Legacy.js';
164164

165165
export * from './materials/nodes/NodeMaterials.js';
166166
export { default as WebGPURenderer } from './renderers/webgpu/WebGPURenderer.js';
167+
export { default as ClippingGroup } from './renderers/common/ClippingGroup.js';
167168
export { default as Lighting } from './renderers/common/Lighting.js';
168169
export { default as BundleGroup } from './renderers/common/BundleGroup.js';
169170
export { default as QuadMesh } from './renderers/common/QuadMesh.js';

src/materials/nodes/NodeMaterial.js

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

217217
if ( builder.clippingContext === null ) return null;
218218

219-
const { globalClippingCount, localClippingCount } = builder.clippingContext;
219+
const { unionPlanes, intersectionPlanes } = builder.clippingContext;
220220

221221
let result = null;
222222

223-
if ( globalClippingCount || localClippingCount ) {
223+
if ( unionPlanes.length > 0 || intersectionPlanes.length > 0 ) {
224224

225225
const samples = builder.renderer.samples;
226226

src/nodes/accessors/ClippingNode.js

+50-39
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11

22
import Node from '../core/Node.js';
3-
import { nodeObject } from '../tsl/TSLBase.js';
3+
import { nodeObject, Fn, bool, float } from '../tsl/TSLBase.js';
44
import { positionView } from './Position.js';
5-
import { diffuseColor, property } from '../core/PropertyNode.js';
6-
import { Fn } from '../tsl/TSLBase.js';
5+
import { diffuseColor } from '../core/PropertyNode.js';
76
import { Loop } from '../utils/LoopNode.js';
87
import { smoothstep } from '../math/MathNode.js';
98
import { uniformArray } from './UniformArrayNode.js';
@@ -29,69 +28,72 @@ class ClippingNode extends Node {
2928
super.setup( builder );
3029

3130
const clippingContext = builder.clippingContext;
32-
const { localClipIntersection, localClippingCount, globalClippingCount } = clippingContext;
31+
const { intersectionPlanes, unionPlanes } = clippingContext;
3332

34-
const numClippingPlanes = globalClippingCount + localClippingCount;
35-
const numUnionClippingPlanes = localClipIntersection ? numClippingPlanes - localClippingCount : numClippingPlanes;
3633

3734
if ( this.scope === ClippingNode.ALPHA_TO_COVERAGE ) {
3835

39-
return this.setupAlphaToCoverage( clippingContext.planes, numClippingPlanes, numUnionClippingPlanes );
36+
return this.setupAlphaToCoverage( intersectionPlanes, unionPlanes );
4037

4138
} else {
4239

43-
return this.setupDefault( clippingContext.planes, numClippingPlanes, numUnionClippingPlanes );
40+
return this.setupDefault( intersectionPlanes, unionPlanes );
4441

4542
}
4643

4744
}
4845

49-
setupAlphaToCoverage( planes, numClippingPlanes, numUnionClippingPlanes ) {
46+
setupAlphaToCoverage( intersectionPlanes, unionPlanes ) {
5047

5148
return Fn( () => {
5249

53-
const clippingPlanes = uniformArray( planes );
50+
const distanceToPlane = float().toVar( 'distanceToPlane' );
51+
const distanceGradient = float().toVar( 'distanceToGradient' );
5452

55-
const distanceToPlane = property( 'float', 'distanceToPlane' );
56-
const distanceGradient = property( 'float', 'distanceToGradient' );
53+
const clipOpacity = float( 1 ).toVar( 'clipOpacity' );
5754

58-
const clipOpacity = property( 'float', 'clipOpacity' );
55+
const numUnionPlanes = unionPlanes.length;
5956

60-
clipOpacity.assign( 1 );
57+
if ( numUnionPlanes > 0 ) {
6158

62-
let plane;
59+
const clippingPlanes = uniformArray( unionPlanes );
6360

64-
Loop( numUnionClippingPlanes, ( { i } ) => {
61+
let plane;
6562

66-
plane = clippingPlanes.element( i );
63+
Loop( numUnionPlanes, ( { i } ) => {
6764

68-
distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
69-
distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
65+
plane = clippingPlanes.element( i );
66+
67+
distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
68+
distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
7069

71-
clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) );
70+
clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) );
7271

73-
clipOpacity.equal( 0.0 ).discard();
72+
} );
73+
74+
}
7475

75-
} );
76+
const numIntersectionPlanes = intersectionPlanes.length;
7677

77-
if ( numUnionClippingPlanes < numClippingPlanes ) {
78+
if ( numIntersectionPlanes > 0 ) {
7879

79-
const unionClipOpacity = property( 'float', 'unionclipOpacity' );
80+
const clippingPlanes = uniformArray( intersectionPlanes );
81+
const intersectionClipOpacity = float( 1 ).toVar( 'intersectionClipOpacity' );
8082

81-
unionClipOpacity.assign( 1 );
83+
let plane;
8284

83-
Loop( { start: numUnionClippingPlanes, end: numClippingPlanes }, ( { i } ) => {
85+
Loop( numIntersectionPlanes, ( { i } ) => {
8486

8587
plane = clippingPlanes.element( i );
8688

8789
distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) );
8890
distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) );
8991

90-
unionClipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ).oneMinus() );
92+
intersectionClipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ).oneMinus() );
9193

9294
} );
9395

94-
clipOpacity.mulAssign( unionClipOpacity.oneMinus() );
96+
clipOpacity.mulAssign( intersectionClipOpacity.oneMinus() );
9597

9698
}
9799

@@ -103,28 +105,37 @@ class ClippingNode extends Node {
103105

104106
}
105107

106-
setupDefault( planes, numClippingPlanes, numUnionClippingPlanes ) {
108+
setupDefault( intersectionPlanes, unionPlanes ) {
107109

108110
return Fn( () => {
109111

110-
const clippingPlanes = uniformArray( planes );
112+
const numUnionPlanes = unionPlanes.length;
111113

112-
let plane;
114+
if ( numUnionPlanes > 0 ) {
113115

114-
Loop( numUnionClippingPlanes, ( { i } ) => {
116+
const clippingPlanes = uniformArray( unionPlanes );
115117

116-
plane = clippingPlanes.element( i );
117-
positionView.dot( plane.xyz ).greaterThan( plane.w ).discard();
118+
let plane;
119+
120+
Loop( numUnionPlanes, ( { i } ) => {
121+
122+
plane = clippingPlanes.element( i );
123+
positionView.dot( plane.xyz ).greaterThan( plane.w ).discard();
124+
125+
} );
126+
127+
}
118128

119-
} );
129+
const numIntersectionPlanes = intersectionPlanes.length;
120130

121-
if ( numUnionClippingPlanes < numClippingPlanes ) {
131+
if ( numIntersectionPlanes > 0 ) {
122132

123-
const clipped = property( 'bool', 'clipped' );
133+
const clippingPlanes = uniformArray( intersectionPlanes );
134+
const clipped = bool( true ).toVar( 'clipped' );
124135

125-
clipped.assign( true );
136+
let plane;
126137

127-
Loop( { start: numUnionClippingPlanes, end: numClippingPlanes }, ( { i } ) => {
138+
Loop( numIntersectionPlanes, ( { i } ) => {
128139

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

src/nodes/display/ToonOutlinePassNode.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class ToonOutlinePassNode extends PassNode {
3434

3535
const currentRenderObjectFunction = renderer.getRenderObjectFunction();
3636

37-
renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode ) => {
37+
renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode, clippingContext ) => {
3838

3939
// only render outline for supported materials
4040

@@ -43,15 +43,15 @@ class ToonOutlinePassNode extends PassNode {
4343
if ( material.wireframe === false ) {
4444

4545
const outlineMaterial = this._getOutlineMaterial( material );
46-
renderer.renderObject( object, scene, camera, geometry, outlineMaterial, group, lightsNode );
46+
renderer.renderObject( object, scene, camera, geometry, outlineMaterial, group, lightsNode, clippingContext );
4747

4848
}
4949

5050
}
5151

5252
// default
5353

54-
renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode );
54+
renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext );
5555

5656
} );
5757

src/renderers/common/Background.js

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

100100
}
101101

102-
renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null );
102+
renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null, null );
103103

104104
} else {
105105

0 commit comments

Comments
 (0)