📌
Unityの動的なMesh作成を高速化する
前置き
Meshオブジェクト動的な変更処理を行っていたのだけど、作成更新に割と処理時間が取られる.
高速化の実験を行ったので、メモしておく.
結論
- Meshは毎回newせずに使い回す
- MarkDynamic + SetVertices(Triangles,UV,Normals)でList要素を割り当てる
- RecalculateNormalsせずに自前のnormalsを使う
- mesh.Clear()は毎回呼び出す
上記を実施することで実行時間が15.10 -> 5.37で35.5%に短縮できた.
PerformanceTest
UnityのPerformacneTestingを利用して比較した.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using Unity.PerformanceTesting;
using UnityEngine;
namespace PerformanceTest
{
public class TrianglePerformanceTest
{
private const int REPEAT = 500;
private const int MEASURE_COUNT = 100;
[Test, Performance]
public void RunBaseline()
{
Measure.Method(() =>
{
var vertices = new List<Vector3>();
var triangles = new List<int>();
var uv = new List<Vector2>();
var normals = new List<Vector3>();
for (var i = 0; i < REPEAT; i++)
{
var offset = new Vector3(i, 0, 0);
var (nextVertices, nextTriangles, nextUV, nextNormals) = CreateTriangle(offset, i * 3);
vertices.AddRange(nextVertices);
triangles.AddRange(nextTriangles);
uv.AddRange(nextUV);
normals.AddRange(nextNormals);
var mesh = CreateMeshPlain(vertices.ToArray(), triangles.ToArray(), uv.ToArray(), normals.ToArray());
}
})
.MeasurementCount(MEASURE_COUNT)
.Run();
}
[Test, Performance]
public void RunUpdateBaseline()
{
Measure.Method(() =>
{
var vertices = new List<Vector3>();
var triangles = new List<int>();
var uv = new List<Vector2>();
var normals = new List<Vector3>();
var mesh = new Mesh();
mesh.MarkDynamic();
for (var i = 0; i < REPEAT; i++)
{
var offset = new Vector3(i, 0, 0);
var (nextVertices, nextTriangles, nextUV, nextNormals) = CreateTriangle(offset, i * 3);
vertices.AddRange(nextVertices);
triangles.AddRange(nextTriangles);
uv.AddRange(nextUV);
normals.AddRange(nextNormals);
UpdateMeshPlain(mesh, vertices.ToArray(), triangles.ToArray(), uv.ToArray(), normals.ToArray());
}
})
.MeasurementCount(MEASURE_COUNT)
.Run();
}
[Test, Performance]
public void RunUpdateList()
{
Measure.Method(() =>
{
var vertices = new List<Vector3>();
var triangles = new List<int>();
var uv = new List<Vector2>();
var normals = new List<Vector3>();
var mesh = new Mesh();
mesh.MarkDynamic();
for (var i = 0; i < REPEAT; i++)
{
var offset = new Vector3(i, 0, 0);
var (nextVertices, nextTriangles, nextUV, nextNormals) = CreateTriangle(offset, i * 3);
vertices.AddRange(nextVertices);
triangles.AddRange(nextTriangles);
uv.AddRange(nextUV);
normals.AddRange(nextNormals);
UpdateMeshList(mesh, vertices, triangles, uv, normals);
}
})
.MeasurementCount(MEASURE_COUNT)
.Run();
}
[Test, Performance]
public void RunUpdateListNoRecalculation()
{
Measure.Method(() =>
{
var vertices = new List<Vector3>();
var triangles = new List<int>();
var uv = new List<Vector2>();
var normals = new List<Vector3>();
var mesh = new Mesh();
mesh.MarkDynamic();
for (var i = 0; i < REPEAT; i++)
{
var offset = new Vector3(i, 0, 0);
var (nextVertices, nextTriangles, nextUV, nextNormals) = CreateTriangle(offset, i * 3);
vertices.AddRange(nextVertices);
triangles.AddRange(nextTriangles);
uv.AddRange(nextUV);
normals.AddRange(nextNormals);
UpdateMeshListNoRecalculation(mesh, vertices, triangles, uv, normals);
}
})
.MeasurementCount(MEASURE_COUNT)
.Run();
}
[Test, Performance]
public void RunUpdateListNoRecalculationNoClear()
{
Measure.Method(() =>
{
var vertices = new List<Vector3>();
var triangles = new List<int>();
var uv = new List<Vector2>();
var normals = new List<Vector3>();
var mesh = new Mesh();
mesh.MarkDynamic();
for (var i = 0; i < REPEAT; i++)
{
var offset = new Vector3(i, 0, 0);
var (nextVertices, nextTriangles, nextUV, nextNormals) = CreateTriangle(offset, i * 3);
vertices.AddRange(nextVertices);
triangles.AddRange(nextTriangles);
uv.AddRange(nextUV);
normals.AddRange(nextNormals);
UpdateMeshListNoRecalculationNoClear(mesh, vertices, triangles, uv, normals);
}
})
.MeasurementCount(MEASURE_COUNT)
.Run();
}
[Test, Performance]
public void RunUpdateListNoRecalculationWithAllocated()
{
Measure.Method(() =>
{
var vertices = new Vector3[REPEAT * 3];
var triangles = new int[REPEAT * 3];
var uv = new Vector2[REPEAT * 3];
var normals = new Vector3[REPEAT * 3];
var mesh = new Mesh();
mesh.MarkDynamic();
Array.Fill(vertices, Vector3.zero);
Array.Fill(triangles, 0);
Array.Fill(uv, Vector2.zero);
Array.Fill(normals, Vector3.up);
for (var i = 0; i < REPEAT; i++)
{
var offset = new Vector3(i, 0, 0);
CreateTriangleWithAllocated(
offset,
i * 3,
vertices, triangles, uv, normals,
i * 3,
i * 3,
i * 3,
i * 3
);
UpdateMeshListWithAllocated(mesh, vertices, triangles, uv, normals);
}
})
.MeasurementCount(MEASURE_COUNT)
.Run();
}
private Mesh CreateMeshPlain(Vector3[] vertices, int[] triangles, Vector2[] uv, Vector3[] normals)
{
var mesh = new Mesh();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.uv = uv;
mesh.normals = normals;
mesh.RecalculateNormals();
return mesh;
}
private void UpdateMeshPlain(Mesh mesh, Vector3[] vertices, int[] triangles, Vector2[] uv, Vector3[] normals)
{
mesh.Clear();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.uv = uv;
mesh.normals = normals;
mesh.RecalculateNormals();
}
private void UpdateMeshList(Mesh mesh, List<Vector3> vertices, List<int> triangles, List<Vector2> uv, List<Vector3> normals)
{
mesh.Clear();
mesh.SetVertices(vertices);
mesh.SetTriangles(triangles, 0);
mesh.SetUVs(0, uv);
mesh.SetNormals(normals);
mesh.RecalculateNormals();
}
private void UpdateMeshListNoRecalculation(Mesh mesh, List<Vector3> vertices, List<int> triangles, List<Vector2> uv, List<Vector3> normals)
{
mesh.Clear();
mesh.SetVertices(vertices);
mesh.SetTriangles(triangles, 0);
mesh.SetUVs(0, uv);
mesh.SetNormals(normals);
}
private void UpdateMeshListNoRecalculationNoClear(Mesh mesh, List<Vector3> vertices, List<int> triangles, List<Vector2> uv, List<Vector3> normals)
{
mesh.SetVertices(vertices);
mesh.SetTriangles(triangles, 0);
mesh.SetUVs(0, uv);
mesh.SetNormals(normals);
}
private void UpdateMeshListWithAllocated(Mesh mesh, Vector3[] vertices, int[] triangles, Vector2[] uv, Vector3[] normals)
{
mesh.SetVertices(vertices);
mesh.SetTriangles(triangles, 0);
mesh.SetUVs(0, uv);
mesh.SetNormals(normals);
}
private (Vector3[], int[], Vector2[], Vector3[]) CreateTriangle(Vector3 verticesOffset, int triangleOffset)
{
var vertices = new Vector3[3];
var triangles = new int[3];
var uv = new Vector2[3];
var normals = new Vector3[3];
vertices[0] = new Vector3(-1, 0, 0) + verticesOffset;
vertices[1] = new Vector3(0, 0, 1) + verticesOffset;
vertices[2] = new Vector3(1, 0, 0) + verticesOffset;
triangles[0] = 0 + triangleOffset;
triangles[1] = 1 + triangleOffset;
triangles[2] = 2 + triangleOffset;
uv[0] = new Vector2(0, 0);
uv[1] = new Vector2(0, 1);
uv[2] = new Vector2(1, 0);
normals[0] = Vector3.up;
normals[1] = Vector3.up;
normals[2] = Vector3.up;
return (vertices, triangles, uv, normals);
}
private void CreateTriangleWithAllocated(
Vector3 verticesOffset,
int triangleOffset,
IList<Vector3> vertices, IList<int> triangles, IList<Vector2> uv, IList<Vector3> normals,
int verticesIndex, int trianglesIndex, int uvIndex, int normalsIndex
)
{
vertices[verticesIndex + 0] = new Vector3(-1, 0, 0) + verticesOffset;
vertices[verticesIndex + 1] = new Vector3(0, 0, 1) + verticesOffset;
vertices[verticesIndex + 2] = new Vector3(1, 0, 0) + verticesOffset;
triangles[trianglesIndex + 0] = 0 + triangleOffset;
triangles[trianglesIndex + 1] = 1 + triangleOffset;
triangles[trianglesIndex + 2] = 2 + triangleOffset;
uv[uvIndex + 0] = new Vector2(0, 0);
uv[uvIndex + 1] = new Vector2(0, 1);
uv[uvIndex + 2] = new Vector2(1, 0);
normals[normalsIndex + 0] = Vector3.up;
normals[normalsIndex + 1] = Vector3.up;
normals[normalsIndex + 2] = Vector3.up;
}
}
}
Discussion