Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

X-Raying effect shader covers too much area

Discussion in 'Shaders' started by mhr2145, Nov 29, 2020.

  1. mhr2145

    mhr2145

    Joined:
    Mar 8, 2019
    Posts:
    8
    Shader (attached to child of blue train sprite game object):
    Code (CSharp):
    1. Shader "Custom/Invisible"
    2. {
    3.     SubShader
    4.     {
    5.         // renders after all transparent objects (queue = 3001)
    6.         Tags { "Queue" = "Transparent+1" }
    7.         // makes this object transparent
    8.         Pass { Blend Zero One }
    9.     }
    10. }
    XRayableBehavior.cs (attached to yellow train sprite game object):
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. public class XRayableBehavior : MonoBehaviour
    5. {
    6.     void Start()
    7.     {
    8.         if (GetComponent<Renderer>())
    9.         {
    10.             // next line queues the object's render order at 3002
    11.             // set renderQueue to render after our invisible mask (3001)
    12.             GetComponent<Renderer>().material.renderQueue = 3002;
    13.         }
    14.     }
    15. }

    I have three sprites: a blue train, a yellow train, and a green shape. The blue train is in front of the yellow train, and the yellow train is in front of the green shape. The blue train has an alpha < 1, so it is less than opaque, and the yellow train is fully opaque, so it occludes what is behind it. Where the blue train overlaps on the yellow train, those sections of the yellow train should disappear so we can see the green shape behind it, or more specifically, we can see the green shape behind the yellow train through the translucent blue train, like a blue stained glass window.

    There are two sprites, a visible and invisible one, attached to the game object representing the blue train. The "invisible" sprite has the shader I mentioned, and the visible sprite is a normal blue sprite with alpha < 1. The visible sprite is on top of the invisible sprite.

    Finally, the yellow sprite has a component attached to it that changes its rendering order such that the shader of the invisible sprite will cause the yellow sprite to vanish where they overlap.

    Now here's the problem. What is actually happening is that the invisible sprite train appears to be slightly larger than the blue sprite train. They should be the same size.

    I call the invisible sprite the "x-ray" train since it has the effect of seeing through what's in front of it. In my code, the x-ray sprite object and the visible blue sprite object both have the same localScale. They both are in "simple" draw mode. Compression is Normal Quality, Filter Mode is Bilinear. The only difference is that the x-ray sprite has the shader above, and the other uses the default Sprite material.

    I've attached two GIFs that demonstrate what currently happens when I run my program. I've also attached a screenshot of what happens when I substitute the default circle sprite for the blue train. You'll notice that it also has a similar issue. Much less noticeable, but there nonetheless.
     

    Attached Files:

  2. mhr2145

    mhr2145

    Joined:
    Mar 8, 2019
    Posts:
    8

    Here's another (perhaps clearer) example of my problem. It should be that the X-ray is the same exact shape as the blue skateboard, but it's as if it takes shortcuts or merges vertices together.
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    That is just the actual shape of the mesh being drawn for that sprite.

    If you're doing things like writing to the depth buffer or stencil buffer, that's the "shape" that's being used, since neither know anything of the alpha of the sprite being used. If you want a closer fit, you need to use a tighter mesh bounds, which Unity will not do for you, or use a cutout shader for the "invisible" pass that samples the sprite texture and uses something like
    clip(spriteAlpha - 0.5)
    to skip the pixels the mesh covers but are transparent in the texture.
     
  4. mhr2145

    mhr2145

    Joined:
    Mar 8, 2019
    Posts:
    8
    Thanks, your clip() suggestion did the trick! For future readers, here is my new shader code:

    Code (CSharp):
    1. /* https://circuitstream.com/blog/occlusion-xray/ */
    2. Shader "Custom/Invisible"
    3. {
    4.     Properties {
    5.         _MainTex("Base (RGB) Trans (A)", 2D) = "white" {}
    6.         _Cutoff("Alpha cutoff", Range(0,1)) = 0.5
    7.     }
    8.     SubShader
    9.     {
    10.         // renders after all transparent objects (queue = 3001)
    11.         Tags { "Queue" = "Transparent+1" }
    12.         Pass
    13.         {
    14.             // makes this object transparent
    15.             Blend Zero One
    16.  
    17.             CGPROGRAM
    18.                 #define appdata_base Vertex
    19.                 #pragma vertex vert
    20.                 #pragma fragment frag
    21.                 #include "UnityCG.cginc"
    22.  
    23.                 struct Fragment {
    24.                     float4 pos : SV_POSITION;
    25.                     float2 texcoord : TEXCOORD0;
    26.                 };
    27.  
    28.                 sampler2D _MainTex;
    29.                 float4 _MainTex_ST;
    30.                 fixed _Cutoff;
    31.  
    32.                 Fragment vert(Vertex v)
    33.                 {
    34.                     Fragment fragment;
    35.                     fragment.pos = UnityObjectToClipPos(v.vertex);
    36.                     fragment.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
    37.                     return fragment;
    38.                 }
    39.  
    40.                 half4 frag(Fragment fragment) : COLOR
    41.                 {
    42.                     fixed4 col = tex2D(_MainTex, fragment.texcoord);
    43.                     // clip stops rendering a pixel if value is negative
    44.                     clip(col.a - _Cutoff);
    45.  
    46.                     // the color (black) doesn't matter because the alpha set to 0 anyway
    47.                     half4 pixel;
    48.                     pixel.r = 0;
    49.                     pixel.g = 0;
    50.                     pixel.b = 0;
    51.                     pixel.a = 0;
    52.                     return pixel;
    53.                 }
    54.             ENDCG
    55.         }
    56.     }
    57. }