本日はコンピュートシェーダー枠です。
現在ComputeShaderを学習中ですが、今回はメッシュの頂点をスムースを掛けて動かします。
〇環境
・Unity6000.0.2f1
・Windows11PC
〇コンピュートシェーダーを使用してスムースを行う
今回はUnityのMeshFilterを使用してメッシュクラスから、頂点情報をバッファに格納してGPUで処理を行います。
コンピュートシェーダーは以下のようになります。
#pragma kernel SmoothKernel
// バッファの宣言
StructuredBuffer<float3> originalVertexBuffer; // 元の頂点座標
StructuredBuffer<float3> vertexBuffer; // 現在の頂点座標
RWStructuredBuffer<float3> updatedVertexBuffer; // 更新後の頂点座標
int vertexCount; // 頂点数
float radius; // スムースの半径
float intensity; // スムースの強度 (0.0 ~ 1.0)
[numthreads(256, 1, 1)]
void SmoothKernel(uint3 id : SV_DispatchThreadID)
{
uint index = id.x;
// 範囲外チェック
if (index >= vertexCount)
return;
// 現在の頂点座標
float3 currentVertex = vertexBuffer[index];
// 元の頂点座標
float3 originalVertex = originalVertexBuffer[index];
// スムース処理用の変数
float3 smoothedVertex = 0.0; // 加算用の座標
float weightSum = 0.0; // 重みの合計
// 半径内の近隣頂点を探索
for (uint i = 0; i < vertexCount; i++)
{
float3 neighborVertex = vertexBuffer[i];
float dist = length(currentVertex - neighborVertex);
// 半径内の頂点を処理
if (dist <= radius)
{
// 距離に応じた減衰を計算 (距離が近いほど影響が大きい)
float weight = 1.0 - (dist / radius);
smoothedVertex += neighborVertex * weight; // 加重平均
weightSum += weight;
}
}
// 重みがゼロでない場合、正規化してスムース座標を計算
if (weightSum > 0.0)
{
smoothedVertex /= weightSum; // 加重平均を計算
}
else
{
smoothedVertex = currentVertex; // 影響がない場合は現在の座標を維持
}
// 元の座標に基づいた補間 (完全な収束を防ぐ)
updatedVertexBuffer[index] = lerp(currentVertex, smoothedVertex, intensity) * 0.9 + originalVertex * 0.1;
}
すべての頂点に対して、近接頂点を取得、平均を取ることでスムースを掛けています。
また、ここで重複頂点を防ぐためにlerpを使用して元の頂点座標に対して重複しない仕組みを採用しています。
このコンピュートシェーダーを使用するC#は以下のようになります。
〇C
using UnityEngine;
using UnityEngine;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class SmoothCS : MonoBehaviour
{
public ComputeShader vertexColorComputeShader; // コンピュートシェーダー
[SerializeField] private float radius = 0.1f; // スムースの半径
[SerializeField] private float intensity = 0.5f; // スムースの強度 (0.0 ~ 1.0)
private Mesh mesh;
private Vector3[] vertices;
private int[] triangles;
private ComputeBuffer vertexBuffer;
private ComputeBuffer updatedVertexBuffer;
private ComputeBuffer originalVertexBuffer;
private ComputeBuffer triangleBuffer;
void Start()
{
// メッシュを取得
mesh = GetComponent<MeshFilter>().mesh;
vertices = mesh.vertices;
triangles = mesh.triangles;
// ComputeBuffer の初期化
vertexBuffer = new ComputeBuffer(vertices.Length, sizeof(float) * 3);
updatedVertexBuffer = new ComputeBuffer(vertices.Length, sizeof(float) * 3);
originalVertexBuffer = new ComputeBuffer(vertices.Length, sizeof(float) * 3);
triangleBuffer = new ComputeBuffer(triangles.Length, sizeof(int));
// バッファに初期データをセット
vertexBuffer.SetData(vertices);
updatedVertexBuffer.SetData(vertices);
originalVertexBuffer.SetData(vertices); // 元の座標を保存
triangleBuffer.SetData(triangles);
}
void Update()
{
ApplySmoothing();
}
void ApplySmoothing()
{
// シェーダーのカーネルを取得
int kernelHandle = vertexColorComputeShader.FindKernel("SmoothKernel");
// シェーダーにバッファとパラメータをセット
vertexColorComputeShader.SetBuffer(kernelHandle, "originalVertexBuffer", originalVertexBuffer);
vertexColorComputeShader.SetBuffer(kernelHandle, "vertexBuffer", vertexBuffer);
vertexColorComputeShader.SetBuffer(kernelHandle, "updatedVertexBuffer", updatedVertexBuffer);
vertexColorComputeShader.SetInt("vertexCount", vertices.Length);
vertexColorComputeShader.SetFloat("radius", radius);
vertexColorComputeShader.SetFloat("intensity", intensity);
// スレッドグループ数を計算
int threadGroups = Mathf.CeilToInt(vertices.Length / 256.0f);
vertexColorComputeShader.Dispatch(kernelHandle, threadGroups, 1, 1);
// 更新された頂点を取得してメッシュに反映
updatedVertexBuffer.GetData(vertices);
vertexBuffer.SetData(vertices); // 更新後の頂点情報を再セット
mesh.vertices = vertices;
mesh.RecalculateNormals(); // 法線の再計算
}
void OnDestroy()
{
// ComputeBuffer を解放
vertexBuffer.Release();
updatedVertexBuffer.Release();
originalVertexBuffer.Release();
triangleBuffer.Release();
}
}
このコードを実行するとスムースがかかります。

以上でコンピュートシェーダーを使用したメッシュにスムースを掛ける実装です。
スムースの制御等がまだうまくいっていませんが改善していきたいです。