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 中的概念,用於指示 GPU 如何根據給定的頂點來安排像素,即它們表示的是三角形、線、還是點等等。可選項見 Mesh.PrimitiveType。
第二各參數 arrays 是儲存網格資訊的實際 Array。該陣列是一個普通的 Godot 陣列,用空括弧 [] 建構。它為每一種型別的資訊儲存一個 Pool**Array (如 PoolVector3Array、PoolIntArray 等),用於建構表面。
arrays 可能包含下列元素,另外還必須在 arrays 中包含位置資訊。另請參閱 Mesh.ArrayType。
索引 |
Mesh.ArrayType 列舉 |
陣列 |
|---|---|---|
0 |
|
|
1 |
|
|
2 |
|
每組包含 4 個浮點數的 PackedFloat32Array 或 PackedFloat64Array。前 3 個浮點數決定了切線,而最後一個浮點數決定了副切線方向為 -1 或 1。 |
3 |
|
|
4 |
|
|
5 |
|
|
10 |
|
PackedFloat32Array 的每組 4 個浮點數,或 PackedInt32Array 的每組 4 個整數。每組列出會影響到指定頂點的 4 個骨骼索引。 |
11 |
|
PackedFloat32Array 或 PackedFloat64Array,每組 4 個浮點數。每個浮點數列出在給定頂點上,在 |
12 |
|
在大多數情況下,建立 Mesh 時,我們透過其頂點位置定義它。所以通常,頂點陣列 (位於索引 0) 是必須的,而索引陣列 (位於索引 12) 是選用的,且只有在包含時才會被使用。也可能僅使用索引陣列而非頂點陣列來建立 Mesh,但這超出本教學的範圍。
其他所有陣列包含的都是關於頂點的資訊。他們也是可選的,包含時才會用到。有些陣列(例如 ARRAY_COLOR`)用每個頂點一個元素的形式來提供額外的頂點資訊。他們的大小必須與頂點陣列一致。另一些陣列(例如 ARRAY_TANGENT)用四個元素來描述一個頂點。他們必須正好是頂點陣列的四倍大小。
正常的使用場景下,add_surface_from_arrays() 的最後兩個參數通常都是留空的。
設定遊戲區域
在編輯器中建立一個 MeshInstance3D,並在屬性面板中加上一個 ArrayMesh。一般來說在編輯器加 ArrayMesh 沒什麼意義,但這裡這麼做可以讓我們直接從程式碼存取該 ArrayMesh 而不用自行建立。
將腳本附加到節點。
在 _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);
}
}
}
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.
索引 |
|
|
|
|---|---|---|---|
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
這是生成球體的範例程式碼。儘管程式碼是用 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);