INFO804 Introduction à l'informatique graphique
|
author: Jacques-Olivier Lachaud
L'objectif de ce TP est de vous montrer comment utiliser la puissance graphique de vos ordinateurs dans de simples pages web. Ainsi, vous pourrez intégrer dans vos pages web de la 3D, du rendu (presque) réaliste temps réel, des jeux, des effets spéciaux 2D ou 3D, etc. Les technologies utilisées ici sont le javascript, la bibliothèque WebGL (une surcouche d'OpenGL pour le javascript), et la bibliothèque three.js, qui simplifie considérablement l'utilisation de WebGL. Nous toucherons aussi un petit peu aux shaders, qui sont des programmes compilés par et exécutés sur la carte graphique.
Il suffit de le télécharger de son site officiel (https://threejs.org). Il est mis aussi dans l'archive du TP. Ensuite, pour pouvoir exécuter facilement vos codes html/js/webgl, il faut qu'il y ait un serveur web (http/https) qui tourne sur votre machine. C'est nécessaire, car pour des raisons de sécurité, votre navigateur interdirait le chargement de fichiers extérieurs. Si vous n'en avez pas déjà un (il suffit de voir si http://localhost:8080 donne qqchose), vous pouvez installer Servez (https://github.com/greggman/servez), qui s'installe et se lance facilement. Ensuite, normalement le premier exemple de code three.js devrait s'exécuter sans problème.
View -> Developer > Developer tools
ou Afficher -> Options pour les développeurs -> Console JavaScript
. Les erreurs sont affichées, la console, etc.three.js
.Comme vous fabriquez une page web, vous avez besoin d'un fichier HTML, éventuellement d'un fichier de style CSS. De plus, il faut charger les bibliothèques javascript utiles dans la page. Enfin, il faut prévoir un champ canvas
qui contiendra la fenêtre WebGL et lui donner un nom (ici webglcanvas
).
Maintenant voilà le code javascript pour faire un rendu minimal 3D. Notez que l'utilisation de three.js
facilite grandement la création de scènes 3D et leur rendu. Le même code WebGL "bas niveau" prendrait pas loin de 150 lignes. Notamment il faudrait gérer à la main la communication des données entre le javascript et les shaders. Ici, three.js
se charge de tout ou presque.
En gros, un code WebGL suit toujours les étapes suivantes:
D'abord l'initialisation:
renderer
qui calculera le rendu,Ensuite, les autres fonctions sont utilisées régulièrement (plusieurs fois pour seconde) pour effectuer le rendu temps réel, l'animation et l'interaction avec l'utilisateur.
run
doit être appelée une première fois puis demande au navigateur à être rappelée dès que nécessaire via requestAnimationFrame
. Ensuite, elle appelle juste render
(pour faire le rendu) et animate
pour mettre à jour les positions, la géométrie, les couleurs, etc. A priori, nous n'avez pas besoin d'y toucher.render
effectue le rendu souhaité. On voit qu'il suffit de la modifier si on veut faire plusieurs zones de rendu avec plusieurs camera.animate
sert à mettre à jour la géométrie de la scène en fonction du temps. On récupère la variation de temps depuis le dernier affichage puis on met à jour les objets en déplacement.Dans l'exemple précédent, la scène se réduit à un cube, placé en (0,0,-8)
, avec un material
qui est juste une texture. La caméra est elle placée en ses coordonnées par défaut (0,0,0)
et regarde vers les z négatifs. L'animation fait juste tourner l'axe y du cube (au sens des coordonnées d'Euler).
WebGLRenderer
ainsiLes objets géométriques 3D sont représentés à l'aide de Mesh
, et combinent deux propriétés: leur géométrie (simple comme cube ou sphère, complexe comme des surfaces triangulées ou des extrusions de splines), et leur matériau (couleur, propriétés diffuses ou spéculaires, textures (diffuses ou spéculaires), carte de normales et bumpmap, etc).
Three.js vous fournit plein de classes toutes faites pour la géométrie:
Exercice 2.1. Remplacez le cube (BoxGeometry
) par une sphère de rayon 1. Remplacez la texture webgl
par la texture earth_atmos_2048.jpg
.
Pour faire plusieurs objets, il suffit de les ajouter à la scène. On verra ci-dessous comment les grouper, ce qui facilitera grandement le placement et l'animation des objets.
three.js
, contrairement à ce que son nom indique, propose aussi des géométries "2D", et peut faire du rendu 2D.Pour donner un aspect plus 3D à nos scènes, il manque deux ingrédients fondamentaux: des lumières et des matériaux sensibles à la position des lumières et de la caméra. Là encore, nous disposons de plusieurs classes pour faire de l'illumination: AmbientLight, DirectionalLight, PointLight, SpotLight sont les plus courantes.
Il suffit aussi d'instancier et d'ajouter une lumière à une scène pour qu'elle soit prise en compte.
Ensuite, on peut créer un matériau qui est sensible aux lumières. Plusieurs matériaux classiques ou moins classiques sont fournis par three.js
: MeshBasicMaterial , MeshDepthMaterial , MeshLambertMaterial , MeshNormalMaterial , MeshPhongMaterial , MeshPhysicalMaterial , MeshStandardMaterial , MeshToonMaterial , etc. Ce qui est pratique, c'est que three.js
genère tout le code du vertex shader et du fragment shader à votre place.
Le côté remarquable est que l'on peut choisir comme on veut les matériaux pour des objets différents. Toute la difficulté d'associer le bon shader aux bons objets est géré en interne par three.js
.
Exercice 2.2. Remplacez le matériau de votre sphère par un MeshPhongMaterial . Donnez-lui une spécularité blanche bien visible.
La scène est organisée sous forme d'un arbre. Les objets géométriques ne sont pas seulement listés dans une scène géométrique, mais sont plutôt organisés sous forme d'une hiérarchie. L'intérêt (en dehors des aspects performances lorsqu'on ajoute des niveaux de détails) est qu'un transformation géométrique appliquée à un noeud est appliquée à tous les descendants du noeud. Ainsi, si on veut créer une voiture, on créera un noeud voiture, puis des descendants comme le chassis ou la carosserie, et les roues seront des descendants du chassis. On pourra faire tourner les roues par rapport au chassis, et un mouvement global de la voiture déplacera tout ce beau monde.
Tout objet géométrique (descendant de Object3D
) peut avoir des fils, mais en général on préfère utiliser un Group
pour rassembler des objets géométriques.
Exercice 2.3. Créer une scène de type "système solaire" avec au centre le soleil (vous placez une lumière PointLight
et une sphère), autour une planète terre (qui tourne autour du soleil), et autour de la terre une lune qui tourne autour. Vous pouvez aussi faire tourner la terre sur elle-même, sachant qu'elle a un axe tourné par rapport au soleil !
position.x
, position.y
, rotation.x
, etc, dans chaque objet géométrique ou groupe. Vous aurez besoin de rajouter des variables globales et de créer des groupes pour rassembler les planètes et aussi les faire tourner autour du soleil. Le déplacement des angles dans animate
devraient ressembler à ça:earthGroup
est positionné sur le soleil et va tourner sur lui-même en 1 an. Un earthSystem
, fils de earthGroup
est translaté relativement à son père (par exemple de 5 selon x). La terre earth
, fille de earthSystem
tourne sur elle-même en 1 jour...De la même façon qu'on peut déplacer les objets, on peut déplacer la caméra pour voir la scène sous d'autres angles et positions. Il suffit par exemple de modifier les champs camera.position
ou camera.lookAt
pour déplacer la caméra. Ajoutez la ligne ci-dessous dans animate
:
De façon remarquable, la caméra pointe toujours vers la terre. Notez que l'on doit demander les coordonnées de la terre en coordonnées absolues dans le monde, car ses coordonnées relatives sont fixes en fait (ici, (5,0,0)
).
Exercice 2.4. Mettez la camera sur une orbite elliptique en définissant une variable globale cameraAngle
que vous mettrez à jour dans animate()
, puis utilisez l'équation paramétrique de l'ellipse pour placer directement la camera:
On peut bien sûr aussi piloter les déplacements de la caméra via une souris ou le clavier. L'idée est de définir des callbacks pour des événements. Cela ressemble à ça:
Faire une caméra manipulable prend beaucoup de temps. Heureusement, three.js fournit quelques classes toutes faites pour contrôler la caméra. Nous utiliserons le script OrbitControls.js
(tiré des exemples). Une fois chargé, il suffit d'instancier un OrbitControls
, de le paramétrer, et d'appeler régulièrement sa méthode update
.
et dans init()
Et dans animate
:
Exercice 2.5. Ajouter un contrôleur "orbite" qui pilote maintenant la camera (vous devez enlever certaines lignes faites à la question précédent). Modifiez la cible du contrôleur (target
) pour que ce soit la terre.
Pour mettre un fond à une scène, on fabrique une sphère ou un cube "lointain" et on plaque dessus – plus précisément dessous – une texture. Ces fonds sont souvent utilisés aussi en tant qu' environment map
pour faire des effets de reflets jolis (e.g. https://threejs.org/examples/?q=en#webgl_materials_envmaps). Three.js donne même un champ background
à une scène pour représenter un fond. Voilà comment rajouter la voie lactée en fond.
Exercice 2.6. Ajouter le fond "MilkyWay" ou "skybox". Cela donne:
Par défaut, le rendu graphique ne tient pas compte d'ombres possibles entre objets suivant la position de la lumière. Comme on a pu voir dans le TP ray-tracing, c'était un peu coûteux à calculer.
Le principe pour calculer des ombres (assez) efficacement est de faire des "cartes d'ombres" (shadowmap). En fait, pour chaque source de lumière qui peut faire de l'ombre, on fait un rendu (très simplifié, juste la profondeur est conservée) des objets qui peuvent faire de l'ombre du point de vue de la lumière. La profondeur mémorisée en chaque point permet ensuite, lorsqu'on fait le rendu du point de vue de la caméra, de voir si le point considéré est dans l'ombre, en regardant la profondeur en ce point.
En three.js
, c'est très simple de rajouter de l'ombre. Il faut d'abord indiquer au renderer qu'il y aura des ombres à calculer:
Ensuite, il faut préciser quelles sont les lumières qui peuvent faire de l'ombre. Nous, on va faire en sorte que la lumière du soleil puisse faire de l'ombre.
Enfin, on précise quels objets peuvent faire de l'ombre, et quels objets peuvent recevoir de l'ombre.
Exercice 2.7. Ajouter l'ombrage de la terre et de la lune. Vous devriez maintenant voir des éclipses !
On va écrire un nouveau material en utilisant la classe ShaderMaterial . Il faut d'abord écrire 2 scripts, l'un pour le vertex shader, l'autre pour le fragment shader.
Le vertex shader s'occupe de calculer les coordonnées de chaque sommet de l'objet dans la vue de la caméra, ainsi que le vecteur normal en ce sommet. On note les 2 variables moment
et scale
(dites uniform) qui sont partagés par les shaders et envoyés par le ShaderMaterial (voir ci-dessous). Par rapport à un shader classique, celui-ci s'amuse à gonfler et dégonfler périodiquement l'objet.
Le fragment shader s'occupe de calculer un couleur pour chaque pixel de l'objet à afficher. On récupère le temps (variable uniform moment
) et on mélange coordonnées globales et vecteur normal pour fabriquer une couleur.
Il faut maintenant créer le ShaderMaterial et l'associer à une géométrie.
Ici on a rajouté un halo au soleil, mais on pourrait remplacer le soleil ou créer une autre planète sur le même principe.
three.js
sont écrits par morceaux sous formes de chunks (dans three.js/src/renderers/shaders/ShaderChunk) puis assemblés par des #include dans les matériaux (dans three.js/src/renderers/shaders/ShaderLib).Il y a bien sûr plein d'autres fonctionnalités dans three.js
et WebGL. Nous n'avons fait qu'effleurer le sujet.
Je vous invite à regarder les multiples exemples de three.js
, qui donnent plein d'idées.
Vous pouvez compléter le système solaire, faire des planètes métalliques, des planètes de lave, des astéroïdes multiples sous forme de particules, utiliser d'autres materials, ou faire vos shaders, en vous inspirant de celui donné plus haut ou en regardant quelques exemples que j'ai mis sur mon site https://jacquesolivierlachaud.github.io/lectures/info804/ .
Vous pouvez aussi faire quelque chose de complètement différent, dans la mesure où c'est un travail personnel.
Bref, étonnez-moi !
Vous me remettrez votre TP via TPLab seul ou en binôme avant le vendredi 23 février 2024 minuit, avec un README expliquant ce que vous avez fait d'original en plus des questions posées. Si vous préférez, vous pouvez aussi uploader votre TP sur un serveur web et me donner le lien.