Traitement de fragments |WebGPU

Nous explorons ce qu'est un fragment, comment il est traité et comment une image est sortie sur notre écran.

Keywords: WebGPU, le pipeline de rendu graphique, le rendu en temps réel

By Carmen Cincotti  

Enfin nous arrivons à la fin de notre histoire du pipeline de rendu (du moins, pour l’instant - je voudrais me concentrer sur la programmation de ma simulation de tissu). Nous avons passé un bon moment ensemble lors de cette aventure !

Cela dit, la question primordiale suivante est celle à laquelle je répondrai lors de cet article sur le pipeline de rendu :

Qu’est-ce que c’est un fragment ?

Pour y répondre, c’est l’heure de parler des fragments et de la façon dont ils sont rendus en pixels à l’écran.

Qu’est-ce que c’est un fragment ?

Un fragment est décrit par Wikipédia comme suit :

Un fragment est la donnée nécessaire pour générer la valeur d’un seul pixel d’une primitive de dessin dans le framebuffer.

Ces données peuvent inclure, mais sans s’y limiter :

  • Position rastérisée
  • Profondeur (valeur z)
  • Attributs interpolés (couleur, coordonnées de texture, etc.)
  • Alpha

Si un fragment est à l’intérieur du triangle, il sera traité pour contribuer à la couleur finale d’un pixel. Une fois que tous les fragments associés à un pixel particulier ont été traités (y compris ceux associés à d’autres primitives, ainsi que tous les échantillons par pixel), la valeur de couleur moyenne est trouvée puis attribuée au pixel de l’écran.

Ce traitement se passe dans le shader de fragment associé au triangle. On verra comment les fragments shaders fonctionnent pour finalement produire une couleur.

💡 Les fragments ne sont pas les pixels

En fait, notre discussion de la semaine dernière sur la rastérisation peut nous aider à comprendre ce point.

Rasterization of a triangle

Une bonne règle à suivre est que :

  • Un fragment - l’unité atomique de vos tampons cibles de rendu
  • Un pixel - l’unité atomique de votre écran

Le shader de fragment

Le shader de fragment est un programme que nous pouvons implémenter nous-mêmes dont la seule responsabilité est de recevoir des informations sur un fragment donné en entrée, puis de les traiter pour produire une couleur basée sur ces informations.

Description of shader de fragment

Quelles sont les entrées disponibles ?

La réponse est que c’est à nous de décider ! Cela dépend de notre objectif. Cependant, les genres d’entrée sont variés :

  • Les attributs interpolés (varyings) - ces données sont définies sur chaque sommet et interpolées sur le triangle formé par les sommets. Ils sont inscriptibles dans le shader de vertex et en lecture seule dans le shader de fragment.
  • Uniforms - les attributs dont la valeur est égale sur tous les fragments. Lecture seulement.

Concentrons-nous sur les varyings en prenant un exemple du code. Voici le code complet du shader (vertex et fragment) que l’on a écrit lors du premier article sur le thème de l’infographie où nous avons rendu un triangle avec WebGPU :

struct VertexOut { @builtin(position) position : vec4<f32>; @location(0) color : vec4<f32>; }; @stage(vertex) fn vertex_main(@location(0) position: vec4<f32>, @location(1) color: vec4<f32>) -> VertexOut { var output : VertexOut; output.position = position; output.color = color; return output; } @stage(fragment) fn fragment_main(fragData: VertexOut) -> @location(0) vec4<f32> { return fragData.color; }

Une question 🤔 : Est-ce que vous pouvez repérer les varyings ?

La réponse 🤓 : C’est color et position. Il y a aussi d’autres attributs que l’on ne peut pas voir dans le code qui sont également interpolés à partir des sommets tels que la valeur z utilisée dans le tampon z.

Un exemple d’interpolation

Examinons de plus près l’attribut color. Nous avons défini color sur chaque sommet comme étant rouge, bleu ou vert :

A triangle where colors are defined on each vertex

La couleur est déterminée par l’interpolation de la couleur du sommet sur le triangle comme suit :

Rendered triangle with interpolated color values

💡Quelle méthode est utilisée par le GPU pour interpoler un attribut ?

La réponse la plus simple est en utilisant les coordonnées barycentriques. Sans voir les mathématiques impliquées - il faut juste comprendre que la position du point entre trois sommets (qui forment le triangle) est calculée puis utilisée pour interpoler les valeurs des données de sommet.

Barycentric coordinates example

Les limitations du shader de fragment

Les limitations sont attribuées (ha !) au fait que le GPU aime paralléliser les exécutions des shaders. Moi, je pense que c’est comme faire des pâtes 🍝. Un peu bizarre comme exemple, mais 🤷

Pasta making

Chaque bande de pâtes est séparée les unes des autres pendant son parcours dans la machine à manivelle (le GPU dans cet exemple).

Mis à part les pâtes, on arrive à la première limitation :

  • Visibilité du voisin - lorsque le GPU est en cours d’exécution, il ne peut pas lire les résultats de couleur des pixels voisins ni leur envoyer les siens. Exception à cette règle - lors du calcul des informations dérivatives.

Une limitation de plus, c’est :

  • Les sorties - Il ne peut écrire qu’à une cible du rendu (render target)

Nous pouvons tirer parti des cibles de rendu multiples (MRTs - multiple render targets) ou prélever un échantillon de l’image de sortie d’une passe de rendu précédente. C’est exactement comme prélever un échantillon d’une texture. Voici une image où les MRTs créent une seule image finale en se combinant :

Multiple rendering targets

Quelques questions restent encore sans réponse 🤔 :

  • Comment plusieurs fragments se combinent-ils en une seule couleur du pixel finale ?
  • Cette discussion à ce point était sur l’affichage d’une seule primitive. Comment est-ce que plusieurs triangles superposés les uns sur les autres se rendent ?

🤓 Je pense que pour y répondre, nous devons d’abord aborder le sujet de la fusion (merging).

La fusion des fragments (merging)

On est enfin sur le point de déterminer ce qui sera écrit à l’écran. Tout notre travail acharné jusqu’à présent portera ses fruits dans cette étape 🍍 !

Avant de continuer, souvenez-vous d’un terme dont on a déjà parlé - le framebuffer.

Un framebuffer est une structure de donnée qui organise les ressources mémoires nécessaires pour rendre une image 🖼️.

Pour chaque image, le framebuffer peut utiliser un tampon de couleur pour accumuler les données de couleur, et un tampon de profondeur pour accumuler la valeur de profondeur (la valeur z) pour chaque pixel. Ces tampons sont attachés au framebuffer. Les deux ont la même taille et la même forme.

Voici une image qui illustre le tampon de couleur (en haut) et le tampon de profondeur (en bas) :

Color and z-buffer images

Nous pouvons imaginer qu’un framebuffer stocke toutes les données sur notre écran à présent. L’objectif est de fusionner toutes les données (couleur et valeur z d’un pixel) de chaque primitive de notre scène avec les données du framebuffer pour les afficher à l’écran. Ceci est réalisé grâce à des tests de profondeur (depth testing) et au mélange de couleurs (color blending).

Tests de profondeur (Depth testing)

Les tests de profondeur sont utilisés pour résoudre la visibilité de toutes les primitives sur l’image finale. Cela dit, s’il y a une primitive cachée derrière une autre, nous voudrions éviter de la rendre sur l’image finale.

Le GPU gère cela pour nous. Il compare les valeurs de profondeur calculées (la valeur z) de tous les pixels associés à différentes primitives à un emplacement donné sur l’écran. Voici un exemple du processus :

Z-buffer example

In this case - the z-value and the color of the closest pixel are stored in the framebuffer to be rendered. This type of testing is called depth testing.

Sinon, ces valeurs du pixel ne sont donc pas écrasées.

💡Early-z

Cela semble être une grosse perte de temps de faire tous ces calculs pour jeter les informations de pixel d’une primitive à la poubelle à la fin. Heureusement, le GPU est intelligent.

Avant d’exécuter le shader de fragment, le GPU peut tester la visibilité du fragment pour voir s’il a du sens de continuer à le traiter dans un processus qui s’appelle early-z.

Le mélange de couleurs (Color blending)

Le mélange de couleurs applique les opérations qui écrasent les pixels d’une primitive correspondant au même pixel dans le framebuffer.

Les fragments peuvent remplacer les valeurs des autres, s’additionner ou se mélanger selon les paramètres de transparence.

L’image est enfin rendue

Enfin, notre histoire s’achève. L’écran affiche le contenu du tampon de couleur avec les valeurs qui ont survécu à ce processus.

Des ressources (en français et anglais)


Comments for Traitement de fragments |WebGPU



Written by Carmen Cincotti, computer graphics enthusiast, language learner, and improv actor currently living in San Francisco, CA.  Follow @CarmenCincotti

Contribute

Interested in contributing to Carmen's Graphics Blog? Click here for details!