La canal alpha

Le concept du canal alpha est très simple. À la place d’écrire un résultat en RGB, on écris un résultat en RGBA :

// Données de sorties : c'est maintenant un vec4 
out vec4 color;

Les trois premières composantes sont toujours accessibles avec .xyz (ou .rgb), tandis que la dernière est accessible avec .a :

color.a = 0.3;

Contre toute logique, alpha = opacité, donc alpha = 1 signifie complètement opaque alors que alpha = 0 signifie complètement transparent.

Ici, on a simplement codé en dur le canal alpha à 0.3, mais tu préféreras sûrement utiliser une variable uniforme ou la lire à partir d’une texture RGBA (le format DDS supporte le canal alpha).

Voici le résultat. Assures-toi de désactiver le « backface culling » (suppression des faces arrières) (glDisable(GL_CULL_FACE)) car comme on peut voir à travers le modèle, on pourrait voir si on ne dessine pas la face « arrière ».

L’ordre est important

La capture d’écran précédente paraît correcte, mais c’est uniquement car on est chanceux.

Le problème

Ici, j’ai dessiné deux carrés ayant 50 % d’alpha, un vert et un rouge. Tu peux voir que l’ordre est important, la couleur finale donne une importante piste visuelle sur la perception de la profondeur.

Ce phénomène se produit aussi sur la scène. Si tu change un peu ton point de vue :

On voit tout de suite le problème apparaitre. En fait, c’est un problème très complexe. Tu ne vois jamais énormément de transparence dans les jeux vidéo, n’est-ce pas ?

Solution classique

La solution classique est de trier tous les triangles transparents. Oui, TOUS les triangles transparents.

  • Dessine la partie du monde opaque afin que le tampon de profondeur puisse déjà rejeter les triangles transparents cachés
  • Trie les triangles transparents, du plus loin aux plus proches
  • Dessine les triangles transparents

Tu peux trier ce que tu veux avec qsort (en C) ou std::sort (en C++). Je n’entrerai pas dans les détails, car…

Mise en garde

Faire comme cela fonctionne (plus de détails dans la section suivante), mais :

  • Tu vas être limité par la bande passante. En effet, chaque fragment sera écrit 10, 20 fois ou même plus. Cela est beaucoup trop pour le pauvre bus mémoire. Habituellement, le tampon de profondeur permet de rejeter assez de fragments « lointains », mais là, tu les tries explicitement, faisant que le tampon de profondeur devient totalement inutile
  • Tu vas faire cela quatre fois par pixel (on utilise le 4xMSAA), sauf si tu utilises une optimisation plus intelligente
  • Le tri des triangles prend du temps
  • Si tu dois changer de texture, ou pire, de shader, de triangle en triangle, tu vas avoir de sérieux problèmes de performance. Ne le fait juste pas.

Une solution assez bonne est souvent de :

  • Limiter à un maximum le nombre de polygones transparents
  • Utiliser le même shader et la même texture pour tous les polygones transparents
  • S’ils sont sensés être très différents, utilise ta texture (atlas) !
  • Si tu peux éviter le tri et que ça ne soit pas trop moche, tu peux t’estimer chanceux :)

Transparence indépendante de l’ordre

De nombreuses autres techniques sont intéressantes si ton moteur a vraiment, vraiment besoin de l’état de l’art de la transparence :

Même un jeu récent comme Little Big Planet, s’exécutant sur une console puissante, utilise une seul couche de transparence.

La fonction de mélange

Afin que le code précédent fonctionne, on doit initialiser la fonction de mélange :

// Enable blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Ce qui signifie :


nouvelle couleur dans le buffer d'écran = 
    alpha actuel dans le buffer d'écran * couleur actuelle dans le buffer d'écran + 
    (1 - alpha actuel dans le tampon d'écran) * la couleur de sortie du shader

Exemple de l’image ci-dessus, avec le rouge au-dessus :

new color = 0.5*(0,1,0) + (1-0.5)*(1,0.5,0.5); // (the red was already blended with the white background)
new color = (1, 0.75, 0.25) = the same orange