M2 - Images

TP 1 - Transformations, affichage
et visibilité



installez gKit et ses dépendances. compilez la doc : cd gKit; doxygen; firefox html/index.html


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 :


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 avant d'appeler createGLResource(). 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().
 

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 libSDL2 est également disponible ici.

mesh_viewer fourni dans l'archive gKit donne un exemple assez complet de la gestion clavier / souris.
question bonus : modifiez la classe gk::Camera et corrigez ses erreurs de rotation, et propagez les modifications dans gk::Orbiter.


Exemple complet d'application utilisant gKit (inclus dans l'archive, tuto.cpp) :

#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
        swap();
        return 1;       // continuer
    }
};


int main( int argc, char **argv )
{
    TP app;
    app.run();
   
    return 0;
}

Exercice 1 : tuto_coreprofile

compilez l'exemple à compléter, tuto_coreprofile.cpp :

premake4 gmake; make tuto_coreprofile
./tuto_coreprofile

Dans un premier temps, utilisez une transformation identité : quelle est la partie de scène visible par la camera ?
Comment paramétrer le shader avec la transformation ? cf gk::setUniform()

Décrivez la position de 3 sommets d'un triangle, visible dans ces condiditions, modifiez le début de la fonction init( ).
rappel : l'ordre des sommets est important, respectez le sens trigo lorsque la face est vue de l'extérieur
ou désactivez l'élimination des faces cachées/mal orientiées dans la fonction init( ): glDisable(GL_CULL_FACE);

Décrivez un cube.

Modifiez les transformations : définissez une transformation perspective avec gk::Perspective() et déplacez votre cube.

Décrivez un cube en utilisant des sommets indexés / partagés.


aide à la solution :

// insere les indices de 3 sommets decrivant un triangle oriente dans un tableau d'indices :
void pushTriangle( std::vector<unsigned int>& indices, const unsigned int a, const unsigned int b, const unsigned int c )
{
    indices.push_back(a);
    indices.push_back(b);
    indices.push_back(c);
}

// insere les indices de 4 sommets decrivant un quad oriente dans un tableau d'indices :
void pushQuad( std::vector<unsigned int>& indices, const unsigned int a, const unsigned int b, const unsigned int c, const unsigned int d )
{
    pushTriangle(indices, a, b, c);
    pushTriangle(indices, a, c, d);
   
    // attention a l'ordre des sommets : respecter le sens trigo
}

initialisation de la position des sommets et des faces du cube avec indexation des sommets :

// initialisation : construire l'ensemble des positions des sommets des triangles a dessiner
std::vector<gk::Point> positions;
{
    positions.push_back( gk::Point(0.0,  0.0,  0.0) );  // sommets face arriere (z= 0)
    positions.push_back( gk::Point(1.0,  0.0,  0.0) );
    positions.push_back( gk::Point(1.0,  1.0,  0.0) );
    positions.push_back( gk::Point(0.0,  1.0,  0.0) );
   
    positions.push_back( gk::Point(0.0,  0.0,  1.0) );  // sommets face avant (z= 1)     // la camera regarde -z
    positions.push_back( gk::Point(1.0,  0.0,  1.0) );
    positions.push_back( gk::Point(1.0,  1.0,  1.0) );
    positions.push_back( gk::Point(0.0,  1.0,  1.0) );
}

// initialisation : construire l'ensemble d'indices des faces du cube
std::vector<unsigned int> indices;
{
    pushQuad(indices, 4, 5, 6, 7);      // face avant
    pushQuad(indices, 1, 2, 6, 5);      // face doite
    pushQuad(indices, 0, 1, 5, 4);      // face dessous
    pushQuad(indices, 0, 3, 2, 1);      // face arriere
    pushQuad(indices, 0, 4, 7, 3);      // face gauche
    pushQuad(indices, 2, 3, 7, 6);      // face dessus
}
m_count= indices.size();      // conserve le nombre d'indices places dans l'index buffer

Exercice 2 : et en couleur ?

Modifiez la description du triangle pour associer une couleur à chaque sommet. et utilisez la paire de shaders :

m_program= gk::createShaderProgram( "vertex_color.vsl", "fragment_color.fsl" );  

Il faut bien sur décrire le nouvel attribut de sommet : une couleur, et l'associer au shader program.

Modifiez la description du cube pour obtenir des faces de couleurs différentes.
Quelle est la définition d'un vertex pour openGL ?


Exercice 3 : se déplacer

Proposez une manière de déplacer l'observateur ou de re-orienter l'objet.


Exercice 4 : charger et afficher un objet

gKit fournit également des fonctions permettant de charger très simplement un objet décrit dans un fichier texte, les fichiers .obj utilisés par Maya/Blender, etc. :

#include "Mesh.h"    // classe Mesh
#include "MeshIO.h"    // contruction d'un Mesh a partir d'un fichier decrivant la forme d'un objet

gk::Mesh *mesh= gk::MeshIO::read("bigguy.obj");
if(mesh == NULL)
    return "erreur de chargement";

La classe gk::Mesh organise ses données de manière à simplifier la création des buffers d'attributs de sommets et d'indices :

std::vector<gk::Point>& positions= mesh->positions();
std::vector<gk::Normal>& normales= mesh->normals();
std::vector<int>& indices= mesh->indices();

il suffit de créer le/les buffers et de transférer les données pour afficher l'objet composé de triangles.

Chargez un objet, "bigguy.obj", par exemple, construisez le/les buffers et affichez-le en utilisant la paire de shaders de base (couleur uniforme).


remarque : si vous trouvez pénible la gestion "manuelle" des buffers et des objets openGL, utilisez les fonctions utilitaires de gKit :

#include "BufferManager.h"

gk::GLAttributeBuffer *position_buffer= gk::createAttributeBuffer( );
gk::GLIndexBuffer *indice_buffer= gk::createIndexBuffer( );


Exercice 5 : mon premier shader

Modifiez les shaders de base, vertex.vsl et fragment.fsl pour déterminer une couleur du pixel qui dépend de l'orientation du triangle dessiné.

"Gonflez" l'objet : poussez / déplacez les sommets des triangles dans la direction de leur normale (orientée vers l'extérieur).


Exercice 6 : encore un shader ?

Testez le geometry shader vu en cours qui dessine la normale des triangles d'un objet.
remarque : vérifiez le fonctionnement avec ou sans vertex shader.

Ecrivez un geometry shader qui calcule la normale des triangles et la transmet au fragment shader pour déterminer une couleur qui dépend de l'orientation des triangles par rapport à la camera, par exemple :

        fragment_color= dot(normalize(normale), vec3(0, 0, 1)) * color;   
        // ou
directement fragment_color= normalize(normale).z


remarque: les fonctions GLSL sont documentées : http://www.opengl.org/sdk/docs/manglsl/

Exercice 7 : instanciation

Affichez plusieurs objets disposés en ligne, ou sur une grille.
Utilisez glDrawXXXInstanced et gl_InstanceID dans un vertex shader pour placer chaque instance dans la scène.

Créez un buffer contenant une translation permettant de placer chaque instance dans la scène.
rappel: utilisez glVertexAttribDivisor( ) pour indiquer que l'attribut est un attribut d'instance (et pas un attribut de sommet).