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

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

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

Предыдущий: 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 textures) */
    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, картой высот является массивом с плавающей точкой, содержащей значения высоты между 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 textures как 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.


    Что такое Местность?

    Генерируемая сетка местности внутри разбивается на плитки(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());  // карта высот
    

    Вы создаете объект местности.

    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 textures для рандомизированной карты высот, вам нужно написать собственный метод, который, например, создает alphamap из карты высот и заменяет на ней некоторые оттенки серого на определенные значения RGB.

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

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


    Вывод

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

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


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


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

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

    Содержание

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