Использование ArrayMesh
В этом уроке будут представлены основы использования ArrayMesh.
Для этого мы будем использовать функцию add_surface_from_arrays(), которая принимает до пяти параметров. Первые два обязательны, а последние три опциональны.
Первым параметром является PrimitiveType, концепция OpenGL, которая указывает GPU, как расположить примитив на основе заданных вершин, т.е. представляют ли они треугольники, линии, точки и т.д. Доступные варианты см. в Mesh.PrimitiveType.
Второй параметр, arrays, представляет собой массив, хранящий информацию о сетке. Это обычный массив Godot, созданный с помощью пустых скобок []. Он хранит Packed**Array (например, PackedVector3Array, PackedInt32Array и т.д.) для каждого типа информации, используемой при построении поверхности.
Распространённые элементы arrays перечислены ниже вместе с позициями, которые они должны занимать в arrays. Полный список см. в Mesh.ArrayType.
Индекс |
Mesh.ArrayType - перечисление |
Тип массива |
|---|---|---|
0 |
|
|
1 |
|
|
2 |
|
PackedFloat32Array или PackedFloat64Array групп по 4 числа с плавающей запятой. Первые 3 числа определяют касательную, а последнее число - направление бинормали как -1 или 1. |
3 |
|
|
4 |
|
|
5 |
|
|
10 |
|
PackedFloat32Array групп по 4 числа с плавающей запятой или PackedInt32Array групп по 4 целых числа. Каждая группа перечисляет индексы 4 костей, влияющих на данную вершину. |
11 |
|
PackedFloat32Array или PackedFloat64Array групп по 4 числа с плавающей запятой. Каждое число указывает вес соответствующей кости из |
12 |
|
В большинстве случаев при создании сетки мы определяем ее по позициям вершин. Поэтому обычно массив вершин (с индексом 0) является обязательным, тогда как массив индексов (с индексом 12) является необязательным и будет использоваться только в том случае, если он включен. Также возможно создать сетку только с массивом индексов и без массива вершин, но это выходит за рамки данного руководства.
Все остальные массивы содержат информацию о вершинах. Они опциональны и будут использоваться только если включены. Некоторые из этих массивов (например, ARRAY_COLOR) используют по одной записи на вершину для предоставления дополнительной информации о вершинах. Они должны иметь тот же размер, что и массив вершин. Другие массивы (например, ARRAY_TANGENT) используют четыре записи для описания одной вершины. Они должны быть ровно в четыре раза больше массива вершин.
При обычном использовании последние три параметра в add_surface_from_arrays() обычно оставляют пустыми.
Настройка ArrayMesh
В редакторе создайте MeshInstance3D и добавьте к нему ArrayMesh в инспекторе. Обычно добавление ArrayMesh в редакторе не имеет особого смысла, но в данном случае это позволяет получить доступ к ArrayMesh из кода без его создания.
Затем добавьте скрипт к MeshInstance3D.
Ниже _ready() создайте новый массив.
var surface_array = []
Godot.Collections.Array surfaceArray = [];
Это будет массив, в котором мы храним нашу информацию о поверхности, он будет содержать все массивы данных, которые нужны поверхности. Godot ожидает, что он будет иметь размер Mesh.ARRAY_MAX, поэтому измените его размер соответствующим образом.
var surface_array = []
surface_array.resize(Mesh.ARRAY_MAX)
Godot.Collections.Array surfaceArray = [];
surfaceArray.Resize((int)Mesh.ArrayType.Max);
Затем создайте массивы для каждого типа данных, который вы будете использовать.
var verts = PackedVector3Array()
var uvs = PackedVector2Array()
var normals = PackedVector3Array()
var indices = PackedInt32Array()
List<Vector3> verts = [];
List<Vector2> uvs = [];
List<Vector3> normals = [];
List<int> indices = [];
После того, как вы заполнили свои массивы данных своей геометрией, вы можете создать сетку, добавив каждый массив в 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)
surfaceArray[(int)Mesh.ArrayType.Vertex] = verts.ToArray();
surfaceArray[(int)Mesh.ArrayType.TexUV] = uvs.ToArray();
surfaceArray[(int)Mesh.ArrayType.Normal] = normals.ToArray();
surfaceArray[(int)Mesh.ArrayType.Index] = indices.ToArray();
var arrMesh = Mesh as ArrayMesh;
if (arrMesh != null)
{
// No blendshapes, lods, or compression used.
arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, surfaceArray);
}
Примечание
В этом примере мы использовали 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)
public partial class MyMeshInstance3D : MeshInstance3D
{
public override void _Ready()
{
Godot.Collections.Array surfaceArray = [];
surfaceArray.Resize((int)Mesh.ArrayType.Max);
// C# arrays cannot be resized or expanded, so use Lists to create geometry.
List<Vector3> verts = [];
List<Vector2> uvs = [];
List<Vector3> normals = [];
List<int> indices = [];
/***********************************
* Insert code here to generate mesh.
* *********************************/
// Convert Lists to arrays and assign to surface array
surfaceArray[(int)Mesh.ArrayType.Vertex] = verts.ToArray();
surfaceArray[(int)Mesh.ArrayType.TexUV] = uvs.ToArray();
surfaceArray[(int)Mesh.ArrayType.Normal] = normals.ToArray();
surfaceArray[(int)Mesh.ArrayType.Index] = indices.ToArray();
var arrMesh = Mesh as ArrayMesh;
if (arrMesh != null)
{
// Create mesh surface from mesh array
// No blendshapes, lods, or compression used.
arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, surfaceArray);
}
}
}
Код, который находится в середине, может быть любым, каким вы захотите. Ниже мы приведем пример кода для генерации сферы.
Создаём геометрию
Здесь приведен пример кода для генерации сферы. Хотя код представлен на 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.
public partial class MyMeshInstance3D : MeshInstance3D
{
private int _rings = 50;
private int _radialSegments = 50;
private float _radius = 1;
public override void _Ready()
{
// Insert setting up the surface array and lists here.
// Vertex indices.
var thisRow = 0;
var prevRow = 0;
var point = 0;
// Loop over rings.
for (var i = 0; i < _rings + 1; i++)
{
var v = ((float)i) / _rings;
var w = Mathf.Sin(Mathf.Pi * v);
var y = Mathf.Cos(Mathf.Pi * v);
// Loop over segments in ring.
for (var j = 0; j < _radialSegments + 1; j++)
{
var u = ((float)j) / _radialSegments;
var x = Mathf.Sin(u * Mathf.Pi * 2);
var z = Mathf.Cos(u * Mathf.Pi * 2);
var vert = new Vector3(x * _radius * w, y * _radius, z * _radius * w);
verts.Add(vert);
normals.Add(vert.Normalized());
uvs.Add(new Vector2(u, v));
point += 1;
// Create triangles in ring using indices.
if (i > 0 && j > 0)
{
indices.Add(prevRow + j - 1);
indices.Add(prevRow + j);
indices.Add(thisRow + j - 1);
indices.Add(prevRow + j);
indices.Add(thisRow + j);
indices.Add(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)
// Saves mesh to a .tres file with compression enabled.
ResourceSaver.Save(Mesh, "res://sphere.tres", ResourceSaver.SaverFlags.Compress);