【Unity】コンピュートシェーダーでメッシュを細分化する方法を紹介します!パート2!
前回の記事で紹介した別の細分化方法を紹介します。
細分化方法の検討
今回は各線分の中間に頂点を作り4つの三角形へ分割する方法にします。
[0,1,2]の三角形に対して[0,a,c] [1,b,a] [2,c,b] [a,b,c] の4つの三角形に分割します。
頂点数は1つの三角形につき3頂点増え、インデックスは4つの三角形に細分化するので4倍になります。
コンピュートシェーダーの実装
前回のコンピュートシェーダーと同じように作ります。
SubdivisionShader2.compute
#pragma kernel CSVertex
#pragma kernel CSCoord
#pragma kernel CSIndex
StructuredBuffer<int> Indices;
StructuredBuffer<float3> Vertices;
RWStructuredBuffer<float3> OutVertices;
[numthreads(8,1,1)]
void CSVertex(uint3 id : SV_DispatchThreadID)
{
int index = 3 * (id.x / 3);
float3 a = 0.5 * (Vertices[Indices[index + 0]] + Vertices[Indices[index + 1]]);
float3 b = 0.5 * (Vertices[Indices[index + 1]] + Vertices[Indices[index + 2]]);
float3 c = 0.5 * (Vertices[Indices[index + 2]] + Vertices[Indices[index + 0]]);
float3 table[3] = { a,b,c };
OutVertices[id.x] = table[id.x % 3];
}
StructuredBuffer<float2> Coords;
RWStructuredBuffer<float2> OutCoords;
[numthreads(8,1,1)]
void CSCoord(uint3 id : SV_DispatchThreadID)
{
int index = 3 * (id.x / 3);
float2 a = 0.5 * (Coords[Indices[index + 0]] + Coords[Indices[index + 1]]);
float2 b = 0.5 * (Coords[Indices[index + 1]] + Coords[Indices[index + 2]]);
float2 c = 0.5 * (Coords[Indices[index + 2]] + Coords[Indices[index + 0]]);
float2 table[3] = { a,b,c };
OutCoords[id.x] = table[id.x % 3];
}
RWStructuredBuffer<int> OutIndices;
int VertexOffset;
[numthreads(8,1,1)]
void CSIndex(uint3 id : SV_DispatchThreadID)
{
uint index = 3 * (id.x / 12);
int o = Indices[index + 0];
int l = Indices[index + 1];
int z = Indices[index + 2];
int a = index + 0 + VertexOffset;
int b = index + 1 + VertexOffset;
int c = index + 2 + VertexOffset;
// (0,a,c) (1,b,a) (2,c,b) (a,b,c)
int table[12] = { o,a,c,l,b,a,z,c,b,a,b,c };
OutIndices[id.x] = table[id.x % 12];
}
スクリプトの実装
前回とスクリプトと大きな相違点はありませんがコンピュートシェーダーを切り替えられるように引数を増やしました。
vertexIncrementは1つの三角形あたりに増える頂点数です。
indexScaleは三角形が何個に細分化されるかの倍数です。
今回の細分化方法ではvertexIncrementは3, indexScaleは4になります。
SubdivisionMesh.cs
using System.Linq;
using UnityEngine;
public static class SubdivisionMesh
{
public static void Subdivide(this Mesh mesh, ComputeShader shader, int vertexIncrement, int indexScale)
{
int vertexOffset = mesh.vertices.Length;
GraphicsBuffer vertexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, mesh.vertices.Length, 12);
vertexBuffer.SetData(mesh.vertices);
GraphicsBuffer coordBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, mesh.uv.Length, 8);
coordBuffer.SetData(mesh.uv);
for (int i=0; i<mesh.subMeshCount; i++)
{
if(mesh.GetTopology(i) == MeshTopology.Triangles)
{
uint indexCount = mesh.GetIndexCount(i);
uint newIndexCount = (uint)(indexCount * indexScale);
uint count = 8 * ((indexCount + 7) / 8);
GraphicsBuffer indexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)count, sizeof(int));
indexBuffer.SetData(mesh.GetIndices(i));
int indexGroupCount = (int)(newIndexCount + 7) / 8;
GraphicsBuffer outIndexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 8 * indexGroupCount, sizeof(int));
int[] newIndices = new int[newIndexCount];
count = (uint)(vertexIncrement * (indexCount / 3));
Vector3[] newVertices = new Vector3[count];
int vertexGroupCount = (int)(count + 7) / 8;
GraphicsBuffer outVertexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 8* vertexGroupCount, sizeof(float) * 3);
int kernel = shader.FindKernel("CSVertex");
shader.SetBuffer(kernel, "Indices", indexBuffer);
shader.SetBuffer(kernel, "Vertices", vertexBuffer);
shader.SetBuffer(kernel, "OutVertices", outVertexBuffer);
shader.Dispatch(kernel, vertexGroupCount, 1, 1);
outVertexBuffer.GetData(newVertices, 0, 0, newVertices.Length);
var vertices = mesh.vertices.Concat(newVertices).ToArray();
mesh.SetVertices(vertices);
int coordGroupCount = vertexGroupCount;
GraphicsBuffer outCoordBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 8 * coordGroupCount, sizeof(float) * 2);
kernel = shader.FindKernel("CSCoord");
shader.SetBuffer(kernel, "Indices", indexBuffer);
shader.SetBuffer(kernel, "Coords", coordBuffer);
shader.SetBuffer(kernel, "OutCoords", outCoordBuffer);
shader.Dispatch(kernel, coordGroupCount, 1, 1);
Vector2[] outCoords = new Vector2[newVertices.Length];
outCoordBuffer.GetData(outCoords, 0, 0, outCoords.Length);
var uv = mesh.uv;
System.Array.Copy(outCoords, 0, uv, vertexOffset, outCoords.Length);
mesh.SetUVs(0, uv);
kernel = shader.FindKernel("CSIndex");
shader.SetInt("VertexOffset", vertexOffset);
shader.SetBuffer(kernel, "Indices", indexBuffer);
shader.SetBuffer(kernel, "OutIndices", outIndexBuffer);
shader.Dispatch(kernel, indexGroupCount, 1, 1);
outIndexBuffer.GetData(newIndices, 0, 0, newIndices.Length);
mesh.SetIndices(newIndices, 0, (int)newIndexCount, MeshTopology.Triangles, i);
vertexOffset += (int)(vertexIncrement * (indexCount / 3));
outVertexBuffer.Release();
outCoordBuffer.Release();
outIndexBuffer.Release();
indexBuffer.Release();
}
}
vertexBuffer.Release();
coordBuffer.Release();
mesh.RecalculateNormals();
mesh.RecalculateTangents();
}
}
使い方
今回の細分化方法は1つの三角形あたり3頂点増えて4つの三角形になります。
Mesh mesh = ...;
ComputeShader shader = 今回のコンピュートシェーダー;
mesh.Subdivide(shader, 3, 4);
前回の細分化方法だと1つの三角形あたり1頂点増えて3つの三角形になります。
Mesh mesh = ...;
ComputeShader shader = 前回のコンピュートシェーダー;
mesh.Subdivide(shader, 1, 3);
他にも細分化方法があれば同様に実装できます。
課題
細分化シェーダーを作成している途中で見えた課題について最後にメモしておきます。
今回計算した頂点は球面上にぴったり配置されていません。
今回のメッシュはスフィアなので頂点を正規化して法線を求め、半径をかけて再計算する必要があります。
position = normalize(position) * radius;
もう1つは法線や接線の計算をMesh.RecalculateNormalsなどに任せていますがスムーズにシェーディングされません。
コンピュートシェーダーで法線と接線も計算するようにした方が良さそうです。
今回はコンピュートシェーダーでメッシュを細分化する方法を紹介しました。
時間があるときに課題項目についてアップデートできればいいなぁと思います。
関連ページ
こちらのページも合わせてご覧下さい。