M2 - Images
TP 1 - openGL3 et shaders
Prise en main
installez gKit et
parcourez la doc.
Ce qu'il faut retenir : la classe gk::App
permet de construire une application openGL, elle crée une
fenetre et un contexte de rendu openGL et permet également de
gérer les évènements claviers, souris, joysticks,
etc., facilement.
Pour construire une application, il faut dériver la classe
gk::App et redéfinir les méthodes suivantes :
- int init( ) : pour
charger et initialiser les objets, les vertex buffers, les shaders, etc.
init( ) doit renvoyer 0 si tout c'est bien passé, ou -1 en cas
d'erreur.
- int quit( ) : pour
libérer les ressources allouées dans init( ).
- int draw( ) : cette
fonction est appelée régulièrement pour dessiner
chaque image.
draw( ) peut renvoyer 0 pour terminer l'application, ou 1 pour
continuer à être appelée.
Utilisation des objets openGL.
Tous les objets openGL dérivent d'une classe de base
gk::GLResource et se manipulent donc de la même
manière.
attention : les objets openGL
/ les GLResources n'appartiennent pas à l'application ! Ils
appartiennent au contexte openGL. L'application ne peut donc
que
les referencer (garder un pointeur sur un objet GLResource).
les GLResources sont construits en deux étapes : new/delete
permettent de créer/détruire l'objet c++ qui permet de
manipuler un objet openGL, et l'objet
openGL lui-même est
crée/détruit par createGLResource()/releaseGLResource().
// creer un shader
// 1. creer l'objet c++ shader
GLShaderProgram *shader= new
gk::GLShaderProgram("simple.vsl", "simple.fsl");
if(shader == NULL)
return "erreur";
// 2. creer effectivement le shader openGL
if(shader->createGLResource() < 0)
return "erreur de creation du
shader";
// utiliser le shader
glUseProgram(shader->name());
// dessiner quelquechose ...
// detruire le shader
// 1. detruire l'objet openGL
shader->releaseGLResource();
// 2. detruire l'objet c++ shader
delete shader;
Les GLResources doivent être configurés
complètement avant d'appeler
createGLResource(), ensuite,
ils ne sont plus modifiables, uniquement utilisables par
l'application. Cette "contrainte" permet de mieux
structurer
l'application en imposant une phase d'initialisation
suivie d'une "boucle" d'utilisation. En effet, il est très
très (très)
lent de créer les objets openGL à chaque utilisation.
Pour résumer, les GLResources seront donc crées
dans
gk::App::init() et pas dans la fonction gk::App::draw().
Fonctions utilitaires pour la gestion
des GLResources.
Cette gestion de la durée de vie des objets openGL peut sembler
un peu lourde mais plusieurs fonctions permettent de la rendre beaucoup
plus pratique. Par exemple, la classe gk::GLManager détruira
automatiquement tous les objets openGL crées à la
fermeture de l'application.
cf. BufferManager, ShaderManager, EffectShaderManager, TextureManager,
SamplerManager, FrambufferManager, etc. pour créer les objects
correspondant.
exemple de création de buffer :
#include
"MeshIO.h"
#include "BufferManager.h"
// charger un objet
gk::Mesh *mesh= gk::MeshIO::read(
"bigguy.obj" );
if(mesh == NULL)
// erreur de
lecture de l'objet 3d.
return -1;
// utiliser BufferManager pour creer un vertex buffer
gk::GLAttributeBuffer
*positions= gk::createAttributeBuffer(mesh->positionCount(),
mesh->positionCount() *
sizeof(gk::Point),
&mesh->positions().front());
// creer l'objet openGL
if(positions == NULL ||
positions->createGLResource() < 0)
// erreur de
creation du buffer.
return -1;
le buffer est maintenant géré
par gk::GLManager<gk::GLAttributeBuffer>, il n'est plus
nécessaire de le détruire dans gk::App::quit() !
la méthode gk::GLResource::name( ) renvoie
l'identifiant de l'objet openGL pour le manipuler directement, par
exemple :
glBindBuffer(GL_ARRAY_BUFFER,
positions->name());
Contrôle au clavier, souris, joystick.
La fonction gk::App::key( code )
renvoie l'état d'une touche identifiée par 'code' : 1 si
la touche est appuyée,
0 si la touche est relachée.
Cette fonction renvoie une référence sur la valeur, ce
qui permet de la modifier, par exemple, key('a')= 0.
Pour tester l'état d'une touche comme les flèches de
directions, il suffit d'utiliser les codes SDLK_xxx
correspondants. Il est également possible de connaître
l'état des touches spéciales : shift, control, etc : SDL_GetModState(
), les codes SDLK_xxx
associés sont documentés en bas de la page.
Il
est
également
possible
de
modifier l'état de ces
touches spéciales avec SDL_SetModState(
).
La gestion de la souris se fait directement en utilisant les fonctions
de libSDL : SDL_GetMouseState(
), SDL_GetRelativeMouseState(
).
Le manuel de programmation complet de libSDL est également disponible.
mesh_viewer fourni dans l'archive gKit donne un exemple assez complet
de la gestion clavier / souris.
Exemple complet d'application utilisant gKit (inclus dans l'archive) :
#include "App.h"
#include "Transform.h"
#include "MeshIO.h"
#include "BufferManager.h"
#include "ShaderManager.h"
class TP : public gk::App
{
gk::GLShaderProgram *program;
public:
TP( )
:
gk::App(1024, 768)
{}
~TP( ) {}
int init( )
{
// gk::MeshIO::read(
mesh_filename ) pour lire un objet 3d
//
gk::createAttributeBuffer( ) pour creer un vertex buffer
//
gk::createIndexBuffer( ) pour creer un index buffer
// pour creer un
shader program en compilant les fichiers sources vertex et fragment
program=
gk::createShaderProgram( "vertex.vsl", "fragment.fsl" );
if(program == NULL ||
program->createGLResource() < 0)
//
erreur de chargement des fichiers sources ou de compilation des
shaders, sortir de l'application
return
-1;
return
0; // tout c'est bien passe, sinon
renvoyer -1
}
int quit( )
{
return 0;
}
int draw( )
{
if(key(SDLK_ESCAPE))
//
fermer l'application si l'utilisateur appuie sur ESCAPE
Close();
// activer le shader
glUseProgram(program->name());
// parametrer le
shader, par exemple, mvpMatrix
// activer les buffers
d'attributs
// activer le buffer
d'indice
// draw
// afficher le buffer
de dessin
SDL_GL_SwapBuffers();
return
1; // continuer
}
};
int main( int argc, char **argv )
{
TP app;
app.run();
return 0;
}
Partie 1 : Affichage avec vertex buffer / index buffer.
Les vertex et index buffers seront donc crées et
initialisés dans la méthode init( ) de gk::App.
Quelques méthodes utiles de gk::Mesh
:
- positions des sommets d'un objet : gk::Mesh::positionCount( ) et
gk::Mesh::positions( ),
- indices : gk::Mesh::indiceCount( ) et gk::Mesh::indices( ),
- normales : gk::Mesh::normalCount( ) et gk::Mesh::normals( ).
Si les normales ne sont pas disponibles, il est possible de les
calculer avec la méthode gk::Mesh::buildNormals( ).
En résumé, pour charger un objet et
récupérer ses normales :
gk::Mesh *mesh=
gk:MeshIO::read("xxx.obj");
if(mesh->normalCount() !=
mesh->positionCount())
mesh->buildNormals();
exercice :
Chargez un objet et affichez-le en utilisant un vertex buffer et un
index buffer.
Vous utiliserez un shader minimaliste (couleur uniforme,
par exemple).
Structurez correctement votre code, en utilisant les methodes init( ),
quit( ) et draw( ) en dérivant la classe gk::App.
La méthode draw( ) commence par redimensionner et effacer le
buffer de dessin. Ensuite, activez le shader, décrire la
projection de la
caméra, puis pour chaque objet à afficher :
décrire la matrice repère local vers repère camera
(modelview), et affichez
l'objet.
Derniere remarque : pensez à désactiver tous les
états openGL modifiés pour afficher chaque objet, vous
gagnerez du temps.
Partie 2 : Afficher plusieurs objets / élimination des
objets occultés.
Un objet affichable est a priori représenté par un
ensemble de buffers, une transformation pour le placer dans le
repère de la scène et une boite englobante.
exercice 1 : visibilité.
Testez la visibilité de la boite englobante d'un objet avant de
l'afficher.
Ecrivez une fonction permettant de déterminer la
visibilité d'un englobant par rapport au volume
de vision de la camera, c'est à dire connaissant la
transformation model * view * projection :
bool
occluded(
const
gk::BBox&
object_bbox,
const gk::Transform& model_view_projection );
ou en travaillant dans le repère de la scène :
bool occluded( const gk::BBox& world_bbox, const
gk::Transform& view_projection );
exercice bonus : visibilité hiérarchique.
Construisez une hiérarchie d'englobants afin de tester des
groupes d'objets proches et d'éviter de tester individuellement
tous les objets de la scène.
Partie 3 : Shaders et matières.
Modifiez les shaders utilisés pour afficher les objets et
utilisez les paramètres du modèle Blinn-Phong
stockés dans une structure gk::MeshMaterial.
Dans quel repère faire les calculs d'éclairement ?
Quels sont les paramètres uniforms des shaders ?
Quels sont les attributs de sommets ?
utilitaire : utilisez
shadercc (fourni avec l'archive gKit) pour vérifier que vos
shaders compilent avant le lancer votre application.
indications : utilisez
gk::Mesh::triangleMaterial( ) pour recupérer une description de
matière.
Les maillages peuvent utiliser plusieurs matières, dans ce cas,
les triangles associés à la même matière
sont regroupés dans un gk::SubMesh, cf gk::Mesh::subMeshCount(),
gk::Mesh::subMeshes()
et gk::subMeshMaterial( ) pour obtenir la description des
différentes parties
du maillage.
exercice bonus : textures.
Chargez les textures associées aux matières, cf.
gk::MeshMaterial::diffuse_texture et gk::MeshMaterial::specular_texture
et utilisez-les pour moduler le résultat des calculs
d'éclairement.