I'm trying to understand the mapping of texels to pixels, especially in the context of pixel shaders. I already found out the following:

  • When I define a standard quad with vertices at integer positions (texture coordinates ranging from 0 to 1), a pixel shader for the upper left pixel will be called with texture coordinates (0,0)

  • When I subtract 0.5 from the vertices' coordinates (as it's recommended when drawing sprites), the pixel shader for the upper left pixel will be called with texture coordinates (1/(2*quadWidth), 1/(2*quadHeight))

My question is about the behaviour of the tex2D function in HLSL:

I would have assumed that it just returns the color of the upper left pixel of the texture when called with texture coordinates of (0,0). But obviously this is not the case, since the second of the above-mentioned variants actually leads to the desired 1:1 mapping.

So is it correct that tex2D returns the original pixel values of a texture when given the following texture coordinates?

enter image description here

  • Note if you use a clamp address mode tex2D @ (0,0) will be the same as tex2D @(0+0.5PX,0+0.5PX) – bobobobo Aug 30 '13 at 1:43
  • @bobobobo: OK, but I guess this special case is only true for (0,0) because there are no adjacent pixels to the left/above for interpolation. So tex2D @ (1,1) will not be the same as tex2D @ (1+0.5PX,1+0.5PX), right? Otherwise the clamp mode would be a much easier way to draw sprites with 1:1 pixel mapping than subtracting 0.5 from every vertex. – ph4nt0m Aug 30 '13 at 8:04
up vote 8 down vote accepted

Short answer: yes, that formula is correct.

Longer answer: if you think of a texture as being a grid of little squares, one for each texel, the actual color value stored in the texture can be thought of as being located at a point at the center of each texel square. So, in UV coordinates where the texture ranges from (0, 0) to (1, 1), the color samples are located at 0.5/texWidth, 1.5/texWidth, 2.5/texWidth, etc. along the U axis, and similarly along the V axis with texHeight.

This applies to bilinear filtering, clamp-to-edge, etc. So if you call tex2D at (0, 0), or any other position that corresponds to an integer number of texels, with bilinear filtering you'll get a blend of four adjacent texels. If called at a position corresponding to an integer-plus-half number of texels, you'll get one texel exactly.

Unfortunately, in D3D9 this convention applies to textures but not to render targets. As you mentioned, when you draw on the screen, the points at which a pixel shader is evaluated are at integer coordinates, not integer-plus-half coordinates. This affects how attributes are interpolated, and you end up with attribute values (including UVs) evaluated as if at the corner of a pixel square instead of its center. And this messes things up when you're trying to draw a texture exactly 1:1 onto the screen. Hence the infamous half-pixel offset that you need to apply to vertex positions in D3D9.

In D3D10 they fixed this (and in OpenGL it was never broken in the first place) so that pixel shaders are evaluated at integer-plus-half coordinates, i.e. at pixel centers instead of corners, just as texels are.

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.