M2 - Images

TP 3 - Gestion de scène, instanciation
et geometry shaders ...





exemple d'utilisation complet du mécanisme de feedback,
+ 2 exemples pour montrer les différences entre l'api historique et l'api "core",
+ classes utilitaires : gk::BufferManager pour creer des buffers facilement et gk::QueryManager pour les requetes, meme utilisation que gk::ShaderManager,
+ mise à jour gKit.

gltp3_b77.tar.gz


gltp3_b132.tar.gz
autre version des memes exemples avec une classe GLBuffer plus complete et sans doute plus simple a utiliser.
+ correction du bug de GLShaderProgram qui empechait de re-linker le shader.

dites moi ce que vous en pensez !





Partie 1 : prise en main

Q1. écrivez les geometry shaders des deux exercices du cours.

Q2. instanciation.

    utilisez glDrawXXXInstanced( ) pour afficher un "grand" nombre d'objets, disposés sur une grille, par exemple.
    utilisez un buffer (d'attribut d'instances) pour paramétrer vos instances. dans un premier temps, une simple translation suffira.

    On peut également déclarer un tableau de matrices de transformations dans le shader et l'utiliser avec gl_InstanceID pour placer et orienter correctement chaque instance de l'objet.

    rappel : la quantité de mémoire disponible pour stocker les uniforms d'un shader est limitée :
    on peut la connaitre exactement pour chaque type de shader avec glGetXXX( ) :
        GLint n;
        glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &n);
        glGetIntegerv(GL_MAX_GEOMETRY_UNIFORM_COMPONENTS, &n);
        glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &n);



Partie 2 : visibilité et instanciation

L'objectif de cette partie du tp est d'arriver à déterminer quels objets (ou quelles instances) sont visibles par la camera. La premiere étape consiste à utiliser un geometry shader pour calculer la visibilite de la boite englobante de chaque objet. Ensuite, il faudra "récupérer" ce résultat et l'utiliser pour ne dessiner que les instances visibles. Il y a donc 2 passes successives.

La solution la plus directe consiste à utiliser un buffer d'attribut d'instances pour placer les instances dans la scène, et à construire le geometry shader pour répliquer en sortie uniquement les paramètres des instances visibles.

Q1. modifiez le geometry shader qui construit la boite englobante à partir de pMin et pMax pour tester la visibilite de la boite englobante.

Q2. enregistrer les résultats de visibilité dans un buffer.

Une texture associée à un framebuffer permet de dessiner dans la texture au lieu de dessiner dans la fenetre de l'application. Il est aussi possible d'associer un buffer aux varyings (varying out, les sorties) du vertex shader et/ou du geometry shader et d'enregistrer dans ce buffer le résultat de l'exécution du shader. Ce mécanisme s'appelle transform feedback dans l'api opengl, il est disponible à partir de la version 3.3 et a été complété et très assoupli par la version 4.1 et les extensions ARB_transform_feedback2 et ARB_transform_feedback3. Nous allons commencer par la version "simple" exposée par opengl 3.3.

Comme toute les fonctionnalités opengl, il faut la configurer et l'activer.

étape 1 : configuration du shader
déclarer quels varyings du shader doivent etre enregistrés dans un ou plusieurs buffers.
glTransformFeedbackVaryings( ) : il suffit de passer un tableau des noms des varyings à enregistrer.
Les varyings peuvent etre stockés de deux manières :
    soit tous dans le meme buffer, les uns à la suite des autres, bufferMode= GL_INTERLEAVED_ATTRIBS, (1 seul buffer à créer)
    soit un seul par buffer, bufferMode = GL_SEPARATE_ATTRIBS (créer un buffer par paramètre).

(re-)linker le shader program :
GLShaderProgram::createGLResource() ou GLShaderProgram::link( ) (qui utilise glLinkProgram()) si le createGLResource() a deja ete utilise sur le shader.

glGetTransformFeedbackVarying( ) permet ensuite de récupérer le type des varyings, par exemple.

étape 2 : configuration des buffers pour enregistrer les varyings déclarés à l'étape 1
créer autant de buffers que nécessaire :
glGenBuffers( )
glBindBuffer( )
glBufferData( ), choisir une taille suffisante pour enregistrer toutes les valeurs.

associer le/les buffers aux varyings enregistrés :
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, index, buffer) le paramètre index est l'indice du varying dans le tableau utilisé pour configurer le shader, cf étape 1.

étape 3 : activer le transform feedback et dessiner
glBeginTransformFeedback( primitive_feedback )
drawXXXInstanced( primitive_draw ... )
glEndTransformFeedback()


remarques :
Après le glEndTransformFeedback() les buffers contiennent les valeurs des varyings enregistrés. il ne reste plus qu'à utiliser ces résultats.

Q3. comment utiliser les résultats de visibilité ?

L'objectif est bien sur de ne pas dessiner les instances non visibles de la camera.
Il y a plusieurs solutions, bien entendu. Soit le geometry shader de la premiere passse enregistre l'indice des instances visibles et il suffit de créer un autre geometry shader qui récupère les informations sur une instance visible en lisant son indice dans un buffer d'instances. Soit le geometry shader de la premiere passe a dupliqué les paramètres des instances visibles et il ne reste plus qu'a dessiner les instances en fonction de ces paramètres.

Pensez à activer le/les buffers utilisés par le transform feedback comme attribut de sommets (ou d'instance) pour dessiner les objets dans cette deuxieme passe.

Reste un dernier problème : comment déterminer le nombre d'instances visibles qui sera inférieur aux nombre d'instances dessinées ?

En opengl 3.3, il faut utiliser une "requête" qui compte le nombre de valeurs stockées dans les buffers pendant l'exécution du transform feedback.

passe 1: dessiner les instances et compter le nombre d'instances visibles.
        // dans App::init() générer un objet requête.
glGenQueries( ),

// dans App::draw( )
glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, )
glBeginTransformFeedback( )
drawXXXInstanced( ... )
glEndTransformFeedback()
glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, )

récupérer la valeur :
        GLuint count;
glGetQueryObject( ,GL_QUERY_RESULT, &count)

passe 2 : dessiner les instances visibles.
glBindBuffer( )
glVertexAttribPointer( )
glEnableVertexAttribArray( )
drawXXXInstanced( )

remarques :

   

question bonus : utiliser le tracking d'une mire pour modifier les parametres d'instances ou le point de vue.


question bonus : visibilité d'objets non instancies.

Comment utiliser ce type de mécanisme pour dessiner des objets différents les uns des autres et qui ne sont pas instanciés ?
Un nouveau mécanisme de opengl 4.1 permet de le faire : cf. DrawXXXIndirect( ), il est maintenant possible d'utiliser un geometry / vertex shader pour déterminer la visibilité d'un objet et construire un buffer de commandes d'affichage d'objets quelconques ...
Le mécanisme de transform feedback est également plus souple :
cf. les extensions ARB :
  1. GL_ARB_transform_feedback2
  2. GL_ARB_transform_feedback3
euh, il va falloir attendre la prochaine version d'opengl pour faire ca, pour l'instant glDrawXXXIndirect( ) est limité à un seul draw.

question bonus : visibilité des instances

Il est également possible de n'afficher que les objets effectivement visibles : en construisant un Z hiérarchique.