環境
Unity2022.2.14f1
概要
NaiveSurfaceNetsを使用して、SDFの3DTextureのポリゴン化を行ってみました。
参考のqiitaのコードをComputeShaderで行ってみた感じです。
マイフレーム実行しても、実用的な速度で処理されているようです。
SDFは参考のGithubにある「SignedDistance.vf」を使用させていただきました。
.vfファイルはVisualEffectGraphにインポータが付属しています。
VectorFieldImporter.csの中のtexture.Apply()の2番目の引数をfalseにしてCPUで読み取れるようにしています。
※ComputeShaderで直接3DTextureを参照するなら必要ない。
また設定でOutput Formatをfloatにしています。
コード
using UnityEngine; public unsafe class TestGpuNaiveSurfaceNets : MonoBehaviour { [SerializeField] private Texture3D _sdfTexture = null; [SerializeField] private ComputeShader _computeShader = null; [SerializeField] private Material _material = null; private GraphicsBuffer _sdfVoxelBuffer = null; private GraphicsBuffer _vertexIdBuffer = null; private GraphicsBuffer _vertexBuffer = null; private GraphicsBuffer _indexBuffer = null; private GraphicsBuffer _neighborBuffer = null; private GraphicsBuffer _edgeBuffer = null; private GraphicsBuffer _indirectArgBuffer = null; private Bounds _bounds; private int _klVertices = -1; private int _klIndices = -1; private int _klIndirectArgs = -1; private Vector3Int _tgVertices; private Vector3Int _tgIndices; private static int _spSdfVoxelSize = Shader.PropertyToID("SdfVoxelSize"); private static int _spSdfVoxels = Shader.PropertyToID("SdfVoxels"); private static int _spVertexIds = Shader.PropertyToID("VertexIds"); private static int _spVertices = Shader.PropertyToID("Vertices"); private static int _spIndices = Shader.PropertyToID("Indices"); private static int _spNeighbors = Shader.PropertyToID("Neighbors"); private static int _spEdges = Shader.PropertyToID("Edges"); private static int _spIndirectArgs = Shader.PropertyToID("IndirectArgs"); private void Start() { if(_sdfTexture.width != _sdfTexture.height || _sdfTexture.width != _sdfTexture.depth) { Debug.LogError("sdfTexture Size"); return; } var size = _sdfTexture.width; var texels = _sdfTexture.GetPixelData<float>(0); _sdfVoxelBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, size * size * size, sizeof(float)); _sdfVoxelBuffer.SetData(texels); _vertexIdBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, size * size * size, sizeof(int)); _vertexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.Counter, size * size * size, sizeof(Vector3)); _vertexBuffer.SetCounterValue(0); _indexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured /*| GraphicsBuffer.Target.Index*/ | GraphicsBuffer.Target.Counter, size * size * size * 18, sizeof(int)); _indexBuffer.SetCounterValue(0); _indirectArgBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 4, sizeof(uint)); _indirectArgBuffer.SetData(new uint[4]{0, 1, 0, 0}); // 立方体上の頂点の番号の決め方 var Neighbors = new Vector3Int[] { new Vector3Int( 0, 0, 0 ), new Vector3Int( 1, 0, 0 ), new Vector3Int( 1, 0, 1 ), new Vector3Int( 0, 0, 1 ), new Vector3Int( 0, 1, 0 ), new Vector3Int( 1, 1, 0 ), new Vector3Int( 1, 1, 1 ), new Vector3Int( 0, 1, 1 ), }; _neighborBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, Neighbors.Length, sizeof(Vector3Int)); _neighborBuffer.SetData(Neighbors); // 辺のつながり方 var Edges = new Vector2Int[] { new Vector2Int( 0, 1 ), new Vector2Int( 1, 2 ), new Vector2Int( 2, 3 ), new Vector2Int( 3, 0 ), new Vector2Int( 4, 5 ), new Vector2Int( 5, 6 ), new Vector2Int( 6, 7 ), new Vector2Int( 7, 4 ), new Vector2Int( 0, 4 ), new Vector2Int( 1, 5 ), new Vector2Int( 2, 6 ), new Vector2Int( 3, 7 ), }; _edgeBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, Edges.Length, sizeof(Vector2Int)); _edgeBuffer.SetData(Edges); float bsize = size;// * 10.0f; _bounds = new Bounds(new Vector3(bsize * 0.5f, bsize * 0.5f, bsize * 0.5f), new Vector3(bsize, bsize, bsize)); _material.SetBuffer(_spVertices, _vertexBuffer); _material.SetBuffer(_spIndices, _indexBuffer); uint numThreadX, numThreadY, numThreadZ; _computeShader.SetInt(_spSdfVoxelSize, size); // Vertices _klVertices = _computeShader.FindKernel("GenerateVertices"); _computeShader.SetBuffer(_klVertices, _spSdfVoxels, _sdfVoxelBuffer); _computeShader.SetBuffer(_klVertices, _spVertexIds, _vertexIdBuffer); _computeShader.SetBuffer(_klVertices, _spVertices, _vertexBuffer); _computeShader.SetBuffer(_klVertices, _spEdges, _edgeBuffer); _computeShader.SetBuffer(_klVertices, _spNeighbors, _neighborBuffer); _computeShader.GetKernelThreadGroupSizes(_klVertices, out numThreadX, out numThreadY, out numThreadZ); _tgVertices.x = ((size - 1) + (int)(numThreadX - 1)) / (int)numThreadX; _tgVertices.y = ((size - 1) + (int)(numThreadY - 1)) / (int)numThreadY; _tgVertices.z = ((size - 1) + (int)(numThreadZ - 1)) / (int)numThreadZ; // Indices _klIndices = _computeShader.FindKernel("GenerateIndices"); _computeShader.SetBuffer(_klIndices, _spSdfVoxels, _sdfVoxelBuffer); _computeShader.SetBuffer(_klIndices, _spVertexIds, _vertexIdBuffer); _computeShader.SetBuffer(_klIndices, _spVertices, _vertexBuffer); _computeShader.SetBuffer(_klIndices, _spIndices, _indexBuffer); _computeShader.SetBuffer(_klIndices, _spNeighbors, _neighborBuffer); _computeShader.GetKernelThreadGroupSizes(_klIndices, out numThreadX, out numThreadY, out numThreadZ); _tgIndices.x = ((size - 2) + (int)(numThreadX - 1)) / (int)numThreadX; _tgIndices.y = ((size - 2) + (int)(numThreadY - 1)) / (int)numThreadY; _tgIndices.z = ((size - 2) + (int)(numThreadZ - 1)) / (int)numThreadZ; // IndirectArgs _klIndirectArgs = _computeShader.FindKernel("UpdateIndirectArgs"); _computeShader.SetBuffer(_klIndirectArgs, _spIndices, _indexBuffer); _computeShader.SetBuffer(_klIndirectArgs, _spIndirectArgs, _indirectArgBuffer); } private void OnDestroy() { if(_sdfVoxelBuffer != null) _sdfVoxelBuffer.Dispose(); if(_vertexIdBuffer != null) _vertexIdBuffer.Dispose(); if(_vertexBuffer != null) _vertexBuffer.Dispose(); if(_indexBuffer != null) _indexBuffer.Dispose(); if(_neighborBuffer != null) _neighborBuffer.Dispose(); if(_edgeBuffer != null) _edgeBuffer.Dispose(); if(_indirectArgBuffer != null) _indirectArgBuffer.Dispose(); } private void LateUpdate() { // Dispatch _vertexBuffer.SetCounterValue(0); _indexBuffer.SetCounterValue(0); _computeShader.Dispatch(_klVertices, _tgVertices.x, _tgVertices.y, _tgVertices.z); _computeShader.Dispatch(_klIndices, _tgIndices.x, _tgIndices.y, _tgIndices.z); _computeShader.Dispatch(_klIndirectArgs, 1,1,1); GL.Flush(); if(_indexBuffer != null) Graphics.DrawProceduralIndirect(_material, _bounds, MeshTopology.Triangles, _indirectArgBuffer); } void OnDrawGizmos() { Gizmos.color = Color.blue; Gizmos.DrawWireCube(_bounds.center, _bounds.size); } }
uint SdfVoxelSize; StructuredBuffer<float> SdfVoxels; StructuredBuffer<uint2> Edges; // 辺のつながり方 StructuredBuffer<uint3> Neighbors; // 立方体上の頂点の番号の決め方 RWStructuredBuffer<uint> VertexIds; RWStructuredBuffer<float3> Vertices; RWStructuredBuffer<uint> Indices; RWStructuredBuffer<uint> IndirectArgs; // v0, v1, v2, v3から構築される面を追加する void MakeFace(uint v0, uint v1, uint v2, uint v3, bool outside) { uint indexOffset = Indices.IncrementCounter() * 6; if (outside) { Indices[indexOffset ] = v0; Indices[indexOffset + 1] = v3; Indices[indexOffset + 2] = v2; Indices[indexOffset + 3] = v2; Indices[indexOffset + 4] = v1; Indices[indexOffset + 5] = v0; } else { Indices[indexOffset ] = v0; Indices[indexOffset + 1] = v1; Indices[indexOffset + 2] = v2; Indices[indexOffset + 3] = v2; Indices[indexOffset + 4] = v3; Indices[indexOffset + 5] = v0; } } // 整数座標から配列に入るときの順序を取得 // +X+Y+Z方向に広がる立方体上のi番目の頂点として順序を取得 uint ToIdx(uint x, uint y, uint z, uint i, uint size) { x += Neighbors[i].x; y += Neighbors[i].y; z += Neighbors[i].z; return x + y * size + z * size * size; } // 整数座標から配列に入るときの順序を取得 // -X-Y-Z方向に広がる立方体上のi番目の頂点として順序を取得 uint ToIdxNeg(uint x, uint y, uint z, uint i, uint size) { x -= Neighbors[i].x; y -= Neighbors[i].y; z -= Neighbors[i].z; return x + y * size + z * size * size; } // 整数座標から実数座標を取得 // +X+Y+Z方向に広がる立方体上のi番目の頂点として実数座標を取得 float3 ToVec(uint i, uint j, uint k, uint neighbor) { i += Neighbors[neighbor].x; j += Neighbors[neighbor].y; k += Neighbors[neighbor].z; return float3(i, j, k); } uint GetKind(uint x, uint y, uint z) { // ビットマスクで8つの点の状態を記憶 // iの位置の点が内側ならばi + 1番目のビットを立てる // 頂点の位置と番号の対応は次のように決める // 7----6 // /| /| // 4----5 | // | 3--|-2 // |/ |/ // (x,y,z)0----1 uint kind = 0; if (0 > SdfVoxels[ToIdx(x, y, z, 0, SdfVoxelSize)]) kind |= 1 << 0; if (0 > SdfVoxels[ToIdx(x, y, z, 1, SdfVoxelSize)]) kind |= 1 << 1; if (0 > SdfVoxels[ToIdx(x, y, z, 2, SdfVoxelSize)]) kind |= 1 << 2; if (0 > SdfVoxels[ToIdx(x, y, z, 3, SdfVoxelSize)]) kind |= 1 << 3; if (0 > SdfVoxels[ToIdx(x, y, z, 4, SdfVoxelSize)]) kind |= 1 << 4; if (0 > SdfVoxels[ToIdx(x, y, z, 5, SdfVoxelSize)]) kind |= 1 << 5; if (0 > SdfVoxels[ToIdx(x, y, z, 6, SdfVoxelSize)]) kind |= 1 << 6; if (0 > SdfVoxels[ToIdx(x, y, z, 7, SdfVoxelSize)]) kind |= 1 << 7; return kind; } void GenerateVertex(uint x, uint y, uint z) { uint kind = GetKind(x, y, z); // 8つの点がすべて内側またはすべて外側の場合はスキップ if (kind == 0 || kind == 255) return; // 頂点の位置を算出 float3 vertex; uint crossCount = 0; // 現在焦点を当てている立方体上の辺をすべて列挙 for (uint i = 0; i < 12; i++) { uint2 p = Edges[i]; uint p0 = p.x; uint p1 = p.y; // 異なる側同士の点でつながってない場合はスキップ // ビットマスクからp0 + 1とp1 + 1ビット目(p0とp1の位置の点の状態)を取り出す if ((kind >> p0 & 1) == (kind >> p1 & 1)) continue; // 両端の点のボクセルデータ上の値を取り出す float val0 = SdfVoxels[ToIdx(x, y, z, p0, SdfVoxelSize)]; float val1 = SdfVoxels[ToIdx(x, y, z, p1, SdfVoxelSize)]; // 線形補間によって値が0となる辺上の位置を算出して加算 vertex += lerp(ToVec(x, y, z, p0), ToVec(x, y, z, p1), (0 - val0) / (val1 - val0)); crossCount++; } vertex /= crossCount; uint vertexOffset = Vertices.IncrementCounter(); Vertices[vertexOffset] = vertex; VertexIds[ToIdx(x, y, z, 0, SdfVoxelSize)] = vertexOffset; } void GenerateIndex(uint x, uint y, uint z) { // 面の追加は0 < x, y, z < size - 1で行う //if (x == 0 || y == 0 || z == 0) // return; uint kind = GetKind(x, y, z); // 8つの点がすべて内側またはすべて外側の場合はスキップ if (kind == 0 || kind == 255) return; // ビットマスクから1ビット目(0の位置の点の状態)を取り出す bool outside = (kind & 1) != 0; // 面を構築する頂点を取り出す // 頂点の位置と番号の対応は次のように決める // 1----0(x, y, z) // /| /| // 2----3 | // | 5--|-4 // |/ |/ // 6----7 uint v0 = VertexIds[ToIdxNeg(x, y, z, 0, SdfVoxelSize)]; uint v1 = VertexIds[ToIdxNeg(x, y, z, 1, SdfVoxelSize)]; uint v2 = VertexIds[ToIdxNeg(x, y, z, 2, SdfVoxelSize)]; uint v3 = VertexIds[ToIdxNeg(x, y, z, 3, SdfVoxelSize)]; uint v4 = VertexIds[ToIdxNeg(x, y, z, 4, SdfVoxelSize)]; uint v5 = VertexIds[ToIdxNeg(x, y, z, 5, SdfVoxelSize)]; // var v6 = VertexIds[ToIdxNeg(x, y, z, 6, SdfVoxelSize)]; // 使われない uint v7 = VertexIds[ToIdxNeg(x, y, z, 7, SdfVoxelSize)]; // ビットマスクから2ビット目(1の位置の点の状態)を取り出す。異なる側同士の点からなる辺ならば交わるような面を追加 bool isBit; isBit = (kind >> 1 & 1) != 0; if ( isBit != outside) MakeFace(v0, v3, v7, v4, outside); // ビットマスクから4ビット目(3の位置の点の状態)を取り出す isBit = (kind >> 3 & 1) != 0; if (isBit != outside) MakeFace(v0, v4, v5, v1, outside); // ビットマスクから5ビット目(4の位置の点の状態)を取り出す isBit = (kind >> 4 & 1) != 0; if (isBit != outside) MakeFace(v0, v1, v2, v3, outside); } #pragma kernel GenerateVertices [numthreads(32, 32, 1)] void GenerateVertices(uint3 id : SV_DispatchThreadID) { if(id.x >= SdfVoxelSize - 1 || id.y >= SdfVoxelSize - 1 || id.z >= SdfVoxelSize - 1) return; uint x = id.x; uint y = id.y; uint z = id.z; GenerateVertex(x, y, z); } #pragma kernel GenerateIndices [numthreads(32, 32, 1)] void GenerateIndices(uint3 id : SV_DispatchThreadID) { if(id.x >= SdfVoxelSize - 2 || id.y >= SdfVoxelSize - 2 || id.z >= SdfVoxelSize - 2) return; uint x = id.x + 1; uint y = id.y + 1; uint z = id.z + 1; GenerateIndex(x, y, z); } #pragma kernel UpdateIndirectArgs [numthreads(1, 1, 1)] void UpdateIndirectArgs(uint3 id : SV_DispatchThreadID) { IndirectArgs[0] = Indices.IncrementCounter() * 6; }
Shader "Unlit/GpuNaiveSurfaceNets" { Properties { } CGINCLUDE StructuredBuffer<float3> Vertices; StructuredBuffer<uint> Indices; ENDCG SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { uint vertexId : SV_VertexID; }; struct v2f { float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(float4(Vertices[Indices[v.vertexId]], 1.0)); return o; } fixed4 frag (v2f i) : SV_Target { return fixed4(1,0,0,1); } ENDCG } } }
参考
Unityでボクセルデータをもとにメッシュを生成する方法(Naive Surface Nets) - Qiita
GitHub - foreverliu/VectorFieldExamples: Unity VFX Graph examples with vector fields