texture2D Array mipmap troubles

Hey. I’ve used atlases for my own terrain texturing in my project. And I’ve had troubles when mip map was enabled. After that Unity 5.4 came with textre2Darrays. So I rewrote code for arrays, but mipmap working like for atlases…

this is atlas without mips

Texture2DArray with autogenerated mip levels

Texture2DArray , mipBias = 3

how can I fix those artefacts?

Looks like a shader mipmap issue rather than a texture mipmap generation issue.

In your shader I assume you’ve got some code wrapping the UVs for use with an atlas that should no longer be needed with an array since they’ll tile without that hack.

For future reference you can fix that artifact even when using an atlas using tex2Dgrad instead of tex2D doing something like this:

float2 scaled_uv = i.uv * scale;
float2 wrapped_uv = frac(scaled_uv) * atlas_scale + atlas_offset;
fixed4 col = tex2Dgrad(_Texture, wrapped_uv, ddx(scaled_uv), ddy(scaled_uv));

The reason for why this is happening, and why the above fixes the issue, is the GPU calculates the mipmap level for a texture by how much the UVs change between pixels (specifically within each 2x2 block of pixels), and if you’ve got some code that wraps the UVs that means in one pixel the UV might be “0.99” and the next will be “0.01” which is a big jump vs “0.99” and “1.01” so it thinks it should use a smaller mip. The tex2Dgrad is telling it to base the mip map size off of UVs with “0.99” to “1.01”, but sample the texture with the “0.99” to “0.01”.

1 Like

Thanks for answer. Before my first message I fixed code after atlas deleting.
Now I’m getting result color without atlas wrapping and I don’t use text2D method…

my code:

float3 uvForLanArr = float3(uvoffset.x, uvoffset.y, type);
float4 result_tex = UNITY_SAMPLE_TEX2DARRAY(_LandArr, uvForLanArr);

type - is array depth where I want to get texel
uvoffset - yes, I calculate some offset for each point but this offset depends of distance from neighbor tile (this terrain have hex geometry but I use quad masks above this) and not depends of position in atlas…Earlier I’ve used my own method UVMultiplayer() and there I calculated atlas offset. But after adding texture2DArray I cleaned code from this.

Real hex grid + texture quad mask ratio f. e.

So, UNITY_SAMPLE_TEX2DARRAY… may be I need something instead this…like UNITY_SAMPLE_TEX2DGRADARRAY ? Not sure that it exists

You should only need to use tex2Dgrad if you were still using an atlas. It should not be necessary with the array. You’d have to post the full shader code for me to know exactly what’s wrong, but my assumption is if you were to just use the texture coordinates unmodified that the issue will go away. Can you try that? I know that’s not what you’ll want / need, but it will mean you’re doing something you shouldn’t in how you’re handling the calculation of uvoffset.

It’s hard to explain what’s happaning in UVoffset because I get offset from early prepared texture. That’s depends of hex position relatively neighbor hexes.

//This method is call 3 times for each of the possible neighboring hexes, in which could exist this point
float2 UVoffset(float direction, float2 localPos, float2 hexCenter)
{
            float4 DD = tex2D(_DirDecode, float2((direction + 0.05) / 10, 0.1f)) * 2;
            hexCenter.x *= round(DD[0] * (DD[2] - 1));
            hexCenter.y *= round(DD[1] * (DD[3] - 1));
            return localPos + hexCenter;
}

But I really using atlas offset in UVmultiplayer method

float2 UVmultiplayer(float n, float2 localPos)
{
            int y_InAtlas = int(n / _AtlasCount);
            int x_InAtlas = n - (y_InAtlas * _AtlasCount);
            float x_Coords = x_InAtlas / _AtlasCount;
            float y_Coords = y_InAtlas / _AtlasCount;
            return float2((localPos.x / _AtlasCount) + x_Coords, (localPos.y / _AtlasCount) + y_Coords);
}

And I have two code rows in main surface method

1. float4 result_tex = tex2D(_Atlas, UVmultiplayer(type, uvoffset)).rgba; -
that’s uncommented when using atlas

2. float3 uvForLanArr = float3(uvoffset.x, uvoffset.y, type);
float4 result_tex = UNITY_SAMPLE_TEX2DARRAY(_LandArr, uvForLanArr); -
that’s uncommented when usingtext2Darray

Nothing else. I don’t change anything else for chose read texture method
looks like a magic

There is two method’s
UNITY_SAMPLE_TEX2DARRAY - first (troubles here)
UNITY_SAMPLE_TEX2DARRAY_LOD - second. I’ve tried each mipmap level and they are looking good…
So, maybe a need to choose mipmap level by hands? How?

You really need the UNITY_SAMPLE_TEX2DARRAY_GRAD, which Unity doesn’t define but you could define yourself. The UNITY_SAMPLE_TEX2DARRAY and similar things are macros that redirect to different function calls depending on the platform being compiled to.

try

#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

4 Likes

Thanks. Should I use this code after that?

float3 uvForLanArr = float3(myUV.x, myUV.y, type);
float4 result_tex = UNITY_SAMPLE_TEX2DARRAY_GRAD(_LandArr, uvForLanArr, ddx(myUV), ddy(myUV));

Now, I’m trying to compile this and have error:
Shader error in ‘Custom/HexTerrain’: unable to find compatible overloaded function “texCUBE(samplerCUBE, float3, float2, float2)” at line 183 (on d3d9)

So, maybe I need to add some code to the macro, or change some preferences…

Oh, yeah I didn’t bother with the dx9 version of the macro. You can add:

#pragma exclude_renderers d3d9

or maybe

#pragma only_renderers d3d11

1 Like

I’ve tried this strings, but compiler stil show error…
Also, I’ve tried insert something like this to the macro

#else
#if defined(SHADER_API_D3D9)
#define float4(0, 0, 0, 0)
#endif
Here the same error code like before

Ok, I’ve change macro code to this:
#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) float4(0,0,0,0) //tex2DArray(tex,coord,dx,dy)
#endif
#endif

but now result not too different from previous

Maybe I should change dx and dy calculaton?
float3 uvForLanArr = float3(myUV.x, myUV.y, type);
float4 result_tex = UNITY_SAMPLE_TEX2DARRAY_GRAD(_LandArr, uvForLanArr, ddx(myUV), ddy(myUV));

1 Like

As I said before the problem is caused specifically by the UVs you’re using, and passing the ddx and ddy of those same UVs makes it no different than using the non-GRAD version of the macro.

Try something like ddx(localPos.xy), though I don’t know exactly where that value is coming from, or worse case try just this:

UNITY_SAMPLE_TEX2DARRAY_GRAD(_LandArr, uvForLanArr, float2(0.05, 0.05), float2(0.05, 0.05));

That won’t be really usable as the textures will become a little blurry, but the lines should go away. Ultimately this method isn’t that much different from using the _LOD macro, so it’s not that useful.

So lets try this:

UNITY_SAMPLE_TEX2DARRAY_GRAD(_LandArr, uvForLanArr, ddx(localPos.xy), ddy(localPos.xy));

This might also make the textures a little too blurry, or too sharp, I still don’t know exactly what you’re doing to the UVs since you only sent me a small snippet of your shader code, but they’ll have some semblance of mipmapping and the lines should go away. If you want to just get it working try applying a scale factor to localPos until it starts to look right to you.

2 Likes

I know this is old theme, but today I’ve got another one problem with arrays)))
So far I’ve used UNITY_SAMPLE_TEX2DARRAY_GRAD(_LandArr, uvForLanArr, ddx(localPos.xy), ddy(localPos.xy)) for my textures and UNITY_SAMPLE_TEX2DARRAY_LOD(_MaskArr, uvForMask, 0).r for B&W masks. Both of these strings are located near. Recently I’m discovered that if I delete second string (with UNITY_SAMPLE_TEX2DARRAY_LOD) then my first string stop works. It gives last one-colored LOD instead full texture as before.

float4 tex = UNITY_SAMPLE_TEX2DARRAY_GRAD(_LandArr, uvForLanArr, ddx(localPos.xy), ddy(localPos.xy));
UNITY_SAMPLE_TEX2DARRAY_LOD(_MaskArr, uvForMask, 0); //even without saving to variable!!!1111
//even if this string will be higher than previous...
o.Albedo = tex.rgb;

it works

float4 tex = UNITY_SAMPLE_TEX2DARRAY_GRAD(_LandArr, uvForLanArr, ddx(localPos.xy), ddy(localPos.xy));
                                                               
o.Albedo = tex.rgb;

It not works…

How it possible?)) Can’t understand…(((
Can it depends of something in this initialization:

        #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)  float4(1,1,1,1)//tex2DArray(tex,coord,dx,dy)

        #endif
        #endif

        UNITY_DECLARE_TEX2DARRAY(_LandArr);
        UNITY_DECLARE_TEX2DARRAY(_MaskArr);

Thank you bgolus. This totally worked for me.

would this macro work for anything besides the 2D array?

For some reason using @bgolus code, from cginc file or .shader, using #define without #if isn’t working, the following error prompts: undeclared identifier ‘UNITY_SAMPLE_TEX2DARRAY_GRAD’