Search Unity

Visualizing the number of lights on an object

Discussion in 'Shaders' started by CharmGames, Jul 14, 2020.

  1. CharmGames

    CharmGames

    Joined:
    Jun 1, 2017
    Posts:
    9
    I'm trying to implement a debug shader to visualize the number of lights a given object is receiving. I have a basic solution: an additive fragment shader pass that tints the fragment a little each time it runs. However, this does not lead to very good differentiation between light counts, and it only works up to the 4 pixel light cap (see attached image, with 0-4 lights left-right)

    Ideally, different light counts would be displayed as very different colors, but in order to achieve this I would need information about the existing color inside the fragment shader. I might be able to do this by reading/writing a render texture in each pass, but I'm not sure if I'm on the right track.

    Any suggestions/examples would be greatly appreciated.


    For reference, here is the basic shader I'm currently using:

    Code (CSharp):
    1. Shader "Unlit/LightCountVisualization"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "black" {}
    6.     }
    7.    
    8.     SubShader
    9.     {
    10.         Tags { "RenderType"="Opaque" }
    11.        
    12.         LOD 100
    13.  
    14.         // Render base objects black
    15.         Pass
    16.         {
    17.             Tags { "LightMode" = "ForwardBase" }
    18.             Blend One OneMinusSrcAlpha
    19.             CGPROGRAM
    20.             #pragma vertex vert
    21.             #pragma fragment frag
    22.             // make fog work
    23.             #pragma multi_compile_fog
    24.  
    25.             #include "UnityCG.cginc"
    26.  
    27.             struct appdata
    28.             {
    29.                 float4 vertex : POSITION;
    30.                 float2 uv : TEXCOORD0;
    31.                 float4 color: COLOR;
    32.             };
    33.  
    34.             struct v2f
    35.             {
    36.                 float2 uv : TEXCOORD0;
    37.                 UNITY_FOG_COORDS(1)
    38.                 float4 vertex : SV_POSITION;
    39.                 float4 color: COLOR;
    40.             };
    41.  
    42.             sampler2D _MainTex;
    43.             float4 _MainTex_ST;
    44.  
    45.             v2f vert (appdata v)
    46.             {
    47.                 v2f o;
    48.                 o.vertex = UnityObjectToClipPos(v.vertex);
    49.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    50.                 o.color = v.color;
    51.                 UNITY_TRANSFER_FOG(o,o.vertex);
    52.                 return o;
    53.             }
    54.  
    55.             fixed4 frag (v2f i) : SV_Target
    56.             {
    57.                 fixed4 col = (fixed4(0,0,0,1));
    58.                
    59.                 return col;
    60.             }
    61.             ENDCG
    62.         }
    63.         // Add tint for each light
    64.         Pass
    65.         {
    66.             Tags { "LightMode" = "ForwardAdd" }
    67.             Blend SrcColor One
    68.             CGPROGRAM
    69.             #pragma vertex vert
    70.             #pragma fragment frag
    71.  
    72.             #include "UnityCG.cginc"
    73.  
    74.             struct appdata
    75.             {
    76.                 float4 vertex : POSITION;
    77.                 float2 uv : TEXCOORD0;
    78.                 float4 color: COLOR;
    79.             };
    80.  
    81.             struct v2f
    82.             {
    83.                 float2 uv : TEXCOORD0;
    84.                 float4 vertex : SV_POSITION;
    85.                 float4 color: COLOR;
    86.             };
    87.  
    88.             sampler2D _MainTex;
    89.             float4 _MainTex_ST;
    90.  
    91.             v2f vert (appdata v)
    92.             {
    93.                 v2f o;
    94.                 o.vertex = UnityObjectToClipPos(v.vertex);
    95.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    96.                 o.color = v.color;
    97.                 UNITY_TRANSFER_FOG(o,o.vertex);
    98.                 return o;
    99.             }
    100.  
    101.             fixed4 frag (v2f i) : SV_Target
    102.             {
    103.                 fixed4 col = (fixed4(0.25,0.1,0.1,0));
    104.                
    105.                 return col;
    106.             }
    107.             ENDCG
    108.         }
    109.     }
    110. }
     

    Attached Files:

  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    You've got the right idea, but the important thing to understand is there's no way to do this in a way that's going to be pretty, and that's okay.

    Really all you need to care about is that you're outputting useful information. In this case the light count. You can easily count up to 255 lights by outputting 1.0/255.0 as the color value instead of 0.25 of 0.1.

    But obviously that doesn't get you very easily viewable data, as it's a gradient from 0 to 1.0 over very small steps. The trick is you need another shader that reads that output and recolors it. You can do this as a post process, or (though I only recommend this for debugging, not a shader you use in a shipping product) using a grab pass.
    https://docs.unity3d.com/Manual/SL-GrabPass.html

    So the idea is you have your forward add pass output
    fixed(1.0/255.0, 0, 0, 1)
    . Then you have a grab pass:
    Code (csharp):
    1. GrabPass { }
    Then you have a shader that reads the grab pass contents, decodes the number of lights, and colors it however you want.
    Code (csharp):
    1.         Pass {
    2.             Blend One Zero
    3.  
    4.             CGPROGRAM
    5.             #pragma vertex vert
    6.             #pragma fragment frag
    7.             #include "UnityCG.cginc"
    8.  
    9.             struct v2f
    10.             {
    11.                 float4 grabPos : TEXCOORD0;
    12.                 float4 pos : SV_POSITION;
    13.             };
    14.  
    15.             v2f vert(appdata_base v) {
    16.                 v2f o;
    17.                 // use UnityObjectToClipPos from UnityCG.cginc to calculate
    18.                 // the clip-space of the vertex
    19.                 o.pos = UnityObjectToClipPos(v.vertex);
    20.                 // use ComputeGrabScreenPos function from UnityCG.cginc
    21.                 // to get the correct texture coordinate
    22.                 o.grabPos = ComputeGrabScreenPos(o.pos);
    23.                 return o;
    24.             }
    25.  
    26.             sampler2D _GrabTexture;
    27.  
    28.             half4 frag(v2f i) : SV_Target
    29.             {
    30.                 // get light count from the grab pass
    31.                 uint numLights = round(tex2Dproj(_GrabTexture, i.grabPos).r * 255.0);
    32.  
    33.                 // convert count to color by checking the integer bit values
    34.                 half3 color = half3(numLights & 1, numLights & 2, numLights & 4);
    35.                 // 0 = black
    36.                 // 1 = red
    37.                 // 2 = green
    38.                 // 3 = yellow (red & green)
    39.                 // 4 = blue
    40.                 // 5 = purple (red & blue)
    41.                 // 6 = cyan (green & blue)
    42.                 // 7 = white
    43.                 // 8 = black again and repeats, ish
    44.  
    45.                 return half4(color, 1.0);
    46.             }
    47.             ENDCG
    48.         }
    In the above I'm converting the number of lights to a color by using the raw bits of the uint itself, but you could also do something like divide the numLights by 4 so the color changes after 4 lights, and modify get a gradient, like:
    Code (csharp):
    1. half3 color = half3((numLights/4) & 1, (numLights/4) & 2, (numLights/4) & 4);
    2. half brightness = pow(frac((float)numLights / 4.0)  + 0.33, 2.2);
    3. return half4(color * brightness, 1.0);
    Or you could sample a gradient texture, or a texture flipbook with some numbers to display the count, or you can use this function (if you convert it from glsl to hlsl) to render numbers without any texture.
    https://www.shadertoy.com/view/4sBSWW
     
  3. CharmGames

    CharmGames

    Joined:
    Jun 1, 2017
    Posts:
    9
    This is perfect, thank you so much! There's just one other issue - 4 of the lights in the scene color in a sort of bounding box, rather than only coloring the geo they are illuminating. I suspect this has something to do with pixel vs vertex lights, but I'm not sure how to resolve it.
     

    Attached Files:

  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    You can't. That's how Unity's lighting works. When rendering additive lights it only renders a screen space rectangular area that the light's bounds cover.
     
  5. CharmGames

    CharmGames

    Joined:
    Jun 1, 2017
    Posts:
    9
    That's a shame - the lights on the left and right side of that screenshot (the ones illuminating the green cube and the red cylinder) are behaving the way I would want ... there's no way to get that behavior for the other lights?
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    There is not, no. What you have above is accurately showing how many lights are affecting each pixel of that object. The rest of the mesh is not rendered, and there's no way to disable that functionality to render the whole mesh instead.