Random lines between tiles when sampling from Texture2DArray

Hello,

I’m trying to tile different textures on a mesh using a custom shader and a Texture2DArray.

Everything is working, except a very small seam line appearing completely randomly between different tiles (and not between identically textured tile). It seems that I should use screen-space derivatives. This would correct the UV discontinuity which causes the GPU to use an wrong mipmap level at the edge of the tile.

Here is a basic tiling exemple : a 2 columns * 5 rows mesh with 3 identical tiles at the center of each column, and “top” and “bottow” tile at each end. You can see the lines appear between the normal and the top/bottom textures only, as well as below/above this top/bottow textures. The lines appears very randomly, most of the time everything is smooth.

Random lines

2958567--219560--lines.PNG

I’m creating the 3-elements texture2DArray from 3 128*128 textures, with mipmapping.

Here is my very basic shader, I simply look up a float from a data texture red channel and tile the appropriate texture2DArray element using scaled UVs. And obviously I don’t know what to put as derivativeUVs in the fragment shader.

To sample the Tex2DArray, I’m using a UNITY_SAMPLE_TEX2DARRAY_GRAD() function provided by forum user bgolus on this thread.

Shader "MyShader/TestShader"
{
    Properties
    {
        _DataTex("Data Texture", 2D) = "white" {}
        _Tex2DArray("Tex2DArray", 2DArray) = "red" {}
    }
    SubShader
    {
        Pass
        {
            Tags{ "RenderType" = "Transparent" }
            Blend SrcAlpha OneMinusSrcAlpha
            LOD 200
            ZWrite Off

            CGPROGRAM
           
            #pragma exclude_renderers d3d9
            #pragma exclude_renderers d3d11_9x
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.5

            #include "UnityCG.cginc"

            // from https://forum.unity3d.com/threads/texture2d-array-mipmap-troubles.416799/
            #if defined(SHADER_API_D3D11) || defined(SHADER_API_XBOXONE) || defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE)
            #define UNITY_SAMPLE_TEX2DARRAY_GRAD(tex,coord,dx,dy) tex.SampleGrad (sampler##tex,coord,dx,dy)
            #else
            #if defined(UNITY_COMPILER_HLSL2GLSL) || defined(SHADER_TARGET_SURFACE_ANALYSIS)
            #define UNITY_SAMPLE_TEX2DARRAY_GRAD(tex,coord,dx,dy) tex2DArray(tex,coord,dx,dy)
            #endif
            #endif

            // custom data
            sampler2D _DataTex;
            UNITY_DECLARE_TEX2DARRAY(_Tex2DArray);

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

            struct v2f
            {
                float4 position : SV_POSITION;
                float4 color : COLOR;
                float2 uv : TEXCOORD0;
            };


            // Vertex Shader
            v2f vert(appdata input)
            {
                v2f output;
                output.position = mul(UNITY_MATRIX_MVP, input.vertex);
                output.uv = input.uv;
                output.color = input.color;
                return output;
            }

            // Fragment Shader
            float4 frag(v2f input) : Color
            {
                // Tiling on a 2*5 grid
                float2 scaledUVs = float2(input.uv.x * 2, input.uv.y * 5);

                // Getting the data texture data
                float data = tex2D(_DataTex, input.uv).r;

                float2 derivativeUVs = scaledUVs; // ??? This is where I'm lost

                 // Top part
                if (data < 0.2)
                {
                    float4 fulltex = UNITY_SAMPLE_TEX2DARRAY_GRAD(_Tex2DArray, float3(scaledUVs, 2), ddx(derivativeUVs), ddy(derivativeUVs));
                    return fulltex;
                }

                // Middle part
                if (data > 0.2 && data < 0.8  )
                {
                    float4 fulltex = UNITY_SAMPLE_TEX2DARRAY_GRAD(_Tex2DArray, float3(scaledUVs, 1), ddx(derivativeUVs), ddy(derivativeUVs));
                    return fulltex;
                }

                // Bottom part
                if (data > 0.8)
                {
                    float4 fulltex = UNITY_SAMPLE_TEX2DARRAY_GRAD(_Tex2DArray, float3(scaledUVs, 0), ddx(derivativeUVs), ddy(derivativeUVs));
                    return fulltex;
                }
                return float4(0, 1, 0, 1);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

Any help to understand this ddx/ddy magic (or any alternate, elegant solution to get rid of the lines) would be greatly appreciated.

Well, the lines you’re seeing here are a completely different issue than that other thread. What you’re seeing is just from the texture wrapping and bilinear texture sampling, it’s unrelated to derivatives or even texture arrays. The values you’re passing to that macro are actually the correct values, but it’s somewhat unnecessary to use it.

The real fix is to either clamp the UVs or slightly scale the UVs, as well as switch to the tiled middle texture a little sooner.

Simpliest thing to do to start off is change the swap points from 0.8 and 0.2 to 0.15 and 0.85. That’ll get rid of the lines on the transitions. For the caps you need to offset the UVs by half a texel. For that you’ll need to add this:

float4 _Tex2DArray_TexelSize; // has the individual texel size and texture resolution

// should scale the UVs y from a range of 0.0 to 5.0 to a range of a half texel to 5 minus a half pixel.
float2 scaledUVs= float2(input.uv.x2, input.uv.y(5 + _Tex2DArray_TexelSize.y) + _Tex2DArray_TexelSize.y * 0.5);

1 Like

Thank you very much bgolus, telling me that derivatives were not the problem and using the texel size of the Tex2DArray indeed solved this.

Here is the corrected fragment shader, which get rid of the random lines between tile transition :
Corrected fragment shader

// Fragment Shader
            float4 frag(v2f input) : Color
            {
                // Tiling on a 2*5 grid
                float2 scaledUVs = float2(input.uv.x * 2, input.uv.y * 5);

                // *** This solve the "random lines between tiles transition" problem ***
                if (frac(scaledUVs.y) > 0.99) scaledUVs.y -= (_Tex2DArray_TexelSize.y * 0.5);
                if (frac(scaledUVs.y) < 0.01) scaledUVs.y += (_Tex2DArray_TexelSize.y * 0.5);

                // Getting the data texture data
                float data = tex2D(_DataTex, input.uv).r;

                 // Top part
                if (data < 0.2)
                {
                    float4 fulltex = UNITY_SAMPLE_TEX2DARRAY(_Tex2DArray, float3(scaledUVs, 2));
                    return fulltex;
                }

                // Middle part
                if (data > 0.2 && data < 0.8  )
                {
                    float4 fulltex = UNITY_SAMPLE_TEX2DARRAY(_Tex2DArray, float3(scaledUVs, 1));
                    return fulltex;
                }

                // Bottom part
                if (data > 0.8)
                {
                    float4 fulltex = UNITY_SAMPLE_TEX2DARRAY(_Tex2DArray, float3(scaledUVs, 0));
                    return fulltex;
                }
                return float4(0, 1, 0, 1);
            }