Optimisation à l’aide de MultiMeshes

Pour un grand nombre d'instances (en milliers), qui doivent être traitées en permanence (et qui nécessitent un certain contrôle), utiliser directement les serveurs est l'optimisation recommandée.

Quand la quantité d'objets atteint des centaines de milliers ou même des millions, aucune de ces approches ne reste efficace. Il reste cependant, en fonction des besoins, une optimisation supplémentaire possible.

MultiMeshes

Un MultiMesh est une primitive de dessin unique qui peut dessiner jusqu'à des millions d'objets en une seule fois. Elle est extrêmement efficace car elle utilise le hardware du GPU pour le faire (dans OpenGL ES 2.0, elle est moins efficace car il n'y a pas de support hardware pour elle, cependant).

Le seul inconvénient est qu'il n'y a pas de screen ou frustum culling possible pour les instances individuels. Cela signifie que des millions d'objets seront toujours ou jamais dessinés, en fonction de la visibilité de l'ensemble du MultiMesh. Il est possible de leur fournir un rectangle de visibilité personnalisé, mais il s'agira toujours d'une visibilité tout ou rien.

Si les objets sont suffisamment simples (juste quelques sommets), cela ne pose généralement pas de problème car la plupart des GPU modernes sont optimisés pour ce cas d'utilisation. Une solution de contournement consiste à créer plusieurs MultiMeshes pour différentes régions du monde.

Il est également possible d'exécuter une logique à l'intérieur du shader de sommet (en utilisant les constantes intégrées INSTANCE_ID ou INSTANCE_CUSTOM). Pour un exemple d'animation de milliers d'objets dans un MultiMesh, voir le tutoriel Animation de milliers de poissons. Les informations pour le shader peuvent être fournies par des textures (il existe des formats en virgule flottante Image qui sont idéaux pour cela).

Une autre alternative consiste à utiliser GDNative et C++, ce qui devrait être extrêmement efficace (il est possible de définir l'état complet de tous les objets utilisant la mémoire linéaire via la fonction VisualServer.multimesh_set_as_bulk_array()). Ainsi, le tableau peut être créé avec plusieurs threads, puis réglé(set) en un seul appel, ce qui permet d'obtenir une grande efficacité du cache.

Enfin, il n'est pas nécessaire que toutes les instances MultiMesh soient visibles. Le nombre d'instances visibles peut être contrôlé avec la propriété MultiMesh.visible_instance_count. Le flux de travail typique consiste à allouer le nombre maximal d'instances qui seront utilisées, puis de modifier la quantité visible en fonction de combien sont actuellement nécessaire.

Exemple Multimesh

Voici un exemple d'utilisation d'un MultiMesh depuis du code. Les langages autres que GDScript peuvent être plus efficaces pour des millions d'objets, mais pour quelques milliers, GDScript devrait convenir.

extends MultiMeshInstance


func _ready():
    # Create the multimesh.
    multimesh = MultiMesh.new()
    # Set the format first.
    multimesh.transform_format = MultiMesh.TRANSFORM_3D
    multimesh.color_format = MultiMesh.COLOR_NONE
    multimesh.custom_data_format = MultiMesh.CUSTOM_DATA_NONE
    # Then resize (otherwise, changing the format is not allowed).
    multimesh.instance_count = 10000
    # Maybe not all of them should be visible at first.
    multimesh.visible_instance_count = 1000

    # Set the transform of the instances.
    for i in multimesh.visible_instance_count:
        multimesh.set_instance_transform(i, Transform(Basis(), Vector3(i * 20, 0, 0)))
using System;

public class YourClassName : MultiMeshInstance
{
    public override void _Ready()
    {
        // Create the multimesh.
        Multimesh = new MultiMesh();
        // Set the format first.
        Multimesh.TransformFormat = MultiMesh.TransformFormatEnum.Transform3d;
        Multimesh.ColorFormat = MultiMesh.ColorFormatEnum.None;
        Multimesh.CustomDataFormat = MultiMesh.CustomDataFormatEnum.None;
        // Then resize (otherwise, changing the format is not allowed)
        Multimesh.InstanceCount = 1000;
        // Maybe not all of them should be visible at first.
        Multimesh.VisibleInstanceCount = 1000;

        // Set the transform of the instances.
        for (int i = 0; i < Multimesh.VisibleInstanceCount; i++)
        {
            Multimesh.SetInstanceTransform(i, new Transform(Basis.Identity, new Vector3(i * 20, 0, 0)));
        }
    }
}