Introduction à OpenGL


OpenGL (Open Graphics Library) est une une bibliothèque de fonctions gratuites qui facilite la programmation d'applications 3D écrites en langage C, C++ ou Fortran. Cette bibliothèque, créée par Silicon Graphics, est en concurence avec la bibliothèque Direct3D de Microsoft..

1) Notions de base

1.1) Interface utilisateur

OpenGL ne contient aucune fonction permettant de mettre en oeuvre une interface graphique utilisateur (GUI) ou une saisie de donnée. Pour pouvoir exécuter une application OpenGL dans une fenêtre de programme, l'utilisateur peut procéder de 2 façons différentes :

  • utiliser une bibliothèque additionnelle (GLUT par exemple) qui permet de simplifier et de réduire les lignes de code associées à la définition de l'interface graphique utilisateur des applications 3D;
  • ou bienutiliser directement l'API de Windows et mettre diretement en œuvre une interface graphique utilisateur (cette solution nécessite davantage de lignes de code).

01

1.2) Programmation 3D

Avec OpenGL, le programmeur doit construire et gérer lui-même les objets 3D complexes en utilisant les primitives simples (points, lignes, polygones) disponibles dans OpenGL. Il est à noter que, sauf à utiliser des librairies utilitaires complémentaires, OpenGL n'offre pas de fonctions de haut niveau permettant au programmeur de décrire et de gérer directement des objets 3D complexes.

02

En plus de construire des objets 3D, OpenGL permet également au programmeur de :

  • colorer ces objets ou de les habiller avec des textures en utilisant des images;
  • positionner ces objets dans l'espace;
  • gérer l'éclairage auquel ces objets sont soumis ainsi que les ombres qui en découlent ;
  • positionner dans l'espace le point (camera) à partir duquel ces objets sont observés;
  • fixer l'angle d'orientation de la caméra et le cone parallèlépipédique de vision avec lequel elle observe les objets 3D;
  • afficher à l'écran les les pixels colorés ( ces informations 2D sont obtenues par l'opération de rasterization, réalisée par OpenGL à partir de toutes les données 3D constituant la scène 3D).

03

1.3) Rasterization

La rasterization est le nom donné à l'ensemble des opérations qui permettent de transformer une scène 3D en une image 2D prête à être affichée à l'écran. Durant l'opération de rasterization, OpenGL ne prend pas en compte les données qui correspondent aux parties des objets qui sont masquées par d'autres (élimination des faces cachées). Cette façon de procéder permet à OpenGL d'optimiser ses performances.

L'opération de rasterization peut se décomposer en trois étapes principales :

  • Transformation de projection, à l'aide de matrices, de la géométrie de la scène (3D) dans un plan (2D) perpendiculaire à l'axe de la caméra, en prenant en compte la dimension de l'écran;
  • Elimination des faces cachées des objets ;
  • Calcul des couleurs des pixels de l'image 2D résultante.

1.4) Pipeline 3D

On appelle pipeline 3D la succession de l'ensemble des traitements appliqués par OpenGL à une scène 3D (constituée d’objets 3D décrits par leurs géométries et par leurs matériaux) en vue d’obtenir une image couleur 2D destinée à être affichée à l’écran. Le pipeline 3D d’OpenGL peut être décomposé en quatre principales opérations successives :

  • La transformation du modèle (model transform);
  • La transformation de vue (view transform);
  • La transformation de projection (projection transform);
  • La transformation de fenêtre (viewport transform).

04

Transformation du modèle (model transform)
Chaque objet d'une scène 3D est décrit dans son propre système de coordonnées appelé espace local (local space) ou espace de l'objet (object space) ou espace du modèle (model space). La transformation d'un objet (model transform) consiste à décrire les coordonnées des points de cet objet non pas dans son espace local propre mais dans l'espace où se trouve l'observateur (camera). Cet espace est appelé espace du monde (world space) ou univers). Il constitue l'espace de référence de la scène 3D.

Transformation de vue
La transformation de vue (view transform) d'un objet consiste à décrire dans l'espace de la camera (camera space) les coordonnées des points de cet objet qui ont été précédemment calculées dans l’espace du monde.

Transformation de projection
La camera qui permet d’observer la scène 3D se caractérise par son angle de vision, son aspect ratio (largeur de vue / hauteur de vue) et par ses plans de coupure proche et éloignés. La transformation de projection consiste à ne conserver que les coordonnées des points (décrits dans l’espace de la caméra) qui peuvent être effectivement vues par la caméra compte tenu des particularités de cette camera (clipping). Le clipping (en français "coupure") est l'opération qui supprime toutes les parties d'une scène 3D qui se situent hors du champs de vision de la caméra ( ce champs de vision se limite à un espace en forme de cône rectangulaire tronqué). Enfin, un effet de perspective (perspective transformation) est finalement ajouté à cette transformation.

Transformation de fenêtre
La fenêtre (viewport) est la partie rectangulaire de l’écran (mesurée en pixels) sur laquelle doit être affichée la scène 3D. La transformation de fenêtre (viewport transformation) consiste à décrire dans les coordonnées de cette fenêtre les valeurs des pixels à afficher qui représente la scène 3D telle qu’elle est vue par la caméra. Lors de cette opération, OpenGL ne prend pas en compte les faces cachées des objets constituant la scène puisque les pixels correspondant ne seront pas affichés à l'écran.

1.5) Listes d’affichage

Une liste d'affichage est tout simplement un ensemble d'instructions OpenGL enregistrées en mémoire en vue d'une utilisation ultérieure. En effet, toutes les données qui décrivent la géométrie des objets 3D ou qui décrivent les données des pixels à l'écran peuvent être utilisées de façon immédiate (immediat mode) ou être enregistrées dans une liste d'affichage (display list) en vue d'une utilisation ultérieure.

1.6) Machine à états

OpenGL est une machine a changement détat . Ainsi lorsqu'une instruction place OpenGL dans un état courant donné, en fixant la couleur courante à jaune ou la matrice de transformation à [0] par exemple, OpenGL fonctionne en utilisant ces valeurs tant que celles-ci n'ont pas été modifiées.
La notion d'état courant de OpenGL concerne notamment les transformations, les motifs de lignes et de polygones, les modes de dessin, les positions et les caractéristiques des lumières et les propriétés des objets dessinés. Des fonctions comme glEnable () ou glDisable () permettent au programmeur d'activer ou de désactiver certaines possibilités offertes par OpenGL). Par exemple, glEnable (GL_ALPHA_TEST) permet d'activer la réalisation par OpenGL des tests de transparence des objets. Avec OpenGL, on peut enregistrer sur une pile puis restaurer depuis cette pile les valeurs d'un état à l'aide des instructions de type glPushxxx () et glPopxxx (). On peut également prendre connaissance d’un état courant à l’aide d’instructions de type glGet{xxx).

2) Créer un premier programme

On commence par installer l’environnement de développement codeblocks comme indiqué dans la page Programmation 3D avec codeblocks et OpenGL

1) Lancer codeblocks et créer un un nouveau projet projet (File/New/Project)

05

2) Choisir "Win32 GUI Project"

06

3) Choisir une application de type fenêtre (Frame based)

07

Codeblocks crée automatiquement un fichier source "main.cpp" contenant un code par défaut.

4) Compiler et exécuter le programme (Build/Build and run). Le programme affiche une fenêtre vide

08

5) Cliquer droit "Main" puis "Properties"

09

6) Cliquer "Project’s Build options... "

10

7) Cliquer "Linker setting" puis "Add"

11

8) Ajouter opengl32 puis glu32 afin d'ajouter au projet les librairies "C: /Program Files /CodeBlocks /MinGW /lib /libopengl32.a" et "C: /Program Files /CodeBlocks /MinGW /lib /libglu32.a" qui seront ultérieurement nécessaires, lors de l'opération d'édition des liens du programme.

12

9) Cliquer "OK"

13

Le projet est désormais configuré pour prendre en compte les librairies OpenGL. Cliquer "File/Save Project" afin d'enregistrer ces réglages dans le projet.

Remplacer dans "main.cpp" le code source généré par défaut par codeblocks par le code suivant. Ce code présente l'avantage de bien isoler entre les doublles traits "==========" la partie du listing qui nous intéresse et que l'on va modifier par la suite. Cliquer "Build/Buid and run"

#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;
//========================== =============================
//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();
glRotatef(theta, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(0.0f, 1.0f);
glColor3f(0.0f, 1.0f, 0.0f); glVertex2f(0.87f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex2f(-0.87f, -0.5f);
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,
256, 256, 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);
}

Le programme affiche dans la fenêtre un triangle coloré qui tourne autour de l'axe z. Une copie des fichiers sources du projet codeblocks se trouve ici : test01.7z

14

3) Analyser le code source

La partie active du listing, c’est à dire celle qui permet de créer et d’afficher en boucle un objet 3D dans la fenêtre du programme est la suivante. Elle peut être modifiée à souhait afin de tester les fonctionnalités de OpenGL Les autres parties ne sont pratiquement pas à modifier car elles servent uniquement à créer une fenêtre pour le programme et à initialiser 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();
glRotatef(theta, 0.0f, 0.0f, 1.0f);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(0.0f, 1.0f);
glColor3f(0.0f, 1.0f, 0.0f); glVertex2f(0.87f, -0.5f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex2f(-0.87f, -0.5f);
glEnd();
glPopMatrix();
SwapBuffers(hDC);
theta += 1.0f;
Sleep (1);
}
// ========================== ===========================

glClearColor()

glClearColor(0.0f, 0.0f, 0.0f, 0.0f); fixe en noir la couleur de fond avec laquelle la fenêtre du programme sera affichée.

glClear()

glClear(GL_COLOR_BUFFER_BIT); affiche, dans la fenêtre du programme, la couleur de fond précédemment fixée. Le paramètre GL_COLOR_BUFFER_BIT indique que les buffers sont activés de façon à prendre en compte les couleurs.

glPushMatrix()

La fonction glPushMatrix() sauvegarde sur le sommet de la pile l’état courant de la matrice de transformation. Quand on effectue glPushMatrix() on enregistre sur le sommet de la pile l’état courant de l’ensemble des transformations (translations, rotations, changements d’échelle…) effectuées jusqu’à présent

glRotatef(theta, 0.0f, 0.0f, 1.0f);

glRotatef(theta, 0.0f, 0.0f, 1.0f) multiplie la matrice courante par une matrice de rotation en prenant l'axe z comme axe de rotation.

void glRotatef(
GLfloat angle,
GLfloat x,
GLfloat y,
GLfloat z
);
angle : angle de la rotation en degrés.
x, y, z : précise l'axe de la rotation qui s'effectue autour du vecteur formé par les points(0,0,0) et (x,y,z)

glRotatef est associée à une matrice de rotation. Elle correspond à une rotation d'un angle donné, dans le sens inverse des aiguilles d'une montre, autour d'un axe déterminé par l'origine et le point x,y,z.

15

Cette instruction est utilisée pour modifier la valeur de la matrice de transformation courante. Par exemple, si le mode de la matrice courante (cf glMatrixMode) est GL_MODELVIEW ou GL_PROJECTION, après appel de glRotatef , les objets 3D sont pivotés. La matrice courante est multipliée par cette matrice de rotation et le produit obtenu remplaçe la matrice courante. Ainsi, si M est la matrice courante et R est la matrice de rotation, alors M est remplacé par M x R. On utilise glPushMatrix et glPopMatrix pour pouvoir sauvegarder puis restaurer un ensemble de coordonnées non pivotées.

glBegin

glBegin(GL_TRIANGLES); indique le début d’une liste de sommets d'une primitive ou d’un groupe de primitives. Le paramètre GL_TRIANGLES traite chaque triplet de sommet comme un triangle indépendant.

glColor3f()

glColor3f(1.0f, 0.0f, 0.0f); fixe la couleur rgb courante à rouge.

void glColor3f(
GLfloat red,
GLfloat green,
GLfloat blue
);
red, green, blue : nouvelles valeurs courantes pour le rouge, le vert et le bleu

glVertex2f

glVertex2f() spécifie les coordonnées d'un point (ou sommet).

glVertex2f (0.0f, 1.0f);
spécifie un sommet
void glVertex2f(
GLfloat x,
GLfloat y
);
x,y : spécifie les coordonnées x et y du sommet

glEnd()

glEnd indique la fin d’une liste de sommets d'une primitive ou d’un groupe de primitives

glPopMatrix()

La fonction glPopMatrix() recupère l’état de transformation enregistré sur le sommet de la pile. Quand on effectue glPopMatrix() on restore cet état, ce qui revient à oublier l’ensemble des transformations effectuées depuis le dernier glPushMatrix();
Ces deux instructions encadrent généralement une série d’instructions effectuant des transformations . Par exemple :

glPushMatrix(); // place la matrice courante sur la pile
glTranslatef( dx, dy, dz); // transformation 1
glRotatef(angle,axe); // transformation 2

AffichageObjet;
glPopMatrix(); // Recupère l'ancienne matrice depuis la pile (sans les transformations)

Ainsi, si l’objet considéré est en (0,0,0), on fait glPushMatrix() pour enregistrer la matrice courante, on déplace l’objet en (1,5,7) par exemple, on affiche cet objet, on fait glPopMatrix() pour récupérer la matrice et ainsi on peut directement revenir en (0,0,0) sans avoir à faire un déplacement de (-1,-5,-7).

SwapBuffers(hDC)

La fonction SwapBuffers(hDC) effectue l'échange entre le buffer avant (écran) et le buffer arrière (mémoire) qui contient une image venant dêtre préparée et destinée à être immédiatement affichée à l'écran.

BOOL SwapBuffers( HDC hdc);
hdc désigne l'identificateur du contexte de périphérique (handle device context) mémorisé pour la fenêtre

Sleep()

La fonction Sleep(1) est une fonction C++ qui permet de suspendre l'exécution d'un traitement pendant un délai de 1 milliseconde.

VOID Sleep(DWORD dwMilliseconds);
dwMilliseconds délai en millisecondes pendant lequel l'exécution du traitement est suspendue

4) Modifier le code source

L’ajout d’un second triangle et la suppression de la rotation permet d’obtenir le résultat suivant.

16

L’ajout de deux points jaunes de taille 10 pixels permet d’obtenir le résultat suivant.

17

On peut aussi dessiner un cercle qui tourne autour de l’axe des x

18

La fonction glOrtho() multiplie la matrice de transformation courante par une matrice de projection orthogonale Une projection orthogonale (Orthographic projection) est une transformation qui permet de présenter un objet 3D dans un plan 2D

void glOrtho(
GLdouble left,
GLdouble right,
GLdouble bottom,
GLdouble top,
GLdouble zNear,
GLdouble zFar
);

left : coordonnée du plan de coupure vertical gauche
right : coordonnée du plan de coupure vertical droit
bottom : coordonnée du plan de coupure horizontal bas
top : coordonnée du plan de coupure horizontal haut
zNear : distance du plan de coupure de profondeur le plus proche (cette distance est négative si ce plan est derrière la caméra)
zFar : distance du plan de coupure de profondeur le plus éloigné (cette distance est négative si ce plan est derrière la caméra)

19

La modification des paramètres de glOrtho() modifie le rendu à l’écran.

21