Comparaison de quatre méthodes opengl pour afficher un objet 3d

Soyez le premier à donner votre avis sur cette source.

Vue 3 947 fois - Téléchargée 760 fois

Description

Ce démonstrateur sert à faire la comparaison de quatre méthodes différentes pour programmer l'affichage d'un objet 3d en OpenGL. L'essentiel de ce logiciel n'est pas de moi, il est publié par le professeur Wayne O. Cochran de l'université de Vancouver, son adresse est en première ligne du source. Mais pour obtenir le bon fonctionnement complet de ce programme dans toutes ses fonctions et selon ce que je souhaitais j'ai dû y apporter diverses modifications. Et je propose ici cette version avec son accord.
Il y a un zoom avec la touche Z ou z.
La souris, avec le bouton gauche, permet de faire tourner l'objet.
Avec le bouton droit on accède à un menu qui permet de :
1°) changer la méthode d'affichage OpenGL de l'objet 3d
2°) changer le mode d'affichage : arrêtes, facettes ou surfaces lissées
3°) avec ou sans effacement des facettes vues de l'arrière
4°) quitter la session ( <echap> fait de même ).
On choisit la méthode d'affichage OpenGL avec le menu en affectant la variable draw à : old, list, va ou vbo.
Cela permet d'avoir l'une des 4 méthodes :
1°) drawSpier_old_school(); pour les fonctions immédiates : glBegin(), glVertex(), ... glEnd()
2°) drawSpier_display_list(); pour les Display Lists : glGenLists(), ... glCallList()
3°) drawSpier_vertex_array(); pour les Vertex Arrays : glDrawArrays(), ... glDrawElements()
4°) drawSpier_vbo(); pour les VBOs : glGenBuffers(), ... glBufferData()
Chaque méthode fournit les mêmes résultats sur les mêmes données. Comme les données sont ici peu volumineuses il n'est pratiquement pas possible de voir les différences de performances. Par contre, les différences de programmation de chaque méthode pour obtenir les mêmes fonctions sont bien démontrées. En principe les VBOs sont plus performants que les Vertex Arrays, lesquels sont plus performants que les Display Lists qui sont plus performants que les fonctions immédiates, lesquelles sont maintenant obsolètes ou dépréciées. Chacune de ces quatre méthodes d'affichage OpenGL a une programmation bien différente des autres et c'est là l'intérêt principal de ce démonstrateur.

Source / Exemple :


// Adapté de : http://ezekiel.vancouver.wsu.edu/~cs442/lectures/vbo/spier/
#ifdef _MSC_VER
#pragma comment(linker, "/subsystem:windows /entry:mainCRTStartup")
#endif
#ifdef WIN32
#include <windows.h>
#endif
#if defined(__APPLE__) || defined(MACOSX)
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLUT/glut.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#endif
#include <GL/glext.h>
#include <cmath>

#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288
#endif

// draw permet d'activer l'une des 4 méthodes d'affichage OpenGL suivantes
// old : pour les fonctions immédiates : glBegin(), glVertex(), ... glEnd()
// list : pour les Display Lists : glGenLists(), ... glCallList()
// va : pour les Vertex Arrays : glDrawArrays(), ... glDrawElements()
// vbo : pour les Vertex Buffer Objects : glGenBuffers(), ... glBufferData() 
// On peut en changer la valeur pendant l'exécution avec le menu.

char draw[] = "vbo";  

PFNGLGENBUFFERSPROC glGenBuffers = NULL;
PFNGLBINDBUFFERPROC glBindBuffer = NULL;
PFNGLBUFFERDATAPROC glBufferData = NULL;

void initOGL() 
{
    glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers");
    glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
    glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData");
}

int width = 500;
int height = 500;
int shadeFlavor = 0;   // arêtes, facettes ou surfaces lissées

const float H = 1.0;   // height of base
const float T = 0.75;  // height of roof
const float R = 0.5;   // radias
const unsigned N = 48; // number of sides

GLuint bufObjects[7];

GLfloat topVertices[2*N][3];   // "roof" triangles stored as quad strip
GLfloat topNormals[2*N][3];
GLushort quadIndices[2*N+2];

GLfloat sideVertices[2*N][3];  // "side walls" stored as quad strip
GLfloat sideNormals[2*N][3];

GLfloat bottomVertices[N][3];  // "bottom floor" stored as polygon
GLfloat bottomNormals[N][3];

GLdouble eyeRadius = 3.5, eyePhi = M_PI/4.0, eyeTheta = M_PI/4.0;
GLdouble eye[3], at[3], up[3] = {0, 0, 1};

GLfloat ambient[4] = {0.24725, 0.1995, 0.0745, 1.0};
GLfloat diffuse[4] = {0.75164, 0.60648, 0.22648, 1.0};
GLfloat specular[4] = {0.628281, 0.555802, 0.366025, 1.0};
GLfloat shininess = 51.2;

float lmodel_ambient[4]      = {0.1, 0.1, 0.1, 1.0};
GLfloat light0_position[4]   = {30.0, 40.0, 30.0, 0.0};
GLfloat light0_ambient[4]    = {0.3, 0.3, 0.3, 1.0};
GLfloat light0_diffuse[4]    = {1.0, 1.0, 1.0, 1.0};
GLfloat light0_specular[4]   = {1.0, 1.0, 1.0, 1.0};

void sphericalToCartesian(double r, double theta, double phi, double *x, double *y, double *z) 
{
    double sin_phi = sin(phi);

  • x = r*cos(theta)*sin_phi;
  • y = r*sin(theta)*sin_phi;
  • z = r*cos(phi);
} void setView() { sphericalToCartesian(eyeRadius, eyeTheta, eyePhi, &eye[0], &eye[1], &eye[2]); eye[0] += at[0]; eye[1] += at[1]; eye[2] += at[2]; glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(eye[0], eye[1], eye[2], at[0], at[1], at[2], up[0], up[1], up[2]); } void init() { glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient); glLightfv(GL_LIGHT0, GL_AMBIENT, light0_ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse); glLightfv(GL_LIGHT0, GL_SPECULAR, light0_specular); glLightfv(GL_LIGHT0, GL_POSITION, light0_position); glEnable(GL_LIGHT0); glMaterialfv(GL_FRONT, GL_AMBIENT, ambient); glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, specular); glMaterialfv(GL_FRONT, GL_SHININESS, &shininess); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(40.0, 1.0, 0.1, 30); at[0] = 0.0; at[1] = 0.0; at[2] = (H+T)/2.0; setView(); glShadeModel(GL_FLAT); glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); glDisable(GL_CULL_FACE); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_LIGHTING); glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST); glColor3f(1,1,1); // On utilise ici 7 buffers pour renseigner l'objet, // d'autres choix sont possibles. glGenBuffers(7, bufObjects); glBindBuffer(GL_ARRAY_BUFFER, bufObjects[0]); glBufferData(GL_ARRAY_BUFFER, 2*N*3*sizeof(GLfloat), topVertices, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, bufObjects[1]); glBufferData(GL_ARRAY_BUFFER, 2*N*3*sizeof(GLfloat), topNormals, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufObjects[2]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (2*N+2)*sizeof(GLushort), quadIndices, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, bufObjects[3]); glBufferData(GL_ARRAY_BUFFER, 2*N*3*sizeof(GLfloat), sideVertices, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, bufObjects[4]); glBufferData(GL_ARRAY_BUFFER, 2*N*3*sizeof(GLfloat), sideNormals, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, bufObjects[5]); glBufferData(GL_ARRAY_BUFFER, N*3*sizeof(GLfloat), bottomVertices, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, bufObjects[6]); glBufferData(GL_ARRAY_BUFFER, N*3*sizeof(GLfloat), bottomNormals, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } void makeSpier() { const float phi = atan2(R,T); const float cos_phi = cosf(phi); const float sin_phi = sinf(phi); const float dtheta = (2*M_PI/N); float theta = 0.0; for (unsigned i = 0; i < N; ++i) { const double cos_theta = cos(theta); const double sin_theta = sin(theta); const float x = R*cos_theta; const float y = R*sin_theta; quadIndices[2*i] = 2*i; quadIndices[2*i+1] = 2*i + 1; topVertices[2*i][0] = 0.0; topVertices[2*i][1] = 0.0; topVertices[2*i][2] = H+T; topNormals[2*i][0] = cos_phi*cos_theta; topNormals[2*i][1] = cos_phi*sin_theta; topNormals[2*i][2] = sin_phi; topVertices[2*i+1][0] = x; topVertices[2*i+1][1] = y; topVertices[2*i+1][2] = H; topNormals[2*i+1][0] = cos_phi*cos_theta; topNormals[2*i+1][1] = cos_phi*sin_theta; topNormals[2*i+1][2] = sin_phi; sideVertices[2*i][0] = x; sideVertices[2*i][1] = y; sideVertices[2*i][2] = H; sideNormals[2*i][0] = cos_theta; sideNormals[2*i][1] = sin_theta; sideNormals[2*i][2] = 0.0; sideVertices[2*i+1][0] = x; sideVertices[2*i+1][1] = y; sideVertices[2*i+1][2] = 0.0; sideNormals[2*i+1][0] = cos_theta; sideNormals[2*i+1][1] = sin_theta; sideNormals[2*i+1][2] = 0.0; bottomVertices[i][0] = x; bottomVertices[i][1] = -y; bottomVertices[i][2] = 0.0; bottomNormals[i][0] = 0.0; bottomNormals[i][1] = 0.0; bottomNormals[i][2] = -1.0; theta += dtheta; } quadIndices[2*N] = 0; quadIndices[2*N+1] = 1; } // NOTA : Ici on a fait aussi le choix d'un GL_QUAD_STRIP // pour le toit et non pas d'un GL_TRIANGLE_FAN en raison // des normales à chaque triangle. Voir ci-joint vbo.pdf void drawSpier_old_school() { unsigned i; glBegin(GL_QUAD_STRIP); for (i = 0; i < 2*N; i++) { glNormal3fv(topNormals[i]); glVertex3fv(topVertices[i]); } glNormal3fv(topNormals[0]); glVertex3fv(topVertices[0]); glNormal3fv(topNormals[1]); glVertex3fv(topVertices[1]); glEnd(); glBegin(GL_QUAD_STRIP); for (i = 0; i < 2*N; i++) { glNormal3fv(sideNormals[i]); glVertex3fv(sideVertices[i]); } glNormal3fv(sideNormals[0]); glVertex3fv(sideVertices[0]); glNormal3fv(sideNormals[1]); glVertex3fv(sideVertices[1]); glEnd(); glBegin(GL_POLYGON); for (i = 0; i < N; i++) { glNormal3fv(bottomNormals[i]); glVertex3fv(bottomVertices[i]); } glEnd(); } void drawSpier_display_list() { static bool firstTime = true; static GLuint displayList; if (firstTime) { displayList = glGenLists(1); glNewList(displayList, GL_COMPILE_AND_EXECUTE); drawSpier_old_school(); glEndList(); firstTime = false; } else glCallList(displayList); } void drawSpier_vertex_array() { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glVertexPointer(3, GL_FLOAT, 0, topVertices); glNormalPointer(GL_FLOAT, 0, topNormals); glDrawElements(GL_QUAD_STRIP, 2*N+2, GL_UNSIGNED_SHORT, quadIndices); glVertexPointer(3, GL_FLOAT, 0, sideVertices); glNormalPointer(GL_FLOAT, 0, sideNormals); glDrawElements(GL_QUAD_STRIP, 2*N+2, GL_UNSIGNED_SHORT, quadIndices); glVertexPointer(3, GL_FLOAT, 0, bottomVertices); glNormalPointer(GL_FLOAT, 0, bottomNormals); glDrawArrays(GL_POLYGON, 0, N); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); } void drawSpier_vbo() { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glBindBuffer(GL_ARRAY_BUFFER, bufObjects[0]); glVertexPointer(3, GL_FLOAT, 0, (GLvoid*) 0); glBindBuffer(GL_ARRAY_BUFFER, bufObjects[1]); glNormalPointer(GL_FLOAT, 0, (GLvoid*) 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufObjects[2]); glDrawElements(GL_QUAD_STRIP, 2*N+2, GL_UNSIGNED_SHORT, (GLvoid*) 0); // NOTA : les quadIndices[] rangés en GPU et désignés ici par bufObjets[2] sont // les mêmes pour le toit et pour les côtés. Ils sont donc utilisés ici deux fois. glBindBuffer(GL_ARRAY_BUFFER, bufObjects[3]); glVertexPointer(3, GL_FLOAT, 0, (GLvoid*) 0); glBindBuffer(GL_ARRAY_BUFFER, bufObjects[4]); glNormalPointer(GL_FLOAT, 0, (GLvoid*) 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufObjects[2]); glDrawElements(GL_QUAD_STRIP, 2*N+2, GL_UNSIGNED_SHORT, (GLvoid*) 0); glBindBuffer(GL_ARRAY_BUFFER, bufObjects[5]); glVertexPointer(3, GL_FLOAT, 0, (GLvoid*) 0); glBindBuffer(GL_ARRAY_BUFFER, bufObjects[6]); glNormalPointer(GL_FLOAT, 0, (GLvoid*) 0); glDrawArrays(GL_POLYGON, 0, N); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); } void printDraw() { glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, width, 0, height); char buf[16]; strcpy(buf, "draw : "); strcat(buf, draw); glDisable(GL_LIGHTING); glRasterPos2i(1, 1); for(unsigned int i=0; i<strlen(buf); i++) glutBitmapCharacter(GLUT_BITMAP_8_BY_13, buf[i]); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); if(shadeFlavor != 0) glEnable(GL_LIGHTING); } void display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if(strcmp(draw,"old")==0) drawSpier_old_school(); if(strcmp(draw,"list")==0) drawSpier_display_list(); if(strcmp(draw,"va")==0) drawSpier_vertex_array(); if(strcmp(draw,"vbo")==0) drawSpier_vbo(); printDraw(); glutSwapBuffers(); } bool mouseRotate = false; int mousex, mousey; const double epsilon = 0.0000001; void mouse(int button, int state, int x, int y) { if (state == GLUT_DOWN) { if (button == GLUT_LEFT_BUTTON) { mouseRotate = true; mousex = x; mousey = y; } } else if (state == GLUT_UP) mouseRotate = false; } void mouseMotion(int x, int y) { if (mouseRotate) { const double radiansPerPixel = M_PI/(2*90.0); int dx = x - mousex, dy = y - mousey; eyeTheta -= dx*radiansPerPixel; eyePhi -= dy*radiansPerPixel; if (eyePhi >= M_PI) eyePhi = M_PI - epsilon; else if (eyePhi <= 0.0) eyePhi = epsilon; setView(); mousex = x; mousey = y; glutPostRedisplay(); } } enum { MENU_OLD_LIST_VA_VBO, MENU_SHADE_NONE_FLAT_SMOOTH = 1, MENU_CULL_BACKFACES, MENU_QUIT = 666 }; void menu(int value) { switch(value) { case MENU_OLD_LIST_VA_VBO: { static unsigned methode = 3; methode = (methode + 1) % 4; switch(methode) { case 0: strcpy(draw,"old"); break; case 1: strcpy(draw,"list"); break; case 2: strcpy(draw,"va"); break; case 3: strcpy(draw,"vbo"); break; } glutPostRedisplay(); } break; case MENU_SHADE_NONE_FLAT_SMOOTH: { shadeFlavor = (shadeFlavor + 1) % 3; switch(shadeFlavor) { case 0: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_LIGHTING); break; case 1: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_LIGHTING); glShadeModel(GL_FLAT); break; case 2: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_LIGHTING); glShadeModel(GL_SMOOTH); break; } glutPostRedisplay(); } break; case MENU_CULL_BACKFACES: { static GLboolean cull = false; cull = !cull; if (cull) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); } glutPostRedisplay(); break; case MENU_QUIT: exit(0); } } void keyboard(unsigned char key, int x, int y) { static const unsigned char ESC = 27; switch(key) { case 'Z': eyeRadius *= 0.95; setView(); glutPostRedisplay(); break; case 'z': eyeRadius *= 1.05; setView(); glutPostRedisplay(); break; case ESC: exit(0); } } void idle(void) { glutPostRedisplay(); } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(width, height); glutInitWindowPosition(10,10); glutCreateWindow("Spier"); glutMouseFunc(mouse); glutMotionFunc(mouseMotion); glutCreateMenu(menu); glutAddMenuEntry("draw : old/list/va/vbo", MENU_OLD_LIST_VA_VBO); glutAddMenuEntry("shading off/flat/smooth", MENU_SHADE_NONE_FLAT_SMOOTH); glutAddMenuEntry("cull backfaces on/off", MENU_CULL_BACKFACES); glutAddMenuEntry("Quit", MENU_QUIT); glutAttachMenu(GLUT_RIGHT_BUTTON); glutKeyboardFunc(keyboard); glutDisplayFunc(display); glutIdleFunc(idle); makeSpier(); initOGL(); init(); glutMainLoop(); return 0; }

Conclusion :


Toutes remarques ou améliorations sont les bienvenues.

Codes Sources

A voir également

Ajouter un commentaire

Commentaires

shenron666
Messages postés
231
Date d'inscription
dimanche 14 septembre 2003
Statut
Membre
Dernière intervention
20 août 2014
-
Salut, comme tu dis, les fonctions immédiates sont dépréciées.
Il faut savoir que les display list le sont également.

Il serait intéressant que ton programme permette de basculer d'un mode à l'autre sans avoir à recompiler le code.
Le menu contextuel est implémenté, ajouter cette possibilité au menu n'est pas compliqué.

Un peu plus de commentaires ne serait pas de refus, surtout si tu cibles les débutants.

Avec tout ça, ta source deviendrait intéressante d'un point de vue éducatif.

ps: Il manque des retours à la ligne dans la description ;)
pgl10
Messages postés
310
Date d'inscription
samedi 18 décembre 2004
Statut
Membre
Dernière intervention
6 juillet 2019
1 -
Shenron666, Je n'ai pas programmé le basculement d'une méthode d'affichage à une autre pendant l'exécution pour deux raisons : il y a dans init() une séquence d'initialisation qui est nécessaire pour les VBOs mais qui est incompatible avec les Vertex Arrays et de plus chaque méthode fournit exactement les mêmes fonctions, donc ce basculement n'aurait aucun effet visible. Selon moi, on pourrait le faire au prix d'une complication qui n'en vaut pas la peine pour un simple démonstrateur. Il est bien vrai que les Display Lists sont dépréciées autant que les fonctions immédiates, elles ne sont là que pour faire la comparaison avec les méthodes plus récentes dans le but d'aider ceux qui par ancienneté ne connaissent que celles-ci et qui souhaitent en changer. Par contre j'ai mis quelques commentaires explicatifs supplémentaires et j'ai, je crois, amélioré la présentation.
shenron666
Messages postés
231
Date d'inscription
dimanche 14 septembre 2003
Statut
Membre
Dernière intervention
20 août 2014
-
Impeccable pour ce qui est de la description.
par contre, je ne comprend pas pourquoi la séquence d'init pour les vbo est incompatible avec les vertex array
tu peux tout à fait utiliser les vbo et les vertex array simultanément
c'est peut-être un état opengl mal configuré qui traine quelque part dans le code
pgl10
Messages postés
310
Date d'inscription
samedi 18 décembre 2004
Statut
Membre
Dernière intervention
6 juillet 2019
1 -
Moi non plus je n'ai pas d'explication sur l'incompatibilité que j'ai rencontrée. Voici plus précisément ce que j'ai observé. Si je désactive le if(strcmp(draw,"vbo")==0) dans init() la totalité des initialisations est faite, y compris celle qui est nécessaire ici pour les VBOs. Dans ces conditions les options draw = "old" ou "list" ou "vbo" fonctionnent très bien. Par contre l'option draw = "va" provoque chez moi une sorte de plantage de mon PC dont je sors plus ou moins difficilement. C'est dommage, parce que j'avais déjà programmé le basculement via le menu avec une petite indication, en bas de la fenêtre, pour indiquer la méthode en service. Ce serait un complément intéressant de trouver l'explication.
pgl10
Messages postés
310
Date d'inscription
samedi 18 décembre 2004
Statut
Membre
Dernière intervention
6 juillet 2019
1 -
Quand on initialise ou bien qu'on utilise les VBOs on active chacun d'eux l'un après l'autre dans un mode ou dans un autre. Mais après la fin d'utilisation il convient de les désactiver. Ce n'était pas fait, mais maintenant la correction est faite. Et c'est pour cela que l'initialisation des VBOs était incompatible avec l'utilisation des Vertex Arrays ensuite. En plus, comme il n'y a pas de changement important dans l'organisation du programme il a été facile d'introduire le basculement d'une méthode d'affichage OpenGL pour l'objet 3d pendant l'exécution et non plus dans le source. C'est mieux, non ?

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.