# Texture Atlas seam issue.

This topic is 2473 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

I want to support a large number of textures on my terrain using splatting. I decided that a texture atlas with splatting would be the best option. Each texture I am using is 512x512 pixels in size, and I store 4 of them on a 2048x2048 surface. I use this method to help reduce or remove UV seams cause of filtering: http://www.ogre3d.or...php?f=4&t=61602

It works great and I have no problems with it and filtering, however I still get UV seams not related to the filtering so I think my coords are a little off.

Here's my look up code:

GetTex(UV, float2(0, 0)) // I would use 1,0 or 1,1 to get the different textures. 0, 0 being the first texture.

GetTex function code:

UVOffset *= .5f;
float2 Coords = ((0.125f + UVOffset) + frac(UV) * (.25f));
return tex2D(Samp, Coords);

.125 is where the texture starts if my UV's are right, and then .375 would be where it ends. and the stuff around that is tiled texture in order to fix the seams when you mip map and filter the texture.

This should return the first texture in the atlas and it works good, I see the first texture it tiles good, the only problem is I still have seams where the texture isn't right. I've turned off all filtering and the problem still exists. I have a feeling its something to do with my UV coords, but I am not entirely sure. The seams are hardly noticeable, but its still annoying when you get close to the landscape, and on some textures the seams are very visible.

Any help will be greatly appreciated!

-Toaster

##### Share on other sites
*Bump* I could really use some help on this.

##### Share on other sites
Can you post a picture of the artifact?

##### Share on other sites
Could be a derivatives problem? Like above, pics needed.

##### Share on other sites
Have you concidered using a texture array instead?

##### Share on other sites
Here is a screenshot of the issue:
http://i.imgur.com/eajsW.png

I know the texture is seamless so that is not an issue. I believe it to be the UV cords that I am using.

Have you concidered using a texture array instead?

This is not an option as I would like to stick with SM2.0 which provides only 8 texture samplers. This is simply not enough for my needs, I would like up to 12 textures atleast, and with this method I am using I should be able to store 12 textures inside of 3 texture samplers.

-Toaster

##### Share on other sites
There's not much to go off. You shader code looks fine, assuming that you've got a 512x512 tileable texture (1/4 width of atlas) located with a 256px margin (1/8th width of atlas)...

Do the artefacts go away if you disable mip-mapping?

If not, I'd have to guess that your atlas isn't correct -- if you use an image editor to cut out the 512x512 section, starting from the 256px margin, is that cut-out area tileable?

##### Share on other sites
Thanks for the fast reply!

[color="#1c2837"]Do the artifacts go away if you disable mip-mapping?[/quote]
Surprise they do. I thought adding the border around the texture would fix this. Does anyone have any advice?

At the advice of someone else I tried something like this:
[color=#1C2837][size=2]

[color=#1C2837][size=2]float HalfPixel = 0.5f / 512.0f; UVOffset *= .5f; float2 Coords = ((0.125f + UVOffset) + frac(UV) * (.25f)); return tex2D(Samp, clamp(Coords, (.125f + UVOffset) + HalfPixel, (.375f + UVOffset) - HalfPixel));
[color=#1C2837][size=2]

[color=#1C2837][size=2]Clamping doesn't seem to be the right thing to do here though cause you'd get sudden drop offs. I think instead I need to make sure the cords are always in between .125f + halfpixel and .375 - HalfPixel.
[color=#1C2837][size=2]

[color=#1C2837][size=2]I am going to keep cracking away at this.
[color=#1C2837][size=2]

[color=#1C2837][size=2]-Toaster

##### Share on other sites
[color="#1c2837"]Do the artifacts go away if you disable mip-mapping?
Surprise they do. I thought adding the border around the texture would fix this. Does anyone have any advice?[/quote]Ok I think I know what your problem is then -- the border pixels around the seam area are choosing a very low-res mip-level, whereas the rest of the pixels are choosing sensible mip-levels.

This is occurring because of the way that your GPU is choosing mip-levels: it looks at the UV coords of the current pixel and the UV coords of the neighbouring pixels, and compares them to determine the rate-of-change in the UV-coordinates.
If the UVs are changing quickly, it selects a low-res mip. If the UVs are changing slowly, it selects a high-res mip.

Your UV-wrapping logic is screwing up this algorithm, basically tricking the GPU into thinking that those border pixels are really zoomed out --- it sees the coord of 0.125 at one pixel, and 0.375 at the next pixel, and assumes you want to get the average of all values between those two texels.

To get around this, you need to determine the rate-of-change values manually. You can give these manually-calculated values to the GPU by using tex2Dgrad instead of tex2D.

When you write:float2 Coords = ((0.125f + UVOffset) + frac(UV) * (.25f)); return tex2D(Samp, Coords);The GPU is actually doing:float2 Coords = ((0.125f + UVOffset) + frac(UV) * (.25f)); float2 dx = ddx(Coords); float2 dy = ddy(Coords); return tex2Dgrad(Samp, Coords, dx, dy);But, instead, you want to the following - get the rate of change of the UV before doing your wrapping logic:float2 dx = ddx(UV * .25f); float2 dy = ddy(UV * .25f); float2 Coords = ((0.125f + UVOffset) + frac(UV) * (.25f)); return tex2Dgrad(Samp, Coords, dx, dy);

##### Share on other sites

[quote name='toasterthegamer' timestamp='1305783865' post='4812885']
[color="#1c2837"]Do the artifacts go away if you disable mip-mapping?
Surprise they do. I thought adding the border around the texture would fix this. Does anyone have any advice?[/quote]Ok I think I know what your problem is then -- the border pixels around the seam area are choosing a very low-res mip-level, whereas the rest of the pixels are choosing sensible mip-levels.

This is occurring because of the way that your GPU is choosing mip-levels: it looks at the UV coords of the current pixel and the UV coords of the neighbouring pixels, and compares them to determine the rate-of-change in the UV-coordinates.
If the UVs are changing quickly, it selects a low-res mip. If the UVs are changing slowly, it selects a high-res mip.

Your UV-wrapping logic is screwing up this algorithm, basically tricking the GPU into thinking that those border pixels are really zoomed out --- it sees the coord of 0.125 at one pixel, and 0.375 at the next pixel, and assumes you want to get the average of all values between those two texels.

To get around this, you need to determine the rate-of-change values manually. You can give these manually-calculated values to the GPU by using tex2Dgrad instead of tex2D.

When you write:float2 Coords = ((0.125f + UVOffset) + frac(UV) * (.25f)); return tex2D(Samp, Coords);The GPU is actually doing:float2 Coords = ((0.125f + UVOffset) + frac(UV) * (.25f)); float2 dx = ddx(Coords); float2 dy = ddy(Coords); return tex2Dgrad(Samp, Coords, dx, dy);But, instead, you want to the following - get the rate of change of the UV before doing your wrapping logic:float2 dx = ddx(UV * .25f); float2 dy = ddy(UV * .25f); float2 Coords = ((0.125f + UVOffset) + frac(UV) * (.25f)); return tex2Dgrad(Samp, Coords, dx, dy);
[/quote]

Wow that really worked nicely, and it makes sense too! Thank you so much for the help! I do notice though that ddx and ddy are not supported by SM2.0 while SM2.0a supports it but SM2.0b doesn't which is odd.

I think I am happy with leaving the lowest as 2.0a those are pretty older cards, and I think everyone should at least have SM3.0 cards by the time I plan on releasing stuff. Although I would be interested in hearing a possible work around for this. Also I get poor performance with SM2.0a which was kind of surprising. 45 FPS without normal mapping compared to the SM3.0 with normal mapping which is running at 80 fps. I guess some of the older commands are slower. :S

Thanks again!
-Toaster

##### Share on other sites
Yeah, the whole 2.0a vs 2.0b thing was the result of a stupid fight between ATI and nVidia... 'a' is nVidias extension to SM2 whereas 'b' is ATIs extension to SM2. You shouldn't think of them as a sequence (e.g. 2 -> 2a -> 2b -> 3) but as both of them being completely seperate vendor-specific extensions to SM2, which were both eventually replaced by SM3.

As a SM2.0 work-around, you can disable mip-mapping Or, instead of using ddx/ddy, you can use an alternative algorithm to choose the mip-level... such as measuring the distance to the pixel and scaling it somehow... there's lots of literature on mip-map selection algorithms, but AFAIK, most of them rely on screen-space rate-of-change information (i.e. the info that ddx/ddy give you).

If you did come up with your own technique, you can use [font="Courier New"]tex2Dlod[/font]:float mipLevel = /*...black magic...*/; return tex2Dlod(Samp, float4(Coords, 0, mipLevel));

##### Share on other sites
You can also create a mip texture with dimension of 1 x 2^mipdepth that stores the mip levels and sample this using tex2D. You have to turn wrap mode on and scale the UVs accordingly. This way you can use the hardware to select the mip level for you.