Les cartes graphiques/L'éclairage d'une scène 3D : shaders et T&L

Un livre de Wikilivres.

Dans le chapitre précédent, nous avons vu que les cartes graphiques ont progressivement évoluées dans le temps. Elles ont d'abord intégré des circuits pour les textures et la rastérisation, puis des circuits géométriques non-programmables. Mais les circuits géométriques fixes ont ensuite été remplacés par des processeurs programmables. Les cartes graphiques se sont mis à exécuter des shaders, des programmes informatiques spécialisés dans le rendu 3D. Les shaders ont remplacé les circuits géométriques, avant de gagner en fonctionnalité et de devenir indispensable pour tout moteur 3D. Pour comprendre pourquoi ce revirement, nous devons étudier le cas particulier du rendu de l'éclairage d'une scène 3D. L'éclairage d'une scène 3D calcule les ombres, mais aussi la luminosité de chaque pixel, ainsi que bien d'autres effets graphiques.

Dans le chapitre précédent, on a vu que le pipeline graphique contient une étape d'éclairage durant la phase de traitement de la géométrie. Elle attribue une illumination/couleur à chaque triangle ou à chaque sommet de la scène 3D, le plus souvent à chaque sommet de la scène 3D. Mais l'étape d'éclairage géométrique du pipeline graphique ne suffit pas à elle seule à éclairer la scène comme voulu. En effet, il faut distinguer deux types différents d'éclairage dans les jeux vidéos : le vertex lighting, où l'éclairage est calculé pour chaque sommet/triangle d’une scène 3D, et l'éclairage par pixel (per-pixel lighting), où l'éclairage est calculé pixel par pixel. L'étape d'éclairage géométrique du pipeline graphique est suffisante pour calculer du vertex lighting, mais ne peut pas faire du per-pixel lighting à elle seule. Avec le per-pixel lighting, l’éclairage est finalisé soit dans l'étape de rastérisation, soit par des pixels shaders. Pour le dire autrement, l'éclairage ne se fait pas qu'au niveau de la géométrie, comme il a pu être dit au chapitre précédent. Même des algorithmes simples demandent une intervention des pixels shaders pour calculer l'éclairage pixel par pixel. Autant dire que cette distinction a été importante dans l'évolution du matériel graphique et l'introduction des shaders. Mais pour comprendre cela, il faut voir comment fonctionnent les algorithmes d'éclairage et voir comment le hardware peut aider à les implémenter.

Les sources de lumière et les couleurs associées[modifier | modifier le wikicode]

L'éclairage d'une scène 3D provient de sources de lumières, comme des lampes, des torches, etc. Celles-ci sont souvent modélisées comme de simples points, qui ont une couleur bien précise (la couleur de la lumière émise) et émettent une intensité lumineuse codée par un entier. La lumière provenant de ces sources de lumière est appelée la lumière directionnelle. Mais en plus de ces sources de lumière, il faut ajouter une lumière ambiante, qui sert à simuler l’éclairage ambiant (d’où le terme lumière ambiante). Par exemple, elle correspond à la lumière du cycle jour/nuit pour les environnements extérieurs. On peut simuler un cycle jour-nuit simplement en modifiant la lumière ambiante : nulle en pleine nuit noire, élevée en plein jour. En rendu 3D, la lumière ambiante est définie comme une lumière égale en tout point de la scène 3D, et sert notamment à simuler l’éclairage ambiant (d’où le terme lumière ambiante).

Lumière ambiante.
Lumière directionnelle.

Dans ce qui suit, on part du principe que les modèles 3D ont une surface, sur laquelle on peut placer des points. Vous vous dites que ces points de surface sont tout bêtement les sommets et c'est vrai que c'est une possibilité. Mais sachez que ce n'est pas systématique. On peut aussi faire en sorte que les points de surface soient les triangles de du modèle 3D. Pour simplifier, vous pouvez considérer que le terme "point de la surface" correspond à un sommet, ce sera l'idéal et suffira largement pour les explications.

L'éclairage attribue à chaque point de la surface une illumination, à savoir sa luminosité, l'intensité de la lumière réfléchie par la surface. L'illumination d'un point de surface est définie par un niveau de gris. Plus un point de surface a une illumination importante, plus il a une couleur proche du blanc. Et inversement, plus son illumination est faible, plus il est proche du noir. L'attribution d'une illumination à chaque point de surface fait que la scène 3D est éclairée. Mais on peut aussi aller plus loin et colorier la géométrie. Pour cela, il suffit que l'éclairage ne fasse pas que rendre la scène 3D en niveaux de gris, mais que les niveaux de luminosité sont calculés indépendamment pour chaque couleur RGB.

Illustration de la dispersion de la lumière diffuse par une surface, suivant sa rugosité.

Le calcul exact de l'illumination de chaque point de surface demande de calculer trois illuminations indépendantes, qui ne proviennent pas des mêmes types de sources lumineuses.

  • L'illumination ambiante correspond à la lumière ambiante réfléchie par la surface.
  • Les autres couleurs proviennent de la réflexion de a lumière directionnelle. Elles doivent être calculées par la carte graphique, généralement avec des algorithmes compliqués qui demandent de faire des calculs entre vecteurs.
    • L'illumination spéculaire est la couleur de la lumière réfléchie via la réflexion de Snell-Descartes.
    • L'illumination diffuse vient du fait que la surface d'un objet diffuse une partie de la lumière qui lui arrive dessus dans toutes les directions. Cette lumière « rebondit » sur la surface de l'objet et une partie s'éparpille dans un peu toutes les directions. La couleur diffuse ne dépend pas vraiment de l'orientation de la caméra par rapport à la surface. Elle dépend uniquement de l'angle entre le rayon de lumière et la verticale de la surface (sa normale).

Elles sont additionnées ensemble pour donner l'illumination finale du point de surface. Chaque composante rouge, bleu, ou verte de la couleur est traitée indépendamment des autres, ce qui donne une scène 3D coloriée.

Couleurs utilisées dans l'algorithme de Phong.

Le calcul des illuminations ambiantes, spéculaire et diffuse est le fait d'algorithmes plus ou moins compliqués. Et ces algorithmes sont très nombreux, au point où on ne pourrait pas en faire la liste. Les algorithmes varient beaucoup d'un moteur de jeu à l'autre, d'une carte graphique à l'autre. Les plus simples se décrivent en quelques équations, les algorithmes les plus complexes prennent un chapitre à eux seuls dans un livre spécialisé.

Les données nécessaires pour les algorithmes d'illumination[modifier | modifier le wikicode]

L'algorithme d’illumination a besoin de plusieurs informations, certaines étant des nombres, d'autres des vecteurs, d'autres des angles. Tous les algorithmes d'éclairage directionnel impliquent de faire des calculs trigonométriques dans l'espace, de déterminer des angles, des distances, et bien d'autres choses.

Le premier de ces paramètres est l'intensité de la source de lumière, à quel point elle émet de la lumière. Là encore, cette information est encodée par un simple nombre, un coefficient, spécifié par l'artiste lors de la création du niveau/scène 3D.

Le second est un nombre attribué à chaque point de surface : le coefficient de réflexion. Il indique si la surface réfléchit beaucoup la lumière ou pas, et dans quelles proportions. Généralement, chaque point d'une surface a plusieurs coefficients de réflexion, pour chaque couleur : un pour la couleur ambiante, un pour la couleur diffuse, et un pour la couleur spéculaire.

Les calculs de réflexion de la lumière demandent aussi de connaitre l'orientation de la surface. Pour gérer cette orientation, tout point de surface est fourni avec une information qui indique comment est orientée la surface : la normale. Cette normale est un simple vecteur, perpendiculaire à la surface de l'objet, dont l'origine est le point de surface (sommet ou triangle).

Normale de la surface.

Ensuite, il faut aussi préciser l'orientation de la lumière, dans quel sens est orientée. La majorité des sources de lumière émettent de la lumière dans une direction bien précise. Il existe bien quelques sources de lumière qui émettent de manière égale dans toutes les directions, mais nous passons cette situation sous silence. Les sources de lumières habituelles, comme les lampes, émettent dans une direction bien précise, appelée la direction privilégiée. La direction privilégiée est un vecteur, notée v dans le schéma du dessous.

Un autre vecteur important est celui de la ligne entre le point de surface considéré et la caméra (noté w dans le schéma ci-dessous).

Il faut aussi tenir comme du vecteur qui trace la ligne entre la source de lumière et le point de surface (noté L dans le schéma ci-dessous).

Enfin, il faut ajouter le vecteur qui correspond à la lumière réfléchie par la surface au niveau du point de surface. Ce vecteur, noté R et non-indiqué dans le schéma ci-dessous, se calcule à partir du vecteur L et de la normale.

Vecteurs utilisés dans le calcul de l'illumination, hors normale.

La plupart de ces informations n'a pas à être calculée. C'est le cas de la normale de la surface ou du vecteur L qui sont connus une fois l'étape de transformation réalisée. Même chose pour le coefficient de réflexion ou de l'intensité de la lumière, qui sont connus dès la création du niveau ou de la scène 3D. Par contre, le reste doit être calculé à la volée par la carte graphique à partir de calculs vectoriels.

Le calcul des couleurs par un algorithme d'illumination de Phong[modifier | modifier le wikicode]

À partir de ces informations, divers algorithmes peuvent éclairer une scène. Dans ce qui va suivre, nous allons voir l'algorithme d'illumination de Phong, la méthode la plus utilisée dans le rendu 3D.S'il n'est peut-être pas l'algorithme le plus utilisé, vous pouvez être certain que c'est au moins une version améliorée ou un autre algorithme proche mais plus poussé qui l'est à sa place.

L'illumination ambiante d'un point de surface s'obtient simplement à partir de la lumière ambiante. On pourrait croire qu'elle est simplement égale à la lumière ambiante, mais c'est légèrement plus compliqué que ça. Deux objets de la même couleur, illuminés par la même lumière ambiante, ne donneront pas la même couleur. Un objet totalement rouge illuminé par une lumière ambiante donnera une couleur ambiante rouge, un objet vert donnera une lumière verte. Et sans même parler des couleurs, certains objets sont plus sombres à certains endroits et plus clairs à d'autres, ce qui fait que leurs différentes parties ne vont pas réagir de la même manière à la lumière ambiante. La couleur de chaque point de surface de l'objet lui-même est mémorisée dans le modèle 3D, ce qui fait que chaque point de surface se voit attribuer, en plus de ces trois coordonnées, une couleur ambiante qui indique comment celui-ci réagit à la lumière ambiante. La couleur ambiante finale d'un point de surface se calcule en multipliant la couleur ambiante de base par l'intensité de la lumière ambiante. Les deux sont des constantes pré-calculées par les concepteurs du jeu vidéo ou du rendu 3D.

avec la couleur ambiante du point de surface et l'intensité de la lumière ambiante.
Vecteurs utilisés dans l'algorithme de Phong (et dans le calcul de l'éclairage, de manière générale).

L'illumination spéculaire et diffuse sont calculées autrement, à partir des vecteurs indiqués dans le schéma ci-contre. l'illumination diffuse est calculée à partir du coefficient de réflexion diffuse qui indique à quel point la surface diffuse de la lumière, l'intensité de la source lumineuse, et l'angle entre N et L. L'angle entre N et L donne la manière dont la surface est penchée par rapport à la surface lumineuse : plus elle est penchée, plus la lumière est diffusée et moins l'illumination diffuse est intense. L'angle en question est noté dans l'équation suivante :

Pour calculer la lumière spéculaire, il faut prendre en compte l'angle que fait la caméra et la lumière réfléchie, c'est à dire l'angle entre v et R, que nous noterons . Là encore, on doit utiliser le coefficient de réflexion spéculaire de la surface et l'intensité de la lumière, ce qui donne :

En additionnant ces trois sources d'illumination, on trouve :

Les algorithmes d'éclairage[modifier | modifier le wikicode]

Maintenant que l'on sait comment est calculé l'illumination d'un point de surface, passons maintenant aux algorithmes d'éclairage proprement dit. C’est une bonne chose de savoir comment les points de surface (sommets ou triangles) sont éclairés, mais rappelons que ce sont des pixels qui s'affichent à l'écran. L'étape d'éclairage réalisée par la géométrie peut calculer la luminosité/couleur d'un sommet/triangle, mais pas celle d'un pixel. Pour cela, il faut déterminer la luminosité d'un pixel à partir de la luminosité/couleur des sommets du triangle associé. Pour cela, il existe plusieurs algorithmes qui font ce calcul. les trois plus connus sont appelés l'éclairage plat, l'éclairage de Gouraud, et l'éclairage de Phong. Notons que ce dernier n'est pas identique à l'éclairage de Phong vu précédemment.

Les trois algorithmes principaux d'éclairage[modifier | modifier le wikicode]

Dans ce dessin, le triangle a un sommet de couleur bleu foncé, un autre de couleur rouge et un autre de couleur bleu clair. L’interpolation plate et de Gouraud donnent des résultats bien différents.

L'éclairage plat calcule l'éclairage triangle par triangle. Typiquement, l'algorithme d'éclairage part de la normale du triangle, puis effectue les calculs d'éclairage à partir de cette normale. La normale est fournie pour chaque triangle, directement dans le modèle 3D, de même que chaque triangle a un coefficient de réflexion ambiante/spéculaire/diffuse. L'algorithme applique ensuite l’algorithme d'illumination triangle par triangle, ce qui fait que chaque triangle se voit attribuer une couleur, puis l'unité de rastérisation applique cette couleur sur tous les pixels associés à ce triangle.

L'éclairage de Gouraud calcule l'éclairage sommet par sommet. Tous les sommets se voient attribuer une illumination, puis l'algorithme calcule la couleur de chaque pixel à partir des couleurs du sommet du triangle associé. Le calcul en question est une sorte de moyenne, où la couleur de chaque sommet est pondéré par un coefficient qui dépend de la distance avec le pixel. Plus le pixel est loin d'un sommet, plus ce coefficient est petit. Typiquement, le coefficient varie entre 0 et 1 : de 1 si le pixel est sur le sommet, à 0 si le pixel est sur un des sommets adjacents. La moyenne effectuée est généralement une interpolation bilinéaire, mais n'importe quel algorithme d'interpolation peut marcher, qu'il soit simplement linéaire, bilinéaire, cubique, hyperbolique. L'étape d'interpolation est prise en charge soit par l'étape de rastérisation, soit par les pixel shaders.

L'éclairage de Phong est différent des précédents dans le sens où il calcule l'éclairage pixel par pixel. Avec cet algorithme, la géométrie n'est pas éclairée : les couleurs des sommets ne sont pas calculées, seuls les vecteurs adéquats le sont. C'est notamment le cas de la normale, qui est calculée pour chaque sommet. La normale est alors calculée pour chaque pixel, par interpolation des normales des sommets du triangle. Puis, l'algorithme d'illumination de Phong est effectué pour chaque normale interpolée.

Interpolation des normales dans l'éclairage de Phong.

L'éclairage de Gouraud et l'éclairage plat sont des cas particulier de vertex lighting, où l'éclairage est calculé pour chaque sommet d’une scène 3D. De l'autre côté, l'éclairage de Phong est un cas particulier d'éclairage par pixel (per-pixel lighting), où l'éclairage est calculé pour chaque pixel à partir des informations géométriques. Il existe d'autres techniques de per-pixel lighting, mais celles-ci sont surtout des techniques logicielles, comme le rendu différé.

Les résultats, avantages et inconvénients[modifier | modifier le wikicode]

D3D Shading Modes
Flat Gouraud Shading

Pour simplifier fortement, l'éclairage plat calcule la couleur triangle par triangle, l'éclairage de Gouraud calcule la couleur sommet par sommet, et l'éclairage de Phong calcule la couleur pixel par pixel. La différence entre les trois algorithmes se voit assez facilement. L'éclairage plat donne un éclairage assez carré, avec des frontières assez nettes. l'éclairage de Gouraud donne des ombres plus lisses, dans une certaine mesure, mais pèche à rendre correctement les reflets spéculaires. L'éclairage de Phong est de meilleure qualité, surtout pour les reflets spéculaires.

Flat shading
Gouraud Shading
Phing Shading

En termes de calculs à effectuer, l'algorithme le plus rapide est l'éclairage plat, suivi par l'éclairage de Gouraud, puis par l'éclairage Phong. Cela tient à la quantité de calculs d'éclairage à effectuer. L’éclairage plat calcule les vecteurs une fois par triangle, alors que l'algorithme de Gouraud fait les mêmes calculs pour chaque sommet. Pour l'algorithme de Phong, on doit faire plus de calculs que pour les deux autres. Certes, il demande de faire moins de calculs géométriques : on a juste à calculer les normales des surfaces et les positions des sommets, pas de calculer l'éclairage au niveau géométrique. Par contre, on doit faire plus de calculs au niveau des pixels : on doit interpoler les normales et calculer l'éclairage sur toutes les normales. Vu qu'il y a plus de normales au final, donc plus de points de surface, on doit faire plus de calculs d'éclairage au total. Bien sûr, tout cela, ne tient pas compte des possibilités d'accélération par la carte graphique, juste du nombre total de calculs à effectuer.

En général, le per-pixel lighting a une qualité d'éclairage supérieure aux techniques de vertex lighting, mais il est aussi plus gourmand. Le per-pixel lighting est utilisé dans presque tous les jeux vidéo depuis DOOM 3 (l'un des premiers jeux à utiliser ce genre d'éclairage), en raison de sa meilleure qualité, les ordinateurs actuels étant assez performants.

L'implémentation en hardware de ces trois algorithmes[modifier | modifier le wikicode]

Pour ce qui est de l'implémentation en hardware, les trois algorithmes ne sont pas équivalents. Le plus simple à implémenter est l'éclairage plat, qui demande juste d'avoir de quoi effectuer l'algorithme d'illumination de Phong. Les cartes graphiques anciennes comme la Geforce 256 savaient le faire en matériel. L'éclairage de Gouraud s'implémente de la même manière, sauf que l'algorithme d'illumination de Phong doit se faire sur les triangles, ce qui ne change pas chose et est à la portée de toutes les cartes graphiques depuis la Geforce 256. Il faut juste rajouter un circuit d'interpolation, mais celui-ci fait normalement partie de l'unité de rastérisation, comme on le verra plus tard dans le chapitre sur celle-ci. Pour donner un exemple, la console de jeu Playstation 1 gérait l'éclairage plat et de Gouraud directement en matériel, dans son GPU.

Avec l’éclairage de Phong, l'éclairage se fait en deux étapes, séparées par l'étape de rastérisation. Et les unités de calculs géométriques non-programmables n'étaient pas prévues pour ce genre de calculs. Si le programmeur a besoin des normales pour chaque pixel mais que l'unité géométrique ne te les fournit pas, l'implémentation de l’algorithme de Phong ne peut se faire qu'en logiciel, (ou en trichant un peu avec le pipeline graphique, mais les performances sont alors rarement au rendez-vous). Il aurait été facile de corriger cela, mais l'unité géométrique aurait dû avoir deux modes de fonctionnements : un où elle fait du vertex lighting, et un autre où elle renvoie les normales des sommets. Mais la vraie difficulté aurait été la seconde partie de l'algorithme, après l'étape de rastérisation. La séparation de l'algorithme en deux circuits séparés par le circuit de rastérisation aurait été compliquée et c'est pour cette raison que cet algorithme n'a pas été implémenté en hardware sur les anciennes cartes graphiques. La seule manière de l'implémenter proprement est d'utiliser les shaders.

Pour résumer, les unités de calculs géométrique non-programmables des Geforce se limitaient au vertex lighting, rendu avec un algorithme d'illumination de Phong, appliqué triangle par triangle ou sommet par sommet, guère plus. Elles permettaient d'implémenter certains algorithmes d'éclairage assez facilement, mais n'étaient pas assez flexibles pour en implémenter d'autres, comme l'éclairage de Phong. Les algorithmes d'éclairages complexes demandent une coordination entre les calculs géométriques et les calculs après rastérisation, ce qui n'est possible qu'avec les vertex et pixels shaders. C'est pour cela que durant longtemps, les moteurs de jeux vidéo ont préféré utiliser des techniques comme le lightmapping au lieu des unités géométriques non-programmables. La meilleure qualité du per-pixel lighting a eu raison des unités de vertex lighting matérielle.

Conclusion et remarques quant à l'évolution des cartes graphiques[modifier | modifier le wikicode]

Pour résumer, le manque de flexibilité des unités de T&L matérielle faisait qu'elles ne servaient pas à grand chose et ne pouvaient accélérer que des algorithmes d'éclairages géométriques pas très agréables visuellement. Implémenter un grand nombre d'algorithmes directement en hardware aurait eu un cout trop important en termes de transistors et de complexité. Cela a poussé les concepteurs de cartes graphiques à passer aux shaders pour les calculs géométriques et quelques autres opérations.

Tout cela est à l'exact opposé de ce qui s'est passé pour d'autres circuits, comme les circuits pour l'étape de transformation, de rastérisation ou de placage de texture, d'antialiasing, etc. Il n'y a pas 36 façons de rastériser une scène 3D et la flexibilité n'est pas un besoin important pour cette opération, alors que les performances sont cruciales. Même chose pour le placage/filtrage de textures, ou l'antialiasing et les autres opérations du genre. En conséquences, les unités de transformation, de rastérisation et de placage de texture sont toutes implémentées en matériel. Faire ainsi permet de gagner en performance sans que cela ait le moindre impact pour le programmeur.

Le parallèle est assez intéressant à étudier entre les unités géométriques abandonnées et le reste des circuits non-programmables d'une carte graphique. Au final, une carte graphique est un mélange de circuits programmables, et de circuits qui n'ont pas besoin de l'être mais qui doivent être ultra-optimisés pour une opération bien précise et quasi immuable. L'architecture d'une carte graphique est un compromis entre performance et flexibilité, qui est conçu pour répondre au mieux aux exigences du rendu 3D et des logiciels. Est mis en hardware ce qui a besoin d'être performant, est rendu programmable ce qui a besoin de l'être pour le programmeur.