WebGL pas à pas

Séance 2 · Travaux pratiques

TP 2 · Couleurs et formes

Trois objets dans la même scène : un triangle multicolore, un rectangle décrit avec un buffer entrelacé puis des indices, et un disque dont on règle le nombre de côtés. Une teinte globale, choisie dans la page, s'applique à tout.

Durée 1 h 30Dossier tp2/depart/Cours Envoyer des données au GPU

Télécharger le code de départtp2-depart.zip · 5 Ko · à décompresser, puis ouvrir le dossier tp2/depart/ dans VS Code

Objectifs

  • Ajouter un attribut et un varying ; observer l'interpolation.
  • Décrire un buffer entrelacé avec stride et offset.
  • Piloter un uniform depuis l'interface.
  • Utiliser des indices et générer une géométrie par programme.
tp2/depart/ ├── index.html canvas + sélecteur de couleur + curseur ├── main.js votre code └── lib/gl-utils.js getContext, createProgram… (vos fonctions du TP 1)

Lancez la page avec Live Server : un triangle blanc s'affiche. Si une erreur survient, elle s'affiche maintenant en bas de la page en plus de la console, avec le code du shader numéroté.

Le triangle multicolore

On donne une couleur à chaque sommet, dans un second buffer.

  • 1.1 Vertex shader : déclarez in vec3 a_couleur; et out vec3 v_couleur;, et recopiez l'un dans l'autre dans main.
  • 1.2 Fragment shader : déclarez in vec3 v_couleur; et utilisez-le : couleur = vec4(v_couleur, 1.0);
  • 1.3 Récupérez locCouleur avec getAttribLocation.
  • 1.4 et 1.5 Créez le tableau des couleurs (rouge, vert, bleu) et son buffer, sur le modèle du buffer des positions. Attention : 3 composantes par sommet.
Vérification

Le triangle, en haut à gauche, présente un dégradé du rouge au vert et au bleu.

Question 1

Combien de fois le vertex shader est-il exécuté pour ce triangle ? Et le fragment shader, approximativement ? (Le triangle couvre environ 13 % du canvas de 600 × 600.) D'où viennent les couleurs intermédiaires ?

Le rectangle entrelacé

Cette fois, un seul buffer contient tout : pour chaque sommet, x, y, r, g, b.

  • 2.1 Écrivez rectSommets : 6 sommets (2 triangles), de (0.15, 0.15) à (0.9, 0.85), avec des couleurs de votre choix. Écrivez un sommet par ligne, c'est beaucoup plus lisible.
  • 2.2 Créez vaoRect et un buffer. Décrivez les deux attributs sur le même buffer : vertexAttribPointer(loc, taille, gl.FLOAT, false, stride, offset). Le stride et l'offset sont en octets ; utilisez la constante F (4 octets).
  • 2.3 Dans dessiner(), liez vaoRect et dessinez ses 6 sommets.
Vérification

Un rectangle dégradé en haut à droite. Si vous obtenez des formes aberrantes ou des couleurs qui « glissent », vérifiez le stride (taille d'un sommet complet) et l'offset de la couleur.

Question 2

Que valent stride et offset pour chacun des deux attributs ? Si on ajoutait des coordonnées de texture (u, v) après la couleur, que deviendraient-ils ?

La teinte globale (uniform)

  • 3.1 Fragment shader : déclarez uniform vec3 u_teinte; et multipliez : couleur = vec4(v_couleur * u_teinte, 1.0);
  • 3.2 Récupérez locTeinte avec gl.getUniformLocation.
  • 3.3 Ajoutez un écouteur "input" sur inputTeinte qui appelle dessiner.
  • 3.4 Dans dessiner(), après useProgram : gl.uniform3fv(locTeinte, couleurHex(inputTeinte.value));
Vérification

Choisir du rouge dans le sélecteur rend le triangle rouge sombre (seule la composante rouge subsiste) et le rectangle orangé. Le blanc restitue les couleurs d'origine.

Question 3

Pourquoi multiplier les couleurs plutôt que les additionner ? Que se passe-t-il si on appelle gl.uniform3fv avant gl.useProgram lors du tout premier dessin ?

Partager les sommets : les indices

Votre rectangle répète 2 sommets sur 6. Supprimons les doublons.

  • Réduisez rectSommets à 4 sommets uniques, dans l'ordre bas-gauche, bas-droite, haut-droite, haut-gauche.
  • Créez new Uint16Array([0, 1, 2, 0, 2, 3]) et un buffer lié à gl.ELEMENT_ARRAY_BUFFER, pendant que vaoRect est lié (le VAO s'en souvient).
  • Remplacez le dessin du rectangle par gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0).
Vérification

Le rectangle est identique à l'étape 2. Un triangle manque ? Vérifiez l'ordre des sommets et des indices.

Le disque

On approche un disque par un polygone à n côtés en éventail (TRIANGLE_FAN) : le centre, puis les points du bord. Le point i est à l'angle a = 2π · i / n, donc en (cx + r·cos a, cy + r·sin a).

  • 5.1 Complétez la fonction disque : une boucle de i = 0 à i = n inclus, qui ajoute pour chaque point ses coordonnées et la couleur teinte(i / n). Astuce : d.push(x, y, ...teinte(i / n)).
  • 5.2 Créez vaoDisque et bufDisque, avec les mêmes réglages d'attributs que le rectangle (sans données pour l'instant).
  • 5.3 Écrivez majDisque(n) : elle calcule disque(0, -0.45, 0.42, n), lie bufDisque, y copie les sommets avec bufferData, et retient le nombre de sommets (n + 2) dans une variable.
  • 5.4 Appelez majDisque au démarrage avec la valeur du curseur, puis à chaque changement du curseur (suivi de dessiner()).
  • 5.5 Dessinez le disque avec gl.drawArrays(gl.TRIANGLE_FAN, 0, nbSommets).
Vérification

Un hexagone en bas, blanc au centre, en arc-en-ciel sur le bord. Avec le curseur à 64, on ne distingue plus le polygone d'un cercle. À 3, c'est un triangle.

Question 4

Pourquoi faut-il n + 2 sommets pour n triangles ? Combien en faudrait-il avec gl.TRIANGLES ? Avec drawElements ?

Bonus

  • A. Étoile. Écrivez etoile(cx, cy, rExt, rInt, branches) : comme le disque, mais les points du bord alternent entre deux rayons. (2 × branches points sur le bord.)
  • B. Contour. Dessinez le contour du disque avec gl.LINE_LOOP en réutilisant le même buffer : à partir du sommet 1, n sommets.
  • C. Motif procédural. Dans le fragment shader, utilisez gl_FragCoord.xy pour assombrir une ligne de pixels sur deux (effet écran cathodique) : mod(gl_FragCoord.y, 2.0).