Mesh, les fondamentaux

Salut à toutes et à tous !

Bienvenue dans ce tutoriel, le premier d’une série sur les meshes dans Unity.
Ici, je vous expliquerai le fonctionnement général d’un mesh puis dans les prochains articles je détaillerai ce fonctionnement sur le moteur de jeu Unity 3d (pour finir par créer des petits générateurs procéduraux, un loader de fichier obj, un morpher, et pleins de petites choses plutôt sympas dans le même genre).

Comment décrire une forme en 3d ?

Il existe plusieurs façons de représenter et de décrire une forme, un objet dans un espace à 3 dimensions. On peut les séparer en 3 groupes principaux :

  • Représentation polygonale : les formes, appelées meshes, sont composées de points reliés par des polygones (le volume est implicite : il n’est pas représenté mais peut être calculé). C’est la représentation la plus utilisé et également la plus simple à manipuler.
  • Représentations surfaciques : les formes sont composées de surfaces, le volume est implicite.
    Ex : surfaces de Bezier, NURBS (Non-Uniform Rational B-Splines ou B-Splines Rationnelles Non Uniformes), modèles de logiciels de CAO (Catia, Inventor, Solidworks et autres), etc…
  • Représentations volumiques : contrairement aux précédentes représentations, celles-ci incluent le volume entier des formes explicitement (petite exception pour les points clouds où on peut choisir de représenter seulement les points constituant les surfaces, ce qui allège considérablement la quantité de données requise).
    Ex : voxels, point clouds (nuages de points), volume meshes, etc…

Dans ce tutoriel, nous verrons en détail ce qu’est un mesh, puis dans les tutoriels suivant comment créer procéduralement un mesh, le manipuler ou encore le charger depuis un fichier.
A noter qu’une carte graphique ne peut ni rendre de surface (Bezier, NURBS ou autres) ni rendre de voxels, on doit donc approximer ces surfaces/voxels par des meshes pour pouvoir les afficher à l’écran.

Qu’est ce qu’un mesh ?

Un mesh (meshes au pluriel et maillage en français) est composé de vertices, edges et faces (polygones).
Un vertex (pl : vertices, fr : point) définie un point dans un espace 3d (avec les composantes x y z, chacune étant une translation sur l’axe correspondant de l’origine du mesh).
Un edge est un segment reliant 2 vertices entre eux.
Une face est un polygone reliant 3 vertices ou plus. Les faces ayant 3 vertices sont appelées tris (triangles), celles ayant 4 vertices sont appelées quads (quadrangles) et les autres (> 4 vertices donc) sont parfois appelées nGons (pour n-sided polygons). Chaque face est entourée d’edges et une face a obligatoirement le même nombre de vertices et d’edges.

Image : cube vertex edge tri

On peut voir 7 vertices, 9 edges (12 en comptant ceux des tris) et 3 quads, donc 6 tris sur ce cube.

Mais une carte graphique ne peut pas rendre des faces, mais seulement des tris. Pourquoi ? Car un tri sera toujours plat : vous prenez 3 points au hasard dans l’espace, un plan passera toujours par ces 3 points, ce qui n’est pas le cas avec 4 points ou plus, et donc chaque face sera décomposée en plusieurs tris. Ex : un quad formera deux tris.

D’autres informations sont également associées à chaque vertex : l’uv et la normale.
Une coordonnée uv est un point dans un espace 2d : à chaque point 3d (vertex) correspond un point 2d (uv). Mais à quoi ça sert ? Mettons que vous venez de modéliser un super personnage, mais il est tout gris et c’est bien dommage. Comment le mettre en couleur ? La réponse : à l’aide du texturing, c’est à dire que l’on va créer une texture (une image) que l’on appliquera sur le mesh à l’aide des uvs. La carte graphique saura donc à quel vertex correspond quel pixel de la texture.

Image : dépliage uv d'un cube

La texture appliquée sur le cube que l’on vient de voir.
On peut y distinguer les vertices, edges et tris du mesh.

Dans le cas où l’on a plusieurs textures et qu’on veut un dépliage différent sur chaque texture (ex : une diffuse map et une lightmap), on utilise plusieurs uv sets (cartes uv) c’est à dire que chaque vertex n’aura plus un uv seulement mais un uv par uv set (il y a un seul uv set par « défaut »).
Il y a toutefois une différence notable entre les logiciels de modélisation et le un peu plus bas niveau : vous avez peut-être remarqué que l’on retrouvait 2 fois l’edge en rouge sur la texture pourtant il n’y en a qu’un sur le screen plus haut, cela vient du fait que l’on est obligé de séparer des edges en deux pour avoir un dépliage correct et minimiser les déformations, ce qui donne au final deux uvs voir plus pour un seul vertex alors que l’on as toujours un seul uv set. Cependant à plus bas niveau, les vertices ont toujours chacun un uv/uv set : il y a donc souvent plusieurs vertices au même endroit mais avec des uvs différents.

Une normale (normal en anglais) est une direction qui indique le sens des faces (c-à-d : ici c’est l’intérieur du mesh, le volume, et là c’est l’extérieur), il y en a une par vertex. Elle sert également à calculer la lumière renvoyée par le mesh.

Rendu et shader

Cette partie vous aidera à comprendre comment une scène 3d est rendue par la carte graphique, elle est toutefois facultative et plus complexe que le reste.

Qu’est-ce qu’un shader ?

http://fr.wikipedia.org/wiki/Shader
Un shader est un programme informatique, utilisé en image de synthèse, pour paramétrer une partie du processus de rendu réalisé par une carte graphique ou un moteur de rendu logiciel. Ils peuvent permettre de décrire l’absorption et la diffusion de la lumière, la texture à utiliser, les réflexions et réfractions, l’ombrage, le déplacement de primitives et des effets post-traitement.

Pour faire simple, un shader est un programme exécuté sur la carte graphique, donc par le gpu (unité de traitement graphique) qui va le plus souvent calculer le rendu d’un mesh (couleur, éclairage, ombrage, etc). Un vertex shader s’exécutera pour chaque vertex alors qu’un pixel shader (ou fragment shader) s’exécutera pour chaque pixel.

Pour afficher une scène 3d sur votre écran, le gpu va avoir besoin de transformer cette scène en pixels. On appelle cela le pipeline 3d, qui va justement utiliser les shaders. Voici les différentes étapes du pipeline 3d :

1. Vertex Pipeline :
Le shader reçoit les données du vertex et opère principalement des transformations (translations, rotations, mises à l’échelle) à l’aide de matrices (une seule matrice peut réaliser les trois opérations en même temps). Ex : passer des coordonnées objets (object coordinates : les vertices sont définis par rapport à l’origine de l’objet) aux coordonnées monde (world coordinates : les vertices sont définis par rapport à l’origine du monde).
Ensuite, mais uniquement pour le vertex lighting, l’éclairage est calculé sur le vertex grâce un produit scalaire entre sa normale et la direction de la lumière.
Tout ceci est effectué par le pipeline fixe (une sorte vertex shader préprogrammé dans le gpu), toutefois il peut être remplacé par un vertex shader (qui sera donc le pipeline programmable) qui est beaucoup plus flexible mais aussi légèrement plus lent.

2. Viewport Transform, Clipping, Culling, Rasterization :
Backface Culling : les faces dont la normale ne pointe pas vers la caméra sont invisibles et sont donc éliminées du rendu (peut être modifié dans le shader).
Clipping : les faces trop près ou trop loin de la caméra sont exclues, « trop près » et « trop loin » étant définis par les clipping planes de la caméra.
Viewport Transform : passe des coordonnées monde aux coordonnées écran (viewport coordinates), les faces en dehors du champ de vision de la caméra sont également exclues.
Rasterization (tramage) : Les triangles sont parcourus pixel par pixel : on passe au pixel pipeline.

3. Pixel Pipeline :
Le pipeline va s’occuper des textures et parfois du pixel lighting (on utilise soit le vertex lighting, soit le pixel lighting.
Pour chaque pixel, le pipeline doit trouver la couleur correspondante sur la texture : il connait la position du pixel par rapport au tri, récupère les uvs associés aux vertices du tri et donc le triangle correspondant dans les coordonnées uvs. A partir de ce triangle il peut calculer l’uv correspondant au pixel. Mais cet uv ne tombera pas forcément pile sur un pixel donc soit on choisit de prendre le pixel le plus proche (parfois appelé point filtering), soit on interpole entre les 4 pixels les plus proches (bilinear filtering, ou filtre d’interpolation linéaire en 2d).

Image : point & bilinear filtering

Ici la texture de terre utilise le point filtering alors que la texture d’herbe utilise le bilinear filtering.

Pour le pixel lighting (éclairage par pixel et non par vertex), la normale sera interpolée à partir des normales des 3 vertices du tri : chaque pixel aura donc une normale « virtuelle ». L’éclairage sera ensuite identique au vertex lighting : produit scalaire entre cette normale et la direction de la lumière. Si le vertex lighting est utilisé, les valeurs calculées dans le vertex shader sont interpolées pour chaque pixel.

Image : vertex lighting vs pixel lighting

vertex lighting vs pixel lighting, ce dernier est quand même mieux, vous ne trouvez pas ?

Idem que pour le vertex pipeline, tout ceci est effectué par le pipeline fixe mais peut être remplacé par un pixel shader, plus flexible mais légèrement plus lent.

4. Depth Test :
Le test de prodondeur (deph test) se base sur la profondeur du pixel (la distance entre la caméra et le point où a été calculé le pixel) et permet de savoir quel pixel est le plus proche de la caméra donc quel pixel doit être affiché à l’écran, Cette profondeur étant stockée dans une image en noir (loin) et blanc (proche) appelée z-buffer.
Ex : on a deux cubes, (nommons les A et B), B partiellement recouvert par A et quand le gpu rend ces cubes, on obtient donc deux pixels pour chaque pixel à l’écran (un de A, un de B), alors qu’on ne pourra en afficher qu’un à l’écran. Mais lors du rendu du deuxième pixel, on regarde dans le z buffer et s’il n’y a rien ou qu’il y a un pixel plus éloigné que l’actuel, on le remplace. On est donc sûr d’avoir au final tous les pixels les plus proches.
S’il y a des objets transparents, cela se complique légèrement car il ne faut pas afficher seulement le plus proche : dans ce cas on trouve le pixel opaque le plus proche de la caméra puis on trie tous les pixels devant lui du plus éloigné au plus proche, pour ensuite procéder à l’apha blending.

5. Alpha Blending :
Les couleurs sont définies par 4 composantes : rouge, vert, bleu et alpha (RVBA ou RGBA en anglais) où la composante alpha détermine la transparence. C’est utile lorsque l’on veut représenter une vitre par exemple, dont la couleur finale sera mélangé avec la couleur de l’objet derrière cette vitre (suivant l’alpha de la vitre). Ex : une vitre devant un cube rouge ne donnera à l’écran ni la couleur de la vitre, ni la couleur exacte du cube, mais un mélange des deux. C’est ce que l’on appelle le mélange alpha ou alpha blending.

6. Frame Buffer :Les pixels finaux sont ensuite écris dans une image tampon (frame buffer) qui sera envoyée à l’écran pour l’affichage. On pourrait très bien écrire directement sur l’écran mais on obtient du tearing, et c’est pas très beau :

Image : tearing effect

Remarquez le décalage au milieu : la moitié des nouveaux pixels a été écrit alors que l’image est affichée et l’autre moitié vient l’image précédente qui n’a pas totalement été effacée.

Source (et pour plus d’informations) : http://laruche.com/2005/05/12/architecture-et-fonctionnement-d-un-gpu-108881

Merci à yoyoyaya pour les 2 premières images !

Bookmark the permalink.

3 Comments

  1. Merci beaucoup car le ton de l’exposé est excellent pour bien comprendre!
    Je m’abonne, c’est sûr!
    Bon courage pour d’autres tutos explicatifs!
    Spirou4D

  2. Merci,
    super article, le vocabulaire est bien détailler.
    simple et clair, les images sont bien choisies.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>