Generating automatic UV Coordinates

Started by
9 comments, last by grhodes_at_work 15 years, 8 months ago
Hi, Since I'm too lazy to generate UV coordinates in a modeller tool, I'd like to make a function that maps a face automatically. So far, I can select a face (a polygon including all its neighbours that face into the same X,Y or Z axis, a wall or floor for example). Each vertex already has 1 texture coordinate, and I know how to directly map them into a 0..1 range. However, in some cases I need to rotate/skew the shape to make it fit better. For example, if I map a floor that has been rotated 45 degrees, I will get this:

       /     /       /        \ 
   \        /
     \    /
       \/
I could rotate it by hand, but it would be nice if the computer could make it like this:

  _______
 |	 |
 |	 |
 |	 |
 |_______|
If possible, I'd like to do the same trick for more complex shapes as well. A pipe with a 90 degrees twist for example, or a rounded cube such as the top view of a pillow. The less I'll have to do with the hand, the better :) Although I'm not planning to use it for really complex surfaces, such as a character model. In the end I'm looking for a fast way to generate UV coordinates for static geometry, such as a house. [edit] dammit, I can't get my ASCII art appear properly for some reason in the first picture. Anyway, what I mean is that square in the second picture, rotated 45 degrees (or any other angle) Greetings, Rick
Advertisement
You could choose one edge of the polygon, in code, and decide that the chosen edge should be horizontal. Then, find the angle between that edge and the horizontal/x axis, and rotate all the verts of the polygon by that angle, with the center of rotation being the first point on the edge. That is one simple approach to re-aligning the polygon without distorting it. Now, if you're trying to automatically fit multiple polygons onto one texture, you have the problem of polygon packing. Is that a current issue?

There are numerous approaches to automatically unwrapping meshes onto a texture map. It certainly can be tricky in some cases, and the best results have at least some human input, e.g., to mark edges where you allow the mesh to be split, or to lock edges that bound areas that should not be distorted....but I think you're not needing something this complex at this point.
Graham Rhodes Moderator, Math & Physics forum @ gamedev.net
Hmm, that is not a bad idea. I agree that you can't map everything automatically. However, the more that can be done with a single click, the better. The trick you describe could work for rotates floors/ceilings.

Besides rotated squares, I have skewed square. I don't know if I explain it properly... I have a simple square, pick the left or right edge, and lift it up. You know a trick to map them as a simple square as well?


Maybe someone knows an overview with several mapping techniques for several shapes. I'd imagine the user has a couple of buttons with mapping techniques to choose from. If none of them works, well, he/she has to do it with the good old hand.

About the "packed polygon" problem, I'm not that far yet :) So far I only did simple (planar?) mapping. Just paste a shape straight on the map with XY, YZ or XZ axis.

Thanks for your tip!
Rick
The simple approach to planar mapping, given that you have a collection of arbitrarily oriented rectangles in space (the sort of things that would define the geometry of a house-like object), would be to:

for each face
{

1) find a normal vector for the face. This will be the z axis of the projection plane
2) choose one vertex to be the origin of the projection
3) choose one edge to be the x axis direction of the projection
4) make sure the normal (z axis) and x axis direction are unit length
5) compute a y axis = z crossproduct x
6) Use the 3 axes to formulate a 3x3 rotation matrix
7) Formulate a full 4x4 transformation using the 3x3 rotation matrix plus translation = the opposite of the origin point (e.g., "-origin")
8) Apply the 4x4 transformation matrix to all of the points that make up the face
9) As long as the face was planar, the resulting points will have z = 0. But, in any case, drop the z...and this will work for faces that are nearly planar but not perfectly planar.
10) Now you have a collection of 2D points (x,y), corresponding to each of the original 3D points. The polygon defined by connecting these points in the same order *is* your 2D projection. It is oriented so that one edge is horizontal and the shape is not distorted. So, if your skewed square was in fact just a perfect square rotated, it will be perfectly square in the projection (except for floating point roundoff error).,
11) If you want to make rectangles square, or have other objects stretch to be more square-like in aspect-ratio, then you could take the (x,y) points and compute an axis-aligned bounding box (AABB), which will give a width/height aspect ratio. Make a second pass through the 2D points and multiply x by height/width *or* y by width/height (one or the other, not both) to make the shape more like a square in the 2D projection. (But if you're going to do texture mapping, I do not recommend this, since it would result in pixels being stretched when you render....an unstretched projection will give a better pixel aspect ratio on the final image, and will enable consistent pixel size and aspect ratios on all the projected faces.)

} // end loop through faces

For a cylinderical face, if you know its a cylinder, then you could unwrap a cylinderical coordinates representation into Cartesian space, e.g., one axis of the Cartesian space mapping would be length along the cylinder, and the other axis would be the angular position around the circumference, e.g., 0 to 360 degrees scaled to, say, 0 to 1.

Spheres are messier to deal with....

[Edited by - grhodes_at_work on July 30, 2008 1:04:15 PM]
Graham Rhodes Moderator, Math & Physics forum @ gamedev.net
I don't know if the approach I discovered is the same as the one Graham describes above, but the way I solved this recently was to begin as above by choosing one edge to be horizontal and normalising it and calling the result a right vector.

I then reversed the face normal and called the result the look vector.

I then crossed them to get an up vector.

Armed with a look, right and up vector, I generated a view matrix which I applied to each point with D3DXVec3TransformCoord(), storing the results in a seperate buffer.

By ignoring the Z components of the result, the result was a collection of X,Y points which defined the face as if the camera was looking straight at it, with the X,Y's in a normalized (0-1) range.

Since I was working on a system where there was a 1-1 correspondance between world units and texture sizes, these X,Y's were almost the U,V's - I just had to find the lowest X and translate all the U's by it and the same for the Y and V to align the texture top left on the shape.

This doesn't address the skewed square issue you mention above, but it worked really well otherwise.

// heavily drawn from Game Programming with Direct3D 9 (Frank Luna)D3DXMATRIX MakeViewMatrix(D3DXVECTOR3 Look,D3DXVECTOR3 Right,D3DXVECTOR3 Up,D3DXVECTOR3 Pos){	D3DXVec3Normalize(&Look,&Look);	D3DXVec3Cross(&Up,&Look,&Right);	D3DXVec3Normalize(&Up,&Up);	D3DXVec3Cross(&Right,&Up,&Look);	D3DXVec3Normalize(&Right,&Right);	D3DXMATRIX M;	M(0,0)=Right.x;	M(0,1)=Up.x;	M(0,2)=Look.x;	M(0,3)=0.0f;		M(1,0)=Right.y;	M(1,1)=Up.y;	M(1,2)=Look.y;	M(1,3)=0.0f;		M(2,0)=Right.z;	M(2,1)=Up.z;	M(2,2)=Look.z;	M(2,3)=0.0f;		M(3,0)=-D3DXVec3Dot(&Right,&Pos);	M(3,1)=-D3DXVec3Dot(&Up,&Pos);	M(3,2)=-D3DXVec3Dot(&Look,&Pos);	M(3,3)=1.0f;	return M;}void snipped_from_my_code(){	D3DXVECTOR3 Pos=Shapes[Select.Index].GetFaceCenter(Select.Face);	D3DXVECTOR3 Norm=Shapes[Select.Index].GetFaceNormal(Select.Face,Select.Vertex);	if(D3DXVec3Length(&Norm)==0)		{		InfoText="Normal from vertex is ambiguous";		Mode=ModeInformation;				return;		}	D3DXVECTOR3 Right=Shapes[Select.Index].GetFaceLateral(Select.Face,Select.Vertex);	if(D3DXVec3Length(&Right)==0)		{		InfoText="Lateral from vertex is ambiguous";		Mode=ModeInformation;				return;		}	D3DXVECTOR3 Look=Norm*-1.0f;	D3DXVECTOR3 Up; D3DXVec3Cross(&Up,&Look,&Right);	D3DXMATRIX M=MakeViewMatrix(Look,Right,Up,Pos);	std::vector<D3DXVECTOR3> Output;	Shapes[Select.Index].TransformFace(Select.Face,M,Output);	float MinX=FLT_MAX,MinY=FLT_MAX;	for(WORD I=0;I<Output.size();++I)		{		if(Output.x<MinX) MinX=Output.x;		if(Output.y<MinY) MinY=Output.y;		}	for(WORD I=0;I<Output.size();++I)		{		Shapes[Select.Index].SetUV(Select.Face,I,Output.x-MinX,(Output.y*-1.0f)-MinY);		}	}}
Quote:Original post by EasilyConfused
I don't know if the approach I discovered is the same as the one Graham describes above, but the way I solved this recently was to begin as above by choosing one edge to be horizontal and normalising it and calling the result a right vector.


Actually, it is the same thing.
Graham Rhodes Moderator, Math & Physics forum @ gamedev.net
Quote:Original post by grhodes_at_work
Quote:Original post by EasilyConfused
I don't know if the approach I discovered is the same as the one Graham describes above, but the way I solved this recently was to begin as above by choosing one edge to be horizontal and normalising it and calling the result a right vector.


Actually, it is the same thing.


Sorry - thought it might be [smile].
Thanks for the detailed information, both you guys. I hope I can find time to try it out, but it won't be easy. I'm going to be a father tomorrow :) Solving hobby-programming problems is one thing, changing diapers is another!
(also a good reason to automate UV coordinates, since the baby and mama probably claim MUCH of papa's precious hobby time)

Rick
Quote:Original post by spek
Thanks for the detailed information, both you guys. I hope I can find time to try it out, but it won't be easy. I'm going to be a father tomorrow :) Solving hobby-programming problems is one thing, changing diapers is another!
(also a good reason to automate UV coordinates, since the baby and mama probably claim MUCH of papa's precious hobby time)

Rick


Not meaning to derail the thread, but congratulations!
Thanks!

This topic is closed to new replies.

Advertisement