Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Intersection Shader help needed for a newb

Discussion in 'Shaders' started by Amuscaria, Oct 13, 2017.

  1. Amuscaria

    Amuscaria

    Joined:
    May 30, 2017
    Posts:
    55
    Hello all. I'm trying to write a shader that renders a plane white wherever it intersects with other geometry, otherwise it's a solid black. Refer to the image below:

    StencilShader.png


    I'm completely new to Unity, and started using it for my college research project couple of months ago. The point of this shader is to fake the black and white image effect of a ultrasound (a very barebones look is enough). I'll parent a camera to the plane with the shader, that only renders the plane and nothing else, and have that displayed on a secondary display area on the screen. I figured a intersection shader would be the simplest approach, but I've actually not found a single example online.

    I've seen cross section ones, where the geometry is actually cut away and anything below the plane is filled with a solid color. But that's beyond my needs. I've also found a Winston Barrier example from the youtuber "Making Stuff Look Good" that sort of does what I want, but not really. I've also found this example:

    Code (csharp):
    1.  
    2. Shader "Unlit/Intersection Glow"
    3. {
    4.     Properties
    5.     {
    6.         _Color ("Color", Color) = (1,0,0,1)
    7.     }
    8.     SubShader
    9.     {
    10.         Tags { "RenderType"="Transparent" "Queue"="Transparent" }
    11.         LOD 100
    12.         Blend One One // additive blending for a simple "glow" effect
    13.         Cull Off // render backfaces as well
    14.         ZWrite Off // don't write into the Z-buffer, this effect shouldn't block objects
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.  
    21.             #include "UnityCG.cginc"
    22.  
    23.             struct appdata
    24.             {
    25.                 float4 vertex : POSITION;
    26.             };
    27.  
    28.             struct v2f
    29.             {
    30.                 float4 screenPos : TEXCOORD0;
    31.                 float4 vertex : SV_POSITION;
    32.             };
    33.  
    34.             sampler2D _CameraDepthTexture; // automatically set up by Unity. Contains the scene's depth buffer
    35.             fixed4 _Color;
    36.  
    37.             v2f vert (appdata v)
    38.             {
    39.                 v2f o;
    40.                 o.vertex = UnityObjectToClipPos(v.vertex);
    41.                 o.screenPos = ComputeScreenPos(o.vertex);
    42.                 return o;
    43.             }
    44.  
    45.             fixed4 frag (v2f i) : SV_Target
    46.             {
    47.                 //Get the distance to the camera from the depth buffer for this point
    48.                 float sceneZ = LinearEyeDepth (tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)).r);
    49.                 //Actual distance to the camera
    50.                 float fragZ = i.screenPos.z;
    51.  
    52.                 //If the two are similar, then there is an object intersecting with our object
    53.                 float factor = 1-step( 0.1, abs(fragZ-sceneZ) );
    54.  
    55.                 return factor * _Color;
    56.             }
    57.             ENDCG
    58.         }
    59.     }
    60. }
    61.  
    But it doesn't seem to do anything other than make my geometry invisible. I read that I was supposed to not have "Zwrite Off", so I commented it out but it still doesn't to anything other than not render the Unity Wireframe lines inside the object with the shader. I've got a script that sends out the Depth buffer (from the WInston Example) attached to my camera, but it doesn't make the shader work any better.

    Any help as to how to best approach the shader would be most appreciated. Thanks. :)
     
    Last edited: Oct 13, 2017
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    A quick description of what those two techniques do and what you need.

    The cross section shaders work by calculating a mathematical plane's position within the shader code and clipping the pixels that are on one side. There are versions that clip with multiple planes, or a sphere, or a box. The key thing is those shapes don't "exist", they're just being manually provided to the shader via a vector (or 4) used to describe the mathematical shape.

    The "intersection glow" or other depth based fades work by reading values from the depth buffer, or more specifically the camera depth texture, which is a "copy" of the depth buffer rendered in a separate pass prior to rendering the scene. The depth buffer, or z buffer, is a real time rendering thing used for sorting opaque geometry and handling depth intersections of transparent objects with opaque geometry. The main thing to understand is the depth buffer and by extension the camera depth texture stores the depth of the closest opaque surface at each pixel, and only that one surface. The shader reads that depth value and compares the depth of the currently rendering geometry and either fades the opacity out or makes it glow, or sometimes both. The other key here is the object using that shader cannot itself be opaque as then it too would render to the depth buffer and that depth comparison would always be a distance of 0.0.

    So, for what you need, the cross section shader is closer to what you're looking for, but is obviously somewhat limited to simple shapes and rendering multiple of these shapes gets difficult if you're using it on your plane. An alternative would be to use a second camera rendering to a render texture and rendering your objects using a cross section shader, or just using that camera's near plane. That will only work if your geometry isn't intersecting and is water tight. Honestly, there are multiple threads on many forums and several blog posts over the years on the best way to get the cross section of arbitrary geometry. This is a much more complex topic then I think you're expecting.
     
  3. Amuscaria

    Amuscaria

    Joined:
    May 30, 2017
    Posts:
    55
    Thanks for the feedback. I wasn't aware that writing a shader would be so difficult. The thing is, I already have a working cross-section shader, but I didn't think it would work because it removes everything above the plane. video detailing what I want it to do:


    If there is a way for me to make the cross-section shader not shave off the geometry above the cut-plane, I'd use it. Or any other method. I want to do this in real-time, though, not as a video. Here's the other half of my project, which has mostly been done:

    https://forum.unity.com/threads/need-help-with-term-search-and-feasibility-questions.490126/
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Except shaving off the geometry above the cut-plane is exactly what you want and need to have happen.

    You need a second camera that's rendering the view for the quad, and only use the cross section shader for that camera's view. That can be done with replacement shaders, or maybe more simply by having a second set of objects setup and use layers to hide them from the main camera.
     
  5. Amuscaria

    Amuscaria

    Joined:
    May 30, 2017
    Posts:
    55
    Ok. Maybe im misunderstanding some fundamentals of how a shader works with a camera. I had no idea I can set a shader for a specific camera. Can I set the camera to ONLY render out the cross-section area without rendering anything that's under the cut-plane that isn't being removed? As as shown in the video, I need both the normal 3D view, and the cross-section view to render at the same time. I get that I need 2 cameras, but how do render the empty areas black? Do I just can just attach a solid black plane just slightly below the cut-plane of appropriate size that the only the cross-section camera sees?
     
  6. Amuscaria

    Amuscaria

    Joined:
    May 30, 2017
    Posts:
    55
    Also, there's another problem that I think I'll have if I use the cut-plane. Like the video, I need a needle to be able to approach from the opposite side of the cut-plane and still have an intersection drawn and rendered to the camera. The shader I have now does not cuts both-ways, or only renders the cross-section of intersection geometry. Without that, the simulator is sort of useless.

    Unless I can have another camera that only renders the needle and some how composite the two renders onto one display.
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Set the second camera's clear flags to Solid Color. Set color to black.

    Specifically you can tell a camera to render everything it sees with a different shader.
    https://docs.unity3d.com/Manual/SL-ShaderReplacement.html

    Replacement shaders can take some time to get your head around, and for your use case I'm not sure if it's the best option. I would recommend using layers and the camera's culling mask.
    https://docs.unity3d.com/Manual/Layers.html


    Ultimately the way I would use the cross section shader is to not use the cross section plane at all. This might seem weird, but just have the object fully intact and use the second camera's near clip plane to "slice" into it. The only part of the cross section shader you really need is the fact it renders the interior faces a different color than the external faces.

    Try this.
    1. Make a second camera.
    2. Set the Clear Flags to Solid Color and set the Background Color to black.
    3. Create a new material, set shader to Unlit/Texture.
    4. Create a new render texture asset. (Right click in the project view, create new > render texture)
    5. Set render texture to the Target Texture of the camera and the texture of the material.
    6. Apply the material to your plane you want the intersections to be visible on.
    Now you can move that camera around and see what it sees on the plane.
    1. Click on Layers > Edit Layers in the top right.
    2. Add a new layer called "Cross Section".
    3. Select your main camera and under Culling Mask disable "Cross Section".
    4. Select your second camera and under Culling Mask set it to "Nothing" then enable "Cross Section".
    Now it'll just be showing black.
    1. Make a copy of your vein game objects.
    2. Set their material to be the cross section material, make sure the cut plane is way off to the side. Also make sure the exterior is black and interior is white.
    3. On those game objects set their Layer to "Cross Section".
    Now move the camera to be right up next to those veins. You could even have the camera be a child of the ultrasound transducer game object. The key is having the camera's near plane line up with the transducer's intersection plane.
     
  8. Amuscaria

    Amuscaria

    Joined:
    May 30, 2017
    Posts:
    55
    I will give that a try, thanks. :)
     
  9. Amuscaria

    Amuscaria

    Joined:
    May 30, 2017
    Posts:
    55
    Last edited: Oct 23, 2017
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    There are a couple of links in this forum if you search for them. It is important to understand that Unity does not really use Cg anymore. It's HLSL with a few Cg-isms left over as a product of legacy code (fragment shader vs pixel shader for example) and backwards compatibility (some functions and #defines in the .cginc files, also "cginc" files).

    Most of the time HLSL and Cg are identical, but there are a handful of functions that are slightly different in their use, and that only exist in one or the other.
     
  11. Amuscaria

    Amuscaria

    Joined:
    May 30, 2017
    Posts:
    55
    I tried the suggestion you gave earlier, and it's not working the way I thought it would. The cross section isn't rendered on the cutting plane, but rather on the model. Thus making the second camera only see the plane just renders a blank plane without anything on it. I'm thinking about two solution, neither of which I know how to do.

    1) I can make the shader slice off everything except the cross section. Put this on a duplicate set of models that only the second camera sees, and render that camera using orthogonal view. I haven't figured out how to make it slice both above and below the plane though. I've tinkered the shader a lot and got a decent idea on which part of the code is doing what, but don't understand it enough to write a section for myself. Below is the shader code that I modified from the "Cross Section" unity asset from the asset store.

    Code (csharp):
    1.  
    2. Shader "CrossSection/OnePlaneBSP"
    3.     {
    4.     Properties
    5.     {
    6.         _Color("Color", Color) = (1,1,1,1)
    7.         _CrossColor("Cross Section Color", Color) = (1,1,1,1)
    8.         _MainTex("Albedo (RGB)", 2D) = "white" {}
    9.         _Glossiness("Smoothness", Range(0,1)) = 0.5
    10.         _Metallic("Metallic", Range(0,1)) = 0.0
    11.         _PlaneNormal("PlaneNormal",Vector) = (0,1,0,0)
    12.         _PlanePosition("PlanePosition",Vector) = (0,0,0,1)
    13.         _StencilMask("Stencil Mask", Range(0, 255)) = 255
    14.     }
    15.  
    16.     SubShader
    17.     {
    18.         Tags { "RenderType"="Opaque" }
    19.  
    20.         Stencil
    21.         {
    22.             Ref [_StencilMask]
    23.             CompBack Always
    24.             PassBack Replace
    25.  
    26.             CompFront Always
    27.             PassFront Zero
    28.         }
    29.  
    30.         Cull Back
    31.  
    32.         CGPROGRAM //Renders the sections below the plane.
    33.         #pragma surface surf Standard fullforwardshadows
    34.         #pragma target 3.0
    35.  
    36.         sampler2D _MainTex;
    37.  
    38.         struct Input
    39.         {
    40.             float2 uv_MainTex; //the rendered screen
    41.             float3 worldPos;
    42.         };
    43.  
    44.         half _Glossiness;
    45.         half _Metallic;
    46.         fixed4 _Color;
    47.         fixed4 _CrossColor;
    48.         fixed3 _PlaneNormal;
    49.         fixed3 _PlanePosition;
    50.  
    51.         bool checkVisability(fixed3 worldPos)
    52.         {
    53.             float dotProd1 = dot(worldPos - _PlanePosition, _PlaneNormal);
    54.             return dotProd1 > 0;
    55.         }
    56.  
    57.         void surf(Input IN, inout SurfaceOutputStandard o)
    58.         {
    59.             if (checkVisability(IN.worldPos))discard;
    60.             fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    61.             o.Albedo = c.rgb;      
    62.             o.Metallic = _Metallic;
    63.             o.Smoothness = _Glossiness;
    64.             o.Alpha = c.a;
    65.         }
    66.         ENDCG
    67.        
    68.         Cull Front
    69.  
    70.         CGPROGRAM //Renders cross section and cuts off geometry above the plane.
    71.         #pragma surface surf NoLighting noambient
    72.  
    73.         struct Input
    74.         {
    75.             half2 uv_MainTex;
    76.             float3 worldPos;
    77.         };
    78.  
    79.         sampler2D _MainTex;
    80.         fixed4 _Color;
    81.         fixed4 _CrossColor;
    82.         fixed3 _PlaneNormal;
    83.         fixed3 _PlanePosition;
    84.  
    85.         bool checkVisability(fixed3 worldPos)
    86.         {
    87.             float dotProd1 = dot(worldPos - _PlanePosition, _PlaneNormal);
    88.             return dotProd1 > 0;
    89.         }
    90.  
    91.         fixed4 LightingNoLighting(SurfaceOutput s, fixed3 lightDir, fixed atten)
    92.         {
    93.             fixed4 c;
    94.             c.rgb = s.Albedo;
    95.             c.a = s.Alpha;
    96.             return c;
    97.         }
    98.  
    99.         void surf(Input IN, inout SurfaceOutput o)
    100.         {
    101.             if (checkVisability(IN.worldPos)) discard;
    102.             o.Albedo = _CrossColor; //render out the cross section
    103.         }
    104.         ENDCG
    105.        
    106.     }
    107.     //FallBack "Diffuse"
    108. }
    109.  
    As I mentioned, I can't find a list of functions for HLSL or CG. I've not idea what the functions like LightNoLight or Surf does, or what the parameters mean. Nor do I understand the syntax fully. If you could how I could get the shader to clip both above and below, it would be great. Or I can use the easier method of just putting a solid black plane just slightly below the clipping plane.

    A second method I thought about using was making a screen shader that takes the normal render of the second camera, and only render out the cross section based on its color. The Cross section's default color is white. If I could write a shade that only renders white, and makes everything else black, I will sill get what I want. I don't know what terminology to google, though. I've tried googling "making camera only render white" and found no examples. ANy ideas on what terminology to use so I can search for it?
     
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Because that's not anything specific to HLSL (or Cg), that's Unity's ShaderLab Surface Shaders.
    https://docs.unity3d.com/Manual/SL-SurfaceShaders.html

    It's written in HLSL, but the #pragma surface line tells Unity to use a shader generator to construct the full vertex / fragment shader. Select the shader in Unity and click on the Show Generated Code button in the inspector to see the full generated shader code. There's also a lot of functionality abstracted away in various .cginc files. It's all calling HLSL code in the end.
     
  13. Amuscaria

    Amuscaria

    Joined:
    May 30, 2017
    Posts:
    55
    So are things like "LightingNoLighting" user defined by the coder, or something that predefined by ShaderLab? I'm just looking for a list. If it's just something the original coder wrote, than I guess I fundamentally misunderstood the shader code.
     
  14. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    LightingNoLighting would be something defined by that shader ... and also should never ever be used as it's a sign of someone not knowing what they're doing. If you're using a Surface Shader and don't want lighting you should be using a vertex fragment shader. Surface shaders are explicitly for generating the passes Unity needs to interface with it's lighting system.
     
  15. Amuscaria

    Amuscaria

    Joined:
    May 30, 2017
    Posts:
    55
    Got this far today. Managed to get the cross section and color-filter shaders working at the most basic level. Still trying to make the cross-section shader clip both sides off so I won't need a 3rd shader for the needle. But this is good enough for right now. USTest.png
     
    bgolus likes this.
  16. Amuscaria

    Amuscaria

    Joined:
    May 30, 2017
    Posts:
    55
    Just out of curiosity, can the difference passes in a Shader have different render queues from one another?