Algorithm for creating spheres?

Does anyone have an algorithm for creating a sphere proceduraly with la amount of latitude lines, loamount of longitude lines, and a radius of r? I need it to work with Unity, so the vertex positions need to be defined and then, the triangles defined via indexes (more info).


EDIT

enter image description here

I managed to get the code working in unity. But I think I might have done something wrong. When I turn up the detailLevel, All it does is add more vertices and polygons without moving them around. Did I forget something?


EDIT 2

enter image description here

I tried scaling the mesh along its normals. This is what I got. I think I'm missing something. Am I supposed to only scale certain normals?

share improve this question
 
Why don't you look at how existing open source implementations do it? have a look at how Three.js does it using meshes, for example. –  brice  Jun 28 '12 at 15:54
2  
As a small note: unless you have to do latitude/longitude you almost certainly don't want to, because the triangles you get will be much further from uniform than those you get with other methods. (Compare the triangles near the north pole with those near the equator: you're using the same number of triangles to get around one line of latitude in either case, but near the pole that line of latitude has very small circumference whereas at the equator it's the full circumference of your globe.) Techniques like the one in David Lively's answer are generally much better. –  Steven Stadnicki  Jun 28 '12 at 20:31
1  
You're not normalizing the vertex positions after subdividing. I didn't include that part in my example. Normalizing makes them all equidistant from the center, which creates the curve approximation you're looking for. –  David Lively  Jun 29 '12 at 3:37
 
Think inflating a balloon at the center of the icosahedron. As the balloon pushes the mesh our, it matches the shape of the balloon (sphere). –  David Lively  Jun 29 '12 at 3:45
3  
"Normalizing" means setting a vector's length to 1. You need to do something like vertices[i] = normalize(vertices[i]). Incidentally, this also gives you your new, correct normals, so you should donormals[i] = vertices[i] afterwards. –  Sam Hocevar  Jun 29 '12 at 14:54
show 6 more comments

6 Answers

up vote 15 down vote accepted

To get something like this:

enter image description here

Create an icosahedron (20-sided regular solid) and subdivide the faces to get a sphere (see code below).

The idea is basically:

  • Create a regular n-hedron (a solid where every face is the same size). I use an icosahedron because it's the solid with the greatest number of faces where every face is the same size. (There's a proof for that somewhere out there. Feel free to Google if you're really curious.) This will give you a sphere where nearly every face is the same size, making texturing a little easier.

enter image description here

  • Subdivide each face into four equally-sized faces. Each time you do this, it'll quadruple the number of faces in the model.

    ///      i0
    ///     /  \
    ///    m02-m01
    ///   /  \ /  \
    /// i2---m12---i1

i0i1, and i2 are the vertices of the original triangle. (Actually, indices into the vertex buffer, but that's another topic). m01 is the midpoint of the edge (i0,i1), m12 is the midpoint of the edge(i1,12), and m02 is, obviously, the midpoint of the edge (i0,i2).

Whenever you subdivide a face, make sure that you don't create duplicate vertices. Each midpoint will be shared by one other face (since the edges are shared between faces). The code below accounts for that by maintaining a dictionary of named midpoints that have been created, and returning the index of a previously created midpoint when it's available rather than creating a new one.

  • Repeat until you've reached the desired number of faces for your cube.

  • When you're done, normalize all of the vertices to smooth out the surface. If you don't do this, you'll just get a higher-res icosahedron instead of a sphere.

  • Voila! You're done. Convert the resulting vector and index buffers into a VertexBuffer andIndexBuffer, and draw with Device.DrawIndexedPrimitives().

Here's what you'd use in your "Sphere" class to create the model (XNA datatypes and C#, but it should be pretty clear):

        var vectors = new List<Vector3>();
        var indices = new List<int>();

        GeometryProvider.Icosahedron(vectors, indices);

        for (var i = 0; i < _detailLevel; i++)
            GeometryProvider.Subdivide(vectors, indices, true);

        /// normalize vectors to "inflate" the icosahedron into a sphere.
        for (var i = 0; i < vectors.Count; i++)
            vectors[i]=Vector3.Normalize(vectors[i]);

And the GeometryProvider class

public static class GeometryProvider
{

    private static int GetMidpointIndex(Dictionary<string, int> midpointIndices, List<Vector3> vertices, int i0, int i1)
    {

        var edgeKey = string.Format("{0}_{1}", Math.Min(i0, i1), Math.Max(i0, i1));

        var midpointIndex = -1;

        if (!midpointIndices.TryGetValue(edgeKey, out midpointIndex))
        {
            var v0 = vertices[i0];
            var v1 = vertices[i1];

            var midpoint = (v0 + v1) / 2f;

            if (vertices.Contains(midpoint))
                midpointIndex = vertices.IndexOf(midpoint);
            else
            {
                midpointIndex = vertices.Count;
                vertices.Add(midpoint);
            }
        }


        return midpointIndex;

    }

    /// <remarks>
    ///      i0
    ///     /  \
    ///    m02-m01
    ///   /  \ /  \
    /// i2---m12---i1
    /// </remarks>
    /// <param name="vectors"></param>
    /// <param name="indices"></param>
    public static void Subdivide(List<Vector3> vectors, List<int> indices, bool removeSourceTriangles)
    {
        var midpointIndices = new Dictionary<string, int>();

        var newIndices = new List<int>(indices.Count * 4);

        if (!removeSourceTriangles)
            newIndices.AddRange(indices);

        for (var i = 0; i < indices.Count - 2; i += 3)
        {
            var i0 = indices[i];
            var i1 = indices[i + 1];
            var i2 = indices[i + 2];

            var m01 = GetMidpointIndex(midpointIndices, vectors, i0, i1);
            var m12 = GetMidpointIndex(midpointIndices, vectors, i1, i2);
            var m02 = GetMidpointIndex(midpointIndices, vectors, i2, i0);

            newIndices.AddRange(
                new[] {
                    i0,m01,m02
                    ,
                    i1,m12,m01
                    ,
                    i2,m02,m12
                    ,
                    m02,m01,m12
                }
                );

        }

        indices.Clear();
        indices.AddRange(newIndices);
    }

    /// <summary>
    /// create a regular icosahedron (20-sided polyhedron)
    /// </summary>
    /// <param name="primitiveType"></param>
    /// <param name="size"></param>
    /// <param name="vertices"></param>
    /// <param name="indices"></param>
    /// <remarks>
    /// You can create this programmatically instead of using the given vertex 
    /// and index list, but it's kind of a pain and rather pointless beyond a 
    /// learning exercise.
    /// </remarks>

    public static void Icosahedron(List<Vector3> vertices, List<int> indices)
    {

        indices.AddRange(
            new int[]
            {
                0,4,1,
                0,9,4,
                9,5,4,
                4,5,8,
                4,8,1,
                8,10,1,
                8,3,10,
                5,3,8,
                5,2,3,
                2,7,3,
                7,10,3,
                7,6,10,
                7,11,6,
                11,0,6,
                0,1,6,
                6,1,10,
                9,0,11,
                9,11,2,
                9,2,5,
                7,2,11 
            }
            .Select(i => i + vertices.Count)
        );

        var X = 0.525731112119133606f;
        var Z = 0.850650808352039932f;

        vertices.AddRange(
            new[] 
            {
                new Vector3(-X, 0f, Z),
                new Vector3(X, 0f, Z),
                new Vector3(-X, 0f, -Z),
                new Vector3(X, 0f, -Z),
                new Vector3(0f, Z, X),
                new Vector3(0f, Z, -X),
                new Vector3(0f, -Z, X),
                new Vector3(0f, -Z, -X),
                new Vector3(Z, X, 0f),
                new Vector3(-Z, X, 0f),
                new Vector3(Z, -X, 0f),
                new Vector3(-Z, -X, 0f) 
            }
        );


    }



}

EDIT

I just noticed that you're trying to create a lat/long-type sphere rather than a geosphere. I'll see what I can dig up, but unless you have a particular reason for doing this - you're drawing this wireframe and want a particular aesthetic - than a geosphere is a lot more texture friendly.

share improve this answer
 
Great answer. Thanks. I can't tell but is this unity code? Oh, and the lat/long doesn't matter, as long as I can set the resolution. –  Dan the Man  Jun 28 '12 at 16:02
 
It's not Unity (XNA) but it'll give you the vertex coordinates and index list. Replace Vector3 with whatever the Unity equivalent is. You set the resolution by adjusting the number of Subdivide iterations. Each loop multiplies the number of faces by 4. 2 or 3 iterations will give a nice sphere. –  David Lively  Jun 28 '12 at 16:09
 
Ah I see. It's almost identical to Unity C#. Just a few questions... Why when the indices are defined, you put them inside of an int array? And what does the .Select(i => i + vertices.Count) do? –  Dan the Man  Jun 29 '12 at 0:23
 
The .Select(i => i + vertices.Count) doesn't work for me at all. Is it a XNA only feature? –  Dan the Man  Jun 29 '12 at 2:30
 
See my edit. That's what I've gotten to work. –  Dan the Man  Jun 29 '12 at 2:30
show 1 more comment

I created something like this a while back to make a sphere of cubes, for fun and science. It's not too hard. Basically, you take a function that creates a circle of vertices, then step through the height increments you want creating circles at each height at the radius required to make a sphere. Here I've modified the code to not be for cubes:

public static void makeSphere(float sphereRadius, Vector3f center, float heightStep, float degreeStep) {
    for (float y = center.y - sphereRadius; y <= center.y + sphereRadius; y+=heightStep) {
        double radius = SphereRadiusAtHeight(sphereRadius, y - center.y); //get the radius of the sphere at this height
        if (radius == 0) {//for the top and bottom points of the sphere add a single point
            addNewPoint((Math.sin(0) * radius) + center.x, y, (Math.cos(0) * radius) + center.z));
        } else { //otherwise step around the circle and add points at the specified degrees
            for (float d = 0; d <= 360; d += degreeStep) {
                addNewPoint((Math.sin(d) * radius) + center.x, y, (Math.cos(d) * radius) + center.z));
            }
        }
    }
}

public static double SphereRadiusAtHeight(double SphereRadius, double Height) {
    return Math.sqrt((SphereRadius * SphereRadius) - (Height * Height));
}

Now this code would just create points for the latitude. However, you can almost use the same code to make the longitude lines. Except you'll need to rotate between each iteration and make a full circle at each degreeStep.

Sorry this is not a complete answer or Unity specific, but hopefully it'll get you started.

share improve this answer
 
This is pretty good if you need a lat/long sphere, but you could simplify it a little by working in spherical coordinates until the last step. –  David Lively  Jun 29 '12 at 21:39
1  
Thanks @David. I agree, if I get around to writing up a version using spherical coords, I'll post it here. –  Byte56  Jun 29 '12 at 22:17
add comment (requires an account with 50 reputation)

Couldn't you just start with a simple shape, could be a box with r distance from center to corner. To make a more detailed sphere, subdivide all the polygons and then move the vertices out to r distance from the center, having the vector going through their current position.

Keep repeating until spherical enough for your tastes.

share improve this answer
 
This is essentially the same as the icosahedral approach, only with a different starting shape. One advantage of starting with a cube that I don't think has been mentioned: it's substantially easier to build decent UV maps for because you can use a cubemap and know that your texture seams will align perfectly with edges in your sphere mesh. –  Steven Stadnicki  Jun 29 '12 at 16:27
 
@StevenStadnicki the only issue I have with cubes is that the faces tend to wind up being very different sizes after a few subdivisons. –  David Lively  Jun 29 '12 at 19:47
 
@DavidLively That depends a lot on how you subdivide - if you chop the square faces of your cube into an even grid and then project outward/normalize then that's true, but if you grid up your faces non-uniformly then you can actually make the projection be evenly spaced along the arcs of the edges; that turns out to work pretty well. –  Steven Stadnicki  Jun 29 '12 at 20:50
 
@StevenStadnicki nifty! –  David Lively  Jun 29 '12 at 21:31
 
@EricJohansson btw, as a teacher I feel compelled to mention that this is a pretty significant insight for someone that apparently hasn't seen the subdivision method before. You've renewed my faith in humanity for the next 12 hours. –  David Lively  Jun 29 '12 at 23:46
show 1 more comment

Do you actually need the 3D geometry or just the shape?

You can make a 'fake' sphere using a single quad. Just put a circle on it and shade it correctly. This has the advantage that it will have exactly the resolution required regardless of the distance to the camera or resolution.

There's a tutorial here.

share improve this answer
 
1  
Nice hack, but fails if you need to texture it. –  David Lively  Jun 29 '12 at 21:40
 
@DavidLively It should be possible to calculate the texture coordinates per-pixel based on it's rotation unless you need to texture polygons individually. –  David C. Bishop  Jul 1 '12 at 2:02
 
@DavidCBishop You'd have to account for the "lensing" of the surface - texel coords are squeezed close to the circle border due to perspective - at which point you're faking rotation. Also, that involves moving a lot more work into the pixel shader that could be performed in the vertex shader (and we all know that VS's are a lot cheaper!). –  David Lively  Aug 6 '12 at 21:53
add comment (requires an account with 50 reputation)

here is some code for any number of equally spaced vertices of a sphere, its like an orange peel it winds a line of dots around a sphere in a spiral. afterwards, how you join the vertices is up to you. you can use neighbour dots in the loop as 2 of each triangle and then find the third would be a proportional one twist around the sphere higher up or lower down... you can also do triangles by loop and nearest neighbour on it, does someone know a better way?

var spherevertices = vector3 generic list...

public var numvertices= 1234;
var size = .03;  

function sphere ( N:float){//<--- N is the number of vertices i.e 123

var inc =  Mathf.PI  * (3 - Mathf.Sqrt(5));
var off = 2 / N;
for (var k = 0; k < (N); k++)
{
    var y = k * off - 1 + (off / 2);
    var r = Mathf.Sqrt(1 - y*y);
    var phi = k * inc;
    var pos = Vector3((Mathf.Cos(phi)*r*size), y*size, Mathf.Sin(phi)*r*size); 

    spherevertices   add pos...

}

};

share improve this answer
 add comment (requires an account with 50 reputation)

Although David is absolutely correct in his answer, I want to offer a different perspective.

For my assignment for procedural content generation, I looked at (among other things) icosahedron versus more traditional subdivided spheres. Look at these procedurally generated spheres:

Awesome spheres

Both look like perfectly valid spheres, right? Well, let's look at their wireframes:

Wow that's dense

Wow, what happened there? The wireframe version of the second sphere is so dense that it looks textured! I'll let you in on a secret: the second version is an icosahedron. It's an almost perfect sphere, but it comes at a high price.

Sphere 1 uses 31 subdivisions on the x-axis and 31 subdivisions on the z-axis, for a total of 3,844 faces.

Sphere 2 uses 5 recursive subdivisions, for a total of 109,220 faces.

But okay, that's not really fair. Let's scale down the quality considerably:

Lumpy

Sphere 1 uses 5 subdivisions on the x-axis and 5 subdivisions on the z-axis, for a total of 100 faces.

Sphere 2 uses 0 recursive subdivisions, for a total of 100 faces.

They use the same amount of faces, but in my opinion, the sphere on the left looks better. It looks less lumpy and a lot more round. Let's take a look at how many faces we generate with both methods.

Icosahedron:

  • Level 0 - 100 faces
  • Level 1 - 420 faces
  • Level 2 - 1,700 faces
  • Level 3 - 6,820 faces
  • Level 4 - 27,300 faces
  • Level 5 - 109,220 faces

Subdivided sphere:

  • YZ: 5 - 100 faces
  • YZ: 10 - 400 faces
  • YZ: 15 - 900 faces
  • YZ: 20 - 1,600 faces
  • YZ: 25 - 2,500 faces
  • YZ: 30 - 3,600 faces

As you can see, the icosahedron increases in faces at an exponential rate, to a third power! That is because for every triangle, we must subdivide them into three new triangles.

The truth is: you don't need the precision an icosahedron will give you. Because they both hide a much harder problem: texturing a 2D plane on a 3D sphere. Here's what the top looks like:

Top sucks

On the top-left, you can see the texture being used. Coincidentally, it's also being generated procedurally. (Hey, it was a course on procedural generation, right?)

It looks terrible, right? Well, this is as good as it's going to get. I got top marks for my texture mapping, because most people don't even get it this right.

So please, consider using cosine and sine to generate a sphere. It generates a lot less faces for the same amount of detail.

share improve this answer
 
4  
I'm afraid I can only downvote this. The icosphere scales exponentially? That’s only because you decided yours should scale exponentially. An UV sphere generates fewer faces than an icosphere for the same amount of detail? That is wrong, absolutely wrong, totally backwards. –  Sam Hocevar  Jun 29 '12 at 7:20
 
@SamHocevar Then how else do you generate a sphere from an icosahedron? You take the faces and subdivide them to get more precision. This is an exponential process and I don't see how else you would do it. I should note that I've made a mistake when calculating the amount of faces for the uv-sphere and I've since corrected it. –  knight666  Jun 29 '12 at 7:26
4  
Subdivision does not need to be recursive. You can divide a triangle's edge into as many equal parts as you wish. Using N parts will give you N*N new triangles, which is quadratic, exactly like what you do with the UV-sphere. –  Sam Hocevar  Jun 29 '12 at 10:04
4  
I must also add that the sphere you say looks "less lumpy and a lot more round" is viewed from the best angle, making that claim dishonest too. Just do the same screenshot with the spheres viewed from above to see what I mean. –  Sam Hocevar  Jun 29 '12 at 10:08
2  
Also, your icosahedron numbers don't look correct. Level 0 is 20 faces (by definition), then 80, 320, 1280, etc. You can subdivide in any number and any pattern that you want. The smoothness of the model is ultimately going to be determined by the number and distribution of faces in the final result (regardless of the method used to generate them), and we want to keep the size of each face as uniform as possible (no polar squeezing) to maintain a consistent profile regardless of view angle. Add to that the fact that the subdivision code is a lot simpler (imho)... –  David Lively  Jun 29 '12 at 19:55
add comment (requires an account with 50 reputation)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值