Let's start by generating all unique vertices. You can then decide whether to index those vertices, or copy them into strips/fans, depending on your needs. Here's some example code showing how to organize these vertices into triangles for rendering.
// One vertex at every latitude-longitude intersection,
// plus one for the north pole and one for the south.
// One meridian serves as a UV seam, so we double the vertices there.
int numVertices = numLatitudeLines * (numLongitudeLines + 1) + 2
vec3[] positions = vec3[numVertices]
vec2[] texcoords = vec2[numVertices]
// North pole.
positions[0] = vec3(0, radius, 0)
texcoords[0] = vec2(0, 1)
// South pole.
positions[numVertices - 1] = vec3(0, -radius, 0)
texcoords[numVertices - 1] = vec2(0, 0)
// +1.0f because there's a gap between the poles and the first parallel.
float latitudeSpacing = 1.0f / (numLatitudeLines + 1.0f)
float longitudeSpacing = 1.0f / (numLongitudeLines)
// start writing new vertices at position 1
int v = 1
for(latitude = 0; latitude < numLatitudeLines; latitude++) {
for(longitude = 0; longitude <= numLongitudeLines; longitude++) {
// Scale coordinates into the 0...1 texture coordinate range,
// with north at the top (y = 1).
texcoords[v] = vec2(
longitude * longitudeSpacing,
1.0f - (latitude + 1) * latitudeSpacing
)
// Convert to spherical coordinates:
// theta is a longitude angle (around the equator) in radians.
// phi is a latitude angle (north or south of the equator).
float theta = texcoords[v].x * 2.0f * PI
float phi = (texcoords[v].y - 0.5f) * PI
// This determines the radius of the ring of this line of latitude.
// It's widest at the equator, and narrows as phi increases/decreases.
float c = cos(phi)
// Usual formula for a vector in spherical coordinates.
// You can exchange x & z to wind the opposite way around the sphere.
positions[v] = vec3(
c * cos(theta),
sin(phi),
c * sin(theta)
) * radius
// Proceed to the next vertex.
v++
}
}
This puts the north Pole at position 0 in the array, then the vertices of each line of latitude in a row (with the first and last vertices in the row being coincident along the texturing seam), one line after another until we reach the south pole at the end of the array.
We've arbitrarily made just one vertex for each pole, all the way on the left side of the texture. You could also choose to make multiple pole vertices to make the texture distortion less extreme, but you'll always have some seams or stretching here due to the nonlinear nature of equirectangular mapping.