Découvrez OpenGL 1.1 en VB6/VBA

Thierry GASPERMENT

Découvrez OpenGL 1.1 en VB6 ou en VBA.
Cet article est destiné aux développeurs VB6/VBA qui souhaitent apprendre le fonctionnement d’OpenGL 1.1.

Commentez cet article : Commentez

- Titre : Découvrez OpenGL 1.1 en VB6/VBA

- Auteur : Thierry GASPERMENT

- Parution : 1 juin 2009

- Mise à jour : 20 mars 2019

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Thierry GASPERMENT. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- I. Introduction

- II. Bibliothèques utilisées

- III. Le pack de module VB pour OpenGL

- IV. Préparation de l'environnement de développement VB

- V. Première fenêtre avec freeGLUT

- VI. Les fonctions de rappel de freeGLUT

- VII. Découverte d'OpenGL

- VII-A. Version ciblée et extensions

- VII-B. La programmation par états

- VII-C. Les procédures et constantes

- VII-D. Les types de données

- VII-E. Les buffers

- VII-E-1. Le tampon de couleurs

- VII-E-2. Le tampon de profondeur

- VII-E-3. Le tampon d'accumulation

- VII-E-4. Le tampon pochoir

- VII-E-5. Le double tampon

- VII-F. Les unités

- VII-G. Les vertices

- VII-H. Les primitives géométriques

- VII-I. Les couleurs des vertices

- VII-J. Orientation des faces

- VII-K. Les normales

- VII-L. Les modes d'affichage des polygones

- VII-L-1. Affichage des points

- VII-L-2. Affichage des lignes

- VII-L-3. Remplissage des polygones

- VIII. Mise à jour régulière de l'image : le frame rate

- VIII-A. La procédure de rappel « Idle »

- VIII-B. La procédure de rappel « Timer »

- VIII-C. Affichage du frame rate

- IX. Mise en scène

- IX-A. Les axes

- IX-B. Les matrices de transformations

- IX-B-1. Les projections

- IX-B-1-a. La projection en perspective

- IX-B-1-b. La projection orthonormée

- IX-C. La caméra

- IX-D. Définition de la projection et de la caméra

- IX-E. Cadrer l'image dans la fenêtre

- X. Gestion des entrées clavier et souris : Rotation et Zoom de la scène

- XI. Rappel du code complet

- XII. Les effets de lumière et de brouillard

- XII-A. La lumière

- XII-A-1. Les types de lumières

- XII-A-2. Les lampes

- XII-A-3. Mise en pratique

- XII-B. Le brouillard

- XIII. Rappel du code complet

- XIV. Les textures

- XIV-A. Chargement des textures

- XIV-B. Application d'une texture sur les faces du cube

- XIV-B-1. Modes d'application d'une texture

- XIV-B-2. Répétition de la texture

- XIV-C. Application d'une texture sur une sphère

- XIV-D. Le mipmapping

- XV. Utilisation des listes et des tableaux

- XV-A. Utilisation des tableaux

- XV-B. Utilisation des listes

- XVI. Rappel du code complet

- XVII. L'affichage de textes

- XVII-A. Affichage d'un texte bitmap statique

- XVII-B. Affichage d'un texte bitmap dynamique avec freeGLUT

- XVII-C. Affichage d'un texte bitmap dynamique avec Windows

- XVII-D. Affichage d'un texte 3D dynamique avec Windows

- XVII-E. Affichage d'un texte bitmap dynamique : méthode générique

- XVIII. Intégrer la fenêtre freeGLUT dans un formulaire

- XVIII-A. Pour Access

- XVIII-B. Pour un UserForm (Excel, Word…)

- XIX. Affichage dans un formulaire sans freeGLUT

- XIX-A. Affichage dans un contrôle image ou une section Access

- XIX-B. Affichage dans un contrôle Image ou Frame d'un UserForm

- XIX-C. Affichage sur un formulaire VB6

- XX. Sauvegarde de la scène dans un fichier bmp

- XXI. Conclusion

- XXII. Téléchargements

I - Introduction

Au cours de ce tutoriel, nous allons découvrir la programmation de la 3D avec OpenGL.
Cette découverte va se faire en langage VBA ou VB6.
Cependant, les fonctions et techniques utilisées sont les mêmes dans tous les langages.

Pour simplifier, lorsque j'emploie le terme VB, il réfère à VB6 ou à VBA.

II - Bibliothèques utilisées

Tout d'abord OpenGL (version 1) est installé avec Windows.

Il y a deux bibliothèques pour OpenGL dans le répertoire système de Windows :

- opengl32.dll : c'est la bibliothèque principale

- glu32.dll = GL Utility : c'est un complément qui contient quelques autres fonctions utiles.

Ces bibliothèques sont disponibles pour les versions 32 bits et 64 bits. Le dossier système est différent, mais le nom des fichiers est le même.

OpenGL ne gère pas le fenêtrage : c'est-à-dire qu'il faut fournir à OpenGL une fenêtre sur laquelle il va effectuer le rendu de l'image.

De plus il ne gère pas les entrées clavier, souris…

Pour gérer l'affichage des fenêtres et des événements divers (clavier, souris, minuterie…), il existe plusieurs bibliothèques.

Glut est une bibliothèque très largement utilisée, mais qui n’est plus maintenue.
Pour une utilisation en VB, je lui ai préféré freeGLUT qui expose des fonctions similaires, et quelques nouveautés qui nous seront utiles, voire indispensables en VBA.

Nous verrons qu'il est également possible de faire un rendu sur un formulaire sans freeGLUT.

Enfin pour appeler les fonctions de ces bibliothèques, il faut déclarer chacune d'entre elles dans VB.
Nous allons donc préparer notre environnement de développement.

III - Le pack de module VB pour OpenGL

Tous les modules VB nécessaires à l'utilisation d'OpenGL sont regroupés dans une archive compressée au format zip.

Téléchargez cette archive : Pack de module VB pour OpengGL

Pour utiliser freeGLUT, téléchargez également la bibliothèque dll : Librairie freeGLUT

Cette bibliothèque est disponible en deux versions : 32 bits (freeglut.dll) et 64 bits (freeglut64.dll).

IV - Préparation de l'environnement de développement VB

Créez d'abord une nouvelle application Microsoft Office : Access, Excel, Word…
Ou créez un nouveau projet Visual Basic 6.

Afin de pouvoir utiliser les fonctions OpenGL en VB :

Importez le module standard ModOpenGL_1_1.
Ce module contient les déclarations des fonctions et constantes de OpenGL 1.1.

Importez le module standard ModOpenGLTools.
Ce module contient les déclarations des fonctions et constantes de OpenGL 1.1 de Glu, ainsi que des fonctions spécifiques à Windows (wgl et gdi32).
Il contient également des fonctions supplémentaires utiles pour l'utilisation d'OpenGL en VB.

Importez le module standard ModOpenGLFreeGlut.
Ce module contient toutes les déclarations nécessaires à freeGLUT.

Pour utiliser FreeGLUT (création de fenêtres) :

Placez la bibliothèque freeGLUT (32 bits, 64 bits, ou les deux si nécessaire) dans le même répertoire que l'application Office ou le projet VB6.

Cette bibliothèque a été compilée avec Visual Studio avec une convention d’appel stdcall pour fonctionner avec VB.

freeGLUT est sous licence : X-Consortium license.

Avec Office, pour ouvrir l'éditeur VBA, tapez ALT + F11.
Pour importer un module, choisissez dans le menu : Fichier => Importer un fichier….

V - Première fenêtre avec freeGLUT

Créez un nouveau module standard (Insertion => Module dans Office).

Dans ce module nous ajoutons une fonction FonctionOpenGL dans laquelle nous écrirons le code de création de la fenêtre.

Fonction FonctionOpenGL

Function FonctionOpenGL()

End Function

Pour VB6, créez une procédure Main à la place de cette fonction.

Procédure d'entrée VB6

Sub Main()

End Sub

Notez qu'on a placé la bibliothèque freeglut.dll dans le même répertoire que l'application.
Pour appeler les fonctions de cette bibliothèque, il faut donc d'abord la charger.

Si on avait placé cette bibliothèque dans le répertoire système de Windows, il n'aurait pas été nécessaire de la charger. Mais mieux vaut ne pas polluer le dossier système et pouvoir éventuellement gérer plusieurs versions pour plusieurs applications.

Pour charger la bibliothèque, utilisons la fonction LoadFreeGlut (elle est déclarée dans le module ModOpenGLFreeGlut).

Chargement de la bibliothèque freeGLUT

' Chargement de freeGLUT
If Not LoadFreeGlut(CurrentProject.Path) Then
MsgBox "Impossible de charger la bibliothèque freeGLUT"
Exit Function
End If

Cette fonction charge la bibliothèque 32 bits ou 64 bits en fonction de la version d’application utilisée (toujours 32 bits pour VB6).

Si le programme n'arrive pas à charger la bibliothèque, la fonction renvoie false et on affiche alors un message d'erreur.

Remplacez CurrentProject en fonction de l'application utilisée :

- CurrentProject pour Access.

- ThisWorkBook pour Excel.

- ThisDocument pour Word.

- App pour VB6.

On peut maintenant appeler les fonctions de freeGLUT.

Première étape : l'initialisation

Initialisation de la bibliothèque freeGLUT

' Initialisation de la bibliothèque
glutInit 0&, ""

D'après la documentation de glutInit, les paramètres de cette fonction d'initialisation sont utilisés pour les systèmes X Window.
Nous n'utilisons pas ces paramètres, on passe donc des valeurs vides.

Deuxième étape : l'initialisation du mode d'affichage

Initialisation du mode d'affichage

' Initialisation du mode d'affichage
glutInitDisplayMode GLUT_RGBA Or GLUT_DOUBLE Or GLUT_DEPTH

- GLUT_RGBA signifie que la fenêtre utilise le modèle de couleur RGB (rouge, vert, bleu) plus une composante alpha de transparence.

- GLUT_DOUBLE signifie que nous souhaitons utiliser un double buffer (nous le détaillerons plus tard).

- GLUT_DEPTH signifie que nous souhaitons utiliser un tampon de profondeur (nous l'utiliserons également plus tard).

Troisième étape : création d'une fenêtre

Création d'une fenêtre

' Création d'une fenêtre
glutCreateWindow "Tutoriel fenêtre GLUT"

Le titre de la fenêtre est passé en paramètre.
Cette fonction renvoie un numéro de fenêtre (interne à freeGLUT) que l'on peut utiliser ensuite pour, par exemple, redimensionner, positionner ou détruire la fenêtre.
Il est également possible de créer plusieurs fenêtres et de passer de l'une à l'autre.
Nous n'utiliserons pas ce numéro dans cet exemple, inutile donc de le stocker.

Quatrième étape : la fonction d'affichage On peut avec freeGLUT définir des fonctions de rappel (CallBack en anglais).
Ces fonctions sont exécutées depuis freeGLUT au cours de la boucle que nous allons ensuite exécuter avec glutMainLoop.
La liste des fonctions et de leur déclaration en VB est décrite dans le chapitre suivant.

La fonction de rappel indispensable est la fonction d'affichage.
Cette fonction est appelée à chaque fois qu'il est nécessaire d'afficher le contenu de la fenêtre.

freeGLUT crashe si on ne la définit pas.

Créez la fonction d'affichage dans le module VB standard :

Création de la fonction d'affichage

' Fonction d'affichage
Public Sub CallBackDraw()

End Sub

Puis définissez la fonction dans freeGLUT, après l'appel à glutCreateWindow.

Définition de la fonction d'affichage

' Fonction d'affichage
glutDisplayFunc AddressOf CallBackDraw

Dans la fonction d'affichage on va :

- vider les buffers avec glClear ;

- réaliser les opérations de rendu (dessin d'objets, éclairage…), ce que nous ajouterons par la suite ;

- échanger les buffers pour afficher le résultat dans la fenêtre.

Contenu de la fonction d'affichage

' Fonction d'affichage
Public Sub CallBackDraw()
' Vide les buffers couleur et profondeur
glClear GL_COLOR_BUFFER_BIT Or GL_DEPTH_BUFFER_BIT
' Échange les buffers
glutSwapBuffers
End Sub

Vous verrez sans doute dans diverses documentations un appel à glFlush qui demande à OpenGL d'exécuter les commandes en attente.
Nous n'appelons pas cette fonction ici, glutSwapBuffers le fait pour nous.

Notez également la fonction utilisée : glutSwapBuffers et non pas SwapBuffers.

Cinquième étape : exécution de la boucle principale

Glut fonctionne avec une boucle principale qu'il est nécessaire d'exécuter.

Exécution de la boucle principale

' Boucle principale
glutMainLoop

Cette boucle est sans fin, les instructions situées après l'appel à cette fonction ne seront pas exécutées tout de suite.

En VBA avec Office

Si on exécute la fonction FonctionOpenGL (touche F5), on voit bien la fenêtre s'afficher, mais si on la ferme toute l'application se ferme brutalement.

Pour éviter ce comportement, on définit une option de freeGLUT très pratique.
Ajoutez cette instruction juste après l'initialisation avec glutInitDisplayMode.

Définition de l'option de sortie de boucle

' Définition de l'option de sortie de boucle
glutSetOption GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS

Cette option demande à freeGLUT de continuer l'exécution après l'instruction glutMainLoop au lieu d'arrêter l'application.
On pourra alors fermer si besoin nous-mêmes l'application correctement.

Testons à nouveau notre programme, on peut fermer la fenêtre tout en conservant l'application ouverte.

Voici le rappel du code complet de notre première fenêtre GLUT :

Module de base pour création fenêtre GLUT

Option Explicit

Function FonctionOpenGL()
' Chargement de freeGLUT
If Not LoadFreeGlut(CurrentProject.Path) Then
MsgBox "Impossible de charger la bibliothèque freeGLUT"
Exit Function
End If
' Initialisation de la bibliothèque
glutInit 0&, ""
' Initialisation du mode d'affichage
glutInitDisplayMode GLUT_RGBA Or GLUT_DOUBLE Or GLUT_DEPTH
' Définition de l'option de sortie de boucle
glutSetOption GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS
' Création d'une fenêtre
glutCreateWindow "Tutoriel fenêtre GLUT"
' Fonction d'affichage
glutDisplayFunc AddressOf CallBackDraw
' Boucle principale
glutMainLoop
End Function

' Fonction d'affichage
Public Sub CallBackDraw()
' Vide les buffers couleur et profondeur
glClear GL_COLOR_BUFFER_BIT Or GL_DEPTH_BUFFER_BIT
' Échange les buffers
glutSwapBuffers
End Sub

Exécutez (F5) la fonction FonctionOpenGL pour afficher la fenêtre, qui est uniquement un fond noir pour l'instant.

Notez que le buffer de couleurs est initialisé avec la couleur définie par glClearColor.
glClearColor 1, 0, 0, 1 par exemple définit une couleur de fond rouge.

Le buffer de profondeur est vidé en donnant à chaque pixel une profondeur de 1.
Ceci peut être changé avec la fonction glClearDepth.

VI - Les fonctions de rappel de freeGLUT

On peut avec freeGLUT définir des fonctions de rappel (CallBack en anglais).
Ces fonctions sont exécutées depuis freeGLUT au cours de la boucle que nous avons exécutée avec glutMainLoop.

Nous avons déjà utilisé glutDisplayFunc qui est indispensable.

Fonction freeGLUT

Description

Déclaration VB

glutDisplayFunc AddressOf CallBackDraw

Appelée lorsqu'un affichage de la fenêtre est nécessaire.

Public Sub CallBackDraw()

glutIdleFunc AddressOf CallBackIdle

Appelée constamment lorsque le programme n'a pas d'événement à traiter dans sa pile.

Public Sub CallBackIdle()

glutReshapeFunc AddressOf CallBackReshape

Appelée lorsque la fenêtre est redimensionnée.

Public Sub CallBackReshape(ByVal width As Long, ByVal height As Long)

glutKeyboardFunc AddressOf CallBackKeyBoard

Appelée lorsqu'une touche est appuyée.
- char est le code ASCII du caractère.
- x et y sont les coordonnées de la souris.

Public Sub CallBackKeyBoard(ByVal char As Byte, ByVal x As Long, ByVal y As Long)

glutKeyboardUpFunc AddressOf CallBackKeyBoardUp

Appelée lorsqu'une touche est relâchée.
- char est le code ASCII du caractère.
- x et y sont les coordonnées de la souris.

Public Sub CallBackKeyBoardUp(ByVal char As Byte, ByVal x As Long, ByVal y As Long)

glutCloseFunc AddressOf CallBackClose

Appelée lorsque la fenêtre est fermée.

Public Sub CallBackClose()

glutSpecialFunc AddressOf CallBackSpecial

Appelée lorsqu'une touche spéciale est appuyée.
- key est le code de la touche.
- x et y sont les coordonnées de la souris.

Retrouvez les valeurs possibles de key dans le module ModFreeGlut.
Ce sont les constantes qui commencent par GLUT_KEY_

Public Sub CallBackSpecial(ByVal key As Long, ByVal x As Long, ByVal y As Long

glutSpecialUpFunc AddressOf CallBackSpecialUp

Appelée lorsqu'une touche spéciale est relâchée.
- key est le code de la touche.
- x et y sont les coordonnées de la souris.

Retrouvez les valeurs possibles de key dans le module ModFreeGlut.
Ce sont les constantes qui commencent par GLUT_KEY_

Public Sub CallBackSpecialUp(ByVal key As Long, ByVal x As Long, ByVal y As Long

glutMouseFunc AddressOf CallBackMouse

Appelée lorsqu'un bouton de la souris change d'état.
- button est le code du bouton.
- state est l'état du bouton.
- x et y sont les coordonnées de la souris.

- button prend les valeurs : GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON ou GLUT_RIGHT_BUTTON.
- state prend les valeurs : GLUT_DOWN ou GLUT_UP.

Public Sub CallBackMouse(ByVal button As Long, ByVal state As Long, ByVal x As Long, ByVal y As Long)

glutMouseWheelFunc AddressOf CallBackMouseWheel

Appelée lorsqu'on déplace la molette de la souris.
- wheel est le numéro de la molette.
- direction est la direction dans laquelle on l'a déplacée : -1 ou 1.
- x et y sont les coordonnées de la souris.

Public Sub CallBackMouseWheel(ByVal wheel As Long, ByVal direction As Long, ByVal x As Long, ByVal y As Long)

glutMotionFunc AddressOf CallBackMotion

Appelée lorsqu'on déplace la souris avec un bouton appuyé.
- x et y sont les coordonnées de la souris.

Public Sub CallBackMotion(ByVal x As Long, ByVal y As Long)

glutPassiveMotionFunc AddressOf CallBackPassiveMotion

Appelée lorsqu'on déplace la souris et qu'aucun bouton n'est appuyé.
- x et y sont les coordonnées de la souris.

Public Sub CallBackPassiveMotion(ByVal x As Long, ByVal y As Long)

glutJoystickFunc AddressOf CallBackJoystick, n

CallBackJoystick sera appelée toutes les n millisecondes.
- buttonMask contient les boutons appuyés.
- x, y et z la valeur de déplacement du joystick sur chacun de ces axes.

Public Sub CallBackJoystick(ByVal buttonMask As Long, ByVal x As Long, ByVal y As Long, ByVal z As Long)

glutTimerFunc n, AddressOf CallBackTimer, val

CallBackTimer sera appelée n millisecondes plus tard.
- val contient une valeur utilisateur.

La fonction de rappel n'est appelée qu'une seule fois.
Il faut la relancer si on veut un timer en permanence.

Public Sub CallBackTimer(ByVal val As Long)

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.

Appel de la fonction de rendu

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.

Dessin d'un triangle

' 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 :

Colore le triangle en jaune

' 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) :

Réinitialise la couleur

' 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 :

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.

Masquer les faces arrière

' 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

[...]
' 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.

Initialisation en mode points

' 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.

Lissage 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.

Initialisation en mode ligne

' 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.

Initialisation en mode ligne et pointillé

' 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 :

Fonction de conversion de motif

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éfinition du motif à partir d'une chaîne de caractères

' 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.

Initialisation en mode remplissage

' Mode d'affichage = remplissage
glPolygonMode GL_FRONT_AND_BACK, GL_FILL

VIII - Mise à jour régulière de l'image : le frame rate

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, en suivant deux méthodes.

VIII-A - La procédure de rappel « Idle »

Cette méthode consomme beaucoup de CPU, car la procédure de rappel est sans cesse appelée.

Préférez la méthode suivante avec un Timer.

Glut nous met à disposition une procédure 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.

Ajoutez une procédure CallBackIdle.

Procédure de rappel attente

Public Sub CallBackIdle()

End Sub

Et définissez cette procédure dans freeGLUT avant l’appel à glutMainLoop.

Définition dans freeGLUT de la fonction d'attente

' 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.

En en-tête de module

' Temps à attendre pour le prochain affichage
Private gWaitTime As Long

Ce temps est initialisé dans la fonction InitScene :

Initialisation du temps d'attente

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.

Attente avant affichage

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
' Rafraichir 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.

VIII-B - La procédure de rappel « Timer »

Glut nous met à disposition une procédure de rappel « Timer ».
Cette fonction est appelée après un temps écoulé en millisecondes.

Ajoutez une procédure CallBackTimer.

Procédure de rappel attente

' Fonction d'attente
Public Sub CallBackTimer(ByVal value As Long)

End Sub

Et définissez cette procédure dans freeGLUT avant l’appel à glutMainLoop.

Définition dans freeGLUT de la fonction d'attente

' Fonction d'attente
glutTimerFunc 1000 / 50, AddressOf CallBackTimer, 0

1000 / 50 correspond à une attente de 1000/50 = 20 millisecondes.
On obtient alors un affichage de 50 images par seconde.

Et enfin dans la fonction d'attente CallBackTimer, on relance un Timer et on déclenche un affichage avec glutPostRedisplay.

Attente avant affichage

' Fonction d'attente
Public Sub CallBackTimer(ByVal value As Long)
' Rappel de la fonction d'attente
glutTimerFunc 1000 / 50, AddressOf CallBackTimer, 0
' Rafraichir l'affichage si une fenêtre openGL est active
If glutGetWindow <> 0 Then glutPostRedisplay
End Sub

Pensez à bien vérifier qu’une fenêtre OpenGL est active avant d’exécuter glutPostRedisplay sinon l’application risque de planter à la fermeture de la fenêtre, le Timer pouvant se déclencher après destruction de la fenêtre.

VIII-C - Affichage du frame rate

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 :

En en-tête de module

' Temps écoulé pour calcul du frame rate
Private gElapsedTime As Long
' Nombre d'affichages pour calcul du frame rate
Private gDisplayCount As Long

Puis on calcule 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

' 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 trois axes pour dessiner une scène en trois 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 six faces comme six quads.
On donne à chaque face du cube une couleur différente.

Remplacez le code de dessin du triangle par celui du cube :

Dessin d'un 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

Évitez 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 trois 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 trois 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, quelle que 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 sœur gluPerspective de la bibliothèque 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

Pour cette projection, il faut utiliser la fonction glOrtho.

On définit un plan à travers lequel on voit la scène, puis les valeurs minimum et maximum de profondeur.

Projection orthonormée avec glOrtho :

Le volume visible de la scène est un pavé délimité par les deux plans de couleur bleu clair.

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 neuf paramètres :

- trois pour la position de la caméra : eyex, eyey et eysez ;

- trois pour le point de visée : centerx, centery et centerz ;

- et trois 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'origine (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 :

Projection et position de la caméra

' 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.

Évitez 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 :

Rotation du cube

' Sauvegarde la matrice
glPushMatrix
' Rotation du cube
glRotated 30, 1, 1, 1

Et après le glEnd, ajoutez :

Rotation du cube

' Restaure la matrice
glPopMatrix

Notez que l'appel aux fonctions glPushMatrix et glPopMatrix n’est pas indispensable 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.

Exécutez 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

' 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 glutTimerFunc) :

Fonctions de rappel souris et clavier

' 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.

En en-tête de module

' 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

' 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 :

Gestion du clavier

' 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

' 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

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.

Placement de la caméra

' 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ées 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 :

Rappel du code complet

Option Explicit

' Temps écoulé pour calcul du frame rate
Private gElapsedTime As Long
' Nombre d'affichages pour calcul du frame rate
Private gDisplayCount As Long
' RotationX
Private gRotateX As Double
' RotationY
Private gRotateY As Double
' Zoom
Private gZoom As Double

Function FonctionOpenGL()
' Chargement de freeGLUT
If Not LoadFreeGlut(CurrentProject.Path) Then
MsgBox "Impossible de charger la bibliothèque freeGLUT"
Exit Function
End If
' Initialisation de la bibliothèque
glutInit 0&, ""
' Initialisation du mode d'affichage
glutInitDisplayMode GLUT_RGBA Or GLUT_DOUBLE Or GLUT_DEPTH
' Définition de l'option de sortie de boucle
glutSetOption GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS
' Création d'une fenêtre
glutCreateWindow "Tutoriel fenêtre GLUT"
' Fonction d'affichage
glutDisplayFunc AddressOf CallBackDraw
' Fonctions de rappel clavier
glutSpecialFunc AddressOf CallBackSpecial
' Fonction de rappel molette de souris
glutMouseWheelFunc AddressOf CallBackMouseWheel
' Appel de la fonction d'initialisation
InitScene
' Fonction d'attente
glutTimerFunc 1000 / 50, AddressOf CallBackTimer, 0
' Boucle principale
glutMainLoop
End Function

' Fonction d'attente
Public Sub CallBackTimer(ByVal value As Long)
' Rappel de la fonction d'attente
glutTimerFunc 1000 / 50, AddressOf CallBackTimer, 0
' Rafraichir l'affichage si une fenêtre openGL est active
If glutGetWindow <> 0 Then glutPostRedisplay
End Sub

' Fonction d'affichage
Public Sub CallBackDraw()
' Appel de la fonction de rendu
Call Render
' Échange 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

' Fonction d'initialisation
Public Sub InitScene()
' Mode d'affichage = remplissage
glPolygonMode GL_FRONT_AND_BACK, GL_FILL
' Tests de profondeur
glEnable GL_DEPTH_TEST
glDepthFunc GL_LEQUAL
' Initialisation des variables de visualisation
gRotateX = 0
gRotateY = 0
gZoom = 10
End Sub

' Fonction de rendu
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

' 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

XII - Les effets de lumière et de brouillard

XII-A - La lumière

XII-A-1 - Les types de lumières

OpenGL gère quatre types de lumière.

La lumière ambiante : GL_AMBIENT.
C'est la lumière de la lampe par défaut.
Elle semble venir de toutes les directions et est réfléchie dans toutes les directions.

La lumière diffuse : GL_DIFFUSE.
Elle arrive d'une direction particulière et est réfléchie dans toutes les directions.
C'est le cas d'une lampe classique, une ampoule par exemple.

La lumière spéculaire : GL_SPECULAR.
Elle arrive d'une direction particulière et est réfléchie dans une direction particulière.
C'est le cas d'un rayon de lumière réfléchi par un miroir par exemple.

La lumière émise : GL_EMISSION.
Elle est émise par un objet et non par une lampe.
La lumière émise n'éclaire pas les autres objets.

Les lumières peuvent se cumuler : par exemple, une lampe peut envoyer de la lumière ambiante et de la lumière diffuse en même temps.

XII-A-2 - Les lampes

OpenGL gère huit lampes, en plus de la lampe par défaut.
La lampe par défaut est une lumière ambiante, on le voit bien : notre cube est éclairé sur toutes ses faces comme si la lumière venait de toutes les directions.

Si on souhaite gérer notre propre lumière, il faut d'abord activer l'éclairage :
glEnable GL_LIGHTING
(ceci désactive la lampe par défaut).

Puis activer les lampes que nous souhaitons gérer :
glEnable GL_LIGHTn avec n de 0 à 7.

Les fonctions glLight* permettent de gérer les attributs des lampes :

- le premier paramètre est le numéro de la lampe, de GL_LIGHT0 à GL_LIGHT7 ;

- le deuxième paramètre est le code de l'attribut à modifier ;

- le troisième paramètre est la valeur à affecter à l'attribut.

Les fonctions en suffixe f demandent un paramètre de type single.
Les fonctions en suffixe i demandent un paramètre de type long.
Les fonctions en suffixe fv demandent un paramètre de type tableau de single.
Les fonctions en suffixe iv demandent un paramètre de type tableau de long.

Fonction

Code de l'attribut

Valeur

Exemple

glLightfv ou glLightiv

GL_AMBIENT

Quatre paramètres RGBA qui déterminent l'intensité et la couleur de la lumière.
Si on utilise des paramètres long avec glLightiv, les valeurs sont recalculées linéairement pour que le maxi des composantes soit égal à 1.
Ces paramètres peuvent être négatifs (de -1 à 1).

Exemple de lampe bleue

Dim lColor(1 To 4) As Single
lColor(1) = 0
lColor(2) = 0
lColor(3) = 1
lColor(4) = 1
glLightfv GL_LIGHT0, GL_AMBIENT, lColor(1)

glLightfv ou glLightiv

GL_DIFFUSE

Quatre paramètres RGBA qui déterminent l'intensité et la couleur de la lumière.
Même utilisation que GL_AMBIENT.

glLightfv ou glLightiv

GL_SPECULAR

Quatre paramètres RGBA qui déterminent l'intensité et la couleur de la lumière.
Même utilisation que GL_AMBIENT.

glLightfv ou glLightiv

GL_POSITION

Quatre paramètres x, y, z et w qui déterminent la position de la lampe.
- Si w vaut 0, la lampe est directionnelle : x, y, z est un vecteur qui définit la direction de la lumière.
- Si w vaut 1, la lampe est positionnelle : x, y, z définit la position de la source de lumière.
Par défaut, la lampe est directionnelle avec un vecteur (0, 0, 1).

Exemple de lampe positionnelle

Dim lposition(1 To 4) As Single
lposition(1) = 10
lposition(2) = 10
lposition(3) = 5
lposition(4) = 1
glLightfv GL_LIGHT0, GL_POSITION, lposition(1)

glLightf ou glLighti

GL_SPOT_CUTOFF

Un paramètre qui définit l'angle d'émission de la lumière.
La valeur est de 180° par défaut.
Les valeurs possibles vont de 0 à 90 ; la valeur 180 est acceptée.

glLightfv ou glLightiv

GL_SPOT_DIRECTION

Trois paramètres qui déterminent la direction d'un spot.
La direction par défaut est (0, 0, -1).
Cet attribut n'est utile que si l'angle d'ouverture de la lampe (GL_SPOT_CUTOFF) n'est pas 180° (sa valeur par défaut).

glLightf ou glLighti

GL_SPOT_EXPONENT

Un paramètre qui définit l'atténuation angulaire.
Valeurs de 0 à 128.
L'atténuation vaut 0 par défaut et correspond à une répartition uniforme de la lumière.
Plus la valeur est élevée, plus la lumière est concentrée au centre.

glLightf ou glLighti

GL_CONSTANT_ATTENUATION

Un paramètre : le coefficient d'atténuation constant.

glLightf ou glLighti

GL_LINEAR_ATTENUATION

Un paramètre : le coefficient d'atténuation linéaire (fonction de la distance).

glLightf ou glLighti

GL_QUADRATIC_ATTENUATION

Un paramètre : le coefficient d'atténuation quadratique (fonction de la distance au carré).

XII-A-3 - Mise en pratique

Nous allons ajouter une lampe diffuse qui tourne autour du cube.

Initialisons d'abord l'éclairage, dans la fonction InitScene :

Initialisation de l'éclairage

' Initialisation de l'éclairage
glEnable GL_LIGHTING
glEnable GL_LIGHT0

On a activé la lampe GL_LIGHT0.

Par défaut, la lampe émet une faible lumière ambiante.
On voit très légèrement le cube.
Donnons-lui en plus une lumière diffuse, à la suite du code précédent :

Ajout d'une lumière diffuse

' Ajout d'une lumière diffuse
Dim lColor(1 To 4) As Single
lColor(1) = 2
lColor(2) = 2
lColor(3) = 2
lColor(4) = 1
glLightfv GL_LIGHT0, GL_DIFFUSE, lColor(1)

La lampe émet maintenant une lumière blanche (2, 2, 2).
Remarquez qu'on peut utiliser des valeurs supérieures à 1.
Par défaut, elle éclaire de l'avant vers le fond.

Si vous tournez autour du cube, vous verrez que l'arrière n'est pas éclairé.

Le cube est blanc, car il est éclairé par une lumière blanche.
On a perdu les couleurs du cube.
Pour les retrouver, activer la coloration des matériaux dans l'initialisation de l'éclairage :

Active la coloration des matériaux

' Active la coloration des matériaux
glEnable GL_COLOR_MATERIAL

On retrouve les couleurs du cube.

Pour faire tourner la lampe autour du cube, on définit une variable en en-tête du module pour conserver l'angle de rotation de la lampe.

En en-tête de module

' RotationY de la lampe
Private gLightRotate As Double

Puis on positionne la lampe dans la fonction Render, juste après l'appel à glClear.

Positionne la lampe

' Positionne la lampe
gLightRotate = gLightRotate + 0.03
Dim lposition(1 To 4) As Single
lposition(1) = 3 * Cos(gLightRotate)
lposition(2) = 0
lposition(3) = 3 * Sin(gLightRotate)
lposition(4) = 1
glLightfv GL_LIGHT0, GL_POSITION, lposition(1)

La lampe tourne, mais on ne voit pas vraiment où elle se trouve et si l'éclairage est correct.

Pour matérialiser la lampe, on va dessiner une petite sphère.
Pour dessiner une sphère, on utilise la fonction gluSphere qui nous facilite la tâche.

Ajoutez le code de dessin de la sphère juste après le positionnement de la lampe :

Matérialise la lampe avec une sphère

' Dessine une sphère à l'endroit où se trouve la lampe
#If VBA7 Then
Dim lQuad As LongPtr
#Else
Dim lQuad As Long
#End If
glPushMatrix
glColor3VB vbYellow
glTranslated 3 * Cos(gLightRotate), 0, 3 * Sin(gLightRotate)
lQuad = gluNewQuadric
gluQuadricOrientation lQuad, GLU_INSIDE
gluSphere lQuad, 0.5, 20, 15
gluDeleteQuadric lQuad
glPopMatrix

Notez l’utilisation de LongPtr à partir de VBA7 (Office 2010) pour gérer correctement les versions 64 bits ; en effet certaines fonctions glu renvoient des pointeurs.

Les fonctions glPushMatrix et glPopMatrix prennent ici toute leur importance.
On peut transformer la sphère et le cube indépendamment en sauvegardant puis en restaurant la matrice de modélisation-visualisation.

Notez qu'on a défini une sphère de couleur jaune, de taille 0.5, avec 20 divisions pour les longitudes et 15 divisions pour les latitudes.
On a également modifié l'orientation de la sphère (GLU_INSIDE) de manière à ce que les faces soient éclairées (sinon une lumière à l'intérieur de la sphère ne l'éclaire pas à l'extérieur).

La sphère est créée centrée sur l'origine.
Nous la déplaçons à l'endroit où se situe la lampe avec la fonction glTranslated.

Testons notre éclairage :

On remarque un problème : la lampe est à droite et commence à éclairer la face gauche en violet.
Il se trouve qu’on n’a pas défini de normale lors du dessin du cube.

Il faut ajouter une normale pour chaque face du cube.

Le dessin du cube avec les normales

' Début de la primitive pour le cube
glBegin GL_QUADS
' Face du haut = vert
Call glColor3d(0, 1, 0)
Call glNormal3d(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 glNormal3d(0, -1, 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 glNormal3d(0, 0, -1)
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 glNormal3d(0, 0, 1)
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 glNormal3d(-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 droite = bleu
Call glColor3d(0, 0, 1)
Call glNormal3d(1, 0, 0)
Call glVertex3f(1, 1, -1)
Call glVertex3f(1, 1, 1)
Call glVertex3f(1, -1, 1)
Call glVertex3f(1, -1, -1)
glEnd

Comme pour les couleurs, on définit une normale par face, mais la normale s'applique à chaque vertice de la face.

Pour que l'éclairage soit correct, il faut que les normales aient une longueur de 1.
Ce qui est le cas pour nos normales lors de leur définition.
Mais lorsqu'on redimensionne la scène, où si on définit des normales plus complexes (non parallèles à un axe), elles peuvent ne plus être à la bonne taille.
Utilisez la commande glEnable GL_NORMALIZE à l'initialisation pour qu'OpenGL redimensionne les normales avant éclairage.

Testons à nouveau l'éclairage avec les normales :

Voilà qui est mieux, les faces sont éclairées correctement.

En y regardant de plus près, on voit que chaque face du cube est éclairée progressivement.
C'est le comportement standard, que l'on peut changer avec la fonction glShadeModel :

- glShadeModel GL_FLAT permet de colorier tout le polygone uniformément.
C'est plus rapide, mais moins joli ;

- glShadeModel GL_SMOOTH permet de colorier le polygone progressivement.
C'est le comportement par défaut.

XII-B - Le brouillard

Le brouillard est très facile à mettre en œuvre.

Pour activer le brouillard : glEnable GL_FOG.

Les fonctions glFog* permettent de gérer les attributs du brouillard :

- le premier paramètre est le code de l'attribut à modifier ;

- le second paramètre est la valeur à affecter à l'attribut.

Les fonctions en suffixe f demandent un paramètre de type single.
Les fonctions en suffixe i demandent un paramètre de type long.
Les fonctions en suffixe fv demandent un paramètre de type tableau de single.
Les fonctions en suffixe iv demandent un paramètre de type tableau de long.

Fonction

Code de l'attribut

Valeur

Exemple

glFogf ou glFogi

GL_FOG_MODE

Un paramètre qui détermine l'équation à utiliser pour gérer l'atténuation du brouillard.
Les valeurs possibles sont :
- GL_LINEAR pour une atténuation linéaire, c'est le plus rapide ;
- GL_EXP pour une atténuation exponentielle, c'est le mode par défaut ;
- GL_EXP2 pour une atténuation exponentielle au carré.

Exemple d'atténuation linéaire

glFogi GL_FOG_MODE, GL_LINEAR

glFogf ou glFogi

GL_FOG_DENSITY

Un paramètre qui détermine la densité du brouillard.
La valeur doit être positive.
La densité par défaut est de 1.

Exemple de densité de brouillard

glFogf GL_FOG_DENSITY, 0.2

glFogf ou glFogi

GL_FOG_START

Un paramètre qui détermine la distance où commence le brouillard.
C'est une distance par rapport à la caméra.
Valable pour un mode GL_LINEAR seulement.

Exemple de début du brouillard

glFogf GL_FOG_START, 5

glFogf ou glFogi

GL_FOG_END

Un paramètre qui détermine la distance où s'arrête le brouillard.
C'est une distance par rapport à la caméra.
Valable pour un mode GL_LINEAR seulement.

Exemple de fin du brouillard

glFogf GL_FOG_END, 10

glFogf ou glFogi

GL_FOG_INDEX

Un paramètre qui détermine l'index de la couleur du brouillard.
Uniquement si la fenêtre a été créée avec des couleurs indexées (GLUT_INDEX).

Exemple de couleur indexée du brouillard

glFogi GL_FOG_INDEX, 10

glFogfv ou glFogiv

GL_FOG_COLOR

Quatre paramètres qui déterminent la couleur RGBA du brouillard.
Uniquement si la fenêtre a été créée avec des couleurs RGB (GLUT_RGB ou GLUT_RGBA).
Par défaut, le brouillard est noir (0, 0, 0, 0).

Exemple de brouillard gris

Dim lfogcolor(1 To 4) As Single
lfogcolor(1) = 0.7
lfogcolor(2) = 0.7
lfogcolor(3) = 0.7
lfogcolor(4) = 1
glFogfv GL_FOG_COLOR, lfogcolor(1)

glHint

GL_FOG_HINT

Un paramètre qui détermine la qualité du brouillard.
Peut valoir GL_FASTEST, GL_NICEST ou GL_DONT_CARE.

Exemple de brouillard moins beau, mais plus rapide

glHint GL_FOG_HINT, GL_FASTEST

Ajoutons un brouillard gris à notre scène, dans la fonction InitScene :

Ajout du brouillard

' Initialisation du brouillard
Dim lfogcolor(1 To 4) As Single
glEnable GL_FOG
lfogcolor(1) = 0.7
lfogcolor(2) = 0.7
lfogcolor(3) = 0.7
lfogcolor(4) = 1
glFogfv GL_FOG_COLOR, lfogcolor(1)
glFogi GL_FOG_MODE, GL_LINEAR
glFogf GL_FOG_START, 7
glFogf GL_FOG_END, 10
glFogf GL_FOG_DENSITY, 0.1
glHint GL_FOG_HINT, GL_NICEST

Et voici notre cube dans le brouillard :

XIII - Rappel du code complet

Un rappel du contenu du module VB à ce stade du tutoriel, avec la lumière et le brouillard :

Rappel du code complet

Option Explicit

' Temps écoulé pour calcul du frame rate
Private gElapsedTime As Long
' Nombre d'affichages pour calcul du frame rate
Private gDisplayCount As Long
' RotationX
Private gRotateX As Double
' RotationY
Private gRotateY As Double
' Zoom
Private gZoom As Double
' RotationY de la lampe
Private gLightRotate As Double

Function FonctionOpenGL()
' Chargement de freeGLUT
If Not LoadFreeGlut(CurrentProject.Path) Then
MsgBox "Impossible de charger la bibliothèque freeGLUT"
Exit Function
End If
' Initialisation de la bibliothèque
glutInit 0&, ""
' Initialisation du mode d'affichage
glutInitDisplayMode GLUT_RGBA Or GLUT_DOUBLE Or GLUT_DEPTH
' Définition de l'option de sortie de boucle
glutSetOption GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS
' Création d'une fenêtre
glutCreateWindow "Tutoriel fenêtre GLUT"
' Fonction d'affichage
glutDisplayFunc AddressOf CallBackDraw
' Fonctions de rappel clavier
glutSpecialFunc AddressOf CallBackSpecial
' Fonction de rappel molette de souris
glutMouseWheelFunc AddressOf CallBackMouseWheel
' Appel de la fonction d'initialisation
InitScene
' Fonction d'attente
glutTimerFunc 1000 / 50, AddressOf CallBackTimer, 0
' Boucle principale
glutMainLoop
End Function

' Fonction d'attente
Public Sub CallBackTimer(ByVal value As Long)
' Rappel de la fonction d'attente
glutTimerFunc 1000 / 50, AddressOf CallBackTimer, 0
' Rafraichir l'affichage si une fenêtre openGL est active
If glutGetWindow <> 0 Then glutPostRedisplay
End Sub

' Fonction d'affichage
Public Sub CallBackDraw()
' Appel de la fonction de rendu
Call Render
' Échange 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

' Fonction d'initialisation
Public Sub InitScene()
' Mode d'affichage = remplissage
glPolygonMode GL_FRONT_AND_BACK, GL_FILL
' Tests de profondeur
glEnable GL_DEPTH_TEST
glDepthFunc GL_LEQUAL
' Initialisation des variables de visualisation
gRotateX = 0
gRotateY = 0
gZoom = 10
' Initialisation de l'éclairage
glEnable GL_LIGHTING
glEnable GL_LIGHT0
' Ajout d'une lumière diffuse
Dim lColor(1 To 4) As Single
lColor(1) = 2
lColor(2) = 2
lColor(3) = 2
lColor(4) = 1
glLightfv GL_LIGHT0, GL_DIFFUSE, lColor(1)
' Active la coloration des matériaux
glEnable GL_COLOR_MATERIAL
' Initialisation du brouillard
Dim lfogcolor(1 To 4) As Single
glEnable GL_FOG
lfogcolor(1) = 0.7
lfogcolor(2) = 0.7
lfogcolor(3) = 0.7
lfogcolor(4) = 1
glFogfv GL_FOG_COLOR, lfogcolor(1)
glFogi GL_FOG_MODE, GL_LINEAR
glFogf GL_FOG_START, 7
glFogf GL_FOG_END, 10
glFogf GL_FOG_DENSITY, 0.1
glHint GL_FOG_HINT, GL_NICEST
End Sub

' Fonction de rendu
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
' Positionne la lampe
gLightRotate = gLightRotate +
Dim lposition(1 To 4) As Single
lposition(1) = 3 * Cos(gLightRotate)
lposition(2) = 0
lposition(3) = 3 * Sin(gLightRotate)
lposition(4) = 1
glLightfv GL_LIGHT0, GL_POSITION, lposition(1)
' Dessine une sphère à l'endroit où se trouve la lampe
#If VBA7 Then
Dim lQuad As LongPtr
#Else
Dim lQuad As Long
#End If glPushMatrix
glColor3VB vbYellow
glTranslated 3 * Cos(gLightRotate), 0, 3 * Sin(gLightRotate)
lQuad = gluNewQuadric
gluQuadricOrientation lQuad, GLU_INSIDE
gluSphere lQuad, 0.5, 20, 15
gluDeleteQuadric lQuad
glPopMatrix
' 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 glNormal3d(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 glNormal3d(0, -1, 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 glNormal3d(0, 0, -1)
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 glNormal3d(0, 0, 1)
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 glNormal3d(-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 droite = bleu
Call glColor3d(0, 0, 1)
Call glNormal3d(1, 0, 0)
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

' 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

XIV - Les textures

Nous poursuivons le tutoriel à partir du module de code précédent.
Désactivez d'abord le brouillard en commentant la ligne glEnable GL_FOG ou en supprimant tout le code concerné
On y verra mieux sans brouillard.

XIV-A - Chargement des textures

OpenGL ne fournit aucune fonction pour charger des images à partir d'un fichier.
À nous de charger l'image et de fournir à OpenGL les valeurs des couleurs de chaque pixel.

Le module ModOpenGLTextureLoader contient une fonction TextureAddFromFile qui crée une texture à partir d’un fichier image, en utilisant si besoin Gdi+ pour charger le format PNG notamment.

Vous pouvez également charger, dans une texture, des images au format TGA grâce à la fonction loadTGATexture du module ModOpenGLTextureTGA.bas.
Ce module est une traduction du loader de TGA en C de David Henry

Certaines cartes graphiques n'acceptent que les images de texture dont les dimensions sont des puissances de 2 (2, 4, 8, 16, 32…).
Il est conseillé de travailler avec des textures de dimensions 2, 4, 8, 16, 32, 64, 128…

Pour charger le fichier, nous utiliserons la fonction VB LoadPicture.
Cette fonction lit les formats : bmp, ico, rle, wmf, emf, gif, jpg.

Pour lire les informations (taille, profondeur de couleur), nous utiliserons la fonction GetObject de la bibliothèque windows gdi32.

Ensuite pour créer une texture, on utilise la fonction glGenTextures.
Cette fonction prend en premier paramètre le nombre de textures qu'on veut créer.
Et elle renvoie en deuxième paramètre le nom (en fait le numéro) de la première texture créée.

Une texture s'active avec la fonction glBindTexture.

Puis on la génère avec la fonction glTexImage2D.

Et enfin on désactive la texture courante (0 en texture), on la redéfinira quand on en aura besoin.

Tout ceci est effectué par la fonction TextureAddFromFile du module ModOpenGLTextureLoader.

Importez le module standard ModOpenGLTextureLoader.
Ce module contient une fonction VB de chargement de textures.

Cette fonction TextureAddFromFile renvoie le numéro de la texture créée à partir du fichier donné en paramètre.

Le paramètre pTransparentColor est optionnel : il permet de rendre une couleur transparente.
Pour activer la transparence, il faut :

- d'abord utiliser la fonction glEnable GL_BLEND;

- puis préciser le mode de mélange de couleurs, par exemple : glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA.

Nous mettrons la transparence en pratique dans le chapitre sur l'affichage des textes.

XIV-B - Application d'une texture sur les faces du cube

Pour qu'OpenGL affiche les textures, il faut d'abord les activer.
On pourrait les activer et les désactiver à chaque fois qu'on en a besoin, mais comme on va tout texturer on va faire simple et activer les textures une fois à l'initialisation.
Donc, dans la procédure d'initialisation InitScene ajoutez :

Active les textures 2D

' Active les textures 2D
glEnable GL_TEXTURE_2D

Puis on effectue le chargement du fichier contenant la texture (une seule fois dans la procédure InitScene), grâce à la fonction créée précédemment.
On ajoute également en en-tête de module une variable pour conserver l'identifiant de la texture.

En en-tête de module

' Identifiant de la texture plancher
Private gTexturePlancher As Long

Chargement de la texture

' Charge la texture
gTexturePlancher = TextureAddFromFile(CurrentProject.Path & "\plancher.jpg")

Voici le fichier de la texture utilisée (c'est une photo de mon plancher) :

Ensuite il faut affecter la texture pour qu'elle soit utilisée lors du dessin du cube.
Avant le dessin du cube (avant glBegin), on affecte la texture :

Avant le glBegin du cube

' Affecte la texture
glBindTexture GL_TEXTURE_2D, gTexturePlancher

après le glEnd du cube

' N'affecte aucune texture
glBindTexture GL_TEXTURE_2D, 0

Il est souvent conseillé d'éviter de trop fréquents appels à glBindTexture.
Pensez à utiliser des méta textures.

On ne voit pas notre texture pour autant.
Et c'est normal, car nous n'avons pas défini de coordonnées de texture.

Il faut spécifier à OpenGL comment mapper la texture.
En effet la texture et le polygone sur lequel on souhaite l'appliquer ne correspondent pas forcément.
On pourrait demander d'appliquer une texture rectangulaire sur un triangle par exemple.

Dans le cas des faces du cube, c'est simple : on demande à ce que la texture recouvre toute la face.

Pour chaque vertice de la forme dessinée, on définit les coordonnées du texel correspondant (un texel c'est comme un pixel, mais pour une texture).

- (0,0) correspond au texel en bas à gauche de la texture.

- (1,1) correspond au texel en haut à droite de la texture.

Voici comment les points de la texture vont être affectés à la face avant par exemple :

On utilise une constante cT que l'on va ensuite modifier pour répéter la texture.

Code de dessin du cube avec ajout des textures

' Début de la primitive pour le cube
Const cT As Long = 1
glBegin GL_QUADS
' Face du haut = vert
Call glColor3d(0, 1, 0)
Call glNormal3d(0, 1, 0)
Call glTexCoord2f(cT, cT)
Call glVertex3d(1, 1, -1)
Call glTexCoord2f(0, cT)
Call glVertex3d(-1, 1, -1)
Call glTexCoord2f(0, 0)
Call glVertex3d(-1, 1, 1)
Call glTexCoord2f(cT, 0)
Call glVertex3d(1, 1, 1)
' Face du bas = orange
Call glColor3d(1, 0.5, 0)
Call glNormal3d(0, -1, 0)
Call glTexCoord2f(0, 0)
Call glVertex3f(-1, -1, -1)
Call glTexCoord2f(cT, 0)
Call glVertex3f(1, -1, -1)
Call glTexCoord2f(cT, cT)
Call glVertex3f(1, -1, 1)
Call glTexCoord2f(0, cT)
Call glVertex3f(-1, -1, 1)
' Face de derrière = rouge
Call glColor3d(1, 0, 0)
Call glNormal3d(0, 0, -1)
Call glTexCoord2f(0, 0)
Call glVertex3f(1, -1, -1)
Call glTexCoord2f(cT, 0)
Call glVertex3f(-1, -1, -1)
Call glTexCoord2f(cT, cT)
Call glVertex3f(-1, 1, -1)
Call glTexCoord2f(0, cT)
Call glVertex3f(1, 1, -1)
' Face de devant = jaune
Call glColor3d(1, 1, 0)
Call glNormal3d(0, 0, 1)
Call glTexCoord2f(0, 0)
Call glVertex3f(-1, -1, 1)
Call glTexCoord2f(cT, 0)
Call glVertex3f(1, -1, 1)
Call glTexCoord2f(cT, cT)
Call glVertex3f(1, 1, 1)
Call glTexCoord2f(0, cT)
Call glVertex3f(-1, 1, 1)
' Face de gauche = violet
Call glColor3d(1, 0, 1)
Call glNormal3d(-1, 0, 0)
Call glTexCoord2f(cT, cT)
Call glVertex3f(-1, 1, 1)
Call glTexCoord2f(0, cT)
Call glVertex3f(-1, 1, -1)
Call glTexCoord2f(0, 0)
Call glVertex3f(-1, -1, -1)
Call glTexCoord2f(cT, 0)
Call glVertex3f(-1, -1, 1)
' Face de droite = bleu
Call glColor3d(0, 0, 1)
Call glNormal3d(1, 0, 0)
Call glTexCoord2f(cT, cT)
Call glVertex3f(1, 1, -1)
Call glTexCoord2f(0, cT)
Call glVertex3f(1, 1, 1)
Call glTexCoord2f(0, 0)
Call glVertex3f(1, -1, 1)
Call glTexCoord2f(cT, 0)
Call glVertex3f(1, -1, -1)
glEnd

Les pixels ne correspondent pas toujours aux texels.
Il faut donc définir ensuite comment est lissée la texture.

On utilise pour cela la fonction glTexParameterf.
Remarque : cette fonction s'applique à la texture courante, il faut donc qu'elle ait été affectée avec glBindTexture.

Ajoutez ces lignes juste après l'appel à glBindTexture :

Filtrage de la texture

' Filtrage de la texture
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR

- GL_TEXTURE_MAG_FILTER définit le filtre lors d'un agrandissement de texture.

- GL_TEXTURE_MIN_FILTER définit le filtre lors d'une réduction de texture.

Les filtres disponibles sont :

- GL_NEAREST : utilise le texel le plus proche : plus rapide ;

- GL_LINEAR : utilise la moyenne des quatre texels les plus proches : plus beau (lissé).

Pour GL_TEXTURE_MIN_FILTER, il y a également les filtres de mipmapping GL_*_MIPMAP_*, mais nous ne le détaillons pas tout de suite.

La valeur de GL_TEXTURE_MAG_FILTER par défaut est déjà GL_LINEAR.
Mais la valeur de GL_TEXTURE_MIN_FILTER par défaut est GL_NEAREST_MIPMAP_LINEAR.

Voici donc notre cube texturé :

XIV-B-1 - Modes d'application d'une texture

On constate que la couleur de la texture s'est multipliée avec la couleur des faces.
On peut changer le mode d'application de la texture avec la fonction glTexEnv.
La fonction glTexEnv GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, param définit le mode d'application avec param qui prend les valeurs :

- GL_MODULATE : multiplie la couleur de texture avec celle du matériau ;

- GL_DECAL : utilise le canal alpha de la texture : à utiliser pour appliquer des textures avec transparence ;

- GL_BLEND : mélange la texture et la couleur ;

- GL_REPLACE : remplace la couleur par la texture : la couleur n'est plus visible.

Attention : contrairement à la fonction glTexParameterf, cette fonction glTexEnv s'applique à l'ensemble des textures.

Essayez les différents modes d'application de texture en ajoutant l'appel à la fonction par exemple après glBindTexture :

Mode d'application de la texture

' Mode d'application de la texture
glTexEnvi GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE

Avec le mode GL_REPLACE, on obtient la texture initiale sans coloration supplémentaire.
Par contre on n'a plus d'éclairage.
Pour éclairer la texture sans la colorer, il suffit de colorer les faces en blanc et de laisser le mode GL_MODULATE par défaut.

XIV-B-2 - Répétition de la texture

Telle que nous l'avons définie, la texture est redimensionnée pour occuper toute la face.
On souhaite répéter par exemple cinq fois la texture sur chaque face.
Ainsi on peut couvrir une grande surface avec une petite texture.

Il suffit de modifier les coordonnées de texture en leur donnant une valeur égale à 5.
Modifiez la valeur de la constante cT :

constante cT

Const cT As Long = 5

La texture est alors répétée cinq fois sur chaque face.

Si on ne souhaite pas répéter la texture, il faut changer les paramètres GL_TEXTURE_WRAP_S (horizontal) et GL_TEXTURE_WRAP_T (vertical).
Par défaut leur valeur est GL_REPEAT.
La valeur GL_CLAMP demande de borner les valeurs entre 0 et 1.

En ajoutant :

Pas de répétition de texture complète

' Pas de répétition de texture complète
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP

On obtient :

Retirez les deux dernières lignes ajoutées, on va conserver le mode de répétition GL_REPEAT (par défaut).

XIV-C - Application d'une texture sur une sphère

Pour appliquer une texture sur la sphère, nous n'allons pas définir les coordonnées de textures pour chaque vertice.

Ce serait compliqué et glu va nous faciliter le travail.

Voici l'image de la texture que nous allons appliquer :

Cette image provient de Visible Earth.
Elle a été redimensionnée à 256*128 pixels.

Ajoutez une variable pour stocker l'identifiant de la texture.

En en-tête de module

' Identifiant de la texture de la sphère
Private gTextureSphere As Long

Puis charger la texture dans la fonction d'initialisation :

Chargement de la texture

gTextureSphere = TextureAddFromFile(CurrentProject.Path & "\terre.jpg")

Ensuite lors du dessin de la sphère avec gluSphere il faut :

- affecter la texture avec glBindTexture ;

- préciser le mode de filtrage avec glTexParameter ;

- activer les textures avec gluQuadricTexture.

Nous utilisons le même code que celui qui a servi à dessiner la première sphère.
Mais on modifie la couleur de la sphère : on remplace la couleur jaune par une couleur blanche qui n'affectera pas la texture.

Et pour mieux la voir, on augmente sa taille à 1 au lieu de 0.5

Voici le code de dessin de la sphère avec texture, c'est une deuxième sphère qu'on dessine à l'opposé de la première :

Code de dessin de la sphère avec texture

' Dessine une sphère à l'opposé de la lampe
#If VBA7 Then
Dim lQuad2 As LongPtr
#Else
Dim lQuad2 As Long
#End If
glPushMatrix
glColor3VB vbWhite
glTranslated -3 * Cos(gLightRotate), 0, -3 * Sin(gLightRotate)
glBindTexture GL_TEXTURE_2D, gTextureSphere
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
lQuad2 = gluNewQuadric
gluQuadricTexture lQuad2, GL_TRUE
gluSphere lQuad2, 1, 20, 15
gluDeleteQuadric lQuad2
glPopMatrix

Et voici notre sphère texturée :

On remarque que la texture n'a pas été placée à partir du haut de la sphère.
La France se retrouve en bas de la sphère, les deux pôles face à nous et derrière.
On va donc tourner la sphère de 90° autour de l'axe X.

Rotation de la sphère de 90°

glRotated -90, 1, 0, 0

Attention, les transformations s'appliquent à l'envers.
C'est-à-dire que si on souhaite tourner la sphère puis la déplacer, il faut d'abord définir le déplacement puis la rotation.

La rotation s'effectue par rapport à l'origine, il faut que la sphère soit centrée sur l'origine pour la tourner autour de son centre.
La ligne précédente est donc à insérer après le déplacement de la sphère avec glTranslated.

Maintenant la France est placée là où on l'attend, et les deux pôles en haut et en bas.

XIV-D - Le mipmapping

Un petit mot sur le mipmapping

Le mipmapping est une technique utilisée pour choisir la texture appropriée en fonction de la taille de l'objet.
Pour un objet lointain, on peut se contenter d'une petite texture basse définition.
Et pour un objet proche on souhaite utiliser une texture de meilleure définition.

Lorsqu'on génère une texture avec glTexImage2D, on peut utiliser le deuxième paramètre level pour préciser le niveau de la texture.
La fonction glu gluBuild2DMipmaps permet de générer des images de textures à différentes dimensions.

Pour utiliser ensuite ces textures mipmaps, il faut le préciser dans le filtre de texture.
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, param avec comme valeur de param :

- GL_NEAREST_MIPMAP_NEAREST et GL_LINEAR_MIPMAP_NEAREST pour choisir la texture mipmap la plus appropriée ;

- GL_NEAREST_MIPMAP_LINEAR et GL_LINEAR_MIPMAP_LINEAR pour mélanger les deux textures mipmap les plus appropriées.

Le MipMapping peut résoudre certains problèmes de scintillement des textures.

XV - Utilisation des listes et des tableaux

Consultez le tutoriel La géométrie en OpenGL : vers les VertexBufferObject.
Il traite notamment des tableaux et des listes, ainsi que des VertexBufferObjets que nous ne verrons pas dans ce tutoriel (c'est une extension).

XV-A - Utilisation des tableaux

Pour dessiner notre cube, nous avons envoyé chaque information une par une.
Il est possible de définir des tableaux d'informations pour réduire les appels aux fonctions.

En VB, nous ne pouvons pas définir des tableaux fixes préremplis.
Pour simplifier un peu le code, nous définissons une petite fonction pour remplir nos tableaux :

Fonction de remplissage de tableau

' Fonction de remplissage de tableau
Public Function BuildArray(pArray, pValues As Variant)
Dim lCpt As Long
Dim lIndice As Long
lIndice = LBound(pArray)
For lCpt = LBound(pValues) To UBound(pValues)
pArray(lIndice) = pValues(lCpt)
lIndice = lIndice + 1
Next
End Function

On donnera à la fonction un paramètre pValues contenant les valeurs sous forme de tableau dynamique (Array).
On recevra dans le paramètre pArray le tableau rempli.

Ajoutons la définition des tableaux en en-tête de module :

En en-tête de module

' Tableaux pour le cube
Private VertexArray(1 To 72) As Single  ' 24 vertices * 3 coordonnées
Private ColorArray(1 To 72) As Single  ' 24 couleurs * 3 composantes
Private NormalArray(1 To 72) As Single  ' 24 normales * 3 coordonnées
Private TexCoordArray(1 To 48) As Single  ' 24 texels * 2 coordonnées
Private IndiceArray(1 To 24) As Long  ' 6 faces * 4 vertex

Les tableaux sont initialisés dans la fonction InitScene :

Initialise les tableaux pour le cube

' Initialise les tableaux pour le cube
Const cT As Long = 5
BuildArray VertexArray, Array( _
1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, _
-1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, _
1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, _
-1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, _
-1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, _
1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1)
BuildArray ColorArray, Array( _
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, _
1, 0.5, 0, 1, 0.5, 0, 1, 0.5, 0, 1, 0.5, 0, _
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, _
1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, _
1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, _
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1)
BuildArray NormalArray, Array( _
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, _
0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, _
0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, _
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, _
-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, _
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
BuildArray TexCoordArray, Array( _
cT, cT, 0, cT, 0, 0, cT, 0, _
0, 0, cT, 0, cT, cT, 0, cT, _
0, 0, cT, 0, cT, cT, 0, cT, _
0, 0, cT, 0, cT, cT, 0, cT, _
cT, cT, 0, cT, 0, 0, cT, 0, _
cT, cT, 0, cT, 0, 0, cT, 0)
BuildArray IndiceArray, Array( _
0, 1, 2, 3, _
4, 5, 6, 7, _
8, 9, 10, 11, _
12, 13, 14, 15, _
16, 17, 18, 19, _
20, 21, 22, 23)

Il y a un tableau par information :

- les vertices ;

- les couleurs ;

- les normales ;

- les coordonnées de texture.

Et un tableau d'indices qui définit les faces.
Ces indices démarrent à partir de 0.

Pour dessiner le cube à partir des tableaux il faut :

- activer chacun des tableaux utilisés avec la fonction glEnableClientState ;

- définir chaque tableau avec la fonction gl*pointer correspondante ;

- dessiner les éléments avec la fonction glDrawElements ;

- désactiver les tableaux avec la fonction glDisableClientState.

Le code de dessin du cube devient :

Code de dessin du cube avec les tableaux

glEnableClientState GL_VERTEX_ARRAY
glEnableClientState GL_COLOR_ARRAY
glEnableClientState GL_NORMAL_ARRAY
glEnableClientState GL_TEXTURE_COORD_ARRAY

glVertexPointer 3, GL_FLOAT, 0, VertexArray(1)
glColorPointer 3, GL_FLOAT, 0, ColorArray(1)
glNormalPointer GL_FLOAT, 0, NormalArray(1)
glTexCoordPointer 2, GL_FLOAT, 0, TexCoordArray(1)

glDrawElements GL_QUADS, 24, GL_UNSIGNED_INT, IndiceArray(1)

glDisableClientState GL_VERTEX_ARRAY
glDisableClientState GL_COLOR_ARRAY
glDisableClientState GL_NORMAL_ARRAY
glDisableClientState GL_TEXTURE_COORD_ARRAY

Dans l'appel à glDrawElements on précise qu'on définit des quads, et qu'il y a 24 vertices.

Dans notre exemple, nous n'avons pas réduit le nombre d'informations, chaque vertice possédant ses propres informations.
Quel intérêt alors d'utiliser les tableaux ?
Avec les tableaux, on peut réduire le nombre d'informations si plusieurs vertices sont identiques (mêmes coordonnées, même couleur…).
Il suffit alors d'utiliser plusieurs fois le même indice dans le tableau IndiceArray.
Nous pouvons également facilement modifier l'objet dynamiquement, en modifiant les informations dans les tableaux.

XV-B - Utilisation des listes

Les listes d'affichage permettent de stocker des instructions de dessin.
On peut ainsi utiliser une liste d'affichage pour dessiner rapidement un même objet plusieurs fois.

Il est possible de combiner les tableaux et les listes, ce que nous allons faire.

Une liste est créée à l'aide de la fonction glGenLists, en donnant en paramètre le nombre de listes à créer.
La fonction renvoie le numéro de la première liste créée.

Ajoutons une variable en en-tête pour sauvegarder cet identifiant :

En en-tête de module

' Identifiant de la liste pour le cube
Private gListCube As Long

Les déclarations de tableaux n'ont plus besoin d'être globales, on peut les déplacer dans la fonction InitScene.

On commence ensuite une liste avec la fonction glNewList.
On la termine avec la fonction glEndList.
Entre ces deux fonctions, on place le code de dessin du cube.

Voici le code de création de la liste d'affichage du cube :

Création de la liste d'affichage du cube

' Crée la liste pour le cube
Const cT As Long = 5
Dim VertexArray(1 To 72) As Single  ' 24 vertices * 3 coordonnées
Dim ColorArray(1 To 72) As Single  ' 24 couleurs * 3 composantes
Dim NormalArray(1 To 72) As Single  ' 24 normales * 3 coordonnées
Dim TexCoordArray(1 To 48) As Single  ' 24 texels * 2 coordonnées
Dim IndiceArray(1 To 24) As Long  ' 6 faces * 4 vertex
BuildArray VertexArray, Array( _
1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, _
-1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, _
1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, _
-1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, _
-1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, _
1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1)
BuildArray ColorArray, Array( _
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, _
1, 0.5, 0, 1, 0.5, 0, 1, 0.5, 0, 1, 0.5, 0, _
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, _
1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, _
1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, _
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1)
BuildArray NormalArray, Array( _
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, _
0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, _
0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, _
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, _
-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, _
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
BuildArray TexCoordArray, Array( _
cT, cT, 0, cT, 0, 0, cT, 0, _
0, 0, cT, 0, cT, cT, 0, cT, _
0, 0, cT, 0, cT, cT, 0, cT, _
0, 0, cT, 0, cT, cT, 0, cT, _
cT, cT, 0, cT, 0, 0, cT, 0, _
cT, cT, 0, cT, 0, 0, cT, 0)
BuildArray IndiceArray, Array( _
0, 1, 2, 3, _
4, 5, 6, 7, _
8, 9, 10, 11, _
12, 13, 14, 15, _
16, 17, 18, 19, _
20, 21, 22, 23)
gListCube = glGenLists(1)
glNewList gListCube, GL_COMPILE
glEnableClientState GL_VERTEX_ARRAY
glEnableClientState GL_COLOR_ARRAY
glEnableClientState GL_NORMAL_ARRAY
glEnableClientState GL_TEXTURE_COORD_ARRAY

glVertexPointer 3, GL_FLOAT, 0, VertexArray(1)
glColorPointer 3, GL_FLOAT, 0, ColorArray(1)
glNormalPointer GL_FLOAT, 0, NormalArray(1)
glTexCoordPointer 2, GL_FLOAT, 0, TexCoordArray(1)

glDrawElements GL_QUADS, 24, GL_UNSIGNED_INT, IndiceArray(1)

glDisableClientState GL_VERTEX_ARRAY
glDisableClientState GL_COLOR_ARRAY
glDisableClientState GL_NORMAL_ARRAY
glDisableClientState GL_TEXTURE_COORD_ARRAY
glEndList

Une fois la liste créée, il suffit de l'appeler avec la fonction glCallList pour effectuer le dessin.

Dessin du cube

' Dessin du cube
glCallList gListCube

Il est également possible d'afficher plusieurs listes en un seul appel à la fonction glCallLists (nous l'utiliserons pour l'affichage des textes).

Avec les listes d'affichage, il devient facile (et rapide) de dessiner plusieurs fois le même objet.
Dessinons 10 cubes en les décalant légèrement :

Dessin de 10 cubes

' Dessin de 10 cubes
Dim lCptCube As Long
For lCptCube = 1 To 10
glCallList gListCube
glTranslated 0, 0, -3
Next

Par contre, les listes d'affichage ne conviennent pas si on souhaite modifier dynamiquement l'objet.
La liste ainsi créée est statique.

XVI - Rappel du code complet

Un rappel du contenu du module VB à ce stade du tutoriel, avec l'affichage de dix cubes en utilisant les listes et les tableaux :

Rappel du code complet

Option Explicit

' Temps écoulé pour calcul du frame rate
Private gElapsedTime As Long
' Nombre d'affichages pour calcul du frame rate
Private gDisplayCount As Long
' RotationX
Private gRotateX As Double
' RotationY
Private gRotateY As Double
' Zoom
Private gZoom As Double
' RotationY de la lampe
Private gLightRotate As Double
' Identifiant de la texture plancher
Private gTexturePlancher As Long
' Identifiant de la texture de la sphère
Private gTextureSphere As Long
' Identifiant de la liste pour le cube
Private gListCube As Long

Function FonctionOpenGL()
' Chargement de freeGLUT
If Not LoadFreeGlut(CurrentProject.Path) Then
MsgBox "Impossible de charger la bibliothèque freeGLUT"
Exit Function
End If
' Initialisation de la bibliothèque
glutInit 0&, ""
' Initialisation du mode d'affichage
glutInitDisplayMode GLUT_RGBA Or GLUT_DOUBLE Or GLUT_DEPTH
' Définition de l'option de sortie de boucle
glutSetOption GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS
' Création d'une fenêtre
glutCreateWindow "Tutoriel fenêtre GLUT"
' Fonction d'affichage
glutDisplayFunc AddressOf CallBackDraw
' Fonctions de rappel clavier
glutSpecialFunc AddressOf CallBackSpecial
' Fonction de rappel molette de souris
glutMouseWheelFunc AddressOf CallBackMouseWheel
' Appel de la fonction d'initialisation
InitScene
' Fonction d'attente
glutTimerFunc 1000 / 50, AddressOf CallBackTimer, 0
' Boucle principale
glutMainLoop
End Function

' Fonction d'attente
Public Sub CallBackTimer(ByVal value As Long)
' Rappel de la fonction d'attente
glutTimerFunc 1000 / 50, AddressOf CallBackTimer, 0
' Rafraichir l'affichage si une fenêtre openGL est active
If glutGetWindow <> 0 Then glutPostRedisplay
End Sub

' Fonction d'affichage
Public Sub CallBackDraw()
' Appel de la fonction de rendu
Call Render
' Échange 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

' Fonction d'initialisation
Public Sub InitScene()
' Mode d'affichage = remplissage
glPolygonMode GL_FRONT_AND_BACK, GL_FILL
' Tests de profondeur
glEnable GL_DEPTH_TEST
glDepthFunc GL_LEQUAL
' Initialisation des variables de visualisation
gRotateX = 0
gRotateY = 0
gZoom = 10
' Initialisation de l'éclairage
glEnable GL_LIGHTING
glEnable GL_LIGHT0
' Ajout d'une lumière diffuse
Dim lColor(1 To 4) As Single
lColor(1) = 2
lColor(2) = 2
lColor(3) = 2
lColor(4) = 1
glLightfv GL_LIGHT0, GL_DIFFUSE, lColor(1)
' Active la coloration des matériaux
glEnable GL_COLOR_MATERIAL
' Active les textures 2D
glEnable GL_TEXTURE_2D
' Charge les textures
gTexturePlancher = TextureAddFromFile(CurrentProject.Path & "\plancher.jpg")
gTextureSphere = TextureAddFromFile(CurrentProject.Path & "\terre.jpg")
' Crée la liste pour le cube
Const cT As Long = 5
Dim VertexArray(1 To 72) As Single  ' 24 vertices * 3 coordonnées
Dim ColorArray(1 To 72) As Single  ' 24 couleurs * 3 composantes
Dim NormalArray(1 To 72) As Single  ' 24 normales * 3 coordonnées
Dim TexCoordArray(1 To 48) As Single  ' 24 texels * 2 coordonnées
Dim IndiceArray(1 To 24) As Long  ' 6 faces * 4 vertex
BuildArray VertexArray, Array( _
1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, _
-1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, _
1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, _
-1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, _
-1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, _
1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1)
BuildArray ColorArray, Array( _
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, _
1, 0.5, 0, 1, 0.5, 0, 1, 0.5, 0, 1, 0.5, 0, _
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, _
1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, _
1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, _
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1)
BuildArray NormalArray, Array( _
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, _
0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, _
0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, _
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, _
-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, _
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0)
BuildArray TexCoordArray, Array( _
cT, cT, 0, cT, 0, 0, cT, 0, _
0, 0, cT, 0, cT, cT, 0, cT, _
0, 0, cT, 0, cT, cT, 0, cT, _
0, 0, cT, 0, cT, cT, 0, cT, _
cT, cT, 0, cT, 0, 0, cT, 0, _
cT, cT, 0, cT, 0, 0, cT, 0)
BuildArray IndiceArray, Array( _
0, 1, 2, 3, _
4, 5, 6, 7, _
8, 9, 10, 11, _
12, 13, 14, 15, _
16, 17, 18, 19, _
20, 21, 22, 23)
gListCube = glGenLists(1)
glNewList gListCube, GL_COMPILE
glEnableClientState GL_VERTEX_ARRAY
glEnableClientState GL_COLOR_ARRAY
glEnableClientState GL_NORMAL_ARRAY
glEnableClientState GL_TEXTURE_COORD_ARRAY

glVertexPointer 3, GL_FLOAT, 0, VertexArray(1)
glColorPointer 3, GL_FLOAT, 0, ColorArray(1)
glNormalPointer GL_FLOAT, 0, NormalArray(1)
glTexCoordPointer 2, GL_FLOAT, 0, TexCoordArray(1)

glDrawElements GL_QUADS, 24, GL_UNSIGNED_INT, IndiceArray(1)

glDisableClientState GL_VERTEX_ARRAY
glDisableClientState GL_COLOR_ARRAY
glDisableClientState GL_NORMAL_ARRAY
glDisableClientState GL_TEXTURE_COORD_ARRAY
glEndList
End Sub

' Fonction de rendu
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
' Positionne la lampe
gLightRotate = gLightRotate +
Dim lposition(1 To 4) As Single
lposition(1) = 3 * Cos(gLightRotate)
lposition(2) = 0
lposition(3) = 3 * Sin(gLightRotate)
lposition(4) = 1
glLightfv GL_LIGHT0, GL_POSITION, lposition(1)
' Dessine une sphère à l'endroit où se trouve la lampe
#If VBA7 Then
Dim lQuad As LongPtr
#Else
Dim lQuad As Long
#End If
glPushMatrix
glColor3VB vbYellow
glTranslated 3 * Cos(gLightRotate), 0, 3 * Sin(gLightRotate)
lQuad = gluNewQuadric
gluQuadricOrientation lQuad, GLU_INSIDE
gluSphere lQuad, 0.5, 20, 15
gluDeleteQuadric lQuad
glPopMatrix
' Dessine une sphère à l'opposé de la lampe
#If VBA7 Then
Dim lQuad2 As LongPtr
#Else
Dim lQuad2 As Long
#End If
glPushMatrix
glColor3VB vbWhite
glTranslated -3 * Cos(gLightRotate), 0, -3 * Sin(gLightRotate)
glRotated -90, 1, 0, 0
glBindTexture GL_TEXTURE_2D, gTextureSphere
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
lQuad2 = gluNewQuadric
gluQuadricTexture lQuad2, GL_TRUE
gluSphere lQuad2, 1, 20, 15
gluDeleteQuadric lQuad2
glPopMatrix
' Sauvegarde la matrice
glPushMatrix
' Rotation du cube
glRotated 30, 1, 1, 1
' Affecte la texture
glBindTexture GL_TEXTURE_2D, gTexturePlancher
' Filtrage de la texture
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
' Dessin de 10 cubes
Dim lCptCube As Long
For lCptCube = 1 To 10
glCallList gListCube
glTranslated 0, 0, -3
Next
' N'affecte aucune texture
glBindTexture GL_TEXTURE_2D, 0
' Restaure la matrice
glPopMatrix
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

' Fonction de remplissage de tableau
Public Function BuildArray(pArray, pValues As Variant)
Dim lCpt As Long
Dim lIndice As Long
lIndice = LBound(pArray)
For lCpt = LBound(pValues) To UBound(pValues)
pArray(lIndice) = pValues(lCpt)
lIndice = lIndice + 1
Next
End Function

XVII - L'affichage de textes

Il y a deux façons d'afficher du texte :

- afficher un bitmap (c'est-à-dire une image) représentant chaque lettre du texte ;

- afficher chaque lettre du texte en 3D avec des vertices.

Si le texte est fixe, il est également possible de créer le texte sous forme d'une image (avec Paint par exemple), et d'appliquer une texture sur un rectangle blanc pour afficher ce texte.

Si on souhaite afficher un texte dynamique, il faut afficher chaque lettre du texte sur un rectangle.
On crée alors une texture contenant la table des caractères et on précise les coordonnées de texture correspondant au caractère à dessiner.

XVII-A - Affichage d'un texte bitmap statique

Voici l'image de texture utilisée :

C'est un simple texte rouge sur fond noir créé avec Paint.

Comme pour les autres textures, on la charge et on sauvegarde l'identifiant dans une variable :

En en-tête de module

' Identifiant de la texture du texte statique
Private gTextureTexte As Long

Chargement de la texture du texte statique dans InitScene

gTextureTexte = TextureAddFromFile(CurrentProject.Path & "\texte.bmp")

Ensuite, à la fin de la procédure de rendu Render, on crée un rectangle et on lui applique la texture :

Création du rectangle avec le texte statique en texture

' Affichage d'un texte sur un rectangle
' Sauvegarde la matrice
glPushMatrix
' Affecte la texture
glBindTexture GL_TEXTURE_2D, gTextureTexte
' Filtrage de la texture
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
' Dessin d'un rectangle
glColor3VB vbWhite
glBegin GL_QUADS
glTexCoord2d 0, 0
glVertex2d -3, -0.5
glTexCoord2d 1, 0
glVertex2d -1, -0.5
glTexCoord2d 1, 1
glVertex2d -1, 0.5
glTexCoord2d 0, 1
glVertex2d -3, 0.5
glEnd
' N'affecte aucune texture
glBindTexture GL_TEXTURE_2D, 0
' Restaure la matrice
glPopMatrix

Ne pas oublier d'affecter une couleur blanche au rectangle (car on est en mode GL_MODULATE pour l'application des textures).

Le texte est alors affiché sur un rectangle qui est éclairé et qui tourne avec le reste de la scène :

On souhaite afficher un texte d'information qui est éclairé en permanence uniformément.
On va donc déjà désactiver la lumière avant l'affichage du texte,et la réactiver ensuite.

Avant le dessin du texte

' Désactive la lumière
glDisable GL_LIGHTING

Après le dessin du texte

' Active la lumière
glEnable GL_LIGHTING

Ainsi le rectangle est éclairé par la lumière ambiante par défaut.

Si on souhaite rendre le fond noir transparent, il faut :

- charger la texture avec le noir transparent (deuxième paramètre) :
gTextureTexte = TextureAddFromFile(CurrentProject.Path & "\texte.bmp", vbBlack);

- activer la gestion de la transparence avant rendu du texte (ou à l'initialisation de la scène si on laisse la transparence activée tout le temps) :
glEnable GL_BLEND
glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA.

Création du rectangle avec le texte statique en texture et avec transparence

' Affichage d'un texte sur un rectangle
' Désactive la lumière
glDisable GL_LIGHTING
' Sauvegarde la matrice
glPushMatrix
' Affecte la texture
glBindTexture GL_TEXTURE_2D, gTextureTexte
' Filtrage de la texture
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
' Active la transparence
glEnable GL_BLEND
glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
' Dessin d'un rectangle
glColor3VB vbWhite
glBegin GL_QUADS
glTexCoord2d 0, 0
glVertex2d -3, -0.5
glTexCoord2d 1, 0
glVertex2d -1, -0.5
glTexCoord2d 1, 1
glVertex2d -1, 0.5
glTexCoord2d 0, 1
glVertex2d -3, 0.5
glEnd
' N'affecte aucune texture
glBindTexture GL_TEXTURE_2D, 0
' Restaure la matrice
glPopMatrix
' Active la lumière
glEnable GL_LIGHTING
' Désactive la transparence
glDisable GL_BLEND

Le texte est maintenant affiché sur un fond transparent :

La méthode avec GL_BLEND est générique, car elle permet de gérer l'affichage translucide.
Pour un affichage totalement transparent, préférez l'utilisation de l'alpha-test (GL_ALPHA_TEST).

On souhaite maintenant afficher un texte d'information à un endroit précis de l'écran.
C'est-à-dire qu'on souhaite que le texte s'affiche par exemple en bas à gauche, et ne se déplace pas lorsqu'on tourne ou redimensionne la scène.

Pour y arriver, on va passer en mode de projection orthonormée le temps de dessiner le texte.
On met en place une projection orthonormée (glOrtho) dont le plan est défini par les coordonnées (-1,-1) et (1,1).

Puis on se place (gluLookAt) au centre un peu en retrait.

On peut alors définir notre rectangle en fonction du plan de projection, (-1,-1) étant le point en bas à gauche :

Création du rectangle avec le texte statique en texture, avec transparence, et fixe en bas à gauche

' Affichage d'un texte statique sur un rectangle
' Passage en matrice de projection
glMatrixMode GL_PROJECTION
' Projection orthonormée
glLoadIdentity
glOrtho -1, 1, -1, 1, 0, 0
' Retour en matrice de visualisation-modélisation
glMatrixMode GL_MODELVIEW
glLoadIdentity
gluLookAt 0, 0, 1, 0, 0, 0, 0, 1, 0
' Désactive la lumière
glDisable GL_LIGHTING
' Sauvegarde la matrice
glPushMatrix
' Affecte la texture
glBindTexture GL_TEXTURE_2D, gTextureTexte
' Filtrage de la texture
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
' Active la transparence
glEnable GL_BLEND
glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
' Dessin d'un rectangle en bas à gauche
glColor3VB vbWhite
glBegin GL_QUADS
glTexCoord2d 0, 0
glVertex2d -1, -1
glTexCoord2d 1, 0
glVertex2d -0.4, -1
glTexCoord2d 1, 1
glVertex2d -0.4, -0.8
glTexCoord2d 0, 1
glVertex2d -1, -0.8
glEnd
' N'affecte aucune texture
glBindTexture GL_TEXTURE_2D, 0
' Restaure la matrice
glPopMatrix
' Active la lumière
glEnable GL_LIGHTING
' Désactive la transparence
glDisable GL_BLEND

Le texte est dessiné en bas à gauche, et y reste même si on tourne autour de la scène :

Voici donc une méthode assez simple pour afficher un texte statique, qui peut également être utilisée pour afficher un logo par exemple.

XVII-B - Affichage d'un texte bitmap dynamique avec freeGLUT

Pour la suite, nous souhaitons afficher le frame rate non plus dans la barre de titre de la fenêtre, mais sur l'image en haut à gauche.
freeGLUT met à notre disposition une fonction très facile à utiliser : glutBitmapString.

Pour positionner le texte, il faut utiliser une fonction glRasterPos* (car la fonction de dessin de texte de freeGLUT utilise la fonction glBitmap).

On conserve notre projection orthonormée : donc pour placer le texte en haut à gauche il faut le placer :

- en X à -1 ;

- en Y à 1 - (la hauteur du texte).

La hauteur du texte peut être récupérée en pixels avec la fonction glutBitmapHeight.
La hauteur de la fenêtre peut être récupérée en pixels avec la fonction glutGet(GLUT_WINDOW_HEIGHT).

Une division nous donnera la taille du texte dans l'unité openGL.

D'après la définition du plan de la projection :

- le bas de la fenêtre est à une ordonnée de -1 ;

- le haut de la fenêtre est à une ordonnée de 1.

Donc on a 1 unité pour une demi-hauteur d'une fenêtre :
glutBitmapHeight(GLUT_BITMAP_TIMES_ROMAN_24) / (glutGet(GLUT_WINDOW_HEIGHT) / 2) est la taille du texte en unités OpenGL

Commençons par retirer le frame rate de la barre de titre.
Après calcul, on le sauvegarde dans une variable.

En en-tête de module

' Frame rate
Private gFrameRate As Double

Calcul et sauvegarde le frame rate

' Calcul et affichage du frame rate
gDisplayCount = gDisplayCount + 1
If gDisplayCount = 100 Then
gFrameRate = gDisplayCount / (glutGet(GLUT_ELAPSED_TIME) - gElapsedTime) * 1000
gDisplayCount = 0
gElapsedTime = glutGet(GLUT_ELAPSED_TIME)
End If

Et on affiche ce frame rate en haut à gauche, en couleur bleue :
(Ajoutez ce code dans la procédure render.)

Affichage du frame rate en haut à gauche

' Affichage du frame rate
glColor3VB vbBlue
glRasterPos2d -1, 1 - glutBitmapHeight(GLUT_BITMAP_TIMES_ROMAN_24) / (glutGet(GLUT_WINDOW_HEIGHT) / 2)
glutBitmapString GLUT_BITMAP_TIMES_ROMAN_24, "Frame rate : " & format(gFrameRate, "0,0")

Au lieu de GLUT_BITMAP_TIMES_ROMAN_24, on peut choisir :

GLUT_BITMAP_9_BY_15, GLUT_BITMAP_8_BY_13 , GLUT_BITMAP_TIMES_ROMAN_10, GLUT_BITMAP_TIMES_ROMAN_24, GLUT_BITMAP_HELVETICA_10, GLUT_BITMAP_HELVETICA_12 ou GLUT_BITMAP_HELVETICA_18.

Pour positionner le texte, on peut également utiliser glutBitmapLength pour obtenir la largeur du texte.

Voici le frame rate affiché sur l'image :

freeGLUT nous offre donc une fonction très simple pour afficher du texte.

XVII-C - Affichage d'un texte bitmap dynamique avec Windows

Sous Windows, nous avons à notre disposition la fonction wglUseFontBitmaps.
Cette fonction permet de générer des listes d'affichage contenant les caractères d'une police.

Il faut d'abord créer une police de caractères avec gdi32.
Ensuite l'appel à wglUseFontBitmaps génère les listes d'affichage.
Et enfin on affiche le texte en appelant ces listes avec glCallLists.

Importez le module standard ModOpenGLFontTools
Ce module contient deux fonctions VB de gestion des textes :

- une fonction CreateFontBitmap qui crée les listes ;

- une fonction DrawText qui affiche le texte.

La création d'une police de caractères se fait avec la fonction gdi32 CreateFont.

Il nous suffit maintenant :

- d'ajouter en en-tête du module principal la déclaration de l'identifiant de liste d'affichage :

En en-tête de module

' Identifiant de la liste pour le texte
Private gListTexte As Long

- de créer les listes d'affichage dans la procédure d'initialisation InitScene :

Dans InitScene

' Création des listes pour le texte
gListTexte = CreateFontBitmap("Arial")

- et enfin d'appeler la fonction DrawText (au lieu de glutBitmapString) pour afficher le texte :

Dessin du texte affichant le frame rate

' Affichage du frame rate
glColor3VB vbBlue
glRasterPos2d -1, 1 - 20 / (glutGet(GLUT_WINDOW_HEIGHT) / 2)
DrawText "Frame rate : " & format(gFrameRate, "0,0"), gListTexte

Nous n'avons pas ici de fonction simple à notre disposition pour retrouver la taille du texte.
J'ai mis 20 empiriquement.

XVII-D - Affichage d'un texte 3D dynamique avec Windows

Sous Windows, nous avons à notre disposition la fonction wglUseFontOutlines.
Cette fonction permet de générer des listes d'affichage contenant les caractères d'une police sous forme 3D.
C'est-à-dire avec des vertices et même une épaisseur.

Il faut d'abord, comme pour la fonction précédente, créer une police de caractères avec gdi32.
Ensuite l'appel à wglUseFontOutlines génère les listes d'affichage.
Et enfin on affiche le texte en appelant ces listes avec glCallLists.

Importez le module standard ModOpenGLFontTools
Ce module contient une fonction VB de gestion des textes 3D, la fonction CreateFontOutline qui crée les listes.

Il nous suffit maintenant :

- de remplacer l'appel à CreateFontBitmap par un appel à CreateFontOutline :

Dans InitScene

' Création des listes pour le texte
' On extrude de 0.3 pour donner de la profondeur au texte
gListTexte = CreateFontOutline("Arial", 0.3)

- et de modifier le positionnement du texte.

On utilise pour les textes 3D les fonctions de transformation de la matrice de modélisation-visualisation :

Dessin du texte 3D affichant le frame rate

' Affichage du frame rate
glColor3VB vbYellow
glPushMatrix
glTranslated -1, 1 - 20 / (glutGet(GLUT_WINDOW_HEIGHT) / 2), 0
glScaled 0.2, 0.2, 0.2
DrawText "Frame rate : " & format(gFrameRate, "0,0"), gListTexte
glPopMatrix

On ne voit pas beaucoup de changement, le texte semble toujours en 2D.
C'est normal, car on dessine le texte avec une projection orthonormée.
Pour voir le texte en 3D, il faut l'afficher avant le passage dans cette projection.

Dessin du texte 3D affichant le frame rate, avant le changement de projection

' Affichage du frame rate en 3D
glColor3VB vbYellow
glPushMatrix
glTranslated -3, 2, 2
DrawText "Frame rate : " & format(gFrameRate, "0,0"), gListTexte
glPopMatrix

Le texte s'affiche alors en 3D, et suit les transformations de la scène :

XVII-E - Affichage d'un texte bitmap dynamique : méthode générique

Les techniques que nous venons de voir nécessitent freeGLUT ou Windows.
Comment faire pour dessiner du texte de manière plus générique ?

Il faut générer « à la main » les listes d'affichage.
Pour nous aider, on peut utiliser des outils de création de textures à partir de polices de caractères.
Par exemple : Bitmap Font Builder permet de générer une image contenant une table de caractères.
On obtient une image de ce type :

Et en parallèle on peut extraire la taille de chaque caractère dans un fichier texte.

Il faut ensuite générer une liste par caractère.
Chaque liste est un quad contenant quatre vertices et les quatre coordonnées de la texture qui correspondent au caractère.
La taille du quad et les coordonnées de texture sont fonction de la position du caractère dans la table et de la largeur de ce caractère.

L'idée est de se créer un module réutilisable de gestion des textures pour faire toutes ces opérations.

Si vous avez bien suivi jusqu'ici, vous devriez être capable de programmer un tel module.

XVIII - Intégrer la fenêtre freeGLUT dans un formulaire

Il est possible d’intégrer la fenêtre freeGLUT dans un formulaire.

Ceci permet de bénéficier des fonctions de freeGLUT (notamment la gestion du clavier, souris…) tout en ayant une fenêtre intégrée à l’application.

Pour masquer la barre de titre et les bordures de la fenêtre freeGLUT, utilisez les options GLUT_BORDERLESS et GLUT_CAPTIONLESS lors de l’appel à la fonction glutInitDisplayMode

XVIII-A - Pour Access

Utilisez la fonction putInForm du module de classe clOpenGLGlutInAccessForm.

Cette fonction demande en arguments :

- l’identifiant de fenêtre freeGLUT (celui retourné par glutCreateWindow) ;

- le formulaire Access ;

- le numéro de section (acDetail, acHeader ou acFooter).

Intégration d’une fenêtre freeGLUT dans la section détail d’un formulaire Access

' Création d'une fenêtre
lWindow = glutCreateWindow("Tutoriel fenêtre GLUT")
' Déplace la fenêtre dans le formulaire
Dim loOpenGLForm As New clOpenGLGlutInAccessForm
loOpenGLForm.putInForm lWindow, Forms("frmOpenGL"), acDetail

Il est impératif de définir la propriété « Sur chargement » du formulaire à « [Procédure événementielle] ».

Sinon il faut détruire la fenêtre freeGLUT manuellement.

Pour que la fenêtre freeGLUT se redimensionne automatiquement, définissez la propriété « Sur redimensionnement » du formulaire à « [Procédure événementielle] ».

XVIII-B - Pour un UserForm (Excel, Word…)

Utilisez la fonction putInFrame du module de classe clOpenGLGlutInUserForm.

Cette fonction demande en arguments :

- l’identifiant de fenêtre freeGLUT (celui retourné par glutCreateWindow) ;

- un contrôle Frame.

La fenêtre freeGLUT est intégrée dans la Frame définie.

Comme il n’est pas possible de capturer l’événement de fermeture du formulaire dans le module de classe, il est nécessaire de créer ce dernier dans le formulaire, sinon la fenêtre freeGLUT n’est pas fermée.

Code dans le UserForm

Dim loGlutInForm As clOpenGLGlutInUserForm

Public Sub putFreeGlutInMe(pWindow As Long)
Set loGlutInForm = new clOpenGLGlutInUserForm
loGlutInForm.putInFrame pWindow, Me.Frame1
End Sub

Intégration d’une fenêtre freeGLUT dans une Frame

' Création d'une fenêtre
lWindow = glutCreateWindow("Tutoriel fenêtre GLUT")
' Déplace la fenêtre dans la frame du formulaire FormOpenGL
FormOpenGL.Show
FormOpenGL.putFreeGlutInMe lGlutWindow

XIX - Affichage dans un formulaire sans freeGLUT

Jusqu'ici nous avons affiché la scène dans une nouvelle fenêtre gérée par freeGLUT.
Il est possible de l'afficher dans un formulaire.
Cela nécessite l'utilisation de quelques fonctions de l'API Windows que j'ai regroupées dans des modules de classe faciles à utiliser.

Notez que l'affichage dans un contrôle image est beaucoup plus lent, car il faut lire les pixels de la scène et les injecter dans le contrôle.
Le frame rate chute sensiblement, mais on pourra s'en contenter selon les besoins.

XIX-A - Affichage dans un contrôle image ou une section Access

Ce chapitre s'applique uniquement à Access.

Importez le module de classe clOpengGLFormAccess
Ce module contient les fonctions nécessaires à l'initialisation d'OpenGL et l'affichage de l'image dans un contrôle ou une section.

Pour l'utiliser, créez un nouveau formulaire avec un contrôle image nommé Image0.

Déclarez un objet en en-tête de module du formulaire (mettre la propriété Avec Code à Oui pour le formulaire) :

En en-tête de formulaire

Private oGL As clOpengGLFormAccess

Sur chargement du formulaire, initialisez OpenGL :

Initialisation d'OpenGL

Private Sub Form_Load()
' Crée l'objet pour dessin dans le contrôle image
Set oGL = New clOpengGLFormAccess
' Initialisation d'openGL
oGL.InitOpenGL Me.Image0
End Sub

La fonction InitOpenGL crée une fenêtre masquée de même taille que le contrôle image.
Cette fenêtre sera utilisée par OpenGL.

Pour afficher la scène sur une section, utilisez la fonction InitOpenGLSection et précisez le formulaire et la section sur laquelle vous souhaitez dessiner.
Toute la section est alors utilisée pour dessiner la scène.
L'affichage est aussi rapide que sur une fenêtre avec freeGLUT.

Ensuite on peut exécuter les commandes OpenGL pour dessiner.

Par exemple, le dessin du triangle :

Dessin du triangle

' 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

L'affichage se fait à l'aide d'une des fonctions Display* :

Display : affichage dans le contrôle :
les pixels de la scène sont injectés dans le contrôle.
Il peut y avoir des clignotements de l'image à des fréquences d'affichage élevées.

DisplayNoRepaint : affichage dans le contrôle avec réduction des clignotements :
effectue en plus un dessin par-dessus le contrôle et annule le rafraîchissement du formulaire.
Cela évite des scintillements de l'image qui peuvent apparaître avec la fonction Display.

DisplayFast : affichage par-dessus le contrôle :
effectue un dessin par-dessus le contrôle.
Cet affichage n'est pas persistant, mais peut s'avérer plus rapide et ne présente pas de problème de clignotements de l'image.

Si la scène est affichée sur une section (initialisation avec la fonction InitOpenGLSection), alors ces trois fonctions ont le même effet.
L'affichage n'est pas persistant lors du dessin sur une section.

Testons avec la fonction Display :

Affichage dans le contrôle

' Affichage dans le contrôle
oGL.Display

Voici le triangle affiché dans un contrôle image standard :

XIX-B - Affichage dans un contrôle Image ou Frame d'un UserForm

Ce chapitre s'applique aux applications Office qui gèrent les UserForms (Excel, Word, Powerpoint…).

Importez le module de classe clOpenGLUserForm
Ce module contient les fonctions nécessaires à l'initialisation d'OpenGL et l'affichage de l'image dans le contrôle.

Pour l'utiliser, créez un nouveau formulaire utilisateur (UserForm) avec par exemple un contrôle image nommé Image1.

Déclarez un objet en en-tête de module du formulaire :

En en-tête de formulaire

Private oGL As clOpenGLUserForm

Sur chargement du formulaire, initialisez OpenGL :

Initialisation d'OpenGL

Private Sub UserForm_Initialize()
' Crée l'objet pour dessin dans le contrôle image
Set oGL = New clOpenGLUserForm
' Initialisation d 'openGL
oGL.InitOpenGL Me.Image1
End Sub

La fonction InitOpenGL avec une image crée une fenêtre masquée de même taille que le contrôle image.
Cette fenêtre sera utilisée par OpenGL.

Si on utilise un contrôle Frame, OpenGL utilise directement ce contrôle et l’affichage est plus rapide.
Attention, il faut que le formulaire soit totalement initialisé avant de pouvoir effectuer le rendu.

Ensuite on peut exécuter les commandes OpenGL pour dessiner.

Par exemple, le dessin du triangle :

Dessin du triangle

' 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

L'affichage se fait à l'aide d'une des fonctions Display* :

Display : affichage dans le contrôle :
les pixels de la scène sont injectés dans le contrôle.
Il peut y avoir des clignotements de l'image à des fréquences d'affichage élevées.

DisplayNoRepaint : affichage dans le contrôle avec réduction des clignotements :
effectue en plus un dessin par-dessus le contrôle et annule le rafraîchissement du formulaire.
Cela évite des scintillements de l'image qui peuvent apparaître avec la fonction Display.

DisplayFast : affichage par dessus le contrôle :
effectue un dessin par-dessus le contrôle.
Cet affichage n'est pas persistant, mais peut s'avérer plus rapide et ne présente pas de problème de clignotements de l'image.

Si la scène est affichée sur une Frame, alors ces trois fonctions ont le même effet.
L'affichage n'est pas persistant lors du dessin sur une Frame.

Testons la fonction Display :

Affichage dans le contrôle

' Affichage dans le contrôle
oGL.Display

Voici le triangle affiché dans un contrôle image d'un UserForm :

XIX-C - Affichage sur un formulaire VB6

Ce paragraphe ne s'applique qu'à un projet VB6.

Nous souhaitons afficher le rendu OpenGL sur un formulaire.
Les formulaires VB6 exposent une propriété hDC : c'est l'identifiant du contexte d'affichage gdi32.
Nous utilisons ce contexte d'affichage pour l'affichage OpenGL.

Ajoutez au projet le module de classe clOpengGLFormVB6

Pour l'utiliser, créez un nouveau formulaire nommé MainForm.

Déclarez un objet en en-tête de module du formulaire :

En en-tête de formulaire

' Classe pour affichage dans un form
Private oGl As clOpengGLFormVB6

Sur chargement du formulaire, initialisez OpenGL :

Initialisation d'OpenGL

' Chargement du formulaire
Private Sub Form_Load()
Set oGl = New clOpengGLFormVB6
oGl.InitOpenGL Me
End Sub

La fonction InitOpenGL lie le contexte d'affichage du formulaire avec un contexte d'affichage OpenGL.

Ensuite on peut exécuter les commandes OpenGL pour dessiner.

Par exemple, le dessin du triangle, à écrire dans l'événement Paint du formulaire :

Dessin du triangle

' Sur dessin du formulaire
Private Sub Form_Paint()
' 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

L'affichage se fait à l'aide de la fonction Display.

Testons cette fonction Display, à la suite du code précédent :

Affichage sur le formulaire

' Affichage sur le formulaire
oGL.Display

Voici le triangle affiché dans un formulaire VB6 :

XX - Sauvegarde de la scène dans un fichier bmp

Peut-être souhaitez-vous enregistrer l'image dans un fichier ?

Il est possible de sauvegarder un fichier bitmap (bmp) à partir des pixels de l'image.
On peut lire les pixels de l'image avec la fonction glReadPixels.
Il faut ensuite mettre en forme les données selon la spécification des images bitmap.

Importez le module standard ModOpenGLSaveBMP

Il suffit ensuite d'appeler la fonction SaveToBMP de ce module, en donnant en paramètre le chemin du fichier.

Par exemple pour sauvegarder en appuyant sur la touche F10 avec freeGLUT :

Code à ajouter dans la procédure CallBackSpecial

Case GLUT_KEY_F10
SaveToBMP CurrentProject.Path & "\capture.bmp"

XXI - Conclusion

Nous venons de faire un petit tour d'horizon d'OpenGL.
Ce tutoriel n'est pas exhaustif, ce n'est qu'une première découverte de la version 1.1.

Pour l'utilisation des extensions, consultez le tutoriel suivant : Les extensions OpenGL en VBA et VB6

Pour en apprendre plus, consultez la page des tutoriels OpenGL.

Merci aux équipes Office, Jeux et VB6 pour leur aide.

Merci à Claude Leloup pour sa relecture orthographique.

XXII - Téléchargements

Téléchargez le pack de modules VB pour OpenGL :
Pack de module VB pour OpenGL

Téléchargez la bibliothèque dll freeGLUT :
Librairie FreeGLUT

Téléchargez les démos de ce tutoriel :

Avec une fenêtre freeGLUT :

Access
Excel

Sans freeGLUT (dessin direct sur contrôle Image ou Formulaire) :

Access
Excel