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.
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.
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 :
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 !