VIII. Mise à jour régulière de l'image : le FrameRate▲
Pour l'instant, la fenêtre n'est mise à jour que lorsqu'il y en a besoin.
C'est-à-dire lorsque la fenêtre est masquée puis réaffichée, ou lorsqu'elle est redimensionnée...
Souvent nous souhaitons afficher l'image à une certaine fréquence car elle est en mouvement, pour un jeu par exemple.
C'est ce que nous allons faire.
Glut nous met à disposition une fonction de rappel "Idle".
Cette fonction est appelée sans cesse par la boucle de freeglut lorsque l'application est libre, c'est-à-dire qu'aucun événement n'est en attente.
Nous allons utiliser cette fonction.
Ajoutez une procédure CallBackIdle.
Public
Sub
CallBackIdle
(
)
End
Sub
Et définissez cette procédure dans freeglut.
Avant ou après la définition de la fonction d'affichage (glutDisplayFunc) :
'
Fonction
d'attente
glutIdleFunc AddressOf CallBackIdle
On peut grâce à la fonction glutGet(GLUT_ELAPSED_TIME) lire le temps écoulé en millisecondes depuis l'initialisation de freeglut (fonction glutInit).
On va définir une variable qui sera le temps écoulé qui déterminera la prochaine mise à jour : gWaitTime.
'
Temps
à
attendre
pour
le
prochaine
affichage
Private
gWaitTime As
Long
Ce temps est initialisé dans la fonction InitScene :
Public
Sub
InitScene
(
)
'
Initialisation
du
temps
déclencheur
de
l'affichage
gWaitTime =
glutGet
(
GLUT_ELAPSED_TIME)
End
Sub
Et enfin dans la fonction d'attente CallBackIdle, on vérifie si on a atteint ce temps.
Si c'est le cas, on déclenche un affichage avec glutPostRedisplay et on décale le temps d'attente.
Public
Sub
CallBackIdle
(
)
Dim
lTimer As
Long
'
Temps
présent
lTimer =
glutGet
(
GLUT_ELAPSED_TIME)
'
Si
temps
présent
>=
au
temps
attendu
If
lTimer >=
gWaitTime Then
'
Rrafraichir
l'affichage
glutPostRedisplay
'
n
affichages
par
seconde
gWaitTime =
lTimer +
(
1000
/
50
)
End
If
End
Sub
1000 / 50 correspond à une attente de 1000/50 = 20 millisecondes.
On obtient alors un affichage de 50 images par seconde.
Pour s'assurer que le frame rate correspond à ce qu'on attend, et pour éventuellement tester les performances de notre programme,
nous allons afficher le frame rate réel dans la barre de titre de la fenêtre.
Ajoutons deux variables pour le calcul du frame rate :
'
Temps
écoulé
pour
calcul
du
frame
rate
Private
gElapsedTime As
Long
'
Nombre
d'affichage
pour
calcul
du
frame
rate
Private
gDisplayCount As
Long
Puis on calcul le frame rate = nombre d'images affichées / temps écoulé en seconde.
A la fin de la procédure CallBackDraw :
'
Calcul
et
affichage
du
frame
rate
gDisplayCount =
gDisplayCount +
1
If
gDisplayCount =
100
Then
glutSetWindowTitle "
Frame
rate
:
"
&
format
(
gDisplayCount /
(
glutGet
(
GLUT_ELAPSED_TIME) -
gElapsedTime) *
1000
, "
0,0
"
)
gDisplayCount =
0
gElapsedTime =
glutGet
(
GLUT_ELAPSED_TIME)
End
If
On ne fait cette opération que toutes les 100 images par exemple.
Le nombre d'images par seconde s'affiche maintenant dans la barre de titre :
IX. Mise en scène▲
IX-A. Les axes▲
Il y a bien entendu 3 axes pour dessiner une scène en 3 dimensions, nommés X, Y et Z.
Par défaut, l'axe X est l'axe horizontal, l'axe Y est l'axe vertical, et l'axe Z est l'axe de profondeur.
L'origine du repère est au centre de la scène.
Les bords droit et haut correspondent à une valeur de 1.
Les bords gauche et bas correspondent à une valeur de -1.
L'axe de profondeur Z est orienté de manière à ce que les valeurs positives partent vers l'avant de l'écran.
Pour mieux visualiser ce que nous réalisons, nous allons créer un cube à la place du triangle.
Pour dessiner un cube, on peut créer les 6 faces comme 6 quads.
On donne à chaque face du cube une couleur différente.
Remplacez le code de dessin du triangle par celui du cube :
'
Début
de
la
primitive
pour
le
cube
glBegin GL_QUADS
'
Face
du
haut
=
vert
Call
glColor3d
(
0
, 1
, 0
)
Call
glVertex3d
(
1
, 1
, -
1
)
Call
glVertex3d
(
-
1
, 1
, -
1
)
Call
glVertex3d
(
-
1
, 1
, 1
)
Call
glVertex3d
(
1
, 1
, 1
)
'
Face
du
bas
=
orange
Call
glColor3d
(
1
, 0
.
5
, 0
)
Call
glVertex3f
(
-
1
, -
1
, -
1
)
Call
glVertex3f
(
1
, -
1
, -
1
)
Call
glVertex3f
(
1
, -
1
, 1
)
Call
glVertex3f
(
-
1
, -
1
, 1
)
'
Face
de
derrière
=
rouge
Call
glColor3d
(
1
, 0
, 0
)
Call
glVertex3f
(
1
, -
1
, -
1
)
Call
glVertex3f
(
-
1
, -
1
, -
1
)
Call
glVertex3f
(
-
1
, 1
, -
1
)
Call
glVertex3f
(
1
, 1
, -
1
)
'
Face
de
devant
=
jaune
Call
glColor3d
(
1
, 1
, 0
)
Call
glVertex3f
(
-
1
, -
1
, 1
)
Call
glVertex3f
(
1
, -
1
, 1
)
Call
glVertex3f
(
1
, 1
, 1
)
Call
glVertex3f
(
-
1
, 1
, 1
)
'
Face
de
gauche
=
violet
Call
glColor3d
(
1
, 0
, 1
)
Call
glVertex3f
(
-
1
, 1
, 1
)
Call
glVertex3f
(
-
1
, 1
, -
1
)
Call
glVertex3f
(
-
1
, -
1
, -
1
)
Call
glVertex3f
(
-
1
, -
1
, 1
)
'
Face
de
droite
=
bleu
Call
glColor3d
(
0
, 0
, 1
)
Call
glVertex3f
(
1
, 1
, -
1
)
Call
glVertex3f
(
1
, 1
, 1
)
Call
glVertex3f
(
1
, -
1
, 1
)
Call
glVertex3f
(
1
, -
1
, -
1
)
glEnd
Evitez l'utilisation de GL_QUADS si vous recherchez la performance.
Utiliser des triangles est plus rapide, car les cartes graphiques travaillent à base de triangles.
Exécutez la fonction FonctionOpenGL.
On voit alors la face avant jaune qui remplit la fenêtre.
IX-B. Les matrices de transformations▲
Il existe 3 matrices dans OpenGL.
Nous passons d'une matrice à une autre avec la fonction glMatrixMode.
- GL_PROJECTION est la matrice de projection : elle détermine la position de la caméra et la manière dont est projeté l'environnement 3D sur la fenêtre 2D.
- GL_MODELVIEW est la matrice de modélisation-visualisation : elle permet de transformer les vertices en leur appliquant une rotation, translation, mise à l'échelle.
- GL_TEXTURE est la matrice de texture : elle permet d'appliquer des transformations aux textures.
Par défaut la matrice active est la matrice de modélisation-visualisation.
Notez que les transformations appliquées à une matrice se cumulent.
Il est nécessaire lorsqu'on effectue une transformation :
- soit de charger la matrice identité (la matrice par défaut qui ne correspond à aucune transformation) avec la fonction glLoadIdentity.
- soit de sauvegarder et restaurer la matrice avec les fonctions glPushMatrix et glPopMatrix.
Sinon chaque transformation se cumule avec la précédente.
Nous allons détailler ensuite l'utilisation de ces fonctions.
Pour modifier ces matrices, le plus simple est d'utiliser les fonctions :
- glRotate* pour la rotation.
- glTranslate* pour la translation.
- glScale* pour la mise à l'échelle.
La fonction glLoadIdentity réinitialise la matrice.
Lorsqu'on souhaite appliquer une transformation à un objet uniquement, on utilise les fonctions :
- glPushMatrix pour sauvegarder la matrice courante.
- glPopMatrix pour la restaurer.
IX-B-1. Les projections▲
Il y a deux types de projections disponibles :
- La projection en perspective : utilisée pour le rendu d'une scène en 3 dimensions.
Dans cette projection, les objets sont affichés tels qu'on les verrait dans le monde réel, les objets plus lointains apparaissant plus petits.
- La projection orthonormée : souvent utilisée pour un rendu 2D.
Dans cette projection, les objets ont la même taille quelque soit leur distance par rapport au point d'observation.
Attention : les paramètres de profondeur des projections ne sont pas définis sur l'axe Z mais sur l'axe de visualisation de la scène.
C'est à dire l'axe qui passe par le point d'observation (caméra) et le centre de la scène.
Nous définirons cet axe juste après.
IX-B-1-a. La projection en perspective▲
Pour définir cette projection, on a à notre disposition la fonction Opengl glFrustum.
Nous disposons également de sa petite soeur gluPerspective de la librairie glu.
gluPerspective appelle la fonction glFrustum.
On peut donc réaliser les mêmes projections avec ces deux fonctions.
Perspective avec glFrustum :
Perspective avec gluPerspective :
Le volume visible de la scène est une pyramide tronquée délimitée par les deux plans de couleur bleu clair.
IX-B-1-b. La projection orthonormée▲
IX-C. La caméra▲
Il nous reste un point important : l'observateur de la scène.
La fonction gluLookAt nous permet de le définir.
La fonction gluLookAt demande 9 paramètres :
- 3 pour la position de la caméra : eyex, eyey et eysez.
- 3 pour le point de visée : centerx, centery et centerz.
- et 3 pour l'orientation de l'axe vertical : upx, upy et upz.
Par défaut la caméra est située à une distance de 1 sur l'axe Z et on regarde vers l'orgine (de coordonnées 0,0,0).
IX-D. Définition de la projection et de la caméra▲
Nous allons redéfinir la projection et la position de la caméra pour prendre du recul et voir le cube en entier.
Au début de la fonction de rendu Render :
'
Passage
en
matrice
de
projection
glMatrixMode GL_PROJECTION
'
Initialisation
de
la
matrice
glLoadIdentity
'
Définition
de
la
perspective
gluPerspective 45
, 1
, 0
.
1
, 100
'
Passage
en
matrice
de
modélisation-visualisation
glMatrixMode GL_MODELVIEW
'
Initialisation
de
la
matrice
glLoadIdentity
'
Position
de
la
caméra
gluLookAt 0
, 0
, 10
, 0
, 0
, 0
, 0
, 1
, 0
Pour la projection :
- on a défini un angle de visualisation de 45°
- l'aspect est de 1, c'est-à-dire que les plans utilisés pour la projection sont des carrés (sinon le cube serait aplati).
- les plans sont placés à 0.1 et 100, c'est-à-dire qu'on verra les points situés entre 0.1 et 100 par rapport à la position de la caméra.
Evitez une valeur de zNear égale à 0 : placer le premier plan sur la caméra mène souvent à un affichage incorrect.
Pour la caméra :
- on se positionne à 10 sur l'axe des Z pour prendre un peu de recul (0,10,0).
- on regarde vers l'origine (0,0,0).
- l'axe vertical est l'axe Y (0,1,0).
Voici notre cube :
On voit les faces de côté, ce qui n'est pas normal.
Faisons effectuer une rotation au cube pour y voir plus clair.
Avant le glBegin GL_QUADS, ajoutez :
'
Sauvegarde
la
matrice
glPushMatrix
'
Rotation
du
cube
glRotated 30
, 1
, 1
, 1
Et après le glEnd, ajoutez :
'
Restaure
la
matrice
glPopMatrix
Notez que l'appel aux fonctions glPushMatrix et glPopMatrix ne sont pas indispensables ici, on n'affiche qu'un objet.
Il le deviendrait si on effectuait, après le cube, le dessin d'autres objets.
La fonction glRotated tourne notre cube de 30° autour d'un axe dont :
- les coordonnées du premier point sont celles de l'origine (0,0,0).
- les coordonnées du deuxième point sont (1,1,1).
En fait la rotation du cube se cumule avec la matrice de modélisation-visualisation définie par gluLookAt.
Exectuez la fonction FonctionOpenGL : vous voyez le cube légérement tourné :
Par la même occasion, on constate des problèmes d'affichage de polygones.
On ne devrait pas voir la face arrière rouge, ni la face droite bleue.
En fait les polygones ont été dessinés dans l'ordre qu'on a donné lors du dessin du cube, sans se soucier de leur profondeur.
Pour corriger cela, il faut activer la fonction de test de profondeur.
Dans la fonction InitScene, ajoutez :
'
Tests
de
profondeur
glEnable GL_DEPTH_TEST
glDepthFunc GL_LEQUAL
glEnable GL_DEPTH_TEST active les tests de profondeur.
glDepthFunc définit le type de test à effectuer : GL_LEQUAL étant souvent un bon compromis qualité/vitesse.
Testons à nouveau l'affichage :
L'affichage du cube est maintenant correct.
IX-E. Cadrer l'image dans la fenêtre▲
Par défaut, l'image occupe toute la fenêtre.
Vous pouvez modifier la position et la taille de l'image avec la fonction glViewport.
La fonction de rappel glutReshapeFunc est utile car elle nous donne la taille de la fenêtre.
On peut également lire la taille de la fenêtre avec glutGet(GLUT_WINDOW_WIDTH) et glutGet(GLUT_WINDOW_HEIGHT).
Il est alors possible de dessiner plusieurs scènes dans la même fenêtre.
Il suffit d'appeler glViewport entre chaque scène pour la positionner.
Attention : il ne faut par contre qu'un seul appel à glClear pour l'ensemble des scènes.
X. Gestion des entrées clavier et souris : Rotation et Zoom de la scène▲
Nous allons utiliser :
- la molette de la souris pour zoomer ou dézoomer.
- les touches fléchées du clavier pour tourner autour du cube (ce sont des touches "spéciales").
Définissons dans la fonction FonctionOpenGL les fonctions de rappel souris et clavier.
Au même endroit que les autres définitions de rappel (glutDisplayFunc et glutIdleFunc) :
'
Fonctions
de
rappel
clavier
glutSpecialFunc AddressOf CallBackSpecial
'
Fonction
de
rappel
molette
de
souris
glutMouseWheelFunc AddressOf CallBackMouseWheel
Définissons également des variables pour conserver les valeurs de zoom et de rotation.
'
RotationX
Private
gRotateX As
Double
'
RotationY
Private
gRotateY As
Double
'
Zoom
Private
gZoom As
Double
On tournera :
- autour de l'axe Y avec les flèches droite et gauche.
- et autour de l'axe X avec les flèches haut et bas.
Initialisons ces variables dans la procédure InitScene :
'
Initialisation
des
variables
de
visualisation
gRotateX =
0
gRotateY =
0
gZoom =
10
Ensuite on gère les fonctions de rappel pour mettre à jour ces variables en fonctions des actions effectuées :
Public
Sub
CallBackSpecial
(
ByVal
key As
Long
, ByVal
x As
Long
, ByVal
y As
Long
)
Select
Case
key
Case
GLUT_KEY_LEFT
gRotateY =
gRotateY -
10
Case
GLUT_KEY_RIGHT
gRotateY =
gRotateY +
10
Case
GLUT_KEY_UP
gRotateX =
gRotateX -
10
Case
GLUT_KEY_DOWN
gRotateX =
gRotateX +
10
End
Select
End
Sub
Public
Sub
CallBackMouseWheel
(
ByVal
wheel As
Long
, ByVal
direction As
Long
, ByVal
x As
Long
, ByVal
y As
Long
)
gZoom =
gZoom +
direction
End
Sub
Il faut enfin utiliser ces variables pour mettre à jour la caméra :
- Pour le zoom c'est facile : il suffit de remplacer le 10 par gZoom dans l'appel à gluLookat.
Ainsi on place la caméra à une distance qui dépend de la valeur de gZoom.
- Pour la rotation, on va utiliser la fonction de transformation gRotated.
Appelée juste après le gluLookAt, elle va ajouter une transformation de rotation à la matrice de modélisation-visualisation.
'
Position
de
la
caméra
gluLookAt 0
, 0
, gZoom, 0
, 0
, 0
, 0
, 1
, 0
glRotated gRotateX, 1
, 0
, 0
glRotated gRotateY, 0
, 1
, 0
Je vous invite à utiliser la molette de la souris et les touches fléchée pour visualiser la scène sous différents angles.
XI. Rappel du code complet▲
Au cas où je n'ai pas été toujours très clair, je fais ici un rappel du contenu du module VB à ce stade du tutoriel :
Option
Explicit
'
Temps
à
attendre
pour
le
prochaine
affichage
Private
gWaitTime As
Long
'
Temps
écoulé
pour
calcul
du
frame
rate
Private
gElapsedTime As
Long
'
Nombre
d'affichage
pour
calcul
du
frame
rate
Private
gDisplayCount As
Long
'
RotationX
Private
gRotateX As
Double
'
RotationY
Private
gRotateY As
Double
'
Zoom
Private
gZoom As
Double
'
Fonction
principale
Function
FonctionOpenGL
(
)
'
Chargement
de
freeglut
If
LoadLibrary
(
CurrentProject.
Path
&
"
\freeglut.dll
"
) =
0
Then
MsgBox
"
Impossible
de
charger
la
librairie
freeglut
"
Exit
Function
End
If
'
Initialisation
de
la
librairie
glutInit 0
&
, "
"
'
Initialisation
du
mode
d'affichage
glutInitDisplayMode GLUT_RGBA Or
GLUT_DOUBLE Or
GLUT_DEPTH
'
Création
d'une
fenêtre
glutCreateWindow "
Tutoriel
fenêtre
GLUT
"
'
Définition
de
l'option
de
sortie
de
boucle
glutSetOption GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS
'
Fonction
d'affichage
glutDisplayFunc AddressOf CallBackDraw
'
Fonction
d'attente
glutIdleFunc AddressOf CallBackIdle
'
Fonctions
de
rappel
clavier
glutSpecialFunc AddressOf CallBackSpecial
'
Fonction
de
rappel
molette
de
souris
glutMouseWheelFunc AddressOf CallBackMouseWheel
'
Appel
de
la
fonction
d'initialisation
InitScene
'
Boucle
principale
glutMainLoop
End
Function
'
Fonction
d'affichage
Public
Sub
CallBackDraw
(
)
'
Appel
de
la
fonction
de
rendu
Call
Render
'
Echange
les
buffers
glutSwapBuffers
'
Calcul
et
affichage
du
frame
rate
gDisplayCount =
gDisplayCount +
1
If
gDisplayCount =
100
Then
glutSetWindowTitle "
Frame
rate
:
"
&
format
(
gDisplayCount /
(
glutGet
(
GLUT_ELAPSED_TIME) -
gElapsedTime) *
1000
, "
0,0
"
)
gDisplayCount =
0
gElapsedTime =
glutGet
(
GLUT_ELAPSED_TIME)
End
If
End
Sub
'
Procédure
de
rappel
attente
Public
Sub
CallBackIdle
(
)
Dim
lTimer As
Long
'
Temps
présent
lTimer =
glutGet
(
GLUT_ELAPSED_TIME)
'
Si
temps
présent
>=
au
temps
attendu
If
lTimer >=
gWaitTime Then
'
Rrafraichir
l'affichage
glutPostRedisplay
'
n
affichages
par
seconde
gWaitTime =
lTimer +
(
1000
/
50
)
End
If
End
Sub
'
Gestion
du
clavier
Public
Sub
CallBackSpecial
(
ByVal
key As
Long
, ByVal
x As
Long
, ByVal
y As
Long
)
Select
Case
key
Case
GLUT_KEY_LEFT
gRotateY =
gRotateY -
10
Case
GLUT_KEY_RIGHT
gRotateY =
gRotateY +
10
Case
GLUT_KEY_UP
gRotateX =
gRotateX -
10
Case
GLUT_KEY_DOWN
gRotateX =
gRotateX +
10
End
Select
End
Sub
'
Gestion
de
la
molette
de
la
souris
Public
Sub
CallBackMouseWheel
(
ByVal
wheel As
Long
, ByVal
direction As
Long
, ByVal
x As
Long
, ByVal
y As
Long
)
gZoom =
gZoom +
direction
End
Sub
'
Initialisation
de
la
scène
Public
Sub
InitScene
(
)
'
Initialisation
du
temps
déclencheur
de
l'affichage
gWaitTime =
glutGet
(
GLUT_ELAPSED_TIME)
'
Tests
de
profondeur
glEnable GL_DEPTH_TEST
glDepthFunc GL_LEQUAL
'
Initialisation
des
variables
de
visualisation
gRotateX =
0
gRotateY =
0
gZoom =
10
End
Sub
'
Rendu
de
la
scène
Public
Sub
Render
(
)
'
Passage
en
matrice
de
projection
glMatrixMode GL_PROJECTION
'
Initialisation
de
la
matrice
glLoadIdentity
'
Définition
de
la
perspective
gluPerspective 45
, 1
, 0
.
1
, 100
'
Passage
en
matrice
de
modélisation-visualisation
glMatrixMode GL_MODELVIEW
'
Initialisation
de
la
matrice
glLoadIdentity
'
Position
de
la
caméra
gluLookAt 0
, 0
, gZoom, 0
, 0
, 0
, 0
, 1
, 0
glRotated gRotateX, 1
, 0
, 0
glRotated gRotateY, 0
, 1
, 0
'
Vide
les
buffers
couleur
et
profondeur
glClear GL_COLOR_BUFFER_BIT Or
GL_DEPTH_BUFFER_BIT
'
Sauvegarde
la
matrice
glPushMatrix
'
Rotation
du
cube
glRotated 30
, 1
, 1
, 1
'
Début
de
la
primitive
pour
le
cube
glBegin GL_QUADS
'
Face
du
haut
=
vert
Call
glColor3d
(
0
, 1
, 0
)
Call
glVertex3d
(
1
, 1
, -
1
)
Call
glVertex3d
(
-
1
, 1
, -
1
)
Call
glVertex3d
(
-
1
, 1
, 1
)
Call
glVertex3d
(
1
, 1
, 1
)
'
Face
du
bas
=
orange
Call
glColor3d
(
1
, 0
.
5
, 0
)
Call
glVertex3f
(
-
1
, -
1
, -
1
)
Call
glVertex3f
(
1
, -
1
, -
1
)
Call
glVertex3f
(
1
, -
1
, 1
)
Call
glVertex3f
(
-
1
, -
1
, 1
)
'
Face
de
derrière
=
rouge
Call
glColor3d
(
1
, 0
, 0
)
Call
glVertex3f
(
1
, -
1
, -
1
)
Call
glVertex3f
(
-
1
, -
1
, -
1
)
Call
glVertex3f
(
-
1
, 1
, -
1
)
Call
glVertex3f
(
1
, 1
, -
1
)
'
Face
de
devant
=
jaune
Call
glColor3d
(
1
, 1
, 0
)
Call
glVertex3f
(
-
1
, -
1
, 1
)
Call
glVertex3f
(
1
, -
1
, 1
)
Call
glVertex3f
(
1
, 1
, 1
)
Call
glVertex3f
(
-
1
, 1
, 1
)
'
Face
de
gauche
=
violet
Call
glColor3d
(
1
, 0
, 1
)
Call
glVertex3f
(
-
1
, 1
, 1
)
Call
glVertex3f
(
-
1
, 1
, -
1
)
Call
glVertex3f
(
-
1
, -
1
, -
1
)
Call
glVertex3f
(
-
1
, -
1
, 1
)
'
Face
de
droite
=
bleu
Call
glColor3d
(
0
, 0
, 1
)
Call
glVertex3f
(
1
, 1
, -
1
)
Call
glVertex3f
(
1
, 1
, 1
)
Call
glVertex3f
(
1
, -
1
, 1
)
Call
glVertex3f
(
1
, -
1
, -
1
)
glEnd
'
Restaure
la
matrice
glPopMatrix
End
Sub