Continuamos a limpar a memória com three.js

Introdução



Recentemente, escrevi sobre minha experiência de limpeza de memória em um aplicativo usando three.js . Deixe-me lembrá-lo que o objetivo era redesenhar várias cenas com o carregamento de modelos gltf.



Desde então, fiz várias experiências e considero necessário complementar o que disse anteriormente com este pequeno artigo. Aqui estão alguns pontos que me ajudaram a melhorar o desempenho do aplicativo.



Parte principal



Estudando vários exemplos de coleta de lixo em three.js, fiquei interessado na abordagem proposta em threejsfundamentals.org . No entanto, após implementar a configuração proposta e envolver todos os materiais e geometria em this.track (), descobriu-se que a carga na GPU continua a crescer ao carregar novas cenas. Além disso, o exemplo proposto não funciona corretamente com EffectComposer e outras classes para pós-processamento, uma vez que track () não pode ser usado nessas classes.



A solução com a adição de ResourceTracker a todas as classes utilizadas não é atraente, por motivos óbvios, por isso resolvi complementar o método de limpeza da referida classe. Aqui estão algumas das técnicas que foram usadas:



Recepção 1. Áspero



Adicione renderer.info após o método de limpeza. Retiramos recursos do aplicativo um a um para entender quais deles compõem a carga e estão escondidos em texturas ou materiais. Esta não é uma forma de resolver problemas, mas apenas uma forma de depurar que alguém pode não conhecer.



Recepção 2. Longa



Tendo aberto o código da classe usada (por exemplo, AfterimagePass, que pode ser encontrado no github three.js ), olhamos onde os recursos são criados que precisamos limpar para manter o número de geometrias e materiais dentro da estrutura necessária.



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


É disso que você precisa. De acordo com a documentação, o WebGLRenderTarget possui uma função dispose para limpar a memória. Recebemos algo como:



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

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


Recepção 3



Funciona, mas o código de limpeza fica inchado neste caso. Vamos tentar usar a abordagem que conhecemos do artigo anterior. Deixe-me lembrar que nele implementamos o método disposeNode (node), no qual o recurso foi iterado para encontrar o que pode ser limpo. disposeNode () pode ser parecido com isto:



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


Ótimo, agora vamos pegar todas as classes adicionais que usamos e adicionar ao nosso 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]);
    }
}


Resultado



Como resultado de todas essas ações, aumentei significativamente o FPS e reduzi a carga da GPU em meu aplicativo. Posso ter usado o ResourceTracker incorretamente, mas não ajudaria com as classes adicionais de qualquer maneira. Eu não vi em nenhum lugar que iterar sobre EffectComposer por meio de nosso disposeNode (nó) afeta o número de texturas na memória (mas é o caso). Esta questão deve ser considerada separadamente.



Para efeito de comparação, a versão anterior permanecerá no endereço antigo , enquanto a nova pode ser visualizada separadamente .



O projeto está de alguma forma no github .



Eu ficaria feliz em ouvir sua experiência com projetos semelhantes e discutir os detalhes!



All Articles