Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Использование ArrayMesh

В этом уроке будут представлены основы использования ArrayMesh.

Для этого мы будем использовать функцию add_surface_from_arrays(), которая принимает до пяти параметров. Первые два обязательны, а последние три опциональны.

Первым параметром является PrimitiveType, концепция OpenGL, которая указывает GPU, как расположить примитив на основе заданных вершин, т.е. представляют ли они треугольники, линии, точки и т.д. Доступные варианты см. в Mesh.PrimitiveType.

Второй параметр, arrays, представляет собой массив, хранящий информацию о сетке. Это обычный массив Godot, созданный с помощью пустых скобок []. Он хранит Packed**Array (например, PackedVector3Array, PackedInt32Array и т.д.) для каждого типа информации, используемой при построении поверхности.

Распространённые элементы arrays перечислены ниже вместе с позициями, которые они должны занимать в arrays. Полный список см. в Mesh.ArrayType.

Индекс

Mesh.ArrayType - перечисление

Тип массива

0

ARRAY_VERTEX

PackedVector3Array или PackedVector2Array

1

ARRAY_NORMAL

PackedVector3Array

2

ARRAY_TANGENT

PackedFloat32Array или PackedFloat64Array групп по 4 числа с плавающей запятой. Первые 3 числа определяют касательную, а последнее число - направление бинормали как -1 или 1.

3

ARRAY_COLOR

PackedColorArray

4

ARRAY_TEX_UV

PackedVector2Array или PackedVector3Array

5

ARRAY_TEX_UV2

PackedVector2Array или PackedVector3Array

10

ARRAY_BONES

PackedFloat32Array групп по 4 числа с плавающей запятой или PackedInt32Array групп по 4 целых числа. Каждая группа перечисляет индексы 4 костей, влияющих на данную вершину.

11

ARRAY_WEIGHTS

PackedFloat32Array или PackedFloat64Array групп по 4 числа с плавающей запятой. Каждое число указывает вес соответствующей кости из ARRAY_BONES для данной вершины.

12

ARRAY_INDEX

PackedInt32Array

В большинстве случаев при создании сетки мы определяем ее по позициям вершин. Поэтому обычно массив вершин (с индексом 0) является обязательным, тогда как массив индексов (с индексом 12) является необязательным и будет использоваться только в том случае, если он включен. Также возможно создать сетку только с массивом индексов и без массива вершин, но это выходит за рамки данного руководства.

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

При обычном использовании последние три параметра в add_surface_from_arrays() обычно оставляют пустыми.

Настройка ArrayMesh

В редакторе создайте MeshInstance3D и добавьте к нему ArrayMesh в инспекторе. Обычно добавление ArrayMesh в редакторе не имеет особого смысла, но в данном случае это позволяет получить доступ к ArrayMesh из кода без его создания.

Затем добавьте скрипт к MeshInstance3D.

Ниже _ready() создайте новый массив.

var surface_array = []

Это будет массив, в котором мы храним нашу информацию о поверхности, он будет содержать все массивы данных, которые нужны поверхности. Godot ожидает, что он будет иметь размер Mesh.ARRAY_MAX, поэтому измените его размер соответствующим образом.

var surface_array = []
surface_array.resize(Mesh.ARRAY_MAX)

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

var verts = PackedVector3Array()
var uvs = PackedVector2Array()
var normals = PackedVector3Array()
var indices = PackedInt32Array()

После того, как вы заполнили свои массивы данных своей геометрией, вы можете создать сетку, добавив каждый массив в surface_array, а затем зафиксировав его в сетке.

surface_array[Mesh.ARRAY_VERTEX] = verts
surface_array[Mesh.ARRAY_TEX_UV] = uvs
surface_array[Mesh.ARRAY_NORMAL] = normals
surface_array[Mesh.ARRAY_INDEX] = indices

# No blendshapes, lods, or compression used.
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array)

Примечание

В этом примере мы использовали Mesh.PRIMITIVE_TRIANGLES, но вы можете использовать любой примитивный тип, доступный из сетки.

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

extends MeshInstance3D

func _ready():
    var surface_array = []
    surface_array.resize(Mesh.ARRAY_MAX)

    # PackedVector**Arrays for mesh construction.
    var verts = PackedVector3Array()
    var uvs = PackedVector2Array()
    var normals = PackedVector3Array()
    var indices = PackedInt32Array()

    #######################################
    ## Insert code here to generate mesh ##
    #######################################

    # Assign arrays to surface array.
    surface_array[Mesh.ARRAY_VERTEX] = verts
    surface_array[Mesh.ARRAY_TEX_UV] = uvs
    surface_array[Mesh.ARRAY_NORMAL] = normals
    surface_array[Mesh.ARRAY_INDEX] = indices

    # Create mesh surface from mesh array.
    # No blendshapes, lods, or compression used.
    mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array)

The code that goes in the middle can be whatever you want. Below we will present some example code for generating shapes, starting with a rectangle.

Generating a rectangle

Поскольку мы используем Mesh.PRIMITIVE_TRIANGLES для рендеринга, мы построим прямоугольник из треугольников.

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

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

Чтобы нарисовать этот прямоугольник, определите координаты каждой вершины в массиве verts.

verts = PackedVector3Array([
        Vector3(0, 0, 0),
        Vector3(0, 0, 1),
        Vector3(1, 0, 0),
        Vector3(1, 0, 1),
    ])

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

uvs = PackedVector2Array([
        Vector2(0, 0),
        Vector2(1, 0),
        Vector2(0, 1),
        Vector2(1, 1),
    ])

Массив normals используется для описания направления, в котором обращены вершины, и применяется в расчетах освещения. Для этого примера мы будем использовать направление по умолчанию Vector3.UP.

normals = PackedVector3Array([
        Vector3.UP,
        Vector3.UP,
        Vector3.UP,
        Vector3.UP,
    ])

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

Например, чтобы нарисовать первый треугольник, нам нужно будет нарисовать вершины (0, 0, 0), (1, 0, 0) и (0, 0, 1) в указанном порядке. Это то же самое, что нарисовать vert[0], vert[2] и vert[1], то есть с индексами 0, 2 и 1, в массиве verts. Эти значения индексов и определяет массив indices.

Индекс

verts[Index]

uvs[Index]

normals[Index]

0

(0, 0, 0)

(0, 0)

Vector3.UP

1

(0, 0, 1)

(1, 0)

Vector3.UP

2

(1, 0, 0)

(0, 1)

Vector3.UP

3

(1, 0, 1)

(1, 1)

Vector3.UP

indices = PackedInt32Array([
        0, 2, 1, # Draw the first triangle.
        2, 3, 1, # Draw the second triangle.
    ])

Put together, the rectangle generation code looks like:

extends MeshInstance3D

func _ready():

  # Insert setting up the PackedVector**Arrays here.

  verts = PackedVector3Array([
          Vector3(0, 0, 0),
          Vector3(0, 0, 1),
          Vector3(1, 0, 0),
          Vector3(1, 0, 1),
      ])

  uvs = PackedVector2Array([
          Vector2(0, 0),
          Vector2(1, 0),
          Vector2(0, 1),
          Vector2(1, 1),
      ])

  normals = PackedVector3Array([
          Vector3.UP,
          Vector3.UP,
          Vector3.UP,
          Vector3.UP,
      ])

  indices = PackedInt32Array([
          0, 2, 1,
          2, 3, 1,
      ])

  # Insert committing to the ArrayMesh here.

Более сложный пример приведен в разделе создание сферы ниже.

Generating a sphere

Здесь приведен пример кода для генерации сферы. Хотя код представлен на GDScript, в подходе к генерации сферы нет ничего специфичного для Godot. Эта реализация не имеет ничего общего с ArrayMeshes и является просто общим подходом к генерации сферы. Если у вас возникли трудности с пониманием или вы хотите узнать больше о процедурной геометрии в целом, вы можете воспользоваться любым учебником, который найдете в Интернете.

extends MeshInstance3D

var rings = 50
var radial_segments = 50
var radius = 1

func _ready():

    # Insert setting up the PackedVector**Arrays here.

    # Vertex indices.
    var thisrow = 0
    var prevrow = 0
    var point = 0

    # Loop over rings.
    for i in range(rings + 1):
        var v = float(i) / rings
        var w = sin(PI * v)
        var y = cos(PI * v)

        # Loop over segments in ring.
        for j in range(radial_segments + 1):
            var u = float(j) / radial_segments
            var x = sin(u * PI * 2.0)
            var z = cos(u * PI * 2.0)
            var vert = Vector3(x * radius * w, y * radius, z * radius * w)
            verts.append(vert)
            normals.append(vert.normalized())
            uvs.append(Vector2(u, v))
            point += 1

            # Create triangles in ring using indices.
            if i > 0 and j > 0:
                indices.append(prevrow + j - 1)
                indices.append(prevrow + j)
                indices.append(thisrow + j - 1)

                indices.append(prevrow + j)
                indices.append(thisrow + j)
                indices.append(thisrow + j - 1)

        prevrow = thisrow
        thisrow = point

  # Insert committing to the ArrayMesh here.

Сохранение

Наконец, мы можем использовать класс ResourceSaver для сохранения ArrayMesh. Это полезно, когда вы хотите сгенерировать сетку, а затем использовать ее позже без необходимости повторной генерации.

# Saves mesh to a .tres file with compression enabled.
ResourceSaver.save(mesh, "res://sphere.tres", ResourceSaver.FLAG_COMPRESS)