Как стать автором
Обновить

Опыт создания нескольких 3д сцен без перезагрузки страницы (three.js)

Время на прочтение4 мин
Количество просмотров4.3K

Что делаю


Для личного проекта потребовалось найти решение, для работы с некоторым количеством разных gltf моделей.


Небольшой опыт работы с three.js у меня был, поэтому, после ознакомления с примерами GLTFLoader, была выбрана именно эта библиотека.


Данные о сценах хранятся в отдельном json файле и содержат информацию о пути к сцене, освещении, маршруте камеры и т.д. но в данном материале они нас интересуют в меньшей мере.


В теории было необходимо рисовать подряд несколько сцен. На практике все оказалось несколько сложней.


Как делаю


Камера


Первая трудность заключалась в построении маршрута для движения камеры. Из требований к
камере и передвижению:


  • Камера должна двигаться по ключевым точкам, достраивая промежуточные;
  • камера должна зависеть от скролла колеса мышки (передвигаясь вперед и назад, соответственно);
  • камера должна плавно «тормозить» и сглаживать свое движение.

Для построения и интерполяции пути подошел CatmullRomCurve3 с методом getPoint(), который строил достаточно плавную кривую. Остальные классы кривых, такие как Curve или CubicBezierCurve3 строили промежуточные точки не достаточно плавно. Это следует учитывать.


Привязка скролла мыши к движению камеры достаточно проста. Определив направление скролла, изменяем текущее положение глобальной переменной (прибавкой или вычетом дельты). Полученное значение делим на расстояние от начального положения камеры до ее центра (по иксу). Это связанно с тем, что в моем случае сцена начинается на некотором удалении от центра, а событие перезагрузки модели срабатывает, когда координата камеры (по иксу) ровна нулю. Решение не универсальное, но я придерживался такого подхода, поскольку именно так задумывал проект.


Дополнительная задача была связана и с вращением камеры. Для этого использовался TrackballControls с начальным фокусом в точке (0, 0, 0). При нажатии на кнопки управления (W, S, D, A в данном случае), фокус можно было, аналогично движению по кривой, смягчить (в обоих случая использовал дополнительные таймеры).


Память


Реализовав несколько подряд идущих сцен обратил внимание, что fps с каждой новой подгрузкой падает. Очевидно, где-то происходит утечка памяти. Делаю снапшоты для нескольких сцен. Подчищаю некоторые мелочи. Проверяю результат.




Не выглядит, как проблема. Однако, фпс продолжает падать. Использую диспетчер задач хрома, чтобы посмотреть, в чем проблема. Выясняется, что память GPU при перезагрузке сцен не очищается, а продолжает забиваться.


Посмотрев материалы на тему использования three.js в SPA (например от Дриеса Де Смета) выяснилось, что очевидный способ удаления элементов сцены не самый правильный.


for (let i = mScene.scene.children.length - 1; i >= 0; i--) {
    mScene.scene.remove(mScene.scene.children[i]); // объекты пропадают, но остаются в памяти
}

Более. Рекомендации к использованию метода очистки геометрии и текстур объекта весьма расплывчаты. Вот что пишут о методе dispose() в документации:


In general, there is no definite recommendation for this. It highly depends on the specific use case when calling dispose() is appropriate. It's important to highlight that it's not always necessary to dispose objects all the time. A good example for this is a game which consists of multiple levels.

Очевидно, что в данном проекте необходимо использовать dispose. Но как? Эмпирически пришел к следующему варианту (представлен в сокращенном виде):


dispose_scene() {
    let self = this;
    self.scroll_timer_stop();
    this.scene.traverse(function (object) {
    self.scroll_timer_stop();
        if (object.type === "Mesh" || object.type === "Group") {
            self.dispose_hierarchy(object, self.dispose_node);
            self.scene.remove(object);
            object = null;
       }
    });
}

dispose_hierarchy(node, callback) {
    for (var i = node.children.length - 1; i >= 0; i--) {
        var child = node.children[i];
        this.dispose_hierarchy(child, callback);
        callback(child);
    }
}

dispose_node(node) {
        if (node.constructor.name === "Mesh") {
            node.parent = undefined;
            if (node.geometry) {
                node.geometry.dispose();
            }
            if (node.geometry) {
                node.geometry.dispose();
            }
            let material = node.material;
            if (material) {
                if (material.map) {
                    material.map.dispose();
                }
                if (material.lightMap) {
                    material.lightMap.dispose();
                }
                ...
                material.dispose();
                material = undefined;
            }
        } else if (node.constructor.name === "Object3D") {
            node.parent.remove(node);
            node.parent = null;
        }
}

dispose_postprocessing() { 
        this.postprocessing.rtTextureColors.dispose();
        this.postprocessing.rtTextureDepth.dispose();
        ...
        this.postprocessing.materialGodraysDepthMask.dispose();
        this.postprocessing.materialGodraysGenerate.dispose();
        ...
}

Итог


В итоге хотелось бы сказать, что текущие механизмы работы с памятью в three.js не очень прозрачные и очевидные. this.postprocessing.dispose() в предыдущем примере не так влияет на общий расход памяти, а вот пошаговое и методичное удаление всех составляющих, к которым применим dispose() приводит к тому, что ты перестаешь платить ресурсами за то, чего не видишь. Хотя, безусловно, комплексного решения для очистки я не нашел. Более. Нагрузка на видеокарту при работе страницы огромная. Geforce 2070 super выдает стабильный фпс и плавность анимаций, но демонстрирует следующую картину:




Был бы рад услышать ваши комментарии и опыт работы с аналогичными проектами. С исходным кодом и примерами можно ознакомиться в моем гитхабе. Спасибо за внимание!

Теги:
Хабы:
+6
Комментарии8

Публикации

Изменить настройки темы

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн