Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Help Understand UVs

Discussion in 'Scripting' started by MikeyJY, Sep 16, 2021.

  1. MikeyJY

    MikeyJY

    Joined:
    Mar 2, 2018
    Posts:
    530
    I'm trying to manually import obj to unity I have this obj file from blender.It is the default cube:



    Code (CSharp):
    1. # Blender v2.82 (sub 7) OBJ File: ''
    2. # www.blender.org
    3. mtllib untitled.mtl
    4. o Cube_Cube.002
    5. v -1.000000 -1.000000 1.000000
    6. v -1.000000 1.000000 1.000000
    7. v -1.000000 -1.000000 -1.000000
    8. v -1.000000 1.000000 -1.000000
    9. v 1.000000 -1.000000 1.000000
    10. v 1.000000 1.000000 1.000000
    11. v 1.000000 -1.000000 -1.000000
    12. v 1.000000 1.000000 -1.000000
    13. vt 0.625000 0.000000
    14. vt 0.375000 0.250000
    15. vt 0.375000 0.000000
    16. vt 0.625000 0.250000
    17. vt 0.375000 0.500000
    18. vt 0.625000 0.500000
    19. vt 0.375000 0.750000
    20. vt 0.625000 0.750000
    21. vt 0.375000 1.000000
    22. vt 0.125000 0.750000
    23. vt 0.125000 0.500000
    24. vt 0.875000 0.500000
    25. vt 0.625000 1.000000
    26. vt 0.875000 0.750000
    27. vn -1.0000 0.0000 0.0000
    28. vn 0.0000 0.0000 -1.0000
    29. vn 1.0000 0.0000 0.0000
    30. vn 0.0000 0.0000 1.0000
    31. vn 0.0000 -1.0000 0.0000
    32. vn 0.0000 1.0000 0.0000
    33. usemtl None
    34. s off
    35. f 2/1/1 3/2/1 1/3/1
    36. f 4/4/2 7/5/2 3/2/2
    37. f 8/6/3 5/7/3 7/5/3
    38. f 6/8/4 1/9/4 5/7/4
    39. f 7/5/5 1/10/5 3/11/5
    40. f 4/12/6 6/8/6 8/6/6
    41. f 2/1/1 4/4/1 3/2/1
    42. f 4/4/2 8/6/2 7/5/2
    43. f 8/6/3 6/8/3 5/7/3
    44. f 6/8/4 2/13/4 1/9/4
    45. f 7/5/5 5/7/5 1/10/5
    46. f 4/12/6 2/14/6 6/8/6
    I set them in c# like this and it's working I have a cube, create a MeshFilter, assign tris and vertecis

    Code (CSharp):
    1. Vector3[] verts =
    2.            {
    3.                new Vector3(-1f, -1f, 1f),
    4. new Vector3(-1f, 1f, 1f),
    5. new Vector3(-1f, -1f, -1f),
    6. new Vector3(-1f, 1f, -1f),
    7. new Vector3(1f, -1f, 1f),
    8. new Vector3(1f, 1f, 1f),
    9. new Vector3(1f, -1f, -1f),
    10. new Vector3(1f, 1f, -1f),
    11.            };
    12. int[] tris =
    13.            {
    14.              1,2,0,
    15. 3,6,2,
    16. 7,4,6,
    17. 5,0,4,
    18. 6,0,2,
    19. 3,5,7,
    20. 1,3,2,
    21. 3,7,6,
    22. 7,5,4,
    23. 5,1,0,
    24. 6,4,0,
    25. 3,1,5,
    26.  
    27.            };
    28.  
    I don't unerstand the uv part.
    1) As far as I understand
    f 3/2/1 means:
    use the 3rd element from position array as this vertex position

    use the 2nd element from uv array as this vertex texture coordinate

    use the 1st element from the normal array as this vertex normal
    If I add the the first element of the three elements of a face I have the tris array, right?

    Then why there are more Vector2s in uvs array than Vector3s in positions array since a vertex has 1 position, 1 uv and 1 normal?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,517
    This is a correct statement for Unity.

    The OBJ format doesn't enforce this constraint.

    At import time Unity combines this information and produces meshes compatible with its internal structure.

    How Unity combines stuff is entirely dependent on import settings such as how to calculate normals and tangents, plus "Optimize Mesh" and perhaps a few others.

    There are plenty of runtime OBJ importer scripts out there to look at, and even exporters, since it's such a nice simple text-based format. It's like "the JSON of 3D objects."
     
  3. MikeyJY

    MikeyJY

    Joined:
    Mar 2, 2018
    Posts:
    530
    However for an unwrapped cube it makes sense for vertex to have more uv coordinates based on the triangle it is part of. The 2 red vertices are actually 1 vertex when we wrap the cube back, however they have 2 uvs. How can the indexed draw calls deal with it?
    upload_2021-9-16_17-43-54.png
    My understanding is that the 8 vertices of a cube are stored in a Vector3[8] and the connectionis stored in a int[36]. Then they are sent to vertex shader and then the vertex shader loops trough all of them and apply the matrix multiplication. Then the vertex shader sends the vertices to the fragment shader which loops trough all of them.
    Then when we sample it:
    Code (CSharp):
    1. Texture2D _MainTex;
    2. SamplerState sampler_MainTex; // "sampler" + “_MainTex”
    3. // ...
    4. half4 color = _MainTex.Sample(sampler_MainTex, uv);
    we sample for the vertex i at the uv i coordinate of the texture. If a vertex can have more uvs, then how the fragment shader knows to sample twice for the same vertex with different uv?
     
    Last edited: Sep 16, 2021
  4. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    Those 2 red dots are on different vertexes. We started with 1 vertex, decided we wanted it to have 2 different UV's, so split it into 2 verts. In other words, if we need a vertex to have N UV-coords, we can't do it, but we can create N-1 more verts at the same location.

    Normals are the same way -- a vertex only gets 1 of those. To give a box sharp edges we split each face. That's why a Unity cube has 24 verts. It's not 8 verts with modifiers(*) that count as 24 -- it's really 24 vertexes.

    (*)In Max or Blender it probably is 8 verts with modifiers, but that's only to help you edit/undo and so on. The graphics card and fbx/obj format doesn't handle modifiers - - they need the final version with every vertex split out into all copies.
     
    Bunny83 likes this.
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,915
    Exactly, that's the important part. And it's not even related to Unity. A GPU has a vertex buffer where the vertices are stored and an index buffer where the indices are stored that form faces / primitives / triangles. However s single vertex does not only have a position but may have other properties as well like a normal vector and UV coordinates. The important thing is that a single vertex (depending on the used hardware vertex format) consists of a set of different information that is grouped as a vertex. The index buffer can only reference vertices. There are no seperate lists or buffers for normals or uv coordinates on the GPU.

    File formats can of course store the information however they like as long as they can translate / transform the necessary information back.

    The obj file contains 12 faces / triangles. The obj file builds up the vertices on the fly, as you said, by giving 3 indices into seperate position, normal and uv coordinate arrays. Though as I just said, that's not how the GPU can work with the data. It needs a single vertex array with all the vertex information that belongs together being already grouped together. when you take your "faces" list and just reorder the 3 indices in order to group them according to their 3 indices, you get a list like that:

    Code (CSharp):
    1. 1/3/1    - V0
    2. 1/9/4    \ V1
    3. 1/9/4    /
    4. 1/10/5   \ V2
    5. 1/10/5   /
    6.  
    7. 2/1/1    \ V3
    8. 2/1/1    /
    9. 2/13/4   - V4
    10. 2/14/6   - V5
    11.  
    12. 3/2/1    \ V6
    13. 3/2/1    /
    14. 3/2/2    - V7
    15. 3/11/5   - V8
    16.  
    17. 4/4/1    - V9
    18. 4/4/2    \ V10
    19. 4/4/2    /
    20. 4/12/6   \ V11
    21. 4/12/6   /
    22.  
    23. 5/7/3    \ V12
    24. 5/7/3    /
    25. 5/7/4    - V13
    26. 5/7/5    - V14
    27.  
    28. 6/8/3    - V15
    29. 6/8/4    \ V16
    30. 6/8/4    /
    31. 6/8/6    \ V17
    32. 6/8/6    /
    33.  
    34. 7/5/2    \ V18
    35. 7/5/2    /
    36. 7/5/3    - V19
    37. 7/5/5    \ V20
    38. 7/5/5    /
    39.  
    40. 8/6/2    - V21
    41. 8/6/3    \ V22
    42. 8/6/3    /
    43. 8/6/6    - V23
    44.  
    So notice we have 8 paragraphs which belong to a certain vertex position, that's why the first index of those groups have the same "position index". As you can see I labelled the vertices on the right. I grouped vertices that have the exact same position, uv and normal as one vertex. As a result we get 24 unique vertices. Also note that each corner has exactly 3 unique vertices and each of those has a different normal and a different uv coordinate. Though for example V21 and V22 have the same uv coordinate and the same position, but a different normal. That's why V21 and V22 have to be two seperate vertices. Those vertices which are 100% equal (so all 3 indices are the same) can actually be grouped / shared between the faces. There are exactly 12 vertices that are shared by two faces. Guess which vertices on the cube that may be? A single face is made up of 2 triangles. So a total of 6 vertices. However the diagonal of that face that splits the face into the two triangles, share the same vertex at each end. Those vertices can actually be shared by the two triangles of a cube face. That's why Unity's default cube has those 24 vertices instead of 36 vertices.

    The naive method to load such an obj file is to just create a seperate vertex for every vertex of a face. This does work just fine. However you can savely share vertices that are identical (have the same position, uv and normal index). This of course requires a bit more work during loading. What you need to do is find all the unique combinations in the faces list and generate an actual vertex for this combination. Then it's just a matter of generating the proper index / triangle list that uses those indices. Using a dictionary may be the best way to map the 3 obj indices to a proper vertex index in Untiy.

    Though if you don't care about being efficient and sharing vertices where possible, you can of course just go the naive way. So you just iterate through the faces and for each vertex tripple you generate a new vertex based on those 3 indices.

    Note sharing can actually becomes more complicated when you think about relative indices which the obj format supports ^^. That means negative values indicate the 1 based index from the last defined vertex upwards. This allows an obj file to essentially group vertices that belong to one part together, define the faces for that part and then defined more vertices followed by the faces for that second part. Thanks to relativ indexing, as long as the vertices and faces stay together, you can actually remove or add parts to the file without messing up the indices. Though as It's stated on the wiki page, not all loaders actually support this. So for simplicity just going the naive way is the simplest one.
     
  6. MikeyJY

    MikeyJY

    Joined:
    Mar 2, 2018
    Posts:
    530
    The following question may be already explained, however I have to ask. After I loop and create all vertices positions, and uvs, how am I going to construct the triangle with them in the int[]?
     
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,915
    When you go down the naive way, each triangle has its own private set of 3 vertices which would be just in order in the vertices array. That means the index array just contains all indices in increasing order. So this would be enough
    Code (CSharp):
    1. int[] tris = new int[verts.Length];
    2. for(int i = 0; i < tris.Length; i++)
    3. {
    4.     tris[i] = i;
    5. }
    6.  
    When you properly share vertices, of course you have to use the correct index of the shared vertex. That's exactly the thing which makes finding the shared vertices tricky. Usually a dictionary comes in handy to keep track of the mapping while the vertices are generated.
     
  8. MikeyJY

    MikeyJY

    Joined:
    Mar 2, 2018
    Posts:
    530
    Thanks. That means that the naive method is like the primitive draw in the opengl and the shared vertices method is like opengl indexed draw?
     
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,915
    Yes, kind of ^^. Though the immediate mode / primitive drawing mode isn't really used nowadays. Though using an index buffer that just draws the vertices in order essentially behaves in a similar way.