Unity:1万個のキューブ/Cube/立方体を頂点移動だけで動かす
目次
はじめに
1万個のキューブを移動する場合、以下のような方法があります。今回は1メッシュに結合して頂点情報を書き換える事により描画を行ってみました。
- プリミティブ型のキューブオブジェクトを1万個素直に配置して、transform.position を移動する方法
- shader で描画する方法(過去に作りました)
- 1メッシュに結合してメッシュの頂点を移動する方法
Shader で描画する方法は高速なのですが、WebGLでは Geometry Shaderが使えなかったため、本手法で検証してみました。
Unityを知らない方は、ぜひ こちらの記事 をご参照ください。
Link
- Github:https://github.com/fastsystem/unity-move-one-mesh
- 関連記事:Unity:Mesh(メッシュ)の動的生成でキューブを生成
- 関連記事:Unity:Geometry Shader を使って100万キューブ/立方体/Cubeを描画する!
- 関連記事:Unity:Compute Shader と Geometry Shader で60fps/100万個のキューブを描画!
- デモ:http://www.fast-system.jp/wp-content/uploads/static/unity-move-one-mesh/
ソースコード
1メッシュで生成して、Positionを書き換える方法
- Start時に、1万個のキューブを1Meshとして作成します。
- この時に頂点情報はメンバで保持しておきます。
- Update時に、1万個のキューブの位置を更新します。
- この時に中心座標ではなく24頂点分の座標を再計算します。
- bounds を再計算すると描画が安定します。
using UnityEngine;
using System.Collections.Generic;
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class CreateOneMesh : MonoBehaviour
{
private Mesh mesh = null;
private int pointMax = 10000; // Cubeの最大数
private List<Vector3> vertices = new List<Vector3>();
void Start()
{
mesh = GetComponent<MeshFilter>().mesh;
InitMesh();
}
void OnDistroy()
{
mesh.Clear();
}
void Update()
{
// 更新後のキューブの位置を定義する
var points = new List<Vector3>();
for (int x = -50; x < 50; x++) // 100
{
for (int z = -50; z < 50; z++) // 100
{
var y = Mathf.Sin(Mathf.Sqrt(x * x + z * z) / 4 + Time.realtimeSinceStartup) * 3;
points.Add(new Vector3(x, y, z));
}
}
UpdateMesh(points);
}
private void InitMesh()
{
vertices = new List<Vector3>();
List<Vector3> normals = new List<Vector3>();
List<int> triangles = new List<int>();
for (int i = 0; i < pointMax; i++)
{
int triangleOffset = vertices.Count;
// 頂点の設定
// 8点だと法線の計算で影が上手く処理できないので、24点で設定する
vertices.AddRange(new Vector3[] {
new Vector3 (0, 0, 0), // face front
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0), // face back
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0), // face top
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0), // face bottom
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0), // face right
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0), // face left
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
new Vector3 (0, 0, 0),
});
// Normalsの定義
normals.AddRange(new Vector3[] {
new Vector3 (0, 0, -1), // face front
new Vector3 (0, 0, -1),
new Vector3 (0, 0, -1),
new Vector3 (0, 0, -1),
new Vector3 (0, 0, 1), // face back
new Vector3 (0, 0, 1),
new Vector3 (0, 0, 1),
new Vector3 (0, 0, 1),
new Vector3 (0, 1, 0), // face top
new Vector3 (0, 1, 0),
new Vector3 (0, 1, 0),
new Vector3 (0, 1, 0),
new Vector3 (0, -1, 0), // face bottom
new Vector3 (0, -1, 0),
new Vector3 (0, -1, 0),
new Vector3 (0, -1, 0),
new Vector3 (1, 0, 0), // face right
new Vector3 (1, 0, 0),
new Vector3 (1, 0, 0),
new Vector3 (1, 0, 0),
new Vector3 (-1, 0, 0), // face left
new Vector3 (-1, 0, 0),
new Vector3 (-1, 0, 0),
new Vector3 (-1, 0, 0),
});
// 面の設定
triangles.AddRange(new int[] {
triangleOffset + 0, triangleOffset + 2, triangleOffset + 1, //face front
triangleOffset + 0, triangleOffset + 3, triangleOffset + 2,
triangleOffset + 5, triangleOffset + 4, triangleOffset + 7, //face back
triangleOffset + 5, triangleOffset + 7, triangleOffset + 6,
triangleOffset + 8, triangleOffset + 10, triangleOffset + 9, //face top
triangleOffset + 8, triangleOffset + 11, triangleOffset + 10,
triangleOffset + 12, triangleOffset + 14, triangleOffset + 13, //face bottom
triangleOffset + 12, triangleOffset + 15, triangleOffset + 14,
triangleOffset + 16, triangleOffset + 18, triangleOffset + 17, //face right
triangleOffset + 16, triangleOffset + 19, triangleOffset + 18,
triangleOffset + 20, triangleOffset + 22, triangleOffset + 21, //face left
triangleOffset + 20, triangleOffset + 23, triangleOffset + 22,
});
}
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
mesh.Clear();
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
mesh.normals = normals.ToArray();
// mesh.Optimize();
// mesh.RecalculateNormals();
}
public void UpdateMesh(List<Vector3> points)
{
var v0 = -0.5f;
var v1 = 0.5f;
for (int i = 0; i < points.Count; i++)
{
float x = points[i].x;
float y = points[i].y;
float z = points[i].z;
int idx = i * 24;
vertices[idx + 0] = new Vector3(x + v0, y + v0, z + v0); // face front
vertices[idx + 1] = new Vector3(x + v1, y + v0, z + v0);
vertices[idx + 2] = new Vector3(x + v1, y + v1, z + v0);
vertices[idx + 3] = new Vector3(x + v0, y + v1, z + v0);
vertices[idx + 4] = new Vector3(x + v0, y + v1, z + v1); // face back
vertices[idx + 5] = new Vector3(x + v1, y + v1, z + v1);
vertices[idx + 6] = new Vector3(x + v1, y + v0, z + v1);
vertices[idx + 7] = new Vector3(x + v0, y + v0, z + v1);
vertices[idx + 8] = new Vector3(x + v0, y + v1, z + v1); // face top
vertices[idx + 9] = new Vector3(x + v0, y + v1, z + v0);
vertices[idx + 10] = new Vector3(x + v1, y + v1, z + v0);
vertices[idx + 11] = new Vector3(x + v1, y + v1, z + v1);
vertices[idx + 12] = new Vector3(x + v1, y + v0, z + v1); // face bottom
vertices[idx + 13] = new Vector3(x + v1, y + v0, z + v0);
vertices[idx + 14] = new Vector3(x + v0, y + v0, z + v0);
vertices[idx + 15] = new Vector3(x + v0, y + v0, z + v1);
vertices[idx + 16] = new Vector3(x + v1, y + v1, z + v1); // face right
vertices[idx + 17] = new Vector3(x + v1, y + v1, z + v0);
vertices[idx + 18] = new Vector3(x + v1, y + v0, z + v0);
vertices[idx + 19] = new Vector3(x + v1, y + v0, z + v1);
vertices[idx + 20] = new Vector3(x + v0, y + v0, z + v0); // face left
vertices[idx + 21] = new Vector3(x + v0, y + v1, z + v0);
vertices[idx + 22] = new Vector3(x + v0, y + v1, z + v1);
vertices[idx + 23] = new Vector3(x + v0, y + v0, z + v1);
}
mesh.SetVertices(vertices);
mesh.RecalculateBounds(); // boundsを計算しなおさないと再描画がおかしいので実行
}
}
1万個のオブジェクトを配置して移動
- 開始時に、Instanced で1万個のキューブを生成します。(Primitive型を生成する場合は GameObject.CreatePrimitive を使います)
- Update時に、1万個のキューブの位置を更新します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CreateManyObject : MonoBehaviour
{
private List<GameObject> cubes;
void OnEnable()
{
cubes = new List<GameObject>();
for (int x = -50; x < 50; x++) // 100
{
for (int z = -50; z < 50; z++) // 100
{
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.transform.position = new Vector3(x, 0, z);
cubes.Add(cube);
}
}
}
void OnDisable()
{
foreach(var cube in cubes)
GameObject.Destroy(cube);
cubes.Clear();
}
void Update()
{
if (cubes.Count == 0) return;
var idx = 0;
for (int x = -50; x < 50; x++)
{
for (int z = -50; z < 50; z++)
{
var y = Mathf.Sin(Mathf.Sqrt(x * x + z * z) / 4 + Time.realtimeSinceStartup) * 3;
cubes[idx].transform.position = new Vector3(x, y, z);
idx++;
}
}
}
}
実行結果
デモがあるでのぜひ試してみてください。
1メッシュでの頂点移動だけだと、60fps前後確保できていました。
1万個のキューブでの位置移動だと、17fps前後といった感じです。
よければ、SNSにシェアをお願いします!