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

Пользовательские Сетки Фигуры

Опубликованно: 08.05.2017, 11:51
Последняя редакция, Andry: 29.09.2017 9:19

custom_mesh
Используйте класс Mesh для создания собственных фигур, выходящих за рамки Quad(квадрат), Box(куб), Cylinder(цилиндр) и Sphere(сфера), возможны даже процедурные фигуры. Спасибо KayTrance за предоставленный пример кода!

В этом уроке мы (повторно) создадим очень простую прямоугольную сетку (четырехугольник), и мы рассмотрим различные способы ее окрашивания. Написание пользовательского квадрата может быть не очень полезным, поскольку оно точно такой же, как встроенный com.jme3.scene.shape.Quad. Мы выбрали простой квадрат, чтобы научить вас строить любую фигуру из треугольников, не отвлекаясь на более сложные фигуры.

Полигональные сетки(Polygon Meshes)

Полигональные сетки состоят из треугольников. Углы треугольников называются вершинами. Когда вы создаете любую новую фигуру, вы разбиваете ее на треугольники.

Пример: Давайте посмотрим на куб. Куб состоит из 6 прямоугольников. Каждый прямоугольник можно разбить на два треугольника. Это означает, что вам нужно 12 треугольников для описания кубической сетки. Для этого вы должны указать координаты углов треугольников (называемых вершинами).

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

Звучит тяжелее, чем есть — давайте создадим простую настраиваемую сетку, квадрат.

Создание Quad Mesh

В этом уроке мы хотим создать квадрат 3×3. Квадрат имеет четыре вершины и состоит из двух треугольников. В нашем примере мы решаем, что нижний левый угол равен 0/0/0, а верхний правый — 3/3/0.

Объект Mesh

Базовым классом для создания сеток является com.jme3.scene.Mesh.

Mesh mesh = new Mesh();
Если вы создаете свой собственный Mesh-класс (public class MyMesh extends Mesh { }), замените переменную mesh на this в следующих примерах.

Координаты вершин(Vertex Coordinates)

Чтобы определить свою собственную фигуру, определите координаты вершин в 3D-пространстве. Сохраните список угловых позиций в массиве com.jme3.math.Vector3f. Для Квадрата нам нужно четыре вершины: Нижняя левая, нижняя правая, верхняя левая, верхняя правая. Мы называем массив vertices[].

Vector3f [] vertices = new Vector3f[4];
vertices[0] = new Vector3f(0,0,0);
vertices[1] = new Vector3f(3,0,0);
vertices[2] = new Vector3f(0,3,0);
vertices[3] = new Vector3f(3,3,0);

Координаты текстуры

Затем мы определяем 2D координаты текстуры Квадрата для каждой вершины в том же порядке, что и вершины: Нижний слева, нижний правый, верхний левый, верхний правый. Мы называем этот Vector2f массив texCoord[]

Vector2f[] texCoord = new Vector2f[4];
texCoord[0] = new Vector2f(0,0);
texCoord[1] = new Vector2f(1,0);
texCoord[2] = new Vector2f(0,1);
texCoord[3] = new Vector2f(1,1);

Этот синтаксис означает, что когда вы примените текстуру к этой сетке, текстура заполнит квадрат из угла в угол на 100%. Особенно, когда вы сшиваете большую сетку, вы используете это, чтобы сообщить рендереру, нужно ли и как именно вы хотите покрыть всю сетку. Например. Если вы используете .5f или 2f в качестве текстурных координат вместо 1f, текстуры будут растянуты или сжаты соответственно.

Соединение точек(Connecting the Dots)

Затем мы превратим эти несвязанные координаты в треугольники: Определим порядок, в котором строится каждый треугольник. Думайте об этих показателях как о трех группах. Каждая группа индексов описывает один треугольник. Если углы идентичны, вы можете (и должны!) Повторно использовать индекс для нескольких треугольников.

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

int [] indexes = { 2,0,1, 1,3,2 };

Этот синтаксис означает:

  • Индексы 0,1,2,3 обозначают четыре вершины, которые вы указали для квадрата в vertices[].
  • Треугольник 2,0,1 начинается в левом верхнем углу, продолжается в нижнем левом углу и заканчивается внизу справа.
  • Треугольник 1,3,2 начинается в правом нижнем углу, продолжает вверху справа и заканчивается в верхнем левом углу.


Против часовой стрелки

Если фигура более сложная, она имеет больше треугольников, а значит и больше вершин/индексов. Просто продолжайте расширять список, добавляя группы из трех индексов для каждого треугольника. (Например, трехмерная «фигура дома» имеет 5 вершин/индексов, и вы должны указать три группы: int [] indexes = { 2,0,1, 1,3,2, 2,3,4 };.)

Если вы выполните неправильный порядок (по часовой стрелке) для некоторых треугольников, тогда эти треугольники обращены назад. Если материал Spatial использует установленный по умолчанию FaceCullMode.Back (см. «Отсечение по направлению нормали»), неправильные треугольники выглядят как отверстия в отрендеренной сетке. Вам необходимо найти их и исправить их коде.

Настройка буфера Mesh

Данные Mesh хранятся в буфере.

  1. Используя com.jme3.util.BufferUtils, мы создаем три буфера для трех типов информации, которая у нас есть:
    • Координаты вершин,
    • Координаты текстур
    • Индексы.
  2. Мы присваиваем данные соответствующему типу буфера внутри объекта Mesh. Три типа буферов (Position, TextCoord, Index) берутся из перечисления в com.jme3.scene.VertexBuffer.Type.
  3. Целочисленный параметр описывает количество компонентов значений. Положения вершин представляют собой 3 float числа, координаты текстуры — 2 float числа, а индексы — 3 целых числа, представляющих 3 вершины в треугольнике.
  4. Чтобы отобразить сетку в сцене, нам нужно предварительно вычислить ограничивающий контейнер для новой сетки: вызовите для неё метод updateBound().
mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
mesh.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoord));
mesh.setBuffer(Type.Index,    3, BufferUtils.createIntBuffer(indexes));
mesh.updateBound();

Наша сетка готова! Теперь мы хотим это увидеть.

Использование сетки в сцене

Мы создаем материал com.jme3.scene.Geometry и com.jme3.material.Material из нашей mesh, применяем к нему простой цветной материал и присоединяем его к корневому узлу, чтобы он появился на сцене.

Geometry geo = new Geometry("OurMesh", mesh); // используем наш собственный mesh-объект
Material mat = new Material(assetManager,
    "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Blue);
geo.setMaterial(mat);
rootNode.attachChild(geo);

Библиотека для assetManager? Ta-даа!

Использование вместо Quad

Мы создали квадратную сетку, которой можно заменить Quad, например:

Quad quad = new Quad(1,1); // Заменим определение координат вершин и текстур плюс индексы
Geometry geo = new Geometry("OurQuad", quad); // использование объекта Quad
Material mat = new Material(assetManager,
    "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Blue);
geo.setMaterial(mat);
rootNode.attachChild(geo);

Если вы хотите изменить координаты текстур, чтобы изменить масштаб текстуры, используйте:

Quad quad = new Quad(1,1);
quad.scaleTextureCoordinates(new Vector2f(width , height));

Динамические сетки(Dynamic Meshes)

Если вы динамически изменяете сетку способом, который изменяет границы модели, вам необходимо обновить её:

  1. Вызовите updateBound() для объекта сетки или
  2. Вызовите updateModelBound() для объекта Geometry, содержащего сетку, которая, в свою очередь, вызывает updateBound() в сетке.

Метод updateModelBound() предупредит вас о том, что обычно не стоит его использовать, но в этом специальном случае его предупреждение можно игнорировать.

N.B.: Это не работает на TerrainQuad. Используйте функцию TerrainQuad.adjustHeight() для редактирования сетки TerrainQuad. Кроме того, если вы хотите впоследствии использовать для них столкновение, вам нужно вызвать TerrainPatch.getMesh().createCollisionData(); Для обновления данных о столкновениях, иначе он столкнется с тем, чтем была старая сетка.

Дополнительные возможности Mesh

В Mesh больше вершинных буферов(vertex buffer), чем в трех, показанных выше. См. Также раздел «Сетка».

Пример: цвета вершин

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

Geometry geo = new Geometry ("ColoredMesh", mesh); // используем пользовательский mesh
Material matVC = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matVC.setBoolean("VertexColor", true);

Вы создаете float массив элементов цветового буфера(color buffer):

  • Назначьте для каждой вершины 4 значения цвета, RGBA.
    • Для обхода четырех значений цвета используйте индекс цвета
int colorIndex = 0;
  • Цветовой буфер содержит четыре значения цвета для каждой вершины.
    • Квадрат в этом примере имеет 4 вершины.
float[] colorArray = new float[4*4];
  • Совет. Если ваш mesh имеет другое количество вершин, вы должны написать:
float[] colorArray = new float[yourVertexCount * 4]

Зациклируйте буфер colorArray, чтобы быстро установить некоторое значение RGBA для каждой вершины. Как обычно, значения цвета RGBA варьируются от 0.0f до 1.0f. Обратите внимание, что значения цветов в этом примере выбраны произвольно. Это просто быстрый цикл, чтобы дать каждой вершине другое значение RGBA (пурпурно-серый, фиолетовый, зеленовато-серый, зеленый, см. Снимок экрана), не записывая слишком много кода. Для вашей собственной сетки вы должны присвоить нужное вам значения для буфера цвета, в зависимости от того, какой цвет вам нужен.

// Примечание: красные и зеленые значения являются произвольными в этом примере
for(int i = 0; i < 4; i++){
   // Красное значение (увеличивается на 0,2 в каждой следующей вершине)
   colorArray[colorIndex++]= 0.1f+(.2f*i);
   // Зеленое значение (уменьшается на 0.2 на каждой следующей вершине)
   colorArray[colorIndex++]= 0.9f-(0.2f*i);
   // Синее значение (в нашем случае остается таким же)
   colorArray[colorIndex++]= 0.5f;
   // Альфа значение (прозрачность не установлена)
   colorArray[colorIndex++]= 1.0f;
}

Затем установите цветовой буфер. Значение цвета RGBA содержит четыре float компонента, таким образом, параметр 4.

mesh.setBuffer(Type.Color, 4, colorArray);
geo.setMaterial(matVC);

Когда вы запускаете этот код, вы видите градиент цветов, выходящий из каждой вершины.

Пример: Использование сеток с Lighting.j3md

В предыдущих примерах использовались сетки вместе с материалом Unshaded.j3md. Если вы хотите использовать сетку с освещенным Фонг материалом (например, Lighting.j3md), сетка должна содержать информацию о своих нормалях. (векторы нормали кодируют, в каком направлении находится полигон сетки, что важно для расчета света и тени!)

float[] normals = new float[12];
normals = new float[]{0,0,1, 0,0,1, 0,0,1, 0,0,1};
mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));

Вам нужно указать столько нормалей, сколько у многоугольника есть вершин. Если для плоского полигона четыре нормали указывают в одном направлении. В этом случае направлением является единичный вектор Z (0,0,1), это означает, что полигон обращен к камере нормалью(лицом).

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

Пример: Режим точки(Point Mode)

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

Geometry coloredMesh = new Geometry ("ColoredMesh", cMesh);
...
mesh.setMode(Mesh.Mode.Points);
mesh.updateBound();
mesh.setStatic();
Geometry points = new Geometry("Points", mesh);
points.setMaterial(mat);
rootNode.attachChild(points);
rootNode.attachChild(geo);

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

Совет по отладке: отсечения

По умолчанию jME3 оптимизирует сетку с помощью «backface culling», это означает, что не нужно рисовать внутри. Он определяет сторону треугольника по порядку вершин: Frontface — это грань, где вершины указаны против часовой стрелки.

Для вас это значит, что по умолчанию ваша пользовательская сетка невидим, когда её смотрят «сзади или изнутри». Это не может быть проблемой, обычно это даже необходимо, потому что так быстрее. В любом случае, игрок не будет смотреть на внутреннюю часть большинства объектов. Например, если ваш заказ сетки является закрытым многогранник, или плоские обои, как объект, то отрисовка задних поверхностей (внутренняя часть стойки, задняя часть картины, и т.д.) действительно будет пустой тратой ресурсов.

Однако в ситуации, когда вы используете объекты у которых должны быть видны задние грани, у вас есть два варианта:

  • Если у вас есть очень простая сцена, вы можете просто деактивировать очистку заднего контура для этого материала этой одной сетки.
mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
  • Другое решение для действительно двусторонних сеток — указать каждый треугольник дважды, второй раз с противоположным порядком вершин. Второй (перевернутый) треугольник — это второй фронт, который закрывает заглушенную заднюю поверхность.
int[] indexes = { 2,0,1, 1,3,2, 2,3,1, 1,0,2 };

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

  • Spatial — содержит дополнительную информацию о том, как отлаживать пользовательские сетки (которые не отображаются должным образом), изменив поведение по умолчанию.
  • Mesh — дополнительные сведения о расширенных свойствах сетки

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

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

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