本日はUnity枠です。
〇環境
・Windows11PC
・Unity2022.3.26f1
〇メッシュのボクセル化
今回はリメッシュの一環としてメッシュのボクセル化を試していきます。

ボクセル化とは対照のメッシュ形状を小さな立方体で構成される形状に変換することを指します。
ボクセルという名前自体は、3次元のピクセルに相当する概念です。
メッシュの解析やリメッシュなどを行うにあたって形状把握を行う上で大切な概念です。
また複雑なメッシュを簡略化することもできます。
〇ボクセルの作成
ボクセルは元のメッシュを空間でグリッド化してオブジェクトを配置するというある種モザイクと同じ原理で実装できます。
ボクセルを作成するためにはまずは元となるメッシュの領域を取得します。
これはmesh.boundsを使用してバウンディングボックスを求めます。
void VoxelizeMesh()
{
//メッシュを取得
Mesh mesh = meshFilter.mesh;
//メッシュに含まれる頂点座標を取得
Vector3[] vertices = mesh.vertices;
// メッシュのバウンディングボックスを取得
meshMinBounds = meshFilter.mesh.bounds.min;
meshMaxBounds = meshFilter.mesh.bounds.max;
Vector3 meshSize = meshMaxBounds - meshMinBounds;
・・・
meshMinBounds = meshFilter.mesh.bounds.min; meshMaxBounds = meshFilter.mesh.bounds.max;
はバウンディングボックスの最大領域、最小領域を取得、差分を取ることでバウンディングボックスの大きさを求めています。
ここからはグリッドを作成するためのボクセルサイズを決定し、グリッドを作成します。
// 各ボクセルのサイズを計算
voxelSize = meshSize / voxelResolution;
// 3Dグリッドを作成
Dictionary<Vector3Int, List<Vector3>> voxelGrid = new Dictionary<Vector3Int, List<Vector3>>();
グリッドができたら割り当てを行います。
// 各頂点のボクセル座標を計算してグリッドに割り当てる
foreach (Vector3 vertex in vertices)
{
Vector3 worldVertexPos = meshFilter.transform.TransformPoint(vertex);
Vector3 localPos = worldVertexPos - meshMinBounds;
// ボクセル座標を整数で計算
Vector3Int voxelCoord = new Vector3Int(
Mathf.FloorToInt(localPos.x / voxelSize.x),
Mathf.FloorToInt(localPos.y / voxelSize.y),
Mathf.FloorToInt(localPos.z / voxelSize.z)
);
// ボクセルに頂点を割り当て
if (!voxelGrid.ContainsKey(voxelCoord))
{
voxelGrid[voxelCoord] = new List<Vector3>();
}
voxelGrid[voxelCoord].Add(worldVertexPos);
}
これを行うことによってメッシュからボクセルを作成することができます。

〇コード全文
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MeshVoxelizer : MonoBehaviour
{
public MeshFilter meshFilter;
public int voxelResolution = 10; // ボクセルの分割解像度
public GameObject voxelCubePrefab; // キューブのプレハブ(Inspectorで設定)
private Vector3 meshMinBounds;
private Vector3 meshMaxBounds;
private Vector3 voxelSize;
void Start()
{
VoxelizeMesh();
}
// メッシュをボクセル化してボクセルごとの空間座標を取得
void VoxelizeMesh()
{
Mesh mesh = meshFilter.mesh;
Vector3[] vertices = mesh.vertices;
// メッシュのバウンディングボックスを取得
meshMinBounds = meshFilter.mesh.bounds.min;
meshMaxBounds = meshFilter.mesh.bounds.max;
Vector3 meshSize = meshMaxBounds - meshMinBounds;
// 各ボクセルのサイズを計算
voxelSize = meshSize / voxelResolution;
// 3Dグリッドを作成
Dictionary<Vector3Int, List<Vector3>> voxelGrid = new Dictionary<Vector3Int, List<Vector3>>();
// 各頂点のボクセル座標を計算してグリッドに割り当てる
foreach (Vector3 vertex in vertices)
{
Vector3 worldVertexPos = meshFilter.transform.TransformPoint(vertex);
Vector3 localPos = worldVertexPos - meshMinBounds;
// ボクセル座標を整数で計算
Vector3Int voxelCoord = new Vector3Int(
Mathf.FloorToInt(localPos.x / voxelSize.x),
Mathf.FloorToInt(localPos.y / voxelSize.y),
Mathf.FloorToInt(localPos.z / voxelSize.z)
);
// ボクセルに頂点を割り当て
if (!voxelGrid.ContainsKey(voxelCoord))
{
voxelGrid[voxelCoord] = new List<Vector3>();
}
voxelGrid[voxelCoord].Add(worldVertexPos);
}
// ボクセルごとの座標にCubeを配置(デバッグ用)
foreach (var voxel in voxelGrid)
{
Vector3 voxelCenter = meshMinBounds + new Vector3(
voxel.Key.x * voxelSize.x + voxelSize.x / 2,
voxel.Key.y * voxelSize.y + voxelSize.y / 2,
voxel.Key.z * voxelSize.z + voxelSize.z / 2
);
// キューブを生成してボクセルの中心に配置
GameObject voxelCube;
if (voxelCubePrefab != null)
{
// プレハブが指定されていればそれを使用
voxelCube = Instantiate(voxelCubePrefab, voxelCenter, Quaternion.identity);
}
else
{
// プレハブが指定されていなければデフォルトのCubeを生成
voxelCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
voxelCube.transform.position = voxelCenter;
}
// キューブのサイズをボクセルサイズに合わせる
voxelCube.transform.localScale = voxelSize;
// デバッグ情報を表示
Debug.Log($"Voxel Coord: {voxel.Key}, Voxel Center: {voxelCenter}, Vertex Count: {voxel.Value.Count}");
}
}
}