Search Unity

frac() function strangeness

Discussion in 'Shaders' started by xenrik, May 21, 2015.

  1. xenrik

    xenrik

    Joined:
    Nov 10, 2013
    Posts:
    2
    Hi All,

    I've been playing with a noise function as part of a shader I'm using and encountered something I don't understand with regards the frac function, specifically that it is returning the originally passed in number (which is greater than 1), rather than just the fractional part.

    To debug the issue I created a very simple shader:

    Code (CSharp):
    1. Shader "Custom/FracTest" {
    2.     SubShader {
    3.         CGPROGRAM
    4.  
    5.         #pragma surface surf Lambert
    6.         #pragma target 3.0
    7.  
    8.         #include "UnityCG.cginc"
    9.  
    10.         struct Input {
    11.             half3 viewDir;
    12.         };
    13.  
    14.         void surf(Input IN, inout SurfaceOutput o) {
    15.             float4 col = 0;
    16.  
    17.             float n = 1;
    18.             float sinn = sin(n); // ~0.84147
    19.             float hash = sinn * 43758.5453; // ~36821.54621
    20.  
    21.             float floorHash = floor(hash); // 36821
    22.             float fracHash = frac(hash); // should be ~0.54621
    23.  
    24.             float manualFrac = hash - floorHash;
    25.  
    26.             col.r = fracHash == hash ? 1 : 0;
    27.             col.g = floorHash == hash ? 1 : 0;
    28.             col.b = manualFrac == fracHash ? 1 : 0;
    29.  
    30.             o.Albedo = col.rgb;
    31.         }
    32.  
    33.         ENDCG
    34.     }
    35.  
    36.     Fallback "Diffuse"
    37. }
    As you can tell, this simply colours the whole object based on some tests against the result of the functions. When I run this, my object appears red, meaning the fraction value is still equal to the original 'hash' value. If I manually get the fraction it seems to give the correct value (as the object doesn't get any blue component).

    I can't see anything obviously wrong here - I'm guessing I'm just hitting some precision limit inside the frac() function, as if I use smaller numbers it seems to return sensible results; when I manually do it perhaps I'm using higher precision numbers and hence it works?

    Any ideas, or am I making some wrong assumption somewhere?
     
  2. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    Code (CSharp):
    1. col.r = frac( sinn * 43758.5453 ) <= 1;
    2. col.g = frac( 43758.5453 ) <= 1;
    3. col.b = sinn < 0.9;
    Note the interesting results of this!
    As it's not the size of what is input into the frac function. (sinn < 0.9)
    This is also funny:

    Code (CSharp):
    1.  
    2. col.r = 48.5453 == frac(48.5453);
    3. col.g = sin(1.1) == frac(sin(1.1));
    4. col.b = abs(0.5453 - frac(48.5453)) < 0.01;
    5.  
    Note that the frac(hardcoded number) works, yet frac(sin(1.1)) does not.

    MORE Fun stuff:
    Code (CSharp):
    1. float numTest = 3.32;
    2. const float numTest2 = 3.32;
    3. col.g = abs(numTest - frac(numTest)) < 0.01;
    4. col.b = abs(numTest2 - frac(numTest2)) < 0.01;
    (Proof that the shader compiler treats non-const floats declared AND defined in a shader differently.)
    And it seems to tell us that using frac on a non-const variable is not good? (Perhaps it just returns the input when not supported by the GPU/shader model?) Yet the compiler probably handles the const numbers in the compiler instead...
     
  3. xenrik

    xenrik

    Joined:
    Nov 10, 2013
    Posts:
    2
    Thanks for taking the time to play with this - at least I know I not going mad now :p

    I know the compiler does do some optimisation around the detection of dead code (as you would probably expect) so I'm not too surprised that it is also capable of optimising the results of calculations on constants.

    However, if you can't use a frac function on a non-const it kind of defeats the purpose of calling it :p - we already know what the result of the frac will be on a constant :)

    I think you must be right and that at run-time it's deciding that the way I'm using the frac function is invalid/unsupported somehow and just returning the passed-in value.

    I'm working around this for now just using my own frac function - but it does make me a little nervous that I don't know why it's failing however as I could be hitting similar problems in other places that I'm unaware of.

    Is there likely to be any noticeable performance difference between me manually doing the calculation vs the built-in function?
     
  4. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    Well, another fun update,
    I sucessfully used frac in a shader today!
    BUT it was based on data the compiler would have NO chance of guessing...
    My GUESS in this case:
    The compiler doesn't work with hard coded numbers/functions that are used trough multiple steps and then a frac.
    If they are marked as constant (either hard coded in the frac or all variables before it marked as const) it does handle it.
    Summary:
    Unity's shader compiler has a bug most likely! (Where the code is optimized away due to being constant, yet it not doing the frac due to it not being const? Sounds weird, but only THEORY I can come up with...)
     
  5. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Please fire reports at unity :)
     
  6. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    Are unity even the ones who write/modify the shader compiler?
     
  7. echo4papa

    echo4papa

    Joined:
    Mar 26, 2015
    Posts:
    158
    Pretty sure Aras has parts of the shader pipeline on github.