Search code examples
c++opengldepth-buffer

Applying OpenGL Depth Test to Orbiting Spheres


I'm writing a gravitational n-body simulation in C++, which is animated using OpenGL and GLUT (this is a hobby project). The animation works fine, for the most part, however I've had two major problems that I'm unable to resolve:

  • Although depth testing is enabled, it isn't working as expected, and
  • The surfaces are ... well, messy.

My question is, how do I fix these problems?

Both problems can be seen in the following images (apologies for the links but I've not enough rep for image posting). These are snapshots from a simulation of a simple orbit, viewed edge on.

Here the yellow sphere is drawn in front of the purple one, as it should be.

After half an orbit the yellow sphere is still drawn in front, even though it is further away.

The code used to create the animation is given below.

#include <GL/glut.h>
#include "Cluster.h" // My own class.

// Scale for animation.  Each unit in the animation = 1/SCALE m.
const double SCALE = 1e-10;
// Size of spheres for animation.
const double SPHERE_SIZE = 2e10*SCALE;

// Cluster object contains bodies and updates their positions.
Cluster cluster();

// Array of rgb colors for spheres.
GLfloat colorArr[4][4] =
{ 
   {0.7, 0.7, 0.0, 1.0},
   {0.73, 0.24, 0.95, 1.0},
}

void display()
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   gluLookAt( 0.0*SCALE, 5e11*SCALE, 0.0*SCALE  // eye is on y-axis outside orbit.
            , 0.0, 0.0, 0.0 
            , 0.0, 0.0, 1.0 ); 

   for (int i=0; i<N; i++) // N is the number of bodies in cluster.
   { 
      glPushMatrix(); 
      glTranslated( SCALE*cluster.getX(i)     // Get coordinate of ith body.
                  , SCALE*cluster.getY(i)
                  , SCALE*cluster.getZ(i) );
      glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, colorArr[i]);
      glutSolidSphere(SPHERE_SIZE, 50, 50);
      glCullFace(GL_BACK);
      glPopMatrix();
   } 

   glutSwapBuffers();
} 

void reshape(GLint w, GLint h) 
{ 
   glViewport(0, 0, w, h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(30.0, (GLfloat)w/(GLfloat)h, SCALE, 5e11*SCALE);
   glMatrixMode(GL_MODELVIEW);
} 

void animate() 
{ 
   // Update positions and redraw.
   cluster.update();
   display();
} 

void init() 
{ 
   GLfloat black[] = {0.0, 0.0, 0.0, 1.0};
   GLfloat white[] = {1.0, 1.0, 1.0, 0.5};
   GLfloat direction[] = {1.0, 1.0, 1.0, 0.0};

   glMaterialfv(GL_FRONT, GL_SPECULAR, white);
   glMaterialf(GL_FRONT, GL_SHININESS, 10);

   glLightfv(GL_LIGHT0, GL_AMBIENT, black);
   glLightfv(GL_LIGHT0, GL_DIFFUSE, white);
   glLightfv(GL_LIGHT0, GL_SPECULAR, white);
   glLightfv(GL_LIGHT0, GL_POSITION, direction);

   glEnable(GL_LIGHTING);
   glEnable(GL_LIGHT0);
   glEnable(GL_DEPTH_TEST);
} 

int main(int argc, char** argv) 
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize(800, 600);
   glutCreateWindow("Test Orbit");
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutIdleFunc(animate);
   init();
   glutMainLoop();
}

Solution

  • OpenGL's depth buffer has limited precision, and the numbers you're using are large enough that you may be running into that precision limit. You may want to read this article about depth-buffer optimization, or simply use a larger unit than meters for representing astronomical distances.