public static void Voxelize(Mesh mesh, int resolution, out List <Voxel_t> voxels, out float unit, bool surfaceOnly = false) { mesh.RecalculateBounds(); var bounds = mesh.bounds; // From the specified resolution, calculate the unit length of one voxel float maxLength = Mathf.Max(bounds.size.x, Mathf.Max(bounds.size.y, bounds.size.z)); unit = maxLength / resolution; // half of the unit length var hunit = unit * 0.5f; // The bounds extended by "half of the unit length constituting one voxel" is defined as the scope of voxelization var start = bounds.min - new Vector3(hunit, hunit, hunit); // Minimum bounds to voxel var end = bounds.max + new Vector3(hunit, hunit, hunit); // Maximum bounds to voxel var size = end - start; // Size of bounds to voxel // The size of three-dimensional voxel data is determined based on the unit length of the voxel and the scope of voxelization var width = Mathf.CeilToInt(size.x / unit); var height = Mathf.CeilToInt(size.y / unit); var depth = Mathf.CeilToInt(size.z / unit); var volume = new Voxel_t[width, height, depth]; // In the subsequent processing, // in order to refer to the position and size of each voxel data, generate an AABB array. var boxes = new Bounds[width, height, depth]; var voxelUnitSize = Vector3.one * unit; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { for (int z = 0; z < depth; z++) { var p = new Vector3(x, y, z) * unit + start; var aabb = new Bounds(p, voxelUnitSize); boxes[x, y, z] = aabb; } } } var vertices = mesh.vertices; var indices = mesh.triangles; // direction to fill the voxels var direction = Vector3.forward; for (int i = 0, n = indices.Length; i < n; i += 3) { // a target triangle var tri = new Triangle( vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]], direction ); // calculate a AABB of a triangle var min = tri.bounds.min - start; var max = tri.bounds.max - start; int iminX = Mathf.RoundToInt(min.x / unit), iminY = Mathf.RoundToInt(min.y / unit), iminZ = Mathf.RoundToInt(min.z / unit); int imaxX = Mathf.RoundToInt(max.x / unit), imaxY = Mathf.RoundToInt(max.y / unit), imaxZ = Mathf.RoundToInt(max.z / unit); iminX = Mathf.Clamp(iminX, 0, width - 1); iminY = Mathf.Clamp(iminY, 0, height - 1); iminZ = Mathf.Clamp(iminZ, 0, depth - 1); imaxX = Mathf.Clamp(imaxX, 0, width - 1); imaxY = Mathf.Clamp(imaxY, 0, height - 1); imaxZ = Mathf.Clamp(imaxZ, 0, depth - 1); uint front = (uint)(tri.frontFacing ? 1 : 0); // inside AABB of a triangle, // check intersections a triangle and voxels for (int x = iminX; x <= imaxX; x++) { for (int y = iminY; y <= imaxY; y++) { for (int z = iminZ; z <= imaxZ; z++) { if (Intersects(tri, boxes[x, y, z])) { var voxel = volume[x, y, z]; voxel.position = boxes[x, y, z].center; if ((voxel.front & 1) == 0) { voxel.front = front; } else { voxel.front = voxel.front & front; } voxel.fill = 1; volume[x, y, z] = voxel; } } } } } if (!surfaceOnly) { // fill inside of a mesh for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { // fill inside of a mesh from z-nearest side (x, y, 0) for (int z = 0; z < depth; z++) { // continue if (x, y, z) is empty if (volume[x, y, z].IsEmpty()) { continue; } // step forward to front face int ifront = z; for (; ifront < depth && volume[x, y, ifront].IsFrontFace(); ifront++) { } // break if position is out of bounds if (ifront >= depth) { break; } int iback = ifront; // step forward to empty for (; iback < depth && volume[x, y, iback].IsEmpty(); iback++) { } if (iback >= depth) { break; } // check if iback is back voxel if (volume[x, y, iback].IsBackFace()) { // step forward to back face for (; iback < depth && volume[x, y, iback].IsBackFace(); iback++) { } } // fill from ifront to iback for (int z2 = ifront; z2 < iback; z2++) { var p = boxes[x, y, z2].center; var voxel = volume[x, y, z2]; voxel.position = p; voxel.fill = 1; volume[x, y, z2] = voxel; } // advance loop to (x, y, iback) z = iback; } } } } // get non-empty voxels voxels = new List <Voxel_t>(); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { for (int z = 0; z < depth; z++) { if (!volume[x, y, z].IsEmpty()) { voxels.Add(volume[x, y, z]); } } } } }
public static GPUVoxelData Voxelize(ComputeShader voxelizer, Bounds bounds, Mesh mesh, int resolution = 64) { // From the specified resolution, calculate the unit length of one voxel float maxLength = Mathf.Max(bounds.size.x, Mathf.Max(bounds.size.y, bounds.size.z)); var unit = maxLength / resolution; // half of the unit length var hunit = unit * 0.5f; // The bounds extended by "half of the unit length constituting one voxel" is defined as the scope of voxelization var start = bounds.min - new Vector3(hunit, hunit, hunit); // Minimum bounds to voxel var end = bounds.max + new Vector3(hunit, hunit, hunit); // Maximum bounds to voxel var size = end - start; // Size of bounds to voxel // The size of three-dimensional voxel data is determined based on the unit length of the voxel and the scope of voxelization int width = Mathf.CeilToInt(size.x / unit); int height = Mathf.CeilToInt(size.y / unit); int depth = Mathf.CeilToInt(size.z / unit); // generate ComputeBuffer representing Voxel_t array var voxelBuffer = new ComputeBuffer(width * height * depth, Marshal.SizeOf(typeof(Voxel_t))); var voxels = new Voxel_t[voxelBuffer.count]; voxelBuffer.SetData(voxels); // initialize // send voxel data to GPU voxelizer.SetVector("_Start", start); voxelizer.SetVector("_End", end); voxelizer.SetVector("_Size", size); voxelizer.SetFloat("_Unit", unit); voxelizer.SetFloat("_InvUnit", 1f / unit); voxelizer.SetFloat("_HalfUnit", hunit); voxelizer.SetInt("_Width", width); voxelizer.SetInt("_Height", height); voxelizer.SetInt("_Depth", depth); // generate ComputeBuffer representing vertex array var vertices = mesh.vertices; var vertBuffer = new ComputeBuffer(vertices.Length, Marshal.SizeOf(typeof(Vector3))); vertBuffer.SetData(vertices); // generate ComputeBuffer representing triangle array var triangles = mesh.triangles; var triBuffer = new ComputeBuffer(triangles.Length, Marshal.SizeOf(typeof(int))); triBuffer.SetData(triangles); // send mesh data to GPU kernel "SurfaceFront" and "SurfaceBack" var surfaceFrontKer = new Kernel(voxelizer, "SurfaceFront"); voxelizer.SetBuffer(surfaceFrontKer.Index, "_VoxelBuffer", voxelBuffer); voxelizer.SetBuffer(surfaceFrontKer.Index, "_VertBuffer", vertBuffer); voxelizer.SetBuffer(surfaceFrontKer.Index, "_TriBuffer", triBuffer); // set triangle count in a mesh var triangleCount = triBuffer.count / 3; voxelizer.SetInt("_TriangleCount", triangleCount); // execute surface construction in front triangles voxelizer.Dispatch(surfaceFrontKer.Index, triangleCount / (int)surfaceFrontKer.ThreadX + 1, (int)surfaceFrontKer.ThreadY, (int)surfaceFrontKer.ThreadZ); // execute surface construction in back triangles var surfaceBackKer = new Kernel(voxelizer, "SurfaceBack"); voxelizer.SetBuffer(surfaceBackKer.Index, "_VoxelBuffer", voxelBuffer); voxelizer.SetBuffer(surfaceBackKer.Index, "_VertBuffer", vertBuffer); voxelizer.SetBuffer(surfaceBackKer.Index, "_TriBuffer", triBuffer); voxelizer.Dispatch(surfaceBackKer.Index, triangleCount / (int)surfaceBackKer.ThreadX + 1, (int)surfaceBackKer.ThreadY, (int)surfaceBackKer.ThreadZ); // send voxel data to GPU kernel "Volume" var volumeKer = new Kernel(voxelizer, "Volume"); voxelizer.SetBuffer(volumeKer.Index, "_VoxelBuffer", voxelBuffer); // execute to fill voxels inside of a mesh voxelizer.Dispatch(volumeKer.Index, width / (int)volumeKer.ThreadX + 1, height / (int)volumeKer.ThreadY + 1, (int)surfaceFrontKer.ThreadZ); // dispose unnecessary mesh data vertBuffer.Release(); triBuffer.Release(); return(new GPUVoxelData(voxelBuffer, width, height, depth, unit)); }