Tuesday, July 3, 2012

UV Texture Coordinates and Texture Mapping - OpenGL / DirectX

Because I am not in school right now, I have been getting pretty heavy into WebGL graphics trying to reinforce and learn new 3D and graphics concepts. At the moment I am trying to get a solid foundation on texture mapping before moving onto bump mapping, shadow mapping and the like, so I decided to write up a simple tutorial.

When I am trying to learn something new I think a great way to start off is to at least skim the associated wikipedia page, so why not pop over there either before or after reading this and check out the page on Texture Mapping. Did you know that texture mapping was first laid out in the Ph.D thesis of Dr. Edwin Catmull all the way back in 1974? No wonder the Atari and the like debuted just a few years later.

Texture coordinates are used in order to map a static image onto a 2D or 3D geometry in order to create a mesh. There are a few different ways that texture coordinates can be defined. In any case the standard is to specify them as an ordered pair (u, v) usually with values ranging from 0 to 1 floating point.

Basically the idea with any kind of texture coordinates is that we will use some of the coordinates to map to particular vertices of a geometry. The rest of the pixels are interpolated between the specified vertices. All kinds of 3D objects can be mapped to using a 2D texture, however the mapping will be different for differently shaped objects and obviously some objects will be harder to map than others.

I think that the typical workflow at most studios would involve a modeler first creating the geometry, then using tools to unfold it and draw a texture, by using these tools they can match the texture coordinates to the objects correct associated vertices. The mapping data would then be handed over to the programmer. I am not very experienced with the modeling side of things so I would love to hear from anyone who knows more about how this works. A lot of graphics libraries will handle this for you in some default way for simple geometries, whilst having some mechanism for the programmer to specify custom mapping of texture coordinates to the objects local coordinates.

With that being said, it makes sense that regardless of how the coordinate system is set up, it seems they are always specified as floating point values between 0 and 1. Basically, this means we are only specifying positions on our texture as percentages. This is because we need to interpolate between vertices and this makes it a lot easier.

Although, Cartesian coordinates are the ones we all learn in school and are probably the simplest, these are not however the coordinates that are usually used with textures. Instead, textures are mapped according to what we call UV Texture Coordinates. Basically UV coordinates are just a way of making the range of our coordinates can take be the same regardless of the width or height of the image.

Even though Cartesian coordinates are not used for mapping a texture to geometry, they are relevant however because a digital image is stored as a Cartesian grid. Where each single pixel is a square of the grid. An image is made up of individual quantifiable pixels starting at the first pixel 1x1 and going to the last pixel which will be (img_width)x(img_height).

Now if we have a range of possible values and we want to map all of these possible values to a floating point range between 0 and 1, how can we do this? Well basically all we have to do is divide each value by the maximum of its possible range. I could talk about math all day, but let me just lay it out more formally:

If we define the pixels of an image as a set of ordered pairs (x,y) on a Cartesian grid, then we can define it's texture coordinates as the range of ordered pairs (U,V) such that U = x / (img_width) and V = y / (img_height).

So for an example, let's say that we have a texture that has a width of 700 pixels and a height of 512 pixels. Pixels are perfect squares, all the same size, so we've got 700x512 different pixels. And let's say we need to know what the (U,V) texture coordinate is at the pixel 310x400. We could therefore come up with the texture coordinate (0.44286, 0.78125).

U = 0.44286 = 310/700 , V = 0.78125 = 400/512.

I am going to attempt to make this tutorial equally valid for both OpenGL and DirectX. So far the above really applies to both. It is really only where the origin begins that they do not agree. I think it is important for people to realize there is not always a "best" way to do things and that not always everyone is going to agree. You are going to have to learn more than one system of doing things in lots of cases. In the case of texture coordinates, the big difference between OpenGL and DirectX is:

In OpenGL the origin of our texture coordinates is in the bottom left corner of the texture and in DirectX it is in the upper left. This means that our first coordinate will always be the same in both systems. We have to flip the second coordinate to convert between the two. This simple illustrations on this page demonstrate the differences. We can do this by subtracting the second coordinate by one.

DirectX to OpenGL --- (U,V) --> (U, 1-V)
OpenGL to DirectX --- (W,X) --> (W, 1-X)
DirectX (U,V)=(0.4, 0.75) --- 40% from left, 75% from top
OpenGL (U,V)=(0.4, 0.75) --- 40% from left, 75% from bottom

DirectX (U,V)=(0.4, 0.25) --- 40% from the left, 25% from top / 75% from bottom
OpenGL (U,V)=(0.4, 0.25) --- 40% from the left, 25% from bottom / 75% from top


Unknown said...

nice post i clearly understand it

cegprakash said...

+1 for DirectX - OpenGL conversions!

Unknown said...

problem is, if u have Text or something in ur Texture and u do 1-v u will flip it and mirror the Text :(

Olivier CARRE said...

Your first image about uv texture coordinate is just wrong ^^
For DirectX the coordinates (1,1) should be (0,1) and (0,1) should be (1,1) as your last image stated.
Anyway thank you for this great article :)

Unknown said...

Bah! Yep those two should be switched. It's been years since I wrote this. Can't believe it hasn't been caught till now. This is my old blog. I guess it never really got any real traffic.

elect said...

Coz the most were at the opengl side..

Anonymous said...

Good job posting this. It's amazing how many discussions and documentation and tutorials have no explanation at all of this, and no pointers to anything that explains it. Often "UV stream" is just mentioned and you get functions that take vectors with no explanation of what it refers to or how it'll be used, nor any pointer to anything with this sort of information.

Oguz said...

Thank you.. it was very helpful..

Post a Comment