Seguimos limpiando la memoria con three.js

Introducción



Recientemente escribí sobre mi experiencia al limpiar la memoria en una aplicación usando three.js . Permítanme recordarles que el objetivo era volver a dibujar varias escenas cargando modelos gltf.



Desde entonces, he realizado una serie de experimentos y considero necesario complementar lo dicho anteriormente con este pequeño artículo. Aquí hay algunos puntos que me ayudaron a mejorar el rendimiento de la aplicación.



Parte principal



Al estudiar varios ejemplos de recolección de basura en three.js, me interesó el enfoque propuesto en threejsfundamentals.org . Sin embargo, después de implementar la configuración propuesta y envolver todos los materiales y geometría en this.track (), resultó que la carga en la GPU continúa creciendo al cargar nuevas escenas. Además, el ejemplo propuesto no funciona correctamente con EffectComposer y otras clases para el posprocesamiento, ya que track () no se puede utilizar en estas clases.



La solución con la adición de ResourceTracker a todas las clases utilizadas no es atractiva, por razones obvias, por lo que decidí complementar el método de limpieza de la clase mencionada. Estas son algunas de las técnicas que se han utilizado:



Recepción 1. Áspero



Agregue renderer.info después del método de limpieza. Eliminamos recursos de la aplicación uno a uno para entender cuáles de ellos componen la carga y están ocultos en texturas o materiales. Esta no es una forma de resolver problemas, sino simplemente una forma de depurar que alguien podría no conocer.



Recepción 2. Larga



Habiendo abierto el código de la clase utilizada (por ejemplo, AfterimagePass, que se puede encontrar en el github three.js ), miramos dónde se crean los recursos que necesitamos limpiar para mantener el número de geometrías y materiales dentro del marco requerido.



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


Eso es lo que necesitas. Según la documentación, WebGLRenderTarget tiene una función de eliminación para limpiar la memoria. Obtenemos algo como:



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

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


Recepción 3



Funciona, pero el código de limpieza se hincha en este caso. Intentemos utilizar el enfoque que nos es familiar del artículo anterior. Permítanme recordarles que en él implementamos el método disposeNode (nodo), en el que se repitió el recurso para encontrar lo que se puede limpiar. disposeNode () podría verse así:



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;
        }
    }


Genial, ahora tomemos todas las clases adicionales que usamos y agreguemos a nuestro 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]);
    }
}


Salir



Como resultado de todas estas acciones, aumenté significativamente el FPS y reduje la carga de la GPU en mi aplicación. Puede que haya utilizado el ResourceTracker incorrectamente, pero de todos modos no ayudaría con las clases adicionales. No he visto en ningún lugar que iterar sobre EffectComposer a través de nuestro disposeNode (nodo) afecte la cantidad de texturas en la memoria (pero este es el caso). Este problema debe considerarse por separado.



A modo de comparación, la versión anterior permanecerá en la dirección anterior , mientras que la nueva se puede ver por separado .



El proyecto está de alguna forma en github .



¡Me encantaría escuchar su experiencia con proyectos similares y discutir los detalles!



All Articles