Search Unity

Noisy vertex color glitch

Discussion in 'Shaders' started by PoshDan, Feb 19, 2019.

  1. PoshDan

    PoshDan

    Joined:
    Feb 19, 2019
    Posts:
    8
    Hey.
    I'm looking to make a shader that uses vertex colors as its main element, with a special case for a "color key" effect (so i can change some colors on the fly with code).
    Normally I'd use the alpha channel of vertex colors for intensity of the color key (i dont plan on using inherently-translucent meshes), but it's not easy to do alpha with my new workflow so I'm trying to use a "magic color" solution. (ie full white faces are color keyed, everything else displays its normal vertex colors)

    So I've managed to get a vertex color shader working but for some reason the source colors are altered in a way that corrupts these magic colors in a noisy pattern.

    Here's how the model looks with raw vertex colors (the top white border has 100% white vcolor)
    https://imgur.com/a/vGlG6Ca
    Here's how it looks with the color keying
    https://imgur.com/a/YS6zZ1H
    And here's the shader code I have
    https://pastebin.com/7xANDtTD

    I've also tried a simpler fragment shader approach but got the same result
    https://pastebin.com/BWCHm5nN

    Moving around with a flying camera it loosely resembles z-fighting (doesn't make sense, there's only one mesh and no faces in it overlap, i double checked for sanity). The faces are properly culled from the back too.
    In fact, the pattern seems related to distance from viewing point BUT maybe also normals?
    https://imgur.com/a/EaumP58
    https://imgur.com/a/fIqmkqq

    I've tried literally passing float4(1.0, 1.0, 1.0, 1.0) from the vertex shader (still affected, but on the whole mesh ofc)
    The only time it works as intended is if I change the if statement to look for values above 0.99 or something, but that's obviously a really poor "solution".

    Any help would be greatly appreciated, thanks!
    I'm using Unity 2018.3.3f1
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    That is actually the solution.

    The problem is the combination of floating point math and barycentric interpolation means that "1.0" will can slightly below and slightly above "1.0" across the face even when all 3 vertices of a triangle are the same value.

    For example, a point on a triangle that's right near the center of the triangle will be roughly equal to:
    VertA * (1/3) + VertB * (1/3) + VertC * (1/3)

    Using arithmetic we know those should add up to 1.0 if all 3 vertex values are 1.0, but with floating point math it might equal 0.9999999, or 1.000001!

    This shader passes a material property value via the vertices and then tests if the value is equal (green), less than (red), or greater than (blue) the material's property.
    upload_2019-2-18_20-16-3.png
    Code (CSharp):
    1. Shader "Interpolation Error"
    2. {
    3.     Properties {
    4.         _Value ("Value", Float) = 3.0
    5.     }
    6.  
    7.     SubShader
    8.     {
    9.         Tags { "Queue"="Transparent" }
    10.      
    11.         Pass {
    12.  
    13.             CGPROGRAM
    14.             #pragma vertex vert
    15.             #pragma fragment frag
    16.  
    17.             #include "UnityCG.cginc"
    18.  
    19.             struct v2f
    20.             {
    21.                 float4 pos : SV_POSITION;
    22.                 // nointerpolation // uncomment this line to remove interpolation from the below value
    23.                 float value : TEXCOORD0;
    24.             };
    25.  
    26.             float _Value;
    27.  
    28.             v2f vert (appdata_full v)
    29.             {
    30.                 v2f o;
    31.                 o.pos = UnityObjectToClipPos(v.vertex);
    32.                 o.value = _Value;
    33.                 return o;
    34.             }
    35.  
    36.             float4 frag (v2f i) : SV_Target
    37.             {
    38.                 float4 col = float4(0,1,0,1);
    39.                 if (i.value < _Value)
    40.                     col = float4(1,0,0,1);
    41.                 else if (i.value > _Value)
    42.                     col = float4(0,0,1,1);
    43.                 return col;
    44.             }
    45.  
    46.             ENDCG
    47.         }
    48.     }
    49. }
    So you really do need to do something like " >= 0.99". I'd suggest using something like this:

    // color values are 8 bit, so they are limited to steps of 1/255
    float interpolationErrorFudge = 0.5 / 255; // half a step of fudge factor
    // tests if all component values are > 0.0
    if( all(i.color.rgb - (1.0 - interpolationErrorFudge)) )
    {
    // do stuff
    }
     
    PoshDan and Excilyano like this.
  3. PoshDan

    PoshDan

    Joined:
    Feb 19, 2019
    Posts:
    8
    Thank you for the explanation!