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...
Usando o ArrayMesh
Este tutorial apresentará o básico de uso de um ArrayMesh.
Para isso, usaremos a função add_surface_from_arrays(), que aceita até cinco parâmetros. Os dois primeiros são obrigatórios, enquanto os três últimos são opcionais.
O primeiro parâmetro é o PrimitiveType, um conceito do OpenGL que instrui a GPU sobre como organizar a primitiva com base nos vértices fornecidos, ou seja, se eles representam triângulos, linhas, pontos etc. Veja Mesh.PrimitiveType para as opções disponíveis.
O segundo parâmetro, arrays, é o Array propriamente dito que armazena as informações da malha. O array é um array normal do Godot, construído com colchetes vazios []. Ele armazena um Packed**Array (por exemplo, PackedVector3Array, PackedInt32Array etc.) para cada tipo de informação que será usada para construir a superfície.
Elementos comuns de arrays estão listados abaixo, junto com a posição que devem ocupar dentro de arrays. Veja Mesh.ArrayType para a lista completa.
Índice |
Mesh.ArrayType Enum |
Tipo de matriz |
|---|---|---|
0 |
|
|
1 |
|
|
2 |
|
PackedFloat32Array ou PackedFloat64Array de grupos de 4 números float. Os primeiros 3 floats determinam a tangente, e o último float determina a direção da binormal como -1 ou 1. |
3 |
|
|
4 |
|
|
5 |
|
|
10 |
|
PackedFloat32Array de grupos de 4 floats ou PackedInt32Array de grupos de 4 inteiros. Cada grupo lista os índices de 4 ossos que afetam um determinado vértice. |
11 |
|
PackedFloat32Array ou PackedFloat64Array de grupos de 4 floats. Cada float indica a quantidade de influência que o osso correspondente em |
12 |
|
Geralmente ao criar uma malha, ela é definida pelas posições dos vértices. Portanto, geralmente o array de vértices (no índice 0) é obrigatório, enquanto o array de índices (no índice 12) é opcional e só será usado se for incluído. Também é possível criar uma malha apenas com o array de índices e sem o array de vértices, mas isso está além do escopo deste tutorial.
Todos os outros arrays carregam informações sobre os vértices. Eles são opcionais e só serão usados se incluídos. Alguns desses arrays (por exemplo, ARRAY_COLOR) usam uma entrada por vértice para fornecer informações extras sobre os vértices. Eles devem ter o mesmo tamanho do array de vértices. Outros arrays (por exemplo, ARRAY_TANGENT) usam quatro entradas para descrever um único vértice. Esses devem ter exatamente quatro vezes o tamanho do array de vértices.
Para uso comum, os três últimos parâmetros de add_surface_from_arrays() são geralmente deixados em branco.
Setting up the ArrayMesh
No editor, crie um MeshInstance3D e adicione um ArrayMesh a ele no Inspetor. Normalmente, adicionar um ArrayMesh no editor não é útil, mas neste caso isso nos permite acessar o ArrayMesh pelo código sem precisar criar um.
Next, add a script to the MeshInstance3D.
Em ''_ready()'', crie uma nova Array.
var surface_array = []
Godot.Collections.Array surfaceArray = [];
Esta será a matriz na qual manteremos nossas informações de superfície - ela conterá todas as matrizes de dados que a superfície necessita. Godot espera que ela seja do tamanho Mesh.ARRAY_MAX, portanto, redimensione-a de acordo.
var surface_array = []
surface_array.resize(Mesh.ARRAY_MAX)
Godot.Collections.Array surfaceArray = [];
surfaceArray.Resize((int)Mesh.ArrayType.Max);
Em seguida, crie as matrizes para cada tipo de dado que você usará.
var verts = PackedVector3Array()
var uvs = PackedVector2Array()
var normals = PackedVector3Array()
var indices = PackedInt32Array()
List<Vector3> verts = [];
List<Vector2> uvs = [];
List<Vector3> normals = [];
List<int> indices = [];
Uma vez que você tenha preenchido suas matrizes de dados com sua geometria, você pode criar uma malha adicionando cada matriz a surface_array e, em seguida, comitando para a malha.
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);
}
Nota
Neste exemplo, utilizamos Mesh.PRIMITIVE_TRIANGLES, mas você pode utilizar qualquer tipo primitivo disponível na malha.
Juntos, o código completo se parece com:
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);
}
}
}
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
Já que estamos usando Mesh.PRIMITIVE_TRIANGLES para renderizar, vamos construir um retângulo com triângulos.
Um retângulo é formado por dois triângulos compartilhando quatro vértices. Para o nosso exemplo, criaremos um retângulo com seu ponto superior esquerdo em (0, 0, 0) com uma largura e comprimento de um como mostrado abaixo:
Para desenhar este retângulo, defina as coordenadas de cada vértice no array 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),
});
O array uvs ajuda a descrever onde partes de uma textura devem ir para a malha. Os valores variam de 0 a 1. Dependendo da sua textura, você pode querer alterar esses valores.
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),
});
O array normals é usado para descrever a direção da face dos vértices e é usado em cálculos de iluminação. Por este exemplo, vamos predefinir para a direção Vector3.UP (cima).
normals = PackedVector3Array([
Vector3.UP,
Vector3.UP,
Vector3.UP,
Vector3.UP,
])
normals.AddRange(new Vector3[]
{
Vector3.Up,
Vector3.Up,
Vector3.Up,
Vector3.Up,
});
O array indices define os vértices da ordem são desenhados. Godot renderiza em uma direção de sentido horário, o que significa que devemos especificar os vértices de um triângulo que queremos desenhar em uma ordem no sentido horário.
Por exemplo, para desenhar o primeiro triângulo, vamos querer desenhar os vértices (0, 0, 0), (1, 0, 0), e (0, 0, 1) nessa ordem. Isso é o mesmo que desenhar vert[0], vert[2], e vert[1], isto é, índices 0, 2, e 1, no array vert. Esses valores de índice são o que o array indices define.
Índice |
|
|
|
|---|---|---|---|
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.
});
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.
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.
}
}
Para um exemplo mais complexo, veja a seção de geração de esferas abaixo.
Generating a sphere
Aqui está uma amostra de código para gerar uma esfera. Embora o código seja apresentado no GDScript, não há nada de específico do Godot sobre a abordagem para gerá-lo. Esta implementação não tem nada em particular a ver com ArrayMeshes e é apenas uma abordagem genérica para a geração de uma esfera. Se você estiver tendo problemas para compreendê-la ou quiser aprender mais sobre a geometria de procedimentos em geral, você pode usar qualquer tutorial que encontrar on-line.
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.
}
}
Salvando
Finally, we can use the ResourceSaver class to save the ArrayMesh. This is useful when you want to generate a mesh and then use it later without having to re-generate it.
# 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);