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:
//http://mathworld.wolfram.com/Point-PlaneDistance.html | |
float distanceToPlane(float3 planePosition, float3 planeNormal, float3 pointInWorld) | |
{ | |
//w = vector from plane to point | |
float3 w = - ( planePosition - pointInWorld ); | |
return ( | |
planeNormal.x * w.x + | |
planeNormal.y * w.y + | |
planeNormal.z * w.z | |
) / sqrt ( | |
planeNormal.x * planeNormal.x + | |
planeNormal.y * planeNormal.y + | |
planeNormal.z * planeNormal.z | |
); | |
} |
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:
float4 posWorld = mul(_Object2World, v.vertex); |
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.
float4 _planePos; | |
float4 _planeNorm; | |
void PlaneClip(float3 posWorld) { | |
clip(distanceToPlane(_planePos.xyz, _planeNorm.xyz, posWorld)); | |
} |
If you have more planes, you can call clip with float2, float3, float4 parameters, or call clip multiple times. For example:
float4 _planePos; | |
float4 _planeNorm; | |
#if (CLIP_TWO || CLIP_THREE) | |
float4 _planePos2; | |
float4 _planeNorm2; | |
#endif | |
#if (CLIP_THREE) | |
float4 _planePos3; | |
float4 _planeNorm3; | |
#endif | |
void PlaneClip(float3 posWorld) { | |
#if CLIP_THREE | |
clip(float3( | |
distanceToPlane(_planePos.xyz, _planeNorm.xyz, posWorld), | |
distanceToPlane(_planePos2.xyz, _planeNorm2.xyz, posWorld), | |
distanceToPlane(_planePos3.xyz, _planeNorm3.xyz, posWorld) | |
)); | |
#else //CLIP_THREE | |
#if CLIP_TWO | |
clip(float2( | |
distanceToPlane(_planePos.xyz, _planeNorm.xyz, posWorld), | |
distanceToPlane(_planePos2.xyz, _planeNorm2.xyz, posWorld) | |
)); | |
#else //CLIP_TWO | |
clip(distanceToPlane(_planePos.xyz, _planeNorm.xyz, posWorld)); | |
#endif //CLIP_TWO | |
#endif //CLIP_THREE | |
} |
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 :
#pragma multi_compile __ CLIP_ONE CLIP_TWO CLIP_THREE |
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:
Properties | |
{ | |
_Color("Color", Color) = (1,1,1,1) | |
_MainTex("Albedo", 2D) = "white" {} | |
_Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5 | |
_Glossiness("Smoothness", Range(0.0, 1.0)) = 0.5 | |
[Gamma] _Metallic("Metallic", Range(0.0, 1.0)) = 0.0 | |
_MetallicGlossMap("Metallic", 2D) = "white" {} | |
_BumpScale("Scale", Float) = 1.0 | |
_BumpMap("Normal Map", 2D) = "bump" {} | |
_Parallax ("Height Scale", Range (0.005, 0.08)) = 0.02 | |
_ParallaxMap ("Height Map", 2D) = "black" {} | |
_OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0 | |
_OcclusionMap("Occlusion", 2D) = "white" {} | |
_EmissionColor("Color", Color) = (0,0,0) | |
_EmissionMap("Emission", 2D) = "white" {} | |
_DetailMask("Detail Mask", 2D) = "white" {} | |
_DetailAlbedoMap("Detail Albedo x2", 2D) = "grey" {} | |
_DetailNormalMapScale("Scale", Float) = 1.0 | |
_DetailNormalMap("Normal Map", 2D) = "bump" {} | |
[Enum(UV0,0,UV1,1)] _UVSec ("UV Set for secondary textures", Float) = 0 | |
// UI-only data | |
[HideInInspector] _EmissionScaleUI("Scale", Float) = 0.0 | |
[HideInInspector] _EmissionColorUI("Color", Color) = (1,1,1) | |
// Blending state | |
[HideInInspector] _Mode ("__mode", Float) = 0.0 | |
[HideInInspector] _SrcBlend ("__src", Float) = 1.0 | |
[HideInInspector] _DstBlend ("__dst", Float) = 0.0 | |
[HideInInspector] _ZWrite ("__zw", Float) = 1.0 | |
_planePos ("Clipping Plane Position", Vector) = ( 0, 0, 0, 1 ) | |
_planePos2 ("Clipping Plane Position 2", Vector) = ( 0, 0, 0, 1 ) | |
_planePos3 ("Clipping Plane Position 3", Vector) = ( 0, 0, 0, 1 ) | |
_planeNorm ("Clipping Plane Normal", Vector) = ( 0, 1, 0, 1 ) | |
_planeNorm2 ("Clipping Plane Normal 2", Vector) = ( 0, 1, 0, 1 ) | |
_planeNorm3 ("Clipping Plane Normal 3", Vector) = ( 0, 1, 0, 1 ) | |
} |
We just added the lines after 41.
We will create a helper .cginc file named "plane_clipping.cginc". Here is its contents:
#ifndef PLANE_CLIPPING_INCLUDED | |
#define PLANE_CLIPPING_INCLUDED | |
//Plane clipping definitions. Uses three planes for clipping, but this can be increased if necessary. | |
#if CLIP_ONE || CLIP_TWO || CLIP_THREE | |
//If we have 1, 2 or 3 clipping planes, PLANE_CLIPPING_ENABLED will be defined. | |
//This makes it easier to check if this feature is available or not. | |
#define PLANE_CLIPPING_ENABLED 1 | |
//http://mathworld.wolfram.com/Point-PlaneDistance.html | |
float distanceToPlane(float3 planePosition, float3 planeNormal, float3 pointInWorld) | |
{ | |
//w = vector from plane to point | |
float3 w = - ( planePosition - pointInWorld ); | |
float res = ( planeNormal.x * w.x + | |
planeNormal.y * w.y + | |
planeNormal.z * w.z ) | |
/ sqrt( planeNormal.x * planeNormal.x + | |
planeNormal.y * planeNormal.y + | |
planeNormal.z * planeNormal.z ); | |
return res; | |
} | |
//we will have at least one plane. | |
float4 _planePos; | |
float4 _planeNorm; | |
//at least two planes. | |
#if (CLIP_TWO || CLIP_THREE) | |
float4 _planePos2; | |
float4 _planeNorm2; | |
#endif | |
//at least three planes. | |
#if (CLIP_THREE) | |
float4 _planePos3; | |
float4 _planeNorm3; | |
#endif | |
//discard drawing of a point in the world if it is behind any one of the planes. | |
void PlaneClip(float3 posWorld) { | |
#if CLIP_THREE | |
clip(float3( | |
distanceToPlane(_planePos.xyz, _planeNorm.xyz, posWorld), | |
distanceToPlane(_planePos2.xyz, _planeNorm2.xyz, posWorld), | |
distanceToPlane(_planePos3.xyz, _planeNorm3.xyz, posWorld) | |
)); | |
#else //CLIP_THREE | |
#if CLIP_TWO | |
clip(float2( | |
distanceToPlane(_planePos.xyz, _planeNorm.xyz, posWorld), | |
distanceToPlane(_planePos2.xyz, _planeNorm2.xyz, posWorld) | |
)); | |
#else //CLIP_TWO | |
clip(distanceToPlane(_planePos.xyz, _planeNorm.xyz, posWorld)); | |
#endif //CLIP_TWO | |
#endif //CLIP_THREE | |
} | |
//preprocessor macro that will produce an empty block if no clipping planes are used. | |
#define PLANE_CLIP(posWorld) PlaneClip(posWorld); | |
#else | |
//empty definition | |
#define PLANE_CLIP(s) | |
#endif | |
#endif // PLANE_CLIPPING_INCLUDED |
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:
// ------------------------------------------------------------------ | |
// Base forward pass (directional light, emission, lightmaps, ...) | |
Pass | |
{ | |
Name "FORWARD" | |
Tags { "LightMode" = "ForwardBase" } | |
Blend [_SrcBlend] [_DstBlend] | |
ZWrite [_ZWrite] | |
CGPROGRAM | |
#pragma target 3.0 | |
// TEMPORARY: GLES2.0 temporarily disabled to prevent errors spam on devices without textureCubeLodEXT | |
#pragma exclude_renderers gles | |
// ------------------------------------- | |
#pragma shader_feature _NORMALMAP | |
#pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON | |
#pragma shader_feature _EMISSION | |
#pragma shader_feature _METALLICGLOSSMAP | |
#pragma shader_feature ___ _DETAIL_MULX2 | |
#pragma shader_feature _PARALLAXMAP | |
#pragma multi_compile_fwdbase | |
#pragma multi_compile_fog | |
#pragma vertex vertForwardBase | |
#pragma fragment fragForwardBase | |
#include "UnityStandardCore.cginc" | |
ENDCG | |
} |
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:// ------------------------------------------------------------------ | |
// Base forward pass (directional light, emission, lightmaps, ...) | |
Pass | |
{ | |
Name "FORWARD" | |
Tags { "LightMode" = "ForwardBase" } | |
Blend [_SrcBlend] [_DstBlend] | |
ZWrite [_ZWrite] | |
CGPROGRAM | |
#pragma target 3.0 | |
// TEMPORARY: GLES2.0 temporarily disabled to prevent errors spam on devices without textureCubeLodEXT | |
#pragma exclude_renderers gles | |
// ------------------------------------- | |
#pragma shader_feature _NORMALMAP | |
#pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON | |
#pragma shader_feature _EMISSION | |
#pragma shader_feature _METALLICGLOSSMAP | |
#pragma shader_feature ___ _DETAIL_MULX2 | |
#pragma shader_feature _PARALLAXMAP | |
#pragma multi_compile_fwdbase | |
#pragma multi_compile_fog | |
#pragma vertex vertForwardBase | |
#pragma fragment fragForwardBase | |
#pragma multi_compile __ CLIP_ONE CLIP_TWO CLIP_THREE | |
#include "standard_clipped.cginc" | |
ENDCG | |
} |
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:
struct VertexOutputForwardBase | |
{ | |
float4 pos : SV_POSITION; | |
float4 tex : TEXCOORD0; | |
half3 eyeVec : TEXCOORD1; | |
half4 tangentToWorldAndParallax[3] : TEXCOORD2; // [3x3:tangentToWorld | 1x3:viewDirForParallax] | |
half4 ambientOrLightmapUV : TEXCOORD5; // SH or Lightmap UV | |
SHADOW_COORDS(6) | |
UNITY_FOG_COORDS(7) | |
// next ones would not fit into SM2.0 limits, but they are always for SM3.0+ | |
#if UNITY_SPECCUBE_BOX_PROJECTION | |
float3 posWorld : TEXCOORD8; | |
#endif | |
}; | |
VertexOutputForwardBase vertForwardBase (VertexInput v) | |
{ | |
VertexOutputForwardBase o; | |
UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o); | |
float4 posWorld = mul(_Object2World, v.vertex); | |
#if UNITY_SPECCUBE_BOX_PROJECTION | |
o.posWorld = posWorld.xyz; | |
#endif | |
o.pos = mul(UNITY_MATRIX_MVP, v.vertex); | |
o.tex = TexCoords(v); | |
o.eyeVec = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos); | |
float3 normalWorld = UnityObjectToWorldNormal(v.normal); | |
#ifdef _TANGENT_TO_WORLD | |
float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w); | |
float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w); | |
o.tangentToWorldAndParallax[0].xyz = tangentToWorld[0]; | |
o.tangentToWorldAndParallax[1].xyz = tangentToWorld[1]; | |
o.tangentToWorldAndParallax[2].xyz = tangentToWorld[2]; | |
#else | |
o.tangentToWorldAndParallax[0].xyz = 0; | |
o.tangentToWorldAndParallax[1].xyz = 0; | |
o.tangentToWorldAndParallax[2].xyz = normalWorld; | |
#endif | |
//We need this for shadow receving | |
TRANSFER_SHADOW(o); | |
// Static lightmaps | |
#ifndef LIGHTMAP_OFF | |
o.ambientOrLightmapUV.xy = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw; | |
o.ambientOrLightmapUV.zw = 0; | |
// Sample light probe for Dynamic objects only (no static or dynamic lightmaps) | |
#elif UNITY_SHOULD_SAMPLE_SH | |
#if UNITY_SAMPLE_FULL_SH_PER_PIXEL | |
o.ambientOrLightmapUV.rgb = 0; | |
#elif (SHADER_TARGET < 30) | |
o.ambientOrLightmapUV.rgb = ShadeSH9(half4(normalWorld, 1.0)); | |
#else | |
// Optimization: L2 per-vertex, L0..L1 per-pixel | |
o.ambientOrLightmapUV.rgb = ShadeSH3Order(half4(normalWorld, 1.0)); | |
#endif | |
// Add approximated illumination from non-important point lights | |
#ifdef VERTEXLIGHT_ON | |
o.ambientOrLightmapUV.rgb += Shade4PointLights ( | |
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, | |
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb, | |
unity_4LightAtten0, posWorld, normalWorld); | |
#endif | |
#endif | |
#ifdef DYNAMICLIGHTMAP_ON | |
o.ambientOrLightmapUV.zw = v.uv2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw; | |
#endif | |
#ifdef _PARALLAXMAP | |
TANGENT_SPACE_ROTATION; | |
half3 viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex)); | |
o.tangentToWorldAndParallax[0].w = viewDirForParallax.x; | |
o.tangentToWorldAndParallax[1].w = viewDirForParallax.y; | |
o.tangentToWorldAndParallax[2].w = viewDirForParallax.z; | |
#endif | |
UNITY_TRANSFER_FOG(o,o.pos); | |
return o; | |
} |
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:
half4 fragForwardBase (VertexOutputForwardBase i) : SV_Target | |
{ | |
FRAGMENT_SETUP(s) | |
UnityLight mainLight = MainLight (s.normalWorld); | |
half atten = SHADOW_ATTENUATION(i); | |
half occlusion = Occlusion(i.tex.xy); | |
UnityGI gi = FragmentGI ( | |
s.posWorld, occlusion, i.ambientOrLightmapUV, atten, s.oneMinusRoughness, s.normalWorld, s.eyeVec, mainLight); | |
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect); | |
c.rgb += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi); | |
c.rgb += Emission(i.tex.xy); | |
UNITY_APPLY_FOG(i.fogCoord, c.rgb); | |
return OutputForward (c, s.alpha); | |
} |
This uses the
FRAGMENT_SETUP
macro:#if UNITY_SPECCUBE_BOX_PROJECTION | |
#define IN_WORLDPOS(i) i.posWorld | |
#else | |
#define IN_WORLDPOS(i) half3(0,0,0) | |
#endif | |
#define IN_LIGHTDIR_FWDADD(i) half3(i.tangentToWorldAndLightDir[0].w, i.tangentToWorldAndLightDir[1].w, i.tangentToWorldAndLightDir[2].w) | |
#define FRAGMENT_SETUP(x) FragmentCommonData x = \ | |
FragmentSetup(i.tex, i.eyeVec, WorldNormal(i.tangentToWorldAndParallax), IN_VIEWDIR4PARALLAX(i), ExtractTangentToWorldPerPixel(i.tangentToWorldAndParallax), IN_WORLDPOS(i)); |
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:
#if UNITY_SPECCUBE_BOX_PROJECTION || PLANE_CLIPPING_ENABLED | |
#define IN_WORLDPOS(i) i.posWorld | |
#else | |
#define IN_WORLDPOS(i) half3(0,0,0) | |
#endif | |
#define IN_LIGHTDIR_FWDADD(i) half3(i.tangentToWorldAndLightDir[0].w, i.tangentToWorldAndLightDir[1].w, i.tangentToWorldAndLightDir[2].w) | |
#define FRAGMENT_SETUP(x) FragmentCommonData x = \ | |
FragmentSetup(i.tex, i.eyeVec, WorldNormal(i.tangentToWorldAndParallax), IN_VIEWDIR4PARALLAX(i), ExtractTangentToWorldPerPixel(i.tangentToWorldAndParallax), IN_WORLDPOS(i)); |
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:
half4 fragForwardBase (VertexOutputForwardBase i) : SV_Target | |
{ | |
FRAGMENT_SETUP(s) | |
PLANE_CLIP(s.posWorld) | |
UnityLight mainLight = MainLight (s.normalWorld); | |
half atten = SHADOW_ATTENUATION(i); | |
half occlusion = Occlusion(i.tex.xy); | |
UnityGI gi = FragmentGI ( | |
s.posWorld, occlusion, i.ambientOrLightmapUV, atten, s.oneMinusRoughness, s.normalWorld, s.eyeVec, mainLight); | |
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect); | |
c.rgb += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi); | |
c.rgb += Emission(i.tex.xy); | |
UNITY_APPLY_FOG(i.fogCoord, c.rgb); | |
return OutputForward (c, s.alpha); | |
} |
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:
#if PLANE_CLIPPING_ENABLED | |
float3 posWorld : TEXCOORD9; | |
#endif |
We use TEXCOORD9 because 8 was used just above it.
And in the vertForwardAdd function, add the lines
#if PLANE_CLIPPING_ENABLED | |
o.posWorld = posWorld.xyz; | |
#endif |
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:#if PLANE_CLIPPING_ENABLED | |
#define IN_WORLDPOS_FWDADD(i) i.posWorld | |
#else | |
#define IN_WORLDPOS_FWDADD(i) half3(0,0,0) | |
#endif | |
#define FRAGMENT_SETUP_FWDADD(x) FragmentCommonData x = \ | |
FragmentSetup(i.tex, i.eyeVec, WorldNormal(i.tangentToWorldAndLightDir), IN_VIEWDIR4PARALLAX_FWDADD(i), ExtractTangentToWorldPerPixel(i.tangentToWorldAndLightDir), IN_WORLDPOS_FWDADD(i)); |
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:
// ------------------------------------------------------------------ | |
// Additive forward pass (one light per pass) | |
struct VertexOutputForwardAdd | |
{ | |
float4 pos : SV_POSITION; | |
float4 tex : TEXCOORD0; | |
half3 eyeVec : TEXCOORD1; | |
half4 tangentToWorldAndLightDir[3] : TEXCOORD2; // [3x3:tangentToWorld | 1x3:lightDir] | |
LIGHTING_COORDS(5,6) | |
UNITY_FOG_COORDS(7) | |
// next ones would not fit into SM2.0 limits, but they are always for SM3.0+ | |
#if defined(_PARALLAXMAP) | |
half3 viewDirForParallax : TEXCOORD8; | |
#endif | |
#if PLANE_CLIPPING_ENABLED | |
float3 posWorld : TEXCOORD9; | |
#endif | |
}; | |
VertexOutputForwardAdd vertForwardAdd (VertexInput v) | |
{ | |
VertexOutputForwardAdd o; | |
UNITY_INITIALIZE_OUTPUT(VertexOutputForwardAdd, o); | |
float4 posWorld = mul(_Object2World, v.vertex); | |
#if PLANE_CLIPPING_ENABLED | |
o.posWorld = posWorld.xyz; | |
#endif | |
o.pos = mul(UNITY_MATRIX_MVP, v.vertex); | |
o.tex = TexCoords(v); | |
o.eyeVec = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos); | |
float3 normalWorld = UnityObjectToWorldNormal(v.normal); | |
#ifdef _TANGENT_TO_WORLD | |
float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w); | |
float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w); | |
o.tangentToWorldAndLightDir[0].xyz = tangentToWorld[0]; | |
o.tangentToWorldAndLightDir[1].xyz = tangentToWorld[1]; | |
o.tangentToWorldAndLightDir[2].xyz = tangentToWorld[2]; | |
#else | |
o.tangentToWorldAndLightDir[0].xyz = 0; | |
o.tangentToWorldAndLightDir[1].xyz = 0; | |
o.tangentToWorldAndLightDir[2].xyz = normalWorld; | |
#endif | |
//We need this for shadow receving | |
TRANSFER_VERTEX_TO_FRAGMENT(o); | |
float3 lightDir = _WorldSpaceLightPos0.xyz - posWorld.xyz * _WorldSpaceLightPos0.w; | |
#ifndef USING_DIRECTIONAL_LIGHT | |
lightDir = NormalizePerVertexNormal(lightDir); | |
#endif | |
o.tangentToWorldAndLightDir[0].w = lightDir.x; | |
o.tangentToWorldAndLightDir[1].w = lightDir.y; | |
o.tangentToWorldAndLightDir[2].w = lightDir.z; | |
#ifdef _PARALLAXMAP | |
TANGENT_SPACE_ROTATION; | |
o.viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex)); | |
#endif | |
UNITY_TRANSFER_FOG(o,o.pos); | |
return o; | |
} | |
half4 fragForwardAdd (VertexOutputForwardAdd i) : SV_Target | |
{ | |
FRAGMENT_SETUP_FWDADD(s) | |
PLANE_CLIP(s.posWorld) | |
UnityLight light = AdditiveLight (s.normalWorld, IN_LIGHTDIR_FWDADD(i), LIGHT_ATTENUATION(i)); | |
UnityIndirect noIndirect = ZeroIndirect (); | |
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, light, noIndirect); | |
UNITY_APPLY_FOG_COLOR(i.fogCoord, c.rgb, half4(0,0,0,0)); // fog towards black in additive pass | |
return OutputForward (c, s.alpha); | |
} |
And here's what the pass definition in StandardClippable.shader should look like:
// ------------------------------------------------------------------ | |
// Additive forward pass (one light per pass) | |
Pass | |
{ | |
Name "FORWARD_DELTA" | |
Tags { "LightMode" = "ForwardAdd" } | |
Blend [_SrcBlend] One | |
Fog { Color (0,0,0,0) } // in additive pass fog should be black | |
ZWrite Off | |
ZTest LEqual | |
CGPROGRAM | |
#pragma target 3.0 | |
// GLES2.0 temporarily disabled to prevent errors spam on devices without textureCubeLodEXT | |
#pragma exclude_renderers gles | |
// ------------------------------------- | |
#pragma shader_feature _NORMALMAP | |
#pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON | |
#pragma shader_feature _METALLICGLOSSMAP | |
#pragma shader_feature ___ _DETAIL_MULX2 | |
#pragma shader_feature _PARALLAXMAP | |
#pragma multi_compile_fwdadd_fullshadows | |
#pragma multi_compile_fog | |
#pragma vertex vertForwardAdd | |
#pragma fragment fragForwardAdd | |
#pragma multi_compile __ CLIP_ONE CLIP_TWO CLIP_THREE | |
#include "standard_clipped.cginc" | |
ENDCG | |
} |
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:
// Has a non-empty shadow caster output struct (it's an error to have empty structs on some platforms...) | |
#if PLANE_CLIPPING_ENABLED || !defined(V2F_SHADOW_CASTER_NOPOS_IS_EMPTY) || defined(UNITY_STANDARD_USE_SHADOW_UVS) | |
#define UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT 1 | |
#endif |
Inside the VertexOutputShadowCaster struct (around line 50), add the posWorld parameter:
#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT | |
struct VertexOutputShadowCaster | |
{ | |
V2F_SHADOW_CASTER_NOPOS | |
#if defined(UNITY_STANDARD_USE_SHADOW_UVS) | |
float2 tex : TEXCOORD1; | |
#endif | |
#if PLANE_CLIPPING_ENABLED | |
float3 posWorld : TEXCOORD2; | |
#endif | |
}; | |
#endif |
And finally, just after it, here's the modified vertShadowCaster and fragShadowCaster functions:
void vertShadowCaster (VertexInput v, | |
#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT | |
out VertexOutputShadowCaster o, | |
#endif | |
out float4 opos : SV_POSITION) | |
{ | |
#if PLANE_CLIPPING_ENABLED | |
float4 posWorld = mul(_Object2World, v.vertex); | |
o.posWorld = posWorld.xyz; | |
#endif | |
TRANSFER_SHADOW_CASTER_NOPOS(o,opos) | |
#if defined(UNITY_STANDARD_USE_SHADOW_UVS) | |
o.tex = TRANSFORM_TEX(v.uv0, _MainTex); | |
#endif | |
} | |
half4 fragShadowCaster ( | |
#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT | |
VertexOutputShadowCaster i | |
#endif | |
#ifdef UNITY_STANDARD_USE_DITHER_MASK | |
, UNITY_VPOS_TYPE vpos : VPOS | |
#endif | |
) : SV_Target | |
{ | |
PLANE_CLIP(i.posWorld) | |
#if defined(UNITY_STANDARD_USE_SHADOW_UVS) | |
half alpha = tex2D(_MainTex, i.tex).a * _Color.a; | |
#if defined(_ALPHATEST_ON) | |
clip (alpha - _Cutoff); | |
#endif | |
#if defined(_ALPHABLEND_ON) || defined(_ALPHAPREMULTIPLY_ON) | |
#if defined(UNITY_STANDARD_USE_DITHER_MASK) | |
// Use dither mask for alpha blended shadows, based on pixel position xy | |
// and alpha level. Our dither texture is 4x4x16. | |
half alphaRef = tex3D(_DitherMaskLOD, float3(vpos.xy*0.25,alpha*0.9375)).a; | |
clip (alphaRef - 0.01); | |
#else | |
clip (alpha - _Cutoff); | |
#endif | |
#endif | |
#endif // #if defined(UNITY_STANDARD_USE_SHADOW_UVS) | |
SHADOW_CASTER_FRAGMENT(i) | |
} |
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.using UnityEngine; | |
using System.Collections; | |
using System.Linq; | |
[ExecuteInEditMode] | |
public class ClippableObject : MonoBehaviour { | |
public void OnEnable() { | |
//let's just create a new material instance. | |
GetComponent<MeshRenderer>().sharedMaterial = new Material(Shader.Find("Custom/StandardClippable")) { | |
hideFlags = HideFlags.HideAndDontSave | |
}; | |
} | |
public void Start() { } | |
//only 3 clip planes for now, will need to modify the shader for more. | |
[Range(0, 3)] | |
public int clipPlanes = 0; | |
//preview size for the planes. Shown when the object is selected. | |
public float planePreviewSize = 5.0f; | |
//Positions and rotations for the planes. The rotations will be converted into normals to be used by the shaders. | |
public Vector3 plane1Position = Vector3.zero; | |
public Vector3 plane1Rotation = new Vector3(0, 0, 0); | |
public Vector3 plane2Position = Vector3.zero; | |
public Vector3 plane2Rotation = new Vector3(0, 90, 90); | |
public Vector3 plane3Position = Vector3.zero; | |
public Vector3 plane3Rotation = new Vector3(0, 0, 90); | |
//Only used for previewing a plane. Draws diagonals and edges of a limited flat plane. | |
private void DrawPlane(Vector3 position, Vector3 euler) { | |
var forward = Quaternion.Euler(euler) * Vector3.forward; | |
var left = Quaternion.Euler(euler) * Vector3.left; | |
var forwardLeft = position + forward * planePreviewSize * 0.5f + left * planePreviewSize * 0.5f; | |
var forwardRight = forwardLeft - left * planePreviewSize; | |
var backRight = forwardRight - forward * planePreviewSize; | |
var backLeft = forwardLeft - forward * planePreviewSize; | |
Gizmos.DrawLine(position, forwardLeft); | |
Gizmos.DrawLine(position, forwardRight); | |
Gizmos.DrawLine(position, backRight); | |
Gizmos.DrawLine(position, backLeft); | |
Gizmos.DrawLine(forwardLeft, forwardRight); | |
Gizmos.DrawLine(forwardRight, backRight); | |
Gizmos.DrawLine(backRight, backLeft); | |
Gizmos.DrawLine(backLeft, forwardLeft); | |
} | |
private void OnDrawGizmosSelected() { | |
if (clipPlanes >= 1) { | |
DrawPlane(plane1Position, plane1Rotation); | |
} | |
if (clipPlanes >= 2) { | |
DrawPlane(plane2Position, plane2Rotation); | |
} | |
if (clipPlanes >= 3) { | |
DrawPlane(plane3Position, plane3Rotation); | |
} | |
} | |
//Ideally the planes do not need to be updated every frame, but we'll just keep the logic here for simplicity purposes. | |
public void Update() | |
{ | |
var sharedMaterial = GetComponent<MeshRenderer>().sharedMaterial; | |
//Only should enable one keyword. If you want to enable any one of them, you actually need to disable the others. | |
//This may be a bug... | |
switch (clipPlanes) { | |
case 0: | |
sharedMaterial.DisableKeyword("CLIP_ONE"); | |
sharedMaterial.DisableKeyword("CLIP_TWO"); | |
sharedMaterial.DisableKeyword("CLIP_THREE"); | |
break; | |
case 1: | |
sharedMaterial.EnableKeyword("CLIP_ONE"); | |
sharedMaterial.DisableKeyword("CLIP_TWO"); | |
sharedMaterial.DisableKeyword("CLIP_THREE"); | |
break; | |
case 2: | |
sharedMaterial.DisableKeyword("CLIP_ONE"); | |
sharedMaterial.EnableKeyword("CLIP_TWO"); | |
sharedMaterial.DisableKeyword("CLIP_THREE"); | |
break; | |
case 3: | |
sharedMaterial.DisableKeyword("CLIP_ONE"); | |
sharedMaterial.DisableKeyword("CLIP_TWO"); | |
sharedMaterial.EnableKeyword("CLIP_THREE"); | |
break; | |
} | |
//pass the planes to the shader if necessary. | |
if (clipPlanes >= 1) | |
{ | |
sharedMaterial.SetVector("_planePos", plane1Position); | |
//plane normal vector is the rotated 'up' vector. | |
sharedMaterial.SetVector("_planeNorm", Quaternion.Euler(plane1Rotation) * Vector3.up); | |
} | |
if (clipPlanes >= 2) | |
{ | |
sharedMaterial.SetVector("_planePos2", plane2Position); | |
sharedMaterial.SetVector("_planeNorm2", Quaternion.Euler(plane2Rotation) * Vector3.up); | |
} | |
if (clipPlanes >= 3) | |
{ | |
sharedMaterial.SetVector("_planePos3", plane3Position); | |
sharedMaterial.SetVector("_planeNorm3", Quaternion.Euler(plane3Rotation) * Vector3.up); | |
} | |
} | |
} |
The Result!
You can find all the code on Github, in Unity3D-Plane-Clipping project I created.
And here's the unitypackage file.
It works well on the PC platform. However, it does not work on the Android platform. Is there an easy way to solve this?
ClippableObject.cs: https://gist.github.com/NocSchecter/ea28e0f9a744b493ba5f6bc26df864e9
plane_clipping.cginc: https://gist.github.com/NocSchecter/4a71815faa06ba0fc960dc2cc49b8ac9
StandardClippeable: https://gist.github.com/NocSchecter/1df2d4f475f8b3b327210eae84d60891
Listo, si, me di cuenta de esos errores y los corregi, pero ahora cuando corregi "CLIP_ THREE" surgio el siguiente error en el debug:
Shader error in 'Custom/StandardClippable': undeclared identifier '_planePos2'
In ClippableObject.cs I see you modified the switch to enable the clip planes for all cases, I recommend to revert that to the previous and only add the case for 4.
For plane_clipping.cginc, you will need to change this:
to:
similar with
#if (CLIP_THREE)
just below that to be#if (CLIP_THREE || CLIP_FOUR)
Unfortunately material keywords are very tricky to deal with...
Well, this is how to write the code to adapt plans, remove the lines that were responsible for drawing the plans and add the calls to _planePos4 and _planeNorm4. Still not working :(
I add a gif of the result, the planes of green, red and blue color make the cuts less the plane of white color that is the new one that adds
ClippableObject script:
http://img.fenixzone.net/i/SdWNiO5.jpeg http://img.fenixzone.net/i/eWbUjNy.jpeg http://img.fenixzone.net/i/mJAnGqo.jpeg
gif with the result:
https://i.ibb.co/ZJ9cBFx/test.gif
Can you please use pastebin or github gist to share the script? It will be easier to comment on it like that.
Looks like you have made a few mistakes, for example you have "planeNorm4" instead of "planeNorm4", "CLIP THREE" instead of "CLIP_THREE" and so on.
change float 3 by float4, the material does not turn pink, but to prepare the shader and the script to add a fourth plane, at the time of compiling this does not make the cut with the new plane
plane_clipping.cginc http://img.fenixzone.net/i/dzl5esp.jpeg http://img.fenixzone.net/i/TG51JZ7.jpeg http://img.fenixzone.net/i/doQsIO4.jpeg
in the shader code add planePos 4, planeNorm4 and in # pragma multicompile add CLIP_FOUR
Shader:
http://img.fenixzone.net/i/nkCHqp3.jpeg
Ok that looks about right, so good progress! You will also need to modify the c# script too so it will know what parameters it has to work with :)
I'm doing it the way you tell me but the material of my model is still looking pink. That's how I got it.
Code:
http://img.fenixzone.net/i/kgwbVD7.jpeg
Pink Material:
http://img.fenixzone.net/i/658mxvM.jpeg
The parentheses are not really matching well, try the float4 version first
change float 3 by float4, the material does not turn pink, but to prepare the shader and the script to add a fourth plane, at the time of compiling this does not make the cut with the new plane
plane_clipping.cginc http://img.fenixzone.net/i/dzl5esp.jpeg http://img.fenixzone.net/i/TG51JZ7.jpeg http://img.fenixzone.net/i/doQsIO4.jpeg
in the shader code add planePos 4, planeNorm4 and in # pragma multicompile add CLIP_FOUR
Shader:
[img]http://img.fenixzone.net/i/nkCHqp3.jpeg[/img]
Hye bro! here again, I am trying to add 3 new planes to the shader code, could you clarify guiat on where to insert the code of the new planes? It really makes me a little difficult to understand how the shaders work; I'm going down in the conditions of the CLIP and I'm doing adding the following
#if (CLIP_FOUR) float4 _planePos4; float4 _planeNorm4; #endif
#if CLIP_FOUR clip (float3 ( distanceToPlane (_planePos.xyz, _planeNorm.xyz, posWorld), distanceToPlane (_planePos2.xyz, _planeNorm2.xyz, posWorld), distanceToPlane (_planePos3.xyz, _planeNorm3.xyz, posWorld) distanceToPlane (_planePos4.xyz, _planeNorm4.xyz, posWorld) )); #else // CLIP_FOUR
in plane_clipping.cginc and I add the references in StadardClippable.shader without having a functional result, because the material turns pink.
Could you guide me a bit with the operation? Thank you.
There were a few problems there. float3 can only accept three numbers, and you were missing a comma before the last parameter.
You may need to do a
float4
instead offloat3
, e.g.Here's specifications for clip parameters: https://developer.download.nvidia.com/cg/clip.html
Or if float4 doesn't work, you can do multiple clips (3 + 1)
If you want to do 5 planes you will definitely need to do 2 clip methods, e.g. 4 + 1
I'm doing it the way you tell me but the material of my model is still looking pink. That's how I got it.
Code:
http://img.fenixzone.net/i/kgwbVD7.jpeg
Pink Material:
http://img.fenixzone.net/i/658mxvM.jpeg
It only works with a material? What happens if I have a model with n quantity of materials? Is it possible to make the cut for more than one material?
I solved it, what I had to do was create an array of materials and replace all the references of sharedMaterial with the array of materials
Example:
Before: case 0: sharedMaterial.DisableKeyword("CLIP_ONE"); sharedMaterial.DisableKeyword("CLIP_TWO"); sharedMaterial.DisableKeyword("CLIP_THREE"); break; case 1: sharedMaterial.EnableKeyword("CLIP_ONE"); sharedMaterial.DisableKeyword("CLIP_TWO"); sharedMaterial.DisableKeyword("CLIP_THREE"); break; case 2: sharedMaterial.DisableKeyword("CLIP_ONE"); sharedMaterial.EnableKeyword("CLIP_TWO"); sharedMaterial.DisableKeyword("CLIP_THREE"); break; case 3: sharedMaterial.DisableKeyword("CLIP_ONE"); sharedMaterial.DisableKeyword("CLIP_TWO"); sharedMaterial.EnableKeyword("CLIP_THREE"); break;
Now:
Nice :)
Hello, I really liked your shader, do you know how to use Gameobjetc planes as cutters?
Thank you for your comment. To use other GameObjects as cutters you can get their position and rotation every frame and apply the values into the behavior
Hello, google brought me here. Thanks for your amazing work! I've tried it and it does clipped the object. But the problem is it's not following the object. I want to make it child game object and make it follow it's game object position. Since it's AR it would be no use if it can't follow the objects. I hope the script is updated soon. Thanks!
It's a great job. however it should be applied to a container and should follow it in its position or it becomes almost useless :(
are you able to improve it?
Thanks a lot for this shader, works great!
But actually I've got a problem in the last version of Unity, the 2018 1.0f2... the shader won't compile because of an error in the file standard_clipped.gcing, at line 175. The error is this one: "FragmentSetup: cannot convert from half4[3] to float4[3]".
The faulty call is the macro FRAGMENT_SETUP_CLIP(s), which is defined like this:
#define FRAGMENT_SETUP_CLIP(x) FragmentCommonData x =
FragmentSetup(i.tex, i.eyeVec, IN_VIEWDIR4PARALLAX(i), i.tangentToWorldAndParallax, IN_WORLDPOS_CLIP(i));
One of these parameters has to be changed, but I've no idea of which one. Also, in the release notes of this version of Unity I can't find what was changed... any ideas?
UPDATE: I managed to fix all errors and now the shader compiles without problems (or at least it seems to work!). I put the updated code here if anyone stumbles on this issue :)
http://www.mediafire.com/file/fo001xcq44qfxre/PlaneClipping.zip
Hello there, and sorry for my english if I make any mistakes. Thanks for the scripts. It looks awesome, but because of my lack of knowledge in shaders I can't do much on my own to improve them. I have just a few questions :
Is it possible to "re-shape" the planes, or to use more than 3 clip planes in order to create a pseudo clip mesh that could hide what's inside of it (for example, the planes could be replaced by a sphere that could hide the cube) ? I don't know how complicated this is, but if it is feasible, what would be the best approach to realize this effect, or at least fake it ? Or maybe there's already a shader that does this job for us ?
Second question : This is once again more a matter of a lack of knowledge than a real issue with your shader, but how can I use my own material without assigning your shader to it ? I've seen in your C# script that you created a new material with your shader and immediately added it to the cube's MeshFilter, but I would like to add my own material instead of the default white one, and mine already has a shader attached to it. Should I create submeshes and assign my different materials to them, or can I combine the effects of the different shaders (if that's ever possible) ? I know it may sound like a silly question, but I'm kinda a noob with shaders, and the most complex thing I've ever made with them is a basic Color Correction shader with only a hue, saturation, brightness, contrast and global Color.
The last question has nothing to do with the topic, but... On your first gifs, at the top of your blog, what shader did you use to create that pixelated effect in your scene? Look at your plane and your sky, they both have that strange pixelated gradient that I'd really like to replicate in my game. If you are the one who made them, do you mind sharing a download link, or at least a tutorial on how to replicate this effect? Thanks a lot ^^
Thank you very much for this demo. I am gonna try it. I would suggest that the heart emoji to be removed tho. lol. Thank you again! :)
The one in "hosted with ❤ by GitHub"? That's a GitHub thing, unsure if it can be removed, I'll check :)
This works amazingly. I love how you taught us the way you did it, so that we can apply it to other shaders as well. Thanks so much.
Hi, Great works..can it be adapted with transparent/diffuse shader?..if yes then can you please guide me a little bit..
Hi Sanidhya, yes it should still be able to work with a transparent shader as well :) The important part is to capture the world coordinates of the pixel and compare it with the plane, then it should work with any type of shader. I am thinking about rewriting this shader later on to be much simpler but I am unsure when I may be able to have time to do that unfortunately so it may be a long time before that.
Hi,
i tried the script , it is working but the plane is not on the object, is it far away, how do i fix the issue
You may need to have a script that moves the plane as well :) What is it that you are trying to do? For example do you want to always cut an object at the same shape?
I honestly think i love you... its been 5 days im trying to figure this out and im totally new to unity, you saved my life <3
Hello, Wonderful job! IT works fine for me in unity but I can't make it work when I deploy it to hololens. Do you know if it works for hololens or if I have to make some changes?
I think you'd need to make changes for it to work on Hololens
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.
Yes that will work, back then I didn't think of that :D
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.
I have the exact same problem. Any help would be greatly appreciated!
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!
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 :)
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.
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 :)
As an example, simply show how to add a photograph or image to a quad, and clip it.
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!
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.
Hi Tomasz!
Please see the commit on https://github.com/firtoz/Unity3D-Plane-Clipping/commit/922d07ff2aef6e687129a68b7ae6eab299833ac6 , I will be cutting a release soon. It does things a bit differently, but unfortunately I do not have enough time to make another blog post for it yet
I'm trying to clip a movie texture and it's not seeing the clipping plane I've configured. I'm using the demo at https://www.assetstore.unity3d.com/en/#!/content/22524 and adding your component. They each work great on their own, but not together. The video output (on a plane or a cube face, for example) is not clipped. Any ideas?
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.
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.
Yes, you can set these meshes with the same material and apply the material property block to them every time the clip object moves :)
hi , ty for your great work :) i just want to know , is there a way to i make this shader transparent ?
no need :) i just figure it out :)
Hi, just saw your messages! How did your do it? :D
Great posting. Really appreciate you taking the time to do this. Learned quite a bit .
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.
Hi, sorry for the late reply. I'm afraid I am not very sure on how to approach that.
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?
http://forum.unity3d.com/threads/shader-for-clipping-plane.11832/ this one looks useful and relevant :)
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
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?
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 :)
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.
Simple answer: try to pass the world position and call clip. Long answer: I'll be able to give it another try soon
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?
Hi Tomasz, I have updated the GitHub project to show how I have added specular setup support :) Check the latest few commits!
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.
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.
Hi, I will be able to take a look this weekend :)
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 ?
I can't think why it shouldn't work :) when I have some time I can take a look.
Hi,
don't waste time with it ! I've managed to adapt it, thought I'm not very sure of what I did...
If you dont like it, code it by yourself...MATE
Great work! Thanks for sharing!
very useful, thanks for putting this together!