|
gKit2 light
|
tous les objets ne sont pas composés d'une seule surface avec une seule couleur / apparence / matière. même le robot utilise plusieurs matières de couleurs différentes :

on a vu comment dessiner le robot et l'éclairer correctement dans premières lumières, éclairer un objet, sRGB et gamma. que faut-il modifier pour gérer plusieurs matières ?
comme d'habitude, il y a plusieurs solutions... soit on fait bosser l'application, soit on utilise les shaders.
la solution intuitive consiste à dessiner chaque triangle avec les bons paramètres de couleur / matière. mais dessiner les triangles un par un n'est vraiment pas une bonne idée. une application ne peut pas vraiment espérer faire plus de ~10000 draws et rester temps réel à 60 images par seconde, ce qui limite un peu trop la quantité de géométrie que l'on peut dessiner. allez voir mesure du temps cpu et gpu pour vous convaincre. une bien meilleure solution consiste à grouper les triangles par matière et à dessiner chaque groupe avec les bonnes couleurs / paramètres de matières.
lorsque l'on charge un objet avec read_mesh(), par exemple, un ensemble de matières, cf Materials, est aussi chargé, et chaque triangle connait l'indice de sa matière. il ne reste plus qu'à trier les triangles en fonction de cet indice de matière. les triangles associées à la matière 1 se trouvent au début, suivis par les triangles de la matière 2, etc. c'est exactement ce que fait Mesh::groups(), la fonction trie les triangles et renvoie la position du premier triangle ainsi que le nombre de triangles de chaque groupe :
ces informations sont représentées par une structure toute simple TriangleGroup :
il suffit de dessiner groupe par groupe :
ou directement avec openGL :
ou sont les couleurs ? dans Materials qui est chargé en même temps que la géométrie par read_mesh() / read_indexed_mesh(). on récupère cet ensemble de matières avec Mesh::materials(). après le tri par matière avec Mesh::groups(), il ne reste plus qu'à parcourir les groupes et TriangleGroup::index indique quelle matière utiliser pour dessiner les triangles. il suffit de récupérer la couleur de chaque matière, ie Material::diffuse dans Material et de paramétrer le shader avant de dessiner. au final, c'est plutôt direct :
code complet dans tuto9_groups.cpp et tuto9_groups.glsl
la solution précédente semble pas mal intuitive, mais on peut faire encore plus direct en utilisant les shaders...
quel est le problème ? il suffit de connaître l'indice de la matière et d'avoir un tableau de couleurs pour utiliser la bonne couleur pour chaque triangle. ces infos sont dans l'application, mais pas dans les shaders... est ce qu'il est possible de les transmettre aux shaders ?
le fragment shader connait l'indice du triangle qu'il est en train de dessiner, ie gl_PrimitiveID, cf shaders et GLSL, il suffit de déclarer dans le shader un tableau d'indice de matière par triangle + un tableau de matière, et hop c'est fini ! pour dessiner ~1000 triangles, ça marche, mais la taille des tableaux uniforms déclarés dans le shader est limitée à 32Ko au total, soit 8000 ints, c'est pas énorme (et en plus il faut déclarer leur taille, ce qui n'est vraiment pas pratique). on pourrait créer des buffers, cf openGL 3.3 : uniform buffers, mais ce ne serait pas aussi simple à manipuler...
que reste-t-il comme moyen de transmettre des infos aux shaders ? les vertex buffers ! il suffit d'ajouter une information aux sommets : l'indice de la matière du triangle qui utilise le sommet. Mesh le fait automatiquement, il suffit de déclarer l'attribut de sommet dans le shader, en respectant la convention de Mesh, cf première application avec des shaders. il suffit de transmettre l'indice au fragment shader, et hop !
mais c'est un peu bizarre d'interpoler un indice... ie interpoler un entier entre les sommets du triangle et l'intérieur du triangle ? euh, si. mais c'est prévu dans GLSL, on peut indiquer que l'on ne veut pas interpoler une sortie du vertex shader, avec le mot clé flat.
pour les curieux : comme la valeur n'est pas interpolée, et pour éviter les surprises lorsque les sommets d'un triangle sont associés à des valeurs entières différentes, le pipeline choisit la valeur du premier sommet du triangle et ignore les autres. petite blague, selon l'api, direct3d ou opengl, ou vulkan ou metal, le "premier sommet" n'est pas le même... cf les conventions "provoking vertex" des différentes apis.
on a enfin l'indice de la matière dans le fragment shader, comment on retrouve la couleur ? avec un (petit) tableau de couleurs, une par matière ! il suffit de le préparer dans l'application. et connaissant l'indice de la matière, il n'y a plus qu'à récupérer la couleur dans le tableau pour finir d'éclairer le point...
et le code de préparation dans l'application va ressembler à :
code complet dans tuto9_materials.cpp et tuto9_materials.glsl
pour les curieux : si vous trouvez que dupliquer l'indice de la matière sur chaque sommet d'un triangle n'est pas très élégant, vous avez en partie raison. mais allez voir Mesh::create_buffers() et Mesh::update_buffers() : l'indice de matières est un entier 8bits, et au final cette solution utilise moins de place qu'un indice entier 32bits. mais on est effectivement limité à 256 matières. en pratique, c'est rarement un problème.