gKit2 light
|
cf tuto9.cpp + tuto9_color.glsl
draw( mesh, model, camera )
et les autres utilitaires de draw.h dessinent un objet avec un shader crée en fonction de la description de l'objet et des paramètres. Il est aussi très simple de continuer à utiliser Mesh pour décrire l'objet et de le dessiner avec un shader différent, cf Mesh::draw( program, ... )
. Il faut par contre créer et configurer le shader avant de pouvoir dessiner l'objet, ainsi que respecter la même convention que Mesh pour décrire les sommets.
repassez dans introduction api 3d, openGL et pipeline graphique et shaders et GLSL, si nécessaire.
les détails openGL sont dans compiler et linker un shader program, le plus simple est d'utiliser l'utilitaire read_program() fournit par program.h :
les shaders sont des fonctions comme les autres, pour produire leurs résultats, il faut d'abord affecter une valeur à leurs paramètres avant de pouvoir les exécuter. la syntaxe est inhabituelle, mais pas compliquée. les paramètres se déclarent avec le mot-clé uniform
dans les fragment et vertex shaders. mais un vertex shader peut déclarer un autre type de paramètre : un ou plusieurs attributs de sommet comme la position, la couleur, la normale, etc...
pourquoi 2 types de parametres ? les shaders (et le reste du pipeline graphique) sont exécutés par les processeurs de la carte graphique au moment ou l'application utilise une fonction draw( )
. La carte graphique transforme tous les sommets en parallèle : un vertex shader (cf l'exemple suivant) est exécuté par n threads. La matrice de transformation est la même pour tous les sommets (et tous les threads), mais bien sur ce n'est pas le cas pour les attributs de sommets... comme la position. Chaque thread qui exécute le vertex shader doit transformer un sommet different... et il faut bien "fournir" la position de chaque sommet à chaque thread.
C'est pour cette raison que les paramètres mvpMatrix
et position
sont déclarés avec des mots-clés différents, uniform
pour indiquer que tous les sommets utilisent la même transformation (la même matrice, c'est un paramètre "normal") et le mot-clé in
pour indiquer que position est différent pour chaque sommet. Et bien sur, affecter une (seule) valeur à un uniform est différent d'affecter un ensemble de valeurs à un attribut. Mesh s'occuppe des attributs, le plus "complique", mais l'application est responsable des uniforms.
il faut affecter une valeur à chaque paramètre uniform déclaré par le vertex shader et par le fragment shader. les fonctions d'openGL ne sont pas très pratiques à utiliser, du coup uniforms.h
fournit plusieurs utilitaires simplifiés, qui permettent d'affecter des valeurs des types courants :
oui c'est moche... les paramètres sont identifiés par une chaine de caractères.
mais pourquoi ?? les variables / les paramètres des shaders sont stockés dans la mémoire de la carte graphique, pas dans la mémoire accessible normalement à l'application. il faut imaginer que l'application et les shaders fonctionnent sur 2 machines différentes connectées par un réseau.
il faut aussi, comme d'habitude, affecter une valeur du bon type. les erreurs ne sont detectés qu'à l'exécution de l'application, pas lors de la compilation. elles s'afficheront dans la console, qu'il faudra penser à surveiller en cas de comportement bizarre, image noire, etc.
remarque : afficher plusieurs triangles, modifier les paramètres uniform d'un shader program explique en détail comment affecter une valeur à chaque uniform déclaré dans les shaders directement avec les fonctions openGL.
Pour calculer la transformation mvp, qui permet de projeter directement un sommet sur la camera, il faut savoir ou se trouve l'objet dans le monde, ou se trouve la camera et quelle est sa projection, c'est à dire les transformations / matrices model, view et projection. Jusqu'à présent afficher un objet ressemblait à ça :
La camera calcule les transformations view et projection, il suffit de les recupérer et de composer model, view, et projection pour obtenir une seule matrice qui enchaine les 3 changements de repères :
remarque : si le fragment shader (ou le vertex shader) utilise d'autres parametres, il ne faut pas oublier de leur affecteur une valeur...
il faut respecter la convention utilisée par Mesh, qui numérote les attributs des sommets et impose un type qu'il faut respecter :
vec3 position
,vec2 texcoord
,vec3 normal
,vec4 color
,uint material;
voila la déclaration à utiliser dans le vertex shader, en fonction des attributs nécessaires :
par exemple, pour un vertex shader tout simple :
les numeros ne changent pas, il suffit de copier les déclarations ci-dessus. par exemple pour utiliser postion et normale :
Une fois le shader program complètement paramétré, il ne reste plus qu'à dessiner l'objet avec Mesh::draw( program ... )
. Un dernier détail, il faut indiquer quels attributs de sommets sont nécessaires à l'exécution des vertex shaders, cf les parametres bool
:
cf tuto9.cpp pour un exemple complet qui utilise un uniform supplémentaire, qui permet de donner la même couleur aux pixels de l'objet.
Mesh::draw( ) vérifie (en mode debug, en tout cas) que les attributs déclarés dans le vertex shader sont disponibles dans le Mesh. par exemple, si le vertex shader déclare / utilise un attribut normale par sommet mais que ces valeurs ne sont pas dans le Mesh, le shader ne peut pas fonctionner...
la chose à retenir de tout ça est que lorsqu'un shader program, composé d'un vertex shader et d'un fragment shader déclare ses paramètres, par exemple :
avant de pouvoir dessiner un objet / des triangles, il faut affecter une valeur aux paramètres uniforms
déclarés :
relancer l'application à chaque fois qu'un shader plante, n'est pas très pratique, il est possible de recharger les shaders à la volée, sans quitter l'application, cf reload_program() de program.h
.
attention : les uniforms sont re-initialisés à 0 par la compilation d'un shader.
si plusieurs shaders, ou plusieurs paramètres (couleurs, matieres, etc) sont nécessaires pour dessiner les triangles de l'objet, une solution est de trier les triangles de l'objet par matière et de dessiner chaque groupe de triangles avec les bons paramètres : exemple complet dans tuto9_groups.cpp, tuto9_groups.glsl.
cf tuto9_buffers.cpp pour un exemple complet qui alloue et initialise un buffer pour stocker les positions des sommets du Mesh et qui configure un format de sommet (un vertex array object) pour dessiner un objet directement avec openGL, sans utiliser les utilitaires draw( ).
Il y a plusieurs étapes :
Lisez la section "écrire le fragment shader (et le vertex shader)" dans textures, samplers et pipeline pour comprendre comment les différents morceaux se connectent ensemble.
Dernière étape, configurer le pipeline pour utiliser la texture, vous pouvez lire les détails dans textures, samplers et pipeline ou utiliser l'utilitaire program_use_texture( );
cf tuto9_texture1.cpp pour un exemple complet qui charge une texture.
C'est la même chose, par contre, il faut charger et configurer une unité de texture par image / texture à utiliser et déclarer un sampler2D par texture dans le shader.
cf tuto9_textures.cpp pour un exemple complet qui charge et utilise 2 textures.