Search Unity

GPU Instancing with Unlit shader broken because "affected by different forward lights"

Discussion in 'Shaders' started by stephero, Nov 13, 2018.

  1. stephero

    stephero

    Joined:
    Feb 8, 2016
    Posts:
    83
    Hi guys,
    I am experiencing an annoying issue when trying to create a Unlit shader supporting GPU Instancing.
    It works fine if I don't put any lights, or if all my objects are affected by the same lights. In this example, my two red sphere use my custom unlit shader and are properly GPU instanced :
    Screenshot_1.png

    But if I put a light (like a spotlight) which only affects one of my sphere, it will break the instancing.
    Here is the setup in the editor scene view :
    Screenshot_12.png

    And the broken instancing result:
    Screenshot_13.png

    Here is the test shader I am using :
    Code (CSharp):
    1. Shader "Unlit/CustomUnlit"
    2. {
    3.     Properties { }
    4.  
    5.     SubShader
    6.     {
    7.         LOD 100
    8.    
    9.         Pass
    10.         {
    11.             ZWrite Off
    12.  
    13.             CGPROGRAM
    14.             #pragma vertex vert
    15.             #pragma fragment frag
    16.             #pragma multi_compile_fog
    17.             #pragma multi_compile_instancing
    18.  
    19.  
    20.             #include "UnityCG.cginc"
    21.      
    22.             struct v2f
    23.             {
    24.                 UNITY_FOG_COORDS(1)
    25.                 float4 vertex : SV_POSITION;
    26.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    27.             };
    28.        
    29.             v2f vert (appdata_base v)
    30.             {
    31.                 v2f o;
    32.                 UNITY_SETUP_INSTANCE_ID(v);
    33.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    34.  
    35.                 o.vertex = UnityObjectToClipPos(v.vertex);
    36.                 UNITY_TRANSFER_FOG(o,o.vertex);
    37.                 return o;
    38.             }
    39.        
    40.             fixed4 frag (v2f i) : SV_Target
    41.             {
    42.                 UNITY_SETUP_INSTANCE_ID(i);
    43.  
    44.                 fixed4 col = float4(1,0,0,1);
    45.                 UNITY_APPLY_FOG(i.fogCoord, col);
    46.                 return col;
    47.             }
    48.             ENDCG
    49.         }
    50.     }
    51. }
    52.  
    I am using a deferred camera. But you can see in the Frame Debugger that after the "RenderDeferred" pass, it draws an additional "RenderForwardOpaque" pass which seems to be the issue. Because if I use a standard build-in shader, it works and the "RenderForwardOpaque" doesn't appear in the Frame Debugger:
    Screenshot_14.png

    Note that I don't care about lighting on my Unlit shader. I don't want it to cast or receive shadows/lights.

    What am I missing?
    Thanks
     
    Last edited: Nov 13, 2018
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    7,677
    This is a long time "quirk" (aka bug) in Unity with unlit objects. For forward rendered objects, Unity sorts them internally depending on the real time lights that overlap them. Unity doesn't actually check to see if a shader uses those lights or not before doing this.

    If you're using the deferred rendering path, one work around is to make your unlit shader deferred compatible.
    Code (CSharp):
    1. Shader "Unlit/Deferred Unlit"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Color", Color) = (1,1,1,1)
    6.         _MainTex ("Texture", 2D) = "white" {}
    7.     }
    8.     SubShader
    9.     {
    10.         Tags { "RenderType"="Opaque" }
    11.         LOD 100
    12.  
    13.         Pass
    14.         {
    15.             Tags { "LightMode"="Deferred" }
    16.  
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             #pragma multi_compile _ UNITY_HDR_ON
    21.             #pragma multi_compile_instancing
    22.            
    23.             #include "UnityCG.cginc"
    24.  
    25.             struct v2f
    26.             {
    27.                 float4 pos : SV_POSITION;
    28.                 float2 uv : TEXCOORD0;
    29.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    30.             };
    31.  
    32.             sampler2D _MainTex;
    33.             float4 _MainTex_ST;
    34.            
    35.             UNITY_INSTANCING_BUFFER_START(Props)
    36.                 UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
    37.             UNITY_INSTANCING_BUFFER_END(Props)
    38.            
    39.             v2f vert (appdata_full v)
    40.             {
    41.                 v2f o;
    42.                 UNITY_SETUP_INSTANCE_ID(v);
    43.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    44.  
    45.                 o.pos = UnityObjectToClipPos(v.vertex);
    46.                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    47.                 return o;
    48.             }
    49.            
    50.             void frag (v2f i,
    51.                 out half4 outDiffuse : SV_Target0,           // RT0: diffuse color (rgb), occlusion (a)
    52.                 out half4 outSpecSmoothness : SV_Target1,    // RT1: spec color (rgb), smoothness (a)
    53.                 out half4 outNormal : SV_Target2,            // RT2: normal (rgb), --unused, very low precision-- (a)
    54.                 out half4 outEmission : SV_Target3           // RT3: emission (rgb), --unused-- (a)
    55.                 )
    56.             {
    57.                 // fill the main gbuffers with empty data
    58.                 outDiffuse = half4(0, 0, 0, 1);
    59.                 outSpecSmoothness = half4(0, 0, 0, 0);
    60.                 outNormal = half4(0, 1, 0, 1);
    61.  
    62.                 UNITY_SETUP_INSTANCE_ID(i);
    63.  
    64.                 // color
    65.                 fixed4 col = tex2D(_MainTex, i.uv) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
    66.  
    67.                 // support for deferred HDR
    68.                 #ifndef UNITY_HDR_ON
    69.                     col.rgb = exp2(-col.rgb);
    70.                 #endif
    71.                 // write "unlit" color to emission buffer, which ends up being the frame buffer
    72.                 outEmission = half4(col.rgb, 1);
    73.             }
    74.             ENDCG
    75.         }
    76.     }
    77. }
    Deferred lighting doesn't care about real time lights when writing the gbuffers, so it doesn't do the per-light sorting that forward rendering does. Thus you can have your objects correctly instance regardless of the lights you have in your scene. However if you use light probes it'll still break unless you remember to turn off light probes on the renderer components.

    For a more general case solution, you'll have to manually draw your objects manually using DrawMeshInstanced to avoid lighting entirely.
     
  3. stephero

    stephero

    Joined:
    Feb 8, 2016
    Posts:
    83
    Many thanks bgolus for your answer. It would be useful to add this information in the documentation, because I don't remember having read something about it.

    I tried your shader workaround and it works. To make it work also when using Forward rendering, do I have to simply add a "LightMode"="ForwardBase" pass which basically does the same thing? But there is no way to make GPU Instancing work in my case (1 light affects one of my object) with Forward rendering, right?

    Thanks for your suggestion about using DrawMeshInstanced.
    The cool thing about using the "standard rendering pipeline" is that Unity handles a lot of things such as culling or ordering. Since DrawMeshInstanced must be called in one shot for all the instanced objects, I would need to handle culling myself for example, which makes it much more complicated.

    Thanks
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    7,677
    You'd think so, but no. Forward rendered objects are always separated by overlapping dynamic lights. The forward base pass can still have per vertex lights, which still requires unique values per object. It'd be nice if Unity had a way mark if a shader doesn't need any localised lighting information ... but it doesn't.
     
  5. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    119
    I got similar problem but with Particle/StandardUnlit shader.
    In deffered rendering particles were affected by lights. (draw calls)
    Some solution is to move particles to different layer and on every light disable this layer.