Introduction au jargon de la 3D et à WebGL

Le monde de la 3D suscite souvent l’admiration et la curiosité des gens. Pourtant, lorsque l’on souhaite se lancer dans l’aventure, on a l’impression que ce monde nous reste fermé et obscur. À vrai dire, les spécialistes de la 3D forment une sorte de caste. Et pour rentrer dans cette caste, il faut souvent avoir été initié par l’un d’entre eux. C’est vraiment la désagréable sensation et frustration que j’ai eu pendant des années. Un jour, j’ai eu la chance d’avoir été initié et j’ai alors décidé de partager ce savoir ancestral de manière plus large. Laissez-moi donc vous aider à décoder ce monde merveilleux et vous donner les clés pour y entrer.

Les bases de la 3D

La première difficulté que vous risquez de rencontrer est le jargon. Pour le décrypter, regardons ensemble les différentes bases à connaître avant de se lancer.

Commençons par comprendre comment on modélise un objet 3D. Dans le jargon des spécialistes, on appelle cela un mesh. Un mesh, c’est tout simplement un objet 3D.

Ce mesh est constitué à partir d’un nuage de points. Le point s’appelle un vertex. Quand il y en a plusieurs, on dit vertices. Un vertex est donc un point 3D.

Prenons l’exemple du fameux cube pour illustrer cela. Un cube est constitué à partir de huit vertices. Les coordonnées de ces points sont donc les suivants (le répère x, y, z étant positionné au centre du cube) :

Cube

Tout cela est bien beau, mais comment maintenant retranscrire cette réalité 3D vivant dans le ventre de votre ordinateur vers la 2D désespérément plate de votre écran ?

Pour le comprendre, il faut simplement vous imaginer comme étant un réalisateur du cinéma. Vous avez une caméra, un environnement où filmer (le monde 3D), des objets (les acteurs, les meubles) et vous allez placer ces objets dans votre monde.

Voici le processus que nous opérons alors :

  1. On commence par se concentrer sur le mesh centré sur lui-même (comme le cube ci-dessus) en le décrivant à l’aide d’autant de points que nécessaire et on répète ces opérations pour tous les objets qui vont constituer votre univers.
  2. On va ensuite positionner ces objets dans l’univers 3D. Les déplacements, rotations, mise à l’échelle sont réalisés grâce à des calculs matriciels. Ne partez pas ! Pas besoin d’être un cador des mathématiques. Ces opérations sont réalisées par des boîtes noires qui s’occupent de tout. En gros, vous lui dites : « déplace l’objet toto en (x : 3, y : 2, z : 5), agrandis le x2 et effectue une rotation sur l’axe des Y de 30° (comme le dirait Perceval, « les degrés, c’est un peuple »).  Des gens plus forts que vous en maths se sont déjà occupés de tout.
  3. Ensuite une caméra va s’occuper de filmer tout ce beau bazar que vous avez créé. Vous allez donc la positionner dans ce monde 3D et viser un objet particulier. La caméra a des propriétés identiques à une « vraie » caméra comme l’ouverture par exemple.
  4. Pour finir on effectue ce que nous appelons une projection de ce que voit la caméra en 3D vers un rendu en 2D pour l’écran.

Toute la magie est réalisée grâce aux matrices. C’est la base de la 3D. Donc si vous voulez un jour écrire votre propre moteur 3D de zéro, il faudra se plonger dans les bouquins sur les matrices. Mais heureusement, nous allons voir que cela n’est pas nécessaire. Malgré tout, il faut vraiment bien comprendre ces quatre séquences précédent le rendu pour être réellement à l’aise en 3D.

À ce stade-là, si nous n’affichons que les points du cube, cela nous donne le résultat suivant :

Démo d'un cube en WebGL

Mouais, pas très convaincant, n’est-ce pas ? Pour aller plus loin, il va falloir dessiner des lignes entre ces points. Pour être plus précis, on va même dessiner des triangles.

Le triangle est la forme 2D la plus simple qui existe en géométrie. Avant le triangle, c’est la ligne, mais ce n’est qu’en une dimension. Et en 2D, juste après le triangle, c’est le carré qui nécessite 4 points. Le carré nécessite donc une information supplémentaire. En résumé, pour pouvoir commencer à envisager une transformation du monde 3D vers le monde 2D de votre écran, il faut commencer par raisonner par groupe de 3 vertices qui vont nous permettre d’en dessiner des triangles. Dans le jargon, vous avez déjà sûrement entendu parler du nombre de polygones qu’une carte graphique est capable de « cracher ». Et bien, ce sont ces fameux triangles.

Il y a ensuite plusieurs façons de dessiner les triangles que l’on appelle également une face. Une face est donc constituée d’un groupe de 3 vertices qui vont nous permettre de savoir comment dessiner nos différents triangles.

Reprenons le cube :

Cube

Un cube a six côtés. Ces côtés vont être dessinés par deux faces chacun. Un cube a donc besoin de douze faces pour être dessiné. Les faces sont représentées par un groupe de trois indices pointant vers trois coordonnées 3D.

En résumé, on pourrait décrire un cube 3D en JavaScript de la manière suivante :

var mesh = new Engine.Mesh("Cube", 8, 12);
meshes.push(mesh);
mesh.Vertices[0] = new Vector3(-1, 1, 1);
mesh.Vertices[1] = new Vector3(1, 1, 1);
mesh.Vertices[2] = new Vector3(-1, -1, 1);
mesh.Vertices[3] = new Vector3(1, -1, 1);
mesh.Vertices[4] = new Vector3(-1, 1, -1);
mesh.Vertices[5] = new Vector3(1, 1, -1);
mesh.Vertices[6] = new Vector3(1, -1, -1);
mesh.Vertices[7] = new Vector3(-1, -1, -1);
mesh.Faces[0] = { A:0, B:1, C:2 };
mesh.Faces[1] = { A:1, B:2, C:3 };
mesh.Faces[2] = { A:1, B:3, C:6 };
mesh.Faces[3] = { A:1, B:5, C:6 };
mesh.Faces[4] = { A:0, B:1, C:4 };
mesh.Faces[5] = { A:1, B:4, C:5 };
mesh.Faces[6] = { A:2, B:3, C:7 };
mesh.Faces[7] = { A:3, B:6, C:7 };
mesh.Faces[8] = { A:0, B:2, C:7 };
mesh.Faces[9] = { A:0, B:4, C:7 };
mesh.Faces[10] = { A:4, B:5, C:6 };
mesh.Faces[11] = { A:4, B:6, C:7 };

On commence par décrire nos huit vertices avec trois coordonnées 3D. Ensuite les douze faces sont créées à partir des indices du tableau Vertices.

Désormais, nous obtenons ce résultat :

Démo d'un cube en WebGL

La classe, non ?

Alors, avant d’aller plus loin, vous devez sûrement vous dire : « vu comme c’est déjà compliqué de décrire les faces d’un cube, je n’ose pas imaginer pour créer ma Ferrari virtuelle ! ». Et vous avez raison. Sauf qu’heureusement, c’est le boulot d’un designer 3D de créer le modèle dans son outil préféré (comme 3DS Studio Max ou Blender). Ensuite, il va exporter son modèle dans un format compatible avec le moteur 3D que vous concevez ou utilisez.

Par exemple, en partant de Blender (qui est gratuit et open source) :

Blender

Vous pouvez facilement écrire un plug-in d’export en Python qui va parcourir tous les vertices créés par l’outil de design ainsi que toutes les faces décrivant l’objet et en produire par exemple un format JSON. Ce morceau de JSON ressemblera à ce que nous avons vu avec le cube en beaucoup plus conséquent mais le principe reste strictement le même.

On obtient alors cela :

Démo de Suzanne en WebGL

Pour finir, pour avoir un résultat encore plus sympathique, il ne reste plus alors qu’à :

  • Remplir les triangles. Cette étape s’appelle la « rasterization » (en anglais dans le jargon technique).
  • Calculer un éclairage pour définir la couleur de ces triangles. Pour cela, on calcule la normale à la face. Plus l’angle entre cette normale et notre source lumineuse sera grand, moins la face sera éclairée. Il y a différents algorithmes d’éclairage connus depuis des années : « flat » où l’on continue de bien voir les triangles, « gouraud » où l’on ne voit presque plus les triangles avec un dégradé de couleur effectué entre chaque extrémité du triangle et « phong » encore plus gourmand en ressources mais plus précis.
  • Pour finir, on finit par appliquer une texture sur l’objet pour augmenter davantage le réalisme.

Le plaquage d’une texture ressemble à cela :

Texture d'un cube

Pour vous rendre compte des différentes étapes en « live », vous pouvez lancer cette démo :

Singe en 3D

Cette démo fonctionne dans n’importe quel navigateur « compatible » HTML5.

Cette première partie est une synthèse d’une série de tutoriels que j’ai écrit en anglais pour apprendre à créer un moteur 3D dit « software » en HTML5 Canvas 2D. Vous pouvez y faire un tour si la version détaillée vous intéresse : Tutorial series : learning how to write a 3D soft engine from scratch in C#, TypeScript or JavaScript.

La 3D accélérée avec WebGL

Nous avons vu un exemple dit « software ». Cela veut qu’uniquement le CPU de votre machine fût utilisé pour le calcul de la 3D. Or, vous voyez déjà que notre petite tête de singe (connue sous le nom de « Suzanne » dans le milieu) met déjà à la peine votre machine.

Depuis bien longtemps, une autre puce nommée GPU est là pour seconder le processeur principal pour le rendu de la 3D. Les puces 3D sont conçues pour être « bêtes et méchantes ». Ce sont des brutes de puissance prévues exclusivement pour dessiner énormément de triangles par secondes. Sachant que vous le savez surement déjà, le graal en 3D est de réussir à afficher un monde 3D tournant à 60 images par seconde (FPS en anglais) le tout de manière stable. Tout simplement car nos écrans sont en 60 Hz.

Pour discuter avec le GPU, il faut utiliser une technologie particulière. Sous Windows, pour les jeux, DirectX est la plus connue et de manière plus cross-plateformes, OpenGL est également très présent. Cependant, que ce soit pour DirectX ou OpenGL, la plupart du temps il faut développer en C++.

WebGL est un sous-ensemble d’OpenGL exposant des API vers le monde JavaScript. WebGL va s’occuper de discuter avec le GPU et de projeter le monde 3D dans le canvas HTML5.

WebGL est également une API d’assez bas niveau. Elle vous permet essentiellement d’aller influencer la manière de dessiner chaque triangle à l’écran. Pour cela, Il y a 2 notions importantes :

  • Le « pixel shader ». C’est un petit morceau de programme qui va dire au GPU comment dessiner chacun des pixels du triangle en fonction de votre propre logique. C’est là-dedans que l’on créé par exemple une équation mathématique simulant l’eau, ses reflets et les vagues.
  • Le « vertex shader ». Comme son nom l’indique, il va plutôt travailler sur les extrémités/points du triangle et va plutôt avoir une incidence sur la géométrie.

Alors pourquoi ce terme « shader » ? J’ai un peu envie de vous dire « parce que ». Les mecs en 3D aiment créer des termes ésotériques. En fait, un shader est simplement un programme qui va être exécuté par le GPU. En WebGL, on utilise le langage GLSL (et HLSL en DirectX). Cela ressemble à du pseudo‑C qui va être compilé par le navigateur en assembleur pour être exécuté par le GPU de la carte graphique. Voici un exemple de « vertex shader » :

<script id="shader-vs" type="x-shader/x-vertex">
	attribute vec3 position;
	attribute vec4 color;
	uniform mat4 uMVMatrix;
	uniform mat4 uPMatrix;
	varying vec4 vColor;
	void main(void) {
		gl_Position = uPMatrix * uMVMatrix * vec4(position, 1.0);
		vColor = color;
	}
</script>

Et là, vous commencez à avoir logiquement peur. Détendez-vous, nous sommes venus à la rescousse.

Les moteurs 3D WebGL

Pour éviter d’avoir à vous embêter avec cela au quotidien, des moteurs 3D WebGL ont été écrits en JavaScript pour faire une abstraction de toutes les notions dont je vous parle depuis le début. Le plus connu d’entre eux est sans aucun doute ThreeJS.

De notre côté en France, David Catuhe, Pierre Lagarde, Michel Rousseau et moi-même avons travaillé sur notre propre moteur de jeux WebGL. Il s’appelle Babylon.JS et il est open-source sur Github. La plupart de nos choix d’architecture sont effectués avec un seul but : rendre la conception de jeux 3D pour le web simple.

Tout d’abord, notre plug-in d’export depuis Blender est assez riche et permet d’exporter tout ce dont un designer a besoin : caméra, sources lumineuses, textures, animations et collisions. Du coup, une fois que votre designer 3D a fait le boulot, laissez-moi vous montrez comment créer un petit FPS en quelques lignes de code et en quelques minutes.

  1. Téléchargez BabylonJS sur Github, et prenez le package complet contenant toutes nos scènes de démo. Nous allons prendre les fichiers de la scène « Espilit ».
  2. Téléchargez notre polyfill Hand.js.
  3. Créez un nouveau projet web avec votre serveur préféré (IIS ou Apache) et avec votre outil de développement préféré. Copiez babylon.1.7.3.js et Hand.js dans un répertoire appelé scripts et copiez tous les fichiers de la scène « Espilit » dans le répertoire « Espilit » de votre projet web.
  4. Ajoutez le support pour les types MIME .fx, .babylon et .babylonmeshdata sur votre serveur web. Si vous utilisez IIS, ajoutez cela dans le web.config par exemple :
    <system.webServer>
    	<staticContent>
    		<mimeMap fileExtension=".fx" mimeType="application/fx" />
    		<mimeMap fileExtension=".babylon" mimeType="application/babylon" />
    		<mimeMap fileExtension=".babylonmeshdata" mimeType="application/babylonmeshdata" />
    	</staticContent>
    </system.webServer>

    Si vous utilisez Apache, ajoutez cela dans votre .htaccess par exemple :

    AddType application/fx .fx
    AddType application/babylon .babylon
    AddType application/babylonmeshdata .babylonmeshdata
  5. Créez une nouvelle page HTML nommée index.html et insérez‑y ce code :
    <!DOCTYPE html>
    <html>
    <head>
    	<title>BabylonJS - Espilit demo</title>
    	<script src="scripts/Hand.js"></script>
    	<script src="scripts/babylon.1.7.3.js"></script>
    	<script src="scripts/main.js"></script>
    	<style>
    		html, body {
    			width: 100%; height: 100%;
    			padding: 0; margin: 0;
    			overflow: hidden;
    		}
    		#renderCanvas {
    			width: 100%; height: 100%;
    			touch-action: none; -ms-touch-action: none;
    		}
    	</style>
    	</head>
    	<body>
    		<canvas id="renderCanvas"></canvas>
    	</body>
    </html>
  6. Créez un fichier main.js dans le répertoire scripts et ajoutez‑y ce code :
    document.addEventListener("DOMContentLoaded", startGame, false);
    function startGame() {
    	if (BABYLON.Engine.isSupported()) {
    		var canvas = document.getElementById("renderCanvas");
    		var engine = new BABYLON.Engine(canvas, true);
    		BABYLON.SceneLoader.Load("Espilit/", "Espilit.babylon", engine, function (newScene) {
    			// On attend que les textures et shaders soient prêts
    			newScene.executeWhenReady(function () {
    					// La caméra de prendre le contrôle des entrées (clavier)
    					newScene.activeCamera.attachControl(canvas);
    					// Une fois que la scène est chargée, on lance la boucle de rendu/jeu
    					engine.runRenderLoop(function () {
    						newScene.render();
    					});
    			});
    		}, function (progress) {
    			// A faire: notifiez l’utilisateur de l’avancement du chargement
    		});
    	}
    }

Et c’est fini ! Nous venons juste de créer en quelques lignes de code la même démo présente sur notre site web. Vous n’aurez pas le panneau de contrôle ou l’écran de chargement. Mais vous pouvez déjà bouger dans la scène à l’aide du combo clavier et souris. Les collisions sont actives, vous ne pouvez pas passer à travers les murs.

Démo de Babylon JS

Pour finir, cet exemple marche très bien dans Chrome, Firefox, Opera et IE11. Le moteur s’occupe de tout pour vous. Tous ces vilains « shaders » sont déjà pré-écrits et compatibles avec toutes les plateformes. Toutes ces vilaines matrices sont déjà gérées. L’éclairage est déjà calculé pour vous. Et les déplacements à l’aide des contrôles classiques (souris, tactile ou accéléromètre) sont supportés par le moteur.

Il ne vous reste donc plus qu’à vous concentrer sur le principal plutôt que sur la plomberie. J’espère que j’aurai contribué à rendre le monde de la 3D moins complexe à vos yeux !

6 commentaires sur cet article

  1. Nico, le mardi 17 décembre 2013 à 09:20

    Chouette tuto, et +1 pour le côté caste, j’ai connu ça aussi :)

  2. MoOx, le mardi 17 décembre 2013 à 15:05

    Quand je vois ça, tu me donne vraiment envie de me mettre à la 3D.
    En tant que dév JavaScript, je vois ici une opportunité de dériver sur le dév de jeu via WebGL ! Mais bon, pas sur de la pertinence de mes propos :)
    En tout cas, je sens que je vais me faire une page 404 bien délirante.
    Reste à trouver un design ou reprendre un existant.
    Mille merci !

  3. ssm2017, le mardi 17 décembre 2013 à 17:29

    salut
    bravo pour ce billet instructif qui est vraiment bien sauf qu’il manque à mon avis quelques details pour le vrai debutant sur les bases de la 3d (je n’ai pas regardée la partie webgl).

    - les coordonnees (Z n’est pas la profondeur en 3d mais la hauteur et vu de face (comme sur le screenshot blender, la profondeur est le y) j’ai souvent eu des gens habitués à la vue de dessus des softs 2d et me disent que Z est la profondeur alors qu’en 3d on utilise plutot la vue de face)

    - tu ne parles pas des 3 transformations de base : deplacement, rotation echelle

    - tu ne detailles pas les 3 niveaux de modelisation : vertex, edge, face.

    - tu parles que des faces avec 3 sommets (la base) sauf que les programmes recents peuvent creer des faces de plus de 3 sommets (ngons)

    - et un dernier truc, tu parles de suite des normales sans expliquer ce que c’est auparavant.
    les normales sont importantes pour l’export car elles vont determiner la partie visible d’une face.
    sans creer de cube, on cree juste 3 « vertices » puis une « face » qui les lie par 3 « edges ».
    si on pivote la camera autour, on verra qu’on voit la face d’un cote mais qu’elle est transparente de l’autre.
    la normale est la perpendiculaire à la face visible.

    bien sûr n’hesites pas à me corriger si j’ai dit des erreurs car je n’ai pas non plus la science infuse :)
    je garde ce lien de coté pour le jour ou j’aurais envie de jouer avec webgl :)

  4. philippe_stack, le mardi 17 décembre 2013 à 21:02

    Ce n’est pas vraiment une « caste » stricto-sensu, c’est juste que les bases sont denses, donc ces bases sont rarement reprises en tutoriels Web. Le format n’est pas adapté.

    Donc une simple recherche Google ne donne pas beaucoup d’éléments. En 3d on lit des livres, c’est plus pratique.

    Les grimoires obligatoires sont les suivants :
    – le « Lengyel » c’est les maths : http://www.amazon.fr/Mathematics-Game-Programming-Computer-Graphics/dp/1584502770
    – le reste (GPU, shaders, rendus) c’est le « Realtime rendering » : http://www.amazon.fr/Real-Time-Rendering-T-Akenine-Moller/dp/1568814240/ref=sr_1_1?s=english-books&ie=UTF8&qid=1387310148&sr=1–1&keywords=realtime+rendering
    – les ombres c’est là : http://www.realtimeshadows.com/

    Les éléments se référent à OpenGL ES (= WebGL, grosse imprécision de l’article à ce sujet) sont traités dans le livre classique à ce sujet : http://opengles-book.com/webgl.html

    On est pas une caste, on est gentils au fond… « pour des spés maths ».

  5. David Rousset, le mercredi 18 décembre 2013 à 08:25

    Salut ssm2017,

    Effectivement, mais je voulais juste introduire les notions principales sans écrire un roman. Donc cela passe par des compromis. J’ai l’impression que cela fut efficace car les retours sur Twitter sont bon. :) Ensuite, je donne beaucoup de liens vers des ressources de références dans ma série de tutoriaux sur mon blog. Cet article est basé sur ma propre expérience sur la manière de former certains de nos stagiaires aux bases de la 3D. J’ai donc déjà éprouvé le format à l’oral.

    @philippe, effectivement, il y a de bon bouquins. Mais je ne trouve pas non plus que cela soit un format adapté. Ce n’est pas ludique, il n’y pas de gratification immédiate et il faut en plus savoir par quel bouquin commencer. J’avais essayé quand j’étais « petit » et cela m’a dégouté. Merci de partager tes bouquins de référence, j’y jetterais un œil. Sinon, effectivement, WebGL est un basé sur OpenGL ES 2.0. De là à dire que c’est une « grosse imprécision »… ;-) (tu vois bien que vous êtes dans votre caste ! :-P)

    Merci pour vos retours !

    David

  6. romain, le samedi 25 janvier 2014 à 16:17

    Vraiment intéressant ! Avec ça donne envie de s’y mettre, il y a de quoi se faire un petit jeu là non ?