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

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

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

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

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

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

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

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

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

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

Создание Сетки Квадрата

В этом уроке мы хотим создать квадрат 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 хранятся в буфере.

  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 = 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() для объекта mesh или
  2. Вызовите updateModelBound() для объекта Geometry, содержащего сетку, которая, в свою очередь, вызывает updateBound() в сетке.

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

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

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

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

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

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

Geometry geo = new Geometry ("ColoredMesh", 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];
  • Совет. Если ваша сетка имеет другое количество вершин, вы должны написать:
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. Все права сохранены.