Search Unity

Resolved Output color in surface shader has artifacts when using InstanceID

Discussion in 'Shaders' started by carcasanchez, Jul 7, 2020.

  1. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    177
    Hello there.
    I have encountered a curious situation when working with surface shader that is:
    -I have an array of instanced quads that have to have different colors.
    -I use instanceID to determine it in the surf function like (this is hardcoded, debug code):
    upload_2020-7-7_13-41-13.png
    The result, however:
    upload_2020-7-7_13-41-46.png
    For a reason I can't discern, some of the color of the first instance bleeds to the second (which should be completely green). The float the code returns is assigned directly to the albedo channel, without any other operations involved.

    Initially, I thought it would be a problem of InstanceID, which could have an unstable value or something.
    But I have debugged the values with RWBuffers, and the values are a prefect sucession of 0, 1, 2, 3, etc.

    Which leaves me scratching my head about what the hell could be happening here. The logic seems perfect.
    I usually don't jump into the "it's a unity bug" wagon easily, but I am starting to contemplate the possibility.
    This, or there is some obscure shader tech I am not aware of (could it be a precission error? Seems unlikely, being both comparing values uints).
    Is anyone aware of an issue like this happening, or what could be causing it?

    EDIT: yes, I have tried to compare the values with a margin (like they were floats), and still happens.
    upload_2020-7-7_13-54-26.png upload_2020-7-7_13-54-37.png
     

    Attached Files:

  2. aleksandrk

    aleksandrk

    Unity Technologies

    Joined:
    Jul 3, 2017
    Posts:
    3,023
    Hi!
    Are you by chance rendering two quads at the same position?
    This looks like z-fighting.
     
  3. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    177
    I have checked, and doesn't seem to. All buffers have size two, and I am only processing two instances
     
  4. aleksandrk

    aleksandrk

    Unity Technologies

    Joined:
    Jul 3, 2017
    Posts:
    3,023
    In this case I suggest filing a bug report :)
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The above very much looks like an interpolation problem. If you put a uint into the Input struct, the Surface Shader is just going to interpolate that as a float value.

    Try this:
    Code (csharp):
    1. Input {
    2.   // .. stuff
    3.   float instanceID;
    4. };
    5.  
    6. // vert function
    7. void vert (inout appdata_full v, out Input o)
    8. {
    9.   o.instanceID = (float)v.instanceID + 0.5;
    10. }
    11.  
    12. // surf
    13. uint instanceID = IN.instanceID;
    14. if (instanceID == 0)
    15.   col = float4(1,0,0,1);
    16. if (instanceID == 1)
    17.   col = float4(0,1,0,1);

    However you should just be able to use the built in instancing data rather than passing it yourself, as that gets handled properly by Surface Shaders. Do this w/o any of the extra stuff in the Input struct or custom vertex function to pass your custom instance ID value.
    Code (csharp):
    1. uint instanceID = 0;
    2. #if defined(INSTANCING_ON)
    3. instanceID = unity_InstanceID;
    4. #endif
     
  6. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    177
    Hey! Thank you very much, I have solved the issue with this. My only interrogant is: what does the '+ 0.5' do?
    That snippet of code didn't work, unity_InstanceID was not recognized. This is the reason I pass the ID along the input struct, it was the only way I found to actually get the ID variable. It still bugs me why I cannot access unity_InstanceID directly.
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    It turns the eventual cast from float to uint into a round.

    Floating point math is messy, and interpolating a floating point value from three vertices across a triangle always leads to some instability.
    https://forum.unity.com/threads/using-2-textures-and-mask-from-vertices.625861/#post-4193557

    For most applications of interpolated data, a value being slightly noisy doesn't cause any problems since usually the noise is way smaller than can be represented by the final rendered image. But in this case because "1.0" interpolated across a face can sometimes be less than 1.0 when the value is eventually cast from a float to an integer it is rounded down. Adding 0.5 to "n.0" means the noise is between "n.499999" and "n.500001", which when cast to an integer becomes "n".

    Alternatively you could skip the + 0.5 in the vert function and do it in the surf function instead, or change the line in the surf function to:
    uint instanceID = round(IN.instanceID);


    Yeah, I didn't actually try it, but it doesn't surprise me. Unity's Surface Shaders are a huge pain because they error on code that will absolutely work in the final compiled shader, but it doesn't know that when doing the initial pass of the code used to determine what it needs to generate.
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Actually, it might be even simpler to fix.
    Code (csharp):
    1.             uint instanceID = 0;
    2.             #if defined(UNITY_SUPPORT_INSTANCING) && defined(INSTANCING_ON)
    3.             instanceID = unity_InstanceID;
    4.             #endif
     
  9. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    177
    This compiles, but strangely seems like instanced is not on (or not allowed), as the ouput reveals that instanceID is always 0.
     
    Last edited: Jul 9, 2020
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Tweaked it a bit to get it to work.
    Code (CSharp):
    1. Shader "Custom/InstanceEmissiveColorSurfaceShader" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "black" {}
    5.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    6.         _Metallic ("Metallic", Range(0,1)) = 0.0
    7.     }
    8.  
    9.     SubShader {
    10.         Tags { "RenderType"="Opaque" }
    11.         LOD 200
    12.         CGPROGRAM
    13.  
    14.         #pragma surface surf Standard fullforwardshadows
    15.         #pragma target 3.0
    16.  
    17.         sampler2D _MainTex;
    18.         struct Input {
    19.             float2 uv_MainTex;
    20.         };
    21.  
    22.         half4 _Color;
    23.         half _Glossiness;
    24.         half _Metallic;
    25.  
    26.         void surf (Input IN, inout SurfaceOutputStandard o) {
    27.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    28.  
    29.             uint instanceID = 0;
    30.  
    31.             // keep surface shader from optimzing away the emission color
    32.             #if defined(SHADER_TARGET_SURFACE_ANALYSIS)
    33.             instanceID = 1;
    34.             #endif
    35.  
    36.             #if defined(UNITY_INSTANCING_ENABLED) || defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) || defined(UNITY_STEREO_INSTANCING_ENABLED)
    37.             instanceID = unity_InstanceID;
    38.             #endif
    39.  
    40.             o.Albedo = c.rgb;
    41.             o.Metallic = _Metallic;
    42.             o.Smoothness = _Glossiness;
    43.  
    44.             // set emission color with instanceID bits
    45.             o.Emission = float3(instanceID & 1, instanceID & 2, instanceID & 4);
    46.             o.Alpha = c.a;
    47.         }
    48.         ENDCG
    49.     }
    50.     FallBack "Diffuse"
    51. }
    upload_2020-7-9_11-31-27.png
     
  11. topitsky

    topitsky

    Joined:
    Jan 12, 2016
    Posts:
    100
    This is pretty cool, is there a way to have this render a unique color for each mesh visible? maybe by having a large texture with unique colors?