Il ne suffit pas de compter les polygones pour optimiser les modèles 3D

image

Après avoir compris les bases du processus de rendu de maillage, vous pouvez appliquer différentes techniques pour optimiser la vitesse de rendu.

Présentation


Combien de polygones puis-je utiliser? C'est une question très courante que les artistes posent lors de la création de modèles pour le rendu en temps réel. Il est difficile de répondre à cette question, car ce n'est pas seulement une question de nombre.

J'ai commencé ma carrière en tant qu'artiste 3D à l'époque de la première PlayStation, puis je suis devenu programmeur graphique. Je voudrais lire cet article avant de commencer à créer des modèles 3D pour les jeux. Les fondements fondamentaux qui y sont considérés sont utiles à de nombreux artistes. Bien que la plupart des informations contenues dans cet article n'affecteront pas de manière significative la productivité de votre travail quotidien, elles vous donneront une compréhension de base de la façon dont l'unité de traitement graphique (GPU) rend les maillages que vous créez.

La vitesse de son rendu dépend généralement du nombre de polygones dans le maillage. Cependant, bien que le nombre de polygones soit souvent en corrélation avec la fréquence d'images par seconde (FPS), vous pouvez constater que même après avoir réduit le nombre de polygones, le maillage s'affiche toujours lentement. Mais en comprenant comment les maillages sont rendus en général, vous pouvez appliquer un ensemble de techniques pour augmenter la vitesse de rendu.

Présentation des données polygonales


Pour comprendre comment le GPU dessine des polygones, vous devez d'abord considérer la structure de données utilisée pour décrire les polygones. Un polygone est constitué d'un ensemble de points appelés sommets et liens. Les sommets sont souvent stockés sous forme de tableaux de valeurs, par exemple, comme la figure 1.


Figure 1. Un tableau de valeurs polygonales simples.

Dans ce cas, quatre sommets en trois dimensions (x, y et z) nous donnent 12 valeurs. Pour créer des polygones, le deuxième tableau de valeurs décrit les sommets eux-mêmes, comme le montre la figure 2.


Figure 2. Un tableau de liens vers les sommets.

Ces sommets connectés ensemble forment deux polygones. Notez que deux triangles, chacun avec trois angles, peuvent être décrits par quatre sommets, car les sommets 1 et 2 sont utilisés dans les deux triangles. Pour que le GPU traite ces données, il est supposé que chaque polygone est triangulaire. Les GPU s'attendent à ce que vous travailliez avec des triangles car ils sont conçus spécifiquement pour les dessiner. Si vous devez dessiner des polygones avec un nombre différent de sommets, vous avez besoin d'une application qui les divise en triangles avant de les rendre au GPU. Par exemple, si vous créez un cube de six polygones, chacun ayant quatre côtés, ce n'est pas plus efficace que de créer un cube de 12 polygones composé de trois côtés; ce sont ces triangles que le GPU dessinera. Rappelez-vous la règle: vous devez compter non pas des polygones, mais des triangles.

Les données de sommet utilisées dans l'exemple précédent sont tridimensionnelles, mais ce n'est pas nécessaire. Deux dimensions peuvent vous suffire, mais vous devez souvent stocker d'autres données, par exemple des coordonnées UV pour les textures et normales pour l'éclairage.

Dessin de polygone


Lors du rendu d'un polygone, le GPU détermine d'abord où dessiner le polygone. Pour ce faire, il calcule la position sur l'écran où les trois sommets doivent être. Cette opération est appelée transformation. Ces calculs dans le GPU sont effectués par un petit programme appelé vertex shader.

Le vertex shader effectue souvent d'autres types d'opérations, telles que le traitement d'animations. Après avoir calculé les positions des trois sommets du polygone, le GPU calcule quels pixels se trouvent dans ce triangle, puis commence à remplir ces pixels avec un autre petit programme appelé «fragment shader» (fragment shader). Un shader de fragment est généralement exécuté une fois par pixel. Cependant, dans certains cas rares, il peut être effectué plusieurs fois par pixel, par exemple, pour améliorer l'anti-crénelage. Les shaders de fragments sont souvent appelés shaders de pixels, car dans la plupart des cas, les fragments correspondent à des pixels (voir figure 3).


Figure 3. Un polygone dessiné à l'écran.

La figure 4 montre la séquence d'actions effectuées par le GPU lors du rendu du polygone.


Figure 4. L'ordre du GPU rendant le polygone.

Si vous divisez le triangle en deux et dessinez les deux triangles (voir figure 5), la procédure correspondra à la figure 6.


Figure 5. Division du polygone en deux.


Figure 6. La procédure du GPU dessinant deux polygones.

Dans ce cas, deux fois plus de transformations et de préparations sont nécessaires, mais comme le nombre de pixels reste le même, l'opération n'a pas besoin de pixelliser des pixels supplémentaires. Cela montre que doubler le nombre de polygones ne double pas nécessairement le temps de rendu.

Utilisation du cache de vertex


Si vous regardez les deux polygones de l'exemple précédent, vous pouvez voir qu'ils ont deux sommets communs. On peut supposer que ces sommets devront être calculés deux fois, mais un mécanisme appelé cache de sommets vous permet de réutiliser les résultats du calcul. Les résultats des calculs de vertex shader pour la réutilisation sont stockés dans le cache - une petite zone de mémoire contenant les derniers sommets. La procédure pour dessiner deux polygones à l'aide du cache de sommets est illustrée à la figure 7.


Figure 7. Dessin de deux polygones à l'aide du cache de sommets.

Grâce au cache de sommets, vous pouvez dessiner deux polygones presque aussi rapidement qu'un s'ils ont des sommets communs.

Nous traitons les paramètres des sommets


Pour que le sommet soit réutilisable, il doit être inchangé à chaque utilisation. Bien sûr, la position doit rester la même, mais les autres paramètres ne doivent pas non plus changer. Les paramètres passés au sommet dépendent du moteur utilisé. Voici deux paramètres communs:

  • Coordonnées de texture
  • Normal

Lorsque des UV sont appliqués à un objet 3D, tout joint créé signifie que les sommets le long du joint ne peuvent pas être partagés. Par conséquent, dans le cas général, les coutures doivent être évitées (voir figure 8).


Figure 8. Texture de suture UV.

Pour un éclairage correct de la surface, chaque sommet stocke généralement une normale - un vecteur dirigé depuis la surface. Du fait que tous les polygones ayant un sommet commun sont définis par une normale, leur forme semble lisse. C'est ce qu'on appelle un ombrage lisse. Si chaque triangle a ses propres normales, alors les arêtes entre les polygones deviennent prononcées et la surface semble plate. Par conséquent, cela s'appelle un ombrage plat. La figure 9 montre deux mailles identiques, l'une avec un ombrage lisse et l'autre avec un ombrage plat.


Figure 9. Comparaison de l'ombrage lisse à plat.

Cette géométrie ombrée lisse se compose de 18 triangles et a 16 sommets communs. L'ombrage plat de 18 triangles nécessite 54 (18 x 3) sommets, car aucun des sommets n'est partagé. Même si deux maillages ont le même nombre de polygones, leur vitesse de rendu sera toujours différente.

Importance de la forme


Les GPU fonctionnent rapidement car ils peuvent effectuer de nombreuses opérations en parallèle. Les supports marketing des GPU se concentrent souvent sur le nombre de pipelines qui déterminent le nombre de GPU pouvant fonctionner en même temps. Lorsque le GPU dessine le polygone, il donne la tâche à de nombreux pipelines de remplir les carrés de pixels. Il s'agit généralement d'un carré de huit par huit pixels. Le GPU continue de le faire jusqu'à ce que tous les pixels soient pleins. Évidemment, les triangles ne sont pas des carrés, donc certains pixels du carré seront à l'intérieur du triangle et d'autres à l'extérieur. L'équipement fonctionne avec tous les pixels d'un carré, même ceux qui sont en dehors du triangle. Après avoir calculé tous les sommets du carré, l'équipement rejette les pixels à l'extérieur du triangle.

La figure 10 montre un triangle, qui nécessite trois carrés (tuiles) pour dessiner. La plupart des pixels calculés (cyan) sont utilisés et ceux affichés en rouge dépassent les limites du triangle et seront supprimés.


Figure 10. Trois tuiles pour dessiner un triangle.

Le polygone de la figure 11 avec exactement le même nombre de pixels, mais étiré, nécessite plus de tuiles à remplir; La plupart des résultats de chaque tuile (zone rouge) seront ignorés.


Figure 11. Remplissage de tuiles dans une image étirée.

Le nombre de pixels rendus n'est qu'un des facteurs. La forme du polygone est également importante. Pour augmenter l'efficacité, essayez d'éviter les polygones longs et étroits et privilégiez les triangles avec des longueurs de côtés approximativement égales, dont les angles sont proches de 60 degrés. Les deux surfaces planes de la figure 12 sont triangulées de deux manières différentes, mais elles se ressemblent lors du rendu.


Figure 12. Surfaces triangulées de deux manières différentes.

Ils ont exactement le même nombre de polygones et de pixels, mais comme la surface de gauche a des polygones plus longs et plus étroits que la droite, son rendu sera plus lent.

Redessiner


Pour dessiner une étoile à six pointes, vous pouvez créer un maillage de 10 polygones ou dessiner la même forme à partir de seulement deux polygones, comme le montre la figure 13.


Figure 13. Deux façons différentes de rendre une étoile à six pointes.

Vous pouvez décider qu'il est plus rapide de dessiner deux polygones que 10. Cependant, dans ce cas, c'est très probablement incorrect, car les pixels au centre de l'étoile seront dessinés deux fois. Ce phénomène est appelé overdraw. En substance, cela signifie que les pixels sont redessinés plus d'une fois. Le redessin se produit naturellement tout au long du processus de rendu. Par exemple, si un caractère est partiellement masqué par une colonne, il sera dessiné dans son intégralité, malgré le fait que la colonne chevauche une partie du caractère. Certains moteurs utilisent des algorithmes complexes pour éviter de rendre des objets invisibles dans l'image finale, mais c'est une tâche difficile. Le CPU est souvent plus difficile à comprendre ce qui n'a pas besoin d'être rendu que le GPU pour le dessiner.

En tant qu'artiste, vous devez accepter le fait que vous ne pouvez pas vous débarrasser de la repeinture, mais c'est une bonne pratique d'enlever les surfaces qui ne sont pas visibles. Si vous collaborez avec une équipe de développement, demandez à ajouter un mode de débogage au moteur de jeu, dans lequel tout devient transparent. Cela facilitera la recherche de polygones cachés pouvant être supprimés.

Mise en place d'un tiroir au sol


La figure 14 montre une scène simple: une boîte posée sur le sol. Le sol se compose de seulement deux triangles et la boîte se compose de 10 triangles. Le redessin de cette scène est affiché en rouge.


Figure 14. Un tiroir posé au sol.

Dans ce cas, le GPU dessinera une partie du sol au sol avec un tiroir, bien qu'il ne soit pas visible. Si à la place, nous avions créé un trou dans le sol sous la boîte, nous aurions reçu plus de polygones, mais beaucoup moins de redessins, comme le montre la figure 15.


Figure 15. Un trou sous le tiroir pour éviter de refaire le dessin.

Dans de tels cas, tout dépend de votre choix. Parfois, cela vaut la peine de réduire le nombre de polygones, d'obtenir un nouveau dessin en retour. Dans d'autres situations, il vaut la peine d'ajouter des polygones pour éviter de les redessiner. Autre exemple: les deux figures ci-dessous sont les mêmes mailles de surface avec des points qui en sortent. Dans le premier maillage (figure 16), les pointes sont situées en surface.


Figure 16. Les embouts sont situés en surface.

Dans le deuxième maillage de la figure 17, des trous sont découpés dans la surface sous les pointes pour réduire la quantité de redessin.


Figure 17. Les trous sont découpés sous les pointes.

Dans ce cas, de nombreux polygones ont été ajoutés pour découper des trous, dont certains ont une forme étroite. De plus, la surface du redessin, dont nous nous sommes débarrassés, n'est pas très grande, donc dans ce cas cette technique est inefficace.

Imaginez que vous modélisez une maison au sol. Pour le créer, vous pouvez soit laisser la terre inchangée, soit percer un trou dans le sol sous la maison. Redessiner est plus lorsque le trou n'est pas découpé sous la maison. Cependant, le choix dépend de la géométrie et du point de vue à partir duquel le joueur verra la maison. Si vous dessinez de la terre sous la base de la maison, cela créera une grande quantité de redessin si vous allez à l'intérieur de la maison et regardez vers le bas. Cependant, la différence ne sera pas particulièrement grande si vous regardez la maison depuis un avion. Dans ce cas, il est préférable d'avoir un mode de débogage dans le moteur de jeu qui rend les surfaces transparentes afin que vous puissiez voir ce qui est dessiné sous les surfaces visibles par le joueur.

Lorsque les tampons Z ont un conflit Z


Lorsque le GPU dessine deux polygones qui se chevauchent, comment détermine-t-il lequel est au-dessus de l'autre? Les premiers chercheurs en infographie ont passé beaucoup de temps à rechercher ce problème. Ed Catmell (qui est devenu plus tard président de Pixar et Walt Disney Animation Studios) a écrit un article qui décrivait dix approches différentes de cette tâche. Dans une partie de l'article, il note que la solution à ce problème sera triviale si les ordinateurs ont suffisamment de mémoire pour stocker une valeur de profondeur par pixel. Dans les années 1970 et 1980, c'était une très grande quantité de mémoire. Cependant, aujourd'hui, la plupart des GPU fonctionnent comme ceci: un tel système est appelé un Z-buffer.

Le Z-buffer (également connu sous le nom de buffer de profondeur) fonctionne comme suit: à chaque pixel sa valeur de profondeur est associée. Lorsque l'équipement dessine un objet, il calcule la distance séparant un pixel de la caméra. Il vérifie ensuite la valeur de profondeur d'un pixel existant. S'il est plus éloigné de l'appareil photo que le nouveau pixel, le nouveau pixel est dessiné. Si un pixel existant est plus proche de l'appareil photo qu'un nouveau, le nouveau pixel n'est pas dessiné. Cette approche résout de nombreux problèmes et fonctionne même si les polygones se croisent.


Figure 18. Polygones entrecroisés traités par un tampon de profondeur.

Cependant, le Z-buffer n'a pas une précision infinie. Si deux surfaces sont presque à la même distance de la caméra, cela confond le GPU et il peut sélectionner au hasard l'une des surfaces, comme le montre la figure 19.


Figure 19. Les surfaces de même profondeur ont des problèmes d'affichage.

Cela s'appelle Z-Fighting et semble très buggé. Souvent, les conflits Z s'aggravent à mesure que la surface de la caméra est éloignée. Les développeurs de moteurs peuvent y incorporer des corrections pour atténuer ce problème, mais si un artiste crée des polygones suffisamment proches et se chevauchant, un problème peut toujours survenir. Un autre exemple est un mur avec une affiche accrochée. L'affiche est située presque à la même profondeur de la caméra que le mur derrière elle, donc le risque de conflits Z est très élevé. La solution consiste à percer un trou dans le mur sous l'affiche. Cela réduira également la quantité de redessin.


Figure 20. Exemple de conflit Z de polygones qui se chevauchent.

Dans les cas extrêmes, un conflit Z peut se produire même lorsque les objets se touchent. La figure 20 montre le tiroir au sol, et comme nous n'avons pas percé de trou dans le sol sous le tiroir, le tampon z peut être confondu à côté du bord où le sol rencontre le tiroir.

Utilisation d'appels de tirage


Les GPU sont devenus extrêmement rapides - si rapides que les CPU peuvent ne pas les suivre. Étant donné que les GPU sont essentiellement conçus pour effectuer une tâche, ils sont beaucoup plus faciles à travailler rapidement. Les graphiques sont intrinsèquement liés au calcul de plusieurs pixels, vous pouvez donc créer un équipement qui calcule plusieurs pixels en parallèle. Cependant, le GPU affiche uniquement ce qu'il ordonne pour dessiner le CPU. Si le CPU ne peut pas «alimenter» rapidement le GPU en données, la carte vidéo sera inactive. Chaque fois que le CPU ordonne au GPU de dessiner quelque chose, cela s'appelle un appel de tirage. L'appel de dessin le plus simple consiste à rendre un maillage, y compris un shader et un ensemble de textures.

Imaginez un processeur lent qui peut transférer 100 appels de dessin par image et un GPU rapide qui peut dessiner un million de polygones par image. Dans ce cas, un appel de tirage idéal peut dessiner 10 000 polygones. Si vos maillages ne contiennent que 100 polygones, le GPU ne pourra dessiner que 10 000 polygones par image. Autrement dit, 99% du temps, le GPU sera inactif. Dans ce cas, on peut facilement augmenter le nombre de polygones dans les mailles sans rien perdre.

La composition de l'appel de tirage et son coût dépendent fortement de moteurs et d'architectures spécifiques. Certains moteurs peuvent combiner plusieurs maillages en un seul appel de dessin (effectuer leur traitement par lots, lot), mais tous les maillages devront avoir le même shader, ou peuvent avoir d'autres restrictions. De nouvelles API comme Vulkan et DirectX 12 sont conçues spécifiquement pour résoudre ce problème en optimisant la façon dont le programme communique avec le pilote graphique, augmentant ainsi le nombre d'appels de tirage qui peuvent être transférés dans une seule trame.

Si votre équipe écrit son propre moteur, demandez aux développeurs du moteur quelles sont les limites des appels d'appel. Si vous utilisez un moteur prêt à l'emploi comme Unreal ou Unity, exécutez des tests de performances pour déterminer les limites des capacités du moteur. Vous pouvez constater que vous pouvez augmenter le nombre de polygones sans provoquer une diminution de la vitesse.

Conclusion


J'espère que cet article vous servira de bonne introduction pour vous aider à comprendre les différents aspects des performances de rendu. Dans les GPU de différents fabricants, tout est mis en œuvre un peu à sa manière. Il existe de nombreuses réservations et conditions spéciales liées à des moteurs et plates-formes matérielles spécifiques. Gardez toujours un dialogue ouvert avec les programmeurs de rendu pour utiliser leurs recommandations dans votre projet.

À propos de l'auteur


Eskil Steenberg est un développeur indépendant de jeux et d'outils, et il travaille en tant que consultant et sur des projets indépendants. Toutes les captures d'écran ont été prises dans des projets actifs à l'aide d'outils développés par Esquil. Vous pouvez en savoir plus sur son travail sur le site Web de Quel Solaar et sur son compte Twitter @quelsolaar.

Source: https://habr.com/ru/post/fr433186/


All Articles