Procedural UV maps (some help needed)

Maybe someone can help me to get a grip on “mesh.uv”, i seem to be lost somewhere in my procedural meshing between array index 16732 and 16733 :wink:

Here is what i want to do:

I got a texture with 4 different small tile textures in it. 2x2 squares. one each for sand, grass, water, rock.

I also got a 100x100 plane mesh (procedurally generated and later transformed).

I now want to place the small tiles from the texture “randomly” onto the plane.

I guess i have to somehow make custom UVs that map a segment of a texture to faces. I just cant figure out how it works. I hope someone here can give me (or direct me to) a primer on how UV maps are actually built using “mesh.uv”. Can’t figure it out from whats written in the documentation.


o/ Pray for help.

UV maps go from 0.0 to 1.0. Any values above or below that just repeat the 0…1 range (so e.g. 1…2 is the same as 0…1). Let’s assume your texture is set up like this:

  0        1      
1 Sand Grass
  Sand Grass
  Sand Grass
  Water Rock
  Water Rock
0 Water Rock

A square with sand would therefore have four vertices, with the UV values for the respective vertices being

(0.0, 1.0) (0.5, 1.0)
(0.0, 0.5) (0.5, 0.5)

A square with rock would be

(0.5, 0.5) (1.0, 0.5)
(0.5, 0.0) (1.0, 0.0)

Note that all vertices are going to have to be split because of UV seams in this case. That is, each square needs four vertices; it’s not going to be feasible to have vertices shared between neighboring squares. In the best case, a 100x100 plane would have 10,000 vertices, which would be possible, for example, if you had the texture mapped once over the entire mesh. Here, it’s going to be 40,000 vertices. But I guess you know that, since you mentioned array index 16732…watch out for 27138–that one’s a doozy. :wink:

Note also that you have to watch out for mipmap problems. Unless you’re not using mipmaps at all, you probably want to inset the values a little so there’s space between the textures or else you usually get texture bleeding at higher mipmap levels. In other words, use the range of, say, 0.55 through 0.95 instead of 0.5 through 1.0 (that was just for the sake of demonstration above). Yes, this makes seamlessly tiling textures somewhat problematic when you’re using texture atlases like this.

Hope that makes some sense!

–Eric

Thanks Eric, that helped! Here is what i managed to create so far:

function Start () {
	
	var mesh = new Mesh ();
	GetComponent (MeshFilter).mesh = mesh;
	
	mesh.Clear();
	var vert = new Array();
	var tria = new Array();
	var uvs = new Array();
	var tiles = new Array();
	var u : float = 0.25;
	var v : float = 0.25;

	var fields : int = 10;
	var scale : float = 1;
	
	// lets make a few maps
		
	// elevation / tile positions
	// makes an array that stores the cathesian coordinates of each tile
	for (x = 0; x < fields; x++) {
		for (z = 0; z < fields; z++) {
			tileIndex = x * fields + z;
			y = Random.value;
			tiles[tileIndex] = Vector3(x,y,z);
		}
	}
	Debug.Log("Tiles initialized: " + tiles.length);
	
	// vertices
	// generate 4 vertices for each tile
	// vertices are not shared between tiles, each has his own 4 vertices
	// the vertices of any tile-index can be found at indices:
	// 0,0 = tileIndex * 4
	// 1,0 = tileIndex * 4 + 1
	// 0,1 = tileIndex * 4 + 2
	// 1,1 = tileIndex * 4 + 3
	vert.length = tiles.length * 4;
	i = 0;
	for (tile in tiles) {
		vertIndex = i * 4; // vertIndex = tileIndex * 4
		vert[vertIndex    ] = Vector3(tile.x+0, tile.y, tile.z+0) * scale;
		vert[vertIndex + 1] = Vector3(tile.x+1, tile.y, tile.z+0) * scale;
		vert[vertIndex + 2] = Vector3(tile.x+0, tile.y, tile.z+1) * scale;
		vert[vertIndex + 3] = Vector3(tile.x+1, tile.y, tile.z+1) * scale;
		i++;
	}
	
	// triangles
	// generates 2 triangles for each tile to make a quad
	tria.length = tiles.length * 6;
	i = 0;
	for (tile in tiles) {
		triaIndex = i * 6; // triaIndex = tileIndex * 6
		vertIndex = i * 4;
		tria[triaIndex    ] = vertIndex + 3; // 1,1
		tria[triaIndex + 1] = vertIndex + 1; // 1,0
		tria[triaIndex + 2] = vertIndex + 2; // 0,1
		tria[triaIndex + 3] = vertIndex + 2; // 0,1
		tria[triaIndex + 4] = vertIndex + 1; // 1,0
		tria[triaIndex + 5] = vertIndex + 0; // 0,0
		i++;
	}

	// assign the vertices and triangles to the mesh now.
	mesh.vertices = vert;
	mesh.triangles = tria;
	Debug.Log("Vertices initialized: " + tria.length);
	Debug.Log("Triangles initialized: " + vert.length);
	
	// initialize the normals
	mesh.RecalculateNormals();
	var norm = mesh.normals;
	for (i=0;i<norm.Length;i++) {
		norm[i] = Vector3.up;
	}
	mesh.normals = norm;
	Debug.Log("Normals initialized: " + norm.Length);
	
	// UV mapping
	uvs.length = vert.length; // there will be as many UVs as there are vertices
	// we have to map each vertix to a location on the texture. 0,0 for the lower
	// left, 1,1 for the top right position on the texture. Or any value in between
	// lets iterate through the tiles once more
	i = 0;
	for (tile in tiles) {
		// assign a tile depending on the tile's elevation
		// (tile.y which was initialized randomly in the beginning)
		if (tile.y >= 0) { // water
			u = 0.5;
			v = 0.5;
		}
		if (tile.y > 0.5) { // sand
			u = 0.5;
			v = 0;
		}
		if (tile.y > 0.8) { // grass
			u = 0;
			v = 0.5;
		}
		if (tile.y > 0.9) { // rock
			u = 0;
			v = 0;
		}
		uvIndex = i * 4;
		uvs[uvIndex + 0] = Vector2(u,		v);
		uvs[uvIndex + 1] = Vector2(u+0.5,	v);
		uvs[uvIndex + 2] = Vector2(u,		v+0.5);
		uvs[uvIndex + 3] = Vector2(u+0.5,	v+0.5);
		i++;
	}

	mesh.uv = uvs;
}