Search Unity

Projector shader with angle limitation

Discussion in 'Shaders' started by SoxwareInteractive, May 26, 2016.

  1. SoxwareInteractive

    SoxwareInteractive

    Joined:
    Jan 31, 2015
    Posts:
    541
    Hi,

    when using a projector for projecting bullet holes, the bullet holes will look ugly when the surface has a corner as seen in the picture below:
    Projector.png

    The projector used is set to orthographic. I was wondering if it is possible to write a shader for the projector material that checks if the normal of the surface exceeds a certain limit in respect to the projectors angle. The bullet hole is then only rendered where the angle is below this limit.
    If the shader would be attached on the bullet hole seen in the picture above, the streched part woud be invisible.

    I have some very basic shader knowledge (I wrote some basic surface shaders that mix some textures etc.), but this is currently out of my league. Please let me know if this is technically possible and if so, how I can realize this!

    Thanks :)
     
  2. SoxwareInteractive

    SoxwareInteractive

    Joined:
    Jan 31, 2015
    Posts:
    541
    Guys,

    I just managed to get it running on my own:
    ProjectorFixed.png

    The shader checks the normals of the object, if it exceeds the angle that was set via a property, the projector material is not rendered. The projector material still bends with the surface, if the angle limit is not exceeded. So the shader only removes the ugly stretching of the decal at sharp angles!

    I wanted to share my modified version of Unity's "ProjectorAdditive" shader (please let me know, if the shader could be optimized somehow):
    Code (CSharp):
    1.  
    2. Shader "Custom/ProjectorAdditiveAngleCutoff"
    3. {
    4.     Properties
    5.     {
    6.         _Color ("Main Color", Color) = (1,1,1,1)
    7.         _ShadowTex ("Cookie", 2D) = "" {}
    8.         _FalloffTex ("FallOff", 2D) = "" {}
    9.         _AngleLimit ("Angle Limit (rad)", Float) = 0
    10.     }
    11.    
    12.     Subshader
    13.     {
    14.         Tags {"Queue"="Transparent"}
    15.         Pass
    16.         {
    17.             ZWrite Off
    18.             AlphaTest Greater 0
    19.             ColorMask RGB
    20.             Blend SrcAlpha OneMinusSrcAlpha
    21.             Offset -1, -1
    22.    
    23.             CGPROGRAM
    24.             #pragma vertex vert
    25.             #pragma fragment frag
    26.             #pragma multi_compile_fog
    27.             #include "UnityCG.cginc"
    28.            
    29.             struct v2f {
    30.                 float4 uvShadow : TEXCOORD0;
    31.                 float4 uvFalloff : TEXCOORD1;
    32.                 half projAngle : TEXCOORD2;
    33.                 UNITY_FOG_COORDS(2)
    34.                 float4 pos : SV_POSITION;
    35.             };
    36.            
    37.             float4x4 _Projector;
    38.             float4x4 _ProjectorClip;
    39.             half3 projNormal;
    40.  
    41.             inline half angleBetween(half3 vector1, half3 vector2)
    42.             {
    43.                 return acos(dot(vector1, vector2) / (length(vector1) * length(vector2)));
    44.             }
    45.  
    46.             v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
    47.             {
    48.                 v2f o;
    49.                 o.pos = mul (UNITY_MATRIX_MVP, vertex);
    50.                 o.uvShadow = mul (_Projector, vertex);
    51.                 o.uvFalloff = mul (_ProjectorClip, vertex);
    52.                 projNormal = mul (_Projector, normal);
    53.                 o.projAngle = abs(angleBetween(half3(0,0,-1), projNormal));
    54.                 UNITY_TRANSFER_FOG(o,o.pos);
    55.                 return o;
    56.             }
    57.            
    58.             fixed4 _Color;
    59.             sampler2D _ShadowTex;
    60.             sampler2D _FalloffTex;
    61.             half _AngleLimit;
    62.            
    63.             fixed4 frag (v2f i) : SV_Target
    64.             {
    65.                 fixed4 texS = tex2Dproj (_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));
    66.                 texS.rgba *= _Color.rgba;
    67.    
    68.                 fixed4 texF = tex2Dproj (_FalloffTex, UNITY_PROJ_COORD(i.uvFalloff));
    69.                 fixed4 res = texS * texF.a * step(_AngleLimit, i.projAngle);
    70.  
    71.                 UNITY_APPLY_FOG_COLOR(i.fogCoord, res, fixed4(0,0,0,0));
    72.                 return res;
    73.             }
    74.             ENDCG
    75.         }
    76.     }
    77. }
    78.  
    Edit: Made some optimizations (moved angle calculation into the vertex shader and replaced if statement in the fragment shader with a step() function)!
     
    Last edited: May 27, 2016
    Ethwood likes this.
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    For future reference, step is implemented as an if statement in the shader compiler; step(y, x) produces an identical shader code to (x >= y ? 1 : 0)

    Also if statements aren't as evil as you've heard they are.
     
  4. SoxwareInteractive

    SoxwareInteractive

    Joined:
    Jan 31, 2015
    Posts:
    541
    Thanks for the hint! So, usually I would assume that using an IF-ELSE statement would be way faster because the whole texture sampling/projection isn't done when not needed (i.e. when the angle is below the angle limit). So would the following code execute faster?

    Like this:
    Code (CSharp):
    1. fixed4 frag (v2f i) : SV_Target
    2.             {
    3.                 if (i.projAngle > _AngleLimit)
    4.                 {
    5.                     fixed4 texS = tex2Dproj (_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));
    6.                     texS.rgba *= _Color.rgba;
    7.    
    8.                     fixed4 texF = tex2Dproj (_FalloffTex, UNITY_PROJ_COORD(i.uvFalloff));
    9.                     fixed4 res = texS * texF.a;
    10.    
    11.                     UNITY_APPLY_FOG_COLOR(i.fogCoord, res, fixed4(0,0,0,0));
    12.                     return res;
    13.                 }
    14.                 else
    15.                 {
    16.                     return fixed4(0, 0, 0, 0);
    17.                 }
    18.             }
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    You might think so, but no. That will most likely be almost exactly the same as your previous shader using step. A confusing part of if statements in shaders is most of the time using an if doesn't prevent code from running like it does with CPU side code; both sides get calculated and then it chooses the result.
     
  6. SoxwareInteractive

    SoxwareInteractive

    Joined:
    Jan 31, 2015
    Posts:
    541
    I understand... Thanks for the explanation ;-)