Rastérisation
La rastérisation, ou matricialisation, est un procédé qui consiste à convertir une image vectorielle en une image matricielle destinée à être affichée sur un écran ou imprimée par un matériel d'impression.
Les scènes en 3 dimensions étant généralement stockées en mémoire sous forme vectorielle, ce terme s'applique également pour leur rendu à l'écran (l'écran fournissant une image matricielle), c'est d'ailleurs la principale utilisation du mot « rastérisation ». Par extension, on englobe aussi dans la rastérisation tous les procédés permettant d'améliorer l'aspect final du rendu 3D.
La rastérisation d'images en deux dimensions étant nécessaire à l'intérieur de son équivalent 3D, la suite de cet article s'intéressera uniquement à la rastérisation d'images en trois dimensions dans le but de détailler les deux méthodes simultanément.
Ce procédé est l'une des deux principales techniques de rendu 3D, avec le ray tracing (« reconstitution du trajet des rayons »). Comparée au ray tracing, la rastérisation est beaucoup plus rapide en matière de calcul.
Étymologie
Rastérisation se calque sur l’anglais rasterize. En anglais, le suffixe -ize forme des verbes à partir de noms ou d'adjectifs, le verbe signifiant alors « faire/fabriquer ce qui est dénoté par le nom »[1]. Le nom raster vient de l’allemand Raster, du latin raster (« râteau, hoyau »), de l’infinitif radere du verbe rādo (« racler, ratisser, gratter, écorcher »)[2].
En anglais courant, raster désigne le motif de lecture affine de lignes parallèles utilisé pour former une image projetée sur le tube cathodique d’un écran. D’où la reprise du terme pour désigner une image matricielle qui est constituée d’une grille de pixels, stockée comme une série de lignes.
Le terme matricialisation, plus rare, découle également du passage à une version matricielle de l’image, par rapport à une image vectorielle. Cependant, ces termes sont eux-mêmes ambigus, des vecteurs pouvant être stockés et manipulés sous forme de matrices. Dans ce vocable, les images matricielles désignent des images décrites point à point, et les images vectorielles tout autre type de description d’une scène nécessitant une transformation vers une image point à point pour affichage.
Principe géométrique de base
La conversion d'images vectorielles en trois dimensions vers une image matricielle en deux dimensions se fait en deux étapes : la conversion de l'image vectorielle 3D en image vectorielle 2D, puis la rastérisation (voir récursivité) de celle-ci en une image matricielle 2D finale.
Calcul des coordonnées 2D
La première étape est la projection des différents modèles de l'image vers le plan 2D. Ce calcul peut être résumé en trois étapes :
- l'intégration du modèle dans le monde, Model to World (M2W) ;
- la prise en compte de la position et de l'angle de la caméra, World to View (W2V) ;
- la prise en compte des dimensions de l'écran, View to Projection (V2P).
Chacune de ces étapes correspond à une matrice. Le calcul est une simple multiplication des coordonnées de chaque sommet avec les trois matrices. On obtient ainsi pour chaque sommet ses coordonnées 2D ainsi que sa profondeur.
Remarque : dans les API 3D actuelles, Direct3D utilise ces trois matrices alors qu'OpenGL n'utilise pas la matrice monde (World) : en effet, elle est combinée avec la matrice View donnant par conséquent une matrice Model-View (GL_MODELVIEW) (remarque valable pour les anciennes versions de l'API OpenGL qui, à partir de la version 4.0, n'implémente plus de système de gestion de matrices, qui doit être pris en charge par le programmeur, dans une optique de flexibilité de la gestion du pipeline graphique).
Transformation Model to World
La matrice M2W correspond à une succession d’opérations réalisées sur un objet. Ces opérations sont :
- la rotation ;
- la translation ;
- la mise à l'échelle.
En termes physiques on dirait qu’appliquer une matrice M2W à un objet revient à passer du référentiel propre à l’objet (le modèle) au référentiel commun à tous les objets d’une scène graphique (le monde).
Les matrices de rotation * Matrice de rotation dans le plan : Soit et
- matrices de rotation dans l'espace :
Dans un repère l'operation ci-dessus équivaut à tourner autour du vecteur c'est-à-dire l'axe de z.
- autour de l'axe des z :
- autour de l'axe des y :
L’angle de vue du repère a changé ainsi que le nom des axes, cependant par rapprochement on peut déduire :
On a alors:- autour de l'axe des x :
On a alors :
La matrice de translation
La matrice de mise à l'échelle
Synthèse M2W - Matrices de rotation
- Matrices de translation
- Matrices de mise à l'échelle
Transformation World to View
Cette matrice procède au calcul effectif des coordonnées de chaque point en fonction de la position et de la direction de la caméra.
Voici une matrice de vue classique. Pos et Dir sont les vecteurs indiquant respectivement la position de la caméra et l'endroit pointé. Le vecteur Up indique la direction verticale. Up, puis F, S et U doivent tous être normalisés.
Avec * produit vectoriel
Transformation View to Projection
La matrice de projection permet d'adapter les coordonnées calculées aux dimensions du framebuffer (c'est-à-dire de la fenêtre ou de l'écran). En effet, une fois les coordonnées multipliées par la matrice de vue, il faut encore prendre en considération la façon dont l'observateur perçoit la scène. Il faut tenir compte de certains paramètres comme les proportions de l'image (4/3, 16/9, ou autre) ou l'angle de vision (120° dans la réalité, généralement 45° à l'écran). C'est à cela que sert cette matrice.
Par exemple, pour un écran de dimension (width, height) on a :
- x_ecran = Xa * width / 2 / Za + width / 2
- y_ecran = Ya * height / 2 / Za + height / 2
Filtrage des vertices
Une fois les coordonnées 2D de chaque sommet connues, différentes techniques existent dans le but d'accélérer la suite du rendu.
Le back-face culling (« élimination des faces cachées ») consiste à calculer la normale de chaque face et à la comparer avec l'orientation de la caméra. Si l'angle entre les deux est supérieur à 90°, la face est dirigée vers l'extérieur et n'a donc pas besoin d'être calculée.
Si les coordonnées 2D à l'écran de tous les sommets d'une face sont à l'extérieur de la ligne formée par un des bords de la fenêtre de vision, il n'est pas non plus nécessaire d'effectuer le reste des opérations pour celle-ci.
Rastérisation des coordonnées 2D vers l'image finale
La dernière opération est de transformer chaque face, ligne ou point en pixels à l'écran.
Par exemple, une ligne qui doit aller des coordonnées (2,3) à (5,3) remplira les pixels (2,3), (3,3), (4,3) et (5,3). L'opération est néanmoins nettement plus complexe pour une ligne oblique ou pour l'intérieur d'un triangle.
Pour chaque pixel ainsi calculé, il s'agit de déterminer ses caractéristiques en pondérant les valeurs aux sommets, qui, elles, sont connues.
Pour reprendre l'exemple de la ligne, si le sommet à gauche est rouge et celui à droite est bleu, le premier pixel à gauche sera complètement rouge, le suivant un peu moins rouge et un peu plus bleu, et ainsi de suite jusqu'au dernier pixel qui sera complètement bleu.
Z-buffer
L'utilisation d'un z-buffer (« tampon des données de profondeur ») est une technique permettant de déterminer quel pixel se trouve au-dessus de quel autre. Il est en effet fréquent que deux éléments se chevauchent, et pour savoir quelles parties de quel(s) élément(s) sont visibles il est possible d'enregistrer au fur et à mesure leur profondeur dans un emplacement mémoire.
Ainsi, si un pixel doit écraser un autre pixel déjà existant, on vérifie grâce au z-buffer que le premier est plus proche de la caméra que le second. Si c'est le cas, le pixel sera écrasé et le z-buffer mis à jour avec la nouvelle valeur de profondeur.
Ne pas utiliser de z-buffer signifie que les derniers éléments à être dessinés le seront forcément au-dessus des autres, même s'ils devraient normalement être en dessous.
Stencil buffer
Un stencil buffer est un tampon mémoire permettant de filtrer les endroits où l'image doit être dessinée. Le mot anglais stencil signifie « pochoir » en français.
Le stencil buffer et le z-buffer sont très liés, puisqu'il s'agit dans les deux cas de filtrer les pixels ou non. Contrairement au z-buffer, il est possible de donner l'ordre au processeur graphique de remplir le stencil buffer dans certaines conditions. L'utilisation d'un stencil buffer repose donc généralement sur deux rendus : un premier rendu partiel permettant de remplir le stencil buffer, puis un second rendu définitif où le filtrage est appliqué.
Pour des exemples pratiques, voir au bas de l'article (ombre portées et reflets).
Anticrénelage
L'anticrénelage ou anti-aliasing permet d'éviter le crénelage des lignes à l'écran. Par exemple si une ligne va des coordonnées (1,3) à (4,4) elle doit remplir entièrement les pixels (1,3) et (4,4), mais seulement partiellement les pixels (2,3), (2,4), (3,3) et (3,4).
Sans anti-crénelage, l'algorithme ne se soucie pas du remplissage partiel, ce qui crée un effet d'escalier. Avec un anti-crénelage, un remplissage partiel est effectué grâce à la moyenne pondérée de la couleur existante à cet endroit et de la nouvelle couleur.
Le FSAA ou Full Screen Anti-Aliasing (« anti-crénelage de tout le décor ») consiste à calculer une image plus grande que la définition souhaitée (image sur-échantillonnée) puis à réduire sa définition vers celle souhaitée. Chaque pixel de l'image résultante est une moyenne des pixels de l'image sur-échantillonnée, adoucissant ainsi naturellement les arêtes obliques. Cette technique offre une bonne qualité et est relativement simple à mettre en œuvre, cependant elle a une influence importante sur les performances. De nombreuses autres techniques et optimisations ont été développées pour pallier ce problème de performances (MSAA, FXAA, MLAA, SMAA, TXAA, etc.).
Résultat final
Le résultat final est une image matricielle stockée dans un frame buffer (« tampon pour le rendu des images »). Des post-traitements peuvent intervenir sur l'ensemble ou des parties de l'image avant d'envoyer le buffer à sa destination finale (i.e., un dispositif de sortie : écran, imprimante…). Par exemple, pour faire « briller » certaines zones (cf. « bloom »), appliquer un filtre de « flou cinétique » (généralement polaire à partir du centre pour émuler un effet de mouvement rapide de l'image dans l'espace ; cf. blur), appliquer un masque sur l'ensemble de l'image (ex. : trou de serrure ou viseur d'une arme à feu), ou encore, appliquer des tampons (ex. du lens flare) , etc. Certains de ces effets s'appliquent à l'ensemble de l'image (cas du bloom) quand les autres utilisent parfois des buffers intermédiaires dédiés à leur tâche précise (cas du blur).
Application des textures (mapping) et éclairage
La base de la rastérisation ne permet néanmoins d'afficher que des objets avec des couleurs simples. La volonté de réalisme a poussé à l'invention des textures et des effets d'ombrage.
Application des textures (mapping)
L'application d'une texture se fait en parallèle tout le long du processus de rastérisation. Chaque sommet initial possède, en plus de ses coordonnées 3D, les coordonnées correspondantes pour la texture à appliquer sur l'ensemble du modèle.
La véritable application de la texture ne se fait qu'au moment du calcul de la couleur de chaque pixel final. Les coordonnées de texture de chaque pixel sont calculées en modulant les valeurs des sommets, de la même manière que pour leur couleur émise et leur profondeur.
La couleur émise de chaque pixel est alors multipliée par la valeur du texel correspondant à ces coordonnées.
Filtrage des textures
Néanmoins un problème se pose : plusieurs pixels peuvent afficher le même texel (le rendu devient flou) ou inversement plusieurs texels devraient être affichés dans le même pixel.
Pour résoudre ce problème, plusieurs techniques existent : le filtrage bilinéaire consiste à prendre la moyenne pondérée des texels autour du texel à afficher, le mipmapping consiste à utiliser une texture plus petite pour les objets éloignés afin d'accélérer les calculs du filtrage. Le filtrage anisotropique est une opération dérivée du mipmapping (« atténuation de l'effet de pixellisation »).
Calcul des ombres
L'application d'une ombre sur un objet consiste simplement à faire varier la couleur émise par chaque sommet en fonction de l'orientation de sa face par rapport aux lumières. Ce calcul se fait souvent en même temps que le calcul des coordonnées 2D.
L'opération se fait généralement en utilisant le produit scalaire de la normale de chaque face par la direction de chaque lumière. Si la normale de la face et la direction de la lumière sont perpendiculaires, alors le produit scalaire sera égal à zéro et la face sera noire.
L'ombrage (i.e., le shading ; cf. ci-dessous) peut également être émulé par l'application de lightmap (consistant à pré-calculer les ombres appliquées aux textures et d'en générer une « sur-texture » d'ombrage) ainsi que par l'utilisation de certains shaders (afin d'émuler un bas-relief des textures ; cf. ci-dessous).
Shaders
Lorsque le processus de rastérisation est effectué par le matériel (carte graphique notamment), tous les calculs ci-dessus sont automatisés. Il est néanmoins possible sur la plupart des cartes depuis 2001 de personnaliser certains calculs en utilisant des shaders.
Un shader est un programme écrit dans un langage proche du C (il en existe plusieurs : HLSL, GLSL, Cg, etc.) qui doit être compilé puis envoyé à la carte graphique pour qu'elle l'utilise.
Vertex shader
Les vertex shaders permettent de commander manuellement le calcul des coordonnées et de l'éclairage. Ce type de shader prend en entrée les données d'un vertex (coordonnées 3D, couleur propre, coordonnées texture) et les renvoie éventuellement modifiés avec leur coordonnées 2D.
Un vertex shader classique sert à modifier la couleur émise par chaque sommet selon divers algorithmes dans le but d'améliorer la qualité du rendu final. Le calcul des coordonnées 2D se fait la plupart du temps de la même manière que la carte l'aurait fait.
Exemple de vertex shader très simple en HLSL :
float4x4 WorldViewProj; float4 VS_Transform(float4 position : POSITION0) { return mul(WorldViewProj, position); }
La variable WorldViewProj
représente les matrices de monde, de vue et de projection multipliées entre elles.
Le shader est en fait la fonction VS_Transform
. Celle-ci multiplie tout simplement les coordonnées du vertex en entrée par les trois matrices afin de placer correctement le point sur l'écran.
Ce shader très simple ne gère pas la lumière.
Geometry shader
Un geometry shader est un nouveau type de shader introduit depuis les shaders model 4.0 qui permet notamment d'ajouter des sommets. Il est exécuté directement après un vertex shader (ou son équivalent par défaut en l'absence de vertex shader).
Ce type de shader prend en entrée tour à tour chaque primitif de la scène (généralement des triangles) et retourne un ou plusieurs primitifs qui sont ensuite passés aux étapes suivantes.
L'absence de geometry shader correspond à un shader qui retournerait directement le paramètre en entrée.
Pixel shader
Le pixel shader, aussi appelé fragment shader, permet de personnaliser le calcul de la couleur de chaque pixel.
Le shader prend en entrée les différents propriétés de chaque pixel (position à l'écran, coordonnées texture, couleur propre) et retourne la couleur du pixel.
Placage de relief (bump mapping)
Le placage de relief ou bump mapping consiste à modifier l'apparence d'une surface de façon à faire croire qu'elle est en relief. Ces techniques interviennent généralement au moment de la rastérisation (du dessin) des texels.
Contrairement au displacement mapping (voir ci-dessous), le bump mapping ne modifie pas la géométrie des objets, seulement leur couleur dans le but de donner une illusion de relief.
Il existe de nombreuses variations de cette technique, utilisant chacune une méthode calcul légèrement différente : parallax mapping, normal mapping, etc.
L'implémentation la plus courante est d'attribuer deux textures à l'objet : sa texture classique, et la texture servant à indiquer le relief. Chaque texel de cette deuxième texture possède une valeur indiquant quelle sera la normale à cet endroit.
L'implémentation se fait alors en deux étapes :
- un vertex shader crée une light map indiquant pour chaque vertex la provenance de la lumière dans le but de la fournir au pixel shader ;
- le pixel shader fait pour chaque pixel le produit scalaire de la provenance de la lumière avec le vecteur récupéré depuis la texture de bump.
La technique est ainsi similaire à l'éclairage simple, mis à part que l'éclairage est calculé par le pixel shader (et non par le vertex shader) et que la deuxième texture est utilisée comme une normale.
Le bump mapping utilise des textures (des bump maps) n'ayant qu'une couche (nuances de gris) correspondant à la hauteur afin d'indiquer comment la lumière doit se comporter selon l'angle d'attaque (ex. : couleur assombrie ou éclaircie si Z est plus bas ou plus haut).
Le normal mapping utilise des textures de normales (des normal maps) ayant trois couches (X, Y, Z). Chaque pixel de ces textures indique l'orientation du vecteur de la normale à émuler, c'est-à-dire, l'inclinaison du pixel/texel selon X et Y, ainsi que son élévation Z. Ce n'est pas une technique de rendu à proprement parler, mais une technique de préservation de l'information de la surface.
Les normal maps sont généralement utilisées avec un shader de parallax mapping qui permet de décaler l'affichage de chaque texel lors de la rastérisation afin d'émuler le volume.
Mixées, ces deux techniques permettent donc d'émuler et le volume de la surface, et son ombrage.
Displacement mapping
Le displacement mapping (« placage d'effets de déplacement ») consiste, contrairement au bump mapping, à modifier la géométrie des objets en fonction de la texture.
Pour cela, on attribue deux textures à chaque objet : sa texture classique et sa displacement map. On utilise alors un vertex shader qui modifie la position de chaque vertex en fonction du texel qui devrait lui être appliqué. Par conséquent, plus il y a de vertex et plus la displacement map sera précise, l'idéal étant un vertex par pixel de celle-ci.
L'arrivée des geometry shaders va peut-être modifier cette technique, grâce à sa possibilité de multiplier les vertex en temps réel[réf. nécessaire].
Cel-shading
Le cel-shading ou toon shading consiste à afficher un rendu de type bande dessinée. Les personnages de bande dessinée ont généralement peu de couleurs différentes. Pour créer un rendu similaire, il suffit donc de limiter le nombre de couleurs à l'écran.
La technique consiste à utiliser un pixel shader qui va arrondir la couleur. Par exemple, entre #000 et #444, la couleur sera arrondie à #222, entre #444 et #999, arrondie à #777, et sinon, arrondie à #CCC. De cette façon, il n'y aura que trois couleurs différentes qui ressortiront.
Autres astuces et effets
Skybox
Le rendu du ciel se fait généralement à l'aide d'une skybox. Il s'agit d'un objet, souvent d'un cube, d'une demi-sphère ou d'un plan, recouvert d'une texture et englobant toute la scène. Il est toujours placé à distance fixe de la caméra, ce qui assure qu'il aura toujours la même dimension à l'écran.
L'ajout de nuage se fait généralement soit par rotation de cet objet (les nuages sont alors dans la texture), soit par des billboards (voir ci-dessous).
Billboards
Dans le but d'optimiser le rendu, on peut utiliser des billboards, c'est-à-dire un objet en 2D qui fera toujours face à la caméra. Cette technique permet souvent de dessiner les arbres (très nombreux), les particules et projectiles, certains effets comme le feu, et parfois même les personnages. De manière générale, ils servent à dessiner les objets très nombreux à l'écran.
Lorsque la caméra est loin d'un billboard, cette technique ne se voit pas, mais de près, elle est très visible.
De nos jours, les jeux vidéo professionnels utilisent de moins en moins cette technique, car la vitesse de rendu gagnée n'est plus assez importante pour justifier la dégradation de l'image finale.
Ombre portée
La création d'une ombre portée repose sur l'utilisation d'un stencil buffer. On détermine tout d'abord les endroits sur l'image 2D où se situent les ombres portées en remplissant le stencil buffer à cet endroit ; on effectue alors un premier rendu normal avec une lumière atténuée, puis un deuxième avec la lumière normale mais avec le filtrage par stencil buffer activé. Le second rendu dessine par-dessus le premier, sauf aux endroits filtrés par le stencil buffer, c'est-à-dire sur les parties ombrées de l'image.
Réflexion
L'ajout de reflets est une technique avancée qui nécessite également l'utilisation d'un stencil buffer.
On dessine tout d'abord l'ensemble de la scène. Ensuite, pour chaque zone réflective (miroir, métal, etc.) :
- on dessine la zone réflective en ordonnant au processeur graphique de remplir le stencil buffer à l'endroit où sont placés les pixels ;
- on applique ensuite une transformation à l'ensemble de la scène pour qu'elle se retrouve de l'autre côté du plan du miroir ;
- on redessine ensuite l'ensemble de la scène (ou au moins les parties que l'on veut voir réfléchies) avec le filtrage activé, la scène est alors redessinée de l'autre côté du miroir mais le résultat n'est affiché que dans la zone du miroir lui-même.
Pour ajouter des effets comme un reflet déformé, il suffit d'adapter la scène entre le premier et le deuxième rendu.
Il est à noter que pour émuler les reflets, on utilise souvent des environment maps (env map) qui utilisent des env box (textures du type skyboxes) réalisées à partir d'un point précis de la scène à rastériser. Le reflet de ces env box viennent se mixer à la texture des surfaces selon les valeurs colorimétriques des trois couches d'une texture dédiée que l'on appelle env map (afin de « coloriser » et pondérer le reflet des envbox selon la nature des surfaces).