Développez vos applications 3D avec OpenGL 3.3

Développez vos applications 3D avec OpenGL 3.3

Mis à jour le vendredi 21 juin 2013
  • Moyen

Créer une fenêtre avec la SDL 2.0

Dans ce chapitre nous allons créer une fenêtre avec la librairie SDL. Pour ceux qui connaissent la SDL avec le tuto de M@teo, vous pourrez comparer les deux versions. ;)

Préparation de l'IDE

Pour coder à travers ce tutoriel, je vous recommande d'utiliser l'IDE Code::Blocks qui est un outil très utilisé sur le site du Zéro. :) Vous pourrez donc trouver plus facilement de l'aide en cas de problème. Pour ceux qui souhaitent l'utiliser, voici un lien pour le télécharger (Windows uniquement). Il existe deux versions de cet IDE, vous pouvez choisir celle que vous voulez, les projets donnés devraient fonctionner sur les deux sans problème.

Télécharger : Code::Blocks 12.11 (Windows uniquement)

Pour les linuxiens :

sudo apt-get install codeblocks

D'ailleurs, si vous utilisez cet IDE je vais pouvoir vous fournir directement le template nécessaire pour créer un projet SDL 2.0. Ce template vous permettra de linker les librairies automatiquement sans que vous ayez à faire des manips compliquées. ;)

Télécharger : Template SDL 2.0 Code::Blocks 10.05 et 12.11 - Windows et Linux

Pour Windows, dézippez l'archive et fusionnez le dossier "share" de l'archive avec le dossier "share" de Code::Blocks (chez moi : C:\Program Files (x86)\CodeBlocks\share).

Pour Linux, dézippez l'archive où vous voulez puis exécutez les commandes suivantes (pas dans le dossier "share" mais dans celui qui le contient) :

sudo cp -r share/CodeBlocks/* /usr/share/codeblocks/
sudo chmod -R 715 /usr/share/codeblocks/templates/wizard/sdl2/
sudo chmod 715 /usr/share/codeblocks/templates/wizard/config.script

Nous pouvons maintenant créer notre premier programme en SDL 2.0. :D

Pour cela, il faut d'abord créer un projet SDL 2.0 et non pas OpenGL, ne vous trompez pas ! Sous Code::Blocks la procédure est la suivante :

. File -> New -> Project -> SDL 2.0 project

Vous vous souvenez du dossier "SDL-2.0" du chapitre précédent ? Si vous ne l'avez pas encore installé au bon endroit faites-le maintenant :) (reportez-vous au chapitre précédent).

Occupons-nous maintenant du linkage d'OpenGL, il faut dire à notre IDE que nous souhaitons utiliser cette librairie sinon il va nous mettre plein d'erreurs au moment de la compilation. Voici la procédure à effectuer sous Code::Blocks :

. Project -> Build options -> Linker settings -> Add

Un petit encadré vous demande quelle librairie vous souhaitez linker, cela dépend de votre système d'exploitation. Vous ne pouvez renseigner qu'une seule librairie par encadré. Ré-appuyez sur le bouton Add pour en ajouter une nouvelle.

IDE

Option

Code::Blocks Windows

opengl32
glew32

Code::Blocks Linux

GL

DevC++

-lopengl32
-lglew32

Visual Studio

opengl32.lib
glew32.lib

Selon votre OS, il suffit d'ajouter le ou les mot-clef(s) dans le petit encadré.

Pour les utilisateurs de CodeBlocks sous Windows et uniquement sous Windows, Code::Blocks va certainement vous demander où se trouve la SDL avec cette popup :

Image utilisateur

Dans le champ base qui vous est proposé, renseignez le chemin vers le répertoire "SDL-2.0" que vous avez installé précédemment (chez moi : C:\Program Files (x86)\CodeBlocks\MinGW\SDL-2.0). Ne renseignez aucun autre champ et ça devrait être bon. Une popup vous indiquera surement un message du genre "Please select a valid location", mais ce sera une fausse alerte ne vous en faites pas, appuyez sur le bouton next. ;)

En parlant de ce dossier, si vous avez jeté un coup d’œil à l'intérieur vous vous apercevrez qu'il y a un sous-dossier nommé "dll". Ce dossier contient les fichiers SDL2.dll et glew32.dll, ils sont indispensables pour lancer vos futurs programmes.

Vous avez deux solutions pour les utiliser : soit vous les incluez dans le dossier de chaque projet, soit vous les copier dans le dossier "bin" du compilateur MinGW (chez moi : C:\Program Files (x86)\CodeBlocks\MinGW\bin). La première solution est la plus propre mais aussi la plus contraignante, faites comme bon vous semble mais il faut que vous utilisiez au moins une de ces techniques pour lancer vos programmes. ;)

Pour ceux qui utilisent d'autres IDE comme Visual C++ ou Eclipse vous allez devoir renseigner d'autres librairies car je n'ai pas de template tout fait à vous proposer. Il faudra que vous renseigneriez les dossiers où se trouvent les librairies et les includes. De plus, vous devrez linker manuellement deux librairies supplémentaires :

Link

SDL2main

SDL2

Premier programme

Nous avons tous les outils, ceux-ci sont tous configurés. Bien, commençons. :magicien:

Voici un code de base permettant de créer une fenêtre avec la SDL 1.2 :

#include <SDL/SDL.h>
#include <iostream>


int main(int argc, char **argv)
{
   // Notre fenêtre et le clavier

   SDL_Surface *fenetre(0);
   SDL_Event evenements = {0};
   bool terminer(false);


   // Initialisation de la SDL

   if(SDL_Init(SDL_INIT_VIDEO) < 0)
   {
       std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;
       SDL_Quit();
               
       return -1;
   }
        

   // Création de la fenêtre

   fenetre = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE); 


   // Boucle principale

   while(!terminer)
   {
        SDL_WaitEvent(&evenements);

        if(evenements.type == SDL_QUIT)
            terminer = true;
   }

	
   // On quitte la SDL

   SDL_Quit();

   return 0;
}

Facile non ? Et bien maintenant, je vais vous demander d'oublier la moitié de ce code. :D

Quoi ? C'est si différent que ça ?

Oui en effet, car souvenez-vous que nous travaillons avec la version 2.0. Et vu que l'on passe d'une version 1.x à une version 2.0, le code change beaucoup.

Commençons à coder avec la SDL 2.0 pour se mettre l'eau à la bouche :

#include <SDL2/SDL.h>
#include <iostream>


int main(int argc, char **argv)
{    
    // Initialisation de la SDL

    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;
        SDL_Quit();
               
        return -1;
    }


    // On quitte la SDL

    SDL_Quit();

    return 0;
}

Bon jusqu'ici, pas de grand changement. Mis à part l'inclusion de la SDL qui passe de "SDL/SDL.h" à "SDL2/SDL.h". En même temps on ne peut pas changer grand chose. :p

Voici le nouveau code qui permet de créer une fenêtre :

#include <SDL2/SDL.h>
#include <iostream>


int main(int argc, char **argv)
{	
    // Notre fenêtre
	
    SDL_Window* fenetre(0);
	
	
    // Initialisation de la SDL
	
    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;
        SDL_Quit();
		
        return -1;
    }
	
	
    // Création de la fenêtre
	
    fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);
	
	
    // On quitte la SDL
	
    SDL_DestroyWindow(fenetre);
    SDL_Quit();
	
    return 0;
}

Nous voyons ici deux choses importantes, premièrement :

SDL_Window* fenetre;

Le pointeur SDL_Window remplacera désormais notre fenêtre, il n'y a donc plus de SDL_Surface. Maintenant notre fenêtre aura sa structure à part entière bien différente des surfaces classiques.

Deuxièmement :

fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);

Vous l'aurez compris, cette fonction va nous permettre de créer notre fenêtre, elle vient donc remplacer notre bonne vielle SDL_SetVideoMode. Voici le prototype de cette fonction :

SDL_Window* SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags);
  • title : le titre de notre fenêtre

  • x : l'abscisse de la position de la fenêtre, SDL_WINDOWPOS_CENTERED signifie que nous centrons la fenêtre par rapport à l'axe x

  • y : l'ordonnée de la position, SDL_WINDOWPOS_CENTERED signifie la même chose mais sur l'axe y

  • w : (widht) la largeur de la fenêtre

  • h : (height) la hauteur de la fenêtre

  • flags : ce paramètre est un peu spécial, il faudra toujours mettre SDL_WINDOW_SHOWN. Plus tard, ce sera ici que l'on indiquera à la SDL que nous souhaitons utiliser OpenGL

Cette fonction retourne notre fenêtre dans un pointeur de type SDL_Window.

Quant à la dernière fonction, son utilisation est simple : elle permet de détruire proprement notre fenêtre :

SDL_DestroyWindow(SDL_Window *window);

Nous lui donnerons la fenêtre créée au début du programme.

SDL_DestroyWindow(fenetre);

Puisque l'on parle de l'initialisation de la fenêtre, on va en profiter pour vérifier la valeur du pointeur SDL_Window. En effet dans de rares cas, celui-ci peut être nul à cause d'un éventuel problème logiciel ou matériel. On va donc tester sa valeur dans un if, s'il est nul alors on quitte la SDL en fournissant un message d'erreur, sinon c'est que tout va bien donc on continue :

// Création de la fenêtre

fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);

if(fenetre == 0)
{
    std::cout << "Erreur lors de la creation de la fenetre : " << SDL_GetError() << std::endl;
    SDL_Quit();

    return -1;
}

Ajoutons maintenant le code gérant les évènements qui connait, lui-aussi, son lot de modifications.

Avant, pour savoir si une fenêtre devait se fermer, nous devions vérifier la variable type d'une structure SDL_Event comme ceci par exemple :

// Variables

SDL_Event evenements;
bool terminer(false);


// Gestion des évènements

SDL_WaitEvent(&evenements); 

if(evenements.type == SDL_QUIT)
    terminer = true;

Avec la SDL 2.0, la gestion des évènements change quelques peu (nous verrons les différences un peu plus tard). Maintenant, pour savoir si on doit fermer une fenêtre, nous ferrons ceci :

// Variables

SDL_Event evenements;
bool terminer(false);


// Gestion des évènements

SDL_WaitEvent(&evenements); 

if(evenements.window.event == SDL_WINDOWEVENT_CLOSE)
    terminer = true;

Bien, résumons tout cela en un seul code. :)

#include <SDL2/SDL.h>
#include <iostream>


int main(int argc, char **argv)
{	
    // Notre fenêtre
	
    SDL_Window* fenetre(0);
    SDL_Event evenements;
    bool terminer(false);
	
	
    // Initialisation de la SDL
	
    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;
        SDL_Quit();
		
        return -1;
    }
	
	
    // Création de la fenêtre

    fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);

    if(fenetre == 0)
    {
        std::cout << "Erreur lors de la creation de la fenetre : " << SDL_GetError() << std::endl;
        SDL_Quit();

        return -1;
    }
	
	
    // Boucle principale
	
    while(!terminer)
    {
	SDL_WaitEvent(&evenements);
		
	if(evenements.window.event == SDL_WINDOWEVENT_CLOSE)
	    terminer = true;
    }
	
	
    // On quitte la SDL
	
    SDL_DestroyWindow(fenetre);
    SDL_Quit();
	
    return 0;
}

Les paramètres OpenGL

Tout ce code c'est bien mais il n'y a rien qui permet d'exploiter OpenGL, mais qu'attendons-nous ? :diable:

Il y a pas mal d'attributs (des options de configuration) à paramétrer pour rendre notre programme compatible avec OpenGL, commençons par le plus important :

SDL_GLContext contexteOpenGL;

Cette structure va permettre à la SDL de créer un contexte OpenGL. Ce contexte est très important, pour utiliser l'API graphique et ses fonctions. Il faut tout d'abord le configurer.

Voici le prototype de la fonction qui permet de spécifier des attributs OpenGL à la SDL :

SDL_GL_SetAttribute(SDL_GLattr attr, int value);
  • attr : c'est notre attribut, nous verrons lesquels il faut spécifier

  • value : c'est la valeur de l'attribut

Avec cette simple fonction, nous allons pourvoir spécifier à la SDL quelle version d'OpenGL on souhaite utiliser :

SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

Vous l'aurez compris :

  • SDL_GL_CONTEXT_MAJOR_VERSION pour OpenGL 3.x

  • SDL_GL_CONTEXT_MINOR_VERSION pour la version mineure : x.1 (Dans le futur, cette valeur sera de 3 lorsque nous saurons manier OpenGL)

Petite question pour ceux qui connaissent la SDL 1.2, vous savez comment on utilise le double buffering ? :-°

Voici la réponse :p :

SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);

Comme avec la SDL, on peut aussi activer le Double Buffuring avec OpenGL. Cependant ça ne se passe pas de la même façon. Voici comment faire :

SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  • SDL_GL_DOUBLEBUFFER : attribut activant le Double Buffering. Mettez 1 pour l'activer et 0 pour faire le contraire. ;)

  • SDL_GL_DEPTH_SIZE : attribut de profondeur du Double Buffer. Nous mettons sa valeur à 24 pour une profondeur de 24 bits.

Bon on avance c'est bien :) . Mais tout à l'heure, tu as parlé d'une structure : SDL_GLContext, qu'est-ce qu'on en fait ?

Très bonne remarque, on va l'utiliser maintenant justement grâce à la fonction :

SDL_GLContext SDL_GL_CreateContext(SDL_Window *window);

Cette fonction demande un pointeur SDL_Window pour y attacher le contexte OpenGL. De plus, elle renvoie une structure SDL_GLContext, et justement c'est ce dont nous avons besoin :)

Dans notre cas, nous lui donnerons notre fenêtre puis nous récupéreront la structure retournée :

contexteOpenGL = SDL_GL_CreateContext(fenetre);

Comme pour la fenêtre SDL, la création du contexte OpenGL peut lui aussi échouer. Le plus souvent ce sera parce que la version OpenGL demandée ne sera pas supportée par la carte graphique. Il faut donc tester la valeur de la variable contexteOpenGL. Si elle est égale à 0 c'est qu'il y a eu un problème. Dans ce cas on affiche un message d'erreur, on détruit la fenêtre puis on quitte la SDL :

// Création du contexte OpenGL

contexteOpenGL = SDL_GL_CreateContext(fenetre);

if(contexteOpenGL == 0)
{
    std::cout << SDL_GetError() << std::endl;
    SDL_DestroyWindow(fenetre);
    SDL_Quit();

    return -1;
}

Si tout se passe bien lors de la création du contexte alors on continue.

Tout comme pour la fenêtre SDL encore une fois, il existe aussi une fonction qui permet de détruire le contexte lorsque nous n'en avons plus besoin. Son appel ressemble à ceci :

SDL_GL_DeleteContext(contexteOpenGL);

Bien, résumons tout cela :

#include <SDL2/SDL.h>
#include <iostream>


int main(int argc, char **argv)
{	
    // Notre fenêtre
	
    SDL_Window* fenetre(0);
    SDL_GLContext contexteOpenGL(0);
	
    SDL_Event evenements;
    bool terminer(false);
	
	
    // Initialisation de la SDL
	
    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;
        SDL_Quit();
		
        return -1;
    }
	
	
    // Version d'OpenGL
	
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
	
	
    // Double Buffer
	
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
	
	
    // Création de la fenêtre

    fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);

    if(fenetre == 0)
    {
        std::cout << "Erreur lors de la creation de la fenetre : " << SDL_GetError() << std::endl;
        SDL_Quit();

        return -1;
    }


    // Création du contexte OpenGL

    contexteOpenGL = SDL_GL_CreateContext(fenetre);

    if(contexteOpenGL == 0)
    {
        std::cout << SDL_GetError() << std::endl;
        SDL_DestroyWindow(fenetre);
        SDL_Quit();

        return -1;
    }
	
	
    // Boucle principale
	
    while(!terminer)
    {
        SDL_WaitEvent(&evenements);
		
        if(evenements.window.event == SDL_WINDOWEVENT_CLOSE)
        terminer = true;
    }
	
	
    // On quitte la SDL
	
    SDL_GL_DeleteContext(contexteOpenGL);
    SDL_DestroyWindow(fenetre);
    SDL_Quit();
	
    return 0;
}

On est presque au bout courage. :p Il nous faut juste rajouter un paramètre dans la fonction GL_CreateWindow. Avec la SDL 1.2, ce paramètre ressemblait à ça :

SDL_SetVideoMode(.., .., .., SDL_OPENGL);

Avec la SDL 2.0, le nom change légèrement :

SDL_WINDOW_OPENGL

Il se place également dans la fonction qui créé la fenêtre :

fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);

Et voila notre code final :

#include <SDL2/SDL.h>
#include <iostream>


int main(int argc, char **argv)
{	
    // Notre fenêtre
	
    SDL_Window* fenetre(0);
    SDL_GLContext contexteOpenGL(0);
	
    SDL_Event evenements;
    bool terminer(false);
	
	
    // Initialisation de la SDL
	
    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;
        SDL_Quit();
		
        return -1;
    }
	
	
    // Version d'OpenGL
	
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
	
	
    // Double Buffer
	
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
	
	
    // Création de la fenêtre

    fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);

    if(fenetre == 0)
    {
        std::cout << "Erreur lors de la creation de la fenetre : " << SDL_GetError() << std::endl;
        SDL_Quit();

        return -1;
    }


    // Création du contexte OpenGL

    contexteOpenGL = SDL_GL_CreateContext(fenetre);

    if(contexteOpenGL == 0)
    {
        std::cout << SDL_GetError() << std::endl;
        SDL_DestroyWindow(fenetre);
        SDL_Quit();

        return -1;
    }
	
	
    // Boucle principale
	
    while(!terminer)
    {
        SDL_WaitEvent(&evenements);
		
        if(evenements.window.event == SDL_WINDOWEVENT_CLOSE)
        terminer = true;
    }
	
	
    // On quitte la SDL
	
    SDL_GL_DeleteContext(contexteOpenGL);
    SDL_DestroyWindow(fenetre);
    SDL_Quit();
	
    return 0;
}

Vous devriez obtenir quelque chose comme ceci :

Image utilisateur

Quoi ! Mais on a une fenêtre blanche c'est nul !

Du calme, vous n'imaginez pas ce que l'on vient de faire (enfin ce que la SDL vient de faire), nous venons de créer un contexte dans lequel OpenGL va évoluer. En gros nous venons de créer l'univers. :D

Bref nous avons enfin une fenêtre SDL compatible avec OpenGL 3.1. Comparez le code avant et après, vous verrez la différence. Il y a plus de code certes, mais toutes ces instructions sont importantes. Ce nouvel environnement va nous permettre de créer des programmes plus paramétrables et donc plus puissants. ;)

Télécharger : Code Source C++ du Chapitre 2

Piouf, ça fait beaucoup de changements pour arriver grosso modo à la même chose. Mais au moins on passe à la nouvelle génération de programme.
Bon, on a une fenêtre c'est bien, mais pour le moment on a linké OpenGL pour rien. Passons maintenant à la partie OpenGL :pirate: .

L'auteur

Exemple de certificat de réussite
Exemple de certificat de réussite