Nous continuons à nettoyer la mémoire avec three.js

introduction



J'ai récemment écrit sur mon expérience de nettoyage de la mémoire dans une application utilisant three.js . Permettez-moi de vous rappeler que le but était de redessiner plusieurs scènes avec le chargement de modèles gltf.



Depuis, j'ai réalisé un certain nombre d'expériences et j'estime nécessaire de compléter ce que j'ai dit plus tôt avec ce petit article. Voici quelques points qui m'ont aidé à améliorer les performances de l'application.



Partie principale



En étudiant divers exemples de garbage collection sur three.js, je me suis intéressé à l'approche proposée sur threejsfundamentals.org . Cependant, après avoir implémenté la configuration proposée et enveloppé tous les matériaux et la géométrie dans this.track (), il s'est avéré que la charge sur le GPU continue d'augmenter lors du chargement de nouvelles scènes. De plus, l'exemple proposé ne fonctionne pas correctement avec EffectComposer et d'autres classes pour le post-traitement, puisque track () ne peut pas être utilisé dans ces classes.



La solution avec l'ajout de ResourceTracker à toutes les classes utilisées n'est pas attrayante, pour des raisons évidentes, j'ai donc décidé de compléter la méthode de nettoyage de la classe mentionnée. Voici quelques-unes des techniques qui ont été utilisées:



Réception 1. Rugueux



Ajoutez renderer.info après la méthode de nettoyage. Nous supprimons les ressources de l'application une par une afin de comprendre lesquelles d'entre elles constituent la charge et sont cachées dans des textures ou des matériaux. Ce n'est pas un moyen de résoudre des problèmes, mais simplement un moyen de déboguer que quelqu'un pourrait ne pas connaître.



Réception 2. Long



Après avoir ouvert le code de la classe utilisée (par exemple, AfterimagePass, qui se trouve sur le github three.js ), nous regardons où sont créées les ressources que nous devons nettoyer afin de maintenir le nombre de géométries et de matériaux dans le cadre requis.



this.textureComp = new WebGLRenderTarget( window.innerWidth, window.innerHeight, { ... }


C'est ce dont vous avez besoin. Selon la documentation, WebGLRenderTarget a une fonction de suppression pour nettoyer la mémoire. Nous obtenons quelque chose comme:



class Scene {
//...
    postprocessing_init(){ //   
        this.afterimagePass = new AfterimagePass(0);
        this.composer.addPass(this.afterimagePass);
    }
//...
}
//...

class ResourceTracker {
//...
    dispose() {
    //...
    sceneObject.afterimagePass.WebGLRenderTarget.dispose();
    //...
    }
}


Réception 3



Fonctionne, mais le code de nettoyage est gonflé dans ce cas. Essayons d'utiliser l'approche qui nous est familière dans l'article précédent. Laissez-moi vous rappeler que nous y avons implémenté la méthode disposeNode (node), dans laquelle la ressource a été recherchée pour ce qui peut être nettoyé. disposeNode () pourrait ressembler à ceci:



disposeNode(node) {
            node.parent = undefined;
            if (node.geometry) {
                node.geometry.dispose();
            }
            let material = node.material;
            if (material) {
                if (material.map) {
                    material.map.dispose();
                }
                if (material.lightMap) {
                    material.lightMap.dispose();
                }
                if (material.bumpMap) {
                    material.bumpMap.dispose();
                }
                if (material.normalMap) {
                    material.normalMap.dispose();
                }
                if (material.specularMap) {
                    material.specularMap.dispose();
                }
                if (material.envMap) {
                    material.envMap.dispose();
                }
                material.dispose();
            }
        } else if (node.constructor.name === "Object3D") {
            node.parent.remove(node);
            node.parent = undefined;
        }
    }


Génial, prenons maintenant toutes les classes supplémentaires que nous avons utilisées et ajoutons à notre ResourceTracker:



dispose() {
    for (let key in sceneObject.afterimagePass) {
        this.disposeNode(sceneObject.afterimagePass[key]);
    }
    for (let key in sceneObject.bloomPass) {
        this.disposeNode(sceneObject.bloomPass[key]);
    }
    for (let key in sceneObject.composer) {
        this.disposeNode(sceneObject.composer[key]);
    }
}


Résultat



À la suite de toutes ces actions, j'ai considérablement augmenté le FPS et réduit la charge du GPU dans mon application. J'ai peut-être utilisé le ResourceTracker de manière incorrecte, mais cela ne m'aiderait pas de toute façon avec des classes supplémentaires. Je n'ai vu nulle part que l'itération sur EffectComposer via notre disposeNode (nœud) affecte le nombre de textures en mémoire (cependant, il en est ainsi). Cette question doit être examinée séparément.



A titre de comparaison, la version précédente restera à l'ancienne adresse , tandis que la nouvelle pourra être consultée séparément .



Le projet est sous une forme ou une autre sur le github .



Je serais heureux d'entendre votre expérience avec des projets similaires et de discuter des détails!



All Articles