Creating a 3D sphere in Opengl using Visual C++
Asked Answered
R

6

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(); 
}
Record answered 13/5, 2011 at 7:31 Comment(4)
Be more specific. What have you tried?Guile
Please show the code that doesn't work.Stay
@Kiril:I wanted to know how can i implement a 3D sphere for my mini project ,which is a pendulum, in opengl..am using visual c++.Record
Related: Drawing a sphere in OpenGL without using gluSphere?Weigh
C
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;
}
Chloric answered 13/5, 2011 at 9:10 Comment(28)
Excellent stuff - do you have code to generate the texture co-ords as well?Prorogue
@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.Chloric
@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().Prorogue
@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.Chloric
@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 GLuints) 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 :-/Prorogue
@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.Chloric
@datenwolf: OK, will do - and the issue with the rendering? Are you sure the indices are being populated correctly?Prorogue
@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?Prorogue
@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.Chloric
Drawing this code with GL_TRIANGLE_STRIP brings ugly results.Seems like the indices gen is wrong.Each
@MichaelIV: Triangle Strips require an entirely different index order than quads. No wonder the sphere looks odd if used with something different than GL_QUADSChloric
Thanks for the headups. Yes I changed to TRIANGLES and it worked with some minor modifications :)Each
Tried to implement this and ran into problems. See the following question for details. An edit may be in place for this question.Miran
@toeplitz: Thanks for the heads up. I'll see into it.Chloric
@toeplitz: Okay, I fixed all the issues.Chloric
I like your use of the const modifier after the type. You don't see that often enough. :-)Sharisharia
@Chloric 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.Cant
@Shiki: I've marked your question and await your posting of a code snippet to work with.Chloric
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...Flaxseed
Okay, if i reverse the normals and the winding it works correctly culled and unculled. Fixed code below:Flaxseed
hey, what is M_PI ? =)Program
@RareFever: M_PI is a pi number ~ 3.14159, M_PI_2 is a half of it.Anesthetist
excuse me for a noobish question, but how do I render the display function on the screen? So far I have this: pastebin.com/cXugwcYgMartinelli
@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.Chloric
@Chloric could you please point me in the right direction, so I don't get even more lost? Thank you!Martinelli
The size of the indices vector should be (rings-1) * (sectors-1) * 4. You might want to fix that.Middlebrow
@Chloric 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 ?Middlebrow
Ohh, I now see what you mean. Loop termination conditions. Good catch.Chloric
S
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!

Sharisharia answered 29/12, 2012 at 16:0 Comment(2)
This should be the accepted answer for the actual question by the OP.Weigh
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.Sharisharia
P
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);
        }
    }
}
Pinwheel answered 12/12, 2012 at 18:51 Comment(8)
You should indicate that this answer does not uses glutSolidSphere as the question called for it.Zimbabwe
:) you a right. I just found this question seeking for a manual generation method WITHOUT glutSolidSphere...Pinwheel
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.Pinwheel
Is this version better for buffer object creation to have higher speed of drawing while elliminating pci-e actions?Entomologize
one quesiton from a java developer: what is M_PI_2 ??Hyunhz
ou just found it. its a half of pi. but why you need it here?Hyunhz
but... could someone explain how to implement this with indexed drawing now?? thank you!Hyunhz
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.Flaxseed
S
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();
Schechinger answered 26/5, 2013 at 15:14 Comment(1)
It's always nice if you leave some words describing your code.Cung
F
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();
    }
Flaxseed answered 10/11, 2014 at 17:11 Comment(1)
Your normals end up pointing in the opposite direction like this - are you sure you aren't seeing your sphere inside out?Cornwallis
C
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);
        }
    }
}
Cecilycecity answered 22/1, 2016 at 14:48 Comment(1)
@Pinwheel Thank you for your Code! Helped me much :)Cecilycecity

© 2022 - 2024 — McMap. All rights reserved.