Per-Object Clipping Planes Shader in Unity3D 5

For a project of mine, I wanted to have custom clipping planes for objects, so that if an object is intersection with another, it would hide any part after the intersection.

It looks like this:

I decided to extend the Standard shader provided by Unity3D to achieve this effect.

If you do not care about the technical details, skip to the bottom!

Some Background

Warning, this post will get quite technical.

You can find the Unity3D shader sources from theΒ Unity3D Download Archive.

A clipping plane can be defined by 2 vectors, a position and a normal.

These two vectors can be used to check whether parts of shapes are in front or behind the plane. We keep the parts in front, and hide the parts behind.

Wolfram Mathworld describes the algorithm to get the distance of a point to a plane. Here it is in code:

In order to find which parts of the objects are to be clipped, we need to extract the world coordinate of all points to be rendered. This is already done for most of the standard shader’s vertex programs:

In the fragment programs, we can then use the clip function with the distance to the plane as the parameter. If the clip function is called with any number less than zero, it will discard the current pixel. This is perfect, because if the distance to the plane is less than zero, a point is behind the plane.

If you have more planes, you can call clip with float2, float3, float4 parameters, or call clip multiple times. For example:

All we need to do now is change all passes of the Standard shader and modify the vertex and fragment programs to call this function.

We will use Unity3D’s wonderful shader program variants feature for this, so that if we do not want any clipping planes it will not cause any performance hits as the code will just be eliminated in that case. The CLIP_TWO and CLIP_THREE definitions are produced by the shader variant system, because in each pass we will have this directive :

It basically tells Unity3D’s shader compiler to generate four variants of the shader, a variant with no clipping planes, a variant with one clipping plane, another variant with two clipping planes, and the last one with three. We can choose how many clipping planes we want to use, by for example enabling the CLIP_ONE keyword, or the CLIP_TWO keyword. The method to enable keywords is: Material.EnableKeyword.

Let’s go!

Create a new shader called StandardClippable.shader, and place it in your project’s Assets/Shaders directory. Copy the contents of the Standard.shader file in the builtin_shaders zip, which can be found inside the DefaultResourcesExtra directory. Paste into the StandardClippable.shader. Change the first line to be:

Shader "Custom/StandardClippable".

Add the properties for the plane positions and normals, so that the properties block will look like this:

We just added the lines after 41.

We will create a helper .cginc file named “plane_clipping.cginc”. Here is its contents:

The comments in the above file should explain what it is doing.

The next step is to use the PLANE_CLIP macro in the fragment programs of all passes.

The First Pass

Let’s look at the FORWARD pass for example:

The lines which are important are 28, 29 and 31. This pass uses the vertForwardBase vertex program, and the fragForwardBase fragment program. These programs are defined in the UnityStandardCore.cginc file.

So, find the UnityStandardCore.cginc file in the default shaders zip. Make a copy of it, and save it as standard_clipped.cginc next to our StandardClippable.shader file.

Change all references of “UnityStandardCore.cginc” to be “standard_clipped.cginc” instead.

Also, add the line
#pragma multi_compile __ CLIP_ONE CLIP_TWO CLIP_THREE
just above the include lines. The forward pass should now look like this:

And your Shaders folder should now have three files:

  • StandardClippable.shader
  • plane_clipping.cginc and
  • standard_clipped.cginc

Let’s open the standard_clipped.cginc file. Add this line to the top of the file:

#include "plane_clipping.cginc"

Place it just below the include for AutoLight.cginc. This now allows us to use the functions and macros defined in that file.

We will be editing the vertex program first. Here it is as copied from the file:

The vertex program will need to pass the world position to the fragment program. It currently does so only if UNITY_SPECCUBE_BOX_PROJECTION is defined (relevant lines in above snippet: 12 and 23).

Change the lines
#if UNITY_SPECCUBE_BOX_PROJECTION
to be:
#if UNITY_SPECCUBE_BOX_PROJECTION || PLANE_CLIPPING_ENABLED
There should be one inside the struct definition just above the function, and one within the function. This way, the posWorld vector will be passed onto the fragment shader to be used by plane clipping.

The next step is the fragment program:

This uses the FRAGMENT_SETUP macro:

Which uses the IN_WORLDPOS macro in order to get the world position if necessary.

The world position is acquired only if UNITY_SPECCUBE_BOX_PROJECTION (similar to above) is defined, so change the line
#if UNITY_SPECCUBE_BOX_PROJECTION
to be:
#if UNITY_SPECCUBE_BOX_PROJECTION || PLANE_CLIPPING_ENABLED

The lines up to FRAGMENT_SETUP should now look like:

And finally, let’s add the plane clipping to the fragment shader. Place this line
PLANE_CLIP(s.posWorld)
just below
FRAGMENT_SETUP(s)

Your fragment shader code should now look like this:

This fixes the FORWARD pass!

Other passes

The next pass is the FORWARD_ADD.

Let’s make the vertex shader pass the world position to the fragment shader:

Add these lines before the closing brace of the VertexOutputForwardAdd struct:

We use TEXCOORD9 because 8 was used just above it.

And in the vertForwardAdd function, add the lines

Just after this line:
float4 posWorld = mul(_Object2World, v.vertex);

The fragment shader uses the FRAGMENT_SETUP_FWDADD macro, which is defined just below FRAGMENT_SETUP.

Above the FRAGMENT_SETUP_FWDADD macro, add these lines:
#if PLANE_CLIPPING_ENABLED
#define IN_WORLDPOS_FWDADD(i) i.posWorld
#else
#define IN_WORLDPOS_FWDADD(i) half3(0,0,0)
#endif

And use the newly created IN_WORLDPOS_FWDADD macro instead of the half3(0,0,0) as the last parameter for FragmentSetup. Here’s how the relevant lines should look:

And finally, you can now call the PLANE_CLIP(s.posWorld) macro right after FRAGMENT_SETUP_FWDADD(s) inside fragForwardAdd.

Here’s the code for the VertexOutputForwardAdd struct, vertForwardAdd function and fragForwardAdd function altogether:

And here’s what the pass definition in StandardClippable.shader should look like:

Shadow pass

The shadow pass uses another file, UnityStandardShadow.cginc. Make a copy of this file and save it as standard_shadow_clipped.cginc. In the shadow pass definition, include the standard_shadow_clipped.cginc instead of UnityStandardShadow.cginc, and don’t forget the #pragma declarations!

Inside standard_shadow_clipped.cginc, include plane_clipping.cginc as usual.

In some conditions, the shadow vertex shader does not use an output struct. We want to ensure that we have the output struct so that we can pass the world position. around line 27, change the code so that it looks like this:

Inside the VertexOutputShadowCaster struct (around line 50), add the posWorld parameter:

And finally, just after it, here’s the modified vertShadowCaster and fragShadowCaster functions:

Guess what we added πŸ˜‰

Deferred pass

The deferred pass is very similar to the forward pass. Try to do it yourself πŸ™‚ If you have any trouble, write a comment below, and I will help you!

Lod 150 passes

You just need to use the correct include files and the #pragma declarations in these passes.

An example script

Here’s an example that shows how to use the EnableKeyword method correctly and define the clipping planes for the shader.

 

The Result!

You can find all the code on Github, in Unity3D-Plane-Clipping project I created.

And here’s the unitypackage file.

40 thoughts on “Per-Object Clipping Planes Shader in Unity3D 5

  1. Hi,

    I’m total noob with shaders.
    thank you very much for sharing this. However, is there a way to adapt this to a very cheap and simple legacy vertexlit shader ?

    1. I can’t think why it shouldn’t work πŸ™‚ when I have some time I can take a look.

      1. Hi,

        don’t waste time with it ! I’ve managed to adapt it, thought I’m not very sure of what I did…

  2. Great work. Thanks for sharing.
    Would it be much work to adapt it to specular setup?
    The clipping seems not complete with specular shader and I guess the standard.clipping.cginc file requires some more editions for the specular setup, could you give a hint?

    1. Hi again, just took a look at it today, looks like there are some problems with the latest version of Unity and it doesn’t work very well, will do more tests and try to get it fixed.

    2. Hi Tomasz, I have updated the GitHub project to show how I have added specular setup support πŸ™‚ Check the latest few commits!

      1. Thanks a lot for the update. I have checked this and it works very well. I have duplicated the Custom/StandardClippable shader and changed it to specular and it also works very well.
        I also works on WebGL and Android because I have tried.

  3. Great thanks.
    The structure of included files has changed in recent versions.
    Now in place of UnityStandardCore.cgi – you have UnityStandardCoreForward.cgi, where the workflow splits between UnityStandardCore or UnityStandardCoreForwardSimple depending on #UNITY_STANDARD_SIMPLE keyword.
    This implies a simple update to your example files which I already have worked out, but I didn’t manage to find out how to support specular setup.

    1. Simple answer: try to pass the world position and call clip. Long answer: I’ll be able to give it another try soon

  4. Is there a way to possibly achieve this but with the terrain. I know it doesn’t have a MeshRenderer which right now seems to be a problem. Can you point me in the direction of any possible modifications I could make?

    1. If you’re able to modify the shader or apply a new one all it needs is to pass the world position and call clip πŸ™‚

  5. Hi,
    First of all, thanks for sharing this project, I’ve learned a lot from it.
    I have a couple of questions: is it possible to “fill”/cap the cut surface of the meshes using only shaders? If so, where do I begin?

    1. Yes, I believe so. However I’m not sure if it can be done through “only shader”… I’d start by googling for things like cap shader, clip fill shader, etc

  6. I don’t have too much experience with shaders in Unity, but I’ve worked with a few such projects. I was attempting to add a pass that renders the backfaces as a simple unlit color. I can get the coloring to work, but I the clipping isn’t working for this pass. Rather than posting my code, perhaps you’d have suggestions for doing this.

    1. Hi, sorry for the late reply. I’m afraid I am not very sure on how to approach that.

  7. hi , ty for your great work πŸ™‚
    i just want to know , is there a way to i make this shader transparent ?

  8. Hey,

    Thank you so much for putting this valuable resource online.

    I’ve been trying to figure out – is there a way to make the plane an intractable/movable object, and to clip through multiple meshes at the same time during a game? I appreciate your help.

    1. Yes, you can set these meshes with the same material and apply the material property block to them every time the clip object moves πŸ™‚

    1. More basic, I’m trying to clip an image that has an alpha background, which uses the default Unlit/Transparent Cutout shader. I can use either the Cutout or StandardClippable shaders, but when I select the Cutout shader, obviously it doesn’t provide clipping.

  9. Hey, could you update this for. Unity5.5?
    I have tried to do it myself, I have even managed to get it working for baked lighting, but failed for realtime. I guess this should be a matter of putting some more clip() or discard commands, but I do not know where.

  10. Thank you so much! I only used a bit of it to make a laser cut shader effect, but your explanation was really simple (as far as shaders go) and effective!

  11. This works great on Windows, but when I try to build for Android I get this error:

    Shader error in ‘Custom/StandardClippable’: invalid subscript ‘boxMax’ at Assets/PlaneClipping/Shaders/standard_clipped.cginc(250) (on d3d11)

    Compiling Vertex program with DIRECTIONAL
    Platform defines: UNITY_ENABLE_REFLECTION_BUFFERS UNITY_PBS_USE_BRDF1 UNITY_SPECCUBE_BOX_PROJECTION UNITY_SPECCUBE_BLENDING SHADER_API_DESKTOP UNITY_COLORSPACE_GAMMA

    I’m using Unity 5.5.0p4
    Any ideas?

    Thanks!

    1. Hi, unfortunately the shaders have not really been tested for Android, and additionally there are more problems that needs to be resolved with 5.5. I will be taking a look today on getting them working on 5.5 πŸ™‚

      1. Seems to me that this component doesn’t work with any object that has a material! You look to replace the material/texture with your own that you then clip. Is it true that this only works with objects without a material/texture applied? If so, what is the use case? Even your animated images show only base objects being clipped.

        1. Yes this post shows how to add clipping support to the standard shader.

          For any other material you would need to implement the clipping method in its pixel shader. If you link a material as an example I could show how you can make it “clippable” as well πŸ™‚

  12. Amazing work!
    Is there anyway to set transparency(alpha channel) for the material?
    I’ve been trying to find out how to do so, but couldn’t figure it out.
    Thanks.

  13. You don’t have to calculate the distance… All you have to do is the dot product between the vector coming from the plane’s center to the fragment and the normal vector, if the result is negative, then it is behind the plane.

Leave a Reply

Your email address will not be published. Required fields are marked *