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

Since we are using Mesh.PRIMITIVE_TRIANGLES to render, we will construct a rectangle with triangles.

A rectangle is formed by two triangles sharing four vertices. For our example, we will create a rectangle with its top left point at (0, 0, 0) with a width and length of one as shown below:

A rectangle made of two triangles sharing four vertices.

To draw this rectangle, define the coordinates of each vertex in the verts array.

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

The uvs array helps describe where parts of a texture should go onto the mesh. The values range from 0 to 1. Depending on your texture, you may want to change these values.

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

The normals array is used to describe the direction the vertices face and is used in lighting calculations. For this example, we will default to the Vector3.UP direction.

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

The indices array defines the order vertices are drawn. Godot renders in a clockwise direction, meaning that we must specify the vertices of a triangle we want to draw in clockwise order.

For example, to draw the first triangle, we will want to draw the vertices (0, 0, 0), (1, 0, 0), and (0, 0, 1) in that order. This is the same as drawing vert[0], vert[2], and vert[1], i.e., indices 0, 2, and 1, in the verts array. These index values are what the indices array defines.

Индекс

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.

For a more complex example, see the sphere generation section below.

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)