Вторник, 12.12.2017, 09:19
Поиск
Никнэйм
Сертификат на никнейм Olelucoye, зарегистрирован на Тимофеев Константин Михайлович
Зарегистрируй свой никнейм
Обратная связь
olelucoye.tk@yandex.ru
Реклама AdSense
Реклама
Друзья сайта
  • Лига медицинского права
  • Гостиница "Зай"
  • FAQ по системе
  • Инструкции для uCoz
  • Главная » Статьи » libGDX

    libGDX. Отбраковка объектов не попадающих в обзор камеры.

    libGDX. Отбраковка объектов не попадающих в обзор камеры.

    При визуализации 3D сцены часто число видимых объектов намного меньше общего числа объектов в сцене. Некоторые объекты не попадают в угол обзора камеры. Рендеринг не попавших в камеру объектов - неблагоразумная трата ресурсов графического процессора и как следствие снижение производительности. Поэтому нам нужно позаботится, чтобы отрисовывались только те объекты, которые попадают в обзор камеры. Рассмотрим, что нужно для этого сделать.

    В качестве заготовки будем использовать сцену с деревьями из прошлой статьи. Для того, что бы иметь обратную связь (видеть числовое представление FPS и количество отрисованных объектов), нам понадобятся несколько новых объектов. Stage - сцена для отображения двумерных объектов, Label - надпись, BitmapFont - шрифт. А также конструктор строк - для построения текстовой строки и целочисленная переменная - в качестве счетчика отрисованных моделей.

        protected Stage stage;
        protected Label label;
        protected BitmapFont font;
        protected StringBuilder stringBuilder;
        private int visibleModels;
                        
        @Override
        public void create () {
            stage = new Stage();
            font = new BitmapFont();
            label = new Label(" ", new Label.LabelStyle(font, Color.WHITE));
            stage.addActor(label);
            stringBuilder = new StringBuilder();
            ......       
            }

    Обратите внимание, как надпись добавляется в двумерную сцену. Теперь изменим немного метод render():

        public void render () {
            camController.update();
            
            Gdx.gl.glViewport ( 0 , 0 , Gdx.graphics.getWidth (), Gdx.graphics.getHeight ());
            Gdx.gl.glClearColor(0.3f, 0.5f, 1f, 1f);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT|GL20.GL_DEPTH_BUFFER_BIT);
            
            modelBatch.begin(cam);
            visibleModels = 0;
            
            for(final ModelInstance tree: forest){
                if(IsVisible(cam, tree)){
                    modelBatch.render(tree, environment);
                    visibleModels++;
                }
            }
            modelBatch.render(groundInstance, environment);
            visibleModels++;
            modelBatch.end();
            
            stringBuilder.setLength(0);
            stringBuilder.append(" FPS: ").append(Gdx.graphics.getFramesPerSecond());
            stringBuilder.append(" Visible: ").append(visibleModels);
            label.setText(stringBuilder);
            stage.draw();
        }
        protected boolean IsVisible(final Camera cam, final ModelInstance instance) {
            return true;
        }

    Обнуляем счетчик, Далее отрисовываем модели из массива forest, но теперь передаем в ModelBatch весь массив, а перебираем каждый элемент массива по отдельности в цикле. Внутри цикла мы проверяем видимость объекта методом IsVisible, который пока напишем как заглушку - он всегда будет возвращать true. В случае выполнения условия рисуем модель и увеличиваем счетчик на 1. Затем отрисовываем площадку на которой все будет стоять и увеличиваем счетчик еще на 1 (площадка ведь тоже модель).

    Далее собираем строку. FPS получаем методом Gdx.graphics.getFramesPerSecond(), а количество отрисованных моделей из нашего счетчика. Передаем строку в надпись и рисуем двумерную сцену. Попробуем запустить. Поскольку метод IsVisible пока ничего не делает и render() отображает все модели, в строке Visible будет показано общее число моделей.

    Теперь изменим метод IsVisible. Добавим в него проверку видимости объекта. В libGDX для этого есть специальные методы.

        protected boolean IsVisible(final Camera cam, final ModelInstance instance) {
            Vector3 position = new Vector3();
            instance.transform.getTranslation(position);
            return cam.frustum.pointInFrustum(position);
        }

    Объявим объект Vector3 - в нем могут храниться координаты точки в трехмерном пространстве. И запишем в него координаты центра проверяемой модели. Передаем полученные координаты в метод cam.frustum.pointInFrustum, который возвратит нам true в случае, если эта точка попадает в угол обзора камеры. Запускаем. Пробуем перемещать камеру и наблюдаем как меняется число отрисованных моделей в строке Visible.

    Количество отображаемых моделей меняется, FPS нет. Это потому, что общее количество моделей не очень большое и графический процессор нормально справляется со всей сценой. Что бы продемонстрировать изменение FPS, я увеличил количество моделей в сцене. Вот что у меня получилось:

    Число FPS в данном случае упало, когда количество отрисовываемых моделей перевалило за 700 штук. Но следует помнить, что FPS зависит не столько от количества моделей, сколько от числа используемых в них полигонов. При использовании высокополигональных объектов FPS упадет при меньшем количестве моделей. А также FPS зависит от мощности графического процессора и общей производительности компьютера. Если вы разрабатываете приложение для андроид, не забывайте, что производительность мобильных устройств обычно ниже, чем у настольных компьютеров.

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

    Для того чтобы решить эту проблему, мы должны убедиться, что весь объект на самом деле не попадает в обзор камеры. Проверка каждой вершины модели будет делом очень утомительным и скорее всего будет иметь обратный эффект - производительность упадет. Мы можем убедится, что объект находится вне видимости камеры, используя размеры модели. Такой способ позволит нам отрисовать модели, которые видны только частично, но в некоторых случаях могут быть ложные срабатывания.

    Чтобы реализовать такой способ, удобно будет хранить размер модели вместе с самим экземпляром. Для этого нам нужно расширить класс ModelInstance.

        public static class ModelInstanceAdvanced extends ModelInstance{
            public final Vector3 center = new Vector3();
            public final Vector3 dimens = new Vector3();
            private final static BoundingBox bound = new  BoundingBox();
            public ModelInstanceAdvanced(Model model) {
                super(model);
                calculateBoundingBox(bound);
                bound.getCenter(center);
                bound.getDimensions(dimens);
            }
        }

    Объявляем два объекта Vector3 для хранения координат центра и размеров модели. А также объект BoundingBox для хранения границ невидимой "коробки", в которой будет находиться модель. Именно попадание этой коробки в обзор камеры мы и будем проверять. В конструкторе вычисляем границы "коробки" для модели и записываем полученные центр и размеры в объекты Vector3. Теперь наш новый класс будет вычислять размеры и координаты центра при создании экземпляра и хранить их вместе с экземпляром. Такое расширение класса нам необходимо для того, чтобы не вычислять эти данные при каждом цикле рендеринга.

    Изменяем тип массива экземпляров моделей и вносим соответствующие изменения в коде.

        public Array<ModelInstanceAdvanced> forest =
                                     new Array<ModelInstanceAdvanced>();
        .......
        @Override
        public void create () {
           .........
            Random rnd = new Random();
            for(float x = -25f; x < 25f; x +=5f){
                for(float z = -25f; z < 25f; z +=5f){
                    if(rnd.nextInt(10) > 2){
                       .....
                        ModelInstanceAdvanced tree;
                        if(rnd.nextInt(2) == 0){
                            tree = new ModelInstanceAdvanced(LeafTree);
                        }else{
                            tree = new ModelInstanceAdvanced(pineTree);
                        }
                       ......
                    }
                }
            }
           ......
        }
        @Override
        public void render () {
           .......
           for(final ModelInstanceAdvanced tree: forest){
                if(IsVisible(cam, tree)){
                    modelBatch.render(tree, environment);
                    visibleModels++;
                }
            }
            .....
        }

    Теперь изменим проверку попадания объекта в область обзора камеры. Получаем положение модели относительно начала координат, добавляем к нему локальные координаты центра модели. Затем передаем полученные координаты центра и размеры экземпляра модели передаем в метод cam.frustum.boundsInFrustum, который возвращает true, в случае попадания границ "коробки" в обзор камеры.

        protected boolean IsVisible(final Camera cam, final ModelInstanceAdvanced instance) {
            Vector3 position = new Vector3();
            instance.transform.getTranslation(position);
            position.add (instance.center);
            return cam.frustum.boundsInFrustum(position, instance.dimens);
        }

    Мне кажется, что здесь нужно некоторое объяснение - для чего мы складываем два вектора. Дело в том, что метод instance.transform.getTranslation дает вектор, который указывает на точку экземпляра модели находившуюся в точке начала координат в момент создания экземпляра, т.е., можно сказать, на центр основания модели. А instance.center дает центр модели относительно координатного начала самой модели, который находится как раз в точке куда указывает первый вектор. Наверное, не совсем понятно, поэтому я решил объяснить это графически:

    Красный вектор - это instance.transform.getTranslation, желтый - instance.center. Сумма этих векторов даст координаты центра объекта относительно глобального начала координат. Не знаю насколько это объяснение было необходимо - возможно кому-то это понятно и так. Но для меня причина сложения векторов была неочевидна с первого взгляда, поэтому я заострил на ней внимание.

    Запускаем приложение. Подвигав камерой видим, объекты не появляются и не исчезают внезапно. Но иногда число видимых моделей в информационной строке больше, чем число реально видимых объектов в камере. Это ложные срабатывания - часть границы "коробки" попадает в обзор камеры, а графической составляющей модели в этой области нет. Вот например такой момент - число реально видимых моделей 10, а рендерится 12.

    У такого метода отбраковки есть еще один минус - если экземпляр модели повернуть объект, границы его "коробки" не повернутся и, соответственно, перестанут отражать его реальное положение. Самый простой и, вероятно, самый лучший, способ - использовать вместо "коробки" сферу с центром совпадающим с центром объекта, которая будет учитывать все возможные повороты экземпляра модели. Внесем поправки в код.

            public static class ModelInstanceAdvanced extends ModelInstance{
            public final Vector3 center = new Vector3();
            public final Vector3 dimens = new Vector3();
            public final float radius;
            private final static BoundingBox bound = new  BoundingBox();
            public ModelInstanceAdvanced(Model model) {
                super(model);
                calculateBoundingBox(bound);
                bound.getCenter(center);
                bound.getDimensions(dimens);
                radius = dimens.len()/2f;
            }
        }
        .........
        protected boolean IsVisible(final Camera cam, final ModelInstanceAdvanced instance) {
            Vector3 position = new Vector3();
            instance.transform.getTranslation(position);
            position.add (instance.center);
            return cam.frustum.sphereInFrustum(position, instance.radius);
        }

    В классе ModelInstanceAdvanced устанавливаем радиус равным половине размера модели. А в методе IsVisible проверяем попадание объекта в камеру еще одним методом - cam.frustum.sphereInFrustum. Такой способ учитывает все положения модели, но приводит к большему числу ложных срабатываний.

    В статье использованы материалы туториала xoppa

    Полный листинг главного класса (проверка по сфере).

    import java.util.Random;
    import com.badlogic.gdx.ApplicationAdapter;
    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.graphics.Camera;
    import com.badlogic.gdx.graphics.Color;
    import com.badlogic.gdx.graphics.GL20;
    import com.badlogic.gdx.graphics.PerspectiveCamera;
    import com.badlogic.gdx.graphics.Texture;
    import com.badlogic.gdx.graphics.VertexAttributes.Usage;
    import com.badlogic.gdx.graphics.g2d.BitmapFont;
    import com.badlogic.gdx.graphics.g3d.Environment;
    import com.badlogic.gdx.graphics.g3d.Material;
    import com.badlogic.gdx.graphics.g3d.Model;
    import com.badlogic.gdx.graphics.g3d.ModelBatch;
    import com.badlogic.gdx.graphics.g3d.ModelInstance;
    import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
    import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute;
    import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight;
    import com.badlogic.gdx.graphics.g3d.model.Node;
    import com.badlogic.gdx.graphics.g3d.utils.CameraInputController;
    import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder;
    import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
    import com.badlogic.gdx.math.Vector3;
    import com.badlogic.gdx.math.collision.BoundingBox;
    import com.badlogic.gdx.scenes.scene2d.Stage;
    import com.badlogic.gdx.scenes.scene2d.ui.Label;
    import com.badlogic.gdx.utils.Array;
    public class Proba3D extends ApplicationAdapter {
        
        public static class ModelInstanceAdvanced extends ModelInstance{
            public final Vector3 center = new Vector3();
            public final Vector3 dimens = new Vector3();
            public final float radius;
            private final static BoundingBox bound = new  BoundingBox();
            public ModelInstanceAdvanced(Model model) {
                super(model);
                calculateBoundingBox(bound);
                bound.getCenter(center);
                bound.getDimensions(dimens);
                radius = dimens.len()/2f;
            }
        }
        
        public PerspectiveCamera cam;
        public Model pineTree, LeafTree, ground;
        public ModelInstance groundInstance;
        public Array<ModelInstanceAdvanced> forest = new Array<ModelInstanceAdvanced>();
        public ModelBatch modelBatch;
        public Environment environment;
        public CameraInputController camController;
        public Material pine, trunk, land, crown, skin;
        
        protected Stage stage;
        protected Label label;
        protected BitmapFont font;
        protected StringBuilder stringBuilder;
        private int visibleModels;
                                
        @Override
        public void create () {
            stage = new Stage();
            font = new BitmapFont();
            label = new Label(" ", new Label.LabelStyle(font, Color.WHITE));
            stage.addActor(label);
            stringBuilder = new StringBuilder();
            
            modelBatch = new ModelBatch();
            environment = new Environment();
            environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f));
            environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
                            
            cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
            cam.position.set(10f, 10f, 10f);
            cam.lookAt(0f, 0f, 0f);
            cam.near = 1f;
            cam.far = 300f;
            cam.update();
            
            camController = new CameraInputController(cam);
            Gdx.input.setInputProcessor(camController);
            
            pine = new Material(ColorAttribute.createDiffuse(Color.GREEN));
            crown = new Material(ColorAttribute.createDiffuse(Color.OLIVE));
            trunk = new Material(ColorAttribute.createDiffuse(Color.GRAY));
            TextureAttribute land_attr = TextureAttribute.createDiffuse(new Texture("land.jpg"));
            land = new Material(land_attr);
            skin = new Material(ColorAttribute.createDiffuse(Color.PINK));
            
            ModelBuilder modelBuilder = new ModelBuilder();
            modelBuilder.begin();
            Node node1 = modelBuilder.node();
            node1.id = "node1";
            node1.translation.set(0f, 2.8f, 0f);
            MeshPartBuilder meshBuilder;
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, pine);
            meshBuilder.cone(1f, 1f, 1f, 20);
            Node node2 = modelBuilder.node();
            node2.id = "node2";
            node2.translation.set(0f, 2f, 0f);
            meshBuilder = modelBuilder.part("part2", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, pine);
            meshBuilder.cone(2f, 1.5f, 2f, 20);
            Node node3 = modelBuilder.node();
            node3.id = "node3";
            node3.translation.set(0f, 1f, 0f);
            meshBuilder = modelBuilder.part("part3", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, pine);
            meshBuilder.cone(3f, 2f, 3f, 20);
            Node node4 = modelBuilder.node();
            node4.id = "node4";
            meshBuilder = modelBuilder.part("part4", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, trunk);
            meshBuilder.cylinder(1f, 1f, 1f, 20);
            pineTree = modelBuilder.end();
            
            modelBuilder.begin();
            Node lnode1 = modelBuilder.node();
            lnode1.id = "lnode1";
            lnode1.translation.set(0f, 1f, 0f);
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, crown);
            meshBuilder.sphere(2f, 2f, 2f, 20, 20);
            Node lnode2 = modelBuilder.node();
            lnode2.id = "lnode2";
            lnode2.translation.set(0.3f, 1.7f, 0f);
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, crown);
            meshBuilder.sphere(2f, 2f, 2f, 20, 20);
            Node lnode3 = modelBuilder.node();
            lnode3.id = "lnode3";
            lnode3.translation.set(-0.3f, 1.7f, 0.3f);
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, crown);
            meshBuilder.sphere(2f, 2f, 2f, 20, 20);
            Node lnode4 = modelBuilder.node();
            lnode4.id = "lnode4";
            lnode4.translation.set(-0.3f, 1.7f, -0.3f);
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, crown);
            meshBuilder.sphere(2f, 2f, 2f, 20, 20);
            Node lnode5 = modelBuilder.node();
            lnode5.id = "lnode5";
            lnode5.translation.set(0f, 2.2f, 0f);
            meshBuilder = modelBuilder.part("part1", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, crown);
            meshBuilder.sphere(2f, 2f, 2f, 20, 20);
            Node lnode6 = modelBuilder.node();
            lnode6.id = "lnode6";
            meshBuilder = modelBuilder.part("part4", GL20.GL_TRIANGLES, Usage.Position | Usage.Normal, trunk);
            meshBuilder.cylinder(0.7f, 1f, 0.7f, 20);
            LeafTree = modelBuilder.end();
            
            Random rnd = new Random();
            for(float x = -25f; x < 25f; x +=5f){
                for(float z = -25f; z < 25f; z +=5f){
                    if(rnd.nextInt(10) > 2){
                        float x_offset = 4*(0.5f - rnd.nextFloat());
                        float z_offset = 4*(0.5f - rnd.nextFloat());
                        ModelInstanceAdvanced tree;
                        if(rnd.nextInt(2) == 0){
                            tree = new ModelInstanceAdvanced(LeafTree);
                        }else{
                            tree = new ModelInstanceAdvanced(pineTree);
                        }
                        tree.transform.setToTranslation(x + x_offset, 0, z + z_offset );
                        forest.add(tree);
                    }
                }
            }
                        
            ground = modelBuilder.createBox(54f, 0.1f, 54f, land, Usage.Position|Usage.Normal|Usage.TextureCoordinates);
            groundInstance = new ModelInstance(ground);
            groundInstance.transform.setTranslation(0f, -0.5f, 0f);
        }
        @Override
        public void render () {
            camController.update();
            
            Gdx.gl.glViewport ( 0 , 0 , Gdx.graphics.getWidth (), Gdx.graphics.getHeight ());
            Gdx.gl.glClearColor(0.3f, 0.5f, 1f, 1f);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT|GL20.GL_DEPTH_BUFFER_BIT);
            
            modelBatch.begin(cam);
            visibleModels = 0;
            
            for(final ModelInstanceAdvanced tree: forest){
                if(IsVisible(cam, tree)){
                    modelBatch.render(tree, environment);
                    visibleModels++;
                }
            }
            modelBatch.render(groundInstance, environment);
            visibleModels++;
            modelBatch.end();
            
            stringBuilder.setLength(0);
            stringBuilder.append(" FPS: ").append(Gdx.graphics.getFramesPerSecond());
            stringBuilder.append(" Visible: ").append(visibleModels);
            label.setText(stringBuilder);
            stage.draw();
        }
        
        protected boolean IsVisible(final Camera cam, final ModelInstanceAdvanced instance) {
            Vector3 position = new Vector3();
            instance.transform.getTranslation(position);
            position.add (instance.center);
            return cam.frustum.sphereInFrustum(position, instance.radius);
        }
        
        @Override
        public void resize(int width, int height) {
            stage.getViewport().update(width, height, true);
        }
        
        @Override
        public void dispose(){
            modelBatch.dispose();
            pineTree.dispose();
            LeafTree.dispose();
        }
    }

    3D модель  для libGDX. Пишем код.
    3D модель для движка libGDX
    Строим модель с помощью ModelBuilder
    libGDX. Основы 3D программирования.
    Игра Flower. Ловим капли.
    TexturePacker.Создаем атлас текстур.
    Создаем проект на движке libGDX
    Кастомизация EditText
    Кастомизация SeekBar'а
    9-patch изображения для Андроид
    Кастомный ползунок в виде дуги (аналог SeekBar)
    Анимация в Андроид
    Кастомизация элементов управления в Android
    Смартфон DEXP Ixion ML 5, обзор.
    Создание кастомного View-элемента интерфейса.
    Создание виджета - электронные часы с кастомным шрифтом
    Будильник для Андроид "Разбуди меня"
    Программируем калькулятор на андроид. Урок 1.

    Категория: libGDX | Добавил: Olelucoye (14.04.2015)
    Просмотров: 1843
    | Теги: Frustum culling, отбраковка, границы, libGDX | Рейтинг: 0.0/0
    Всего комментариев: 0
    Добавлять комментарии могут только зарегистрированные пользователи.
    [ Регистрация | Вход ]
    Меню сайта
    Категории раздела
    Андроид разработка [23]
    libGDX [24]
    Мои андроид проекты [6]
    Excel [7]
    Железяки [5]
    Скрипты в блокноте [4]
    Разное [1]
    Форма входа
    Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0
    Яндекс Метрика
    Яндекс.Метрика