Créer et afficher des objets 3D avec OpenGL


La librairie graphique OpenGL, utilisée avecun langage de programmation ( lC, C++ ou Fortran), permet de créer et de manipuler des objets 3D et de les afficher à l'intérieur d'une fenêtre de programme . Elle permet notamment d'afficher, à l'intérieur d'une fenêtre de programme, des objets 3D créés avec un logiciel de modélisation 3D.

On regarde ci après comment (en créant une fenêtre de programme C++ et en utilisant 'OpenGL) :
1) Spécifier des sommets d'objets 3D ;
2) Créer et afficher un tétraèdre directement créé par le programme;
3) Afficher un cube créé avecle logiciel de modélisation anim8or ;
4) Afficher un objet 3D quelconque créé avec anim8or.

1) Spécifier des sommets d'objets 3D

1.1) Fonctions de spécification de sommets

Il existe de nombreuses fonctions de spécification de sommets. Ces fonctions présentent la syntaxe glVertexNT où N représente le nombre de coordonnées considérés et T le type de ces coordonnées. Par exemple, les fonctions glVertex2s(), glVertex2d(), glVertex3d(), glVertex3d() et glVertex4f() servent à spécifier des sommets avec 2 ou 3 coordonnées (coordonnées x, y ou x,y,z de type simple, double ou flottant). Ces fonctions se placent entre les instructions glBegin() and glEnd() et les sommets correspondants sont dessinés dans l'ordre indiqué. Elles permettent de spécifier des points, des lignes, des triangles...

void glVertex4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w)
x : spécifie l'abscisse x d'un sommet
y : spécifie l'ordonnée y d'un sommet
z : spécifie la coordonnée z d'un sommet
w : spécifie la coordonnée w d'un sommet (les coordonnées x,y et z sont divisées par w)

void glVertex3d( GLdouble x, GLdouble y,GLdouble z ) et void glVertex3f (GLfloat x,GLfloat y,GLfloat z)
x : spécifie l'abscisse x d'un sommet
y : spécifie l'ordonnée y d'un sommet
z : spécifie la coordonnée z d'un sommet
Avec cette fonction, la valeur w est fixée à 1.0

void glVertex2s( GLshort x, GLshort y) et void glVertex2d( GLdouble x, GLdouble y)
x : spécifie l'abscisse x d'un sommet
y : spécifie l'ordonnée y d'un sommet
Avec cette fonction, la valeur z est fixée à 0

1.2) Exemple

Pour voir comment dréer un programme C++ avec OpenGL lire la page "Introduction à opengl".

//============= =========
//INSERER MES VARIABLES GLOBALES ET MON CODE OPENGL ICI --
float theta = 0.0f;
void MonAffichageEnBoucle()
{
/* OpenGL animation code goes here */
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glOrtho (-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
glRotatef(theta, 0.1f, 0.1f, 0.0f);
glPointSize(10);
glBegin(GL_POINTS);
glColor3f (1.0f, 0.0f, 0.0f);
glVertex3f (-0.5, -0.5, 0.5);
glVertex3f (0.5, -0.5, 0.5);
glVertex3f (-0.5, 0.5, 0.5);
glVertex3f (0.5, 0.5, 0.5);
glVertex3f (-0.5, 0.5, -0.5);
glVertex3f (0.5, 0.5, -0.5);
glVertex3f (-0.5, -0.5, -0.5);
glVertex3f (0.5, -0.5, -0.5);
glEnd();
glPopMatrix();
SwapBuffers(hDC);
theta += 1.0f;
Sleep (1);
}

01

Code source complet du projet codeblocks associé à cet exemple : test-1.7z

Il est à noter que pour les coordonnées x,y et z, OpenGL (contrairement à Direct3D de Microsoft) utilise le sens du triède direct. ( l'axe des x est l'axe horizontal de l'écran avec le sens positif dirigé vers la droite, l'axe des y est l'axe vertical de l'écran avec le sens positif dirigé vers le haut et l'axe des z est l'axe perpendiculaire à l'écran avec le sens positif dirigé vers l'utilisateur de l'ordinateur).

Avec les paramètres utilisés ici avec la fonction glOrtho(), à savoir glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0), le point (0,0,0) se trouve au centre de l'écran.

02

Si on remplace le paramètre GL_POINTS par GL_LINES, dans l'instruction glBegin(), on obtient l'affichage de lignes et non plus de points.

03

La fonction glLineWidth() permet de spécifier la largeur des lignes à tracer. La valeur par défaut est de 1 pixel.

void glLineWidth ( GLfloat width)

04

L'exemple suivant trace deux triangles identiques tourant autour de l'axe des x, l'un coloré en rouge placé dans le plan x,y à la profondeur z=1 et l'autre, coloré en vert, placé dans le même plan mais à la profondeur z= -1

//============ =============
//INSERER MES VARIABLES GLOBALES ET MON CODE OPENGL ICI --
float theta = 0.0f;
void MonAffichageEnBoucle()
{
/* OpenGL animation code goes here */
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glOrtho (-2.0, 2.0, -2.0, 2.0, -2.0, 2.0);
glRotatef(theta, 0.1f, 0.0f, 0.0f);
glPointSize(10);
glBegin(GL_TRIANGLES);
glColor3f (1.0f, 0.0f, 0.0f);
glVertex3f (-1.0, -1.0, 1.0);
glVertex3f (-1.0, 0.0, 1.0);
glVertex3f (1.0, 0.0, 1.0);
glColor3f (0.0f, 1.0f, 0.0f);
glVertex3f (-1.0, -1.0, -1.0);
glVertex3f (-1.0, 0.0, -1.0);
glVertex3f (1.0, 0.0, -1.0);
glEnd();
glPopMatrix();
SwapBuffers(hDC);
theta += 1.0f;
Sleep (1);
}
//=============== ================

05

2) Créer et afficher un tétraèdre

Un tetraèdre (ou pyramide) est un objet 3D composé de 4 triangles.
On considère le tétraèdre constitué des quatres sommets (v0 à v3) positionnés aux coordonnées x,y,z suivantes :
v0  10  10  0
v1  10  0  10
v2  20  0  0
v3  0  0  0

Ce tétraèdre est constitué des quatre triangles suivants qui constituent ses quatres faces f0 à f3:
f0 v0 v1 v2
f1 v0 v2 v3
f2 v3 v1 v0
f3 v3 v2 v1

06

Une solution possible, pour afficher le tétraèdre dans la fenêtre du programme, consiste à utiliser la fonction glVertex3fv(). Cette fonction recoit en paramètre un pointeur vers un tableau qui contient les coordonnées x,y, z d'un sommet.

void glVertex3fv (const GLfloat *v); v : pointeur vers un tableau de trois éléments x,y et z constituant les coordonnées d'un sommet

La fonction glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ) permet d'afficher le tétraèdre en mode maille (wire). La fonction glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ) permet d'afficher le tétraèdre en mode plein (fill).

07

Code source complet du projet codeblocks associé à cet exemple : test-2.7z

3) Afficher un cube créé avec anim8or

Pour voir comment modéliser un cube avec le logiciel anim8or, lire la page suivante : http://turrier.fr/articles/3d-02/modeliser-un-objet-3d-avec-anim8or.php

Depuis anim8or créer et exporter un cube ("Object/Export...")

08

Choisir un le type de fichier "c source file (*.c)", un répertoire et un nom de fichier (cube.c par exemple) puis cliquer "Enregistrer".

09

Ouvrir le fichier "cube.c" à l'aide d'un éditeur de texte (notepad++ par exemple qui est gratuit) ou avec le Bloc-notes.
Examiner les parties suivantes qui vont pouvoir être récupérées et insérées telles quelles dans un programme C++ utilisant OpenGL.

  • static float cube01_coords[] qui indique les coordonnées x,y et z de chaque sommet du cube
  • static int cube01_indices[] qui décrit les différentes faces (chaque triangle est considéré comme une face) du cube en indiquant les n° des sommets qui constituent chacune de ces faces

10

Le listing du programme ainsi créé est le suivant :

#include <windows.h>
#include <gl/gl.h>
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
void EnableOpenGL(HWND hwnd, HDC*, HGLRC*);
void DisableOpenGL(HWND, HDC, HGLRC);
HDC hDC;
int k;
GLfloat x,y,z;
static GLfloat v[] = {
-14.1605, -7.591, -14.1605,
14.1605, -7.591, -14.1605,
14.1605, 7.591, -14.1605,
-14.1605, 7.591, -14.1605,
-14.1605, -7.591, 14.1605,
-14.1605, 7.591, 14.1605,
14.1605, 7.591, 14.1605,
14.1605, -7.591, 14.1605,
-14.1605, -7.591, -14.1605,
-14.1605, 7.591, -14.1605,
-14.1605, 7.591, 14.1605,
-14.1605, -7.591, 14.1605,
14.1605, -7.591, -14.1605,
14.1605, -7.591, 14.1605,
14.1605, 7.591, 14.1605,
14.1605, 7.591, -14.1605,
-14.1605, 7.591, -14.1605,
14.1605, 7.591, -14.1605,
14.1605, 7.591, 14.1605,
-14.1605, 7.591, 14.1605,
-14.1605, -7.591, -14.1605,
-14.1605, -7.591, 14.1605,
14.1605, -7.591, 14.1605,
14.1605, -7.591, -14.1605,
};
static int i[] = {
0, 2, 1,
0, 3, 2,
4, 6, 5,
4, 7, 6,
8, 10, 9,
8, 11, 10,
12, 14, 13,
12, 15, 14,
16, 18, 17,
16, 19, 18,
20, 22, 21,
20, 23, 22,
};
//=============== =============
//INSERER MES VARIABLES GLOBALES ET MON CODE OPENGL ICI --
float theta = 0.0f;
void MonAffichageEnBoucle()
{
/* OpenGL animation code goes here */
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glOrtho (-50.0, 50.0, -50.0, 50.0, -50.0, 50.0);
glRotatef(theta, 1.0f, 1.0f, 0.0f);
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
//glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glBegin(GL_TRIANGLES);
//0, 2, 1,
glVertex3f(v[0], v[1], v[2]);
glVertex3f(v[6], v[7], v[8]);
glVertex3f(v[3], v[4], v[5]);
//0, 3, 2,
glVertex3f(v[0], v[1], v[2]);
glVertex3f(v[9], v[10], v[11]);
glVertex3f(v[6], v[7], v[8]);
//4, 6, 5,
glVertex3f(v[12], v[13], v[14]);
glVertex3f(v[18], v[19], v[20]);
glVertex3f(v[15], v[16], v[17]);
//4, 7, 6,
glVertex3f(v[12], v[13], v[14]);
glVertex3f(v[21], v[22], v[23]);
glVertex3f(v[18], v[19], v[20]);
// 8, 10, 9,
glVertex3f(v[24], v[25], v[26]);
glVertex3f(v[30], v[31], v[32]);
glVertex3f(v[27], v[28], v[29]);
//8, 11, 10,
glVertex3f(v[24], v[25], v[26]);
glVertex3f(v[33], v[34], v[35]);
glVertex3f(v[30], v[31], v[32]);
//12, 14, 13,
glVertex3f(v[36], v[37], v[38]);
glVertex3f(v[42], v[43], v[44]);
glVertex3f(v[39], v[40], v[41]);
//12, 15, 14,
glVertex3f(v[36], v[37], v[38]);
glVertex3f(v[45], v[46], v[47]);
glVertex3f(v[42], v[43], v[44]);
//16, 18, 17,
glVertex3f(v[48], v[49], v[50]);
glVertex3f(v[54], v[55], v[56]);
glVertex3f(v[51], v[52], v[53]);
//16, 19, 18,
glVertex3f(v[48], v[49], v[50]);
glVertex3f(v[57], v[58], v[59]);
glVertex3f(v[54], v[55], v[56]);
//20, 22, 21,
glVertex3f(v[60], v[61], v[62]);
glVertex3f(v[66], v[67], v[68]);
glVertex3f(v[63], v[64], v[65]);
//20, 23, 22,
glVertex3f(v[60], v[61], v[62]);
glVertex3f(v[69], v[70], v[71]);
glVertex3f(v[66], v[67], v[68]);
glEnd();
glPopMatrix();
SwapBuffers(hDC);
theta += 1.0f;
Sleep (1);
}
// ============= ===========
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nCmdShow){
WNDCLASSEX wcex; HWND hwnd;HGLRC hRC; MSG msg; BOOL bQuit = FALSE;
wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_OWNDC;
wcex.lpfnWndProc = WindowProc; wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0; wcex.hInstance = hInst;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH) GetStockObject( BLACK_BRUSH );
wcex.lpszMenuName = NULL; wcex.lpszClassName = "GLSample";
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);;
if (!RegisterClassEx( &wcex ) ) return 0;
hwnd = CreateWindowEx(0, "GLSample", "OpenGL Sample",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
400, 400, NULL, NULL, hInst, NULL); ShowWindow(hwnd, nCmdShow);
EnableOpenGL(hwnd, &hDC, &hRC);
while (!bQuit){
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
if (msg.message == WM_QUIT){bQuit = TRUE;}else
{TranslateMessage( &msg ); DispatchMessage( &msg );}}
else { MonAffichageEnBoucle() ; } }
DisableOpenGL( hwnd, hDC, hRC); DestroyWindow( hwnd); return msg.wParam;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
switch (uMsg){
case WM_CLOSE:PostQuitMessage(0);break;
case WM_DESTROY:return 0;
case WM_KEYDOWN: { switch ( wParam ) {case VK_ESCAPE: PostQuitMessage(0); break; } } break;
default:return DefWindowProc( hwnd, uMsg, wParam, lParam); }
return 0;
}
void EnableOpenGL( HWND hwnd, HDC* hDC, HGLRC* hRC){
PIXELFORMATDESCRIPTOR pfd;
int iFormat; *hDC = GetDC(hwnd); ZeroMemory(&pfd, sizeof(pfd));
pfd.nSize = sizeof(pfd); pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW |PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; pfd.cDepthBits = 16;
pfd.iLayerType = PFD_MAIN_PLANE;
iFormat = ChoosePixelFormat( *hDC, &pfd ); SetPixelFormat( *hDC, iFormat, &pfd );
*hRC = wglCreateContext( *hDC ); wglMakeCurrent( *hDC, *hRC );
}
void DisableOpenGL (HWND hwnd, HDC hDC, HGLRC hRC){
wglMakeCurrent( NULL, NULL); wglDeleteContext( hRC ); ReleaseDC( hwnd, hDC );
}

A l'exécution, le programme affiche le cube en mode maillé qui tourne de façon continue autour de la bissectrice de l'axe (x,y).

11

Code source complet du projet codeblocks associé à cet exemple : test-3.7z

Ce code peut être réduit et généralisé afin de s'appliquer à un objet présentant un nombre de faces quelconques. C'est ce que fait le code suivant qui calcule, à l'aide de l'instruction sizeof(), la taille du tableau f[ ] afin de déterminer le nombre de faces de l'objet 3D considéré.:

//=============== ==============
//INSERER MES VARIABLES GLOBALES ET MON CODE OPENGL ICI --
float theta = 0.0f;
void MonAffichageEnBoucle()
{
GLfloat x,y,z;
int n, i, j, k, p, q, r;
int nombre_de_faces;
nombre_de_faces = sizeof(f)/(3*sizeof(f[0]));
/* OpenGL animation code goes here */
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glOrtho (-50.0, 50.0, -50.0, 50.0, -50.0, 50.0);
glRotatef(theta, 1.0f, 1.0f, 0.0f);
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
//glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glBegin(GL_TRIANGLES);
for (n=0; n<nombre_de_faces; n++){ //12 faces (triangles)
i=f[3*n]; //sommet 1 du triangle en cours
p=3*i; q=3*i+1;r=3*i+2;
x=v[p];y=v[q];z=v[r];
glVertex3f(x,y,z);
i=f[3*n+1]; //sommet 2 du triangle en cours
p=3*i; q=3*i+1;r=3*i+2;
x=v[p];y=v[q];z=v[r];
glVertex3f(x,y,z);
i=f[3*n+2]; //sommet 3 du triangle en cours
p=3*i; q=3*i+1;r=3*i+2;
x=v[p];y=v[q];z=v[r];
glVertex3f(x,y,z);
}
glEnd();
glPopMatrix();
SwapBuffers(hDC);
theta += 1.0f;
Sleep (1);
}
// =========== =================

12

Code source complet du projet codeblocks associé à cet exemple : test-4.7z

L'ajout des lignes de code suivantes, au début de la fonction d'affichage, permet d'observer la valeur des variables au moment de l'exécution. Cette possibilité est bien utile pour comprendre ce qui se passe lorsque le résultat obtenu à l'affichage n'est pas celui escompté...

char buf[256]; int a,b; a=sizeof(v) / sizeof( v[0] ); b=sizeof( f ) / sizeof( f[0] );
sprintf( buf, "nbre elts de v= %d : nbre elts de f= %d ",a,b);
TextOut( hDC, 0, 0, buf, strlen(buf) );

13

4) Afficher un objet 3D quelconque créé avec anim8or

Lancer Anim8or, cliquer "Build/Primitives/Sphere" et dessiner une sphère. La sphère apparaît entourée d'un cadre jaune indiquant qu'elle est sélectionnée.

14

Cliquer "Build/Convert to Mesh" pour convertir la sphère en objet maillé. Une fois cette conversion effectuée, la sphère apparaît entourée d'un cadre blanc.

15

Cliquer sur le bouton de changement d'échelle et déformer la sphère.

16

Cliquer "Object/Export" pour exporter l'objet 3D ainsi créé.

17

Dans la boîte de dialogue qui s'ouvre choisir le type "c source file (*.c)", choisir un répertoire et donner un nom de fichier ("sphere.c" par exemple) puis cliquer "Enregistrer"

18

Placer le fichier "sphere.c" (venant d'être généré automatiquement par anim8or) à l'intérieur du répertoire du projet codeblocks en cours. Puis renommer ce fichier en "sphere.h"

19

Ouvrir ce fichier avec un éditeur de texte (notepad++ par exemple) ou avecle bloc-notes et ne conserver que les données décrivant les coordonnées des sommets (mesh01_coords[ ] ) ainsi que les données décrivant les numéros des trois sommets consftituant chacune des faces (mesh01_indices[ ] )

20

Dans ce fichier, remplacer mesh01_coords[ ] et mesh01_indices[ ] (qui sont un peu longs) respectivement par v[ ] (comme vertex) et f [ ] (comme face). Remplacer également float par GLfloat (type de donnée de OpenGL). Une fois ces modifications effectuées, le fichier "sphere.h" doit présenter l'apparence suivante.

21

Le tableau static GLfloat v[] décrit les coordonnées des sommets de l'objet 3D créé avec anim8or et le tableau static int f [ ] décrit les numéros des trois sommets constituant chacune des faces de cet objet 3D.

Ouvrir le projet codeblocks en cours. Cliquer droit sur "main" et choisir l'option "Add files..."

22

Pour pouvoir ajouter le fichier "sphere.h" au projet, sélectionner le fichier "sphere.h" puis cliquer "Ouvrir" dans la boîte de dialogue "Add files to project...".

23

Le fichier "sphere.h" est aussitôt ajouté au projet et son nom apparaît dans la partie headers de la fenêtre de projet de codeblocks.

24

Ajouter la ligne #include "sphere.h" au début du fichier "main.cpp".

25

Compiler et exécuter le programme ("Build/Build and run"). L'objet maillé créé avec anim8or est aussitôt affiché, dans la fenêtre de notre programme et pivote de façon continue autour de la bissectrice de l'axe (x,y).

26

Code source complet du projet codeblocks associé à cet exemple : test-5.7z