2
\$\begingroup\$

I have created a procedural low poly rock mesh and now I would like to apply textures on it.

I have the 3D-coordinates of every vertex of the mesh but that's about it. How would I automatically generate a UV map for this mesh, since this list of vertices will change every time I generate a rock? How do I get all these faces correctly mapped in a (1,1) square?

I am working in Unity3D if that helps.

Thanks,

Alex

EDIT: Preserving the face shapes/sizes is priority! I don't want distortion at all. The texture will be generally just the same color with some darker/lighter spots so every edge could be used as a seam. Faces can overlap! I just want all the faces in one 2D box (1,1), with the correct dimensions.

EDIT 2:

I have this code:

    Vector3[] verts = mesh.vertices;

Vector2[] uvs = new Vector2[verts.Length];

for (int i = 0; i < uvs.Length; i++)
{

    uvs[i] = new Vector2(Mathf.Abs(verts[i].x), Mathf.Abs(verts[i].z));

}

Which gives me the following UV map: [![enter image description here][2]][2]

EDIT 3:

  float scaleFactor = 0.5f;

int[] tris = mesh.triangles;

Vector3[] verts = mesh.vertices;
Vector2[] uvs = new Vector2[verts.Length];

// Iterate over each face (here assuming triangles)
for(int index = 0; index < tris.Length; index += 3)
{
    // Get the three vertices bounding this triangle.
    Vector3 v1 = verts[tris[index]];
    Vector3 v2 = verts[tris[index + 1]];
    Vector3 v3 = verts[tris[index + 2]];

    // Compute a vector perpendicular to the face.
    Vector3 normal = Vector3.Cross(v3 - v1, v2 - v1);

        Debug.Log("surface " + index/3 + " : " + (normal*0.5f).magnitude);

    // Form a rotation that points the z+ axis in this perpendicular direction.
    // Multiplying by the inverse will flatten the triangle into an xy plane.
    Quaternion rotation = Quaternion.Inverse(Quaternion.LookRotation(normal));

    // Assign the uvs, applying a scale factor to control the texture tiling.
    uvs[tris[index]]     = (Vector2)(rotation * v1) * scaleFactor;
    uvs[tris[index + 1]] = (Vector2)(rotation * v2) * scaleFactor;
    uvs[tris[index + 2]] = (Vector2)(rotation * v3) * scaleFactor;
}

The red triangle on the left and the dark blue one on the right should be the same shape/size:

EDIT 4:

Mesh mesh = new Mesh();
mesh.name = "RockMesh";
List<int> Triangles = new List<int>();

var vertices = Vertices.Select(x => new Vertex(x)).ToList();

var result = MIConvexHull.ConvexHull.Create(vertices);
mesh.vertices = result.Points.Select(x => x.ToVec()).ToArray();
var xxx = result.Points.ToList();

foreach(var face in result.Faces)
{
    Triangles.Add(xxx.IndexOf(face.Vertices[0]));
    Triangles.Add(xxx.IndexOf(face.Vertices[1]));
    Triangles.Add(xxx.IndexOf(face.Vertices[2]));
}

mesh.triangles = Triangles.ToArray();

EDIT 5:

I had to move the code a bit but it finally works now! Like you guys predicted, the seems are not optimal, but I'm happy how it is now. Thanks!

enter image description here

\$\endgroup\$
  • \$\begingroup\$ Preserving the face shapes/sizes is priority! I don't want distortion at all. The texture will be generally just the same color with some darker/lighter spots so every edge could be used as a seam. \$\endgroup\$ – Alexander Ameye Apr 16 '17 at 13:05
  • \$\begingroup\$ Try looking into triplanar texturing \$\endgroup\$ – Bálint Apr 16 '17 at 13:46
  • \$\begingroup\$ Faces may overlap! (I edited main post) \$\endgroup\$ – Alexander Ameye Apr 16 '17 at 13:58
  • \$\begingroup\$ You're going to get awful seams / feature cutoffs if you do things like that. Refer to my answer for a start. Either keep features away from edges, or allow a certain number of joined triangles to be UV-mapped in a connected manner. \$\endgroup\$ – Engineer Apr 16 '17 at 14:09
1
\$\begingroup\$

The real problem here is seams. Since the question remains underspecified, here are some contrived solutions, leaving implementation specifics to you.

Don't let textural features cross triangle edges. Ever. Simple.

You want to find the "fattest" triangles, i.e. tris where there is a lot of room to work, and start generating textural features from (near) their centres. Ignore long/slim triangles that won't accommodate a texture well.

How to know whether a triangle is fat? Should be easy - for each triangle, determine:

  • the length L of its longest side.
  • its area A.
  • the ratio between these (A:L) - store this

If A:L (A divided by L) is low, it means it is a "fat" triangle.

Now order the triangles by this ratio, and choose say the best n triangles in terms of A:L, that you intend to generate on. Now generate texture features from their centres. On placement of every texel, you'll need to check whether you have crossed any of the edges - as you have a requirement for zero distortion, this is the best you can do.

Alternatively, Let textural features cross just one edge. This increases realism a bit, as you can have two nigh-coplanar faces sharing a feature. Then you can select 2 triangles at a time (based on average fatness of the 2 constituting triangles), which share a single edge, and overlay your tris onto your blank texture space in such a way as to minimise their distortion. Then knock yourself out generating a square texture for that, and just make sure your generated texels never get too near the tri-pair's outer edges; the easiest way to do this would be to simply fade into the regular colour (say mid-brown) as you approach the edges of the map being generated. Here you will, on every single texel draw, need to check you have not crossed one of the four tri-pair outer edges. Could be tedious and the tedium depends on your texel size relative to tri(s) area.

Or, you can avoid checking against edges of triangles on every single texel placement, if you simply morph your tri-pair to exactly fit the UV texture space (perfect square) - but this will then distort texels on application to the geometry.

Also note for this approach, that you need to be sure that any triangle that has already been chosen as part of a tri-pair / quad, must not become part of another tri-pair or you will have to use multi-texturing / multiple draw calls to achieve that.

N.B. Crucially, with either of these approaches you're going to need to make some attempt to keep texel scales similar between different faces / pairs. I would do that by ensuring that I do not scale the tris or quads too much to fit the UV quad. This is in line with your "no distortion" policy.

\$\endgroup\$
2
\$\begingroup\$

If we take as the requirements:

  • no distortion
  • no constraints on seam placement
  • OK to overlap faces

then we can use a pretty simplistic approach, independently rotating each face into a 2D plane and more or less leaving it wherever it ends up.

// Iterate over each face (here assuming triangles)
for(int index = 0; index < triangles.length; index += 3)
{
    // Get the three vertices bounding this triangle.
    Vector3 v1 = verts[triangles[index]];
    Vector3 v2 = verts[triangles[index + 1]];
    Vector3 v3 = verts[triangles[index + 2]];

    // Compute a vector perpendicular to the face.
    Vector normal = cross(v3 - v1, v2 - v1);

    // Form a rotation that points the z+ axis in this perpendicular direction.
    // Multiplying by the inverse will flatten the triangle into an xy plane.
    Quaternion rotation = Quaternion.Inverse(Quaternion.LookRotation(normal));

    // Assign the uvs, applying a scale factor to control the texture tiling.
    uvs[triangles[index]]     = (Vector2)(rotation * v1) * scaleFactor;
    uvs[triangles[index + 1]] = (Vector2)(rotation * v2) * scaleFactor;
    uvs[triangles[index + 2]] = (Vector2)(rotation * v3) * scaleFactor;
}

This meets the above criteria, placing faces in the texture space with no distortion at all.

You may get faces outside the 0-1 range, but for a wrapping texture this won't be an issue. If you need to use a clamped texture or an atlas, then you can apply an offset to the UVs to bring the triangle back into the 0-1 square as long as your scaleFactor is small enough. Just note that you'll need to pay special attention to coplanar polygonal faces made up of multiple triangles - the method above will map them onto adjacent parts of the texture space, but if you start applying offsets then you either need to consider the whole multi-triangle polygon as a whole and move it together, or live with creating seams across the middle of these polygons.

As Arcane Engineer notes, seams will be very visible if texture features like those green blobs fall across a triangle edge, so you'll need to carefully evaluate whether that's acceptable for your use, or whether you need a more sophisticated layout algorithm that's aware of the texture content.

\$\endgroup\$
  • \$\begingroup\$ I've implemented the code, but shapes still seem distorted (check main post for code) \$\endgroup\$ – Alexander Ameye Apr 16 '17 at 15:13
  • \$\begingroup\$ I added int[] tris = mesh.triangles; on the top but it's still distorted (check main post for new code + image of distortion) \$\endgroup\$ – Alexander Ameye Apr 16 '17 at 15:23
  • \$\begingroup\$ Post is edited with image \$\endgroup\$ – Alexander Ameye Apr 16 '17 at 15:27
  • \$\begingroup\$ Does your mesh have any shared vertices? By construction, the code above can only rotate faces and apply a uniform scale. But if you're re-using vertices with faces that have different normals then you can end up overwriting the correct UVs for one face with UVs for a different face, distorting it in arbitrary ways. \$\endgroup\$ – DMGregory Apr 16 '17 at 15:30
  • \$\begingroup\$ I edited main post with the code I have above the code you gave me. It is indeed possible that the mesh has shared vertices. I'll check. \$\endgroup\$ – Alexander Ameye Apr 16 '17 at 15:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.