I have a script that generates a procedural cylinder. Each side is a quad that consists of two triangles. The two triangles share vertexes, so, a side with two triangles is defined by 4 points.
The mesh generates fine as you see here:
However, I am unable to UV map it correctly. My current progress has led me here:
With a different texture you can see how it stretches at certain points. If the first quad is the "front" of the cylinder, then these points occur on each side.
I thought it would work to use the vertex data directly as UV points, but I can't get it to work.
Here is the (old) script.
private void _CreateSegmentSides()
{
if(m_Sides > 2) {
float angleStep = 360.0f / (float)m_Sides;
BranchSegment seg = new BranchSegment(m_NextID++);
Quaternion rotation = Quaternion.Euler(0.0f, angleStep, 0.0f);
float height = m_SegmentStartLength;
int max = m_Sides - 1;
int index_tr = 0, index_tl = 3, index_br = 2, index_bl = 1;
float angle = 0f;
// Make first triangles.
seg.vertexes.Add(rotation * (new Vector3(m_Radius, height, 0f))); // top right
seg.vertexes.Add(rotation * (new Vector3(m_Radius, 0f, 0f))); // bottom left
seg.vertexes.Add(rotation * seg.vertexes[seg.vertexes.Count - 1]); // bottom right
seg.vertexes.Add(rotation * seg.vertexes[seg.vertexes.Count - 3]); // top left
// Add triangle indices.
seg.triangles.Add(index_tr); // 0
seg.triangles.Add(index_bl); // 1
seg.triangles.Add(index_br); // 2
seg.triangles.Add(index_tr); // 0
seg.triangles.Add(index_br); // 2
seg.triangles.Add(index_tl); // 3
// UV.
seg.uv.Add(rotation * seg.vertexes[seg.vertexes.Count - 4]);
seg.uv.Add(rotation * seg.vertexes[seg.vertexes.Count - 3]);
seg.uv.Add(rotation * seg.vertexes[seg.vertexes.Count - 2]);
seg.uv.Add(rotation * seg.vertexes[seg.vertexes.Count - 1]);
for (int i = 0; i < max; i++)
{
Debug.Log("add side ("+(i+1).ToString()+"/"+ max.ToString()+ ") angle: " + angle.ToString());
// First triangle.
seg.vertexes.Add(rotation * seg.vertexes[seg.vertexes.Count - 2]);
seg.triangles.Add(seg.vertexes.Count - 1); // new vertex
seg.triangles.Add(seg.vertexes.Count - 2); // shared
seg.triangles.Add(seg.vertexes.Count - 3); // shared
seg.uv.Add(rotation * seg.vertexes[seg.vertexes.Count - 1]);
// Second triangle.
seg.vertexes.Add(rotation * seg.vertexes[seg.vertexes.Count - 2]);
seg.triangles.Add(seg.vertexes.Count - 3); // shared
seg.triangles.Add(seg.vertexes.Count - 2); // shared
seg.triangles.Add(seg.vertexes.Count - 1); // new vertex
seg.uv.Add(rotation * seg.vertexes[seg.vertexes.Count - 1]);
angle += angleStep;
}
m_Segments.Add(seg);
}
}
I've been at this all day, what is going wrong here ??
Edit: Current progress, updated code
// Making the first two UV coords:
rad = angle * Mathf.Deg2Rad;
ux = rad * m_Radius;
uy = seg.vertexes[0].y;
seg.uv.Add(rotation * new Vector2(ux, uy));
uy = seg.vertexes[1].y;
seg.uv.Add(rotation * new Vector2(ux, uy));
// in the loop
for (int i = 0; i < max; i++)
{
angle += angleStep;
Debug.Log("add side (" + (i + 1).ToString() + "/" + max.ToString() + ") angle: " + angle.ToString());
rad = angle * Mathf.Deg2Rad;
ux = rad * m_Radius;
// First vertex.
seg.vertexes.Add(rotation * seg.vertexes[seg.vertexes.Count - 2]);
seg.triangles.Add(seg.vertexes.Count - 1); // new vertex
seg.triangles.Add(seg.vertexes.Count - 2); // shared
seg.triangles.Add(seg.vertexes.Count - 3); // shared
// UV
uy = seg.vertexes[seg.vertexes.Count - 1].y;
seg.uv.Add(rotation * new Vector2(ux, uy));
// Second vertex.
seg.vertexes.Add(rotation * seg.vertexes[seg.vertexes.Count - 2]);
seg.triangles.Add(seg.vertexes.Count - 3); // shared
seg.triangles.Add(seg.vertexes.Count - 2); // shared
seg.triangles.Add(seg.vertexes.Count - 1); // new vertex
// UV
uy = seg.vertexes[seg.vertexes.Count - 1].y;
seg.uv.Add(rotation * new Vector2(ux, uy));
}
And trying to fix the last to the first section
rad = 360f * Mathf.Deg2Rad; // (I've tried keeping the angle from the loop, but that didn't solve it either)
ux = rad * m_Radius;
uy = seg.vertexes[seg.vertexes.Count - 2].y;
seg.uv.Add(rotation * new Vector2(ux, uy));
uy = seg.vertexes[seg.vertexes.Count - 1].y;
seg.uv.Add(rotation * new Vector2(ux, uy));
Edit: Implemented the suggestion from the second comment.
Okay, so I'm going over the calculated UV values in a second loop after the part that I've posted, then I use the rounded value from UVx_max / UVx_max_rounded_to_int
to multiply each UV to adjust the UV tiling.
This works! (I wonder if it is possible to do it without a second loop?)
There is a visible line (normal related?) on the seam even if the texture is seamless. I am calling RecalculateNormals()
on the last part of the mesh creation.
Edit: Implementation of the corrected code:
private void _CreateSegmentSides2()
{
if(m_Sides > 2)
{
float angleStep = 360.0f / (float)m_Sides;
BranchSegment seg = new BranchSegment(m_NextID++);
Quaternion rotation = Quaternion.Euler(0.0f, angleStep, 0.0f);
float height = m_SegmentStartLength;
float angle = 0f;
int max = m_Sides - 1;
float ux, uy, rad;
// Make first side.
seg.vertexes.Add(rotation * (new Vector3(m_RadiusTop, height, 0f))); // tr
seg.vertexes.Add(rotation * (new Vector3(m_RadiusBottom, 0f, 0f))); // bl
seg.vertexes.Add(rotation * seg.vertexes[seg.vertexes.Count - 1]); // br
seg.vertexes.Add(rotation * seg.vertexes[seg.vertexes.Count - 3]); // tl
// Add triangle indices.
seg.triangles.Add(0);
seg.triangles.Add(1);
seg.triangles.Add(2);
seg.triangles.Add(0);
seg.triangles.Add(2);
seg.triangles.Add(3);
// Making the first two normals:
if (m_CustomNormals) {
seg.normals.Add(seg.vertexes[0].normalized); // Points "out" of the cylinder.
seg.normals.Add(seg.normals[0]);
}
// Making the first two UV coords:
float circumferenceTop = Mathf.PI * 2.0f * m_RadiusTop; // We know this in advance.
float circumferenceBottom = Mathf.PI * 2.0f * m_RadiusBottom;
float uvScaleTop = Mathf.Max(1, Mathf.Round(circumferenceTop)) / circumferenceTop;
float uvScaleBottom = Mathf.Max(1, Mathf.Round(circumferenceBottom)) / circumferenceBottom;
float uvScaleDiff = Mathf.Abs(uvScaleBottom - uvScaleTop);
// UV starts here.
rad = angle * Mathf.Deg2Rad;
ux = 0;
uy = seg.vertexes[0].y * uvScaleTop;
seg.uv.Add(new Vector2(ux, uy));
uy = seg.vertexes[1].y * uvScaleBottom;
seg.uv.Add(new Vector2(ux, uy));
// The remaining sides.
for (int i = 0; i < max; i++)
{
angle += angleStep;
rad = angle * Mathf.Deg2Rad;
// First vertex.
seg.vertexes.Add(rotation * seg.vertexes[seg.vertexes.Count - 2]);
seg.triangles.Add(seg.vertexes.Count - 1); // new vertex
seg.triangles.Add(seg.vertexes.Count - 2); // shared
seg.triangles.Add(seg.vertexes.Count - 3); // shared
// UV: x = arc length around the cylinder, y = height, scaled to tile uniformly.
ux = (rad * m_RadiusTop) * uvScaleTop;
uy = seg.vertexes[seg.vertexes.Count - 1].y * uvScaleTop;
seg.uv.Add(new Vector2(ux, uy));
// Normal: rotate normal from the previous column.
if(m_CustomNormals) { seg.normals.Add(rotation * seg.normals[seg.normals.Count - 1]); }
// Second vertex.
seg.vertexes.Add(rotation * seg.vertexes[seg.vertexes.Count - 2]);
seg.triangles.Add(seg.vertexes.Count - 3); // shared
seg.triangles.Add(seg.vertexes.Count - 2); // shared
seg.triangles.Add(seg.vertexes.Count - 1); // new vertex
// UV: x is the same, update y for new vertical position.
ux = (rad * m_RadiusBottom) * uvScaleBottom;
uy = seg.vertexes[seg.vertexes.Count - 1].y * uvScaleBottom;
seg.uv.Add(new Vector2(ux, uy));
// Normal: copy previous normal.
if(m_CustomNormals) { seg.normals.Add(seg.normals[seg.normals.Count - 1]); }
}
// Last UV to connect the seam.
rad = 360f * Mathf.Deg2Rad;
ux = (rad * m_RadiusTop) * uvScaleTop;
uy = seg.vertexes[1].y * uvScaleTop;
seg.uv.Add(new Vector2(ux, uy));
ux = (rad * m_RadiusBottom) * uvScaleBottom;
uy = seg.vertexes[0].y * uvScaleBottom;
seg.uv.Add(new Vector2(ux, uy));
m_Segments.Add(seg);
}
}
Custom normals:
Automatic normals by unity:
UV skew on different top and bottom radius:
(Seems like the UV is rotated ever so slightly sideways with each side, making it really messed up when it comes back around. height is also oddly scaled)
It has correct UV mapping, it is for WebGL in javascript, and it represents the mesh in form of simplicial complex (a point XYZ is an array of 3 numbers) and the code is quite easy to read/follow - also if you're not familiar with javascript I think.
Hope it helps!
– nkint Mar 17 '19 at 21:21