Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question How to UV Map Script-Generated Mesh

Discussion in 'Scripting' started by Ewu_Uwe, Jul 10, 2021.

  1. Ewu_Uwe

    Ewu_Uwe

    Joined:
    Apr 13, 2020
    Posts:
    21
    Hello,
    I'm relatively new to 3D Mesh Generation and want to create a simple 200x200 flat square, wich should be able to be textured tile by tile.
    The Code for the Mesh Generation:
    Code (CSharp):
    1. Mesh mesh;
    2.  
    3.     Vector3[] vertices;
    4.     int[] triangles;
    5.     Vector2[] uvs;
    6.  
    7.     public int xSize = 20;
    8.     public int zSize = 20;
    9.  
    10.     void Start()
    11.     {
    12.         mesh = new Mesh();
    13.         GetComponent<MeshFilter>().mesh = mesh;
    14.  
    15.         CreateShape();
    16.         UpdateMesh();
    17.     }
    18.  
    19.     void CreateShape()
    20.     {
    21.         vertices = new Vector3[(xSize + 1) * (zSize + 1)];
    22.         for (int i = 0, z = 0; z <= zSize; z++)
    23.         {
    24.             for (int x = 0; x <= xSize; x++)
    25.             {
    26.                 vertices[i] = new Vector3(x, 0, z);
    27.                 i++;
    28.             }
    29.         }
    30.  
    31.         triangles = new int[xSize * zSize * 6];
    32.  
    33.         for (int vert = 0, tris = 0, z = 0; z < zSize; z++)
    34.         {
    35.             for (int x = 0; x < xSize; x++)
    36.             {
    37.                 triangles[0 + tris] = vert + 0;
    38.                 triangles[1 + tris] = vert + xSize + 1;
    39.                 triangles[2 + tris] = vert + 1;
    40.                 triangles[3 + tris] = vert + 1;
    41.                 triangles[4 + tris] = vert + xSize + 1;
    42.                 triangles[5 + tris] = vert + xSize + 2;
    43.  
    44.                 vert++;
    45.                 tris += 6;
    46.             }
    47.             vert++;
    48.         }
    49.        
    50.     }
    51.  
    52.     private void UpdateMesh()
    53.     {
    54.         mesh.Clear();
    55.         mesh.vertices = vertices;
    56.         mesh.triangles = triangles;
    57.         mesh.uv = uvs;
    58.         mesh.RecalculateNormals();
    59.     }
    60. }
    This generates a flat square, but I'm not sure how to texture this correctly, I've tried to UV-Map it, but it didn't really work.
    Basically what I want is a simple flat Map, which should be procedually generated, and I'm not sure if the Method above is the best way to get this Result.

    I would be very happy If someone tells me if there is a better way to do something like this, or how to texture a Mesh like the one above.

    Thank you for your Help,
    EwuUwe
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,711
    acandael likes this.
  3. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    You created a fully shared mesh. So all your quads share the vertices in between. This makes it impossible to texture them individually since they would have to share the UV coordinates as well. In order to texture each tile seperately you have to create 4 seperate vertices for each tile.

    Currently, assuming your xSize and zSize are both 20 like shown in your code, you generate 21x21 vertices for 20x20 quads. So you would generate 441 vertices for 400 quads. As I said, this would not work. What you have to do is create
    xSize * zSize * 4
    vertices, so 1600 vertices. Each quad would use it's own distinct 4 vertices.

    Something like this:

    Code (CSharp):
    1.     void CreateShape()
    2.     {
    3.         vertices = new Vector3[xSize * zSize * 4];
    4.         uvs = new Vector2[vertices.Length];
    5.         triangles = new int[xSize * zSize * 6];
    6.         for (int v = 0, t=0, z = 0; z < zSize; z++)
    7.         {
    8.             for (int x = 0; x < xSize; x++,v+=4,t+=6)
    9.             {
    10.                 vertices[v] = new Vector3(x, 0, z);
    11.                 vertices[v+1] = new Vector3(x, 0, z+1);
    12.                 vertices[v+2] = new Vector3(x+1, 0, z+1);
    13.                 vertices[v+3] = new Vector3(x+1, 0, z);
    14.                 uvs[v] = new Vector2(0, 0);
    15.                 uvs[v+1] = new Vector2(0, 1);
    16.                 uvs[v+2] = new Vector2(1, 1);
    17.                 uvs[v+3] = new Vector2(1, 0);
    18.                 triangles[t] = v;
    19.                 triangles[t+1] = v + 1;
    20.                 triangles[t+2] = v + 2;
    21.                 triangles[t+3] = v;
    22.                 triangles[t+4] = v + 2;
    23.                 triangles[t+5] = v + 3;
    24.             }
    25.         }
    26.     }
    This would map each tile to the whole image. Of course if you use a texture atlas you have to modify the uvs of each quad to represent the area you are interested in.
     
    Kurt-Dekker, All_American and Ewu_Uwe like this.
  4. Ewu_Uwe

    Ewu_Uwe

    Joined:
    Apr 13, 2020
    Posts:
    21
    This works perfectly. Thank you!
     
  5. cemugurk

    cemugurk

    Joined:
    Nov 27, 2014
    Posts:
    33
    I'm using the same technique to create a tile based terrain. My initial purpose was to create an atlas an apply uv locations to specific tiles. It works but I have some glitch-like white lines appears on screen.

    Screenshot 2023-04-19 at 10.37.02.png
    This is a 8x8 single mesh generated. You can see horizontal white glitch-like lines clearly.

    This is my small atlas to test
    Screenshot 2023-04-19 at 10.43.43.png

    This is how I calculate uvs based on the texture:

    Code (CSharp):
    1.     public static Vector2[] GRASS = new Vector2[]
    2.     {
    3.         new Vector2(0f,   0.5f),
    4.         new Vector2(0.5f, 0.5f),
    5.         new Vector2(0f,     1f),
    6.         new Vector2(0.5f,   1f),
    7.     };
     
  6. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    Well, you have enabled mipmapping which can always cause issues with an atlas. An atlas that is supposed to have mipmaps should only contain power-of-two sized tiles and the mipmap level should be capped to the size when a single tile is reduced to a single texel / pixel. So if you have tiles of 64x64 inside the atlas, after 6 levels (64>32>16>8>4>2>1) a single tile would only contain a single pixel. Going lower means you would mix adjacent tiles.

    There are two ways how you can use an atlas. Either don't use mipmapping and use padding. So you would add a border area around each tile that simply copies the outer pixels so you don't get color bleeding to the neighbor. The other solution is to actually offset the UVs by half a texel. So in your example you pick a portion from 0 to 0.5. However it may want to map from 0.001 to 0.499. The 0.001 is just an example here. The offset should be less than a texel but also not too small and not too large.

    When you map a texture right at the edge to a new tile in texture space, due to floating point rounding you may step into the neighbor tile. Just think about a texture that is just 4x4 pixels large. So the portion from 0 to 0.5 would map to the first two pixels. However the 0.5 is right between two pixels. So the GPU may pick the left or the right. You should query a position that is still inside your desired pixel / texel.
     
    All_American likes this.
  7. cemugurk

    cemugurk

    Joined:
    Nov 27, 2014
    Posts:
    33
    Wow thanks for the detailed answer and your time! I understand bleeding issues and disabling mipmap couldn't work for me either.

    I'm fairly new to this concept and I kinda bit confused with the process. Having a single material for the whole map(multiple chunks) sounds a good idea (from my naive point of view of course:) ). I can create multiple texture files and in runtime I can combine them into a single texture with an offset/padding like you described. But ultimately, which process would be the best solution for a tile based terrain that contains several chunks of meshes?
     
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Having atlas textures is independent of having chunks. Chunk is just a spatially delimited model, which is useful for optimal generation of geometry and frustum culling. Your chunks can have multiple materials (i.e. made out of submeshes), but you have to modify your chunk generation algorithm to address the local materials array properly.

    Example
    Let's say you have 3 atlas textures A, B, and C, each with 64 subtextures.
    If you have a chunk that needs to have 16 different subtextures, that come from textures A and C, your materials array (in the renderer) would have to be [A, C]. Then you'd build two submeshes, the 1st would include UVs for A (and skip anything else), and the 2nd would include UVs for C. Basically in your generator, there has to be as many passes as there are different atlases needed by the chunk. The final result is a single mesh, but its data is serialized in such a way that first N vertices are mesh #1, and the rest is mesh #2 (see subMeshCount and SetSubMesh).
    --

    Another option, and I'm not suggesting that's the best solution (if there is any such thing), you might want to check out texture arrays as well. Texture arrays are slightly more difficult to set up, but you'd avoid having to build atlases (because these natively behave as atlases on the GPU). Heads up: texture arrays are not supported on every platform. Note: AFAICT there is practically no upper limit on how many textures you use (apart from VRAM consumption), so you also don't have to split your atlases or do anything if you decide to add new textures..

    Instead, you populate this texture array with multiple (sub)textures (a prerequisite is that they all have the same dimensions), and supply the index via the W coordinate of UVW (instead of just UV), although you'd have to write a custom shader (you can find examples online, both for URP and built-in rendering). Although there is some overhead to get this rolling, this setup makes tile-based geometry much more maintenance-friendly in the long-run. And besides, you'd probably have to write your own shader for this kind of terrain anyway, it's just a matter of time, so if you attack this early, you have saved yourself from having to do this later in the project.

    Example
    Now you can solve the previous example without having two submeshes as well. You simply supply your texarray material, and build the UVW map in one go.
     
    Last edited: Apr 19, 2023
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,711
    The best process is to not necro-post to 2021 threads for a new problem because now all those poor folks are getting notifications for stuff they (probably) don't care about.

    You may wish to work through some tutorials on making environments, looking for games that use similar setups to what you contemplate for your game. Remember that making a 2023 AAA game is going to be a different system entirely than making a Playstation 1 game or a GameBoy Advance retro game.

    Your solution WILL be intimately tied to your texture budget, which is intimately tied to your target hardware (top-level gaming rig computers? Old Android devices from 2015?) These are going to be different.
     
    All_American and orionsyndrome like this.
  10. cemugurk

    cemugurk

    Joined:
    Nov 27, 2014
    Posts:
    33
    My question was a reply to a user that answered to the specific question, that all about on the concept that originally asked from the thread owner. So why do you bother yourself to tell me about new tech if you don't care about this topic?

    What made you think that I need to learn 2023 AAA graphics pipelines based on my question?

    How did you come up that posting to 2 year old thread is considered necro-posting anyway? This is not a unity 5 question or something related to specific framework.
    What's the point hosting old(!) forum threads to users and allow them post?
     
  11. cemugurk

    cemugurk

    Joined:
    Nov 27, 2014
    Posts:
    33
    Thanks to everyone who polite to answer and for their time.
     
  12. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    First, replying to a post that is two years old, doesn't count as a reply any more. It's a necro-post.
    Second, Kurt is also one of the people who responded originally, so he's also the "owner" of this thread. Kind of.

    There was nothing impolite about Kurt's reply.

    Kurt is a veteran on this forum and that was a proper advice to you, given that you both necro'd and hijacked a thread, which is against the rules, mostly because it doesn't help anybody and notifies the original owners about something that they didn't partake in. If we're half-tolerant, that doesn't mean you get a free pass to misbehave all the way.

    Either calm yourself down and learn the rules of conduct, or else I will surely find ways to avoid your questions. And I bet Kurt feels the same.
     
    Bunny83 and Kurt-Dekker like this.
  13. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    Because many seem to not be able to find it, here's the link to the Code of Conduct which is a sticky post in the scripting subforum.

    When you have a problem or question, just create your own thread. You can always link to another thread where you may have found other relevant information. However hijacking other threads should generally be avoided. Note that this thread was about generating UV coordinates for a procedural mesh. The thread is based on the OP. You essentially brought in a new sub topic like an atlas, color bleeding and some other things that weren't even a thing in the OP.

    When you justify that the thread is "kinda" related to your problem, after 5 slight topic changes would could arrive at a complete different topic since every new "problem" could introduce a slightly different aspect and slowly deviating from the actual topic.

    Another issue is that when packing several topics that are kinda related into a single thread, the thread grows and it gets more and more difficult for others to find relevant information. We have some monster threads here with 200+ pages. Those are a pure nightmare. They got big because a lot of people contributed at the time it was started. Though later it was necro'd a couple of times and has grown into chat room with 50 different topics spread over countless pages. Sometimes necro posts are ok when they are actually on topic and may actually clear up some confusion. For example this thread was about Rigidbody.drag and what it actually does. At that time I actually did several tests and reverse engineered the actual forumla. Though I directly addressed the question in the OP to add information that was still missing. The necro post by FariAnderson several years later was completely off topic since the thread was about rigidbody.drag and not real world drag.

    So just create a new thread next time :). That way the thread is yours. If you want to address a certain person, you can always use the @ syntax to get the attention of a certain user. Though only do that when it's really necessary or your thread is a direct follow up to something that person has posted. Choosing a good title can also help. We have way too many "I need help!!!", "What should I do?", "I got xxx error", "What is the best ....?" threads which don't tell you anything about the content of the thread :)
     
    orionsyndrome and Kurt-Dekker like this.