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
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:
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),
])
verts.AddRange(new Vector3[]
{
new Vector3(0, 0, 0),
new Vector3(0, 0, 1),
new Vector3(1, 0, 0),
new 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),
])
uvs.AddRange(new Vector2[]
{
new Vector2(0, 0),
new Vector2(1, 0),
new Vector2(0, 1),
new 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,
])
normals.AddRange(new Vector3[]
{
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.
Í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.
}
}
For a more complex example, see the sphere generation section below.
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);