本日はShader枠です。
先日からコンピュートシェーダーの勉強を始めた筆者ですが、あらゆるプログラミングにおいてデバッグログというものはとても大切です。
しかしながらGPU上で実行されるシェーダーはもともと色を出力するためのプログラムであるためログとしてのデバッグが困難です。
一般的にShaderの場合はRenderDocなどのツールを使用するか検証を行いたい箇所で色として出力させることで動作検証を行います。
コンピュートシェーダーに関してもGPU上で実行されるため、同様のことが言えます。
しかし通常のシェーダーと異なる点としてコンピュートシェーダーは色を出力することを目的としたシェーダーではなく、そのGPUの演算力を使用して汎用的に演算を行うGPGPUであり、バッファを使用してC#側からのデータの流入によって動作します、このためログ用のデータのバッファを作成することで通常のUnityC#同様にDebug.Logが使用できるのではないかと思い今回実証してみます。
〇環境
・Windows11PC
・Unity2022.3.26f1
〇コンピュートシェーダーのデバッグ
まずはコンピュートシェーダー内でバッファの定義を行います
コンピュートシェーダーではStructuredBuffer<>を使用してバッファを定義しますが、RWStructuredBufferを使用することでC#側から値の更新(Read/Write=読み書き)が可能になります。
今回はfloat型で定義しました。
RWStructuredBuffer<float> debugBuffer;
任意の値をdebugBufferに格納します。
このfebugBufferに任意の値を代入します。
// 頂点とターゲットの距離を計算
float distance = length(vertexPos - targetPos);
float epsilon = 0.0001;
float force = 1.0 / (distance * distance + epsilon); // 引力の計算
debugBuffer[index] = force;//デバッグ
C#側のStart関数内でバッファを作成します。
ComputeBuffer debugBuffer = new ComputeBuffer(1, sizeof(float)); vertexColorComputeShader.SetBuffer(kernelHandle, "debugBuffer", debugBuffer);
同様にUpdate関数内でコンピュートシェーダーのバッファにセットします
vertexColorComputeShader.SetBuffer(kernelHandle, "debugBuffer", debugBuffer);
最後にdebugDataの値を受け取ります。
float[] debugData = new float[1];
debugBuffer.GetData(debugData);
Debug.Log($"Debug Value: {debugData[0]}");
これによってDebugLogとしてコンソールに表示されるようになります。

〇今回使用したコード
・コンピュートシェーダー
#pragma kernel CSMain
// バッファの宣言
StructuredBuffer<float3> vertexBuffer;
RWStructuredBuffer<float4> colorBuffer;
StructuredBuffer<float3> targetBuffer;
RWStructuredBuffer<float3> updatedVertexBuffer;
RWStructuredBuffer<float> radiusBuffer;
RWStructuredBuffer<float> debugBuffer;
// ワールド変換行列
float4x4 localToWorldMatrix;
[numthreads(256, 1, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
uint index = id.x;
// バッファの範囲をチェック
if (index >= vertexBuffer.Length)
return;
// 頂点位置を取得
float3 localPos = vertexBuffer[index];
float3 vertexPos = mul(localToWorldMatrix, float4(localPos, 1.0)).xyz;
// ターゲット位置をバッファから取得
float3 targetPos = targetBuffer[0];
// 頂点とターゲットの距離を計算
float distance = length(vertexPos - targetPos);
float epsilon = 0.0001;
float force = 1.0 / (distance * distance + epsilon); // 引力の計算
debugBuffer[index] = force;
if (distance < radiusBuffer[0])
{
// 頂点とターゲットの方向ベクトルを計算
float3 direction = normalize(targetPos - vertexPos);
vertexPos += direction * force * 0.001;
// 移動量
// 更新後の頂点位置を保存
updatedVertexBuffer[index] = vertexPos;
}
else
{
updatedVertexBuffer[index] = vertexPos;
}
}
・C#
using UnityEngine;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class VertexColorCompute : MonoBehaviour
{
public ComputeShader vertexColorComputeShader;
public GameObject targetObject; // ターゲットオブジェクト
private Mesh mesh;
private Vector3[] vertices;
private int[] triangles;
private Color[] colors;
[SerializeField] private float radius = 0.1f;
private ComputeBuffer vertexBuffer;
private ComputeBuffer updatedVertexBuffer;
private ComputeBuffer triangleBuffer;
private ComputeBuffer colorBuffer;
private ComputeBuffer targetBuffer; // ターゲットオブジェクトの位置を保持するバッファ
private ComputeBuffer radiusBuffer;
private ComputeBuffer debugBuffer;
void Start()
{
// メッシュの取得
mesh = GetComponent<MeshFilter>().mesh;
vertices = mesh.vertices;
triangles = mesh.triangles;
colors = new Color[vertices.Length];
// 頂点バッファの作成
vertexBuffer = new ComputeBuffer(vertices.Length, sizeof(float) * 3);
vertexBuffer.SetData(vertices);
// インデックスバッファの作成
triangleBuffer = new ComputeBuffer(triangles.Length, sizeof(int));
triangleBuffer.SetData(triangles);
// カラーバッファの作成
colorBuffer = new ComputeBuffer(colors.Length, sizeof(float) * 4);
colorBuffer.SetData(colors);
// 更新後の頂点位置バッファの作成
updatedVertexBuffer = new ComputeBuffer(vertices.Length, sizeof(float) * 3);
radiusBuffer = new ComputeBuffer(1, sizeof(float));
radiusBuffer.SetData(new float[] { radius });
debugBuffer = new ComputeBuffer(1, sizeof(float));
// ターゲットオブジェクトの位置を保持するバッファの作成
targetBuffer = new ComputeBuffer(1, sizeof(float) * 3);
targetBuffer.SetData(new Vector3[] { targetObject.transform.position });
// メッシュに初期カラーを設定
mesh.colors = colors;
}
void Update()
{
// ターゲットオブジェクトの位置をターゲットバッファに更新
targetBuffer.SetData(new Vector3[] { targetObject.transform.position });
vertexBuffer.SetData(mesh.vertices);
// コンピュートシェーダーのセットアップ
int kernelHandle = vertexColorComputeShader.FindKernel("CSMain");
radiusBuffer.SetData(new float[] { radius });
vertexBuffer.SetData(mesh.vertices);
// シェーダーにバッファをセット
vertexColorComputeShader.SetBuffer(kernelHandle, "vertexBuffer", vertexBuffer);
vertexColorComputeShader.SetBuffer(kernelHandle, "colorBuffer", colorBuffer);
vertexColorComputeShader.SetBuffer(kernelHandle, "targetBuffer", targetBuffer);
vertexColorComputeShader.SetBuffer(kernelHandle, "updatedVertexBuffer", updatedVertexBuffer); // 追加
vertexColorComputeShader.SetBuffer(kernelHandle, "radiusBuffer", radiusBuffer);
vertexColorComputeShader.SetBuffer(kernelHandle, "debugBuffer", debugBuffer);
// ワールド変換行列をセット(必要に応じて)
vertexColorComputeShader.SetMatrix("localToWorldMatrix", transform.localToWorldMatrix);
// シェーダーをディスパッチ
int threadGroups = Mathf.CeilToInt(vertices.Length / 256.0f);
vertexColorComputeShader.Dispatch(kernelHandle, threadGroups, 1, 1);
// カラーバッファからデータを取得
colorBuffer.GetData(colors);
updatedVertexBuffer.GetData(vertices);
// メッシュに適用
mesh.vertices = vertices;
mesh.colors = colors;
float[] debugData = new float[1];
debugBuffer.GetData(debugData);
Debug.Log($"Debug Value: {debugData[0]}");
}
void OnDestroy()
{
// バッファの解放
if (vertexBuffer != null)
vertexBuffer.Release();
if (triangleBuffer != null)
triangleBuffer.Release();
if (colorBuffer != null)
colorBuffer.Release();
if (targetBuffer != null)
targetBuffer.Release();
if(updatedVertexBuffer != null)
updatedVertexBuffer.Release();
if(debugBuffer != null)
debugBuffer.Release();
}
}