optimiser le rendu graphique dans les jeux

Dans cet article, nous allons apprendre ce qui se passe en arrière-plan lorsque Unity rend une frame, quel type de problèmes de performance peut survenir lors du rendu et comment résoudre les problèmes de performance liés au rendu.

Avant de lire cet article, il est essentiel de comprendre qu'il n'existe pas d'approche unique pour améliorer les performances de rendu. Les performances de rendu sont affectées par de nombreux facteurs au sein de notre jeu et sont également fortement dépendantes du matériel et du système d'exploitation sur lequel tourne notre jeu. La chose la plus importante à retenir est que nous résolvons les problèmes de performance en examinant, expérimentant et profilant rigoureusement les résultats de nos expériences.

Cet article contient des informations sur les problèmes de performances de rendu les plus courants, des suggestions sur la manière de les résoudre et des liens vers d'autres articles. Il est possible que votre jeu ait un problème - ou une combinaison de problèmes - qui n'est pas mentionné ici. Cet article, cependant, vous aidera quand même à comprendre votre problème et vous donnera les connaissances et le vocabulaire nécessaires pour trouver une solution efficace.

Une brève introduction au rendu

Avant de commencer, jetons un coup d'oeil rapide et simplifié sur ce qui se passe quand Unity rend une frame. Comprendre le déroulement des événements et les bons termes nous aidera à comprendre, à faire des recherches et à résoudre nos problèmes de performance.

Au niveau le plus élémentaire, le rendu peut être décrit comme suit :

  • L'unité centrale de traitement, appelée CPU, détermine ce qui doit être dessiné et comment le dessiner.
  • Le CPU envoie des instructions à l'unité de traitement graphique, appelée GPU.
  • Le GPU dessine les choses selon les instructions du CPU.

Voyons maintenant de plus près ce qui se passe. Nous reviendrons sur chacune de ces étapes plus en détail plus loin dans l'article, mais pour l'instant, familiarisons nous avec les mots utilisés et comprenons les différents rôles que le CPU et le GPU jouent dans le rendu.

La phrase souvent utilisée pour décrire le rendu est le pipeline de rendu, et c'est une image utile à garder à l'esprit; un rendu efficace consiste à maintenir la circulation de l'information.

Pour chaque frame rendue, le CPU effectue le travail suivant :

  • Le CPU vérifie chaque objet de la scène pour déterminer s'il doit être rendu. Un objet n'est rendu que s'il répond à certains critères; par exemple, une partie de sa boîte de délimitation doit se trouver dans le frustum de vue d'une caméra. On dit que les objets qui ne seront pas rendus sont éliminés. Pour plus d'informations sur l'élimination du frustum et du frustum, veuillez consulter cette page.
  • Le CPU rassemble des informations sur chaque objet qui sera rendu et trie ces données en commandes appelées draw calls. Un draw call contient des données sur un mesh unique et la façon dont ce mesh doit être rendu; par exemple, quelles textures doivent être utilisées. Dans certaines circonstances, les objets qui partagent des options peuvent être combinés dans le même draw call. La combinaison de données pour différents objets dans un même draw call s'appelle la mise en lots (batching).
  • Le CPU crée un paquet de données appelé batch pour chaque draw call. Les batches peuvent parfois contenir des données autres que les draw calls, mais il est peu probable que ces situations contribuent à des problèmes de performance communs et nous n'en tiendrons donc pas compte dans le présent article.

Pour chaque batch qui contient un draw call, le CPU doit maintenant procéder comme suit :

  • Le CPU peut envoyer une commande au GPU pour changer un certain nombre de variables appelées collectivement l'état de rendu. Cette commande est appelée appel SetPass. Un appel SetPass indique au GPU quels paramètres utiliser pour rendre le mesh suivant. Un appel SetPass n'est envoyé que si le mesh suivant nécessite un changement d'état de rendu par rapport au mesh précédent.
  • Le CPU envoie le draw call au GPU. Le draw call indique au GPU de rendre le mesh spécifié en utilisant les paramètres définis dans l'appel SetPass le plus récent.
  • Dans certaines circonstances, plus d'une passe peut être requise pour le batch. Une passe est une section de shader code et une nouvelle passe nécessite un changement à l'état de rendu. Pour chaque passe du batch, l'unité centrale doit envoyer un nouvel appel SetPass puis renvoyer le draw call.

En attendant, le GPU fait le travail suivant :

  • Le GPU gère les tâches du CPU dans l'ordre dans lequel elles ont été envoyées.
  • Si la tâche courante est un SetPass call, le GPU met à jour l'état de rendu.
  • Si la tâche courante est un draw call, le GPU restitue le mesh. Cela se produit par étapes, définies par des sections séparées du shader code. Cette partie du rendu est complexe et nous ne la couvrirons pas dans les moindres détails, mais il est utile de comprendre qu'une section de code appelée le shader vertex indique au GPU comment traiter les vertices du mesh et qu'une section de code appelée shader fragment indique au GPU comment dessiner les pixels individuels.
  • Ce processus se répète jusqu' à ce que toutes les tâches envoyées par le CPU aient été traitées par le GPU.

Maintenant que nous comprenons ce qui se passe quand Unity rend une frame, considérons le genre de problèmes qui peuvent se produire lors du rendu.

Types de problèmes de rendu

La chose la plus importante à comprendre au sujet du rendu est la suivante : tant le CPU que le GPU doivent terminer toutes leurs tâches afin de rendre la frame. Si l'une ou l'autre de ces tâches prend trop de temps, cela entraînera un retard dans le rendu de la frame.

Les problèmes de rendu ont deux causes fondamentales. Le premier type de problème est causé par un pipeline inefficace. Un pipeline inefficace se produit lorsqu'une ou plusieurs des étapes du pipeline de rendu sont trop longues à réaliser, interrompant ainsi le flux régulier de données. Les inefficacités au sein du pipeline sont connues sous le nom de goulots d'étranglement. Le deuxième type de problème est causé par le simple fait d'essayer de faire passer trop de données dans le pipeline. Même le pipeline le plus efficace a une limite à la quantité de données qu'il peut traiter dans une seule frame.

Lorsque notre jeu prend trop de temps à restituer une frame car le processeur met trop de temps à effectuer ses tâches de rendu, notre jeu est ce que l'on appelle le GPU bound.

Comprendre les problèmes de rendu

Il est essentiel que nous utilisions des outils de profilage pour comprendre la cause des problèmes de performance avant d'apporter des changements. Différents problèmes exigent des solutions différentes. Il est également très important que nous mesurions les effets de chaque changement que nous apportons; la résolution des problèmes de performance est un exercice d'équilibre, et l'amélioration d'un aspect de la performance peut avoir un impact négatif sur un autre.

Nous utiliserons deux outils pour nous aider à comprendre et résoudre nos problèmes de performances de rendu: la fenêtre Profiler et le Frame Debugger. Ces deux outils sont intégrés à Unity.

La fenêtre Profiler

La fenêtre Profiler nous permet de voir en temps réel comment notre jeu fonctionne. Nous pouvons utiliser la fenêtre Profiler pour voir des données sur de nombreux aspects de notre jeu, y compris l'utilisation de la mémoire, le pipeline de rendu et les performances des scripts utilisateur.

Si vous n'êtes pas encore familiarisé avec l'utilisation de la fenêtre Profiler, cette page du Manuel d'Unity est une bonne introduction et ce tutoriel montre comment l'utiliser en détail.

Le Frame Debugger

Le Frame Debugger nous permet de voir comment une frame est rendu, étape par étape. À l'aide du Frame Debugger, nous pouvons voir des informations détaillées telles que ce qui est dessiné pendant chaque draw call, les propriétés des shaders pour chaque draw call et l'ordre des événements envoyés au GPU. Ces informations nous aident à comprendre comment notre jeu est rendu et où nous pouvons améliorer la performance.

Trouver la cause des problèmes de performance

Avant d'essayer d'améliorer les performances de rendu de notre jeu, nous devons être sûrs que notre jeu fonctionne lentement en raison de problèmes de rendu. Il ne sert à rien d'essayer d'optimiser nos performances de rendu si la vraie cause de notre problème est des scripts utilisateur trop complexes ! Si vous n'êtes pas sûr que vos problèmes de performance sont liés au rendu, vous devriez suivre ce tutoriel.

Une fois que nous avons établi que nos problèmes sont liés au rendu, nous devons également comprendre si notre jeu est lié au CPU ou au GPU. Ces différents problèmes exigent des solutions différentes, il est donc essentiel que nous comprenions la cause du problème avant d'essayer de le résoudre.

Si vous êtes certains que vos problèmes sont liés au rendu et que vous savez si votre jeu est lié au CPU ou au GPU, vous êtes prêts à lire la suite.

Si votre jeu est lié au CPU

D'une manière générale, le travail qui doit être effectué par le CPU pour rendre une frame est divisé en trois catégories :

  • Déterminer ce qui doit être dessiné
  • Préparation des commandes pour le GPU
  • Envoi de commandes au GPU

Ces grandes catégories contiennent de nombreuses tâches individuelles, et ces tâches peuvent être exécutées à travers de multiples threads. Les threads permettent à des tâches séparées de se produire simultanément; tandis qu'un thread exécute une tâche, un autre thread peut exécuter une tâche complètement séparée. Cela signifie que le travail peut être fait plus rapidement. Lorsque les tâches de rendu sont divisées à travers des threads séparés, ceci est connu sous le nom de rendu multithreaded.

Il y a trois types de threads impliqués dans le processus de rendu d'Unity: le thread principal, le thread de rendu et les threads ouvriers. Le fil conducteur principal est l'endroit où se déroulent la majorité des tâches CPU pour notre jeu, y compris certaines tâches de rendu. Le thread de rendu est un thread spécialisé qui envoie des commandes au GPU. Les threads ouvriers effectuent chacun une seule tâche, comme le culling ou le mesh skinning. Quelles tâches sont exécutées par quel thread dépend des paramètres de notre jeu et le matériel sur lequel tourne notre jeu. Par exemple, plus il y a de cœurs de CPU dans notre matériel cible, plus il y a de threads ouvriers. Pour cette raison, il est très important de profiler notre jeu sur le matériel cible; notre jeu peut être très différent sur différents appareils.

Parce que le rendu multithreading est complexe et dépend du matériel, nous devons comprendre quelles tâches sont à l'origine de notre jeu pour être lié au CPU avant d'essayer d'améliorer les performances. Si votre jeu fonctionne lentement parce que les opérations de culling prennent trop de temps sur un thread, cela ne nous aidera pas à réduire le temps qu'il faut pour envoyer des commandes au GPU sur un thread différent.

Toutes les plates-formes ne supportent pas le rendu multithreading; au moment de la rédaction, WebGL ne supporte pas cette fonctionnalité. Sur les plates-formes qui ne supportent pas le rendu multithreading, toutes les tâches CPU sont exécutées sur le même thread. Si nous sommes liés au CPU sur une telle plate-forme, l'optimisation de n'importe quel travail CPU améliorera les performances du CPU. Si c'est le cas pour votre jeu, vous devriez lire toutes les sections suivantes et considérer quelles optimisations peuvent être les plus appropriées pour votre jeu.

Graphics jobs

L'option Graphics jobs du Player Settings détermine si Unity utilise des threads ouvriers pour effectuer des tâches de rendu qui seraient autrement effectuées sur le thread principal et, dans certains cas, sur le thread de rendu. Sur les plates-formes où cette fonctionnalité est disponible, elle permet d'améliorer considérablement les performances. Si vous souhaitez utiliser cette fonctionnalité, vous devriez profiler votre jeu avec et sans les Graphics jobs activés et observer l'effet qu'il a sur la performance.

Envoi de commandes au GPU

Le temps nécessaire pour envoyer des commandes au GPU est la raison la plus courante pour laquelle un jeu doit être lié au CPU. Cette tâche est exécutée sur le thread de rendu sur la plupart des plates-formes, bien que sur certaines plates-formes (par exemple, PlayStation 4) elle puisse être exécutée par des threads ouvriers.

L'opération la plus coûteuse qui se produit lorsque vous envoyez des commandes au GPU sont les appels SetPass. Si notre jeu est lié au CPU par l'envoi de commandes au GPU, la réduction du nombre d'appels SetPass est probablement la meilleure façon d'améliorer les performances.

Nous pouvons voir combien d'appels SetPass et de batches sont envoyés dans le Rendering profiler de la fenêtre Profiler d'Unity. Le nombre d'appels SetPass qui peuvent être envoyés avant que les performances ne diminuent dépend beaucoup du matériel cible; un PC haut de gamme peut envoyer beaucoup plus d'appels SetPass avant que les performances ne diminuent par rapport à un appareil mobile.

Le nombre d'appels SetPass et sa relation avec le nombre de batches dépend de plusieurs facteurs, et nous aborderons ces sujets plus en détail plus loin dans l'article.

Cependant :

  • La réduction du nombre de batches et/ou la création de plusieurs objets partageant le même état de rendu réduira, dans la plupart des cas, le nombre d'appels SetPass.
  • La réduction du nombre d'appels SetPass améliorera, dans la plupart des cas, les performances du CPU.

Si la réduction du nombre de batches ne réduit pas le nombre d'appels SetPass, cela peut tout de même conduire à des améliorations de performance. Ceci est dû au fait que le CPU peut traiter plus efficacement un seul batch que plusieurs batches , même s'ils contiennent la même quantité de données de mesh.

Il existe trois manières de réduire le nombre de batches et d'appels SetPass :

  • La réduction du nombre d'objets à afficher réduira probablement les batches et les appels SetPass.
  • La réduction du nombre de fois que chaque objet doit être rendu réduit généralement le nombre d'appels SetPass.
  • La combinaison des données des objets qui doivent être rendus en moins de batches réduira le nombre de batches.

Différentes techniques seront appropriées pour différents jeux, donc nous devrions considérer toutes ces options, décider lesquelles pourraient fonctionner dans notre jeu et expérimenter.

Réduire le nombre d'objets rendus

Réduire le nombre d'objets à rendre est la façon la plus simple de réduire le nombre de batches et d'appels SetPass. Il existe plusieurs techniques que nous pouvons utiliser pour réduire le nombre d'objets rendus.

  • Réduire simplement le nombre d'objets visibles dans notre scène peut être une solution efficace. Si, par exemple, nous reproduisons un grand nombre de caractères différents dans une foule, nous pouvons expérimenter en ayant simplement moins de ces caractères dans la scène. Si la scène semble toujours belle et que les performances s'améliorent, ce sera probablement une solution beaucoup plus rapide que des techniques plus sophistiquées.
  • Nous pouvons réduire la distance de dessin de notre caméra en utilisant la propriété Far Clip Plane de la caméra. Cette propriété est la distance au-delà de laquelle les objets ne sont plus rendus par la caméra. Si nous voulons masquer le fait que les objets éloignés ne sont plus visibles, nous pouvons essayer d'utiliser le brouillard pour cacher le manque d'objets éloignés.
  • Pour une approche plus fine de la dissimulation d'objets basée sur la distance, nous pouvons utiliser la propriété Layer Cull Distances de notre caméra pour fournir des distances d'élimination personnalisées pour les objets qui sont sur des couches séparées. Cette approche peut être utile si nous avons beaucoup de petits détails décoratifs à l'avant-plan; nous pourrions cacher ces détails à une distance beaucoup plus courte que les éléments de terrain.
  • Nous pouvons utiliser une technique appelée élimination d'occlusion pour désactiver le rendu des objets qui sont cachés par d'autres objets. Par exemple, s'il y a un grand bâtiment dans notre scène, nous pouvons utiliser l'abattage par occlusion pour désactiver le rendu des objets derrière. L'élimination d'occlusion d'Unity n'est pas adaptée à toutes les scènes, elle peut entraîner des coûts généraux de CPU supplémentaires et peut être complexe à mettre en place, mais elle peut grandement améliorer les performances dans certaines scènes. En plus d'utiliser l'élimination d'occlusion d'Unity, nous pouvons également implémenter notre propre méthode d'élimination d'occlusion en désactivant manuellement les objets que nous savons qu'ils ne sont pas vus par le joueur. Par exemple, si notre scène contient des objets qui sont utilisés pour une scène mais qui ne sont pas visibles avant ou après, nous devrions les désactiver. Utiliser notre connaissance de notre propre jeu est toujours plus efficace que de demander à Unity de travailler dynamiquement.

Réduire le nombre de fois que chaque objet doit être rendu

L'éclairage, les ombres et les reflets en temps réel ajoutent beaucoup de réalisme aux jeux, mais peuvent être très coûteux. L'utilisation de ces fonctionnalités peut conduire à des objets à rendre plusieurs fois, ce qui peut avoir un impact considérable sur les performances.

L'impact exact de ces fonctionnalités dépend du chemin de rendu que nous choisissons pour notre jeu. Le chemin de rendu est le terme pour l'ordre dans lequel les calculs sont effectués lors du dessin de la scène, et la principale différence entre les chemins de rendu est la façon dont ils traitent les lumières en temps réel, les ombres et les réflexions. En règle générale, le rendu différé est un meilleur choix si notre jeu fonctionne sur du matériel haut de gamme et utilise beaucoup de lumières, d'ombres et de réflexions en temps réel. Forward Rendering est probablement plus approprié si notre jeu fonctionne sur du matériel bas de gamme et n'utilise pas ces fonctionnalités. Toutefois, il s'agit d'une question très complexe et si nous souhaitons utiliser des lumières, des ombres et des réflexions en temps réel, il est préférable de faire des recherches sur le sujet et d'expérimenter.

Cette page du Manuel de Unity donne plus d'informations sur les différents chemins de rendu disponibles dans Unity et est un point de départ utile.

Quel que soit le chemin de rendu choisi, l'utilisation de lumières, d'ombres et de réflexions en temps réel peut avoir un impact sur les performances de notre jeu et il est important de comprendre comment les optimiser.

  • L'éclairage dynamique dans Unity est un sujet très complexe et sa discussion en profondeur dépasse le cadre de cet article.
  • L'éclairage dynamique coûte cher. Lorsque notre scène contient des objets qui ne bougent pas, comme des paysages, nous pouvons utiliser une technique appelée called baking pour pré-calculer l'éclairage de la scène de sorte que les calculs d'éclairage en cours d'exécution ne sont pas nécessaires.
  • Si nous voulons utiliser des ombres en temps réel dans notre jeu, c'est probablement un domaine où nous pouvons améliorer les performances.
  • Les sondes de réflexion créent des réflexions réalistes mais peuvent être très coûteuses en termes de batches. Il est préférable de réduire au minimum l'utilisation des réflexions lorsque la performance est un souci, et de les optimiser autant que possible là où elles sont utilisées.

Combinaison d'objets en moins de batches

Un batch peut contenir les données pour plusieurs objets lorsque certaines conditions sont remplies. Pour être admissibles au groupage, les objets doivent :

  • Partager la même instance du même matériau
  • Avoir des réglages de matériaux identiques (c. -à-d. texture, shader et paramètres de shader)

Le batching d'objets admissibles peut améliorer les performances, bien que, comme pour toutes les techniques d'optimisation, nous devions établir avec soin un profil afin de nous assurer que le coût de le batching n'excède pas les gains de performance.

Il existe quelques techniques différentes pour grouper les objets admissibles :

  • Le Static batching est une technique qui permet à Unity de traiter par batch des objets éligibles proches qui ne bougent pas. Un bon exemple de quelque chose qui pourrait bénéficier d'un static batching est une pile d'objets similaires, comme des blocs rocheux. Cette page du Manuel d'Unity contient des instructions sur la mise en place d'un static batching dans notre jeu. Le static batching peut conduire à une plus grande utilisation de la mémoire, nous devons donc garder ce coût à l'esprit lors de l'établissement du profil de notre jeu.
  • Le Dynamic batching est une autre technique qui permet à Unity de traiter par batch des objets admissibles, qu'ils bougent ou non. Il y a quelques restrictions sur les objets qui peuvent être batched à l'aide de cette technique. Le Dynamic batching a un impact sur l'utilisation du CPU, ce qui peut lui faire perdre plus de temps qu'il n'en économise. Nous devrions garder ce coût à l'esprit lorsque nous expérimentons cette technique et être prudents avec son utilisation.
  • Le Batching Unity's UI elements est un peu plus complexe, car il peut être affecté par la disposition de notre interface utilisateur.
  • L'instanciation GPU est une technique qui permet de doser très efficacement un grand nombre d'objets identiques. Il y a des limites à son utilisation et ce n'est pas supporté par tout le matériel, mais si notre jeu a de nombreux objets identiques à la fois, nous pouvons bénéficier de cette technique.
  • L'atlas des textures est une technique où plusieurs textures sont combinées en une texture plus grande. Il est couramment utilisé dans les jeux 2D et les systèmes d'interface utilisateur, mais peut également être utilisé dans les jeux 3D. Si nous utilisons cette technique lors de la création d'art pour notre jeu, nous pouvons nous assurer que les objets partagent des textures communes et sont donc éligibles pour le groupage. Unity est doté d'un atlas de textures intégré appelé Sprite Packer pour les jeux 2D.
  • Il est possible de combiner manuellement des meshes qui partagent le même matériau et la même texture, soit dans l'éditeur Unity Editor, soit par code lors de l'exécution. Lorsque vous combinez des meshes de cette manière, nous devons être conscients que les ombres, l'éclairage et l'abattage fonctionneront toujours au niveau par objet; cela signifie qu'une augmentation des performances de la combinaison de meshes pourrait être contrecarrée en ne pouvant plus éliminer ces objets alors qu'ils n'auraient pas été rendu. Si nous voulons étudier cette approche, nous devrions examiner la fonction Mesh.CombineMeshes. Le script CombineChildren du package Unity's Standard Assets est un exemple de cette technique.
  • Nous devons être très prudents lorsque nous accédons à Renderer.material dans les scripts. Ceci duplique le matériel et renvoie une référence à la nouvelle copie. Cela brisera le batching si le rendu faisait partie d'un batch parce que le rendu n'a plus de référence à la même instance du matériau. Si nous souhaitons accéder au contenu d'un objet batched dans un script, nous devons utiliser Renderer.sharedMaterial.

Culling, triage et batching

Le culling, la collecte de données sur les objets qui seront dessinés, le tri de ces données en lots et la génération de commandes GPU peuvent tous contribuer à être liés au CPU. Ces tâches seront effectuées soit sur le thread principal, soit sur des threads de travail individuels, en fonction des paramètres de notre jeu et du matériel cible.

  • Le culling n'est probablement pas très coûteux en soi, mais la réduction des culling inutiles peut contribuer au rendement. Tous les objets de scène actifs, même ceux qui se trouvent sur des calques qui ne sont pas rendus, ont un plafond par objet par caméra. Pour réduire cela, nous devrions désactiver les caméras ou désactiver les moteurs de rendu qui ne sont pas utilisés actuellement.
  • Le batching peut grandement améliorer la vitesse d'envoi des commandes vers le GPU, mais elle peut parfois ajouter des surcharges inutiles ailleurs. Si les opérations de batching contribuent à ce que notre jeu soit lié au CPU, nous pouvons limiter le nombre d'opérations de batching manuelles ou automatiques dans notre jeu.

Skinned meshes

SkinnedMeshRenderers sont utilisés lorsque nous animons un mesh en le déformant à l'aide d'une technique appelée animation osseuse. Il est le plus communément utilisé dans les personnages animés. Les tâches liées au rendu des Skinned meshes seront généralement effectuées sur le thread principal ou sur des fils de threads individuels, en fonction des paramètres de notre jeu et du matériel cible.

Rendre des Skinned meshes peut être une opération coûteuse. Si nous pouvons voir dans la fenêtre Profiler que le rendu des Skinned meshes contribue à ce que notre jeu soit relié au CPU, il y a quelques choses que nous pouvons essayer d'améliorer :

  • Nous devrions examiner si nous devons utiliser les composants SkinnedMeshRenderer pour chaque objet qui en utilise un. Il se peut que nous ayons importé un modèle qui utilise un composant SkinnedMeshRenderer mais que nous ne l'animons pas réellement, par exemple. Dans un cas comme celui-ci, le remplacement du composant SkinnedMeshRenderer par un composant MeshRenderer améliorera les performances. Lors de l'importation de modèles dans Unity, si nous choisissons de ne pas importer d'animations dans les paramètres d'importation du modèle, le modèle aura un MeshRenderer au lieu d'un SkinnedMeshRenderer.
  • Si nous animons notre objet seulement une partie du temps (par exemple, seulement au démarrage ou seulement quand il se trouve à une certaine distance de la caméra), nous pourrions changer son mesh pour une version moins détaillée ou son composant SkinnedMeshRenderer pour un composant MeshRenderer. Le composant SkinnedMeshRenderer a une fonction BakeMesh qui peut créer un mesh dans une pose correspondante, ce qui est utile pour permuter entre différents meshes ou rendus sans aucune modification visible de l'objet.
  • Cette page du manuel Unity contient des conseils sur l'optimisation des caractères animés qui utilisent des skinned meshes, et la page du manuel Unity sur le composant SkinnedMeshRenderer inclut des améliorations qui peuvent améliorer les performances. En plus des suggestions sur ces pages, il convient de garder à l'esprit que le coût du skinning mesh augmente par vertex; donc en utilisant moins de vertices dans nos modèles nous réduisons la quantité de travail qui doit être fait.
  • Sur certaines plateformes, skinning peut être géré par le GPU plutôt que par le processeur. Cette option peut valoir la peine d'être expérimentée si nous avons beaucoup de capacité sur le GPU. Nous pouvons activer le skinning GPU pour la plate-forme actuelle et la cible de qualité dans les paramètres du lecteur.

Opérations de thread principales non liées au rendu

Il est important de comprendre que de nombreuses tâches CPU non liées au rendu se déroulent sur le thread principal. Cela signifie que si nous sommes liés au processeur principal, nous pouvons améliorer les performances en réduisant le temps CPU consacré aux tâches non liées au rendu.

Par exemple, notre jeu peut effectuer des opérations de rendu coûteuses et des opérations de script utilisateur coûteuses sur le thread principal à un certain point dans notre jeu, ce qui nous rend le CPU lié. Si nous avons optimisé autant que possible les opérations de rendu sans perte de fidélité visuelle, il est possible que nous soyons en mesure de réduire le coût CPU de nos propres scripts pour améliorer les performances.

Si notre jeu est lié au GPU

La première chose à faire si notre jeu est lié au GPU est de savoir ce qui cause le goulot d'étranglement du GPU. Les performances du GPU sont le plus souvent limitées par le taux de remplissage, en particulier sur les appareils mobiles, mais la bande passante de la mémoire et le traitement des vertex peuvent également poser problème. Examinons chacun de ces problèmes et apprenons quelles sont les causes, comment le diagnostiquer et comment le réparer.

Taux de remplissage

Le taux de remplissage fait référence au nombre de pixels que le GPU peut afficher à l'écran chaque seconde. Si notre jeu est limité par le taux de remplissage, cela signifie que notre jeu essaie de dessiner plus de pixels par image que ce que le GPU peut gérer.

Il est simple de vérifier si le taux de remplissage fait que notre jeu est lié au GPU:

  • Profiter le jeu et noter le temps GPU.
  • Diminuez la résolution d'affichage dans le Player Settings.
  • Profiler le jeu à nouveau. Si la performance s'est améliorée, il est probable que le taux de remplissage soit le problème.

Si le taux de remplissage est la cause de notre problème, il existe quelques approches qui peuvent nous aider à résoudre le problème.

Les shaders de fragmentation sont les sections du code de shader qui indiquent au GPU comment dessiner un seul pixel. Ce code est exécuté par le GPU pour chaque pixel qu'il doit dessiner, donc si le code est inefficace, les problèmes de performances peuvent facilement s'empiler. Les shaders fragmentés complexes sont une cause très fréquente de problèmes de taux de remplissage.

  • Si notre jeu utilise des shaders intégrés, nous devrions viser à utiliser les shaders les plus simples et les plus optimisés possibles pour l'effet visuel que nous voulons. À titre d'exemple, les shaders mobiles livrés avec Unity sont hautement optimisés; nous devrions essayer de les utiliser et voir si cela améliore les performances sans affecter l'apparence de notre jeu. Ces shaders ont été conçus pour être utilisés sur des plates-formes mobiles, mais ils conviennent à tous les projets. Il est parfaitement possible d'utiliser des shaders "mobiles" sur des plates-formes non mobiles pour augmenter les performances s'ils donnent la fidélité visuelle requise pour le projet.
  • Si les objets de notre jeu utilisent le Shader standard d'Unity, il est important de comprendre que Unity compile ce shader en fonction des paramètres actuels du matériau. Seules les fonctionnalités actuellement utilisées sont compilées. Cela signifie que la suppression de fonctionnalités telles que les cartes détaillées peut aboutir à un code de shader de fragment beaucoup moins complexe, ce qui peut grandement améliorer les performances. Encore une fois, si c'est le cas dans notre jeu, nous devrions expérimenter avec les paramètres et voir si nous sommes en mesure d'améliorer les performances sans affecter la qualité visuelle.
  • Si notre projet utilise des shaders sur mesure, nous devrions viser à les optimiser autant que possible.

Overdraw est le terme pour quand le même pixel est dessiné plusieurs fois. Cela se produit lorsque des objets sont dessinés au-dessus d'autres objets et contribue grandement à résoudre les problèmes de taux. Pour comprendre l'overdraw, nous devons comprendre l'ordre dans lequel Unity dessine des objets dans la scène. Le shader d'un objet détermine son ordre de dessin, généralement en spécifiant dans quelle file de rendu se trouve l'objet. Unity utilise cette information pour dessiner des objets dans un ordre strict, comme détaillé sur cette page du manuel Unity. En outre, les objets dans différentes files d'attente de rendu sont triés différemment avant d'être dessinés. Par exemple, Unity trie les éléments d'avant en arrière dans la file d'attente Géométrie pour minimiser les recouvrements, mais trie les objets en avant-plan dans la file d'attente Transparent pour obtenir l'effet visuel requis. Ce tri inversé a pour effet de maximiser le recouvrement des objets dans la file d'attente transparente. L'overdraw est un sujet complexe et il n'y a pas d'approche unique pour résoudre les problèmes de recouvrement, mais réduire le nombre d'objets qui se chevauchent que Unity ne peut pas trier automatiquement est la clé. Le meilleur endroit pour commencer à étudier ce problème est en mode Scène Unity; il y a un mode Draw qui nous permet de voir un overdraw dans notre scène et, à partir de là, d'identifier où nous pouvons travailler pour le réduire. Les causes les plus courantes d'une surcharge excessive sont les matériaux transparents, les particules non optimisées et les éléments d'interface utilisateur qui se chevauchent. Nous devrions donc essayer d'optimiser ou de réduire ces éléments.

L'utilisation d'effets d'image peut grandement contribuer aux problèmes de taux de remplissage, en particulier si nous utilisons plus d'un effet d'image. Si notre jeu utilise des effets d'image et est confronté à des problèmes de taux de remplissage, nous souhaitons expérimenter avec différents paramètres ou des versions plus optimisées des effets d'image (tels que Bloom (Optimisé) à la place de Bloom). Si notre jeu utilise plus d'un effet d'image sur la même caméra, cela entraînera plusieurs passes de shader. Dans ce cas, il peut être avantageux de combiner le code shader de nos effets d'image en un seul passage, comme dans PostProcessing Stack d'Unity. Si nous avons optimisé nos effets d'image et que nous rencontrons toujours des problèmes de taux de remplissage, nous devrons peut-être envisager de désactiver les effets d'image, en particulier sur les périphériques d'entrée de gamme.

Bande passante mémoire

La bande passante mémoire se réfère à la vitesse à laquelle le GPU peut lire et écrire dans sa mémoire dédiée. Si notre jeu est limité par la bande passante mémoire, cela signifie généralement que nous utilisons des textures trop grandes pour que le GPU puisse les gérer rapidement.

Pour vérifier si la bande passante mémoire est un problème, nous pouvons faire ce qui suit :

  • Profiter le jeu et noter le temps GPU.
  • Réduire la qualité de la texture pour la plate-forme actuelle et la cible de qualité dans le Quality Settings.
  • Profiler le jeu à nouveau. Si les performances se sont améliorées, il est probable que la bande passante mémoire soit le problème.

Si la bande passante mémoire est notre problème, nous devons réduire l'utilisation de la mémoire de texture dans notre jeu. Encore une fois, la technique qui fonctionne le mieux pour chaque jeu sera différente, mais il y a plusieurs façons d'optimiser nos textures.

  • La compression de texture est une technique qui peut réduire considérablement la taille des textures sur le disque et dans la mémoire. Si la bande passante mémoire est un problème dans notre jeu, l'utilisation de la compression de texture pour réduire la taille des textures en mémoire peut aider les performances. Il existe de nombreux formats et paramètres de compression de texture disponibles dans Unity, et chaque texture peut avoir des paramètres distincts. En règle générale, une forme de compression de texture doit être utilisée autant que possible; Cependant, une approche d'essai et d'erreur pour trouver le meilleur réglage pour chaque texture fonctionne le mieux.
  • Les Mipmaps sont des versions à résolution inférieure des textures que Unity peut utiliser sur des objets distants. Si notre scène contient des objets éloignés de la caméra, nous pourrons utiliser des mipmaps pour atténuer les problèmes de bande passante mémoire. Le mode Dessin de Mipmaps en mode Scène nous permet de voir quels objets de notre scène pourraient bénéficier de Mipmaps, et cette page du Manuel de Unity contient plus d'informations sur l'activation de Mipmaps pour les textures.

Traitement des vertex

Le traitement des vertex fait référence au travail que le GPU doit effectuer pour rendre chaque vertex dans un mesh. Le coût du traitement des vertex est influencé par deux choses: le nombre de vertices à rendre et le nombre d'opérations à effectuer sur chaque sommet.

Si notre jeu est lié au GPU et que nous avons établi qu'il n'est pas limité par le taux de remplissage ou la bande passante mémoire, il est probable que le traitement des vertex soit la cause du problème. Si tel est le cas, expérimenter avec la réduction de la quantité de traitement de vertex que le GPU doit faire est susceptible d'entraîner des gains de performance.

Il y a quelques approches que nous pourrions envisager pour nous aider à réduire le nombre de vertex ou le nombre d'opérations que nous effectuons sur chaque vertex.

  • Premièrement, nous devrions viser à réduire toute complexité de mesh inutile. Si nous utilisons des meshes qui ont un niveau de détail qui ne peut pas être vu dans le jeu, ou des meshes inefficaces qui ont trop de vertex en raison d'erreurs dans leur création, c'est un travail inutile pour le GPU. Le moyen le plus simple de réduire le coût du traitement des vertex est de créer des meshes avec un nombre de vertex plus faible dans notre programme d'art 3D.
  • Nous pouvons expérimenter avec une technique appelée mapping normal, où les textures sont utilisées pour créer l'illusion d'une plus grande complexité géométrique sur un mesh. Bien qu'il y ait une surcharge de GPU pour cette technique, cela entraînera dans de nombreux cas un gain de performance.
  • Si un mesh de notre jeu n'utilise pas le mappage normal, nous pouvons souvent désactiver l'utilisation de tangentes de vertex pour ce mesh dans les paramètres d'importation du mesh. Cela réduit la quantité de données envoyées au GPU pour chaque vertex.
  • Le niveau de détail, également connu sous le nom de LOD, est une technique d'optimisation où les meshes éloignées de la caméra sont réduites en complexité. Cela réduit le nombre de vertex que le GPU doit restituer sans affecter la qualité visuelle du jeu.
  • Les shaders Vertex sont des blocs de code shader qui indiquent au GPU comment dessiner chaque vertex. Si notre jeu est limité par le traitement des vertex, alors réduire la complexité de nos vertex shaders peut aider.

Conclusion

Nous avons appris comment le rendu fonctionne dans Unity, quels types de problèmes peuvent survenir lors du rendu et comment améliorer les performances de rendu dans notre jeu. En utilisant ces connaissances et nos outils de profilage, nous pouvons résoudre les problèmes de performances liés au rendu et à la structure de nos jeux afin qu'ils aient un pipeline de rendu fluide et efficace.

les réactions

Pour laisser un avis, vous devez être inscrit et connecté

Se connecter