Search Unity

Strange line artifact when doing %1 on uv coordinates

Discussion in 'Shaders' started by fredlllll, Aug 7, 2018.

  1. fredlllll

    fredlllll

    Joined:
    Nov 20, 2014
    Posts:
    13
    ok so i have a shader and it is necessary to do a modulus 1 on an uv coordinate. but for some weird reason i get this artifact at the 0 point when doing this. im working of the surface shader template. running unity 2018.2.2f1 on windows 10 64 bit



    for demonstration this is done in a less complex shader to isolate the issue. code for shader is here:

    Code (HLSL):
    1.  
    2. Shader "Custom/bug" {
    3.    Properties {
    4.        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.        [Toggle(ENABLE_BUG)] _Bug ("Bug", Float) = 0
    6.        [Toggle(ENABLE_UV)] _UV("UV",Float) = 0
    7.    }
    8.    SubShader {
    9.        Tags { "RenderType"="Opaque" }
    10.        LOD 200
    11.  
    12.        CGPROGRAM
    13.        // Physically based Standard lighting model, and enable shadows on all light types
    14.        #pragma surface surf Standard fullforwardshadows
    15.        #pragma shader_feature ENABLE_BUG
    16.        #pragma shader_feature ENABLE_UV
    17.  
    18.        // Use shader model 3.0 target, to get nicer looking lighting
    19.        #pragma target 3.0
    20.  
    21.        sampler2D _MainTex;
    22.  
    23.        struct Input {
    24.            float2 uv_MainTex;
    25.        };
    26.  
    27.        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
    28.        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
    29.        // #pragma instancing_options assumeuniformscaling
    30.        UNITY_INSTANCING_BUFFER_START(Props)
    31.            // put more per-instance properties here
    32.        UNITY_INSTANCING_BUFFER_END(Props)
    33.  
    34.        void surf (Input IN, inout SurfaceOutputStandard o) {
    35.            float t = _Time.y;
    36.            float2 uv = IN.uv_MainTex;
    37.            uv.x += t/5;
    38. #ifdef ENABLE_BUG
    39.            uv.x = uv.x % 1;
    40. #endif
    41.  
    42. #ifdef ENABLE_UV
    43.            fixed4 c = fixed4(uv, 0, 1);
    44. #else
    45.            fixed4 c = tex2D (_MainTex, uv);
    46. #endif
    47.            o.Albedo = c.rgb;
    48.            o.Alpha = c.a;
    49.        }
    50.        ENDCG
    51.    }
    52.    FallBack "Diffuse"
    53. }
    54.  
    55.  
    i added a convenient method to turn the bug on an off using shader variants and the toggle. the only difference is adding uv.x = uv.x % 1; the same artifact appears when using fmod, or a custom modulus implementation with floor or so. i also added display of uv coordinates as a toggle so you can see whats in the uvs, but i cant see a problem there.

    im really stumped about this issue. the color of the line reflects colors of the texture that is scrolled (not very apparent in my demo picture, but its true with others). could this be a bug that only my gpu(hd6950) has? can somebody confirm this on his hardware?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,338
    Not a bug, working as intended. The GPU is doing exactly what you're telling it to do.

    The problem comes from how GPUs determine what mipmap level to use for a texture. The short version is the GPU looks to see how much the UV changes from one pixel to the one next to it, and from that it figures out the best mip level to sample the texture at. These are per pixel derivatives. Since you're using a modulo on the UVs, on one pixel the UV x value might be 0.99, and the next 0.01. So between those two pixels as far as the GPU knows the entire texture has to be shown, so it drops down to the lowest available mip level.

    As for why it's not a constant line, derivatives are calculated for each 2x2 set of pixels. These are called pixel quads. If the modulo happens between two of these quads, the derivatives are don't see the sudden break in the UVs. If the modulo happens within a quad, then you get the above.

    Three solutions.
    1. Disable mip maps. This is the brute force approach. Effective, but ugly if you need minification (if you display the texture on the screen at a resolution lower than the texture itself.
    2. Use tex2Dlod and calculate the appropriate mip level yourself.
    3. Use tex2Dgrad and pass in the unmodified UV derivatives.

    I like option 3 as it's relatively simple, even if it sounds scary. Though be warned it doesn't work on all mobile devices.

    float2 uv_ddx = ddx(IN.uv_MainTex);
    float2 uv_ddy = ddy(IN.uv_MainTex);
    float2 uv = frac(IN.uv_MainTex); // same as % 1
    fixed4 c = tex2Dgrad(_MainTex, uv, uv_ddx, uv_ddy);
     
    forestrf likes this.
  3. fredlllll

    fredlllll

    Joined:
    Nov 20, 2014
    Posts:
    13
    thanks. this does indeed solve the issue. i learned writing shaders on older hardware that didnt automatically get the correct mipmap and sample that instead. i just expected the shader to work pixel by pixel without any consideration of neighbors, but i guess surface shaders work different

    mobile is not my target platform, and if, i can still go the route of disabling mip maps
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,338
    Nothing to do with Surface Shaders. Surface Shaders are just vertex fragment shader generators that Unity has. This is also to the best of my knowledge how GPUs have chosen the mip level since mip map support has existed on GPUs. I'm very curious what hardware, and what kind of shaders you were working with.
     
  5. fredlllll

    fredlllll

    Joined:
    Nov 20, 2014
    Posts:
    13
  6. fredlllll

    fredlllll

    Joined:
    Nov 20, 2014
    Posts:
    13
    btw, i just noticed that the mip levels are pretty agressive with your solution, it looks kinda blurry even up close, and it gets very apparent that mipmaps are used when moving away ~10 units

    is there a way to make it less agressive?
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,338
    If you're using the code above exactly as is, it should look identical to using tex2D with out the frac. If you're applying any scale to the UVs, you need to do that before getting the derivatives, or scale the derivatives the same amount.
     
  8. fredlllll

    fredlllll

    Joined:
    Nov 20, 2014
    Posts:
    13
    ah yes with scaling it too it works now, thanks
     
  9. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Hi !

    I'm facing the same issue in a shader graph, and tried your fix, but it seems to not solve my case.
    Could you please tell me if I've made a mistake ?
     

    Attached Files:

  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,338
    Note how in my example code it does
    frac()
    after getting the
    ddx()
    and
    ddy()
    values. That is explicitly required. You're only passing in the already repeating UVs at which point it's too late to fix it.
     
  11. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Yeah ! I slept over it and came to the same conclusion this morning ;)
    Thanks for the confirmation :)

    So now I have the borders fixed... but the mipmaps filtering is also lost when zooming out (thus giving the same visual result as is the mimaps where disabled). Is it the normal result ?
     
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,338
    The UVs you pass to the
    ddx()
    and
    ddy()
    calls should to be exactly the same UVs as being used for sampling the texture, just without
    frac()
    . That means if the UVs used for sampling are being scaled, the UVs passed to those functions need to be scaled too.

    Going by the picture, it would appear the starting UVs are being multiplied by 4.
     
    Alesk likes this.
  13. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,338
    However, I suspect the rest of the code you’re not showing is doing something more like selecting a tile from an atlas. That’s going to cause more problems. There are some threads on the forum that already dive pretty deep into the problems there.
     
    Alesk likes this.
  14. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    You're right, I'm trying to display tiles extracted from an atlas.
    I'll dig in the forum to find the other topics about it... But if you have one in mind, I'm interested ;)

    Thanks for your help !
     
    Last edited: May 18, 2021