Докуметация Cтарт Статьи Форум Лента Вход
Не официальное русскоязычное сообщество
Главная
    Документация jMonkeyEngine
        jMonkeyEngine Уроки и Документация
            jMonkeyEngine3: Привет мир, Обучающая Серия
                jMonkeyEngine 3 урок (10) — Hello Terrain

jMonkeyEngine 3 урок (10) — Hello Terrain

Опубликованно: 05.04.2017, 16:52
Последняя редакция, Andry: 24.03.2018 20:09

Предыдущий: Hello Collision, Следующий: Hello Audio

Одним из способов создания 3D-ландшафта является лепка огромной модели местности. Это дает вам много свободы творчества — но визуализация такой огромный модели может быть довольно медленной. В этом руководстве объясняется, как создавать участки местности которые быстро рендерятся, с помощью карты высот(heightmaps), и как использовать texture splatting(метод комбинирования разных текстур), чтобы местность хорошо выглядела.

beginner-terrain

Если вы получаете сообщение об ошибке при попытке создать объект ImageBasedHeightMap, возможно, потребуется обновить SDK, нажмите на кнопку Справка ▸ Проверить обновления
Для того, чтобы использовать примеры игровых ресурсов в вашем новом проекте в jMonkeyEngine SDK, щелкните [ПК мыши] ваш проект, выберите Свойства, перейдите в раздел Библиотеки, нажмите кнопку [Добавить библиотеку] и добавьте библиотеку jme3-test-data.

Пример кода

package jme3test.helloworld;
 
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.renderer.Camera;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
import com.jme3.terrain.heightmap.HillHeightMap; // для упражнения 2
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import java.util.ArrayList;
import java.util.List;
 
/** Пример 10 - Как создать быстро-визуализирующуюся месности с помощью карты высот, 
  * и как использовать texture splatting, чтобы местность выглядела хорошо .  */
public class HelloTerrain extends SimpleApplication {
 
  private TerrainQuad terrain;
  Material mat_terrain;
 
  public static void main(String[] args) {
    HelloTerrain app = new HelloTerrain();
    app.start();
  }
 
  @Override
  public void simpleInitApp() {
    flyCam.setMoveSpeed(50);
 
    /** 1. Создаем материал ландшафта и загружаем в него четыре текстуры. */
    mat_terrain = new Material(assetManager, 
            "Common/MatDefs/Terrain/Terrain.j3md");
 
    /** 1.1) Добавляем ALPHA карту (для RBG кодировки splat текстур) */
    mat_terrain.setTexture("Alpha", assetManager.loadTexture(
            "Textures/Terrain/splat/alphamap.png"));
 
    /** 1.2) Добавляем GRASS (трава) текстуру в красный слой (Tex1). */
    Texture grass = assetManager.loadTexture(
            "Textures/Terrain/splat/grass.jpg");
    grass.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex1", grass);
    mat_terrain.setFloat("Tex1Scale", 64f);
 
    /** 1.3) Добавляем DIRT (грязь) текстуру в зеленый слой (Tex2) */
    Texture dirt = assetManager.loadTexture(
            "Textures/Terrain/splat/dirt.jpg");
    dirt.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex2", dirt);
    mat_terrain.setFloat("Tex2Scale", 32f);
 
    /** 1.4) Добавляем ROAD (дорога) текстуру в синий слой (Tex3) */
    Texture rock = assetManager.loadTexture(
            "Textures/Terrain/splat/road.jpg");
    rock.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex3", rock);
    mat_terrain.setFloat("Tex3Scale", 128f);
 
    /** 2. Создаем карту высот */
    AbstractHeightMap heightmap = null;
    Texture heightMapImage = assetManager.loadTexture(
            "Textures/Terrain/splat/mountains512.png");
    heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
    heightmap.load();
 
    /** 3. Мы подготовили материал и карту высот.
     * Теперь мы создаем саму местность:
     * 3.1) Создаём TerrainQuad с названием "my terrain" (мой ландшафт).
     * 3.2) Хорошее значение для плиток ландшафта 64x64 - поэтому мы предоставляем 64 + 1 = 65.
     * 3.3) Мы подготовили карту высот размером 512x512 - поэтому мы предоставляем 512 + 1 = 513.
     * 3.4) В качестве шага масштаба LOD мы предоставляем Vector3f (1,1,1).
     * 3.5) Мы предоставляем саму подготовленную карту высот.
     */
    int patchSize = 65;
    terrain = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap());
 
    /** 4. Мы даем ландшафту его материал, местоположение, масштабируем, и прикрепляем его. */
    terrain.setMaterial(mat_terrain);
    terrain.setLocalTranslation(0, -100, 0);
    terrain.setLocalScale(2f, 1f, 2f);
    rootNode.attachChild(terrain);
 
    /** 5. LOD (уровень детализации) зависит от того, далеко ли камера: */
    TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
    terrain.addControl(control);
  }
}

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


Что такое карта высот(Heightmap)?

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

В Java, карта высот является float массивом, содержащим значения высоты между 0f и 255f. Вот очень простой пример ландшафта, генерируемого из карты высот с количеством значений высот 5×5=25.

terrain-from-float-array

Важные вещи на заметку:

  • Низкие значения (например, 0 или 50) являются долины.
  • Высокие значения (например, 200, 255) являются холмами.
  • Карта высот задает лишь несколько точек, а движок интерполирует остальное. Интерполяция является более эффективной, чем создание модели с несколькими миллионами вершин.

При взгляде на типы данных Java для хранения массива из значений от 0 до 255, на ум приходит изображение. Сохранение значений высоты ландшафтом, в качестве изображение из градаций серого, имеет одно большое преимущество: Такой результат является очень удобным, и похожим на топографическую, карта:

  • Низкие значения (например, 0 или 50) являются темно-серым — это долины.
  • Высокие значения (например, 200, 255) являются светло серым — это холмы.

Посмотрите на следующий скриншот: В левом верхнем углу вы видите изображение размером 128×128 в градациях (карта высот), которое использовалось в качестве основы для создания изображения местности. Для того, чтобы холмистая форма поверхности была лучше видна, горные вершины окрашены в белый цвет, долины — коричневый, а участки между ними зеленым:

terrain-from-heightmap

В реальной игре, вы будете хотеть использовать более сложные и более гладкие местности, чем простые карты высот, показанные здесь. Как правило, карты высот имеют квадратные размеры 512×512 или 1024×1024, и содержат от сотни тысяч, до 1 миллиона значений высоты. Независимо от того, какой размер, концепция является такой же, как описано здесь.


Взглянем на код карты высот

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

  1. Размер должен быть квадратным, и при этом степенью двойки.
    • Примеры: 128×128, 256×256, 512×512, 1024×1024&
  2. Цветовой режим должен быть 255 оттенков серого.
    • Не задавайте цветное изображение, оно будет интерпретировано как оттенки серого цвета, с, возможными, странными результатами.
  3. Сохраните картинку как .jpg или .png файл изображения.

mountains512

Файл mountains512.png, что вы видите здесь, является типичным примером изображения карты высот.

Вот как вы создаёте объект heightmap в вашем коде jME:

  1. Создайте объект типа Texture.
  2. Загрузите подготовленное изображение карты высот в объект типа Texture.
  3. Создать AbstractHeightmap объект из ImageBasedHeightMap.
    Для это потребует изображения из объекта типа Texture JME.
  4. Загрузите карту высот.
        AbstractHeightMap heightmap = null;
        Texture heightMapImage = assetManager.loadTexture(
                "Textures/Terrain/splat/mountains512.png");
        heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();

Что такое Texture Splatting?

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

Texture splatting позволяет создать пользовательский материал, и «рисовать» текстурами на нем, как «кистью». Это очень удобно для ландшафтов: Как вы видите в приведённом здесь примере, вы можете разрисовать текстурой травы долины, текстурой грязи горы, и нарисовать дороги любых размеров между ними.

JMonkeyEngine SDK поставляется с плагином TerrainEditor. Используя плагин TerrainEditor, вы можете создавать рельеф местности с помощью мыши, и сохранить результат в виде карты высот. Вы можете разрисовать текстурами местность и плагин сохранит полученную splat texture-у как alphamap(Алфа карту). В следующем параграфе есть руководство описывающее для вас сам процесс. Вы можете выбрать, создать рельеф местности вручную или с помощью плагина TerrainEditor.

Splat textures основаны на материале Terrain.j3md. Если вы откроете файл Terrain.j3md, и посмотрите в разделе параметров материала, вы увидите, что у вас есть несколько текстурных слоев для рисования: Tex1, Tex2, Tex3 и.т.д.

Прежде чем начать рисовать, вы должны принять несколько решений:

  1. Выберите три текстуры. Например, grass.jpg, dirt.jpg и road.jpg.
    grassdirtroad
  2. Вы «нарисуете» три текстурных слоя, используете три цвета: красный, синий и зеленый. Вы можете произвольно выбрать, что …
    1. Красный — это трава — красный слой это Tex1, поэтому текстура травы в Tex1.
    2. Зеленый — это грязь — зеленый слой это Tex2, поэтому текстура грязи в Tex2.
    3. Синий — это дороги — синий слой это Tex3, поэтому текстура дороги в Tex3.

Теперь вы начинаете рисовать текстуру:

  1. Сделайте копию вашей карты высот местности, mountains512.png. Использование её, будет вам полезно тем что вы будете лучше знать форму поверхности ландшафта.
  2. Назовите вашу копию alphamap.png.
  3. Откройте alphamap.png в графическом редакторе и переключитесь на режим цветного изображения.
    1. Закрасьте черные долины красным — это будет трава.
    2. Закрасьте белые холмы зеленым — это будет грязь гор.
    3. Закрасьте синими линиями, те места где вы хотите, чтобы на ландшафте пролегали дороги.
  4. Конечный результат должен выглядеть примерно так:
mountains512

alphamap


Взглянем на код текстурирования

Как обычно, вы создаете объект типа Material. Основанный на Описании Материала(Material Definition) Terrain.j3md, который включен в фреймворк jME3.

Material mat_terrain = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");

Загрузите четыре текстуры в этот материал. Первый из них, Alpha, это alphamap, которую вы только что создали.

mat_terrain.setTexture("Alpha",
    assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));

Три другие текстуры это слои, которые вы ранее нарисовали: трава, грязь, и дороги. Вы создаете объекты текстуры и загружаете три текстуры, как обычно. Обратите внимание, как вы назначаете их соответствующим текстурным слоям (Tex1, Tex2 и Tex3) внутри Материала!

    /** 1.2) Добавляем GRASS (трава) текстуру в красный слой (Tex1). */
    Texture grass = assetManager.loadTexture(
            "Textures/Terrain/splat/grass.jpg");
    grass.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex1", grass);
    mat_terrain.setFloat("Tex1Scale", 64f);
 
    /** 1.3) Добавляем DIRT (грязь) текстуру в зеленый слой (Tex2) */
    Texture dirt = assetManager.loadTexture(
            "Textures/Terrain/splat/dirt.jpg");
    dirt.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex2", dirt);
    mat_terrain.setFloat("Tex2Scale", 32f);
 
    /** 1.4) Добавляем ROAD (дорога) текстуру в синий слой (Tex3) */
    Texture rock = assetManager.loadTexture(
            "Textures/Terrain/splat/road.jpg");
    rock.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex3", rock);
    mat_terrain.setFloat("Tex3Scale", 128f);

Масштаб конкретной текстуры (например, mat_terrain.setFloat(«Tex3Scale», 128f);) зависит от размера используемых вами текстур.

  • Вы можете сказать, что вы выбрали слишком маленький масштаб, если, вдруг ваши дорожные плитки появляются как крошечные песчинки.
  • Вы можете сказать, вы выбрали слишком большой масштаб, если, например, маленькие травинки выглядят как целые ветви.
  • Используйте setWrap(WrapMode.Repeat), чтобы заполнять большую области небольшими текстурами которые будут многократно повторятся. Если повторение слишком заметно, попробуйте отрегулировать значение Tex*Scale.


    Что такое Ландшафт(Местность)?

    Генерируемая сетка(mesh) ландшафта внутри разбивается на плитки(tiles) и блоки(blocks). Это оптимизация, упрощающая отсечение. Вам не стоит слишком беспокоиться о «плитках» и «блоках» просто используйте рекомендованные значения которое на данный момент — 64 хорошее для начала.

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

    Создайте TerrainQuad(Квадрант Ландшафта) со следующими аргументами:

    1. Задайте название: например, my terrain.
    2. Задайте размер плитки: Вы хотите плитки местность размером 64×64, значит вы зададите 64+1=65.
      • В целом, 64 является хорошим начальным значением для плиток местности.
    3. Задайте размер блока: Так как вы подготовили карту высот размером 512×512, вы зададите 512+1=513.
      • Если вы укажете размер блока в карте высот размером 2x (1024+1=1025), вы получите боле растянутый, более широкий, и плоский рельеф местности.
      • Если вы укажите размер блока 1/2 размера карты высот (256+1=257), вы получите меньшую размером, и более точную местность.
    4. Задайте объект, карту высоты 512×512, как она создана.

    Взглянем на код местности

    Вот код:

    terrain = new TerrainQuad(
      "my terrain",               // название
      65,                         // размер плитки
      513,                        // размер блока
      heightmap.getHeightMap());  // карта высот
    

    Вы создаете объект terrain.

    1. Не забудьте применить созданный материал:
    2. terrain.setMaterial(mat_terrain);
      
    3. Не забудьте прикрепить местность к rootNode.
    4. rootNode.attachChild(terrain);
      
    5. При необходимости, масштабируйте и перемещайте объект Ландшафт, так же как и любой другой Spatial.
    Terrain.j3md является незатененным описание материала, так что вам не нужен источник света. Вы также можете использовать TerrainLighting.j3md плюс свет, если вы хотите затеняемую местность.

    Что такое LOD (уровень детализации)?

    JME3 включает в себя оптимизацию, которая регулирует уровень детализации (LOD) ландшафта в зависимости от того, насколько близко или далеко участок ландшафта находится от камера.

        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
        terrain.addControl(control);
    

    Близкие участки ландшафта визуализируются полностью. Части ландшафта что расположенные в отдалении от камеры в любом случае видны хуже, и JME3 повышает производительность, делает их менее детализированными. Таким образом, вы можете себе позволить загружать огромные территории без серьезных тормозов, вызванных деталями ландшафта что все равно не видны или видны плохо.


    Упражнения

    Упражнение 1: Текстурные слои

    Что произойдет, если поменять местами два слоя, например Tex1 и Tex2?

    ...
    mat_terrain.setTexture("Tex2", grass);
    ...
    mat_terrain.setTexture("Tex1", dirt);
    

    Как можете увидеть, легче поменять местами слои в коде, чем изменить цвета в alphamap.

    Упражнение 2: Случайный Ландшафт

    Следующие три строчки генерируют объект heightmap на основе вашего пользовательского изображения:

        AbstractHeightMap heightmap = null;
        Texture heightMapImage = assetManager.loadTexture(
            "Textures/Terrain/splat/mountains512.png");
        heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
    

    Вместо этого, вы так же можете позволить JME3 генерировать случайный ландшафт для вас:

    1. Какой результат вы получите, когда вы замените вышеуказанные три строки с heightmap на эти строки, и запустите образец?
    2. HillHeightMap heightmap = null;
      HillHeightMap.NORMALIZE_RANGE = 100; // необязательно
      try {
          heightmap = new HillHeightMap(513, 1000, 50, 100, (byte) 3); // byte 3 случайны семя
      } catch (Exception ex) {
          ex.printStackTrace();
      }
      
    3. Измените один параметр, за раз и снова запускайте образец. Обратите внимание на различия. Можете ли вы выяснить, какие из значений влияют на генерируемый Ландшафт (смотреть также javadoc)?
      • Какое значение управляет размером?
        • Что произойдет, если размер не квадратное число +1?
      • Какое значение управляет количеством генерируемых холмов?
      • Какие значения контролируют размер и крутизну холмов?
        • Что произойдет, если минимальные будут больше, чем или равны максимальным?
        • Что произойдет, если как минимум так и максимум малые значения (например, 10/20)?
        • Что произойдет, если как минимальное так и максимальное большие значения очень большие (например, 1000/1500)?
        • Что произойдет, если минимальные и максимальные значения очень близки (например. 1000/1001, 20/21)? Очень далеки друг от друга (например, 10/1000)?

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

    Для этого упражнения, вы можете продолжать использовать splat материал из примера кода выше. Не удивляйтесь, что материал не соответствует форме недавно рандомизированного ландшафта. Если вы хотите создавать действительно сопоставляемые splat текстуры для рандомизированной карты высот, вам нужно написать собственный метод, который, например, создает alphamap из карты высот и заменяет на ней некоторые оттенки серого на определенные значения RGB.

    Упражнение 3: Твердая местность

    Можете ли вы объединить то, что вы узнали здесь и в Hello Collision, и сделать рельеф местности твердым?


    Вывод

    Вы научились создавать ландшафты местность, которые являются более эффективными, чем загрузка одной гигантской модели. Вы знаете, как генерировать случайные карты высот, или создавать их вручную. Вы можете добавить control LOD для ускорения рендерига больших территорий. Вы знаете, как вы можете применить, то что вы узнали об обнаружении столкновения, чтобы сделать рельеф местности твердым для игрока с физикой. Вы также можете текстурировать ландшафт «как босс», используя слоистые материалы и texture splatting. Вы знаете, что jMonkeyEngine SDK предоставляет TerrainEditor, который помогает справиться с большинством из этих задач вручную.

    Вы хотите услышать как ваши игроки говорят «ой!», когда они врезаются в стену или падают с горы? Тогда продолжите обучение, как добавить звук в вашу игру.


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


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

    Добавить комментарий

    Содержание

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