Skip to main content
GameDev.net gamedev.net
🔒 Locked

Emulating texture wrapping on sub-rectangle of texture atlas

Started by emeyex Apr 17, 2008 at 4:37 PM 7 replies 5.6k views
Original Post
emeyex
emeyex
So, if you read around a bit people obviously recommend against this, but it still seems to me that it should be possible. Unfortunately, I'm getting seams every time the texture wraps back around. If I disable bilinear and mipmap filtering it seems to work fine (no seam), so my guess is my math is maybe off by a pixel or two. Basically, I compute a tiled UV in the pixel shader (based off of some world-space geometry), and then I use frac to get the actual uv. I can see where this would go terribly wrong if I were doing this calculation in the vertex shader (i.e., interpolating from 0.99 to 0.01 would be very wrong), but in the pixel shader I have control over this (i.e., no interpolation, I can compute the actual sample point for the given rendered pixel). I've tried extending my sub-rectangle by one row/column on either end, and I've tried shrinking my uv-wrap-window slightly by various amounts (so it starts slightly inside of the rectangle and wraps slightly before the end), but to no avail... So, my question: has anyone gotten this to work successfully? If it is impossible, what am I not understanding? Naturally on a platform/API with texture arrays I could avoid this issue, but I'm trying to get this to work on PC D3D9. Thanks in advance for any insights.
mattnewport
mattnewport
The problem, and the reason you see seams, is that you don't get correct filtering at the edges unless you are point sampling. When you use a wrapping sampler state and have bilinear filtering turned on, the hardware blends between the texels on one edge and the texels on the opposite edge as you interpolate across the wrap point. You can't reproduce that in a pixel shader unless you do the filtering manually yourself which is pretty expensive. Instead you get filtering with the adjacent pixels outside the area you wish to wrap within.

A common workaround is to create a border or gutter around your sub region in the texture atlas. If you're trying to emulate wrapping then this border should really be a copy of the pixels from the opposite edge but just copying the current edge pixels will usually work as well. If you want mip mapping to work correctly as well then you'll need to make the border large enough at the highest mip level to allow a one pixel border at the lower mip levels as well.
emeyex
emeyex
Thanks for the reply.
Quote:
A common workaround is to create a border or gutter around your sub region in the texture atlas. If you're trying to emulate wrapping then this border should really be a copy of the pixels from the opposite edge but just copying the current edge pixels will usually work as well.

Yeah, that's what I meant by "extending my sub-rectangle by one row/column on either end", though admittedly that wasn't very clear. So I'm not wrong-headed then in thinking this should work, I guess... I'll just have to have another look. Glad to know I'm not necessarily heading down a dead end :).
mattnewport
mattnewport
Quote:
Original post by emeyex
Yeah, that's what I meant by "extending my sub-rectangle by one row/column on either end", though admittedly that wasn't very clear. So I'm not wrong-headed then in thinking this should work, I guess... I'll just have to have another look. Glad to know I'm not necessarily heading down a dead end :).


Your problem might be with mip maps - you need to have a border that's 1 pixel wide for lower mip levels which means it will be 2^n pixels wide for your highest mip level. The number of mip levels you have a correct border for is a tradeoff between avoiding seams and using too much space in your atlas for the borders.
emeyex
emeyex
The problem was definitely with the mip-maps. Had to fiddle with the gutter size and my custom wrapping a bit, but seems to be working now. Thanks again.
Ysaneya
Ysaneya
It's not that simple. When you perform the "fract" operation, you are introducing a discontinuity in the derivatives of the texture coordinates (by that I mean that fract(x) can have a completely different value than fract(x + dx), think of x=1.0). And those derivatives are used by the hardware to select the mipmap level. So even when correctly building and uploading the good mipmap chain, taking the seams of each tile within the atlas into account, you will still see some seams.

The solution is to compute the mipmap level and sample the texture at the computed mipmap level manually. You can find shader code for this in my gamedev.net journal.

Y.
emeyex
emeyex
Nice write-up, that does look a bit more rigorous than my manually fudged gutter size ;). Fortunately my primary target APIs/hardware support texture arrays, but I'll probably still look into doing this a bit more correctly for dx9 on the pc.
mattnewport
mattnewport
If you don't need the explicit mip level value for other purposes you can also use tex2Dgrad, using the gradients derived from the original texture coordinates to avoid problems with discontinuities. On some cards tex2Dgrad is a lot slower than tex2Dlod though so look out for a performance hit.
AndyTX
AndyTX
Actually you should just be able to scale the derivatives properly and use a derivative texture lookup. Thus you can avoid computing the mip level manually, etc. tex2Dgrad also supports anisotropic filtering while tex2Dlod does not.

Note that you also want to clamp the maximum mipmap level (you can do that in the sampler state) to avoid using any mipmaps that start to average across texture atlas boundaries. This is pretty simple with pow2 tiles, and pretty irritating otherwise ;)

But yes, texture arrays are the "right" and prettiest solution if they are supported.

[Edit] Curses, Matt beats me to it again ;)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.