27

I am not able to create a simple 3D sphere using the OpenGL library function glutSolidSphere() in C++.

Here's what I tried:

#include<GL/glu.h> 
void display() 
{ 
    glClear(GL_COLOR_BUFFER_BIT); 
    glColor3f(1.0,0.0,0.0); 
    glLoadIdentity(); 
    glutSolidSphere( 5.0, 20.0, 20.0); 
    glFlush(); 
} 

void myInit() 
{
    glClearColor(1.0,1.0,1.0,1.0); 
    glColor3f(1.0,0.0,0.0); 
    glMatrixMode(GL_PROJECTION); 
    glLoadIdentity(); 
    gluOrtho2D(0.0,499.0,0.0,499.0); 
    glMatrixMode(GL_MODELVIEW); 
} 

void main(int argc,char **argv) 
{ 
    qobj = gluNewQuadric(); 
    glutInit(&argc,argv); 
    glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB); 
    glutInitWindowSize(500,500); 
    glutCreateWindow("pendulum");         
    glutDisplayFunc(display); 
    myInit(); 
    glutMainLoop(); 
}
Mat
  • 202,337
  • 40
  • 393
  • 406
Lloyd
  • 271
  • 1
  • 3
  • 4

6 Answers6

77

In OpenGL you don't create objects, you just draw them. Once they are drawn, OpenGL no longer cares about what geometry you sent it.

glutSolidSphere is just sending drawing commands to OpenGL. However there's nothing special in and about it. And since it's tied to GLUT I'd not use it. Instead, if you really need some sphere in your code, how about create if for yourself?

#define _USE_MATH_DEFINES
#include <GL/gl.h>
#include <GL/glu.h>
#include <vector>
#include <cmath>

// your framework of choice here

class SolidSphere
{
protected:
    std::vector<GLfloat> vertices;
    std::vector<GLfloat> normals;
    std::vector<GLfloat> texcoords;
    std::vector<GLushort> indices;

public:
    SolidSphere(float radius, unsigned int rings, unsigned int sectors)
    {
        float const R = 1./(float)(rings-1);
        float const S = 1./(float)(sectors-1);
        int r, s;

        vertices.resize(rings * sectors * 3);
        normals.resize(rings * sectors * 3);
        texcoords.resize(rings * sectors * 2);
        std::vector<GLfloat>::iterator v = vertices.begin();
        std::vector<GLfloat>::iterator n = normals.begin();
        std::vector<GLfloat>::iterator t = texcoords.begin();
        for(r = 0; r < rings; r++) for(s = 0; s < sectors; s++) {
                float const y = sin( -M_PI_2 + M_PI * r * R );
                float const x = cos(2*M_PI * s * S) * sin( M_PI * r * R );
                float const z = sin(2*M_PI * s * S) * sin( M_PI * r * R );

                *t++ = s*S;
                *t++ = r*R;

                *v++ = x * radius;
                *v++ = y * radius;
                *v++ = z * radius;

                *n++ = x;
                *n++ = y;
                *n++ = z;
        }

        indices.resize(rings * sectors * 4);
        std::vector<GLushort>::iterator i = indices.begin();
        for(r = 0; r < rings; r++) for(s = 0; s < sectors; s++) {
                *i++ = r * sectors + s;
                *i++ = r * sectors + (s+1);
                *i++ = (r+1) * sectors + (s+1);
                *i++ = (r+1) * sectors + s;
        }
    }

    void draw(GLfloat x, GLfloat y, GLfloat z)
    {
        glMatrixMode(GL_MODELVIEW);
        glPushMatrix();
        glTranslatef(x,y,z);

        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_NORMAL_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        glVertexPointer(3, GL_FLOAT, 0, &vertices[0]);
        glNormalPointer(GL_FLOAT, 0, &normals[0]);
        glTexCoordPointer(2, GL_FLOAT, 0, &texcoords[0]);
        glDrawElements(GL_QUADS, indices.size(), GL_UNSIGNED_SHORT, &indices[0]);
        glPopMatrix();
    }
};

SolidSphere sphere(1, 12, 24);

void display()
{
    int const win_width  = …; // retrieve window dimensions from
    int const win_height = …; // framework of choice here
    float const win_aspect = (float)win_width / (float)win_height;

    glViewport(0, 0, win_width, win_height);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45, win_aspect, 1, 10);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

#ifdef DRAW_WIREFRAME
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#endif
    sphere.draw(0, 0, -5);

    swapBuffers();
}

int main(int argc, char *argv[])
{
    // initialize and register your framework of choice here
    return 0;
}
datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • Excellent stuff - do you have code to generate the texture co-ords as well? – trojanfoe May 13 '11 at 09:20
  • @trojanfoe: Depends on what kind of mapping you're interested. Spheres have two poles, at which it's difficult to supply proper texture coordinates. But a good choice is simply using the spherical coordinates, mapped to ST. See my edit. – datenwolf May 13 '11 at 09:32
  • @datenwolf: Many thanks. I like your approach; for me this would form part of a `Model` class where vertices, normals, etc are attributes and `solidSphere()` would be one of many methods used to generate the geometry which is later drawn using `draw()`. – trojanfoe May 13 '11 at 09:48
  • @trojanfoe: I'd make this two classes: Model and Mesh. And instead of having a Sphere class implement generator functions, that create the desired mesh. Because after the mesh is created it's just a mesh. Model could consists of a number of meshes. – datenwolf May 13 '11 at 10:04
  • 1
    @datenwolf: Ok, I'll look into that. I have implemented the code above (I assume the `glDrawElement()` call should be using `GL_UNSIGNED_INT` given the vector stores `GLuint`s) but it's just generating the lower quarter of the sphere and also backface culling is culling the outside of the sphere, unless I use `glFrontFace(GL_CW)` to change the winding. Can you help with these problems? Sorry to be a pain - I know what it's like to try and help and then be burdened with support requests :-/ – trojanfoe May 13 '11 at 10:18
  • @trojanfoe: Yes, I copied that code from another example, and missed that. However I suggest you change the datatype from GLuint to GLushort, as current GPUs perform best with unsigned short indices. – datenwolf May 13 '11 at 11:26
  • 1
    @datenwolf: OK, will do - and the issue with the rendering? Are you sure the indices are being populated correctly? – trojanfoe May 13 '11 at 11:32
  • @datenwolf: For example the index population code uses the same r/s loop values as the vertices, normals, tex-coord, so couldn't it be moved into that loop? But each index is supposed to represent a single 3-float vertex, so shouldn't there be something like 1/4 or 1/3 the number of indices as there are vertices? – trojanfoe May 13 '11 at 11:41
  • @trojanfoe: Yes, the looping variables are the same, so in theory they could be merged. However I didn't to be more cache friendly. The index array generation utilizes different registers and has a different access mode; the loop itself comes almost for free, cache locality is much more important. Also take a look on the sizing of vertex arrays and index array: Each ring/sector segment has 4 vertices, so the index array size is `rings * sectors * 4`, whereas the vertex array is created between the faces; only because a sphere has cyclic coordinates the loops look identical. – datenwolf May 13 '11 at 12:38
  • Drawing this code with GL_TRIANGLE_STRIP brings ugly results.Seems like the indices gen is wrong. – Michael IV Nov 28 '12 at 13:05
  • @MichaelIV: Triangle Strips require an entirely different index order than quads. No wonder the sphere looks odd if used with something different than GL_QUADS – datenwolf Nov 28 '12 at 14:43
  • Thanks for the headups. Yes I changed to TRIANGLES and it worked with some minor modifications :) – Michael IV Nov 28 '12 at 15:50
  • Tried to implement this and ran into problems. See the following [question](http://stackoverflow.com/questions/14080932/implementing-opengl-sphere-example-code) for details. An edit may be in place for this question. – toeplitz Dec 29 '12 at 13:52
  • @toeplitz: Thanks for the heads up. I'll see into it. – datenwolf Dec 29 '12 at 15:09
  • 2
    @toeplitz: Okay, I fixed all the issues. – datenwolf Dec 29 '12 at 15:32
  • 1
    I like your use of the `const` modifier *after* the type. You don't see that often enough. :-) – Victor Zamanian Jan 02 '13 at 00:12
  • @datenwolf I tried to check your page for a contact info, but I couldn't find anything. Maybe this will work. I have a Q (see my prof). Tried to implement your A in OSG, but I failed at it (quite hard). Do you have any experience with OSG? Ps.: Your domain will expire in 04 January. – Apache Dec 11 '13 at 00:01
  • @Shiki: I've marked your question and await your posting of a code snippet to work with. – datenwolf Dec 11 '13 at 01:58
  • It would be good to have the fixed code posted, please. As posted when rotated around the Y axis, the tetxure appears to revolve the opposite direction... which suggests Im seeing back faces. Ive been playing with this and found it works as expected IF I reverse the normals AND set the cull to CCW. Feel like a winding problem but Im unsure how to fix that in your code... – user430788 Nov 10 '14 at 17:01
  • Okay, if i reverse the normals and the winding it works correctly culled and unculled. Fixed code below: – user430788 Nov 10 '14 at 17:09
  • hey, what is M_PI ? =) – Volodymyr Balytskyy Jun 27 '15 at 11:39
  • @RareFever: M_PI is a pi number ~ 3.14159, M_PI_2 is a half of it. – tepl Aug 26 '15 at 01:45
  • excuse me for a noobish question, but how do I render the display function on the screen? So far I have this: http://pastebin.com/cXugwcYg – Mark Jun 09 '16 at 21:06
  • @Mark: The code I gave in the StackOverflow answer uses old style OpenGL-1.1 client side vertex arrays. Your program stub uses shaders and server side vertex attribute arrays. These are very similar, but you'll have to port my sphere generation code for use with shaders first. Otherwise you'll not see anything usefull. – datenwolf Jun 10 '16 at 08:58
  • @datenwolf could you please point me in the right direction, so I don't get even more lost? Thank you! – Mark Jun 10 '16 at 09:40
  • The size of the indices vector should be `(rings-1) * (sectors-1) * 4`. You might want to fix that. – N0vember May 13 '18 at 00:07
  • @datenwolf I'm not sure what you mean, but creating a vector of `rings * sectors * 4` indices but only writing `(rings - 1) * (sectors - 1) * 4` indices in the vector seems wrong anyway ? You have unset indices at the end. Or you mean these last indices *should* be 0 ? – N0vember May 14 '18 at 11:15
  • Ohh, I now see what you mean. Loop termination conditions. Good catch. – datenwolf May 14 '18 at 22:35
23

It doesn't seem like anyone so far has addressed the actual problem with your original code, so I thought I would do that even though the question is quite old at this point.

The problem originally had to do with the projection in relation to the radius and position of the sphere. I think you'll find that the problem isn't too complicated. The program actually works correctly, it's just that what is being drawn is very hard to see.

First, an orthogonal projection was created using the call

gluOrtho2D(0.0, 499.0, 0.0, 499.0);

which "is equivalent to calling glOrtho with near = -1 and far = 1." This means that the viewing frustum has a depth of 2. So a sphere with a radius of anything greater than 1 (diameter = 2) will not fit entirely within the viewing frustum.

Then the calls

glLoadIdentity();
glutSolidSphere(5.0, 20.0, 20.0);

are used, which loads the identity matrix of the model-view matrix and then "[r]enders a sphere centered at the modeling coordinates origin of the specified radius." Meaning, the sphere is rendered at the origin, (x, y, z) = (0, 0, 0), and with a radius of 5.

Now, the issue is three-fold:

  1. Since the window is 500x500 pixels and the width and height of the viewing frustum is almost 500 (499.0), the small radius of the sphere (5.0) makes its projected area only slightly over one fiftieth (2*5/499) of the size of the window in each dimension. This means that the apparent size of the sphere would be roughly 1/2,500th (actually pi*5^2/499^2, which is closer to about 1/3170th) of the entire window, so it might be difficult to see. This is assuming the entire circle is drawn within the area of the window. It is not, however, as we will see in point 2.
  2. Since the viewing frustum has it's left plane at x = 0 and bottom plane at y = 0, the sphere will be rendered with its geometric center in the very bottom left corner of the window so that only one quadrant of the projected sphere will be visible! This means that what would be seen is even smaller, about 1/10,000th (actually pi*5^2/(4*499^2), which is closer to 1/12,682nd) of the window size. This would make it even more difficult to see. Especially since the sphere is rendered so close to the edges/corner of the screen where you might not think to look.
  3. Since the depth of the viewing frustum is significantly smaller than the diameter of the sphere (less than half), only a sliver of the sphere will be within the viewing frustum, rendering only that part. So you will get more like a hollow circle on the screen than a solid sphere/circle. As it happens, the thickness of that sliver might represent less than 1 pixel on the screen which means we might even see nothing on the screen, even if part of the sphere is indeed within the viewing frustum.

The solution is simply to change the viewing frustum and radius of the sphere. For instance,

gluOrtho2D(-5.0, 5.0, -5.0, 5.0);
glutSolidSphere(5.0, 20, 20);

renders the following image.

r = 5.0

As you can see, only a small part is visible around the "equator", of the sphere with a radius of 5. (I changed the projection to fill the window with the sphere.) Another example,

gluOrtho2D(-1.1, 1.1, -1.1, 1.1);
glutSolidSphere(1.1, 20, 20);

renders the following image.

r = 1.1

The image above shows more of the sphere inside of the viewing frustum, but still the sphere is 0.2 depth units larger than the viewing frustum. As you can see, the "ice caps" of the sphere are missing, both the north and the south. So, if we want the entire sphere to fit within the viewing frustum which has depth 2, we must make the radius less than or equal to 1.

gluOrtho2D(-1.0, 1.0, -1.0, 1.0);
glutSolidSphere(1.0, 20, 20);

renders the following image.

r = 1.0

I hope this has helped someone. Take care!

Victor Zamanian
  • 3,100
  • 24
  • 31
  • 3
    This should be the accepted answer for the actual question by the OP. – legends2k Mar 19 '14 at 03:18
  • Thank you for that feedback, @legends2k. I added some more detail regarding the exact proportions of projected area that makes the circle very difficult to see when it is being drawn in the corner of the window. This in order to more clearly explain where I am getting my numbers and why the circle isn't being shown even though it is being drawn. – Victor Zamanian Mar 26 '14 at 11:42
10

I don't understand how can datenwolf`s index generation can be correct. But still I find his solution rather clear. This is what I get after some thinking:

inline void push_indices(vector<GLushort>& indices, int sectors, int r, int s) {
    int curRow = r * sectors;
    int nextRow = (r+1) * sectors;

    indices.push_back(curRow + s);
    indices.push_back(nextRow + s);
    indices.push_back(nextRow + (s+1));

    indices.push_back(curRow + s);
    indices.push_back(nextRow + (s+1));
    indices.push_back(curRow + (s+1));
}

void createSphere(vector<vec3>& vertices, vector<GLushort>& indices, vector<vec2>& texcoords,
             float radius, unsigned int rings, unsigned int sectors)
{
    float const R = 1./(float)(rings-1);
    float const S = 1./(float)(sectors-1);

    for(int r = 0; r < rings; ++r) {
        for(int s = 0; s < sectors; ++s) {
            float const y = sin( -M_PI_2 + M_PI * r * R );
            float const x = cos(2*M_PI * s * S) * sin( M_PI * r * R );
            float const z = sin(2*M_PI * s * S) * sin( M_PI * r * R );

            texcoords.push_back(vec2(s*S, r*R));
            vertices.push_back(vec3(x,y,z) * radius);
            push_indices(indices, sectors, r, s);
        }
    }
}
coin
  • 129
  • 1
  • 4
  • You should indicate that this answer does not uses `glutSolidSphere` as the question called for it. –  Dec 12 '12 at 19:15
  • :) you a right. I just found this question seeking for a manual generation method WITHOUT glutSolidSphere... – coin Dec 20 '12 at 08:24
  • btw datenwolf`s index generation appears to be correct. But I did't know about ogl capability to draw a square instead of triangle. I'm noob in this. – coin Dec 20 '12 at 08:27
  • Is this version better for buffer object creation to have higher speed of drawing while elliminating pci-e actions? – huseyin tugrul buyukisik Aug 25 '13 at 19:25
  • one quesiton from a java developer: what is M_PI_2 ?? – T_01 Aug 28 '14 at 21:50
  • ou just found it. its a half of pi. but why you need it here? – T_01 Aug 28 '14 at 21:51
  • but... could someone explain how to implement this with indexed drawing now?? thank you! – T_01 Aug 28 '14 at 22:01
  • What do you mean by indexed drawing? The above is a indexed polygon. If you mean in immediate draw mode, then just loop through the resulting arrays and set the coords. – user430788 Nov 10 '14 at 18:59
3

Here's the code:

glPushMatrix();
glTranslatef(18,2,0);
glRotatef(angle, 0, 0, 0.7);
glColor3ub(0,255,255);
glutWireSphere(3,10,10);
glPopMatrix();
Mateusz
  • 3,038
  • 4
  • 27
  • 41
sarah
  • 31
  • 1
3

Datanewolf's code is ALMOST right. I had to reverse both the winding and the normals to make it work properly with the fixed pipeline. The below works correctly with cull on or off for me:

std::vector<GLfloat> vertices;
std::vector<GLfloat> normals;
std::vector<GLfloat> texcoords;
std::vector<GLushort> indices;

float const R = 1./(float)(rings-1);
float const S = 1./(float)(sectors-1);
int r, s;

vertices.resize(rings * sectors * 3);
normals.resize(rings * sectors * 3);
texcoords.resize(rings * sectors * 2);
std::vector<GLfloat>::iterator v = vertices.begin();
std::vector<GLfloat>::iterator n = normals.begin();
std::vector<GLfloat>::iterator t = texcoords.begin();
for(r = 0; r < rings; r++) for(s = 0; s < sectors; s++) {
    float const y = sin( -M_PI_2 + M_PI * r * R );
    float const x = cos(2*M_PI * s * S) * sin( M_PI * r * R );
    float const z = sin(2*M_PI * s * S) * sin( M_PI * r * R );

    *t++ = s*S;
    *t++ = r*R;

    *v++ = x * radius;
    *v++ = y * radius;
    *v++ = z * radius;

    *n++ = -x;
    *n++ = -y;
    *n++ = -z;
}

indices.resize(rings * sectors * 4);
std::vector<GLushort>::iterator i = indices.begin();
for(r = 0; r < rings-1; r++)
    for(s = 0; s < sectors-1; s++) {
       /* 
        *i++ = r * sectors + s;
        *i++ = r * sectors + (s+1);
        *i++ = (r+1) * sectors + (s+1);
        *i++ = (r+1) * sectors + s;
        */
         *i++ = (r+1) * sectors + s;
         *i++ = (r+1) * sectors + (s+1);
        *i++ = r * sectors + (s+1);
         *i++ = r * sectors + s;

}

Edit: There was a question on how to draw this... in my code I encapsulate these values in a G3DModel class. This is my code to setup the frame, draw the model, and end it:

void GraphicsProvider3DPriv::BeginFrame()const{
        int win_width;
        int win_height;// framework of choice here
        glfwGetWindowSize(window, &win_width, &win_height); // retrieve window
        float const win_aspect = (float)win_width / (float)win_height;
        // set lighting
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        glEnable(GL_DEPTH_TEST);
        GLfloat lightpos[] = {0, 0.0, 0, 0.};
        glLightfv(GL_LIGHT0, GL_POSITION, lightpos);
        GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
        glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
        glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
        // set up world transform
        glClearColor(0.f, 0.f, 0.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT|GL_ACCUM_BUFFER_BIT);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();

        gluPerspective(45, win_aspect, 1, 10);

        glMatrixMode(GL_MODELVIEW);

    }


    void GraphicsProvider3DPriv::DrawModel(const G3DModel* model, const Transform3D transform)const{
        G3DModelPriv* privModel = (G3DModelPriv *)model;
        glPushMatrix();
        glLoadMatrixf(transform.GetOGLData());

        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_NORMAL_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        glVertexPointer(3, GL_FLOAT, 0, &privModel->vertices[0]);
        glNormalPointer(GL_FLOAT, 0, &privModel->normals[0]);
        glTexCoordPointer(2, GL_FLOAT, 0, &privModel->texcoords[0]);

        glEnable(GL_TEXTURE_2D);
        //glFrontFace(GL_CCW);
        glEnable(GL_CULL_FACE);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, privModel->texname);

        glDrawElements(GL_QUADS, privModel->indices.size(), GL_UNSIGNED_SHORT, &privModel->indices[0]);
        glPopMatrix();
        glDisable(GL_TEXTURE_2D);

    }

    void GraphicsProvider3DPriv::EndFrame()const{
        /* Swap front and back buffers */
        glDisable(GL_LIGHTING);
        glDisable(GL_LIGHT0);
        glDisable(GL_CULL_FACE);
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }
Sergey
  • 7,985
  • 4
  • 48
  • 80
user430788
  • 2,143
  • 2
  • 17
  • 17
  • Your normals end up pointing in the opposite direction like this - are you sure you aren't seeing your sphere inside out? – darklon Aug 02 '15 at 13:13
3

I like the answer of coin. It's simple to understand and works with triangles. However the indexes of his program are sometimes over the bounds. So I post here his code with two tiny corrections:

inline void push_indices(vector<GLushort>& indices, int sectors, int r, int s) {
    int curRow = r * sectors;
    int nextRow = (r+1) * sectors;
    int nextS = (s+1) % sectors;

    indices.push_back(curRow + s);
    indices.push_back(nextRow + s);
    indices.push_back(nextRow + nextS);

    indices.push_back(curRow + s);
    indices.push_back(nextRow + nextS);
    indices.push_back(curRow + nextS);
}

void createSphere(vector<vec3>& vertices, vector<GLushort>& indices, vector<vec2>& texcoords,
                  float radius, unsigned int rings, unsigned int sectors)
{
    float const R = 1./(float)(rings-1);
    float const S = 1./(float)(sectors-1);

    for(int r = 0; r < rings; ++r) {
        for(int s = 0; s < sectors; ++s) {
            float const y = sin( -M_PI_2 + M_PI * r * R );
            float const x = cos(2*M_PI * s * S) * sin( M_PI * r * R );
            float const z = sin(2*M_PI * s * S) * sin( M_PI * r * R );

            texcoords.push_back(vec2(s*S, r*R));
            vertices.push_back(vec3(x,y,z) * radius);
            if(r < rings-1)
                push_indices(indices, sectors, r, s);
        }
    }
}
Harald
  • 526
  • 4
  • 26