Search Unity

How todo fully Automated Mesh UV UnWrapping?

Discussion in 'Scripting' started by KaotikZ, Mar 8, 2020.

  1. KaotikZ

    KaotikZ

    Joined:
    Mar 2, 2016
    Posts:
    6
    Does someone know how todo fully automated UV UnWrapping? I wish that bigger triangles would get more texture uv surface.
     
  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    what exactly do you need?
    (fully automated uv unwrapping does not exist, you need to be more specific)
     
  3. KaotikZ

    KaotikZ

    Joined:
    Mar 2, 2016
    Posts:
    6
    I wish to generate UV coordinates for arbitrary mesh. Every triangle should get a surface area on UV plane that is in a relation to the his surface area in vertex space. Triangles that are close on mesh don't have to be close on UV plane, as texture will be generated procedural as well.

    We assume 1 triangle -> 3 Vertex, 3 UVs

    It should be generic method that works with any mesh, it does not have to be ideal.

    (I have been able to make simpler algorithm, where we assign to each triangle the same amount of UV space.)
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,726
    You could always look at an open source project like Blender3D and see how they do it. I'm sure they have put hundreds (if not thousands) of hours of study into the process, and I know it is one of those "traveling salesman" type Hard Problems(tm) that people periodically come up with better and different ways to do.
     
  5. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,638
    One way that uvs are often mapped automatically is by a tri-planar projection. This works especially well with rocks and terrain-type elements where it looks better for areas of the surface to be continuous, but not necessarily precise.
     
  6. KaotikZ

    KaotikZ

    Joined:
    Mar 2, 2016
    Posts:
    6
    Thanks ... i have been trying to solve it in 3 hours and i did end up with many non elegant solutions xD So it's not a simple thing
     
  7. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    This is what I do.
    This is deeply embedded inside my framework, so take it as a pseudo-code.
    edgeUVs has only two UV coordinates that are in relation to edge.

    The general algorithm resolves the entire UV island based on just one edge, because you have to begin the cascade from somewhere.

    I basically find the missing vertex in the absolute 3D triangle space, and then transform that space into UV space by using the known 2D points as anchors. I had to modify Quaternion.FromToRotation to prevent anomalous biases, but there are other ways to do this, this was just the simplest.

    Quaternion.FromToRotation cannot mathematically give you a proper quaternion for two approximately opposite vectors, because the rotation is not well-defined. However, Unity's implementation will throw it's own biases in, which act rather weirdly (I'm guessing there is a simple reason for this implementation, but in this case, it was behaving completely crazy, and definitely not as robust as it should be), where if you work with a fixed plane, you can easily implement your own bias (in one plane there is only 1 solution for the case of two approximately opposite vectors), if only you can detect a malign scenario, which is what I did.

    Code (csharp):
    1. public Vector2[] InferRelaxedUVsFromEdge(Face face, in Edge edge, Vector2[] edgeUVs) {
    2.   int index = face.ExclusionIndex(edge.Ids); // throws an error if the ids do not belong
    3.  
    4.   Vector3 v01 = edge.ToVector(face.Mesh);
    5.   Vector3 v12 = FindEdge(edge[1], face[index]).ToVector(face.Mesh);
    6.   Vector3 vuv = (edgeUVs[1] - edgeUVs[0]);
    7.  
    8.   Quaternion plane3DRot = QuaternionEx.FromToRotation(face.CalcSurfaceNormal(), Vector3.back);
    9.   Quaternion zRot = QuaternionEx.FromToRotation(plane3DRot * v01.normalized, vuv.normalized, Quaternion.Euler(0f, 0f, 180f));
    10.  
    11.   float projScale = (vuv.magnitude / v01.magnitude); // no need to check for zero, that would be a degenerate triangle anyway
    12.  
    13.   var uvs = new Vector2[] {
    14.     edgeUVs[0],
    15.     edgeUVs[1],
    16.     edgeUVs[1] + projScale * (zRot * plane3DRot * v12).ToVector2_XY()
    17.   };
    18.  
    19.   // finally, the uvs need to match the actual face indice
    20.   if(edge.IsAlignedWith(face)) {
    21.     uvs.Shift((Face.NODES - 1) - index);
    22.   } else {
    23.     uvs.Shift(index);
    24.     uvs.Reverse();
    25.   }
    26.  
    27.   return uvs;
    28. }

    There is a thread somewhere around here where I posted the modification of FromToRotation.
    If the second argument is ignored, it falls back to Unity's method anyway.
    Code (csharp):
    1.   static public Quaternion FromToRotation(Vector3 dir1, Vector3 dir2, Quaternion ifOpposite = default) {
    2.   float w = 1f + Vector3.Dot(dir1, dir2);
    3.  
    4.   if(w < 1E-6f) {
    5.     if(ifOpposite == default) return Quaternion.FromToRotation(dir1, dir2);
    6.     return ifOpposite;
    7.   }
    8.  
    9.   Vector3 xyz = Vector3.Cross(dir1, dir2);
    10.   return new Quaternion(xyz.x, xyz.y, xyz.z, w).normalized;
    11. }

    Yes it's a weird, simple thing, yet it's probably the most profound of all quaternion methods.
     
    SoftwareGeezers and KaotikZ like this.
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    Here, something fun you can experiment with.

    I made a tiny Quaternion.Euler implementation that supports an arbitrary order of rotations, using this FromToRotation above. This is just a proof of concept, but I imagine it could be lowered to a more fundamental level for speed.

    But this is mighty readable and a good teaching tool because of it. In fact, you won't find this anywhere on the internet, as far as I can tell. Quaternion.Euler is like some sort of a black box, and even if you find it, almost always looks like it's pulled out from an assembly and is impossible to set straight, considering that all engines apply different conventions.

    According to Unity's docs on Quaternion.Euler
    So ZXY should be the default order, if we are to compare the behavior of my Euler with Unity's.
    This demo also showcases how to treat permutations without having to manually branch in code.

    It includes a shoulder-to-shoulder comparison with Unity's Euler, and is pretty much self-contained, except for QuaternionEx.FromToRotation which you have above, and VectorEx.Polar which is simply
    Code (csharp):
    1. static public Vector2 Polar(float theta) => new Vector2(Mathf.Cos(theta), Mathf.Sin(theta));
    and ok, the swizzling helpers for Vectors, but I hope they're easy to read anyway.

    Code (csharp):
    1. using CommonExtensions;
    2. using UnityEngine;
    3.  
    4. [ExecuteInEditMode]
    5. public class QuaternionEulerTest : MonoBehaviour {
    6.  
    7.   [SerializeField] [Range(0f, 360f)] public float x;
    8.   [SerializeField] [Range(0f, 360f)] public float y;
    9.   [SerializeField] [Range(0f, 360f)] public float z;
    10.   [SerializeField] public EulerOrder order = EulerOrder.ZXY;
    11.   [SerializeField] public bool sameOrigin;
    12.   [SerializeField] public VectorEnum pickVector = VectorEnum.Basis;
    13.  
    14.   public Quaternion myEuler(Vector3 euler, EulerOrder order = EulerOrder.ZXY) {
    15.     euler *= Mathf.Deg2Rad;
    16.  
    17.     var qx = QuaternionEx.FromToRotation(Vector3.forward, VectorEx.Polar(-euler.x).ToVector3_ZY(), new Quaternion(1f, 0f, 0f, 0f));
    18.     var qy = QuaternionEx.FromToRotation(Vector3.right,   VectorEx.Polar(-euler.y).ToVector3_XZ(), new Quaternion(0f, 1f, 0f, 0f));
    19.     var qz = QuaternionEx.FromToRotation(Vector3.right,   VectorEx.Polar(+euler.z).ToVector3_XY(), new Quaternion(0f, 0f, 1f, 0f));
    20.  
    21.     Quaternion q = Quaternion.identity;
    22.  
    23.     int o = (int)order;
    24.     for(int axis = 0; axis < 3; axis++) {
    25.       switch((o >> (axis << 1)) & 0x3) {
    26.         case 1:  q *= qy; break;
    27.         case 2:  q *= qz; break;
    28.         default: q *= qx; break;
    29.       }
    30.     }
    31.  
    32.     return q;
    33.   }
    34.  
    35.   void OnDrawGizmos() {
    36.     if(pickVector != VectorEnum.Basis) {
    37.       draw(pickVector, Color.white);
    38.     } else {
    39.       draw(VectorEnum.Right, Color.red);
    40.       draw(VectorEnum.Up, Color.green);
    41.       draw(VectorEnum.Forward, Color.blue);
    42.     }
    43.   }
    44.  
    45.   void draw(VectorEnum vecType, Color lineColor) {
    46.     drawRotatedPointAround(Vector3.zero, myEuler(new Vector3(x, y, z), order), vecType, lineColor, Color.white);
    47.     drawRotatedPointAround(!sameOrigin? 2f * Vector3.right : Vector3.zero, Quaternion.Euler(new Vector3(x, y, z)), vecType, lineColor, Color.grey);
    48.   }
    49.  
    50.   void drawRotatedPointAround(Vector3 p, Quaternion q, VectorEnum vecType, Color lineColor, Color dotColor) {
    51.     var v = Vector3.forward;
    52.     if(vecType == VectorEnum.Right) v = Vector3.right;
    53.     else if(vecType == VectorEnum.Up) v = Vector3.up;
    54.  
    55.     var k = p + q * v;
    56.  
    57.     Gizmos.color = lineColor;
    58.     Gizmos.DrawLine(p, k);
    59.  
    60.     Gizmos.color = dotColor;
    61.     Gizmos.DrawSphere(k, .1f);
    62.   }
    63.  
    64.   public enum EulerOrder : int {
    65.     XYZ = 0b00_01_10, // (6)
    66.     XZY = 0b00_10_01, // (9)
    67.     YXZ = 0b01_00_10, // (18)
    68.     YZX = 0b01_10_00, // (24)
    69.     ZXY = 0b10_00_01, // (33)
    70.     ZYX = 0b10_01_00  // (36)
    71.   }
    72.  
    73.   public enum VectorEnum {
    74.     Right,
    75.     Up,
    76.     Forward,
    77.     Basis
    78.   }
    79.  
    80. }

    Hopefully this helps you reach a better understanding of how quaternions work. You will need them.
    If only someone exposed them to me like this, back when I was bashing my head against the wall....
     
    Michelle_Ca likes this.
  9. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    Oh and btw, just remove the last argument
    new Quaternion(...)
    in rows with
    var qx/qy/qz
    , and try X: 90, Y:180, Z: 0 and you'll catch the evilness of Unity's FromToRotation. I'm embarrassed for even witnessing it, especially when you can see how cheap it is to fix.

    So good luck, non-math-inclined programmers :)
    (I'm actually S*** at math if you were to ask my former educational facilities, so chin up)
     
    Jilbarkus likes this.