Utilisation d'OpenGL sous Delphi

OpenGL est une API disponible aussi bien sous Windows que Linux qui permet d'effectuer le rendu de scènes graphiques en 3 dimensions. C'est en ce sens un concurrent de Microsoft DirectX.

Les performances

Que l'on soit sous DirectX ou OpenGL, c'est en réalité la carte graphique qui travaille (pour peu que les bons drivers soient installés). Donc du point de vue purement technique, la différence n'est pas flagrante. Là ou DirectX aura un petit avantage, c'est qu'il est plus proche du noyau de Windows...il n'y a qu'à voir comment une application DirectX peut planter méchamment Windows pour s'en rendre compte :D

Mais le seul domaine ou j'ai constaté une réelle différence est dans l'accès direct à la mémoire vidéo. En effet DirectDraw permet d'attaquer directement (?) le buffer video en plein écran...par contre l'API 2D de DirectDraw étant assez limitée, il faut - comme au bon vieux temps du DOS - tout faire par soit même.

Initialisation d'OpenGL

La fonction wglCreateContext permet d'initialiser OpenGL sous Windows (les fonctions préfixées "wgl" sont des extensions propres à Windows). Dans une application Delphi classique comprenant une fiche MainForm, cela pourra prendre cette forme :

uses OpenGL;

procedure TMainForm.FormCreate(Sender: TObject);
var
 pfd:TPIXELFORMATDESCRIPTOR;
 pixelformat:integer;
 rgb:cardinal;
begin
// il nous faut un context de device
 fDC:=GetDC(Handle);
 if fDC=0 then RaiseLastOSError;
// nous allons indiquer à Windows les caractéristiques de notre fenêtre OpenGL
 FillChar(pfd,SizeOf(pfd),0);
 pfd.nSize       := sizeof(pfd);
 pfd.nVersion    := 1;
// on dessine directement sur une fiche (et non dans un BITMAP), avec un double buffer
 pfd.dwFlags     := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;
 pfd.iLayerType  := PFD_MAIN_PLANE;
// l'image est en RGBA 32bits
 pfd.iPixelType  := PFD_TYPE_RGBA;
 pfd.cColorBits  := 32;
// le ZBuffer utilise 24 bits
 pfd.cDepthBits  := 24; // 32 pose des pb sur certaines config ?!
 pixelformat:=ChoosePixelFormat(fDC, @pfd);
 if PixelFormat=0 then RaiseLastOSError;
 if not SetPixelFormat(fDC, pixelformat, @pfd) then RaiseLastOSError;
// maintenant on peut initialiser OpenGL
 fGL:=wglCreateContext(fDC);
// et fixer le context courant (le seul dans cette application)
 wglMakeCurrent(fDC,fGL);
// la couleur d'effacement est calquée sur celle de la fiche Delphi
 rgb:=ColorToRGB(Color);
 glClearColor((rgb and $FF)/255,((rgb shr 8) and $FF)/255,((rgb shr 16) and $FF)/255,0);
// valeur d'effacement du ZBuffer
 glClearDepth(1);
// autoriser le test ZBuffer
 glEnable(GL_DEPTH_TEST);
// gérer les faces cachées
 glEnable(GL_CULL_FACE);
// forcer le resize
 FormResize(Self);
end;

On utilise le OnDestroy pour détruire le contexte.
procedure TMainForm.FormDestroy(Sender: TObject);
begin
 wglMakeCurrent(fDC,0);
 wglDeleteContext(fGL);
 ReleaseDC(Handle,fDC);
end;

Vous aurez noté que lors de la création on invoque FormResize(), on va définir l'événement OnResize pour ajuster le rendu OpenGL à la taille de la fenêtre
{ ajuster le rendu OpenGL quand la fiche est redimensionnée }
procedure TMainForm.FormResize(Sender: TObject);
begin
 glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluPerspective(45,ClientWidth/ClientHeight,0.1,1000);
 glMatrixMode(GL_MODELVIEW);
  glViewport(0, 0, ClientWidth, ClientHeight);
 Invalidate;
end;

Dans ce mode de fonctionnement "pleine fiche", on pourra éviter une scintillement de la fiche on lui interdisant de dessiner sa couleur de fond (message WM_ERASEBKGND)
{ OpenGL redessine tout la fiche, on évite ici de gérer le background }
procedure TMainForm.WMEraseBkGnd(var Msg:TMessage);
begin
 Msg.Result:=1;
end;

Il ne reste plus qu'à gérer le rendu dans le OnPaint !

Le rendu OpenGL

C'est quand la fiche demande à être dessinée qu'on va définir ce que OpenGL va dessiner.
procedure TMainForm.FormPaint(Sender: TObject);
begin
{ on efface les pixels et le Z Buffer }
 glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
{ reset de la matrice de projection }
 glLoadIdentity;

 { ICI ON DOIT EFFECTUER LE RENDU DE LA SCENE }

 // swaper les buffers vidéos
 glFlush;
 SwapBuffers(fDC);
end;

Quelques définitions

Dans le code présenté ici (qui ne dessine encore rien) on évoque quelques notions qu'il faut bien comprendre pour travailler avec OpenGL.

Le double Buffer

On a initialisé OpenGL avec l'attribut PFD_DOUBLEBUFFER, cela veux dire qu'il existe en permanence deux écrans de rendu. Celui sur lequel on travaille et celui qui est visible à l'écran. Cette technique permet d'éviter le scintillement de l'écran pendant deux rendus. L'ordinateur actualise en permanence ce qu'affiche votre moniteur d'après le contenu du buffer video. Si on venait travailler directement sur ce buffer, il faudrait pouvoir faire le rendu complètement entre la fin du balayage écran et le retour du signal...ce qui pose un problème de temps d'exécution d'une part et de synchronisation d'autre part. Pour simplifier le processus, on travaillera donc sur un buffer hors écran, et quand il sera complet, on échange (swap) les deux buffers...de préférence en fin de balayage écran (voir vos paramètres de carte video), sinon on aura l'ancien buffer en début d'écran et le nouveau en fin d'écran...ce qui peut donner un effet de vague sur une scène animée.

Le Z Buffer

Quand on active le DEPTH_TEST, OpenGL garde pour chaque pixel dessiné, non seulement sa couleur qui vient se loger dans le COLOR_BUFFER, mais également sa profondeur (position en Z) dans le DEPTH_BUFFER. Dès lors, tout pixel qui serait dessiné à une position Z plus élevée (donc derrière celui déjà affiché) serait simplement ignoré ! Le Z Buffer gère donc la profondeur de l'image.

Il vous appartient de le vider avant chaque rendu sinon les objets dessinés lors du précédent rendu viendrait perturber le rendu de la nouvelle scène. Notez cependant que vous pouvez très bien utiliser un Z Buffer JUSTEMENT non vide pour obtenir un rendu particulier. Pour dessiner un rectangle percé d'un trou, il est par exemple possible de remplir le Z Buffer avec le dessin d'une sphère invisible, et ensuite de dessiner un rectangle autour...la sphère présente dans le Z Buffer venant "percer" un trou dans le rectangle :D

La matrice

Sous OpenGL il existe deux matrices (contre trois sous DirectX), la matrice de transformation et la matrice de projection. La première va gérer les transformations appliquées aux objets en 3D dimensions qu'on va dessiner...on va pouvoir par exemple l'utiliser pour influence le point de vue (la position de la caméra) dans une scène 3D. La matrice de projection quand à elle va déterminer comment la scène 3D va être projetée sur l'espace 2D de l'écran, cela correspond au réglage de la lentille de la caméra et non plus sa position.

Translations et rotations

Les translations et les rotations sont les deux transformations de base qu'on va pouvoir appliquer aux objets OpenGL. En combinant une série de ces transformations, on va pouvoir positionner tous nos objets n'importe où dans l'univers. Imaginer que vos objet sont des éléments télécommandés avec 2 manettes, une pour avancer, une pour tourner...en jonglant avec ces deux manettes vous pouvez le déplacer où vous vouler.
Date de dernière modification : 19/06/2008