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.
Checking the stable version of the documentation...
Використання ArrayMesh
Цей урок дасть основи використання ArrayMesh.
Для цього ми використаємо функцію add_surface_from_arrays(), яка приймає до п’яти параметрів. Перші два обов’язкові, а останні три необов’язкові.
Перший параметр - PrimitiveType, це концепція OpenGL, яка інструктує графічний процесор, як розташувати примітив на основі вершин, враховуючи, чи це трикутники, лінії, точки тощо. Повний список варіантів дивіться в Mesh.PrimitiveType.
Другий параметр, arrays, є фактичним масивом, який зберігає інформацію про сітку. Масив є звичайним масивом Godot, який складається з порожніх дужок []. Він зберігає Packed**Array (наприклад, PackedVector3Array, PackedInt32Array тощо) для кожного типу інформації, яка використовуватиметься для створення поверхні.
Загальні елементи масивів перераховані нижче разом із позицією, яку вони повинні мати в масивах. Див. Mesh.ArrayType для повного списку.
Індекс |
Сітка.ТипМасиву.Enum |
Тип масиву |
|---|---|---|
0 |
|
PackedVector3Array або PackedVector2Array |
1 |
|
PackedVector3Array |
2 |
|
PackedFloat32Array або PackedFloat64Array груп із 4 плаваючих елементів. Перші 3 числа з плаваючою точкою визначають тангенс, а останні з плаваючою точкою — бінормальний напрямок як -1 або 1. |
3 |
|
PackedColorArray |
4 |
|
PackedVector2Array або PackedVector3Array |
5 |
|
PackedVector2Array або PackedVector3Array |
10 |
|
PackedFloat32Array груп з 4 чисел або PackedInt32Array груп з 4 int. Кожна група містить індекси 4 кісток, які впливають на дану вершину. |
11 |
|
PackedFloat32Array або PackedFloat64Array груп із 4 плаваючих елементів. Кожен float перераховує кількість ваги відповідної кістки в |
12 |
|
PackedInt32Array |
У більшості випадків при створенні сітки ми визначаємо її положенням вершин. Тому зазвичай масив вершин (з індексом 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);
}
}
}
Код, який розміщується посередині, може бути будь-яким. Нижче ми наведемо приклад коду для створення фігур, починаючи з прямокутника.
Генерація прямокутника
Оскільки ми використовуємо 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),
])
verts.AddRange(new Vector3[]
{
new Vector3(0, 0, 0),
new Vector3(0, 0, 1),
new Vector3(1, 0, 0),
new Vector3(1, 0, 1),
});
Масив uvs допомагає описати, де частини текстури повинні розміщуватися на сітці. Значення варіюються від 0 до 1. Залежно від вашої текстури, ви можете захотіти змінити ці значення.
uvs = PackedVector2Array([
Vector2(0, 0),
Vector2(1, 0),
Vector2(0, 1),
Vector2(1, 1),
])
uvs.AddRange(new Vector2[]
{
new Vector2(0, 0),
new Vector2(1, 0),
new Vector2(0, 1),
new Vector2(1, 1),
});
Масив normals використовується для опису напрямку, куди спрямовані вершини, та використовується в розрахунках освітлення. У цьому прикладі ми за замовчуванням використовуватимемо напрямок Vector3.UP.
normals = PackedVector3Array([
Vector3.UP,
Vector3.UP,
Vector3.UP,
Vector3.UP,
])
normals.AddRange(new Vector3[]
{
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.
Індекс |
|
|
|
|---|---|---|---|
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.
])
indices.AddRange(new int[]
{
0, 2, 1, // Draw the first triangle.
2, 3, 1, // Draw the second triangle.
});
У сукупності код генерації прямокутника виглядає так:
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.
using System.Collections.Generic;
public partial class MeshInstance3d : MeshInstance3D
{
public override void _Ready()
{
// Insert setting up the surface array and lists here.
verts.AddRange(new Vector3[]
{
new Vector3(0, 0, 0),
new Vector3(0, 0, 1),
new Vector3(1, 0, 0),
new Vector3(1, 0, 1),
});
uvs.AddRange(new Vector2[]
{
new Vector2(0, 0),
new Vector2(1, 0),
new Vector2(0, 1),
new Vector2(1, 1),
});
normals.AddRange(new Vector3[]
{
Vector3.Up,
Vector3.Up,
Vector3.Up,
Vector3.Up,
});
indices.AddRange(new int[]
{
0, 2, 1,
2, 3, 1,
});
// Insert committing to the ArrayMesh here.
}
}
Для більш складного прикладу дивіться розділ про генерацію сфер нижче.
Генерація сфери
Ось зразок коду для створення сфери. Хоча код представлений в GDScript, немає нічого специфічного для Godot в підході до його створення. Ця реалізація не має нічого спільного з ArrayMesh-ами і є лише загальним підходом до створення сфери. Якщо у вас виникли проблеми з розумінням, або ви хочете дізнатися більше про процедурну геометрію, ви можете використовувати будь-який урок, який знайдете в Інтернеті.
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);