Optimisation du rendu dans Unity : Comprendre et réduire l'Overdraw

Qu'est-ce que l'Overdraw ?

Dans l'univers du développement avec Unity, l'Overdraw (ou sur-dessin) représente l'un des goulots d'étranglement les plus critiques pour le GPU, particulièrement sur les plateformes mobiles. Ce phénomène se produit lorsqu'un même pixel à l'écran est calculé et dessiné plusieurs fois au cours d'une seule et même frame. Plus la densité d'objets superposés est élevée, plus le GPU doit fournir d'efforts pour déterminer la couleur finale de chaque pixel, ce qui sature le taux de remplissage (Fill Rate).

Les causes principales de la surcharge

L'Overdraw est principalement généré par les éléments transparents. Contrairement aux objets opaques qui bénéficient du Z-Buffer (permettant de ne pas dessiner ce qui est caché), les objets transparents (Alpha Blending) obligent le moteur à lire la couleur déjà présente en arrière-plan, à la mélanger avec la nouvelle couleur, puis à écrire le résultat. Les sources courantes incluent :

  • Les systèmes de particules denses (fumée, feu, explosions).
  • Les superpositions d'interfaces utilisateur (UI) complexes.
  • Le feuillage et les herbes utilisant des shaders de type "Transparent".

Visualisation et diagnostic

Unity propose un mode de vue spécifique pour identifier ce problème. Dans l'onglet Scene, en changeant le mode de rendu de "Shaded" à "Overdraw", les zones critiques apparaissent avec une intensité lumineuse élevée. Plus une zone est claire ou blanche, plus le nombre de couches superposées est important, signalant un besoin immédiat d'optimisation.

Stratégies de réduction par le code

1. Consolidation des maillages pour limiter les appels

Bien que le regroupement d'objets transparents doive être manipulé avec précaution à cause de l'ordre de rendu, il est parfois judicieux de fusionner des géométries statiques pour réduire la surcharge administrative du moteur de rendu.


using UnityEngine;
using System.Collections.Generic;

public class MeshOptimizer : MonoBehaviour
{
    public void FusionnerGeometriesTransparentes(GameObject racine)
    {
        MeshFilter[] filtres = racine.GetComponentsInChildren<MeshFilter>();
        List<CombineInstance> instances = new List<CombineInstance>();
        
        foreach (var filtre in filtres)
        {
            MeshRenderer rendu = filtre.GetComponent<MeshRenderer>();
            if (rendu != null && rendu.sharedMaterial.shader.name.Contains("Transparent"))
            {
                CombineInstance ci = new CombineInstance
                {
                    mesh = filtre.sharedMesh,
                    transform = filtre.transform.localToWorldMatrix
                };
                instances.Add(ci);
                filtre.gameObject.SetActive(false);
            }
        }

        GameObject objetFusionne = new GameObject("Maillage_Optimise");
        MeshFilter mf = objetFusionne.AddComponent<MeshFilter>();
        mf.sharedMesh = new Mesh();
        mf.sharedMesh.CombineMeshes(instances.ToArray());
        
        objetFusionne.AddComponent<MeshRenderer>().sharedMaterial = new Material(Shader.Find("Standard"));
    }
}

2. Substitution dynamique de Shader

Une technique efficace consiste à remplacer les shaders transparents par des versions opaques ou "Cutout" (Alpha Test) lorsque la transparence n'est pas strictement nécessaire, car le matériel opaque peut être traité plus efficacement par le GPU.


using UnityEngine;

public class ShaderManager : MonoBehaviour
{
    public void ConvertirEnOpaque(GameObject zoneCible)
    {
        Renderer[] tousLesRendus = zoneCible.GetComponentsInChildren<Renderer>();
        Shader shaderStandard = Shader.Find("Standard");

        foreach (Renderer r in tousLesRendus)
        {
            // Vérification si le shader actuel appartient à la file d'attente transparente
            if (r.sharedMaterial.renderQueue >= 3000)
            {
                Material matOptimise = new Material(shaderStandard);
                // On tente de conserver la texture principale
                if (r.sharedMaterial.HasProperty("_MainTex"))
                {
                    matOptimise.mainTexture = r.sharedMaterial.mainTexture;
                }
                r.material = matOptimise;
            }
        }
    }
}

Bonnes pratiques de conception

Au-delà du code, l'optimisation de l'Overdraw repose sur des choix de design rigoureux :

Optimisation de l'UI : Désactivez l'option "Raycast Target" sur les éléments de texte ou d'image qui ne nécessitent pas d'interaction. Cela évite des calculs inutiles dans la boucle graphique.

Gestion des particules : Réduisez le taux d'émission au profit de textures plus détaillées, et assurez-vous que les particules ne couvrent pas l'intégralité de l'écran avec de multiples couches d'alpha.

Utilisation du Cutout : Pour la végétation, préférez les shaders de type "Alpha Test". Bien qu'ils désactivent l'anticrénelage sur certains bords, ils permettent au GPU d'utiliser le Z-test et d'ignorer les pixels masqués, éliminent ainsi l'Overdraw pour ces surfaces.

Étiquettes: Unity3D optimization GPU shaders rendering

Publié le 25 juillet à 21h10