gKit2 light
Loading...
Searching...
No Matches
bilan : démarrer avec gKit

il est temps de faire une petite pause et de reprendre calmement tout ça...

application interactive ?

les tutos précédents ont utilisé la classe App ou AppCamera pour créer une fenêtre et initialiser openGL. Il est peut être plus clair d'écrire directement les différentes étapes et la boucle de gestion d'évènements :

étape 1 : création de la fenêtre et du contexte openGL

#include "window.h"
Window window= create_window(1024, 576);
Context context= create_context(window);
Context create_context(Window window)
cree et configure un contexte opengl
Definition window.cpp:344
Window create_window(const int w, const int h, const int major, const int minor, const int samples)
creation d'une fenetre pour l'application.
Definition window.cpp:249

les détails de la création de fenêtre et des options du contexte openGL sont dans écrire une application openGL.

étape 2 : initialisation, chargement d'un objet 3d et des shaders

#include "mesh.h"
#include "wavefront.h"
#include "program.h"
// chargement d'un objet / format .obj
Mesh mesh= read_mesh( "..." );
if(mesh.vertex_count() == 0)
return "erreur"; // pas de géométrie, pas d'affichage...
// charge un vertex et un fragment shader pour dessiner
GLuint program= read_program( "..." );
representation d'un objet / maillage.
Definition mesh.h:121
Mesh read_mesh(const char *filename)
charge un fichier wavefront .obj et renvoie un mesh compose de triangles non indexes....
Definition wavefront.cpp:14
GLuint read_program(const char *filename, const char *definitions)
Definition program.cpp:218
int program_print_errors(const GLuint program)
affiche les erreurs de compilation.
Definition program.cpp:446

étape 3 : boucle principale et gestion des évènements, on attend que l'utilisateur ferme la fenêtre ou appuie sur la touche esc / échap. SDL renvoie les évènements un par un avec SDL_PollEvent(), il suffit d'agir en fonction de leur type, appui sur une touche, fermeture, déplacement, redimensionnement de la fenêtre, etc.

bool close= false;
while(!close)
{
SDL_Event event;
while( SDL_PollEvent(&event) ) // récupère un évènement
{
if(event.type == SDL_QUIT)
close= true; // sortir si click sur le bouton 'fermer' de la fenêtre
else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)
close= true; // sortir si la touche esc / échap est enfoncée
}
// dessiner
glUseProgram(program);
program_uniform(program, "mvpMatrix", ...);
mesh.draw(program);
// presenter / montrer le résultat
SDL_GL_SwapWindow(window);
}

étape 4 : libération des ressources / nettoyage

mesh.release();
release_program(program);
release_context(context);
void release_window(Window window)
destruction de la fenetre.
Definition window.cpp:314
void release_context(Context context)
detruit le contexte openGL.
Definition window.cpp:404
int release_program(const GLuint program)
detruit les shaders et le program.
Definition program.cpp:225

évènements : en pratique, il y a d'autres évènements à surveiller, toutes les touches du clavier, la souris, le redimensionnement de la fenêtre, le drag&drop de fichiers... le code précédent est minimaliste... mais comme c'est toujours la même chose, la fonction events() de window.h s'occupe de ça. ce qui permet d'écrire la boucle de manière très compacte :

while( events(window) )
{
// dessiner
glUseProgram(program);
program_uniform(program, "mvpMatrix", ...);
mesh.draw(program);
// presenter / montrer le résultat
SDL_GL_SwapWindow(window);
}
int events(Window window)
fonction interne de gestion d'evenements.
Definition window.cpp:168

events() renvoie false lorsque la fenêtre doit être fermée, il suffit de sortir de la boucle et de nettoyer / libérer tous les objets openGL.

gestion des touches

events() maintient l'état de toutes les touches du clavier. on peut savoir, dans n'importe quelle fonction de l'application, si une touche est enfoncée, ou pas, avec key_state( ). Pour connaître l'état de la touche d'une lettre, il suffit d'utiliser la lettre comme paramètre, cf if(key_state('t')) { ... }, mais ça ne marche pas pour les accents, uniquement pour les caractères ASCII basiques. Pour les autres touches comme les flèches ou tab, ctrl, etc, il suffit d'utiliser le code correspondant, cf SDLK_RETURN, SDLK_LEFT, SDLK_TAB, SDLK_PAGEUP, SDLK_F1, etc. la liste complète est dans la doc de SDL2.

if(key_state(SDLK_LEFT))
// tourner à gauche
if(key_state(SDLK_RIGHT))
// tourner à droite
if(key_state(SDLK_SPACE))
// sauter
...
int key_state(const SDL_Keycode key)
renvoie l'etat d'une touche du clavier. cf la doc SDL2 pour les codes.
Definition window.cpp:40

il y a un exemple complet dans premiers objets, affichage et transformations.

gestion de la souris

il suffit d'utiliser directement les fonctions de SDL2, cf doc en ligne. par exemple, récupérer la position de la souris et l'état des boutons avec SDL_GetMouseState() :

int x= 0;
int y= 0;
unsigned buttons= SDL_GetMouseState(&x, &y);
if(buttons & SDL_BUTTON(1)) // ou if(buttons & SDL_BUTTON_LMASK)
printf("click gauche %d, %d\n", x, y);
if(buttons & SDL_BUTTON(3)) // ou if(buttons & SDL_BUTTON_RMASK)
printf("click droit %d, %d\n", x, y);
void printf(Text &text, const int px, const int py, const char *format,...)
affiche un texte a la position x, y. meme utilisation que printf().
Definition text.cpp:140

buttons est un masque binaire qui représente l'état de tous les boutons. chaque bouton est représenté par un bit du masque, il faut utiliser SDL_BUTTON() pour identifier un bouton: 1, 2 et 3, pour gauche, milieu et droit. Si on préfère obtenir le mouvement de la souris plutôt que sa position, on peut utiliser SDL_GetRelativeMouseState(). attention GetRelativeMouseState() renvoie le mouvement de la souris calculé depuis le dernier appel. résultat, il ne faut l'appeler qu'une fois par frame, sinon les appels suivants renverront de toutes petites valeurs de mouvements ou zero.

molette souris / trackpad

pour récupérer la rotation de la molette / bouton de milieu de la souris ou un mouvement sur le pad d'un portable, il faut intercepter les évènements SDL_MouseWheelEvent (comme dans l'étape 3 juste au dessus) ou utiliser events() et wheel_event() (comme dans l'étape évènements) :

SDL_MouseWheelEvent wheel= wheel_event();
printf("wheel %d %d, mouse %d, %d\n", wheel.x, wheel.y, wheel.mouseX, wheel.mouseY);
SDL_MouseWheelEvent wheel_event()
renvoie le dernier evenement. etat de la molette de la souris / glisser sur le pad.
Definition window.cpp:110

exemple d'utilisation pour déplacer une caméra dans update_camera() dans app_camera.cpp.

dessiner avec openGL ?

Dans le même genre, il n'est pas très difficile de dessiner directement avec openGL, cf glDrawArrays() ou glDrawElements() pour des maillages indexés / sommets partagés. par contre, il faut transférer les sommets des triangles dans des buffers openGL et décrire le format des sommets, ie position uniquement, ou coordonnées de texture, normales, etc. Les détails ne sont pas si horribles, cf tuto9_buffers.cpp, mais Mesh peut faire le travail tout seul, cf Mesh::create_buffers(). on peut remplacer l'init et l'affichage de l'objet avec du code openGL :

// chargement d'un objet 3d / format .obj
Mesh mesh= read_mesh( "..." );
if(mesh.vertex_count() == 0)
return "erreur"; // pas de géométrie, pas d'affichage...
// configure le format des sommets, ie le Vertex Array, et transfère les sommets dans des buffers...
GLuint vao= mesh.create_buffers( USE_POSITION | USE_NORMAL );
unsigned count= mesh.vertex_count();
// mesh n'est plus nécessaire ! toutes les données sont transférées sur la carte graphique.
// dessiner
glUseProgram(program);
program_uniform(program, "mvpMatrix", ...);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, count);
// nettoyage
void release_buffers(const GLuint vao)
détruit le vao et les buffers associés. a utiliser a la place de Mesh::release() après Mesh::create_b...
Definition mesh.cpp:863
@ USE_POSITION
inclut l'attribut position dans les buffers.
Definition mesh.h:112
@ USE_NORMAL
inclut l'attribut normale dans les buffers.
Definition mesh.h:114

bouger la caméra ?

Une fois que l'on arrive à afficher un objet, on a rapidement envie de bouger autour pour l'observer, il n'est pas si difficile d'écrire une caméra, cf premiers objets, affichage et transformations, mais, parfois, on veut juste un truc simple à utiliser. la classe Orbiter est faite pour ça, elle permet de tourner autour d'un objet. Il suffit de la créer avec les dimensions de l'objet à observer, de définir ses paramètres de projection, et de la mettre à jour à chaque image avec update_camera() de app_camera.h:

#include "app_camera.h"
// init
Mesh mesh= { ... };
// calcule l'englobant de l'objet
Point pmin, pmax;
mesh.bounds(pmin, pmax);
// regle l'orbiter
Orbiter camera;
camera.lookat(pmin, pmax);
// proportion largeur / hauteur et ouverture de la camera
camera.projection(window_width(), window_height(), 45);
// dessiner
update_camera( camera );
// recuperer les transformations
Transform model= Identity();
Transform view= camera.view();
Transform projection= camera.projection();
Transform mvp= projection * view * model;
glUseProgram(program);
program_uniform(program, "mvpMatrix", mvp);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES, 0, count);
representation de la camera, type orbiter, placee sur une sphere autour du centre de l'objet.
Definition orbiter.h:17
void lookat(const Point &center, const float size)
observe le point center a une distance size.
Definition orbiter.cpp:7
int window_height()
renvoie la hauteur de la fenetre de l'application.
Definition window.cpp:27
int window_width()
renvoie la largeur de la fenetre de l'application.
Definition window.cpp:23
Transform Identity()
construit la transformation identite.
Definition mat.cpp:187
representation d'un point 3d.
Definition vec.h:21
representation d'une transformation, une matrice 4x4, organisee par ligne / row major.
Definition mat.h:21

et les shaders ?

on a dit calmement... ce sera pour le prochain épisode...