frac(x) results in strange artifacts when x is close to zero

I am using a cg shader to create a white grid on my terrain. The terrain iteself consists of tiles where each tile is 4x4 units big. I’m using this fact to calculate locale UV coordinates for each tile to draw the content of a grid texture. This grid texture’s size is only 128x128 pixels. It’s border consists of white pixels and the middle part is transparent.

The calculation of those local uv coordinates is done by using frac(x). However, it results in artifacts on the transition to neighbouring tiles. Disabling the generation of MipMaps for the grid texture makes those artifacts disappear. I still don’t understand why those MipMaps are causing this.

Do you know why? Can I avoid this somehow?

Shader "Custom/World"
{
    Properties
    {
        _Color ("Main Color", Color)            = (0.5,0.5,0.5,1)
        _MainTex ("Base (RGB) Trans (A)", 2D)   = "white" {}
        _Grid ("Base (RGA) Trans (A)", 2D)      = "white" {}
        _Cutoff ("Alpha cutoff", Range(0,1))    = 0.5
    }

    SubShader
    {
        Tags
        {
            "Queue"           = "AlphaTest"
            "IgnoreProjector" = "True"
            "RenderType"      = "TransparentCutout"
        }
        LOD 200
      
        CGPROGRAM
        #pragma surface surf Lambert alphatest:_Cutoff

        sampler2D _MainTex;
        sampler2D _Grid;
        fixed4 _Color;

        struct Input
        {
            float2 uv_MainTex;
            float3 worldNormal;
            float3 worldPos;
        };

        float2 getGridUV(float3 worldPos)
        {
            float u = frac(worldPos.x / 4.0); // a tile is 4 units wide
            float v = frac(worldPos.z / 4.0); // and also 4 units deep

            return float2(u, v);
        }

        void surf(Input IN, inout SurfaceOutput o)
        {
            fixed4 c    = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            float alpha = tex2D(_Grid, getGridUV(IN.worldPos)).a;

            o.Albedo    = c.rgb * (1.0 - alpha) + (1,1,1,1) * alpha;
            o.Alpha     = c.a;
        }

        ENDCG
    }
}

Edit: The title isn’t really correct but I can’t change it anymore. Sorry.

This source of this issue is explained pretty well here: Screenspace vs. mip-mapping · Aras' website

Thank you Daniel. :slight_smile: Now I finally understand! When the texture coordinates are jumping from 0.9999 to 0.0001 the graphics card seems to ‘think’ that I am trying to render the whole texture at once in that tiny spot and therefore uses the lowest possible mip map level.

In another forum someone also suggested a really easy solution that now makes me feel dump:

Skipping the ‘frac’ method and setting the texture’s wrap mode to ‘repeat’! :slight_smile: