0
\$\begingroup\$

I came across a way to create the vertices of a sphere, located here: https://gamedev.stackexchange.com/a/150293/62793

I converted this to the following C# example:

public static GraphicsMesh GenerateSphere(Device device, int numLatitudeLines, int numLongitudeLines, int radius)
        {
            // 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;

            Vector3[] positions = new Vector3[numVertices];
            Vector2[] texcoords = new Vector2[numVertices];

            // North pole.
            positions[0] = new Vector3(0, radius, 0);
            texcoords[0] = new Vector2(0, 1);

            // South pole.
            positions[numVertices - 1] = new Vector3(0, -radius, 0);
            texcoords[numVertices - 1] = new Vector2(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 (int latitude = 0; latitude < numLatitudeLines; latitude++)
            {
                for (int longitude = 0; longitude <= numLongitudeLines; longitude++)
                {

                    // Scale coordinates into the 0...1 texture coordinate range,
                    // with north at the top (y = 1).
                    texcoords[v] = new Vector2(
                                      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 = (float)(texcoords[v].X * 2.0f * Math.PI);
                    float phi = (float)((texcoords[v].Y - 0.5f) * Math.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 = (float)Math.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] = new Vector3(
                        (float)(c * Math.Cos(theta)),
                        (float)Math.Sin(phi),
                        (float)(c * Math.Sin(theta))
                      ) * radius;

                    // Proceed to the next vertex.
                    v++;
                }
            }
            //Converting it to a Vertex
           Vertex[] vertices = new Vertex[positions.Length];
            for (int i = 0; i < positions.Length; i++)
            {
                vertices[i] = new Vertex(positions[i]);
            }
            return new GraphicsMesh(device, vertices);
        }

And it does indeed create all the vertices of a sphere; here's a picture:enter image description here

My question now, is how do I take those vertices, and create triangles out of them? Or in other words, how do I link said vertices together to form triangle faces?

\$\endgroup\$
5
  • \$\begingroup\$ I notice your current code uses only a vertex buffer. Are you able to use a version that takes both a vertex buffer and an index buffer? That will be a little lighter weight than copying the repeated vertices each time they're visited by another triangle. \$\endgroup\$ – DMGregory Jan 26 at 15:55
  • \$\begingroup\$ @DMGregory The Vertex struct I use keeps Location, Texture Coordintes, and Normals in it. And the shader uses it as such. My renderer also accepts raw location data, without texture coordinates and normals, as well. \$\endgroup\$ – Krythic Jan 26 at 16:08
  • \$\begingroup\$ What I mean is that usually for models like this we'll have a collection of vertex structures like you describe, AND a list of indices like {0,1,2,0,2,3,0,3,4...} that says "Make a triangle from vertex structures 0, 1, and 2. Then make another triangle from vertex structures 0, 2, and 3...". Do you have access to something like a DrawIndexed method that uses these two buffers — a vertex buffer and an index buffer? If not, we can just copy the vertices in each slot, it just tends to get less efficient as we add more attributes to each vertex. \$\endgroup\$ – DMGregory Jan 26 at 16:18
  • \$\begingroup\$ @DMGregory I'm fully aware of how model loading works; I wrote my own model loader: pastebin.com/xZsuesht You can also find plenty of videos showing off my game engine here: youtube.com/channel/UC8jFi-NbUhiEjelmcAaV8mg/videos Lastly, I intentional unwrap the indices when loading a model. This is by design. \$\endgroup\$ – Krythic Jan 26 at 16:22
  • \$\begingroup\$ This isn't just for model loading. We use these indices to send to the GPU too, as it helps make use of the post-transform cache to speed up rendering. That way the GPU can say "Vertex 0? Oh, I already ran the vertex shader on that one, I don't need to re-run it again! Here's the results from last time." But if you use non-indexed rendering, and send copies of the vertex for each visit to it, then your memory use is higher and this optimization is unavailable. \$\endgroup\$ – DMGregory Jan 26 at 18:00
1
\$\begingroup\$

First up, our triangle count is...

 int numTriangles = numLatitudeLines * numLongitudeLines * 2;

(That's a middle section of (numLatitudeLines - 1) * numLongitudeLines quads, doubled to two triangles each, and a row of numLongitudeLines triangles around each pole)

Triple that to get our ultimate count of vertex copies:

Vertex[] vertices = new Vertex[numTriangles * 3];

Now we can fill this in row by row. The first row of triangles all start at position 0 (the north pole), and connect two consecutive vertex positions from the top latitude line:

int v = 0;

for (int i = 0; i < numLongitudeLines; i++) {
    vertices[v++] = new Vertex(positions[0]);
    vertices[v++] = new Vertex(positions[i + 2]);
    vertices[v++] = new Vertex(positions[i + 1]);
}

I'm not quite sure what coordinate system you're using, so you might need to exchange the order of two of those lines to flip the winding of the triangle to ensure it faces outward. The same trick works for the other triangle triplets below.

Next, we can connect up the large rectangular section in the middle, by iterating over each row and column.

// Each row has one more unique vertex than there are lines of longitude,
// since we double a vertex at the texture seam.
int rowLength = numLongitudeLines +1;

for (int latitude = 0; latitude < numLatitudeLines - 1; latitude++) {
    // Plus one for the pole.
    int rowStart = latitude * rowLength + 1;
    for (int longitude = 0; longitude < numLongitudeLines; longitude++) {        
        int firstCorner = rowStart + longitude;

        // First triangle of quad: Top-Left, Bottom-Left, Bottom-Right
        vertices[v++] = new Vertex(positions[firstCorner);
        vertices[v++] = new Vertex(positions[firstCorner + rowLength + 1]);
        vertices[v++] = new Vertex(positions[firstCorner + rowLength]);

        // Second triangle of quad: Top-Left, Bottom-Right, Top-Right
        vertices[v++] = new Vertex(positions[firstCorner);
        vertices[v++] = new Vertex(positions[firstCorner + 1]);
        vertices[v++] = new Vertex(positions[firstCorner + rowLength + 1]);
    }
}

And then we finish off with the same loop as we started, but this time for the south pole.

int pole = positions.Length - 1;
int bottomRow = (numLatitudeLines - 1) * rowLength + 1;

for (int i = 0; i < numLongitudeLines; i++) {
    vertices[v++] = new Vertex(positions[pole]);
    vertices[v++] = new Vertex(positions[bottomRow + i]);
    vertices[v++] = new Vertex(positions[bottomRow + i + 1]);
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Just a small off-by-one error. The limit in the latitude loop should be numLatitudeLines - 1 - I've fixed the code above, and also reversed the winding of the triangles to make them face outward in the coordinate system I used for my test. \$\endgroup\$ – DMGregory Jan 27 at 4:14
  • \$\begingroup\$ Here's a picture showing the result of this: imgur.com/a/jznClwg And for anyone who wants it, here's the full code written in C#: pastebin.com/276PwiYM \$\endgroup\$ – Krythic Jan 27 at 5:58

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.