Texture vs. Surface pixel addressing in DirectX

Posted: October 28, 2008 in Code, Graphics
Tags:

Let’s say you’re rendering a D3DImage, and you want to blit a texture to it. The logical thing to do is draw an untransformed quad to the scene, with corners at (-1,1), (1,1), (1,-1), and (-1,-1). Those same vertices would have a UV coordinate to read into the texture at, respectively, (0,1), (1,1), (1,0), and (0,0). That should work, right?

It doesn’t. If you are using D3DTEXF_POINT (point filtering) to filter your texture, and you video card just happens to round the texture address in the right direction, it’ll work. But it won’t necessarily on every machine, and it won’t if you use bilinear filtering.

How can you tell it’s going wrong? Use a texture the same size as your render target, and observe the top row and left column of rendered pixels. It’s easiest if you use D3DTADDRESS_WRAP texture addressing.You’ll notice they aren’t quite right.

What’s going wrong? It turns out that DirectX addresses surfaces and textures a little bit differently. With a surface, the coordinate (0,0) (not considering any viewport transforms) lies directly in the center of the top-left pixel. With a texture, on the other hand, the coordinate (0,0) lies on the top-left corner of the top-left pixel. So what happens if you map (0,0) on the surface to (0,0) on the texture? You’re actually mapping the upper-left pixel of your rendering surface to a corner of the texture shared by 4 pixels–which means you may get an even blend of the 4 pixels, or if you use point filtering your GPU will choose one of those pixels for you. If you’re lucky, it’ll be the one you want.

How to fix it? Your texture coordinates need to be offset by half a pixel, so that the top-left pixel of the surface maps to coordinate (PWidth/2, PHeight/2), where PWidth and PHeight are the relative height and width of each pixel in texture space. You can make this correction in your vertex buffer’s UV coordinates, but personally I don’t like that solution since it doesn’t automatically scale to different-sized textures. You can also use a transform matrix to move your quad left and up half a pixel in surface space, but I don’t like that solution either, because you’ll have to rescale it to ensure it doesn’t completely forget to render the right and bottom edges of your image. Too much math.

The ideal solution, I find, is to modify your UV coordinates in the vertex shader. A very common parameter that you’ll find yourself passes to almost all of your effects and procedural images is the relative size of a pixel. All you have to do is bind that same parameter in your vertex shader, and just adjust your UV coordinates like this:

output.UV += g_PixelSize / 2.0;

Problem solved!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s