VII. Découverte d'OpenGL▲
VII-A. Version ciblée et extensions▲
Le module ModOpenGL_1_1 contient les déclarations de la version 1.1 d'OpenGL.
Il existe des versions plus récentes d'OpenGL et également des extensions.
Nous nous contenterons, pour ce tutoriel de découverte, de la version 1.1 qui permet déjà de réaliser de nombreuses opérations 3D.
Les fonctions des versions supérieures à la version 1.1 sont considérées comme des extensions, nous ne les utiliserons pas au cours de ce premier tutoriel.
De nombreuses fonctions utilisées dans cet article sont dépréciées dans la version 3 d'OpenGL.
Il est cependant toujours possible de les utiliser, toutes les cartes graphiques n'offrent pas encore à ce jour de driver compatible OpenGL 3.
Vous pouvez consulter les spécifications OpenGL 3 pour une liste des fonctions dépréciées.
Pour l'utilisation des extensions, consultez le tutoriel suivant : Les extensions OpenGL en VBA et VB6
VII-B. La programmation par états▲
La programmation en OpenGL1 est basée sur des états.
C'est-à-dire que l'on définit un état (une couleur, une épaisseur de trait…), et il est alors utilisé par toutes les opérations de dessin qui suivent.
Par exemple, si on définit la couleur rouge, tous les points dessinés ensuite seront de couleur rouge tant qu'on ne fait pas appel à une autre couleur.
VII-C. Les procédures et constantes▲
Les fonctions et constantes OpenGL commencent par le préfixe gl.
Elles sont déclarées dans le module ModOpenGL_1_1.
Celles de Glu commencent par le préfixe glu.
Les fonctions préfixées par wgl sont spécifiques à Windows.
Ces fonctions sont déclarées dans le module ModOpenGLTools.
Les fonctions et constantes freeGLUT commencent par le préfixe glut.
Elles sont toutes déclarées dans le module ModOpenGLFreeGlut.
Quelques fonctions gdi32 sont également utiles pour créer une fenêtre et un contexte OpenGL sans freeGLUT.
Ces fonctions sont déclarées dans le module ModOpenGLTools.
VII-D. Les types de données▲
Le suffixe des procédures est fonction du type de données attendu.
suffixe | type de données | type de données VB |
---|---|---|
b | Byte | Byte |
s | Short | Integer |
i | Integer | Long |
f | Float | Single |
d | Double | Double |
ub | Unassigned Byte | Byte |
us | Unassigned Short | Integer |
ui | Unassigned Integer | Long |
On peut également trouver un suffixe v qui désigne un paramètre attendu sous forme de tableau.
Par exemple :
- glNormal3d attend trois paramètres de type Double ;
- glNormal3i attend trois paramètres de type Long ;
- glNormal3iv attend un paramètre de type tableau de Long.
VII-E. Les buffers▲
OpenGL utilise plusieurs Tampons (ou Buffers).
Ces buffers contiennent des informations relatives à la scène que nous dessinons.
VII-E-1. Le tampon de couleurs▲
Également appelé tampon chromatique, il contient la couleur de chaque pixel de la scène.
Le paramètre GLUT_RGBA que nous avons utilisé pour initialiser freeGLUT demande l'utilisation d'un tampon de couleurs contenant quatre composantes par pixel : R pour rouge, G pour vert, B pour bleu, et A pour le canal Alpha qui détermine la transparence.
C'est sur ce tampon que nous dessinons.
Ce tampon est vidé en utilisant le paramètre GL_COLOR_BUFFER_BIT de la fonction glClear.
VII-E-2. Le tampon de profondeur▲
Appelé également Z-Buffer, ce tampon contient la distance des pixels par rapport à l'observateur.
La couleur utilisée dans le tampon de couleurs est la couleur qui a la plus faible valeur dans le tampon de profondeur.
En cas de transparence, c'est un peu plus complexe : la couleur affichée est définie par le mode de mélange de couleurs choisi.
Ceci est géré par OpenGL :
- si on a défini l'utilisation de ce tampon par l'utilisation de GLUT_DEPTH lors de l'initialisation du mode d'affichage de freeGLUT ;
- et si on active les tests de profondeur avec l'appel à la fonction : glEnable GL_DEPTH_TEST.
Ce tampon est vidé en utilisant le paramètre GL_DEPTH_BUFFER_BIT de la fonction glClear.
VII-E-3. Le tampon d'accumulation▲
Ce tampon peut cumuler plusieurs images.
On ne dessine pas directement sur ce tampon.
On transfère les pixels depuis ou vers le tampon de couleurs.
Une utilisation courante de ce tampon est l'anti-aliasing (lissage), obtenu en cumulant plusieurs images légèrement décalées.
Pour utiliser ce tampon il faut, à l'initialisation du mode d'affichage de freeGLUT, préciser le paramètre GLUT_ACCUM.
Ce tampon est vidé en utilisant le paramètre GL_ACCUM_BUFFER_BIT de la fonction glClear.
VII-E-4. Le tampon pochoir▲
Plus souvent appelé tampon stencil.
Ce tampon permet de restreindre l'affichage à une zone réduite de l'image.
Les utilisations courantes de ce tampon sont :
- les effets d'ombre ;
- les effets miroir ;
- l'affichage à travers un trou (serrure, fenêtre…).
Pour utiliser ce tampon il faut, à l'initialisation du mode d'affichage de freeGLUT, préciser le paramètre GLUT_STENCIL.
Ce tampon est vidé en utilisant le paramètre GL_STENCIL_BUFFER_BIT de la fonction glClear.
VII-E-5. Le double tampon▲
Vous avez sans doute remarqué l'utilisation du mode d'affichage GL_DOUBLE pour notre première fenêtre.
Ce paramètre définit l'utilisation du double tampon (ou double buffer).
C'est le tampon de couleurs qui est doublé.
C'est un paramètre très important, car cela permet de réduire les scintillements à l'affichage.
En effet, au lieu de directement dessiner un par un les éléments sur la fenêtre, on va dessiner sur un tampon caché puis intervertir les deux tampons une fois tout le dessin réalisé.
Le changement de tampon se fait à l'aide de la fonction gluSwapBuffers.
L'affichage de la fenêtre est mis à jour au moment de l'appel à cette fonction.
VII-F. Les unités▲
L'unité n'est pas le cm, le mètre ou autre…
Tout est relatif, à vous de définir l'unité de votre scène.
Si par exemple vos créez une scène sur laquelle vous souhaitez déplacer un personnage, vous pouvez arbitrairement décider qu'une valeur de 1 correspondra à 1 mètre.
Ainsi se déplacer de 0,5 correspondra à un déplacement de 50 cm.
Pour créer un cube de 2 mètres de côté, vous créerez un cube de 2 de côté.
Toute la scène sera alors à l'échelle.
Il suffit de choisir une unité en fonction de la scène dessinée pour se simplifier les calculs.
Si vous créez un monde à l'échelle d'un insecte, vous prendrez peut-être plutôt 1cm pour une unité OpenGL.
VII-G. Les vertices▲
Un vertice est un point. Vous lirez également souvent le mot anglais vertex.
Les vertices sont la base du dessin en 3D avec OpenGL
Ces vertices sont regroupés en primitives géométriques (point, triangle, rectangle…).
VII-H. Les primitives géométriques▲
Pour dessiner une primitive géométrique, il faut d'abord appeler la fonction glBegin en précisant la primitive souhaitée.
Nom de la primitive | Description | Exemple |
---|---|---|
GL_POINTS | Points : chaque vertice est traité comme un point indépendant. | |
GL_LINES | Lignes : chaque paire de vertices forme un segment. | |
GL_LINE_STRIP | Lignes connectées : chaque vertice (hormis le premier et le dernier) est utilisé deux fois. Les vertices N et N+1 forment un segment. |
|
GL_LINE_LOOP | Lignes connectées en boucle: même chose que GL_LINE_STRIP. De plus le dernier et le premier vertice forment un segment qui referme le polygone. |
|
GL_TRIANGLES | Triangles : chaque triplet de vertices forme un triangle. | |
GL_TRIANGLE_STRIP | Triangles connectés : - pour un nombre de vertices pair : un triangle est formé des vertices N, N+1 et N+2 ; - pour un nombre de vertices impair : un triangle est formé des vertices N+1, N et N+2. |
|
GL_TRIANGLE_FAN | Triangles connectés : chaque triangle est formé du vertice 1 et des vertices N+1 et N+2. |
|
GL_QUADS | Rectangles : chaque groupe de quatre vertices forme un rectangle. |
|
GL_QUAD_STRIP | Rectangles : chaque paire de vertices (hormis la première paire) forme un rectangle avec la paire de vertices précédente. |
|
GL_POLYGON | Polygone : chaque vertice définit un point du polygone. Le polygone doit être convexe. |
Chaque vertice est défini par ses coordonnées à l'aide d'une fonction glVertex*.
La primitive doit se terminer par un appel à glEnd.
On peut, entre glBegin et glEnd, cumuler plusieurs primitives géométriques de même type.
Testons l'affichage d'un triangle :
Après le vidage des buffers (glClear) et avant l'échange de buffer (glutSwapBuffers), ajoutons le code de dessin d'un triangle.
Pour bien structurer le programme, nous allons ajouter une procédure Render dans laquelle nous déplaçons le code de vidage des tampons.
Public
Sub
CallBackDraw
(
)
' Appel de la fonction de rendu
Call
Render
' Échange les buffers
glutSwapBuffers
End
Sub
Tout le code de dessin est regroupé dans la fonction Render, qui pourra ainsi être appelée de divers endroits du code si besoin.
' Fonction de rendu
Public
Sub
Render
(
)
' Vide les buffers couleur et profondeur
glClear GL_COLOR_BUFFER_BIT Or
GL_DEPTH_BUFFER_BIT
' Début de la primitive
glBegin GL_TRIANGLES
' Ajoute les trois sommets
glVertex2d 0
, 0
glVertex2d 1
, 0
glVertex2d 1
, 1
' Fin de la primitive
glEnd
End
Sub
Exécutez la fonction FonctionOpenGL pour visualiser le triangle.
Vous l'avez sans doute remarqué, nous avons utilisé des coordonnées à deux dimensions pour dessiner notre triangle.
Malgré tout, on a bien un affichage en trois dimensions, le triangle a été placé avec une profondeur de 0 sur l'axe Z.
glVertex2d 1, 1 est équivalent à glVertex3d 1, 1,0.
VII-I. Les couleurs des vertices▲
Le triangle ainsi dessiné est blanc. C'est la couleur par défaut.
On peut affecter une couleur différente à chaque point du triangle.
Il suffit d'appeler une fonction glColor*.
On ne gère pas de transparence, on peut donc utiliser une fonction à trois paramètres.
Ajoutez juste après l'appel à glBegin la fonction de changement de couleur :
' Début de la primitive
glBegin GL_TRIANGLES
' Couleur jaune
glColor3d 1
, 1
, 0
[...
]
Un seul appel à la fonction glColor* suffit à colorer tous les vertices.
En fait la couleur jaune reste la couleur de tous les vertices qui seront ensuite dessinés par le programme.
Si on souhaite réinitialiser la couleur, il faut redéfinir la valeur par défaut (couleur blanche) :
' Couleur blanche par défaut
glColor4d 1
, 1
, 1
,1
En VB on utilise souvent un entier long pour définir la couleur, notamment les constantes vbWhite, vbRed, vbGreen…
Pour simplifier le choix des couleurs, j'ai ajouté au module ModOpengGLTools les fonctions glColor3VB et glColor4VB.
On peut leur donner en argument une couleur de type entier long.
Utilisons ces fonctions pour affecter à chaque point une couleur différente :
Public
Sub
Render
(
)
' Vide les buffers couleur et profondeur
glClear GL_COLOR_BUFFER_BIT Or
GL_DEPTH_BUFFER_BIT
' Début de la primitive
glBegin GL_TRIANGLES
' Ajoute les trois sommets
glColor3VB vbYellow
' Jaune
glVertex2d 0
, 0
glColor3VB vbBlue
' Bleu
glVertex2d 1
, 0
glColor3VB vbRed
' Rouge
glVertex2d 1
, 1
' Fin de la primitive
glEnd
End
Sub
Nous remarquons que les couleurs s'appliquent en dégradé :
VII-J. Orientation des faces▲
Chaque polygone a deux faces : une face avant et une face arrière.
Ces faces sont définies par l'ordre des vertices du polygone.
La face avant est, par défaut, celle qui est face à l'observateur lorsque les points sont définis dans le sens inverse des aiguilles d'une montre.
Appelez glFrontFace GL_CW pour inverser l'ordre des faces.
glFrontFace GL_CCW revient au comportement par défaut.
Notre triangle a donc été dessiné avec une face avant vers nous :
Il est possible de masquer certaines faces, c'est ce qu'on appelle le culling.
Masquer les faces arrière des polygones peut accélérer le rendu.
Il faut cependant bien s'assurer que les faces soient créées avec la bonne orientation pour ne pas voir disparaître des polygones qui devraient être visibles.
' D'abord activer le culling
glEnable GL_CULL_FACE
' Puis préciser les faces masquées
glCullFace GL_BACK
Ce code masque la face arrière (qui n'est pas visible sur notre dessin).
Si on utilise glCullFace GL_FRONT, le triangle disparaît.
VII-K. Les normales▲
On peut pour chaque point définir une normale avec une fonction glNormal*.
La normale est généralement définie perpendiculairement à la surface dessinée.
Pour un triangle, la normale est définie perpendiculairement à la face avant, dans la direction allant de la face arrière à la face avant.
Lorsqu'on utilise de la lumière, les normales deviennent importantes, car elles déterminent l'éclairage du polygone.
On verra l'importance des normales dans le chapitre sur la lumière.
VII-L. Les modes d'affichage des polygones▲
Nous n'avons défini que des points, OpenGL a dessiné des polygones pleins.
C'est le mode d'affichage par défaut.
Ce mode d'affichage peut être modifié en utilisant la fonction glPolygonMode.
Inutile de redéfinir ce mode d'affichage à chaque fois que l'on dessine la scène, c'est un état qui peut être défini une seule fois à l'initialisation.
Ajoutons une procédure InitScene que nous appelons juste avant le lancement de la boucle glutMainLoop.
[...
]
' Appel de la fonction d'initialisation
InitScene
' Boucle principale
glutMainLoop
End
Sub
VII-L-1. Affichage des points▲
Dans la fonction d'initialisation, définissez le mode d'affichage GL_POINT.
Ajoutez un appel à la fonction glPointSize pour agrandir la taille des points à 10 pixels.
Par défaut les points ne font qu'un pixel de large.
' Fonction d'initialisation
Public
Sub
InitScene
(
)
' Mode d'affichage = Points
glPolygonMode GL_FRONT_AND_BACK, GL_POINT
' Taille des points
glPointSize 10
End
Sub
Les points sont représentés par des carrés de 10 pixels de côté.
Pour lisser les points, activer l'anti-aliasing des points.
' Fonction d'initialisation
Public
Sub
InitScene
(
)
' Mode d'affichage = Points
glPolygonMode GL_FRONT_AND_BACK, GL_POINT
' Taille des points
glPointSize 10
' Lissage des points
glEnable GL_POINT_SMOOTH
End
Sub
Les points sont lissés :
En fonction des capacités de la carte graphique, le lissage peut ne pas fonctionner.
VII-L-2. Affichage des lignes▲
Dans la fonction d'initialisation, définissez le mode d'affichage GL_LINE.
Ajoutez un appel à la fonction glLineWidth pour agrandir la taille des lignes à 5 pixels.
Par défaut les lignes ne font qu'un pixel de large.
' Mode d'affichage = Lignes
glPolygonMode GL_FRONT_AND_BACK, GL_LINE
' Taille des lignes
glLineWidth 5
Les polygones sont représentés par des lignes de 5 pixels de large :
On peut également afficher des lignes en pointillés.
Il faut utiliser la fonction glLineStipple, en ayant pris soin auparavant d'activer les motifs de lignes.
' Mode d'affichage = Lignes
glPolygonMode GL_FRONT_AND_BACK, GL_LINE
' Taille des lignes
glLineWidth 5
' Active les motifs de lignes
glEnable GL_LINE_STIPPLE
' Définit le motif pointillé
glLineStipple 5
, 43690
La fonction glLineStipple demande deux arguments :
- le premier est le nombre de fois que chaque partie du motif se répète ;
- le deuxième est un entier long qui représente le motif (16 bits égaux à 0 ou 1).
Pour définir le nombre représentant le motif (43690 dans l'exemple), il faut convertir un binaire sur 16 bits en entier.
Le motif pointillé en binaire est : 1010101010101010.
Converti en base décimale, cela donne 43690.
Pour convertir un motif depuis une chaîne de caractères, on peut écrire une petite fonction :
Function
BinToDec
(
pBin As
String
) As
Long
Dim
lDec As
Long
Dim
lCpt As
Long
lDec =
0
For
lCpt =
0
To
Len
(
pBin) -
1
lDec =
lDec +
Mid
(
pBin, Len
(
pBin) -
lCpt, 1
) *
2
^
lCpt
Next
BinToDec =
lDec
End
Function
Puis pour définir le motif :
' Définit le motif pointillé
glLineStipple 5
, BinToDec
(
"1010101010101010"
)
Le nombre 5 précise que chaque bit du motif est répété cinq fois.
Ce qui correspond à un motif : 111110000011111000001111100000…
Où chaque chiffre correspond à un pixel.
VII-L-3. Remplissage des polygones▲
C'est le comportement par défaut : pour le réactiver, utilisez le mode d'affichage GL_FILL.
' Mode d'affichage = remplissage
glPolygonMode GL_FRONT_AND_BACK, GL_FILL