How to generate normals and UVs for a procedurally generated mesh

I have a mesh generation script that produces meshes that look like this:

The problem is when I render them they turn out like this:

I know I need to generative normals and UV coords for it, but I have no idea how to do that. All I want is for the object to have a solid color and for shadows/lighting to work correctly.

This is what my mesh generation code looks like:

    public void RenderMesh(List<Vector3> positions, int maxIterations, int layerResolution)
    {
        var triangles = new List<int>();
        var meshFilter = gameObject.GetComponent<MeshFilter>();
        var meshCollider = gameObject.GetComponent<MeshCollider>();
        Mesh mesh = new Mesh();
        meshFilter.mesh = mesh;


        for (int level = 1; level <= maxIterations; level++)
        {
            if (level < maxIterations)
            {
                for (int circlePoint = 0; circlePoint < layerResolution; circlePoint++)
                {
                    var index = level * layerResolution + circlePoint;
                    if (circlePoint < layerResolution - 1)
                    {
                        triangles.Add(index);
                        triangles.Add(index - layerResolution + 1);
                        triangles.Add(index - layerResolution);

                        triangles.Add(index);
                        triangles.Add(index + 1);
                        triangles.Add(index - layerResolution + 1);

                        triangles.Add(index - layerResolution);
                        triangles.Add(index - layerResolution + 1);
                        triangles.Add(index);

                        triangles.Add(index - layerResolution + 1);
                        triangles.Add(index + 1);
                        triangles.Add(index);
                    }
                    else
                    {
                        triangles.Add(index);
                        triangles.Add(index - 2 * layerResolution + 1);
                        triangles.Add(index - layerResolution);

                        triangles.Add(index - layerResolution + 1);
                        triangles.Add(index - 2 * layerResolution + 1);
                        triangles.Add(index);

                        triangles.Add(index - layerResolution);
                        triangles.Add(index - 2 * layerResolution + 1);
                        triangles.Add(index);

                        triangles.Add(index);
                        triangles.Add(index - 2 * layerResolution + 1);
                        triangles.Add(index - layerResolution + 1);
                    }
                }
            }
            else
            {
                var lastPointIndex = positions.Count - 1;
                for (int circlePoint = 0; circlePoint < layerResolution; circlePoint++)
                {
                    var index = (maxIterations - 1) * layerResolution + circlePoint;
                    if (circlePoint < layerResolution - 1)
                    {
                        triangles.Add(index);
                        triangles.Add(index + 1);
                        triangles.Add(lastPointIndex);

                        triangles.Add(index);
                        triangles.Add(lastPointIndex);
                        triangles.Add(index + 1);
                    }
                    else
                    {
                        triangles.Add(index);
                        triangles.Add(index - layerResolution + 1);
                        triangles.Add(lastPointIndex);

                        triangles.Add(index);
                        triangles.Add(lastPointIndex);
                        triangles.Add(index - layerResolution + 1);
                    }
                }
            }
        }


        mesh.vertices = positions.ToArray();
        mesh.triangles = triangles.ToArray();

        mesh.RecalculateTangents();

        mesh.RecalculateNormals();

        Vector2[] uv = new Vector2[positions.Count];
        for (int i = 0; i < uv.Length; i++)
        {
            uv[i] = new Vector2(positions[i].x, positions[i].y);
        }
        mesh.uv = uv;

        meshCollider.sharedMesh = mesh;
    }

When rendering with a texture, it seems like it’s not the UVs that are the problem (not perfect, but do the job for a solid color it seems), but the normals.

Well I’d look at mapping it before you do the procedural distortion if possible. So if the math is twisting this thing, apply uvs before the twist. Then it’s just a planar 0…1 thing if that’s possible?

It’s not quite a twist - each vertex is using a flocking algorithm to decide it’s next position as it goes up layer by layer. That might work though if I assume it’s a cylinder.

Although actually the problem seemed to be having a double sided mesh - once I created triangles only for the outside layer of the mesh this problem stopped.