gKit2 light
|
mais à quoi ça sert ? à décorer des objets (en plus des textures habituelles), ou à ajouter des détails à la volée, à peindre sur le décor et, après un peu de travail, à projetter l'ombre des objets sur le décor !
vous pouvez regarder des exemples de jeux de plateforme ou de runners, souvent les ombres sont très simples / simplifiées et ne représentent pas vraiment la forme de l'objet qui les projette...
pour projetter une texture, une image, on commence par placer et orienter une camera qui doit observer la zone sur laquelle projetter la texture, relisez en détail la partie camera dans premiers objets, affichage et transformations, si nécessaire.
ce type de détail, ajouté par projection d'une image, s'appelle un décal.
comment dessiner une ombre simplifiée sous un personnage ?
comme suggéré juste au dessus, il 'suffit' de placer et d'orienter une camera à la verticale au dessus d'un personnage, en suivant les explications de premiers objets, affichage et transformations.
reste à afficher / dessiner une texture sur le décor observé par cette camera :
quelles sont les matrices view
et projection
de cette nouvelle camera ? pour view, pas de problèmes, c'est l'inverse de position
et m
, pour la projection, on a le choix : soit une perspective comme d'habitude, cf Perspective(), soit une projection orthographique cf Ortho() :
pour obtenir ca :
pour ajuster les paramètres des projections, le plus simple est de pouvoir visualiser la forme du frustum de la camera : afficher la région observée par la camera.
c'est encore une application (directe) des compositions des transformations et des produits de matrices...
on connait les coordonnées des sommets de la région observée par la camera, mais dans le repère projectif de la camera. ce sont les sommets du cube [-1 1]
sur les 3 axes (par définition)... pour dessiner ce cube dans le repère de la scène, il faut calculer le changement vers la scène. habituellement, on connait les coordonnées dans le repère de la scène, puis ils sont transformés dans le repère camera, puis dans le repère projectif. là, on veut faire le contraire : on connait les coordonnées dans le repère projectif...
c'est tout simplement l'inverse des transformations (de leur produit) ! si q
représente les coordonnées dans le repère projectif, et p
les coordonnées dans le repère de la scène, on peut écrire :
\( q= projection * view * p \)
et dans l'autre sens :
\( p= (projection * view)^{-1} * q \)
maintenant que tous les paramètres sont bien choisis, il ne reste plus qu'à finir le travail... pour chaque point p
de chaque objet que l'on dessine, il faut trouver ses coordonnées dans le repère projectif de la camera que l'on vient de positionner, récupérer la couleur de l'image et modifier la couleur de l'objet...
c'est encore une application des compositions de transformations, il suffit de calculer un changement de repère : depuis la scène vers la projection puis vers les coordonnées de pixels dans l'image / la texture que l'on projette.
c'est la même chose que toute à l'heure :
\( q= viewport * projection * view * p \)
avec un détail supplémentaire : les coordonnées utilisées pour lire la couleur d'un pixel de la texture sont dans [0 1], alors que les coordonnées de la projection sont dans [-1 1], c'est la transformation Viewport() qui ajuste ça :
il ne reste plus qu'à écrire les shaders pour faire les calculs sur la carte graphique :
le vertex shader est sans surprise, il fait la transformation habituelle pour dessiner un objet, mais en plus calcule aussi les coordonnées du sommet dans la repère projectif de la camera placée au dessus de l'objet pour projetter le décal.
le fragment shader récupère ces coordonnées, lit la texture et modifie la couleur de l'objet :
pourquoi les bords de la texture se repètent sur tout l'objet ? tout simplement parce que tous les points de l'objet se transforment correctement dans l'espace projectif de la camera du décal. mais, bien sur, il est possible d'éliminer les bordures et vérifiant les coordonnées que l'on vient de calculer... si les coordonnées transformées ne sont pas dans le repère image [0 1], ils ne correspondent pas à un pixel de la texture, mais openGL affiche quand meme quelque chose (la couleur du bord), au lieu de crasher (parce qu'on accède à un pixel de la texture qui n'existe pas)...
on peut vérifier les coordonnées avant de lire la couleur de la texture et ne pas modifier la couleur de l'objet :
il suffit de charger une autre texture, par exemple, une magnifique patate noire sur fond blanc, dessinée dans gimp....
ou un splash de couleur... on peut aussi dessiner le decal sur plusieurs objets, pas uniquement le decor...
code complet dans tuto_decal.cpp et decal.glsl
openGL permet d'ajuster la couleur renvoyee par la texture pour des coordonnées en dehors de [0 1] : il faut modifier les parametres de la texture, après l'avoir chargée dans l'init :
GL_CLAMP_TO_BORDER
indique que les coordonnées en dehors de la texture renvoie la couleur du bord
(la couleur du premier ou du dernier pixel de la ligne ou de la colonne dans l'image) , et GL_TEXTURE_BORDER_COLOR
permet de choisir cette couleur, mais uniquement du blanc ou du noir, les autres valeurs ne sont pas prises en compte. dans le cas des décals, on peut utiliser cette fonctionnalité (renvoyer du blanc en dehors de la texture). sinon, il faut faire les tests sur texcoord
dans le shader et renvoyer la bonne couleur, comme dans l'exemple au dessus qui colorie chaque bordure d'une couleur différente.
remarque : ce problème n'est visible que lorsque le bord de la texture n'est pas constant, comme sur la grille. avec une patate peinte au milieu de la texture, le problème n'est pas visible...
openGL peut aussi faire le passage coordonnées homgènes (4d) vers coordonnées réelles (3d) automatiquement, il suffit d'utiliser textureProj()
pour lire la texture au lieu de la fonction habituelle texture()
:
attention : la texture renvoie aussi la couleur du bord
dans ce cas. on peut corriger ce défaut en modifiant les paramètres de la texture ou en écrivant les tests sur les coordonnées, comme au dessus.