1
votes

I am working on a Unity shader that can display multiple different tiles on a single plane. Unity passes a floating point array to the shader with a list of tile IDs and a 4x4 tile map and for the mostpart it works perfectly, however there is one slight issue.

I have attached an image of the phenomenon, at the seams of the tiles you can see grey lines. I turned on the tile masking to extenuate the issue so it's easier to see.

Below is the shader code:


Shader "Unlit/TileMap"
{
    Properties
    {
        // Texture configuration
        _DiffuseTex("Diffuse", 2D) = "white" {}
        _MaskTex("Mask", 2D) = "black" {}
        _MapWidth("Map Width", int) = 0
        _MaskMapWidth("Mask Mask Width", int) = 0
        _WorldWidth("World Width", int) = 0


        // Global illumination modifiers
        _GlobalLumosity("Global Lumosity", Range(0, 1)) = 1
        _GlobalHue("Global Hue", Color) = (1, 1, 1, 1)

    }
    SubShader
    {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        LOD 200
        ZWrite On

        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma target 4.0
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _DiffuseTex;
            float4 _DiffuseTex_ST;

            sampler2D _MaskTex;
            float4 _MaskText_ST;

            uint _ID;
            uint _MapWidth;
            uint _MaskMapWidth;
            uint _WorldWidth;

            half _GlobalLumosity;
            float4 _GlobalHue;

            float _allIDs[16];

            v2f vert (appdata v) {
                v2f o;

                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _DiffuseTex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                float mapSizeModif = 1.0f / _MapWidth;
                float maskMapSizeModif = 1.0f / _MaskMapWidth;
                float worldSizeModif = 1.0f / _WorldWidth;

                // float lightness = tex2D (_MaskTex, i.uv * _MapWidth);
                float lightness = 1.0f;

                uint index = (int)  (floor(i.uv.x * _WorldWidth) +
                                    (floor(i.uv.y * _WorldWidth) * _WorldWidth));

                i.uv.x = i.uv.x % worldSizeModif;
                i.uv.y = i.uv.y % worldSizeModif;

                uint id = (uint) _allIDs[index];

                i.uv.x += mapSizeModif * (id % _MapWidth);
                i.uv.y += mapSizeModif * floor (((float)id) / _MapWidth);

                fixed4 col = tex2D (_DiffuseTex, i.uv);
                col *= _GlobalLumosity;
                col *= _GlobalHue;

                col.a = lightness;

                return col;
            }

            ENDCG
        }
    }
}

And here is an image of what happens: enter image description here

I feel like some sort of padding would be needed to fix this issue but I don't know what kind of padding I would need. I feel it has something to do with the lines, and an issue that involves some sort of floating point accuracy error:

// Convert position into localised scaling
i.uv.x = i.uv.x % worldSizeModif;
i.uv.y = i.uv.y % worldSizeModif;

And this is the MonoBehaviour controller that sets the tile information:

void Update()  {
    propBlock.SetFloatArray("_allIDs", new float[16] { 1, 1, 3, 2, 1, 1, 2, 2, 1, 3, 2, 1, 1, 2, 1, 1 });
    renderer.SetPropertyBlock(propBlock);
}

I thought it may be simple texture bleed between the tiles, however, even when forcing it so that no tile bleed is possible (by putting an offset on the UV) it still happens

Any help would be appreciated

Let me know if there is any more info you'd like

1
Have you tried changing wrap mode to "clamp" in the texture import settings?Kalle Halvarsson
Yes, I have and, unfortunately, it still has the same result.Polymer
Try i.uv.x = i.uv.x % (worldSizeModif - 1); (and the equivalent same for uv.y).3Dave

1 Answers

2
votes

The problem here is that the lowest mip of your texture is being fetched on some pixels, showing up as the average of your picture (gray-ish). The reason for this is that mip level is computed based on the derivative of the texture coordinates, which works when they are continuous (in screen space), which is not your case since you do a modulo per pixel.

You can get around this by using tex2Dgrad (https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-tex2dgrad) and passing the derivatives of the continuous UVs, using ddx() and ddy()