Séance 6 · Travaux pratiques
TP 6 · Scène 3D
Du cube qui tourne à une petite ville de caisses que l'on survole à la souris : profondeur, perspective, caméra et textures, avec les outils construits pendant tout le module.
Télécharger le code de départtp6-depart.zip · 14 Ko · à décompresser, puis ouvrir le dossier tp6/depart/ dans VS Code
Objectifs
- Écrire des matrices 4 × 4 et les tester.
- Constater puis corriger les problèmes de visibilité (profondeur, culling).
- Mettre en place la chaîne projection · vue · modèle et une caméra
lookAt. - Texturer des objets 3D.
Lisez geometrie.js : chaque face a sa couleur, et ses triangles sont dans le sens anti-horaire vus de l'extérieur. Au départ, un rectangle d'une seule couleur s'affiche : c'est une face du cube, vue de face, sans aucune transformation.
Un cube qui tourne
- 1.1 Dans
m4.js, écriveztranslationetscaling(16 nombres, par colonnes). Les rotations etlookAtsont fournies. Vérifiez avectests.html: 8 / 8. - 1.2 Dans
image(), faites tourner le cube :modele = m4.mul(m4.rotationY(temps), m4.rotationX(0.7 * temps), m4.scaling(0.8, 0.8, 0.8)).
Le cube tourne… mais c'est un désordre : des faces arrière apparaissent par-dessus des faces avant, l'image clignote.
Expliquez ce que l'on voit. Dans quel ordre les faces sont-elles dessinées ? Pourquoi le cube paraît-il aussi étiré horizontalement ?
Profondeur et culling
- Avant la boucle, activez
gl.enable(gl.CULL_FACE)seul. Le résultat est-il correct ? - Activez ensuite
gl.enable(gl.DEPTH_TEST)et ajoutezgl.DEPTH_BUFFER_BITaugl.clear.
Le cube est maintenant cohérent : on ne voit jamais plus de trois faces.
- Pour un cube seul, le culling suffit-il ? Pourquoi garder quand même le test de profondeur ?
- Retirez
gl.DEPTH_BUFFER_BITduclearen gardantDEPTH_TEST. Que se passe-t-il, et pourquoi ? - Dans
geometrie.js, inversez l'ordre des indices d'une face (o, o + 2, o + 1, …). Que devient cette face ?
Perspective et vue
- 3.1 et 3.2 Ajoutez les uniforms
u_vueetu_projectionet calculezgl_Position = u_projection * u_vue * u_modele * vec4(a_position, 1.0); - 3.3 Projection :
m4.perspective(fov, canvas.width / canvas.height, 0.1, 100), avec le fov du curseur converti en radians. - 3.4 Vue :
m4.translation(0, 0, -3). Envoyez les deux matrices avecgl.uniformMatrix4fv.
Le cube apparaît en perspective, au centre, et n'est plus déformé quelle que soit la taille de la fenêtre. Le curseur de champ de vision fait un effet de zoom.
- Pourquoi faut-il déplacer le cube vers les z négatifs pour le voir ?
- Mettez la vue à
translation(0, 0, -0.5). Pourquoi le cube est-il coupé ? Quel paramètre de la perspective est en cause ? - Diminuer le fov rapproche-t-il le cube ? Quelle différence avec le fait d'avancer la caméra ?
La caméra et la scène
On remplace la translation par une vraie caméra qui tourne autour de la scène. L'objet camera (angle, hauteur, distance) est déjà piloté par la souris.
- 4.1 Calculez la position de l'œil en coordonnées sphériques :
et utilisezconst oeil = [ camera.distance * Math.cos(camera.hauteur) * Math.sin(camera.angle), camera.distance * Math.sin(camera.hauteur), camera.distance * Math.cos(camera.hauteur) * Math.cos(camera.angle), ];m4.lookAt(oeil, [0, 1, 0], [0, 1, 0])comme matrice de vue. - 4.2 Dessinez le sol : le cube, mis à l'échelle en 24 × 0,5 × 24 et descendu de 0,25 (son dessus est alors en y = 0). Puis chaque élément de
objets, qui est une pile dehcaisses de 1,6 de côté : la caisse numéro k (en partant de 0) a pour échelle (1,6 ; 1,6 ; 1,6) et pour translation (x ; (k + 0,5) × 1,6 ; z). Remontez le cube coloré à y = 1,2 et agrandissez-le à 1,5.
Des piles de cubes colorés sur un plateau, autour d'une place vide ; on tourne autour en glissant la souris, on zoome à la molette. Le cube central tourne toujours sur lui-même.
Pourquoi l'ordre translation · scaling pour les caisses ? Que donnerait scaling · translation ? Quelle matrice change d'une caisse à l'autre, lesquelles restent identiques pendant toute une image ?
Les textures
- 5.1 à 5.3 Ajoutez
u_uvEchelle(vertex shader :v_uv = a_uv * u_uvEchelle),u_imageetu_texture(fragment shader, avecmix), et leurs emplacements. - 5.4 Écrivez
creerTexture(image)et chargez les deux images. Ici, on utiliseUNPACK_FLIP_Y_WEBGL. - Écrivez une fonction
dessinerCube(modele, texture, uvEchelle)qui règle les uniforms (avecu_textureà 0 sitexturevautnull) et dessine. Les caisses utilisenttexCaisse; le soltexDallerépétée 24 fois ; le cube central garde ses couleurs.
Des piles de caisses sur un sol dallé, et le cube multicolore au centre.
Au TP 5, il ne fallait pas retourner l'image ; ici, si. Qu'est-ce qui a changé ? (Regardez le sens de l'axe y et les UV dans geometrie.js.)
Bonus
- A. Brouillard. Transmettez au fragment shader la distance à la caméra (
-(u_vue * u_modele * position).z) et mélangez la couleur avec celle du fond :mix(c, fond, smoothstep(12.0, 40.0, distance)). - B. Éclairage simple.
geo.normalesdonne la normale de chaque sommet. Envoyez-la (attribut), tournez-la avec la matrice modèle (mat3(u_modele) * a_normale), et multipliez la couleur parmax(dot(normalize(n), normalize(direction_lumiere)), 0.2). - C. Caméra libre. Remplacez l'orbite par un déplacement au clavier (ZQSD) dans la direction du regard, et l'orientation à la souris.