Search Unity

Reset UV animation value without conditional

Discussion in 'Shaders' started by Glaswyll, May 3, 2019.

  1. Glaswyll

    Glaswyll

    Joined:
    Feb 13, 2014
    Posts:
    103
    Hello,

    I haven't been able to find an answer to this yet, mostly because I'm still too new to shader writing to know what question to ask.

    I've noticed odd tearing issues with UV animation when leaving the editor running for long periods of time, more than a day.
    shader-tearing-01.jpg

    I'm using something like this:
    Code (CSharp):
    1. o.uv.x += _Time.x * _SpriteAnimSpeedX;
    When I restart the editor, it stops behaving like that and goes back to being smoothly rendered. My suspicion is the number is so high, it's causing errors. The other thing that makes me think the number is too high is it doesn't happen when using this:
    Code (CSharp):
    1. o.uv.x += sin(_Time.x * _SpriteAnimSpeedX);
    My question is: Is there a way to reset the UV value back to zero without using a conditional?

    Thanks!
     
  2. Glaswyll

    Glaswyll

    Joined:
    Feb 13, 2014
    Posts:
    103
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Your suspicions are accurate. You're running into floating point precision issues when you scroll a UV for too long.

    A 32 bit floating point number's precision depends on the value it's representing. A value between 0.0 and 1.0 will have a precision in the tens of millionths or better. For every range of values between powers of 2 the precision drops in half. So after one day of adding an unmodified _Time.y (unscaled time) there's only enough precision to represent 128 unique values between each full integer, which means significant and obvious banding.

    But when it comes to UVs you usually really only care about the 0.0 to 1.0 range as the texture repeats anyway, so if you can focus on only the fractional part of the offset value you would avoid the reduction in precision. That post you linked to mentions fract(), which is the GLSL equivalent of HLSL's frac(), and HLSL is what Unity's shaders are written in unless you're explicitly writing GLSLPROGRAM shaders.
    https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-frac

    Note, regardless of what a lot of forum posts here say, or even Unity's own documentation and .shader files might suggest, Unity does not use Nvidia Cg anymore. Everything in CGPROGRAM blocks is written using HLSL. However the Cg documentation site is still excellent for giving good examples of what several common shared functions do and show example implementations. Cg and HLSL are mostly compatible, with the vast majority of functions existing and functioning identically between both, so it's a handy site to use. Just know if something doesn't seem to be working like you think, you may want to check the official HLSL documentation to make sure they're actually the same.
    https://developer.download.nvidia.com/cg/frac.html


    So, that'll fix the precision issue in terms of the texture stretching, but there will still be visual issues with panning a texture for multiple days. While the texture won't start getting banding and tearing issues, the panning will start to stutter. This is because the offset & time values themselves will eventually start having precision issues. So the same 1 day of _Time.y example from above, the texture can only be panned in steps of 1/128. That's enough to still be smooth, though some amount of judder from each step not being quite the same distance might start to show. After a week you'll start to notice the scrolling is effectively running at a lower framerate as there isn't enough precision in _Time.y to update to unique values for every 30th of a second. The only solution to this is to not use _Time, but rather use a c# script and Time.deltaTime to accumulate and wrap the offset there. Even in c# the Time.timeSinceLevelLoad and similar values are 32 bit floats, so will have precision issues running for over a week.

    BTW, the tearing is caused by floating point precision during vertex interpolation. The UV position each fragment gets is a barycentric interpolation of the 3 vertices of the triangle being rendered, so you're dealing with the additional imprecisions of multiple floating point values being multiplied and added together.
     
    Glaswyll likes this.
  4. Glaswyll

    Glaswyll

    Joined:
    Feb 13, 2014
    Posts:
    103
    The thorough response is appreciated, thanks! I've been pleasantly surprised at the power of custom shaders and only recently bit the bullet to learn them. Took awhile to wrap my head around the 0.0 - 1.0 thing.