Search Unity

Projector shader doesn't receive shadows

Discussion in 'Shaders' started by SozoDev, Jul 30, 2019.

  1. SozoDev


    Nov 15, 2016
    I have a shader based on the projector shader from the Standard Assets. I use the shader to project an image onto the ground plane - like a decal. The image consists of a circle with transparent background. The projection works fine but the decal doesn't receive any shadows. See image below.

    I have tried examples to get the shader to receive shadows (mainly from here: - but it doesn't do anything. Is it possible to do? Blending options almost works, but then I get the original red texture blended with the decal, which I don't want. I removed transparency effects from the shader when I read that shadows can't be rendered on transparent objects, so I moved it to the geometry queue and made the shader clip the transparent part of the image instead. I've tried adding the light attenuation (in code example) from the link above as well as shadow attenuation from other examples. It might be that the projected texture doesn't work with what I'm trying to do but my knowledge is limited here.

    Code (CSharp):
    1. Shader "Custom/ProjectorShadow" {
    2.     Properties{
    3.         _MainTex("Image", 2D) = "white" {}
    4.     }
    5.     Subshader{
    6.         Tags{ "Queue" = "Geometry"
    7.               "RenderType"="Opaque"
    8.         }
    9.         Pass{          
    10.             Tags { "LightMode"="ForwardBase"}
    11.             //Blend Off
    12.             //ColorMask RGB          
    13.             //Offset -1, -1
    15.             CGPROGRAM
    16.             #pragma vertex vert
    17.             #pragma fragment frag          
    18.             #pragma multi_compile_fwdbase
    19.             #include "UnityCG.cginc"
    20.             #include "Lighting.cginc"
    21.             #include "AutoLight.cginc"
    23.             struct v2f {
    24.                 float4 uvShadow : TEXCOORD0;                          
    25.                 float4 pos : SV_POSITION;                
    26.                 LIGHTING_COORDS(1, 2)
    27.             };
    29.             float4x4 unity_Projector;
    30.             float4x4 unity_ProjectorClip;
    32.             v2f vert(float4 vertex : POSITION)
    33.             {
    34.                 v2f o;
    35.                 o.pos = UnityObjectToClipPos(vertex);
    36.                 o.uvShadow = mul(unity_Projector, vertex);                
    37.                 TRANSFER_VERTEX_TO_FRAGMENT(o);
    38.                 return o;
    39.             }
    41.             sampler2D _MainTex;
    43.             fixed4 frag(v2f i) : SV_Target
    44.             {              
    45.                 fixed4 texS = tex2Dproj(_MainTex, UNITY_PROJ_COORD(i.uvShadow));
    46.                 if (texS.a < 0.5)
    47.                     discard;
    48.                 float attenuation = LIGHT_ATTENUATION(i);
    49.                 texS.rgb *= attenuation;              
    50.                 return texS;
    51.             }
    52.             ENDCG
    53.         }
    54.     }
    55.     FallBack "Diffuse"
    56. }
  2. bgolus


    Dec 7, 2012
    Not possible. Unity projectors don't receive any lighting or shadowing information. They also ignore any queue you've set and always draw between the opaque queue and transparent queue range (effectively queue 2500.5).

    Protectors are a legacy thing that should be considered deprecated. There's a reason why there's no menu option to create a projector gameobject, and why neither of the SRPs support it.
  3. SozoDev


    Nov 15, 2016
    Thanks for the response. So if it's not possible in the method above, do you have another solution you can point me to? Basically I want to dynamically add textures to certain areas as an overlay (with receive shadows). The difficulty is the overlay might span across different objects on the ground, hence the projection method I tried. An alternative I came across was to have a mesh with my texture and then snap the vertices to conform to the ground plane beneath it.
  4. bgolus


    Dec 7, 2012
    Decals are a complicated topic.

    The legacy Projector works by doing an overlap test between the projector's frustum and the bounding boxes of renderers in the scene. Any renderers it overlaps with get drawn again using the projector's material. This means a lot of re-rendering of objects, potentially large ones that have a lot of vertices outside the projector's frustum and / or might cover a large part of the screen. Technically you could recreate this system manually if you were so inclined.

    You could extend that system to actually do frustum culling against the individual triangles and build a custom mesh for the projector of only those triangles that are needed. This was common a decade or more ago, especially for static decals, but with high polygon meshes and fast GPUs it isn't usually faster than the "dumb" option.

    Using a quad or other simple geometry shape and placing it just above a surface is a common technique. Works great if the surfaces you're projecting onto are generally flat, or the decals are small enough that it won't be obvious with they clip or hang over edges. This is the technique used in some of the earliest 3D games to do shadows, or impact effects, and is still used today for mobile games, and even big AAA console games like Overwatch. For uneven surfaces you might be able to get away with doing a hand full of raycasts and finding a "good enough" position.

    Deferred decals are the more common way this is done in modern games over the last decade plus. Deferred rendering isn't itself required, but makes some things easier. The are example projects and assets on the store that can do this, a few of which include both deferred and forward shading support. The basic idea is to project onto the camera depth texture instead of re-rendering the scene meshes, or just using a quad and its UVs. The HDRP uses this for deferred objects with the new Decal Projector.

    Lastly there are in-shader options where the objects you want to project onto themselves use shaders which can display the decals. The HDRP uses something like this for forward rendered objects.

    Each option has pros and cons.
    Goyoman likes this.