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...
Nutzen des ArrayMeshs
In diesem Tutorial werden die Grundlagen der Verwendung eines ArrayMeshs erläutert.
Zu diesem Zweck verwenden wir die Funktion add_surface_from_arrays(), die bis zu fünf Parameter benötigt. Die ersten beiden sind erforderlich, während die letzten drei optional sind.
Der erste Parameter ist der PrimitiveType, ein OpenGL-Konzept, das die GPU anweist, wie das Primitiv auf der Grundlage der angegebenen Vertices anzuordnen ist, d.h. ob sie Dreiecke, Linien, Punkte usw. darstellen. Siehe Mesh.PrimitiveType für die verfügbaren Optionen.
Der zweite Parameter, arrays, ist das eigentliche Array, das die Mesh-Informationen speichert. Das Array ist ein normales Godot-Array, das mit leeren Klammern [] aufgebaut ist. Es speichert ein Packed**Array (z.B. PackedVector3Array, PackedInt32Array, etc.) für jede Art von Information, die zum Aufbau der Oberfläche verwendet wird.
Allgemeine Elemente von Arrays sind unten aufgelistet, zusammen mit der Position, die sie innerhalb von Arrays haben müssen. Siehe Mesh.ArrayType für eine vollständige Liste.
Index |
Mesh.ArrayType-Enum |
Array-Typ |
|---|---|---|
0 |
|
|
1 |
|
|
2 |
|
PackedFloat32Array oder PackedFloat64Array aus Gruppen von 4 Floats. Die ersten 3 Floats bestimmen den Tangens und der letzte Float die binormale Richtung als -1 oder 1. |
3 |
|
|
4 |
|
|
5 |
|
|
10 |
|
PackedFloat32Array aus Gruppen von 4 Floats oder PackedInt32Array aus Gruppen von 4 Ints. Jede Gruppe listet die Indizes von 4 Knochen auf, die einen bestimmten Vertex betreffen. |
11 |
|
PackedFloat32Array oder PackedFloat64Array aus Gruppen von 4 Floats. Jeder Float listet die Menge an Gewicht auf, die der entsprechende Knochen in |
12 |
|
In den meisten Fällen, in denen wir ein Mesh erstellen, definieren wir es durch seine Vertexpositionen. Normalerweise ist also das Vertex-Array (bei Index 0) erforderlich, während das Index-Array (bei Index 12) optional ist und nur verwendet wird, wenn es angegeben wird. Es ist auch möglich, ein Mesh nur mit dem Index-Array und ohne Vertex-Array zu erstellen, aber das würde den Rahmen dieses Tutorials sprengen.
Alle anderen Arrays enthalten Informationen über die Vertices. Sie sind optional und werden nur verwendet, wenn sie angegeben werden. Einige dieser Arrays (z.B. ARRAY_COLOR) verwenden einen Eintrag pro Vertex, um zusätzliche Informationen über die Vertices zu liefern. Sie müssen die gleiche Größe wie das Vertex-Array haben. Andere Arrays (z.B. ARRAY_TANGENT) verwenden vier Einträge, um einen einzelnen Vertex zu beschreiben. Diese müssen genau viermal so groß sein wie das Vertex-Array.
Bei normaler Verwendung werden die letzten drei Parameter in add_surface_from_arrays() üblicherweise leer gelassen.
Einrichten des ArrayMesh
Erstellen Sie im Editor ein MeshInstance3D und fügen Sie ihm im Inspektor ein ArrayMesh hinzu. Normalerweise ist es nicht sinnvoll, ein ArrayMesh im Editor hinzuzufügen, aber in diesem Fall erlaubt es uns, vom Code aus auf das ArrayMesh zuzugreifen, ohne eines zu erstellen.
Fügen Sie als Nächstes der MeshInstance3D ein Skript hinzu.
Erstellen Sie unter _ready() ein neues Array.
var surface_array = []
Godot.Collections.Array surfaceArray = [];
Dies wird das Array sein, in dem wir unsere Oberflächeninformationen aufbewahren - es wird alle Arrays von Daten enthalten, die von der Oberfläche benötigt wird. Godot erwartet, dass es die Größe Mesh.ARRAY_MAX hat, also passen Sie es entsprechend an.
var surface_array = []
surface_array.resize(Mesh.ARRAY_MAX)
Godot.Collections.Array surfaceArray = [];
surfaceArray.Resize((int)Mesh.ArrayType.Max);
Erstellen Sie als Nächstes die Arrays für jeden Datentyp, den Sie verwenden möchten.
var verts = PackedVector3Array()
var uvs = PackedVector2Array()
var normals = PackedVector3Array()
var indices = PackedInt32Array()
List<Vector3> verts = [];
List<Vector2> uvs = [];
List<Vector3> normals = [];
List<int> indices = [];
Sobald Sie Ihre Daten-Arrays mit Ihrer Geometrie gefüllt haben, können Sie ein Mesh erstellen, indem Sie jedes Array zu surface_array hinzufügen und dann dem Mesh zuweisen.
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);
}
Bemerkung
In diesem Beispiel haben wir Mesh.PRIMITIVE_TRIANGLES verwendet, aber Sie können jeden primitiven Typ verwenden, der im Mesh verfügbar ist.
Zusammengenommen sieht der vollständige Code wie folgt aus:
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
Hier ist ein Beispielcode für die Erzeugung einer Kugel. Obwohl der Code in GDScript präsentiert wird, gibt es nichts Godot-spezifisches an der Herangehensweise zur Erzeugung. Diese Implementierung hat nichts Besonderes mit ArrayMeshes zu tun, sondern ist nur ein allgemeiner Ansatz zur Erzeugung einer Kugel. Wenn Sie Probleme mit dem Verständnis haben oder mehr über prozedurale Geometrie im Allgemeinen erfahren möchten, können Sie jedes beliebige Tutorial verwenden, das Sie online finden.
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.
}
}
Speichern
Schließlich können wir die Klasse ResourceSaver verwenden, um das ArrayMesh zu speichern. Dies ist nützlich, wenn man ein Mesh generieren und es später verwenden möchte, ohne es erneut generieren zu müssen.
# 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);