vendredi 29 janvier 2021

Afficher un quad plein écran avec OpenGL

Je suis en train de m'intéresser aux "skyboxes", c'est à dire l'affichage d'un environement tout autour de la caméra via une texture. En gros, l'idée est de créer un cube texturé qui suive la position de la caméra (voyez les explications techniques ici) pour afficher le "fond" du paysage, comme un ciel, une galaxie, un horizon urbain, qui remplisse le monde à peu de frais avant d'afficher les vrais objets à proximité. Manifestement, juste afficher un gros cube est maintenant dépassé, et bien plus recommandé est l'idée d'afficher juste un quad formé de deux triangles qui couvrent complètement l´écran, et de projeter la texture correctement. Je passe sur les détails de la projection avec lesquels je suis encore en train de me battre, pour vous parler plutôt de l'affichage du quad.

Comment afficher ce polygone ? La première idée qui vient, évidemment, c'est de créer les vertex buffers, les element buffers, de faire tous les appels à "bind" qui vont bien, et tout le toutim. Mais il semblerait qu'il y ait des solutions bien plus intelligentes...

En effet, pourquoi ne pas mettre toute la géométrie directement dans le vertex shader, puisqu'elle est fixe ? On sait que l'on veut des triangles étirés entres les coordonnées (-1, -1 ) et (1, 1), et l'on sait également quelles seront les coordonnées de textures voulues.

Le truc, c'est qu'un vertex shader a accès à l'index du vertex qui est en train d'être calculé. Donc, en incluant dans le shader un tableau qui indique pour chaque index quelles sont ses coordonnés de position et de texture, il n'y a plus qu'à retrouver les données à la volée, et c'est bon. Regardez moi ça, je vous le fais en simplifié :

Dans le code C++, on a un simple appel à draw, à poil, sans rien autour (à part la mise en oeuvre du shader)

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

Et dans le vertex shader, on utilise l'index gl_VertexID pour accéder aux coordonnées voulues dans les tableaux positions et coords, et on fixe notre z à 0.99 pour l'avoir au fond :

out vec2 textureCoords;

void main() {
    const vec2 positions[4] = vec2[](
        vec2(-1, -1),
        vec2(+1, -1),
        vec2(-1, +1),
        vec2(+1, +1)
    );
    const vec2 coords[4] = vec2[](
        vec2(0, 0),
        vec2(1, 0),
        vec2(0, 1),
        vec2(1, 1)
    );

    textureCoords = coords[gl_VertexID];
    gl_Position = vec4(positions[gl_VertexID], 0.99, 1.0);
}

Voilà, c'est incroyablement simple. Avec un seul appel OpenGL (1 ! UN !) on arrive à afficher le Quad qui va bien. Magique, non ?

(Plus d'infos, et plus d'approches supplémentaires, VBO, geometry shaders et tout le toutim, sur la sur la question StackOverflow qui va bien)

samedi 16 janvier 2021

Git et les sous-modules

Je viens de découvrir le concept des sous-modules dans Git, et je trouve ça vachement malin.

En effet, il fut un temps où pour construire une bibliothèque ou application tierce, il fallait commencer par aller chercher tout un tas de dépendances, les construire, les installer, et ainsi de suite, en espérant ne pas trop se tromper dans les versions. Un cauchemard.

Et pourtant, j'ai découvert que maintenant, nombre de projets sur Github incluent directement leurs dépendances sous forme de sous-modules, ce qui fait que l'on clone le dépôt, qui ensuite va cloner d'autres dépôts dans les répertoires qui vont bien, et ainsi de suite. Et surtout, le dépôt principal enregistre non seulement le lien vers le sous-dépôt, mais la version du code, ce qui permet de récupérer exactement la bonne version.

Voyez par exemple ce dépôt : dans son sous-répertoire "externals", il possède un lien vers les dépôts de gli et de glm, à une version donnée. Glm, par exemple, peut avancer, et produire un changement qui casse la compatibilité. Si je clone Vulkan-glTF-PBR en utilisant la commande "git clone --recursive", je vais récupérer la version ancienne de Glm. Je peux ensuite si je le souhaite bouger la version GLM pointée, corriger le code pour que ça compile, et envoyer la pull request.

J'ai commencé à utiliser cette approche pour séparer mon moteur de rendu de la démo qui l'utilise: ainsi, je peux avancer sur le moteur à travers une démo, puis créer une nouvelle démo qui pointe vers ce même moteur et avancer sur un autre point. Ainsi, le code du moteur reste propre, et il est particulièrement simple de cloner une démo et de commencer à coder. Pas besoin de gérer chemins ou variables d'environnement, tout est à la bonne place.

Bonne année !

Bonne année à tous, oh, vous lecteurs innombrables de mes petits billets par trop infréquents. Je vous souhaite une excellent année 2021, et vous fait de gros bisous !