Shader de vertex et de fragment | WebGPU | Vidéo

Nous examinerons en détail comment définir notre shader de vertex et de fragment dans notre projet Fun Triangle.

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

By Carmen Cincotti  

Vidéo

Cette vidéo a des sous-titres en français.

Nous continuons cette série en examinant comment configurer le shader de vertex et le shader de fragment de notre triangle de WebGPU.

WebGPU Triangle

Ne pouvez-vous pas attendre la fin de la série ?

Si vous souhaitez aller de l’avant sans attendre la prochaine vidéo de la série, je vous recommande de faire défiler jusqu’au code ci-dessous ou de consulter mon article : dessinons un triangle avec WebGPU.

Le code de la série vidéo

Au cours de cette série, nous étudierons ce code que vous pouvez retrouver dans cet article où on commence la série.

Avant de pouvoir rendre ce code, vous devez télécharger un navigateur capable d’exécuter du code WebGPU.

Qu’est-ce qu’un shader ?

Un shader est un programme que nous pouvons coder qui s’exécute sur un GPU.

Dans cet article, nous allons programmer deux types de shaders de GPU :

  • Shader de vertex - Un programme qui s’exécute sur le GPU pour chaque sommet. Il renvoie la position du sommet dans l’espace 3D. Il peut également transmettre des données au shader de fragment.
  • Shader de fragment - Un programme qui s’exécute sur le GPU pour chaque fragment (pixel) entre nos sommets. Il doit renvoyer la couleur du fragment (pixel) d’intérêt.

A high level representation of a graphics application

Les shaders de fragment et de vertex

Rappelons que dans un article précédent, nous avons défini et passé un tableau de sommets au GPU en les poussant vers un tampon GPU.

Une fois que le GPU est prêt à restituer une image sur notre écran, il lit et analyse chaque sommet en exécutant un vertex shader.

Le shader de vertex

Le shader de vertex est un programme qui s’exécute sur le GPU. Il est exécuté pour traiter chaque sommet dans une scène 3D.

Le but d’un vertex shader est de renvoyer une position dans l’espace 3D du vertex qu’il est en train de traiter.

Contrairement à la plupart des choses dans le pipeline de rendu, cette étape doit être configurée avec notre propre code (que nous verrons bientôt) !

Exemple de code

Voici un exemple du shader de vertex le plus simple qui peut être écrit :

@vertex fn vertex_main() -> vec4<f32> { return vec4(1.0, 0, 0, 1.0); }

En renvoyant vec4(1.0, 0, 0, 1.0), nous définissons la position de ce sommet particulier à cette position dans l’espace 3D. Cela sera envoyé dans le pipeline de rendu pour un traitement ultérieur.

Une fois que le shader de vertex a traité les sommets, les données de position et les autres données (nous les verrons bientôt) sont traitées par d’autres étapes “cachées” dans le pipeline de rendu.

Finalement, nous voudrons « colorer » notre image. C’est le travail du shader de fragment.

Le shader de fragment

Le shader de fragment est un autre programme qui s’exécute sur le GPU qui renvoie une valeur de couleur pour chaque fragment (pensez simplement pixel pour l’instant) qui sera rendu sur notre image.

Le but d’un fragment shader est de renvoyer une couleur pour le fragment (pixel) qu’il est en train de traiter.

Exemple de code

Voici un exemple de shader de fragment où chaque pixel est coloré en rouge :

@fragment fn fragment_main() -> @location(0) vec4<f32> { return vec4(1.0, 0, 0, 1.0); // Red (RGBA) }

Combien de fois chaque shader s’exécute-t-il sur le GPU par image ?

Prenons par exemple notre triangle que nous voulons rendre :

Triangle de WebGPU

Pour notre triangle, le shader de vertex s’exécutera trois fois par image. J’ai marqué les trois sommets en rose, pour être clair.

Le shader de fragment est exécuté pour chaque pixel entre les sommets de notre triangle WebGPU pour chaque image.

Comme vous pouvez le voir, le shader de fragment est généralement exécuté beaucoup plus de fois que le shader de fragment !

Les shaders du triangle

Voici le code que nous utiliserons pour rendre un triangle :

const shaderModule = device.createShaderModule({ code: ` struct VertexOut { @builtin(position) position : vec4<f32>, @location(0) color : vec4<f32>, }; @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; } @fragment fn fragment_main(fragData: VertexOut) -> @location(0) vec4<f32> { return fragData.color; } `, });

Comme nous devrions le savoir maintenant, nous devons utiliser le GPUDevice, device, afin de communiquer avec le GPU.

Vous pouvez voir qu’on utilise device.createShaderModule() pour enregistrer les shaders.

Le code du shader de vertex WebGPU

Maintenant, approfondissons le code du shader de vertex pour voir ce que fait chaque ligne :

struct VertexOut { @builtin(position) position : vec4<f32>, @location(0) color : vec4<f32>, }; @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; }

Quel est le mot-clé ‘struct’ dans WebGPU ?

Tout d’abord, on voit VertexOut. Nous pouvons utiliser ce struct afin d’organiser nos sorties du shader de vertex.

struct VertexOut { @builtin(position) position : vec4<f32>, @location(0) color : vec4<f32>, };

(Pour en savoir plus des structs de WebGPU, visitez ce lien.)

Quel est le mot-clé ‘@builtin’ dans WebGPU ?

Lorsqu’il est utilisé comme sortie du shader de vertex, le mot-clé @builtin peut être utilisé pour transmettre des informations cruciales des shaders aux étapes ultérieures du pipeline de rendu.

Les informations qui peuvent être transmises aux étapes ultérieures du pipeline de rendu à l’aide du mot-clé @builtin sont prédéfinies par WebGPU lui-même.

Examinons la ligne de code suivante qui existe dans notre application triangle :

@builtin(position) position : vec4<f32>

Entre les parenthèses de @builtin(), nous voyons position. Si vous jetez un œil à la documentation, vous verrez que le mot-clé position dans WebGPU correspond à la description suivante :

Position de sortie du sommet courant, en utilisant des coordonnées homogènes.

Pourquoi est-il nécessaire que nous définissions la position en utilisant cette valeur @builtin ?

Parce que la tâche principale du shader de vertex est de renvoyer la position du sommet donné qu’il traite. La position doit également être vec4<f32>.

Si vous venez de WebGL, il est égal à définir gl_Position.

En savoir plus sur le mot-clé WebGPU @builtin ici

Quel est le mot-clé @location dans WebGPU ?

Le mot-clé @location est responsable du stockage des données dans un emplacement spécifié de la mémoire.

Par exemple, on définit une variable color dans le struct VertexOut comme ici :

@location(0) color : vec4<f32>

Le chiffre, 0, est l’emplacement de l’Input/Output location (location IO).

Il est nécessaire de définir la location IO afin que nous sachions où trouver ces données lorsque nous y accédons dans un shader de fragment.

Voici un autre exemple de code de la documentation WebGPU.

En savoir plus du mot-clé @location ici

Quel est le mot-clé @vertex dans WebGPU ?

Le mot-clé @vertex indique que la fonction définie sous ce mot-clé est le point d’entrée d’un shader de vertex. Nous verrons ce que cela signifie dans la section suivante.

Le reste du code du shader de vertex WebGPU

Voyons maintenant ce que fait le shader de vertex :

@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; }

Tout d’abord, nous définissons la fonction fn vertex_main().

Si nous regardons la fonction d’un peu plus près, nous verrons deux paramètres @location(0) position : vec4<f32> et @location(1) color: vec4<f32>.

C’est important ! Rappelez-vous qu’il y a une semaine, nous avons défini vertexBufferDescriptors où nous avons défini le shaderLocation de la position sur 0 et la color sur 1. Nous pouvons maintenant accéder à ces données dans le shader de vertex à ces emplacements de shader !

De plus, nous définissons le type de sortie, output, comme struct VertexOut.

Comme prévu, dans le corps de la fonction, nous remplissons les champs de cette structure comme ceci :

var output : VertexOut; output.position = position; output.color = color; return output;

Chaque champ de notre structure output, à l’exception de position car il est du type @builtin, sera transmis au shader de fragment.

Par conséquent, à l’étape suivante, nous pourrons utiliser les données color pour colorer nos pixels dans le shader de fragment !

Le code de shader de fragment WebGPU

Enfin, on définit ce shader de fragment en utilisant le mot-clé @fragment :

@fragment fn fragment_main(fragData: VertexOut) -> @location(0) vec4<f32> { return fragData.color; }

Notez que nous renvoyons une couleur, fragData.color à la fin de la fonction. Un shader de fragment doit renvoyer une couleur.

Le code de la partie 5

Vous pouvez trouver le code de cette partie dans ce GitHub Gist.

La suite

Nous continuerons ce projet sur YouTube ! À la prochaine !

Des ressources (en français et anglais)


Comments for Shader de vertex et de fragment | WebGPU | Vidéo



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!