Использование 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)

Код, который находится в середине, может быть любым, каким вы захотите. Ниже мы приведем пример кода для генерации сферы.

Создаём геометрию

Здесь приведен пример кода для генерации сферы. Хотя код представлен на 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)