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...
Utilisation du ArrayMesh
Ce tutoriel présente les bases de l'utilisation d'un ArrayMesh.
Pour ce faire, nous utiliserons la fonction add_surface_from_arrays(), qui prend jusqu'à cinq paramètres. Les deux premiers sont obligatoires, tandis que les trois autres sont facultatifs.
Le premier est le PrimitiveType, c'est un concept OpenGL qui indique au GPU comment arranger la primitive en fonction des sommets donnés, qu'il s'agisse de triangles, de lignes, de points, etc. Voir Mesh.PrimitiveType pour les options disponibles.
Le deuxième paramètre, arrays, est le véritable Array qui stocke les informations de maillage. Le tableau est un tableau Godot normal qui est construit avec des crochets vides []. Il stocke un Packed**Array (par exemple, PackedVector3Array, PackedInt32Array, etc.) pour chaque type d'informations qui seront utilisées pour construire la surface.
Les éléments communs de arrays sont listés ci-dessous, avec la position qu'ils doivent avoir dans arrays. Voir aussi Mesh.ArrayType pour une liste complète.
Index |
Mesh.ArrayType Enum |
Type de tableau (array) |
|---|---|---|
0 |
|
|
1 |
|
|
2 |
|
PackedFloat32Array ou PackedFloat64Array en groupe de 4 flottants. Les trois premiers flottants déterminent la tangente, et le dernier flottant la direction binormale en -1 ou 1. |
3 |
|
|
4 |
|
|
5 |
|
|
10 |
|
PackedFloat32Array en groupes de 4 flottants ou PackedInt32Array en groupes de 4 entiers. Chaque groupe énumère les index des 4 os qui affectent un sommet donné. |
11 |
|
PackedFloat32Array ou PackedFloat64Array en groupes de 4 flottants. Chaque flottant liste la quantité de poids que l'os correspondant dans |
12 |
|
In most cases when creating a mesh, we define it by its vertex positions. So usually, the array of vertices (at index 0) is required, while the index array (at index 12) is optional and will only be used if included. It is also possible to create a mesh with only the index array and no vertex array, but that's beyond the scope of this tutorial.
All the other arrays carry information about the vertices. They are optional and will only be used if included. Some of these arrays (e.g. ARRAY_COLOR)
use one entry per vertex to provide extra information about vertices. They must have the same size as the vertex array. Other arrays (e.g. ARRAY_TANGENT) use
four entries to describe a single vertex. These must be exactly four times larger than the vertex array.
Pour une utilisation normale, les trois derniers paramètres de add_surface_from_arrays() sont généralement laissés vides.
Setting up the ArrayMesh
Dans l'éditeur, créez un MeshInstance3D et ajoutez un ArrayMesh à celui-ci dans l'inspecteur. Normalement, l'ajout d'un ArrayMesh dans l'éditeur n'est pas utile, mais dans ce cas, il nous permet d'accéder à l'ArrayMesh depuis le code sans en créer un.
Ensuite, ajoutez un script au MeshInstance3D.
Sous _ready(), créez un nouvel Array.
var surface_array = []
Godot.Collections.Array surfaceArray = [];
Ce sera le tableau dans lequel nous conserverons nos informations de surface, il contiendra tous les tableaux de données dont la surface a besoin. Godot s'attendra à ce qu'il soit de taille Mesh.ARRAY_MAX, alors redimensionnez-le en conséquence.
var surface_array = []
surface_array.resize(Mesh.ARRAY_MAX)
Godot.Collections.Array surfaceArray = [];
surfaceArray.Resize((int)Mesh.ArrayType.Max);
Créez ensuite les arrays pour chaque type de données que vous utiliserez.
var verts = PackedVector3Array()
var uvs = PackedVector2Array()
var normals = PackedVector3Array()
var indices = PackedInt32Array()
List<Vector3> verts = [];
List<Vector2> uvs = [];
List<Vector3> normals = [];
List<int> indices = [];
Une fois que vous avez rempli vos arrays de données avec votre géométrie, vous pouvez créer un maillage en ajoutant chaque array à surface_array, puis en validant le maillage.
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);
}
Note
Dans cet exemple, nous avons utilisé Mesh.PRIMITIVE_TRIANGLES, mais vous pouvez utiliser n'importe quel type primitif disponible à partir de mesh.
Mis ensemble, le code complet ressemble à ceci :
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.
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.
])
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
Voici un exemple de code pour générer une sphère. Bien que le code soit présenté en GDScript, il n'y a rien de spécifique à Godot dans l'approche de sa génération. Cette implémentation n'a rien de particulier à voir avec ArrayMeshes et n'est qu'une approche générique pour générer une sphère. Si vous avez du mal à comprendre ou si vous souhaitez en savoir plus sur la géométrie procédurale en général, vous pouvez utiliser n'importe quel tutoriel que vous trouverez en ligne.
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.
}
}
Enregistrer
Enfin, nous pouvons sauvegarder l'ArrayMesh en utilisant la classe ResourceSaver. Ceci est utile lorsque vous souhaitez générer un maillage, puis l'utiliser plus tard sans avoir à le générer de nouveau.
# 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);