Докуметация Cтарт Статьи Форум Лента Вход
Не официальное русскоязычное сообщество
Главная
    2.6.6 LWJGL3 миграция
        2.6.6 LWJGL3 миграция

Andry

Опубликованно: AndryAndry08.05.2018, 18:25
Последняя редакция, Andry: 17.05.2018 13:27

LWJGL3 Руководство по миграции. Данное руководство предназначено для того что бы показать процесс переноса приложения с LWJGL2 на LWJGL3. Для этого я написал простую игру Pong в LWJGL2 (2.9.3), и это руководство будет описывать, как я перенес его на LWJGL3 (3.0.0b Build # 64 — последняя стабильная на момент написания статьи). Руководство будет разделено на commit-ы, по одному commit-у для каждой функции, которую я переношу на LWJGL3, и вы можете точно увидеть, что я изменил, просмотрев разные commit-ы в репозиторий Github: Миграция Pong в Github Репозитории. Репозиторий содержит оригинал игры в папке «PongLWJGL2», а в папке «PongLWJGL3» содержится обновленная версия.

Прежде чем вы прочитаете это руководство, если вы хотите использовать улучшения стабильности (не функции) LWJGL3, но не хотите переносить свою игру, то у вас есть альтернатива. Каппа написал слой совместимости называемый LWJGLX, смотрите это сообщение на форуме. ##Commit 1 — Переключение Этот commit просто переключает natives и библиотеки на использование LWJGL3, ничего более. Как и следовало ожидать, это не сработает. ##Commit 2 — Синтаксис команд OpenGL. Одно из наиболее незначительных изменений, внесенных в LWJGL3, является изменение синтаксиса OpenGL на актуальный синтаксис, которые вы можете найти в C API. Как это бывает, единственное изменение, которое требуется сделать в примере программы, заключается в изменении всех вызовов:

glUniformMatrix4(int location, boolean transpose, FloatBuffer values);

на:

glUniformMatrix4fv(int location, boolean transpose, FloatBuffer values);

Логика синтаксиса LWJGL2: подразумевается, что это float матрица, поскольку вы проходите FloatBuffer. LWJGL3 возвращается к исходному API, чтобы быть более совместимым с другими источниками информации, особенно официальной ссылкой.

Как правило, вы будете добавлять «f» и «i’s» и «fv» и.т.п. для вызова команд. Не должно вызывать слишком много проблем и, вероятно, будет достаточно очевидно, что заменить.

Следующий commit будет охватывать самое большое изменение из всех — переход от собственной оконной системы LWJGL2 к GLFW, используемой LWJGL3. ##Commit 3 — Создание Контекста Window/OpenGL. Я думаю, что есть три важных момента, которые нужно знать, когда дело доходит до создания окна и контекста OpenGL в LWJGL3 (то есть GLFW, использует библиотеку окон LWJGL).

  1. GLFW может управлять несколькими окнами и, подобно тому, как OpenGL управляет своими объектами, на них ссылается обработчик, который передается функциям, которые управляют этим окном. LWJGL3 привязки используют «long» переменные для хранения этого обработчика.
  2. Перед созданием окна вы указываете все атрибуты, которые вы хотели бы иметь с помощью подсказок окна. Все передаётся с помощью одной функции: glfwWindowHint()
  3. Поскольку вы можете иметь дело с несколькими окнами, каждое из которых потенциально имеет свой собственный контекст OpenGL, то после его создания вы должны указать LWJGL3, какой контекст вы используете.

Прежде чем мы что-либо сделаем в GLFW, мы должны инициализировать GLFW с помощью glfwInit(), у этого есть сопутствующая функция для завершения GLFW, реализующая его ресурсы glfwTerminate(). В Pong они добавляются к началу и концу функций init() и deinit() соответственно.

Затем неплохо было бы заставить GLFW печатать свои ошибки на консоли, что он не делает по умолчанию, но это просто, и LWJGL предоставляет функцию утилиту для создания соответствующего обратного вызова (подробнее об обратных вызовах позже) в org.lwjgl.glfw.GLFWErrorCallback классе.

glfwSetErrorCallback(errorCallback = GLFWErrorCallback.createPrint(System.err));

Это будет работать отлично, но errorCallback ДОЛЖЕН будет быть полем, а НЕ локальной переменной (запомните дополнительно о обратных вызовах позже).

Теперь, чтобы задать свойства окна. Шаг за шагом в Pong: заголовок передается как параметр функции glfwCreateWindow(), свойство resizeable устанавливается с флагом GLFW_RESIZABLE, запрошенная версия OpenGL устанавливается с помощью флагов GLFW_CONTEXT_VERSION_MAJOR и GLFW_CONTEXT_VERSION_MINOR, запрошенный профиль устанавливается с помощью Флаг GLFW_OPENGL_PROFILE и с полноэкранным режимом мы пока не будем иметь дело.

Наконец, мы должны создать окно с glfwCreateWindow(), помня, что мы должны сохранить обработчик, возвращаемый функцией, поскольку это наше окно.

В старом LWJGL2

Display.setTitle("Pong - LWJGL2");
Display.setResizable(true);
Display.create(
    new PixelFormat(), 
    new ContextAttribs(3, 3).withProfileCore(true)
);

В новом LWJGL3

glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 
window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Pong - LWJGL3", 0, 0);

«0» в вызове glfwCreateWindow() относятся к полноэкранному и совместно используемому контекстам OpenGL, если вам все равно, их можно игнорировать, но они будут объяснены позже. Дополнительную информацию об этих функциях смотрите в официальной документации: glfwWindowHint(), glfwCreateWindow(). Вы также заметите, что LWJGLException теперь никогда не используется в коде (и фактически удален в LWJGL3), поэтому все случаи применения в Pong можно удалить.

Однако это ещё не все, так как мы все еще проверяем, было создано окно успешно, сообщите LWJGL, чтобы использовать контекст OpenGL этого окна и сделать окно видимым (поскольку окно GLFW скрыто по умолчанию).

Теперь glfwCreateWindow() вернет ‘0’, предоставляя значение NULL, если оно по какой-либо причине не сработает. Таким образом, в Pong добавлена простая проверка, и ваш обратный вызов может поймать причину ошибки, если вы этого хотите:

if(window == 0) {
    throw new RuntimeException("Failed to create window");
}

Теперь мы должны установить контекст этого окна как текущий контекст в этом потоке, вызвав glfwMakeContextCurrent() с обработчиком нашего окна в качестве параметра и, наконец, вызвать GL.createCapabilities(), чтобы LWJGL использовал контекст текущего (нашего окна). Подробнее о контекстах здесь (OpenGL Контексты), и вот документация по glfwMakeContextCurrent().

glfwMakeContextCurrent(window);
GL.createCapabilities();

И, наконец, сделайте окно видимым с помощью glfwShowWindow(). Документация здесь: glfwShowWindow().

glfwShowWindow(window);

После этого у вас будет работающее и видимое окно, которое можно визуализировать, поэтому давайте обновим использование этого окна. Большинство функций, кроме код обработки ввода и изменения размера, являются прямой аналогией, поэтому это не займет много времени.

Прежде всего уничтожить окно, как только вы с ним закончите. Эквивалентом Display.destroy() является glfwDestroyWindow(), документация: glfwDestroyWindow().

glfwDestroyWindow(window);

Теперь Display.isCloseRequested() становится glfwWindowShouldClose(), но это API-интерфейс C, поэтому он не будет возвращать логическое значение, а будет возвращать целое число, представляющее логическое значение, посмотрите здесь: glfwWindowShouldClose.

while(glfwWindowShouldClose(window) == GL_FALSE && 
		remainOpen) {
	...
}

И, наконец, Display.update() не имеет прямого аналога в основном потому, что он выполняет две функции: свопирует буферы и опросы устройств ввода. В GLFW вы должны использовать две функции: glfwPollEvents() и glfwSwapBuffers(), дополнительную информацию смотрите здесь glfwPollEvents() и здесь glfwSwapBuffers().

glfwPollEvents();
glfwSwapBuffers(window);

На этом этапе, если вы закомментируете все содержимое update(), setDisplayMode() и получите что currentTimeMillis() вернёт 0, Pong запустится и отобразит растягиваемое закрываемое окно нужного размера. К несчастью, оно будет просто пустым, но не потому, что рендеринг не работает, а потому что, так как мы не имеем дело с событиями изменения размера, проекционная матрица неправильна.

Это то, что мы будем делать дальше. ##Commit 4 — Framebuffer Resize Handling(Обработка Изменения Размера Кадрового буфера) Прежде чем продолжить, я хочу объяснить важное различие между фреймбуфером(кадровым буфером) и окном. Фреймбуфер — это «холст» OpenGL — массив пикселей, который может быть отображен. Окно — это то, что содержит фреймбуфер, и его можно перемещать и изменять размер и.т.п. Как правило, изменение размера окна приведет к изменению размера фреймбуфера, но это два разных события, и GLFW позволит вам обрабатывать их независимо. Здесь мы сосредоточимся только на событиях изменения размера фреймбуфера. Подробнее о фреймбуферах читайте здесь.

Чтобы обрабатывать события изменения размера фреймбуфер, а также большинства других событий, GLFW использует обратные вызовы. Фактически мы уже использовали обратный вызов для обработки ошибок GLFW. Теперь обратные вызовы — это, по сути, другое название для слушателей, которые мы используем в Swing, JavaFX и во многом другом. LWJGL3 предоставляет абстрактные классы для переопределения, для всех вариантов обратных вызовов GLFW и даже интерфейсы для использования в качестве функциональных интерфейсов, если вы используете лямбды Java8, в этом случае org.lwjgl.glfw.GLFWFramebufferSizeCallback и функциональный интерфейс: org.lwjgl.glfw.GLFWFramebufferSizeCallback.SAM.

Есть одна критически важная вещь связанная с обратными вызовами GLFW. ВСЕГДА ПОДДЕРЖИВАЙТЕ В ПОРЯДКЕ ССЫЛКИ ОБРАТНОГО ВЫЗОВА. Иначе обратные вызовы будут накапливаться в виде мусора, что приведет к довольно сложной ошибке отладки.

Теперь задайте функции изменения размера фреймбуфера для обратного вызова — это glfwSetFramebufferSizeCallback() подробнее здесь: glfwSetFramebufferSizeCallback().

В LWJGL2 этот код был у нас в методе update(). Обратите внимание, на то что нам нужно было иметь дополнительный флаг, чтобы прослушивать полноэкранные изменения, так как был только флаг изменения размера окна по умолчанию:

if(Display.wasResized() || goneFullscreen) {
    //Сбросить флаг goneFullscreen.
    goneFullscreen = false;
    onResize(Display.getWidth(), Display.getHeight());
}

В LWJGL3 мы можем избавиться от флага goneFullscreen, и все получается намного проще. Мы имеем это в коде инициализации:

glfwSetFramebufferSizeCallback(window, (framebufferSizeCallback = new GLFWFramebufferSizeCallback() {
    @Override
    public void invoke(long window, int width, int height) {
        onResize(width, height);
    }
}));
onResize(WINDOW_WIDTH, WINDOW_HEIGHT);

Мы вызываем onResize() после настройки обратного вызова фреймбуфера для инициализации проекционной матрицы до её начального значения.

Теперь Pong запустится, он создаст рабочее окно и отобразит в нем все элементы Pong. Но пока что он ещё не будет реагировать на ввод, или обновление любого из его элементов. Это будет в следующем commit, но прежде чем мы сможем обрабатывать ввод, нам нужно обновить наш код настройки времени. ##Commit 5 — Получение Времени. LWJGL2 давал нам две функции для получения точного времени Sys.getTime() и Sys.getTimerResolution(). Последняя требовалась, потому что первая, в зависимости от платформы могла работать в других единицах, чтобы вы получали лучшее разрешение и точность. Дело в том, что эта функциональность была написана в то время, когда эквивалентов JRE просто не было, что теперь уже не так, поэтому их можно заменить на функции, найденные в java.lang.System.

Однако GLFW также предоставляет альтернативу, которую мы будем использовать здесь: glfwGetTime() возвращает время в секундах, подробности здесь. Для использования этих значений в Pong мы должны преобразовать их в double значения для хранения текущего времени с точностью, которую может обеспечить этот метод, currentTimeMillis поэтому будет:

public static double currentTimeMillis() {
    return GLFW.glfwGetTime() * 1000;
}

##Commit 6 — Ввод с Клавиатуры. В Pong и, в общем случае, мы получаем ввод с клавиатуры двумя способами. Во-первых, мы запрашиваем клавиатуру из её текущего состояния, выясняя, находится ли какая-то конкретная клавиша нажатой или нет в это время, во-вторых, мы действуем на события клавиатуры, о которых сообщает наша система ввода.

Первый метод является самым простым, и именно это мы рассмотрим в первую очередь. Итак, в LWJGL2 у нас была Keyboard.isKeyDown (). В GLFW мы имеем glfwGetKey (), который вместо логического возвращает GLFW_PRESS или GLFW_RELEASE. Так в Понге:

updatePaddle(
    paddle1, delta, 
    glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS, 
    glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS
);
updatePaddle(
    paddle2, delta, 
    glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS,
    glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS
);

Теперь для обработки событий клавиатуры, и здесь GLFW использует обратные вызовы. На этот раз функция glfwSetKeyCallback (), абстрактный класс LWJGL — org.lwjgl.glfw.GLFWKeyCallback, а функциональный интерфейс — org.lwjgl.glfw.GLFWKeyCallback.SAM. Как всегда с обратными вызовами, вы должны ПОМНИТЬ, ЧТОБЫ СОХРАНЯТЬ СИЛЬНУЮ СПРАВКУ К ВЫЗОВУ. Поэтому в Понге мы должны добавить это к нашей функции init ().

glfwSetKeyCallback(window, (keyCallback = new GLFWKeyCallback() {

    @Override
    public void invoke(long window, int key, int scancode, int action, int mods) {
        if(key == GLFW_KEY_SPACE && action == GLFW_RELEASE) {
            onPlayPauseToggle();
        } else if(key == GLFW_KEY_F5 && action == GLFW_RELEASE) {
            setDisplayMode(true);
        } else if(key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
            //Request close.
            remainOpen = false;
        }
    }

}));

Полное описание параметров метода invoke () callback можно найти в связанном описании, но важно отметить mods, бит-поле, описывающее ключи модификатора, включенные во время этого события. Вы найдете это очень полезным, помните об этом. Также стоит отметить, что callback может получать события GLFW_REPEAT, а также события GLFW_PRESS и GLFW_RELEASE.

Если вы раскомментируете код обновления мяча в update (), на данный момент у вас будет полностью работающая игра в Понге, хотя и отсутствуют некоторые из особенностей оригинала. Следующее commit мы заменим код ввода мыши. ## Commit 7 — Ввод мыши Перед тем, как мы начнем, есть одна важная разница с записью с GLFW по сравнению с LWJGL2. В LWLJGL2, когда он подошел к позиции курсора, нижний левый угол был выбран как начало большинства приложений OpenGL. GLFW берет начало координат в верхнем правом углу, как и в большинстве графических интерфейсов. В Понге это потребует небольшой модификации.

Как и в случае с клавиатурой, мы должны обрабатывать наши запросы мыши и события мыши, а API следует точно так же, как и ввод с клавиатуры. Однако самый простой способ получить позицию курсора в GLFW — использовать обратный вызов позиции курсора, glfwSetCursorPosCallback (), org.lwjgl.glfw.GLFWCursorPosCallback. Как всегда помните, что ХРАНИТЕ СИЛЬНУЮ СПРАВОЧНУЮ ЗАДЕРЖКУ.

В этом фрагменте cursorPos — это просто поле, в котором хранятся текущие позиции x и y текущего курсора. Мы вычитаем заданную позицию y из высоты фреймбуфера, чтобы учитывать GLFW, беря верхний левый угол в качестве источника, когда мы ожидаем, что он будет внизу слева.

glfwSetCursorPosCallback(window, (cursorPosCallback = new GLFWCursorPosCallback() {

    @Override
    public void invoke(long window, double xpos, double ypos) {
        cursorPos.x = xpos;
        cursorPos.y = framebuffer.height - ypos;
    }

}));

Следует отметить, что GLFW использует удвоение, поскольку поддерживает платформу подпиксельной точностью. Это означает, однако, что некоторые из кода обновления должны быть обновлены для использования удвоений. Теперь на фактический код обновления.

updateNewBall(Mouse.getX(), Mouse.getY());

будет выглядеть так:

updateNewBall(cursorPos.x, cursorPos.y);

И цикл событий мыши в update () становится:

glfwSetMouseButtonCallback(window, (mouseButtonCallback = new GLFWMouseButtonCallback() {

    @Override
    public void invoke(long window, int button, int action, int mods) {
        if(button == 0) {
            if(action == GLFW_PRESS && addBall == null) {
                onNewBall(cursorPos.x, cursorPos.y);
            } else if(action == GLFW_RELEASE && addBall != null) {
                onNewBallRelease(cursorPos.x, cursorPos.y);
            }
        }
    }

}));

И с этим изменением у нас есть полностью функционирующая игра Понга. Единственное, чего у нас не было, это полноэкранная возможность, которая будет нашей следующей фиксацией. ## Commit 8 — Fullscreen Первое, что нужно сказать, это то, что при переходе на GLFW мы не сможем реализовать полноэкранное переключение с тех пор, поскольку с момента написания этого GLFW не поддерживает полноэкранное переключение. Это можно сделать, но это связано с созданием нового окна и простым переключением между ними.

Способ, которым мы указываем полноэкранный режим, — передать не-нулевой (не нулевой) монитор в glfwCreateWindow (). Этот аргумент говорит GLFW, который контролирует размещение полноэкранного приложения. Когда вы передаете ненулевой монитор, ширина и высота становятся запрошенным разрешением, которое GLFW будет делать, это лучше всего выполнить. Если мы хотим разрешение рабочего стола (что обычно является хорошей идеей для плавного запуска и производительности).

Мы можем получить основной монитор с помощью glfwGetPrimaryMonitor () (документация), который возвращает дескриптор монитора. Выбор монитора, который не является основным монитором, довольно сложный, и если вам действительно нужно, что лучший вариант — позволить пользователю выбрать из списка.

Когда у нас есть монитор, нам нужно разрешение рабочего стола, которое мы можем получить, запросив текущее разрешение монитора. GLFW хранит эту информацию в структуре видеорежима, GLFWVidMode может быть восстановлен с помощью glfwGetVideoMode.

monitor = glfwGetPrimaryMonitor();
GLFWVidMode vidMode = glfwGetVideoMode(monitor);
width = vidMode.width();
height = vidMode.height();

Теперь у нас есть монитор и разрешение рабочего стола, которое мы можем создать. Итак, наш старый

window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Pong - LWJGL3", 0, 0);

становится

int windowWidth = WINDOW_WIDTH;
int windowHeight = WINDOW_HEIGHT;
long monitor = 0;
if(START_FULLSCREEN) {
    monitor = glfwGetPrimaryMonitor();
    Retrieve the desktop resolution
    GLFWVidMode vidMode = glfwGetVideoMode(monitor);
    windowWidth = vidMode.width();
    windowHeight = vidMode.height();
}
window = glfwCreateWindow(windowWidth, windowHeight, "Pong - LWJGL3", monitor, 0);

Это создаст полноэкранное окно с разрешением рабочего стола или обычное окно размера WINDOW_WIDTH x WINDOW_HEIGHT на основе значения START_FULSCREEN.

Но в Понге есть еще одна вещь — когда мы инициализируем нашу матрицу проекций с первым вызовом

onResize(WINDOW_WIDTH, WINDOW_HEIGHT

мы не принимаем во внимание, что фреймбуфер не будет такого размера, если мы находимся в полноэкранном режиме. Мы должны получить размер фреймбуфера, используя glfwGetFramebufferSize () (документация). Связывание LWJGL3 этого немного отличается, поскольку мы не имеем указателей. Мы используем java.nio.Buffers, в данном случае IntBuffers, чтобы дать функции где-то положить возвращаемые значения, а затем извлечь значения из буферов. Следовательно:

IntBuffer framebufferWidth = BufferUtils.createIntBuffer(1), 
                framebufferHeight = BufferUtils.createIntBuffer(1);
glfwGetFramebufferSize(window, framebufferWidth, framebufferHeight);
onResize(framebufferWidth.get(), framebufferHeight.get());

Понг теперь будет корректно отображаться и будет полностью функционировать.


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Добро пожаловать в MonkeyBrains вики!

  • С чего начать
  • Простое поведение
  • Реализация Пользовательского оружия
  • Реализация системы Очков Здоровья
  • Реализация системы Инвенторя
  • Система Событий в MonkeyBrains
  • Реализация Пользовательский control-ов для игры

С чего начать

  • Скачайте MonkeyBrains отсюда github.
  • Откройте его как проект в jMonkeyEngine SDK
  • Если не будут найдены библиотеки то добавьте библиотеку jme3-core.
  • В свойствах проекта в пункте Исходные файлы напротив Формат исходного/бинарного файла выберите JDK 8 или выше.
  • Далее очистите и соберите проект
  • Теперь в папке проекта dist появится файл MonkeyBrains.jar
  • Добавить его в свой проект в качестве библиотеки, и можете пользоваться.
  • Есть вероятность того что вам потребуется что то обновить так как он создавался при более ранней версии движка, нежели нынешняя.

Также следует знать что для не релизной версии, а версии в разработке данная статья устарела так как автор внёс некоторые существенные изменения в свой проект не исправив документацию и не обновив примеры. По сути она не закончена и я бы не рекомендовал её использовать новичкам. Вот его разъяснение:

Я сделал некоторые коммиты MonkeyBrains: переместил некоторые вещи в другой пакет (monkeystuff) и лишил агентов некоторых функций.

Старая функциональность сохраняется в новых BluePillAgents. Я назвал их таким образом, потому что я думаю, что это передает мое намерение: «Голубая Таблетка»(Blue Pill) — обычный способ, он хорошо документированы в вики, и он должны работать с текущими примерами.

Поскольку есть «Голубая Таблетка», будет также «Красная Таблетка»(Red Pill), которая будет немного отличаться. Я не являюсь экспертом в области ИИ, я просто хочу приключений в этом аспекте:

«Красная Таблетка» будет построена вокруг потребностей моей игры, так что да, я на самом деле буду есть свою собачью еду (обезьянью еду?).

Основные понятия:

Свой класс Агента вы наследуете от класса Agent. Он содержит атрибуты и операции над этими атрибутами. Их не рекомендуется изменять(расширять) при наследовании. Вместо расширения вы можете использовать метод setModel(T model) и задать свои настраиваемые атрибуты для агента.

Behavior Базовый класс для поведения агента. Каждое создаваемое поведение должно расширить этот класс. Каждое поведение затем должно реализовывать controlUpdate(float tpf). Те будет запрашивается com.jme3.scene.control.AbstractControl, который был расширен поведением(behavior). Также класс, который расширяет Behavior, должен иметь конструктор, который будет содержать агента в качестве параметра. При этом behavior(поведение) может изменять атрибуты агента, которые являются behavior(поведением). В com.jme3.ai.agents.behaviors.npc вы можете увидеть примеры некоторых простых видов поведения, которые легко использовать для ваших агентов NPC (Не персонажа Игрока). В com.jme3.ai.agents.behaviors.player есть некоторые примеры простого поведения, достаточные что бы поиграться с агентами.

Класс GameEntity нужен для всех сущностей в игре, которые имеют некоторое отношение к агентам. Примером может быть: Предметы, пули, препятствия, другие агенты (класс Agent расширяет GameEntities) …

MonkeyBrainsAppState Единственный класс, который содержит все игровые объекты и обновляет их. Он создан для упрощения манипуляций с агентами.

Пример:

public class Example extends SimpleApplication {

    //определение игры
    private MonkeyBrainsAppState brainsAppState = MonkeyBrainsAppState.getInstance(); 
    
    public static void main(String[] args) {
        Example app = new Example();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        //определение app
        brainsAppState.setApp(this);

        //инициализация агентов с их именами и spatial-ами
        Agent agent = new Agent("First agent", createAgentSpatial()); 
        //в фреймворке нет такого метода как createAgentSpatial()
        //предполагается что пользователь должен сам создавать свой spatial для игры

        //добавление агента в MonkeyBrainsAppState
        brainsAppState.addAgent(agent);

        //настройте moveSpeed(скоротьДвижения), rotationSpeed(скорость поворота), mass(вес)..
        agent.setMoveSpeed(20); 
        agent.setRotationSpeed(30);
        //используйте для управления поведения из com.jme3.ai.agents.behaviors.npc.steering
        agent.setMass(40);
        agent.setMaxForce(3);

        //создание основного поведения
        //агент может иметь только одно поведение, но это поведение может содержать другое поведение
        agent.setMainBehavior(new MyCustomBehavior(agent));

        //старт агентов
        brainsAppState.start();

    }

    @Override
    public void simpleUpdate(float tpf) {
        brainsAppState.update(tpf);
    }
}

Простое поведение

Простым поведением являются некоторые общие поведения, поддерживаемые MonkeyBrains.

SimpleMainBehavior — основное общее поведение для агентов. Основное поведение содержит другие поведения, и если оно активно, он обновит все разрешенные поведения. Вы можете добавить только одно управление поведением к этому контейнеру. Но вы можете использовать CompoundSteeringBehavior, чтобы объединить больше управлений поведениями в одно.

Пример:

Agent agent = new Agent("SimpleMain", createAgentSpatial());
SimpleMainBehavior mainBehavior = new SimpleMainBehavior(agent);
mainBehavior.addBehavior(new MyCustomBehavior1(agent));
mainBehavior.addBehavior(new MyCustomBehavior2(agent));
agent.setMainBehavior(mainBehavior);

SimpleMoveBehavior — это общее поведение перемещений для агентов. Агент должен перейти в намеченное положение или по moveDirection(направлению Движения). Если оба будут добавлены, то тогда агент переместится в намеченное положение. Предупреждение: иногда Агент никогда не будет двигаться точно в намеченное положение, если moveSpeed(скорость Движения) слишком высока, поэтому добавьте соответствующую ошибку расстояния. Вместо этого рекомендуется использовать управление поведениями(steering behaviors).

SimpleRotateBehavior — это общее поведение вращения. Если spatial не добавлен, то будут вращать все spatial-ы агентов. Если spatial был добавлен, то будет вращаться только этот spatial.

SimpleLookBehavior — это общее поведения зрения(взгляда) для агентов. Он требуется, для всех поведений в которые добавят слушатели. Это поведение может видеть только GameEntity(Игровые Сущности), если оно добавлено в MonkeyBrainsAppState. Чтобы правильно настроить это поведение, вам необходимо задать следующие параметры:

/**
 * Диапазон видимости. Как далеко может видеть агент.
 */
float visibilityRange;
/**
 * Угол, в котором будут видны GameEntities.
 */
float viewAngle;

Если эти параметры не заданы, тогда поведение зрения никогда не сообщит, что оно что-то увидело. Также необходимо добавить поведение, которое реализовано интерфейсом GameEntitySeenListener. (подробнее см. здесь).

Существует необязательный параметр:

   /**
     * Какие сущности будут сообщать поведению увиденное.
     */
    protected TypeOfWatching typeOfWatching;

    public static enum TypeOfWatching {

        /**
         * Зрение только для агентов.
         */
        AGENT_WATCHING,
        /**
         * Зрение только для игровых сущностей.
         */
        GAME_ENTITY_WATCHING,
        /**
         * Зрение для агентов и игровых сущностей.
         */
        WATCH_EVERYTHING;
    }

Вы можете изменить WATCH_EVERYTHING идущий по умолчанию, чтобы сообщалось только то, что вам нужно.

SimpleAttackBehavior — это общее поведение атаки для агентов. Это поведение агента для атаки targetEntity(намеченнойСущности) или фиксированной точки в пространстве. Он будет атаковать с оружием, и он не проверяет, есть ли у него боеприпасы. Оно реализует GameEntitySeenListener. Если не задана targetedEntity ни фиксированная точка в пространстве, поведение не будет активировано.

Простые поведения игрока реализованы здесь 2 поведениями для играбельности агента в MonkeyBrains.

SimplePlayerMoveBehavior — это общее поведение перемещения для агента, контролируемого игроком. Оно поддерживает AnalogListener. Вы должны добавить для него поведение и соответствующее имя при его использовании (оно должно быть таким же, как и в InputManager).

SimplePlayerAttackBehavior — это общая атака для агента, контролируемого игроком. Он поддерживает ActionListener. Он атакует огнём пуль в направлении щелчка мыши.

Реализация Пользовательского оружия

Оружие — самое важное в играх шутерах, поэтому MonkeyBrains позволяет легко реализовать пользовательское оружие, и оно хорошо интегрируется в фреймвор.

Основные понятия:

AbstractWeapon — абстрактный класс являющийся базовым классом для определения всего оружия, используемого агентами. Он расширяет GameEntity, поэтому вы можете использовать его как элемент, который вы можете найти на карте. Некоторые важные параметры для оружия:

/**
 * Название оружия.
 */
protected String name;
/**
 * Имя агента, к которому добавлено оружие.
 */
protected Agent agent;
/**
 * Максимальный диапазон оружия.
 */
protected float maxAttackRange;
/**
 * Минимальный диапазон оружия.
 */
protected float minAttackRange = 0;
/**
 * Урон от атаки оружием.
 */
protected float attackDamage;
/**
 * Сколько времени потребуется ждать для следующей атаки.
 */
protected float cooldown;

Методы, которые необходимо реализовать:

public abstract void attack(Vector3f targetPosition, float tpf);

/**
 * Метод для вычислений, если для оружия было выполнено требования
 * быть использованным. (Есть ли боеприпасы и.т.п.)
 *
 *Примечание: Не делайте проверку перезарядки, она уже проверяется этим фреймворком.
 *
 * @return true если используется, false в противном случае
 */
public abstract boolean isUsable();

/**
 * Метод для проверки есть ли здесь неограниченного использования оружия.
 *
 * @return true - неограниченное использование, false в противном случае
 */
protected abstract boolean isUnlimitedUse();

/**
 * Метод для уменьшения характеристик от оружия после использования. (патроны, мана, хп и.т.п.)
 */
protected abstract void useWeapon();

Поскольку в классе Agent нет слотов для оружия. Вам нужно будет реализовать Inventory(инвентарь) и поместить оружие внутри него. Не забудьте поместить все методы обновления оружия в метод обновления инвентаря. Если вы этого не сделаете, перезарядка оружия никогда не закончится.

AbstractBulletBasedWeapon — это абстрактный класс для оружия, который стреляет какой-то разновидностью пуль. Он имеет еще один параметр, и это AbstractBullet. Рекомендуется клонировать этои пули для целей оптимизации. Вместо поведения атаки вам необходимо реализовать:

/**
 * Настраиваем пулю, которая должна быть выпущена и придаем ей начальную скорость.
 *
 * @param направление
 * @param tpf
 * @return
 */
protected abstract AbstractBullet controlAttack(Vector3f direction, float tpf);

Поведение атаки уменьшит количество боеприпасов, сбросит перезарядку и добавит пулю в MonkeyAppState для обновления.

AbstractBullet — это базовый абстрактный класс для пуль в игре. Он также расширяет GameEntity. У него есть только один параметр, и это оружие, из которого им стреляли.

AbstractFirearmWeapon — это абстрактное оружие, которое имеет параметр numberOfBullets и имеет реализацию для всех методов, за исключением метода controlAttack(…).

AbstractEnergyWeapon аналогичен AbstractFirearmWeapon, за исключением того, что он имеет параметры:

/**
 * Количество энергии, необходимое для зарядки оружия. Установите значение -1, если бесконечно.
 */
protected int energyRequired;
/**
 * Источник энергии, который питает оружие.
 */
protected AbstractPowerSource powerSource;

AbstractPowerSource — это интерфейс для источника питания, используемого для AbstractEnergyWeapon. Он имеет методы:

/**
 * @param energyRequest(ТребуемаяЭнергия)
 * @return true если источник питания содержит в себе достаточно энергии.
 */
public boolean contains(int energyRequest);

/**
 * Уменьшая количество мощности в источнике питания.
 *
 * @param energyRequest(ТребуемаяЭнергия)
 */
public void consume(int energyRequest);

Реализация системы Очков Здоровья или Hit Points(Implementing Hit Points system)

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

Основные понятия

HitPoints — это интерфейс для реализации вашего собственного объекта HitPoints для вашей игровой сущности. Методы, которые должны быть реализованы:

/**
 * @return текущие очки здоровья игровой сущности.
 */
public float getCurrentHitPoints();

/**
 * @return максимальное значение очков здоровья для игровой сущности.
 */
public float getMaxHitPoints();

/**
 * Метод уменьшения очков здоровья.
 * @param damage(ущерб)
 */
public void decreaseHitPoints(double damage);

Каждая игровая сущность, которая должна быть уничтожена, должна иметь реализацию HitPoints.

SimpleAgentHitPoints — это класс, который реализует HitPoints. Он реализован для агентов.

HitPointsControl — это интерфейс для игровой сущности цели которая потеряет свои очки здоровья. Он реализует только один метод:

/**
     * Уменьшение hitPoints сущности цели на величину ущерба причинённого 
     * атакующей сущностью.
     *
     * @param target(цель)
     * @param damage(ущерб)
     */
    public void decreaseHitPoints(GameEntity target, float damage);

Пример:

MonkeyBrainsAppState.getInstance().setHitPointsControl(new MyCustomHitPointsControl());
Agent target = new Agent("target", createAgentSpatial());
target.setHitPoints(new SimpleAgentHitPoints(agent));
//уменьшение очков здоровья цели на 30f
MonkeyBrainsAppState.getInstance().decreaseHitPoints(target, 30f);

Реализация системы Инвентаря(Implementing Inventory system)

Фреймворк MonkeyBrains обеспечивает гибкую реализацию системы инвентаризации. Это означает, что программист может реализовать, как будут храниться оружие, предметы и подобные вещи.

Inventory — это интерфейс для реализации инвентаря (дух :)). Он содержит три метода:

/**
 * Метод для обновления все предметов инвентаря, таких как оружие и.т.п.
 *
 * @param tpf время на кадр
 */
public void update(float tpf);

/**
 * Возвращает общую массу инвентаря. Используется главным образом для управления поведением.
 *
 * @return
 */
public float getInventoryMass();

/**
 * Возвращает активное оружие. Это необходимо реализовать, если вы используете
 * SimpleAttackBehavior.
 *
 * @see SimpleAttackBehavior
 * @return
 */
public AbstractWeapon getActiveWeapon();

Пример:

Agent agent = new Agent("AgentWithInventory", createAgentSpatial());
agent.setInventory(new MyCustomInventory());

Система Событий в MonkeyBrains(Event system in MonkeyBrains)

MonkeyBrains поддерживает программирование на основе событий. Если агент видит что-то, то тогда GameEntitySeenEvent будет активирован, если он будет атакован, то будет активирован AgentHitEvent и.т.д. MonkeyBrains реализовал только основные события, и программист должен расширять этот список для своих нужд.

Основные понятия:

GameEntityEvent — это базовое событие для всех событий игровых сущностей. Рекомендуется расширять этот класс для всех ваших пользовательских событий.

GameEntitySeenEvent — это событие, связанное со всеми увиденными игровыми сущностями. Он используется в SimpleLookBehavior.

GameEntitySeenListener — это интерфейс для слушателей события GameEntitySeenEvent. Нет никакого слушателя по умолчанию, предоставленного MonkeyBrains, и настоятельно рекомендуется использовать EventListener в качестве базового интерфейса для слушателей.

Пример: Сделаем пользовательское события игровой сущности:

public class MyCustomGameEntityEvent extends GameEntityEvent {
    private GameEntity customEntity;
    
    public MyCustomGameEntityEvent(Object source, GameEntity gameEntity) {
        super(source);
        this.customEntity = gameEntity;
    }

    public GameEntity getCustomEntity() {
        return customEntity;
    }
}

Сделаем пользовательский слушатель:

public interface MyCustomGameEntityListener extends EventListener {
    public void handleMyCustomGameEntityEvent(MyCustomGameEntityEvent event);
}

Сделаем триггер поведения:

public class TriggerBehavior extends Behavior{
    private List<MyCustomGameEntityListener> listeners;

    public TriggerBehavior(Agent agent){
        super(agent);
        listeners = new ArrayList<MyCustomGameEntityListener>();
    }
    
    public void addListener(MyCustomGameEntityListener listener){
        listener.add(listener);
    }

    @Override
    protected void controlUpdate(float tpf) {
        ...
        //создадим MyCustomGameEntityEvent
        MyCustomGameEntityEvent event = new MyCustomGameEntityEvent(agent, customEntity);
        //перешлите его всем слушателям
        for (MyCustomGameEntityListener listener : listeners) {
            listener.handleMyCustomGameEntityEvent(event);
        }
    }    
}

Сделаем обработку поведения:

public class HandleBehavior extends Behavior implements MyCustomGameEntityListener{

    public HandleBehavior(Agent agent) {
        super(agent);
    }

    public void handleMyCustomGameEntityEvent(MyCustomGameEntityEvent event) {
        //сделайте что-нибудь с этим событием
    }

    @Override
    protected void controlUpdate(float tpf) {
        //код который обрабатывает поведение, обычно в игровом цикле
    }
}

Реализации основной логики:

Agent agent = new Agent("EventAgent", createAgentSpatial());
SimpleMainBehavior mainBehavior = new SimpleMainBehavior(agent);
TriggerBehavior triggerBehavior = new TriggerBehavior(agent);
HandleBehavior handleBehavior = new HandleBehavior(agent);
triggerBehavior.addListner(handleBehavior);
mainBehavior.addBehavior(triggerBehavior);
mainBehavior.addBehavior(handleBehavior);
agent.setMainBehavior(mainBehavior);

С помощью этого кода мы создали агент, который активирует определенное поведение, когда выполняются некоторые условия внутри него.

Реализация Пользовательский control-ов для игры

Класс MonkeyBrainsAppState с информацией об агентах и последовательности их поведения в игре. Нет необходимости использовать его, но он позволяет упростить обновление статуса игры. Содержит агенты и игровые сущности и обеспечивает общее управление ИИ. Он может быть расширен для реализации ваших собственных пользовательских операций, но это не рекомендуется. Вместо этого используйте GameControl.

GameControl — это интерфейс для игровых Control-ов, используемых в игре. Методы, которые необходимо выполнить:

/**
 * Добавить все inputManagerMapping которые будет использовать игроком.
 */
public void setInputManagerMapping();

/**
 * Добавить все настройки камеры, которые будут использоваться в игре.
 * @param cam 
 */
public void setCameraSettings(Camera cam);

/**
 * Добавьте все настройки fly camera(летающей камеры), которые будут использоваться в игре.
 * @param flyCam 
 */
public void setFlyCameraSettings(FlyByCamera flyCam);

/**
 * Метод для маркироваки конца. Должен так же останавливать игру.
 * @see Game#stop() 
 * @return 
 */
public boolean finish();

/**
 * Вычисление, выиграл ли агент игру.
 * @param agent
 * @return 
 */
public boolean win(Agent agent);

/**
 * Перезапуск всех параметров игры.
 */
public void restart();   

/**
 * Метод создания сущностей в заданной области.
 * @param gameEntity сущность, которая должна быть создана
 * @param область, в которой будет создана сущность
 */
public void spawn(GameEntity gameEntity, Vector3f... area);

Пример:

MonkeyBrainsAppState.getInstance().setGameControl(new MyCustomGameControl());

Смотрите так же:


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Этот репозиторий, это результат этапа проектирования фрймворка Atom в течение многих лет.

Мотивация

Он родился для того, чтобы помочь JME3 сосредоточиться на разработке игры/симуляции, и в дальнейшем продвигать последние и будущие Java-технологии, чтобы превратить разработку игр в предприятие/AAA в качестве конечной цели, но пойти на компромисс/совместимости с существующей инфраструктурой JME3 и OpenGL.

Его концепция, дизайн и архитектура вдохновлены игровым движком AAA и решением Enterprise Java.

Обзор

На этой странице:

  1. Цели Фреймворка Atom — это расширять JME3 и язык Java, чтобы они больше подходили для разработчика игр.
  2. Проблемы приложений в реальном времени (особенно игры).
  3. Решения и те базовые платформы, которые мы собираемся использовать для достижения этих целей.
  4. Оставшиеся работы и видение будущего

Цели дизайна

Общие цели

  • Гибкость: Игра | симуляции главное, но не навязанное!
  • Модульная: Введение зависимостей вместе с введением компонентов
  • Параллельно: Использовать параллельные вычисления
  • Следующий сведенья: Приходит с ещё развивающимися технологиями и мощью языка Java.
  • Облачность готова: Масштабирование до сети и распределённых вычислений
  • С простотой: У инструментов GUI почти везде, нулевые потребности в конфигурации
  • Перенос на устройства: Может работать на широком спектр устройств PC-мобильные-TV.. любое, на котором есть Java!

Дополнительные подцели

  • Минимальные зависимости и их наложение
  • Малый размер и эффективность

Слайды

Здесь вы можете просматривать слайды и загружать документы о дизайне фреймворка Atom.

Приложения реального времени

Дизайн Atom СОЗДАН одновременно для приложений в реальном времени, встроенных систем и ИГР

Атом в сравнении с Птолемеем

Как упоминалось в введении в фреймворк Atom, он действительно вдохновлен проектом Птолемей

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

В сравнении

Atom предназначен для СОЗДАНИЯ одновременно, приложений в реальном времени, встроенных систем и ИГР. Поэтому он больше фокусируется на генерации кода, профилировании, мониторинге; фокусируется больше на графике, физике, опыте игрока… и.т.д. В основе этого лежит довольно много концепций, собранных в Птолемее (не коде!).

Цели

Таким образом, цели двух проектов частично совпадать, но имеют разные точки зрения и образ мышления!

  • Atom не имеет ни малейшего отношения/интереса относительно математической/физической коррекции (то есть симуляции), но имеет интерес к графической стороне и общему поведению.
  • У Atom есть дух приложений с открытым исходным кодом… но не академический! Вы можете видеть, что Птолемей довольно «сложный и не очень популярный, поскольку он предоставлен в первую очередь для академической среды; в отличие от него, Atom предоставляется для каждого пользователя и для игры как для детей в песочнице.

Различия в методах:

  • Atom не просто зависит от четко определенных моделей!
  • Atom использует простые модели, а иногда проще значит лучше!
    • Модель Простые Сущности (Simplier Entity) (не абстрактный объект из Java)
    • Модель Актера(Actor) так же не абстрактная и на самом деле из потоковой среды
    • Никаких контрактов в Системе, изучение Системных связей и интерактивности не связано только с анализом данных, то есть системой мониторинга потока данных по рабочей системе.
    • Timming: из-за отсутствия интересов в математике / физике, модель времени и прецизионная модель не определены, но также и на платформе Java.
  • Те, что выше и основные методы, которые вы можете найти ниже, заставляют Atom быть менее независимым от Java, но также хорошо встраивают характеристики языков Java и среды времени выполнения. Так что можно сказать, что Atom строится прямо на вершине Java без колебаний!

Atom в сравнении с JScience

Atom также зависит от Javolution и имеет некоторые части из базы кода JScience, но в очередной раз цель, сосредоточение Atom в играх и симуляции … Также много методов Atom отличается от используемого в JScience.

Можно также видеть, что Atom полностью заполняет экспериментальный пакет игр JScience. :)

Целевые Устройства и Платформы

PC


Mobile


Android


Web


HTML5 и WebGL

Проблемы


Решения и Фреймворки и Платформы

В Java много хороших проектов с открытым исходным кодом уже предоставляют решения для различных задач в разработке программного обеспечения. Проблема в том, как склеить эти драгоценные камни соответствующим образом и привести к эффективному продукту хорошего качества — экономить время и усилия.

Сотни проектов с открытым исходным кодом …

Например, модуль AtomCore зависит от этих высококачественных библиотек:

  • JME3
  • Общие аннотации JSR Java:
  • Apache commons
    • Lang
    • Configurations
    • BeanUtils
    • Collections
  • Google’s
    • Guava:
    • Guice: Dependency injection
    • Snappy:
    • LevelDB
    • Auto

Другие требуют кусочков писать из sk

Платформа Atom для разработки дизайна

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

Игра и приложение в реальном времени


Кросс-игровые элементы

С уровня абстракции Game — это особый вид программного обеспечения (почти всегда):

  • составленные сущностями, и их Сцена;
  • где Действия происходят в цикле, процедура События;

Немного более подробно, Геймплей — это то, как игрок играет в Игру, имеет:

  • Logic:
    • Trigger: in which Conditions, active some appropriate Action, as primitive brick.
    • Rule: the laws, restrictions form the game rule which player, entities obey.
  • Routines: Situations/ Events/ Actions that happen in the game Cycle.
    • Story/Cinematic mode: When player just watch the game like a movie.
    • Interactive mode: When player interact with the game world
  • Control: The way player handle their entities
  • League:
    • Single: Infos, score, rewards stick to an individual
    • Multi: The way players join, left, make friend and interactive and play together…
  • Status: Way to pause/continue , save/load current game

«Программное обеспечение для игры» должно быть опубликовано в конкретной среде, а затем:

  • Configurations : appropriate settings for specific enviroment, device.
  • Data : appropriate size and format

Взаимодействие CPU-GPU


Java-родные взаимодействия


Тайминг


Цикл

Архитектура AtomCore

Ядро — это часть, которая фокусируется на игре и в режиме реального времени. Он объявляет

Здесь вы можете узнать больше об архитектуре Core. atomcore

Шаблоны проектирования и парадигмы программирования

Изучите эти надежные ресурсы, прежде чем мы углубимся в архитектуру Atom и где / почему / как они применяют каждый шаблон дизайна:

узоры

Вопросы программирования


Java, но не только Java

Большинство «Java-вещей в AtomCore — это Bean and Properties. Два чистых типа данных Java, которые очень полезны в игровом мире. Бин предназначен для моделирования игровых объектов и свойств для конфигурации.

Конечно, другие технологии Java также используются, но не упоминаются, потому что это не обязательно. Но Bean and Properties — это те технологии, которые сильно используются!

«Хорошие расширения Java, используемые в AtomCore 0.2+: Guava:

  • Принесите Eventbus, сеть в порядке
  • Коллекция, Свободный, функциональный синтаксис и вкус к Java.
  • Гуава также поддерживает операции кэширования, отражения и более низкого уровня

Guice: принести инъекцию зависимости, лучший модульный тест, рефакторинг в легкой манере.

Groovy — это JVM-язык и глубоко интегрирован с AtomCore, наиболее удобен как язык сценариев, но помнит, что он также может заменить Java или рассматривать как Java. Groovy также предлагает намного больше превосходных вещей.

Код и данные

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

В какой-то момент нам нужно найти решение для сокращения «ручной обработки данных + кода и поддержки». То, что «генерирующий код также можно рассматривать как своего рода данные, родилось. Это называется управляемой данными архитектурой (решением). В AtomCore 0.2 у него есть функции для поддержки этой тенденции.

Вокруг Bean

Это так важно, чтобы упомянуть, что каждая инфраструктура технологий Atom основана на технологиях Bean / POJO.

Например:

  • AtomEX : the bridge to AKKA Actor model also use POJO as its candidate
  • EJB leverage…
  • Fx: use POJO as its effect elements

Вот краткое объяснение, почему Bean / POJO выбрано основой ядра Atom?

Как построено в Java-технологиях, Bean / POJO является единственным «хорошим решением»:
** «мост» ** ** от Java OOP до COP **, ** от Java OOP до AOP **
также можно рассматривать как
** от объектно-ориентированного программирования до ориентированного на данные программирования **
** от объектно-ориентированного программирования до аспектно-ориентированного программирования **
или ** Код, но также Данные ** …

Программирование Polyglot

Я хочу Лучшее из обоих миров! (.. если это возможно?)

Atom в своей основе пытается быть полиморфизмом (программирование полигонов), с набором с ООП, КС, АОП или функциональным … Да, это Java, но хороший вид Java.

Поскольку AtomScripting и другие используют Groovy, поэтому он наследует (много) емкость полигонов от Groovy.

Читайте: http://groovy-lang.org/Polyglot+Programming+with+Groovy

Функциональное реактивное программирование


Программирование на основе потока


Компонентное программирование


Комбинированное программирование


Управляемый данными и управляемый моделью и управляемый доменом


Модульная архитектура

Я хочу повторно использовать (или СУХОЙ) !!

Посмотрите, например, на игру JME3, например, менеджер, если вы хотите, чтобы работа двух менеджеров была совместной, но не зависела друг от друга, или что, если вы хотите, чтобы государство направляло Менеджер делать что-то, но имело минимальную информацию о них …

Более абстрактно, всякий раз, когда у вас есть какая-то Служба, которая свободно зависит друг от друга, вы должны попробовать Injection Dependency http://martinfowler.com/articles/injection.html.

Вот где Guice помогает в большой картине.

Внедрение зависимости


https://code.google.com/p/google-guice/

Инъекция компонентов


http://wiki.apidesign.org/wiki/Component_Injection

Инъекция зависимостей VS Компонентная инъекция


http://code.imagej.net/gbh/lookup/DependencyInjectionandLookup.html

Охлаждение управления зависимостью

Итак, что круто в зависимости от приложения в реальном времени и игры, в которую включен Atom … Многое, но позвольте мне отметить несколько:

Взаимозависимость в реальном времени — это новая функция для разработки игр … Представьте себе, что даже игра просто может загружать часть активов, а другая задерживается или отсутствует, график зависимости может помочь игровому циклу продолжать работать, часть его в среднем время.

Фактически граф зависимостей можно рассматривать как топографическую структуру графика зависимостей активов JME до его создания, означает жесткие ссылки через ссылки. Теперь, даже когда график активов только частично загружен, игра может запускаться, потому что после этого известно, что разрешение на безопасность разрешает график активов и график сцены.

Возможности предприятия. Вы можете себе представить, как инфраструктура Atom имеет тенденцию к объединению JME-игры и универсальной сети. На самом деле это не так сложно. Потому что Java-технологии уже существуют. Многие из них построены на вершине инъекции зависимостей и инверсии контроля (или еще) … Мне действительно нравится инъекция зависимостей, но я не могу согласиться с тем, что я всегда должен сочетаться с IoC как таковой. Это будет обсуждаться позже в этой документации

Предприятия


Услуги, зависимость и развязка

Мир enteprise развивает модульную парадигму, чтобы связать сервисы (база данных, конфигурации, сетевые протоколы, сеть …) и помочь им работать вместе в одном приложении.

Доступные услуги

Попробуйте AtomEx

К базе данных


В другой репозиторий


К конфигурациям


В Интернете

Мониторинг и рабочий процесс разработки


Будущее видение


Ссылки и вдохновение

Конструкция фреймворка Atom ограничена:

  • Игровая книга
  • Серия игр для игр
  • ИИ Game Engine книга
  • Книга Мудрости ИИ

другой GameEngine, который я использовал:

  • UDK
  • Единство
  • CryEngineSDK
  • Двигатели JavaScript-игр: CraftyJs, GameQuery ..
  • Flash-игры: Starling,
  • … десяток двигателей с близким исходным кодом.

другие технологии Java:

  • EJB
  • весна
  • Groovy
  • Netbean
  • … hunread проектов с открытым исходным кодом

Приступают к работе.


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Открытая версия игры Dungeon Keeper 2

Цель: Полностью реализовать игру (версии 1.7 с 3 бонусными пакетами) в качестве открытой кросс-платформенной версии
— Минимум изменений;
— Использование оригинальных ресурсов игры.

Будущее: совершенствование графики и прочие ништяки.

Реализация: осуществляется в JAVA помощью JMonkeyEngine. В настоящее время мы используем JME 3.1 + JAVA 8

Проект размещен здесь: https://github.com/tonihele/OpenKeeper/

Группа проекта в ВК: https://vk.com/openkeeper
Группа проекта в Facebook: https://ru-ru.facebook.com/openkeeper/

Существует множество способов создания скинов с различными уровнями сложности и издержками. Программа Terragen упоминается как один из способов сделать это, но теперь она ограничена в использовании, и для коммерческих целей теперь не является бесплатной. Другая программа, Bryce, также не является бесплатной, и похоже затормозилась в разработке, есть только в 32-битной версии. Возможно, они что то делают за кулисами, но это всё же не бесплатная программа. Целью этих статей является предоставление пользователю JME3 других возможностей для создания skymap-ов с использованием бесплатных программ Blender и Gimp. Это ещё не все, все способы создания skymap. Если вы знаете лучшие способы или инструменты, то не стесняйтесь и делиться своей мудростью на форумах.

Blender

Используя Blender, вы можете бесплатно создавать свои skymap-ы. Как только вы настроите Blender, вы сможете достаточно легко создавать skymap-ы. В этой статье я подробно расскажу о том, как настроить Blender для создания Угловых Карт(Angular Map) на основе видеоролика, созданного jMonkey вкладчиком glaucomardano, и упомянутого в этом сообщении на форуме. Участник glaucomardano сделал хорошую работу на видео, но для этого требуется знание Blender, чтобы за ним можно было легко следовать. Таким образом, я переведу его на бумагу для вас. Вы можете просмотреть его видео, следуя этой ссылке: jMonkeyEngine Tutorial — Как создавать skymap-ы с помощью Blender.

Этот урок — это адаптация того что сделал вкладчик glaucomardanos, а не точное дублирование.
В этих шагах предполагается, что вы используете настройку Blender по умолчанию и применяете всё к версии 2.78c+.
Урок сделан в формате контрольного списка, чтобы вы могли отмечать галочкой пункты которые вы уже выполнили.

Blender Контрольный список Skybox

Начните с нового файла в Blender.

Выберите Лампу и куб который идёт по умолчанию в окне 3D-вида(3d view) или на панели Структура проекта(Outliner) (верхняя правая панель) и удалите их: X. Они не нужны для этой процедуры.

Выберите объект камеры в 3D-виде(3d view) или на панели Структура проекта(Outliner), затем очистите его положение и вращение, используя сочетания клавиш Alt+G и Alt+R.

На панели Свойства (правая нижняя панель) выберите вкладку Объект (оранжевый куб). Это даст вам визуальное представление параметров камеры, которые вы будете менять.

Переименуйте камеру на Camera-north.

Сделав активным окно 3D-вида(кликнув по нему) перейдите в ортогональный вид сверху, нажав NumPad5, а затем NumPad 7.

Если камера все еще выбрана, то нажмите R, а затем X, затем сделайте 90°, и затем нажмите Enter, чтобы камера повернулась на 90° градусов относительно оси X.

Если камера всё еще выбрана, нажмите Shift+D. Это позволит дублировать камеру. Затем нажмите Enter, чтобы задать выбор.

Сразу же нажмите R, затем сделайте 180°, и затем нажмите Enter. Это повернет камеру на 180° градусов относительно оси Z. Переименуйте эту камеру в Camera-south.

Если камера всё еще выбрана, нажмите Shift+D. Это позволит дублировать камеру. Затем нажмите Enter, чтобы задать выбор.

Сразу же нажмите R, затем сделайте 90°, и затем нажмите Enter. Это повернет камеру на 90° градусов относительно оси Z. Переименуйте эту камеру в Camera-west.

Если камера всё еще выбрана, нажмите Shift+D. Это позволит дублировать камеру. Затем нажмите Enter, чтобы задать выбор.

Если камера всё еще выбрана, нажмите R, затем сделайте -180°, и затем нажмите Enter. Это повернет камеру на -180° градусов относительно оси Z. Переименуйте эту камеру в Camera-east.

Сделав активным окно 3D-вида(кликнув по нему) перейдите в ортогональный вид справа, нажав NumPad3.

Выберите Camera-south в 3D-виде или в панели Структура проекта(Outliner) (верхняя правая панель) и нажмите Shift+D. Это позволит дублировать камеру. Затем нажмите Enter, чтобы задать выбор.

Если камера все еще выбрана, нажмите R, затем сделайте 90°, и затем нажмите Enter. Это повернет камеру на 90° градусов относительно оси X. Теперь камера должна указывать вверх. Переименуйте эту камеру в Camera-up.

Если камера всё еще выбрана, нажмите Shift+D. Это позволит дублировать камеру. Затем нажмите Enter, чтобы задать выбор.

Если камера все еще выбрана, нажмите R, затем сделайте 180°, и затем нажмите Enter. Это повернет камеру на 180° градусов по оси X. Теперь камера должна быть направлена ​​вниз. Переименуйте эту камеру в Camera-down.

Сохраните ваш файл.

Угловая Карта(Angular Map)

Откройте веб-браузер и выполните поиск «free high res skymap»(бесплатные skymap с высоким разрешением) и выберите Угловую Карту(Angular Map) на свой вкус, или вы можете найти её здесь на странице Бесплатные skymap-ы в высоком разрешении вместо этого. Не забудьте выбрать ANGULAR map.

Сохраните карту на свой компьютер в месте, где вы позже сможете её легко найти для использования в Blender.

Вкладка Текстура

На панели Свойства (нижняя правая панель) выберите вкладку Текстура (красно-белая шахматная доска), затем нажмите кнопку Создать, чтобы создать новую текстуру. Переименуйте эту текстуру в AngMap.

На панели Изображение выберите Открыть и перейдите к файлу, который вы сохранили ранее.

На панели Отображение выберите Угловая Карта(AngMap) из раскрывающегося списка.

На панели Влияние уберите галочку напротив Смещение и поставьте галочку напротив Горизонт.

Вкладка Данные

На панели Свойства выберите вкладку Данные (Камера с 2 катушками).

Измените Фокусное расстояние с 35 на 16. Сделайте это для каждой камеры.

Вкладка Визуализация

На панели Свойства выберите вкладку Визуализация (фото камера).

На панели Размеры установите разрешение на любое число, кратное 2. Для этого примера 1024 x 1024.

Сместите масштаб разрешения до 100%.

На панели Вывод измените тип изображения на JPEG. Я обнаружил в результате проб и ошибок, что использование файла JPEG имеет то же качество изображения, что и файл PNG или DDS, но с огромной разницей в размере изображения. Одно PNG-изображение будет отображаться в формате более 8 Мбайт для JPEG будет размер 325 КБ. Даже конвертация в файл DDS поставляется со скоростью более 3 Мб для сравнения (используйте RGB888, как это рекомендовано разработчиком Momoko_Fan/Core Developer на этой ветке форума), без улучшения качества изображения, как я мог видеть. При желании вы можете самостоятельно поэкспериментировать.

Задайте формат изображения в RGB.

Настройка ползунка Качество влияет на уменьшение размера изображения, где 0 — максимальное сжатие, а 100 — нет сжатия. В этом примере зададим 0 уменьшив размеры изображений до менее 125 кбайт, еще раз без заметного ухудшения изображения, как я мог видеть.

Вкладка Окружающая среда(мир)

На панели свойств выберите вкладку Окружающая среда(мир).

Поставьте галочку Реал. небо.

Настройка генератора карт

Наведите курсор мыши в место между окном 3D-вида и заголовке Информация в верхней части окна. Когда появится знак курсора в виде двунаправленной стрелочки сделайте клик [ПК Мыши] в появившемся меню выберите Разделить область и разделителем поделите окно 3D-вида как вам удобно(лучше на пополам).

В одном из двух полученных окон 3D-вида, с лева внизу рядом со словом Вид, находится кнопка Текущий тип редактора для этой области. Нажмите её и измените на Редактор UV/изображений.

В нажмите кнопку [Выбрать разметку экрана](на рядом со словом Default в панели Информация) Нажмите знак [+], чтобы создать новую разметку.

Переименуйте эту новую разметку в Angular Map Generator или как сами захотите.

Наведите указатель мыши на правую сторону окна 3D-вида и нажмите NumPad5, чтобы переключится в орто-представление. Теперь вы настроили визуализацию своей угловой карты.

Сохраните файл.

Визуализация(Рендериг) и Сохранение

Выберите вашу первую камера (в данном случае Camera-down) и вашей мышь сделан активным оно 3D-вид кликом по его правой части, нажмите Ctrl+NumPad0, чтобы сделать выбранную камеру, активной камерой.

Нажмите F12, чтобы визуализировать сцену. Изображение появится в окне Редактор UV/изображений.

С помощью [СК мыши] в Редактор UV/изображений вы можете прокруткой увеличивать или уменьшать изображение, и кликом перемещать его.

Сделав активным окно Редактор UV/изображений кликнув по нему нажмите F3, чтобы сохранить изображение. В этом случае мы переименуем изображение в down.jpg.

Сделайте то же самое для остальных камер. Визуализация, переименование и сохранение каждого изображения. После того, как вы сделали все свои изображения, вы можете скопировать и вставить их в свою папку игровых ресурсов(Assets) JME3. Обычно в папке Assets/Texture.

Чтобы использовать ваши изображения в вашем коде, в simpleInitApp() загрузите текстуры и используйте SkyFactory для создания своего неба.

Texture west = getAssetManager().loadTexture("Textures/Sky/west.jpg");
Texture east = getAssetManager().loadTexture("Textures/Sky/east.jpg");
Texture north = getAssetManager().loadTexture("Textures/Sky/north.jpg");
Texture south = getAssetManager().loadTexture("Textures/Sky/south.jpg");
Texture up = getAssetManager().loadTexture("Textures/Sky/up.jpg");
Texture down = getAssetManager().loadTexture("Textures/Sky/down.jpg");
getRootNode().attachChild(SkyFactory.createSky(getAssetManager(), west, east, north, south, up, down));

Большое спасибо вкладчику glaucomardano за его видео. У него отличный вкус в музыке.

Ниже перечислены другие обучающие материалы по Blender. Для пользователей JME3 могут оказаться полезными.

Gimp

Вы можете использовать Gimp для создания SkyMaps из одного изображения с добавлением 2 скриптов.

Скачав скрипты скопируйте их в одну из этих папок:

  • Windows (пользовательский): C:\Users\«ваше имя пользователя»\.gimp-2.6\plug-ins
  • Windows (системный): C:\Program Files\GIMP-2.0\lib\gimp\2.0\plug-ins

После установки скриптов вы открываете изображение в gimp. Этот скрипт работает, разрезая изображение на 6 слоёв равного размера, каждый кратен 2.

  1. После того, как вы откроете изображение, вы выберите Фильтры ▸ Общие ▸ Cubemap Layers Generator.
  2. Заполните детали следующим образом.
    • Source: перейдите к изображению, которое вы нарезаете.
    • Cubemap layout: Cross Horizontal
    • 2 to the power of: 10 (для Слоев размером 1024)
  3. Нажмите [OK], чтобы разрезать изображение.
  4. Выберите Файл ▸ Экспортировать как и измените имя и тип файла на .dds. Выберите место сохранения, обычно Assets/Textures.
  5. Нажмите Экспортировать.
  6. Откроется панель DDS. Примените следующие настройки:
    • Compression: None
    • Format: RGB8
    • Save: As cube map
    • MipMaps: No mipmaps
  7. Нажмите [OK] для экспорта.

Вы прикрепляете экспортированное изображение к своей сцене, как это объясняется в уроке как добавить небо в вашу Сцену.


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

ИЗУЧЕНИЕ GROOVY

Синтаксис

Синтаксис Groovy может быть гораздо более компактным, чем Java. Например, объявление в стандартном Java 5+:

 for (String it : new String[] {"Rod", "Carlos", "Chris"})
     if (it.length() <= 4)
         System.out.println(it);

может быть выражено в Groovy как:

 ["Rod", "Carlos", "Chris"].findAll{it.size() <= 4}.each{println it}

То, что вы видите в круглых скобках \ {\}, является магическим *Закрытием*. Что делает Groovy настолько мощным, что он сравним с Java! Мы узнаем об этом позже.

Официальное описание синтаксиса

http://groovy-lang.org/For+those+new+to+both+Java+and+Groovy

http://groovy-lang.org/User+Guide

http://groovy-lang.org/Logical+Branching

http://groovy-lang.org/Looping

В качестве обзора для вас, я расскажу о наиболее важном в синтаксисе языка:

Логическое ветвление


Циклы


Операции


Основные языковые функции

Помимо элегантного синтаксиса, Groovy имеет функции, которые вы всегда хотите, чтобы были в Java. Ниже под подпунктами, называют несколько функций, которые действительно помогают в программировании игры:

Перегрузка операций(Operation overload)


Коллекции(Collections)


Reflection


Закрытие(Closure)

Об этом подробнее здесь:

http://groovy-lang.org/Closures

http://groovy-lang.org/Closures-Informal+Guide

http://groovy-lang.org/Closures-+Formal+Definition

Что такое Закрытие(Closure)?

Groovy Closure — это как «блок кода или указатель метода». Это часть кода, которая определяется и затем выполняется в более поздней точке. Он имеет некоторые специальные свойства, такие как неявные переменные, поддержку currying и поддержки свободных переменных (что мы рассмотрим позже). На данный момент мы будем игнорировать подробные подробности (см. Формальное определение, если вам это нужно) и посмотрим на некоторые простые примеры. Простой пример

def clos = { println "hello!" }

println "Executing the Closure:"
clos()                          //печатает "hello!"

Обратите внимание, что в приведенном выше примере «hello»! печатается, когда вызывается Closure, а не когда оно задаётся.

и Подводные камни

http://groovy-lang.org/Differences+from+Java

Метапрограммирование

Вот и получилось! Для тех, кто помнит о силе ключевого слова prototype в JavaScript, вы можете делать то же в Groovy. Для тех, кто даже не знает, что такое JavaScript и метапрограммирование — это способ расширения класса с помощью методов и атрибутов «на лету».

Groovy Builder – SwingBuilder

Groovy использует большую мощь шаблона Builder.

В нижеприведенных фрагментах я покажу, что вы пишете несколько builders, чтобы ускорить ваш JME код. Полный список Builder приведен в частях 3 и 4.

Language comperation


JavaScript comperation


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Событие — Триггер — Менеджер(Event – Trigger — Manager)

Event, Trigger, Manager — это 3 наиболее важных термина в программировании игр и сценариях, которые стоит упомянуть в первую очередь!

Событие

Событие(Event) — это основной элемент (концепция) и единица Системы Обмена Сообщениями. Когда компоненты в игровом мире общаются друг с другом по средством конвейеров, Сообщения из одного объекта должны быть отправлены другим объектам, и также тем что должны знать об этом (для осведомления), когда что-то происходит. Это называется событием(event); и внутри события содержится фрагмент информации, которая описывается в Сообщении.

Если написании Сценариев(Скриптов), События также играют важную роль. Это почти показано в EventHook для цикла как: onInit, onUpdate, onClick.. Он также обеспечивает ad-hoc-связь, которая, в свою очередь, обеспечивает дополнительное сложное и гибкое использование существующих систем. Позже мы углубимся в его использование.

EventBus

Парадигма Событий реализуется по-разному в каждом программном обеспечении, где они требуются, в соответствии с конкретными требованиями. В последнее время у нас (мир Java) есть нечто довольно общее, чем набор для широкого круга случаев и использование интуитивных методов для программирования. Это называется EventBus. И, к счастью, EventBus отлично вписывается в решение для создания скриптов Groovy (kudos!)

… и несколько Событий. Разъяснения!

http://code.google.com/p/guava-libraries/wiki/EventBusExplained

http://codingjunkie.net/guava-eventbus/

Groovy Eventbus HelloWorld

import com.google.common.eventbus.EventBus
import com.google.common.eventbus.Subscribe

EventBus eventBus = new EventBus();

class EventHandler{
    @Subscribe
    public void hello(CustomEvent event){
        println "Hello world"
    }
}

class CustomEvent{

}
handler = new EventHandler();
eventBus.register(handler);

eventBus.post(new CustomEvent());

В этом примере,

Здесь EventBus будет воспроизводить средства коммуникации медиа в нашем решении для создания скриптов Groovy.

Hooks

Триггер

Если вы собираете свой игровай мир activites от Action, Trigger — это кирпич для создания Gameplay. Это комбинация: Event + Condition → Action(Событие + Условие → Действие)

Концепция триггера может быть описана следующим образом: Когда произойдет Событие, если он прошел проверку Условия, Действие выполняется. Более подробное описание envolve: Enviroment(Окружение) — определенный Context этих 3 (E — C — A), Durations(Продолжительности), Threaded или нет, Executor … Но здесь мы относимся к самой простой форме, Context является единственной конкретной процедурой Event и его Global(Глобальной) осведомленности.

В Groovy,

  • Событие основано на Классах, упомянутом в главе выше.
  • Условие может быть смоделировано с Closure(Закрытием)
  • Действие также очень подходит для представления Closure.

Пример1:

Пример1:

Пример2:

Пример2:

Manager

Менеджер — это концепция того, кто имеет ответственность и власть над другими (как над детьми или работниками в реальном мире), по сути это список своих потомков, и он имеет основные операции, такие как добавление, удаление для управления этим списком … Вы также можете думать об этом как о контроле над парадигмой MVC, где он является посредником между моделью и представлением. В JME3 вы видите «Менеджер» где по всюду, например AssetManager, StateManager в качестве wraper базовых функций. Таким образом, в событии переплелось довольно много понятий сразу, Менеджер в написании Сценариев чрезвычайно полезен и полностью заполняет недостающую часть картины, которую мы рисуем уже какое-то время здесь.

  • Чтобы немного устранить туман путаницу о смешанных концепциях, есть несколько практических советов о реализации Менеджера:
    • Менеджер действует глобально, удобно: обычно Singleton или действительно просто ссылается в скрипте
    • Менеджер обертывает базовые данные интуитивным способом
    • Менеджер имеет общую информацию
    • Выполняется менеджеров часто: например, в цикле обновления по умолчанию
    • У менеджера есть власть над своими потомками: его управляет своими потомками; в почти сценариях потомок покидая список своего Менеджера, становится пустым (как null)

Все концепции и wisdoms описывают гораздо более подробно в Дизайне Игровой архитектуры и Дизайне фреймворка Atom.

GQuery

Подставка GQuery для «JQuery как в Groovy. JQuery — известная фреймворк в мире JavaScript и Web. GQuery пытается предоставить некоторые из своих возможностей, immtimate свой синтаксис и сладости, использовать Groovy:

  • Query, выберите Дерево Узлов (например, HTML, Nifty, …) с минимальным синтаксисом Path, таким же, как XPath
  • Крючки к событиям Узла (компоненты, элементы ..) с помощью Eventbus

GQuery является частью моих дополнений для NiftyGUI, другое:

  • Собрать Groovy
  • CSS (каскадный стиль) реализация (для NiftyGUI), даже МЕНЬШЕ
  • упрощенный фреймворк Локализации
  • много для напичания сценариев и функциональные сладости для NiftyGUI

Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Willem H. de Boer, whdeboer@iname.com
Проект E-mersion, Октябрь 2000, http://www.connectii.net/emersion

1 Введение

Существует множество алгоритмов, которые ускоряют рендеринг данных-ландшафта, варьирующиеся от простого древа квадрантов на основе отсечения по пирамиде видимости невидимых клочков местности, до использования наборов окклюдера для использования (постоянно действующих) Уровней Детализации. Все эти технические приемы служат для уменьшения количества треугольников, которые будет нужно пропихнуть через конвейер рендеринга. Многие алгоритмы рендеринга ландшафта используют комбинацию нескольких из этих методов, чтобы еще больше ускорить рендеринг. Известными примерами являются алгоритм ROAM [3], Lindstrom и другие. Статья SIGGRAPH 96 о рендеринге ландшафта [1] и подход NDL к рендерингу ландшафта, который будет использоваться в играх реального времени [2]. Большинство из этих алгоритмов были изобретены (задолго) до того, как аппаратное рендеринг стал стандартом отрасли, и поэтому они могут быть больше непригодны для использования в сочетании с аппаратным 3D рендерингом. Поэтому необходимо найти новые алгоритмы, которые принесут наилучшие результаты при использовании вместе с аппаратным 3D-рендерингом. Поскольку аппаратные средства для 3D способны обрабатывать и отображать большое количество треугольников на кадр, алгоритм может прибегать к более консервативным методам отсечения, тем самым не обязательно обеспечивая «идеальный набор» данных рендеринга, и увеличивая количество треугольников по конвейеру с помощью аппаратного обеспечения, можно уменьшить нагрузку на процессор. Я придумал метод, который соответствует сказанному выше, и, насколько я знаю, раньше не использовался. Алгоритм реализован как часть проекта E-mersion. В этой статье будет дано полное описание алгоритма.

2 ОБЗОР

В этом разделе описывается полностью весь алгоритм, который разбит на три отдельные части. В первой части будет дано описание представления данных ландшафта, способы его организации в памяти и способы его создания. В разделе 2 рассказывается в основном об отсечении по пирамиде видимости «клочков» ландшафта, а в последнем разделе описываются GeoMipMap-ы использующий стандартный подход mipmap для текстур, как аналогию.

2.1 Представление данных ландшафта

Представление ландшафта может быть сделано несколькими способами, но я решил использовать тот, который используется большинством современных механизмов рендеринга местности. Причина, по которой я выбрал это представление, состоит в том, что оно 1) просто в реализации, 2) требует минимум места на жестком диске, 3) подходит для GeoMipMap-ов, как объясняется в разделе 2.3. Одним из недостатков этого представления (которое в основном является 2D решением) является то, что оно не поддерживает «выступы».

Terrain-MipMapping-1

Рисунок 1: Представления блоков сетки(mesh) ландшафта, вид сверху. Белые круги представляют собой вершины сетки. Линии представляют собой соединения между вершинами. Обратите внимание, что каждый четырехугольник (или квадрат) построен из двух треугольников.

Ландшафт выкладывается в клетки(grid) вершины которых имеют фиксированном расстоянием между ними. Горизонтальное и вертикальное число вершин в клетке должно иметь вид 2n + 1, где n ∈ [1, →). Это приводит к 2n четырёхугольнику, у которого 4 вершины являющиеся его углами, это вершины его соседних четырёхугольников. Каждый четырехугольник состоит из 2 треугольников, которые используются в качестве примитивов рисования, которые конечном итоге отправляются в графический конвейер.
Каждому x и z компоненту вершины устанавливается фиксированное значение, которое не будет изменятся в течение всего процесса. Компонент вершины который представляет собой значение-высоты ландшафта для какого то конкретного места, считывается во время загрузки ландшафта — из 8-битного растрового файла, состоящего из градаций серого цвета, который имеет размеры изображения пропорциональный клеткам(grid) ландшафта. Затем все клетки ландшафта разрезается на то, что я называю, блоки ландшафта, которые имеют фиксированный размер и должны иметь вид 2n + 1 ( n ∈ [1, →) ). Это шаг предварительной обработки, отсечения по пирамиде видимости для древа квадрантов, этап который будет разъяснён позже, и эти блоки также будут служить примитивами уровня 0 для GeoMipMap-ов (см. Раздел 2.3). На рисунке 1 показан такой блок ландшафта, который имеет 5×5 вершин. Размер блок ландшафта можете выбран какой захотите. В реализации E-mersion это 17×17, что я считаю самым быстрым, учитывая мой общий размер ландшафта-клеток 257×257. С этим нужно эксперементировать.
Одним из преимуществ этой компоновки в виде блоков ландшафта является то, что любой такой блок может быть оптимизирован для рендеринга, используя один вызов примитива-рисования для всего блока и, еще лучше, с помощью индексации, чтобы избавиться от множественного преобразования вершин. Небольшим недостатком блоков ландшафта является то, что вершины каждого из четырех ребер являются общими с соседними блоками ландшафта, и поэтому они будут преобразованы дважды.

2.2 Базовое отсечение по пирамиде видимости

Поскольку значительные части ландшафта не будут видны (т.е. не внутри пирамиды видимости) в определенных местах расположения камеры, то их не нужно рендерить, и поэтому их нужно заранее отсекать, чтобы избежать ненужных вычисления. Метод, который оказался достаточно эффективным для быстрого отсечения, представляет собой структуру данных называемую древом квадрантов(quadtrees). Я не собираюсь объяснять что такое древо квадрантов здесь, потому что в Интернете есть много хороших уроков/заметок/статей.

Древо квадрантов генерируется во время загрузки ландшафта и состоит только из 3D ограничивающих параллелепипедов. Размер ландшафта вместе с размером блоков ландшафта определяет возможную глубину древа квадрантов. Узлы древа квадрантов состоят из 3D ограничивающих параллелепипедов, которые внутри содержат ограничивающие параллелепипеды для всех под-деревьев узла, где — на листе — можно найти блока ландшафта, ограничивающий параллелепипед. Каждый лист имеет ссылку на блок ландшафта, который содержит ограничивающий параллелепипед. Причина, по которой древо квадрантов используются вместо октодрева, заключается в том, что компоновка ландшафта по существу происходит в 2D, и поэтому нам может быть достаточно 2D пространственной организационной схемы.
Блоки ландшафта помечаются как видимые, когда ограничивающий параллелепипед листа, хотя бы, частично находится внутри пирамиды видимости. После того, как вы закончите спускаться по древу, начнётся отсечение от корневого узлу дерева, и исходя из пометок блоков ландшафта видимые или невидимые, мы остаёмся только с набором блоков ландшафта, которые можно сразу отправить в графический конвейер. Поскольку сложность ландшафта может быть довольно высокой, мы не получим желаемую частоту кадров, если ландшафт не очень мал; описанный выше метод не будет достаточным для ландшафта высокой сложности (например, большые клетки). Здесь я введу новый термин: GeoMipMap-ы. Они делают этот метод рендеринга местности уникальным и подходящим для аппаратного 3D-рендеринга, как я опишу дальше.

2.3. Аналогично Mipmap текстурированию (введение в GeoMipMap-ы)

Блоки ландшафта, которые находятся далеко от камеры, нет необходимости рендерить с тем же количеством деталей, что и блоки ландшафта, которые находятся рядом с камерой. Они могут быть аппроксимированной версией с более низким разрешением, тем самым резко уменьшая количество треугольников и увеличивая скорость рендеринга. Это называется Уровнем Детализации(Level of Detail), он используется во многих современных алгоритмах рендеринга ландшафта. Многие из современных алгоритмов быстрого рендеринга данных ландшафта используют некую схему уровня детализации, хотя многие из них относятся к методам расчёта-треугольников, которые не подходят для использования в сочетании с аппаратным 3D рендерингом. Чтобы уменьшить нагрузку на CPU, мы должны выполнить уровень детализации на более высоком уровне, и это — то, где и появляются GeoMipMap-ы.
Рассмотрим обычные технические приемы mipmapping для текстур [5]. Цепочка mipmap-ов создается для каждой текстуры, первый элемент в цепочке — это оригинальная текстура, а каждый последующий элемент является копией предыдущего элемента, но уменьшенным до половины его разрешения, и так до тех пор, пока не будет достигнуто желаемое количество элементов (уровней). Когда текстура находится на определенном расстоянии, d, от камеры, выбирается соответствующий уровень из цепочки mipmap, и он используется для рендеринга вместо оригинальной текстуры с высоким разрешением. Мы также можем применить этот подход к 3D сеткам, где эквивалент 3D-текстуры с высоким разрешением — это блок ландшафта, а mipmap-ы вычисляются путем масштабирования блока ландшафта. На рисунке 2 показан блок ландшафта и его прямой наследник в цепи mipmap. Эта цепочка может быть расширена другим элементом в цепочке, который имеет еще более низкое разрешение. Мы можем заранее рассчитать и хранить эти GeoMipMap-ы в памяти, это будет хорошим плюсом, во время загрузки.

Далее в этой статье будет использовать термин GeoMipMap уровня 0 для блока ландшафта с разрешением по умолчанию, и уровень GeoMipMap N ( N ∈ [1, →) ) для каждой из версий с низким разрешением.

Terrain-MipMapping-2

Рисунок 2: Разрешение по умолчанию блок сетки(mesh) ландшафта слева (GeoMipMap уровня 0) и версия с низким разрешением (GeoMipMap уровня 1) справа. Обратите внимание, что также может быть сгенерирован GeoMipMap уровня 2.

2.3.1 Выбор нужного уровня GeoMipMap

В этой части я хотел бы объяснить определение того, какой уровень GeoMipMap использовать, для того или иного расстояния (d). Использование заранее заданного (фиксированного) d для каждого уровня GeoMipMap приведет к появлению нежелательных дефектов в виде трещин. Если просто переключится с одного уровня на другой, при фиксированном d без каких-либо условий, то вы увидите как произойдёт резкое изменение геометрии; вершины добавляются при уменьшении уровня GeoMipMap, и вершины убираются при увеличении уровня GeoMipMap. Посмотрите еще раз на Рисунок 2. При переключении с уровня 0 на уровень 1 белые вершины уровня 0 будут удалены, так будет получен уровень 1. Это делает ландшафт неправильным, и выглядит он при осмотре с близкого расстояния, не таким каким вы его создали. Когда вы смотрите на текстуры с mipmapping, при отношении пикселя к текселю текущего mipmap не больше 1:1, алгоритм выбирает только mipmap с более низким разрешением, это происходит при определенном d [5]. Точный расчет для выбора нужного уровня происходит в аппаратном обеспечении, и на самом деле для нас сейчас не имеет значения. Мы можем применить эту же концепцию и к GeoMipMap-ам, чтобы уменьшить появление дефектов.

При переключении уровня GeoMipMap с 0 на 1 уменьшится детализация, что приведет к неправильности или ошибкам в блоке ландшафта. Это вызвано удалением вершин, что приведёт к изменению высоты для δ, как показано на Рисунке 3. Этот δ будет менее заметным, чем больше будет d из-за перспективы. Мы также можем проецировать δ на эквивалентную ему длину в пикселях пространства экрана (которую мы будем называть ε), что в конечном итоге будет то что пользователь заметит. Когда ε превышает определенный предел τ, скажем, 4 пикселя, ошибки будут слишком заметны, и поэтому не разрешается переключаться на более высокий уровень GeoMipMap до тех пор, пока ε не станет меньше τ. Но поскольку каждый уровень GeoMipMap состоит из некоторого количества этих ошибок (по одной на каждую удаленную вершину), мы должны как-то вычислить, какой δ взять, для проецирования пикселей экрана. Принимая получившийся δ из max{δ0, …, δn-1} (где n — количество δ в GeoMipMap) для вычисления ε, мы рассмотрели наихудший сценарий. Если это конкретное значение меньше, чем τ, то все значения ошибок GeoMipMap будут меньше, чем τ. И наоборот, если это значение будет выше чем τ, то будет как минимум, одна ошибка, которая слишком заметна (эквивалентно, больше, чем τ). Мы выбираем это δ при создании GeoMipMap и храним его. С этого момента, если упоминается δ или ε, подразумевается max{δ0, …, δn-1} и max{ε0, …, εn-1} GeoMipMap-ов (n — число значений δ или ε в GeoMipMap) ,

Terrain-MipMapping-3

Рисунок 3: Ошибка(искажение) вершины, если смотреть сбоку. Пунктирная линия представляет собой изменение-высоты (δ) геометрии, которое произойдет при удалении белой вершины. Точное значение изменения-высоты можно вычислить, вычитанием белой вершины из серой вершины. После удаления белой вершины позиция вершины будет в воображаемой серой вершине.

Чтобы узнать, целесообразно ли переключиться на более высокий уровень GeoMipMap для данного d, мы должны сравнить ε с τ для текущих GeoMipMap-ов. Если верно неравенство ε > τ, то переход на более высокий уровень приведет к слишком большому искажению. Если это не так, то разрешено переключиться на один уровень выше. Обратите внимание, что следующий уровень GeoMipMap-ов ε также ниже, чем τ, и в этом случае мы можем использовать этот уровень GeoMipMap и.т.д., до тех пор, пока не будет достигнут правильный уровень.
Правильное неравенство для решения вышеупомянутого условия можно найти в [1]. Эквивалент 3D-пространства, который использует 3D-эквивалент τ, можно найти в [4]. Формула, упомянутая в [1], вычисляет точное значение для ε с любой данной точки зрения относительно GeoMipMap. Чем выше наклон вектора просмотра камеры относительно GeoMipMap, тем меньше будет ε, и чем выше вероятность использования более высокого уровня GeoMipMap. Это означает, что, если вектор направления камеры параллелен плоскости x/z (то есть горизонтальный), ε будет иметь наивысший уровень. Мы можем использовать этот факт, чтобы ускорить наш алгоритм определения уровня GeoMipMap.

2.3.1.1 Ускорение определения уровня GeoMipMap

Из-за способности 3D-аппаратных средств прорисовки отображать большое количество треугольников, нам больше не нужно вычислять «идеальный набор» треугольников. Если использовать менее точный алгоритм, то в результате будет меньше нагрузка CPU, поэтому лучше использовать его, поскольку лучше создать лишние треугольники что бы тем самым высвободить дополнительные ресурсы CPU. Цель состоит в том, чтобы отправить столько треугольников на оборудование, сколько оно может обрабатывать.
Вернемся к определению уровня GeoMipMap. Вычисляя ε и сравнивая его с τ для каждого видимого GeoMipMap, каждый кадр может быть очень интенсивно обработан. Поскольку, согласно [1] и [4], ε зависит от d и положения камеры относительно положения GeoMipMap, нет способа предварительно вычислить ε для каждого d и поместить его в Look-Up Table ( LUT)(Таблица поиска). Но если мы будем рассматривать вектор направления камеры как постоянно горизонтальный (т.е. не вертикальный наклон), мы можем предварительно вычислить ε. Это не даст точных результатов в тех случаях, когда вертикальный наклон вектора просмотра камеры относительно GeoMipMap высок; он обычно будет использовать слишком высокую детализацию для этих случаев с высоким наклоном, но как мы читали в предыдущем параграфе это не должно быть проблемой. Мы существенно снизили нагрузку на CPU до минимума.
Чтобы еще больше ускорить эту обработку, во время загрузки-ландшафта вычисляются дополнительные поля данных в каждом уровне GeoMipMap, которое устанавливается на минимальный d (D), на котором этот уровень может использоваться. Теперь, что бы выбрать нужный уровень для каждого видимого GeoMipMap для каждого кадра, достаточно сравнить d с Dn (где n — уровень GeoMipMap-ов). См. Рисунок 4 с псевдокодом.

For Each GeoMipMap Level N             Для Каждого GeoMipMap Уровня N
    Compare L to DN                        Сравните L с DN
    if (L > DN) store to RESULT            Если (L > DN) сохраните в RESULT
End For                                Конец Для
return RESULT                          возврата RESULT

Рисунок 4: Псевдокод для выбора соответствующего уровня GeoMipMap, где L — расстояние от камеры до центра GeoMipMap в 3D, и D это Dn.

2.3.1.2 Предварительное вычисление d

Чтобы предварительно вычислить Dn для каждого уровня GeoMipMap, используйте уравнение, из Уравнение 1. Это уравнение принимает δ как параметр и возвращает соответствующую Dn. Обратите внимание, что желательно иметь симметричную пирамиду-видимости, которая должна быть настроена с использованием параметров (l, r, b, t, n, f) пирамиды-видимости.

 

Dn = |δ| · C

 

Уравнение 1: Используйте это уравнение что бы вычислить Dn для δ. C — константа, которая может быть вычислена с помощью уравнения, которое вы найдёте в Уравнении 1a)

 

C =
A
T

 

Уравнение 1a), используется для вычисления C. A и T, являются константами, которые могут быть вычислены с помощью уравнений, которые вы найдёте в 1b) и 1c) соответственно.

 

A =
n
|t|

 

Уравнение 1b), используемое для вычисления A. n — это плоскость отсечения, а t — верхняя координата плоскости отсечения, как в (l, r, b, t, n, f)

 

T =
2 · τ
vres

 

Уравнение 1c), используется для вычисления T. vres — это разрешение экрана по вертикали в пикселях.

Чтобы сохранить инструкцию с квадратным корнем каждый раз, когда расстояние от текущей камеры до GeoMipMap должно рассчитыватся в режиме реального времени, Уравнение 1 можно переписать, как показано в Уравнение 2. Предварительное вычисление Dn2 в режиме реального времени будет вычислять уменьшение расстояние от GeoMipMap до камеры d2 = (ex — cx)2 + (ey — cy)2 + (ez — cz)2, где e — вектор [ex, ey, ez], который является текущим положением камеры, и c — вектор [cx , cy , cz], который является центром текущего обрабатываемого GeoMipMap.

 

Dn2 = δ2 · C2

 

Уравнение 2: квадрат Уравнения 1, сохраняющий инструкцию с квадратным корнем для каждого вычисления d на каждый GeoMipMap за кадр.

2.3.2. Решение проблемы разрывов-геометрий

Когда два соседних GeoMipMap-а ландшафта имеют разные уровни детализации, будут возникать трещины на краях GeoMipMap-ов. Это нежелательно для любого приложения с рендерингом ландшафта, и этого следует избегать. Края GeoMipMap-ов с большей детализацией (т.е. больше вершин) содержат более полную информацию о карте высот, в отличие от краёв GeoMipMap-ов, с меньшей детализацией, и в местах где эти два GeoMipMap-а отделяются друг от друга общими ребрами, появляется разрывы-геометрии. Решение этой проблемы по сути подразумевает перерасстановку соединений вершин, чтобы края плотно подходили друг другу.
Один из способов решения этой проблемы, это добавить дополнительные вершины на край со стороны более низкого уровня детализации из двух GeoMipMap-ов, чтобы он подходил к краю GeoMipMap-а с большим количеством деталей. Это означает, что по умолчанию компоновка вершин и связывание вершин для низкого уровня GeoMipMap должны изменятся динамически и должны обновляться всякий раз, когда соседний GeoMipMap изменяет уровни. Дополнительная копия исходного GeoMipMap-а должна быть создана и сохранена в памяти. Это медленная и потребляющая память задача, поэтому этого способа следует избегать.
Другим способом решения этой проблемы является выровнять те вершины краёв с большей детализации GeoMipMap-а, которые добавляют детали, так что бы они соответствовали краю с более низкой детализацией GeoMipMap. Это вызовет так называемые T-junctions или T-vertices. Из-за неточностей с плавающей запятой они обеспечивают очень заметный эффект «отсутствия пикселей».
Я нашел хороший способ разрешения проблемы разрывов или трещин геометрий ландшафта, который не изменяет компоновку вершин уровня GeoMipMap, и не будет создавать эффект «недостающих пикселей». Единственное, что он делает, это изменяет связи (или индексацию) вершин для высокой детализации GeoMipMap. Взгляните на Рисунок 5. Вы увидите более высокую детализацию GeoMipMap-а, которая отделяет левый край (не показан на Рисунке 5) с более низкой детализацией GeoMipMap-а. Серые вершины несут дополнительные данные карты высот, что вызывает трещины. Путем пропуска этих вершин из «связывания», край будет бесшовно сопоставляться с более низкой детализацией GeoMipMap. Быстрый способ рендерить это — использовать два раздутых-треугольника для этого края, выходящих из верхней левой верхней чёрной вершины и черной вершины непосредственно под ним. Остальные вершины рисуются обычным способом. Этот процесс должен выполняться для каждого из четырех ребер, которые соединяют GeoMipMap с GeoMipMap-ами более низкой детализации. При реализации алгоритма это означает, что каждый GeoMipMap уровня 0 должен иметь ссылки на всех своих четырех соседей.
Недостатком этого метода является то, что при изменении связности вершин произойдет небольшое изменение (gouraud — гуро) затенения. Иногда это может быть пятнистость в режиме реального времени, но это едва заметно.

Terrain-MipMapping-4

Рисунок 5: Решение T-junctions и разрывы-геометрии ландшафта между более высоким уровнем (низкое разрешение) GeoMipMap с левой стороны. Обратите внимание: хотя вершины, отмеченные серым, они все еще являются частью этой сетки(mesh) GeoMipMap, они не будут рендерится из-за использования раздутых-треугольников. Пропуск предпочтительней добавления вершин, так как ничего не нужно менять для любого GeoMipMap-а. Они могут оставаться неизменными, единственное, что меняется, — это данные связывания.

3 ДОПОЛНЕНИЕ К ОСНОВНОМУ ПОДХОДУ

Хотя базовый подход обеспечивает результаты, которые будут удовлетворительными для использования в таких приложениях, как игры с активными действиями в режиме реального времени, но могут быть некоторые приложения, которые нуждаются в более сложном решении двух проблем, которые я опишу и дам к ним решение в следующих двух разделах.

3.1. Трилинейный GeoMipMapping

Хотя использование пиксельных ошибок пространства-экрана для выбора уровней GeoMipMap приведет к значительному уменьшению появления дефектов, они все равно ещё изредка будут появляться, особенно когда τ задано высокое значение. Несмотря на то, что задание τ как 1 или меньше, исключает в целом их появление, это также означает, что вероятность использования более высокого уровня GeoMipMap (низкая детализация) будет очень низкой. Необходимо найти определенное значение τ, которое дает хороший баланс между сохранением низких значений количества-треугольников и сохранением появления-дефектов на достаточно низком уровне. Это значение уникально для каждого приложения. Чтобы сохранить количество треугольников низким, без каких-либо появляющихся дефектов, я снова использую аналогию с текстурным mipmapping, рассматривая технический прием, называемый Трилинейная Фильтрация(Trilinear Filtering).
Когда вы смотрите на обычный текстурный mipmapping, можно четко увидеть границы того, где два mipmap-а разных уровней касаются друг друга. Эти границы или линии, бывают на фиксированном расстоянии от камеры и могут быть устранены с помощью Трилинейной Фильтрации. Я не собираюсь объяснять детали Трилинейной Фильтрации здесь, но концепция проста. Вместо того, чтобы выбирать дискретный mipmap для определенного d и использовать его до достижения d, для которого может использоваться mipmap более высокого уровня, интерполяция mipmap с mipmap на один уровень выше, вычисляется с использованием дроби расстояния на оба mipmap-а с предварительно вычисленным d. Это даст плавное смешивание mipmap-ов для любого значения d. Этот же принцип может быть применен к GeoMipMap-ам. Посмотрите еще раз на Рисунок 2. Уровень GeoMipMap 0 имеет определенный предварительно рассчитанный d (D0), который указывает, какой d этого уровня может заменять предыдущий уровень для рендеринга. Следующий уровень имеет эквивалент d (D1). Если мы вычислим дробь между этими двумя, как показано в Уравнении 3, мы можем использовать эту дробь как множитель (см. Уравнение 4) для белых вершин уровня GeoMipMap 0. Это будет медленно «трансформировать(morphing)» уровень 0 в уровень 1 до d достигшего D1, после чего уровень 0 может быть заменен уровнем 1. Это по существу заменяет внезапное изменение (popping — выскакивать, появлятся) уровней GeoMipMap плавным переходом между ними.

 

t =
(d — Dn)
(Dn+1 — Dn)

 

Уравнение 3: Расчет интерполяционной дроби t. d — расстояние от камеры до GeoMipMap, Dn — предварительно рассчитанная d для текущего GeoMipMap, а Dn + 1 — предварительно рассчитанная d для следующего GeoMipMap.

 

v, = v — t · δv

 

Уравнение 4: Используется для вычисления нового положения вершин, v ,, для белых вершин v. δv — изменение высоты (ошибка геометрии) v. Смотрите Рисунок 3.

Любые белые вершины края, которые являются соседними с GeoMipMap-ами более низкой детализаций, не должны умножаться на t, поскольку они больше не вносят вклад в GeoMipMap (см. Раздел 2.3.2).

3.2 Основы Поступательного стриминга GeoMipMap

Существуют определенные приложения, которые требуют визуализации данных-ландшафта, которые не будут полностью помещаться в память. Чтобы решить эту проблему, необходимо загружать только те блоки ландшафта, которые необходимо рендерить или, которые расположены рядом с камерой, только их нужно загружать с диска и сохранять в памяти. Всякий раз, когда блоки ландшафта, которые еще не хранятся в памяти, становятся видимыми, их необходимо загружать с диска. Это можно сделать посредством стриминга.
При запуске приложения древо квадрантов генерируется для всей местности. Координаты узлов ограничивающих объёмов x и z могут быть вычислены заранее, и y-координата должна быть считаны из файла данных-ландшафта, растрового изображения.

Нам нужно пройти через каждый блок ландшафта и сделать следующее:

  1. Загрузить блок ландшафта
  2. Рассчитать ограничивающий объём блоков ландшафта и сохраните его в соответствующем листе
  3. Рассчитать все уровни GeoMipMap для этого блока ландшафта
  4. Вычислите Dn для каждого Уровня GeoMipMap и сохраните его в блоках ландшафта листа древа квадрантов.
  5. Стереть все GeoMipMapУровни и продолжить шаги с 1. по 5. для следующих блоков ландшафта

После этого у нас остается древо квадрантов всего ландшафта, каждый лист которого содержит все Dn значения его блоков ландшафта. Обратите внимание, что мы не сохранили никаких GeoMipMap-ов.
Во время выполнения, значения Dn видимых листьев древа квадрантов сравниваются с текущим d, и соответствующий уровень GeoMipMap создается с диска. Всякий раз, когда d меньше, чем загруженный в настоящий момент Dn GeoMipMap-ов, соответствующий уровень GeoMipMap создается поступательно. Это краткое описание технического приёма и оно может быть дополнительно расширено.

4 РЕЗУЛЬТАТЫ В РЕЖИМЕ РЕАЛЬНОГО ВРЕМЕНИ

Метод, описанный в этой статье, реализован в рамках Проекта E-mersion. В настоящее время он использует только базовый алгоритм; функции вроде тех что описаны в разделе 3, не были реализованы, хотя их и не сложно интегрировать их в средство рендеринга ландшафта E-mersion. На рисунке 6 показан скриншот GeoMipMap-ов в действии. Как вы можете видеть, блоки ландшафта, которые находятся дальше от зрителя, нарисованы с использованием высоких уровней из цепочки GeoMipMap (то есть с более низким разрешением), за исключением случаев когда ошибка геометрии слишком высока даже для этого расстояния.
Целые блоки ландшафта рисуются с использованием вызова одного drawprimitive с использованием индексированных вершин для дальнейшего ускорения рендеринга. В этой демонстрации используется τ = 8.0. Средняя частота кадров 50 с-1 достигается с примерно 11 000 треугольников на кадр, на PentiumII 434 МГц с видеокартой Diamond Viper 550 и 64 МБ ОЗУ.

Пробную демонстрацию можно найти на моем сайте (URL вверху этой статьи) на главной странице. И конечно же, требуется оборудование для 3D рендеринга.

Terrain-MipMapping-5

Рисунок 6: Смешение двух скриншотов полученных из одного и того же положения камеры, при рендеринга рельефа в E-mersion. Одно изображение которое четко показывает GeoMipMap-ы в действии.

5. ВЫВОД

В этой статье я описал метод рендеринга данных ландшафта с высокой скоростью с использованием аппаратного 3D-рендеринга. Из-за способности аппаратного 3D-рендеринга отображать большое количество треугольников, нужно разработать алгоритм, который использует это, и способен обрабатывать большое количество треугольников, сохраняя нагрузку на CPU на низком уровне. Это означает, что алгоритм должен прибегать к более консервативным методам отсечения, то есть не предоставляя «идеальный набор данных». Метод, описанный в этой статье, на мой взгляд является, подходящим решением этой проблемы. Избегая как можно большего количества проверок на треугольник, я написал расширение на основе-блочного Уровня Детализации. Уровень Детализации основанный на блоках оказался очень полезным и быстрым при использовании в аппаратном 3D-рендеринге из-за низкой нагрузки на CPU. Заметные появляющиеся дефекты — которые характерны методам УД на основе блоков — были уменьшены за счет использования пиксельных ошибок пространства экрана и могут быть полностью устранены с использованием описанного метода трилинейной «трансформации(morphing)». В то время как основанный на блоках УД использовался и раньше, я никогда не видел, чтобы он использовался подобно текстурным mipmap-ам в работе с GeoMipMap-ами. Этот метод не слишком сложно понять и, что более важно, он не приведет к сложному (читаемому: медленно) коду, что является одним из ключевых факторов при разработке алгоритмов, которые будут использоваться вместе с аппаратным 3D-рендерингом. Цитата «Keep It Simple, Stupid(Будь проще, тупица)» определенно относится к алгоритмам, использующим аппаратный 3D-рендеринг.

Об авторе

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

Благодарности

Спасибо Kent Kuné за помощь в разработке графического дизайна проекта E-mersion; Эдварду Кметту за то, что он дал мне полезные советы при решении проблем с разрывами-геометрий ландшафта; Саймону О’Коннору и Джакко Биккеру за то, что нашли время, чтобы прочитать и высказать своё мнение об ещё неготовой версии этой статьи; и Lourens Veen за то, что этот документ стало возможно просматривать как файл Portable Document Format(PDF).


Список цитированной литературы

1. Real-Time, Continuous Level of Detail Rendering of Height Fields. Peter Lindstrom, David Koller, William Ribarsky, Larry F. Hodges, Nick Faust, and Gregory A. Turner. In Proceedings of ACM SIGGRAPH 96, August
1996, pp. 109-118.
http://www.cc.gatech.edu/gvu/people/peter.lindstrom/

2. The NetImmerse Terrain System. Dave Eberly, Director of Engineering, Numerical Design, Ltd.
http://www.ndl.com/terrainwhitepaper.html

3. ROAMing Terrain: Real-time Optimally Adapting Meshes. Mark Duchaineau, LLNL, Murray Wolinksy, LANL, David E. Sigeti, LANL, Mark C. Miller, LLNL, Charles Aldrich, LANL, Mark B. Mineev-Weinstein, LANL.
http://www.llnl.gov/graphics/ROAM/

4. Continuous LOD Terrain Meshing Using Adaptive Quadtrees. Thatcher Ulrich, Slingshot Game Technology.
http://www.gamasutra.com/features/20000228/ulrich_01.htm

5. Real-Time Rendering. Tomas Möller, Eric Haines. A K Peters, Ltd. ISBN 1-56881-101-2


Переведено для jmonkeyengine.ru, оригинал.
Автор перевода: Andry

  1. Если вы еще не установили SDK jMonkeyEngine3 то сделайте это, запустите среду IDE, и создайте JME3 проект.
  2. Установите приложения MakeHuman и Blender, и инструменты MakeHuman Blender.
    • Загрузите последнюю стабильную версию приложения MakeHuman с www.makehuman.org
    • Загрузите последние стабильные инструменты MakeHuman Blender с www.makehuman.org
    • Загрузите соответствующую (не обязательно последнюю) версию приложения Blender с www.blender.org
    • Установите приложение MakeHuman как указанно в описании установки MakeHuman
    • Установите приложение Blender.
    • Разархивируйте плагин импорта инструментов MakeHuman в папку скриптов Blender. На машинах с Windows 7 это включает распаковку архива в
      C:\Program Files\Blender Foundation\Blender\<версия>\scripts\addons
    • Запустите приложение Blender, откройте редактор Параметры Blender(Blender User Preferences), активируйте вкладку Дополнения(Addons) и установите флажок для
        MakeHuman: Import-Runtime: MakeHuman Exchange 2 (.mhx2)
    • Нажмите кнопку [Сохранить настройки](Save User Preferences).
    • Разархивируйте плагин экспорта инструментов MakeHuman в папку плагинов MakeHuman.
  3. Создание файла .mhx2, содержащего фигуру человека.
    • Запустите приложение MakeHuman.
    • На вкладке Settings(Настройки) вы можете переключится на Русский язык.
    • Настройте фигуру человека, используя вкладки Моделирование, Геометрии и Материалы.
    • На вкладке Поза / Анимировать выберите нужный скелет для анимации, если это необходимо.
    • На вкладке Файлы выберите вкладку Сохранить. Обратите внимание, что есть две кнопки Сохранить: одна в окне Сохранить как(в виде []), с помощью которой просто задаётся название и путь, и другая в главном окне. Файл .mhm не записывается, пока вы не нажмете вторую кнопку [Сохранить].
    • Сохраните файл (если вам может понадобится внести в него изменения позже).
    • На вкладке Файлы выберите вкладку Экспорт.
    • Выберите пункт MakeHuman Exchange (mhx2) в качестве формата экспорта.
    • Экспортируйте файл. Тут так же как и с сохранением есть две кнопки […], которая просто задает путь, и есть кнопка [Экспорт]. Файл .mhx2 не записывается, пока вы не нажмете кнопку [Экспорт] в главном окне.
    • Теперь вы можете закрыть приложение MakeHuman.
  4. Преобразование файла .mhx2 в файл .blend:
    • Запустите приложение Blender и удалите исходный куб.
    • На панели инструментов редактора Информация выберите Файл ▸ Импорт ▸ MakeHuman (.mhx2)…
    • В открывшемся окне выберите ваш файл .mhx2 и нажмите кнопку [Import MHX2].
    • После завершения импорта вы можете просмотреть и отредактировать модель с помощью редактора 3D Вида.
    • Далее на панели инструментов редактора Информация выберите Файл ▸ Сохранить как…
    • В открывшемся окне выберите путь в папке assets/Models вашего JME3 проекта.
    • Кликните по кнопке [Сохранить как файл Blender] и файл будет сохранён.
    • Теперь вы можете закрыть приложение Blender.
  5. Преобразование файла .blend в файл .j3o:
    • Запустите IDE jMonkeyEngine3.(У меня в IDE jMonkeyEngine3 для версии 3.1 зависало при конвертировании но прекрасно конвертировалось в jMonkeyBuilder)
    • Откройте проект JME3.
    • В узле Project Assets вашего проекта откройте папку Models.
    • Щелкните [ПК мыши] файл .blend и выберите Convert to j3o binary.

Смотрите так же:


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Вы можете попробовать самый новый jME3 и внести исправления и функции! В этом документе показано, как загрузить, настроить, создать и запустить последнюю разрабатываемую версию из исходников. Эти инструкции работают в среде IDE NetBeans 8.x или выше.

Предпосылки

  1. У вас есть аккаунт GitHub.
  2. Вы являетесь участником проекта jMonkeyEngine.

Если вы не являетесь участником, вы fork репозиторий в своей учетной записи GitHub и pull requests оттуда.

Чтобы fork репозиторий jMonkeyEngine в своей учетной записи GitHub:

  1. Перейдите на страницу https://github.com/jMonkeyEngine/jmonkeyengine.
  2. В меню в верхней части страницы выберите кнопку «Fork».
В дальнейшем всегда заменяйте символ ~ путём к вашему домашнему каталогу.

Загрузка исходников

Клонирование исходников из репозитория.

  1. В NetBeans перейдите в Группа ▸ Git ▸ Клонировать.
    1. URL-адрес репозитория:
      https://github.com/jMonkeyEngine/jmonkeyengine
      Используйте URL-адрес для своего fork репозитория и клонируйте свой fork, если вы не являетесь участником, и вы хотите внести свой вклад.
    2. Вы можете оставить Пользователь/Пароль пустыми для анонимного доступа, если только вы не собираетесь push и commit.
    3. Укажите папку назначения.
    4. Локальная папка: ~/NetBeansProjects]/jmonkeyengine
    5. Нажмите [Далее].
  2. Выберите:

      master*

    1. Нажмите [Далее].
  3. Проверьте правильность информации автозаполнения в диалоговом окне Целевой каталог.
    1. Необязательно:
        Сканирование проектов NetBeans после клонирования
    2. Нажмите [Готово] и подождите.

Через некоторое время загрузка закончится.

Информация о Pull/Pushing Commits

Теперь у вас есть локальная копия репозитория jmonkeyengine. Чтобы внести свой вклад, вам нужно следовать нескольким правилам.

Участники и нет Pull/Push

Обновите локальную копию и нажмите фиксации с помощью NetBeans команды Git ▸ Удалённый ▸ Вытягивание из выше стоящего сервера или Git ▸ Удалённый ▸ Втолкнуть на выше стоящий сервер.

Тем кто не являются участниками

Чтобы обновить ваш fork на GitHub:

  1. Перейдите к своей версии репозитория на GitHub.
  2. Нажмите кнопку вверху [New Pull Request].
  3. Обратите внимание, что репозиторий jMonkeyEngine будет слева, а ваш репозиторий будет справа.
  4. Нажмите кнопку [Base Fork], а в раскрывающемся меню выберите forked репозиторий.
  5. Выберите ссылку «compare across forks».
  6. Нажмите кнопку [Head Fork] и в раскрывающемся списке выберите «jMonkeyEngine/jmonkeyengine».
  7. Нажмите зеленую кнопку [Create pull request].
  8. Дайте краткое и информативное заглавие, и в поле комментария дайте краткое описание изменений, и нажмите зеленую кнопку [Create pull request] еще раз.
  9. Выберите кнопку [Commit And Merge].
  10. Нажмите кнопку [Commit And Merge] еще раз, чтобы подтвердить свой выбор.

Чтобы отправить изменения в репозиторий jMonkeyEngine после pushing на свой fork:

  1. Перейдите к своей версии репозитория на GitHub.
  2. Нажмите вверху кнопку [New Pull Request].
  3. Обратите внимание, что репозиторий jMonkeyEngine будет слева, а ваш репозиторий будет справа.
  4. Нажмите зеленую кнопку [Create pull request]. Дайте краткое и информативное заглавие, и в поле комментария дайте краткое описание изменений и нажмите зеленую кнопку [Create pull request] еще раз.
Всем
Смотрите раздел Как внести вклад в jMonkeyEngine перед отправкой P/R.

Проверка Клонированных Подпроектов(Verify Cloned Subprojects)

Посмотрите на узел Subprojects и убедитесь, что эти подпроекты клонированы:

  • jme3-android
  • jme3-android-native
  • jme3-blender
  • jme3-bullet
  • jme3-bullet-native
  • jme3-bullet-native-android
  • jme3-core
  • jme3-desktop
  • jme3-effects
  • jme3-examples
  • jme3-ios
  • jme3-jbullet
  • jme3-jogg
  • jme3-jogl
  • jme3-lwjgl
  • jme3-lwjgl3
  • jme3-networking
  • jme3-niftygui
  • jme3-plugins
  • jme3-terrain
  • jme3-testdata
  • jme3-vr

Подробное описание отдельных jar файлов см. в этом списке.

Соберите Проект и Запустите Приложение Пример

Когда вы собираете движок из корневого узла, часть процесса сборки включает в себя сборку заголовочных файлов для подпроекта jme3-bullet-native. Это обновляет временную метку в заголовочных файлах, даже если вы их не редактировали. Затем они будут отмечены как измененные, что добавит их в следующую фиксации(commit).

Чтобы избежать их фиксации(commit) сделайте то что описано ниже прежде чем делать что-либо еще:

  1. В окне Проекты откройте узел подпроекта jme3-bullet-native.
  2. Перейдите в папку Пакеты исходные кодов / ‹пакет по умолчанию›.
  3. Выберите все заголовочные файлы .h, выделив их.
  4. [КП Мыши] выберите выделенные файлы, затем выберите Git ▸ Игнорировать ▸ Исключить из фиксации
  1. [КП Мыши] кликните по корневому узлу проекта jmonkeyengine и выберите Очистить и собрать проект.
  2. В окне Проекты выберите [КП Мыши] и откройте узел jme-examples, который содержит примеры приложений. Вы делаете это для любого подпроекта, в который вы хотите внести изменения.
  3. Каждый файл в папке Пакеты исходные кодов с Гланым классом (например, jme3test.model/TestHoverTank.java или jme3test.games/CubeField.java) является приложением.
  4. Щелкните [КП Мыши] пример приложения и выберите Выполнить файл (Shift+F6).
  5. Как правило, в примерах приложений:
    1. перемещение осуществляется мышью и клавишами WASD
    2. клавиша Esc выход из приложения
Вы можете кликнуть [КП Мыши] по узлу jme-examples и выбрать Выполнение, чтобы запустить приложение Test Chooser независимо от того, открыли ли вы узел проекта или нет.

Дополнительно: всплывающие окна Javadoc и исходная навигационная система в NetBeans

Если вы работаете с исходниками jmonkeyengine:

  1. Убедитесь в окне Файлы, что javadoc был создан в ~/NetBeansProjects/jmonkeyengine/dist/javadoc
  2. В редакторе кода поместите каретку на jme класс и нажмите ctrl+пробел, чтобы просмотреть javadoc.

Если вы работаете над проектом игры, который зависит от jmonkeyengine:

  1. В исходниках jmonkeyengine:
    1. Щелкните [КП Мыши], по узлу подпроекта и выберите Tasks ▸ install будет установленная сборка jar-ов для этого подпроекта в вашу локальную папку maven репозитория org.jmonkeyengine, который на linux в ~/.m2, а в Windows может быть в AppData или в вашем домашнем каталоге ,
    2. Щелкните [КП Мыши], по корневому узлу и выберите Tasks ▸ dist, создает jME3 примеры распространяемые со всеми jme3 бинарниками, javadoc и внешними библиотеками в ~/NetBeansProjects/jmonkeyengine/dist.
    3. Щелкните [КП Мыши], по корневому узлу и выберите Tasks ▸ libDist соберём и копируем двоичные файлы и исходники движка в ~/NetBeansProjects/jmonkeyengine/build/libDist.
    4. Щелкните [КП Мыши], по корневому узлу и выберите Tasks ▸ copyLib копирует зависимости движка в ~/NetBeansProjects/jmonkeyengine/build/libDist/lib-ext.
  2. В своем игровом проекте добавьте jme3 jar кликнув [КП Мыши], по узлу Библиотеки и выбрав Добавить файл JAR /папку.
  3. Перейдите в нужную папку и выберите интересующую вас jar библиотеку. Проверьте «правильность путь» и нажмите [Open].
  4. Кликнув [КП Мыши] выберите недавно добавленный jar и выберите Изменение.
  5. В диалоговом окне Изменить ссылку на JAR, в пункте Документация JavaDoc: кликните [Обзор] и выберите в папку javadoc/. Проверьте «правильность пути» и нажмите [Open].
  6. В диалоговом окне Изменить ссылку на JAR в пункте Исходные файлы: выберите папку, которая содержит ваши источники».
    кликните [Обзор] Проверьте «правильность пути» и нажмите [Open].
  7. В редакторе кода наведите курсор на jme класс и нажмите ctrl+space, чтобы просмотреть javadoc. Ctrl+Клик по любому методу jme3, позволит перейти к его переопределяющим методам в исходниках.

Этот совет работает для любой сторонней библиотеки JAR, которую вы используете. (Возможно, вам придется загружать javadoc/исходники со своей домашней страницы отдельно).


Используемые исходники: https://github.com/jMonkeyEngine/jmonkeyengine


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

jMonkeyEngine.ru © 2017. Все права сохранены.