8

I am trying to section my 3D models using three.js in a way similar to how Unity can: enter image description here

I have been playing around with the camera controls and of course I can adjust the near / far fields to clip in the direction of the camera, but what if I wanted to just clip in the X or Y plane? I looked at potentially adding a large transparent block that could slide on that axis and then use binary operations to merge / subtract where it intersects the object, but the tools end up creating a new mesh along the new plane rather than actually removing everything along that axis.

Do I need to be working with multiple viewports, or is there a simpler way?

WestLangley
  • 102,557
  • 10
  • 276
  • 276
Alan
  • 2,046
  • 2
  • 20
  • 43
  • 1
    Clipping is now supported in the dev branch r.76dev. See http://ci.threejs.org/api/pullrequests/8465/examples/webgl_clipping.html. – WestLangley Apr 11 '16 at 19:40
  • Amazing, thanks West, if you add that as an answer I can mark it as the solution :) – Alan Apr 11 '16 at 19:50

2 Answers2

26

Clipping can be easily done in the shader. Using some calculations you could even have a for example sphere like clipping area. Clipping at a plane orthogonal to the coordinate system is the easiest. In the vertex shader calculate the world position of the pixel:

worldPosition = modelMatrix * vec4( position, 1.0 );

In the fragment shader discard the drawing of the pixel if it is beyond your clipping limit:

if ( worldPosition.x > clippingLimitX ) {
    discard;
}

This will however leave the mesh open at the clipping edge. To close it use a stencil buffer. Decrement the stencil with a scene showing the backfaces. Then increment the stencil with a scene showing the clipped front faces. The materials used in these scenes should not be visible so disable their color and depth write:

new THREE.ShaderMaterial( { colorWrite: false, depthWrite: false, ... } );

Use the stencil to render a plane that is located at the clipping planes position. After disabling the stencil render the clipped front faces.

renderer.autoClear = false;
renderer.clear();

var gl = renderer.context;

renderer.state.setStencilTest( true );

renderer.state.setStencilFunc( gl.ALWAYS, 1, 0xff );
renderer.state.setStencilOp( gl.KEEP, gl.KEEP, gl.INCR );
renderer.render( backStencilScene, camera );

renderer.state.setStencilFunc( gl.ALWAYS, 1, 0xff );
renderer.state.setStencilOp( gl.KEEP, gl.KEEP, gl.DECR );
renderer.render( frontStencilScene, camera );

renderer.state.setStencilFunc( gl.EQUAL, 1, 0xff );
renderer.state.setStencilOp( gl.KEEP, gl.KEEP, gl.KEEP );
renderer.render( capsScene, camera );

renderer.state.setStencilTest( false );

renderer.render( scene, camera );

I made a demo showing how to clip at more than one plane at once:

http://daign.github.io/clipping-with-caps/

I didn't use the build-in three.js clipping planes because for this demo to work I have to render the stencil using a shader that determines whether a clipping plane is facing away from the camera or not, and only clip at those planes facing the camera.

daign
  • 899
  • 1
  • 10
  • 18
  • 1
    Nice. Also see [this answer](http://stackoverflow.com/questions/37090942/how-to-render-clipped-surfaces-as-solid-objects/37093210#37093210). Both approaches have problems, however, with an open-ended cylinder geometry in which the diameter of the cylinder is much greater than the cylinder's height. – WestLangley Jun 02 '16 at 23:59
  • 5
    Awesome! Would you have a suggestion on how to draw a "thick" line at the clip location. Something intermediate between the open and closed mesh. Thanks! – Nicolas Aug 09 '16 at 20:54
  • @daign thanks so much for this! Was wondering how to achieve this effect – andrevenancio Nov 08 '17 at 16:03
20

Clipping is now supported.

Here is the pattern to follow. Adapt it according to your use case.

var localPlane = new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 1 );

var globalPlane = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 1 );

renderer.clippingPlanes = [ globalPlane ];

renderer.localClippingEnabled = true;

var material = new THREE.MeshPhongMaterial( {
    clippingPlanes: [ localPlane ],
    clipShadows: true
} );

See these three.js examples:

https://threejs.org/examples/webgl_clipping.html https://threejs.org/examples/webgl_clipping_advanced.html

three.js r.85

WestLangley
  • 102,557
  • 10
  • 276
  • 276
  • Sorry I am a bit late to this party. @WestLangley, I was trying to enable clipping planes but I wasn't sure from your answer - what is local vs global clipping plane ? Should I enable both ? – Rahul Pillai Jun 05 '20 at 14:37
  • 2
    @RahulPillai Global clipping planes are per renderer. Local clipping planes are per material. See https://threejs.org/docs/index.html#api/en/materials/Material.clippingPlanes. – WestLangley Jun 05 '20 at 16:12
  • 1
    For me specifically I had moved three.js code from one project to another and have spent hours trying to find the culprit. Ultimately it's because `render.localClippingEnabled` was set to false :/ – Jacksonkr May 21 '21 at 14:53