20 faces -> 80 faces -> 320 faces -> 1280 faces -> 20 * 2^(2*N) faces
Instead I found a python Icosphere algorithm that allows a variable subdivision of each face, allowing a much finer resolution for the final number of faces:
20 faces -> 80 faces -> 180 faces -> 320 faces -> 500 faces -> 20 * N^2 faces
Below is version re-coded in C# for Unity, which calculates the Icosphere and generates a mesh. Just attach this script to an empty game object along with a mesh filter and mesh renderer. You can even add a texture to the mesh renderer, though there is a seam at the boundary and poles.
1: //
2: // Author: Kevin Tritz (tritz at yahoo *spamfilter* com)
3: // copyright (c) 2014
4: // license: BSD style
5: // derived from python version: Icosphere.py
6: //
7: // Author: William G.K. Martin (wgm2111@cu where cu=columbia.edu)
8: // copyright (c) 2010
9: // license: BSD style
10: // https://code.google.com/p/mesh2d-mpl/source/browse/icosphere.py
11: using UnityEngine;
12: using System.Collections;
13: using System.Collections.Generic;
14: public class IcoSphere : MonoBehaviour
15: {
16: public int num; // subdivision level of icosphere, max value of 79 to fit within 64k mesh index limit
17: // num = 1 is 1 level of subdivision, 80 faces, 42 verticies
18: public Vector3[] vertices; // Vector3[M] array of verticies, M = 10*(num+1)^2 + 2
19: public int[] triangleIndices; // int[3*N] flat triangle index list for mesh, N = 20*(num+1)^2
20: int[,] triangles; // int[N,3] triangle verticies index list, N = 20*(num+1)^2
21: void Start()
22: {
23: Icosahedron ico = new Icosahedron(); // initialize base Icosahedron, 20 faces, 12 vertices, radius = 1
24: get_triangulation(num, ico); // main function to subdivide and triangulate Icosahedron
25: Mesh mesh = new Mesh(); // mesh initialization and display
26: GetComponent<MeshFilter>().mesh = mesh; // add Mesh Filter component to game object with this script
27: mesh.vertices = vertices;
28: mesh.normals = vertices;
29: mesh.uv = getUV(vertices); // UV mapping is messed up near poles and longitude boundary
30: // cube-mapping might work better
31: mesh.triangles = triangleIndices;
32: }
33: void get_triangulation(int num, Icosahedron ico)
34: {
35: Dictionary<Vector3, int> vertDict = new Dictionary<Vector3, int>(); // dict lookup to speed up vertex indexing
36: float[,] subdivision = getSubMatrix(num+2); // vertex subdivision matrix calculation
37: Vector3 p1, p2, p3;
38: int index = 0;
39: int vertIndex;
40: int len = subdivision.GetLength(0);
41: int triNum = (num+1)*(num+1)*20; // number of triangle faces
42: vertices = new Vector3[triNum/2 + 2]; // allocate verticies, triangles, etc...
43: triangleIndices = new int[triNum*3];
44: triangles = new int[triNum,3];
45: Vector3[] tempVerts = new Vector3[len]; // temporary structures for subdividing each Icosahedron face
46: int[] tempIndices = new int[len];
47: int[,] triIndices = triangulate(num); // precalculate generic subdivided triangle indices
48: int triLength = triIndices.GetLength(0);
49: for(int i=0; i < 20; i++) // calculate subdivided vertices and triangles for each face
50: {
51: p1 = ico.vertices[ico.triangles[i*3]]; // get 3 original vertex locations for each face
52: p2 = ico.vertices[ico.triangles[i*3+1]];
53: p3 = ico.vertices[ico.triangles[i*3+2]];
54: for(int j=0; j < len; j++) // calculate new subdivided vertex locations
55: {
56: tempVerts[j].x = subdivision[j,0]*p1.x + subdivision[j,1]*p2.x + subdivision[j,2]*p3.x;
57: tempVerts[j].y = subdivision[j,0]*p1.y + subdivision[j,1]*p2.y + subdivision[j,2]*p3.y;
58: tempVerts[j].z = subdivision[j,0]*p1.z + subdivision[j,1]*p2.z + subdivision[j,2]*p3.z;
59: tempVerts[j].Normalize();
60: if(!vertDict.TryGetValue(tempVerts[j], out vertIndex)) // quick lookup to avoid vertex duplication
61: {
62: vertDict[tempVerts[j]] = index; // if vertex not in dict, add it to dictionary and final array
63: vertIndex = index;
64: vertices[index] = tempVerts[j];
65: index += 1;
66: }
67: tempIndices[j] = vertIndex; // assemble vertex indices for triangle assignment
68: }
69: for(int j=0; j < triLength; j++) // map precalculated subdivided triangle indices to vertex indices
70: {
71: triangles[triLength*i+j,0] = tempIndices[triIndices[j,0]];
72: triangles[triLength*i+j,1] = tempIndices[triIndices[j,1]];
73: triangles[triLength*i+j,2] = tempIndices[triIndices[j,2]];
74: triangleIndices[3*triLength*i + 3*j] = tempIndices[triIndices[j,0]];
75: triangleIndices[3*triLength*i + 3*j + 1] = tempIndices[triIndices[j,1]];
76: triangleIndices[3*triLength*i + 3*j + 2] = tempIndices[triIndices[j,2]];
77: }
78: }
79: }
80: int[,] triangulate(int num) // fuction to precalculate generic triangle indices for subdivided vertices
81: {
82: int n = num + 2;
83: int[,] triangles = new int[(n-1)*(n-1),3];
84: int shift = 0;
85: int ind = 0;
86: for(int row=0; row < n-1; row++)
87: {
88: triangles[ind,0] = shift + 1;
89: triangles[ind,1] = shift + n - row;
90: triangles[ind,2] = shift;
91: ind += 1;
92: for(int col = 1; col < n-1-row; col++)
93: {
94: triangles[ind,0] = shift + col;
95: triangles[ind,1] = shift + n - row + col;
96: triangles[ind,2] = shift + n - row + col - 1;
97: ind += 1;
98: triangles[ind,0] = shift + col + 1;
99: triangles[ind,1] = shift + n - row + col;
100: triangles[ind,2] = shift + col;
101: ind += 1;
102: }
103: shift += n - row;
104: }
105: return triangles;
106: }
107: Vector2[] getUV(Vector3[] vertices) // standard Longitude/Latitude mapping to (0,1)/(0,1)
108: {
109: int num = vertices.Length;
110: float pi = (float)System.Math.PI;
111: Vector2[] UV = new Vector2[num];
112: for(int i=0; i < num; i++)
113: {
114: UV[i] = cartToLL(vertices[i]);
115: UV[i].x = (UV[i].x + pi)/(2.0f*pi);
116: UV[i].y = (UV[i].y + pi/2.0f)/pi;
117: }
118: return UV;
119: }
120: Vector2 cartToLL(Vector3 point) // transform 3D cartesion coordinates to longitude, latitude
121: {
122: Vector2 coord = new Vector2();
123: float norm = point.magnitude;
124: if(point.x != 0.0f || point.y != 0.0f)
125: coord.x = -(float)System.Math.Atan2(point.y, point.x);
126: else
127: coord.x = 0.0f;
128: if(norm > 0.0f)
129: coord.y = (float)System.Math.Asin(point.z / norm);
130: else
131: coord.y = 0.0f;
132: return coord;
133: }
134: float[,] getSubMatrix(int num) // vertex subdivision matrix, num=3 subdivides 1 triangle into 4
135: {
136: int numrows = num * (num + 1) / 2;
137: float[,] subdivision = new float[numrows,3];
138: float[] values = new float[num];
139: int[] offsets = new int[num];
140: int[] starts = new int[num];
141: int[] stops = new int[num];
142: int index;
143: for(int i=0;i < num; i++)
144: {
145: values[i] = (float)i / (float)(num - 1);
146: offsets[i] = (num - i);
147: if(i > 0)
148: starts[i] = starts[i-1] + offsets[i-1];
149: else
150: starts[i] = 0;
151: stops[i] = starts[i] + offsets[i];
152: }
153: for(int i=0; i < num; i++)
154: {
155: for(int j=0; j < offsets[i]; j++)
156: {
157: index = starts[i] + j;
158: subdivision[index,0] = values[offsets[i]-1-j];
159: subdivision[index,1] = values[j];
160: subdivision[index,2] = values[i];
161: }
162: }
163: return subdivision;
164: }
165: }
166: public class Icosahedron
167: {
168: public Vector3[] vertices;
169: public int[] triangles;
170: public Icosahedron()
171: {
172: vertices = getPoints();
173: triangles = getTriangles();
174: }
175: Vector3[] getPoints()
176: {
177: Vector3[] vertices = new Vector3[12];
178: // Define the verticies with the golden ratio
179: float a = (1.0f + (float)System.Math.Sqrt(5))/2.0f;
180: vertices[0] = new Vector3(a, 0.0f, 1.0f);
181: vertices[9] = new Vector3(-a, 0.0f, 1.0f);
182: vertices[11] = new Vector3(-a, 0.0f, -1.0f);
183: vertices[1] = new Vector3(a, 0.0f, -1.0f);
184: vertices[2] = new Vector3(1.0f, a, 0.0f);
185: vertices[5] = new Vector3(1.0f, -a, 0.0f);
186: vertices[10] = new Vector3(-1.0f, -a, 0.0f);
187: vertices[8] = new Vector3(-1.0f, a, 0.0f);
188: vertices[3] = new Vector3(0.0f, 1.0f, a);
189: vertices[7] = new Vector3(0.0f, 1.0f, -a);
190: vertices[6] = new Vector3(0.0f, -1.0f, -a);
191: vertices[4] = new Vector3(0.0f, -1.0f, a);
192: for(int i = 0; i < 12; i++)
193: {
194: vertices[i].Normalize();
195: }
196: // rotate top point to the z-axis
197: float angle = (float)System.Math.Atan(vertices[0].x / vertices[0].z);
198: float ca = (float)System.Math.Cos (angle);
199: float sa = (float)System.Math.Sin (angle);
200: Matrix4x4 rotation = Matrix4x4.identity;
201: rotation.m00 = ca;
202: rotation.m02 = -sa;
203: rotation.m20 = sa;
204: rotation.m22 = ca;
205: for(int i = 0; i < 12; i++)
206: {
207: vertices[i] = rotation.MultiplyPoint3x4(vertices[i]);
208: }
209: return vertices;
210: }
211: int[] getTriangles()
212: {
213: int[] tris = {
214: 1,2,0,
215: 2,3,0,
216: 3,4,0,
217: 4,5,0,
218: 5,1,0,
219: 6,7,1,
220: 2,1,7,
221: 7,8,2,
222: 2,8,3,
223: 8,9,3,
224: 3,9,4,
225: 9,10,4,
226: 10,5,4,
227: 10,6,5,
228: 6,1,5,
229: 6,11,7,
230: 7,11,8,
231: 8,11,9,
232: 9,11,10,
233: 10,11,6,
234: };
235: return tris;
236: }
237: }