夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

ComputeShaderでオブジェクトを回転させる

本日はShader枠です。

現在ComputeShaderを学習中ですが、今回はその例としてオブジェクトを回転させていきます。

〇環境

・Unity2022.3.26f1

・Windows11PC

〇コンピュートシェーダーでメッシュを回転させる

コンピュートシェーダーでメッシュを回転させるためには回転行列を使用します。

redhologerbera.hatenablog.com

ここから回転行列をHLSLで以下のようにあらわすことができます。

今回はY軸に対して回転を行わせています。

float4x4 GetRotationMatrixY(float angle)
{
    float s = sin(angle);
    float c = cos(angle);
    return float4x4(
        float4(c, 0, -s, 0),
        float4(0, 1,  0, 0),
        float4(s, 0,  c, 0),
        float4(0, 0,  0, 1)
    );
}

頂点を回転行列を使用して回転させるには以下のような実装を行います。

 // 元の頂点を取得
    float3 vertex = inputVertices[id.x];

    // 回転行列を計算
    float4x4 rotationMatrix = GetRotationMatrixY(time);

    // 回転を適用
    float4 rotatedVertex = mul(rotationMatrix, float4(vertex, 1.0));
    outputVertices[id.x] = rotatedVertex.xyz;

コメントの通り、頂点を取得し行列を使用して変換しています。

コンピュートシェーダーは以下のようになります。

// RotateMesh.compute
#pragma kernel CSMain

// バッファの宣言
StructuredBuffer<float3> inputVertices; // 元の頂点データ
RWStructuredBuffer<float3> outputVertices; // 計算結果の頂点データ

// 時間
float time;

// 回転行列
float4x4 GetRotationMatrixY(float angle)
{
    float s = sin(angle);
    float c = cos(angle);
    return float4x4(
        float4(c, 0, -s, 0),
        float4(0, 1,  0, 0),
        float4(s, 0,  c, 0),
        float4(0, 0,  0, 1)
    );
}

[numthreads(256, 1, 1)] // スレッドの設定
void CSMain(uint3 id : SV_DispatchThreadID)
{
    if (id.x >= inputVertices.Length) return;

    // 元の頂点を取得
    float3 vertex = inputVertices[id.x];

    // 回転行列を計算
    float4x4 rotationMatrix = GetRotationMatrixY(time);

    // 回転を適用
    float4 rotatedVertex = mul(rotationMatrix, float4(vertex, 1.0));
    outputVertices[id.x] = rotatedVertex.xyz;
}

C#側は以下のようになります。

using UnityEngine;

public class RotateMesh : MonoBehaviour
{
    public ComputeShader computeShader; // コンピュートシェーダー
    private Mesh mesh;
    private ComputeBuffer inputBuffer;
    private ComputeBuffer outputBuffer;

    private Vector3[] vertices;
    private int kernel;

    void Start()
    {
        // メッシュの取得
        mesh = GetComponent<MeshFilter>().mesh;
        vertices = mesh.vertices;

        // コンピュートバッファの作成
        inputBuffer = new ComputeBuffer(vertices.Length, sizeof(float) * 3);
        outputBuffer = new ComputeBuffer(vertices.Length, sizeof(float) * 3);

        // 元の頂点データをバッファにセット
        inputBuffer.SetData(vertices);

        // カーネルを取得
        kernel = computeShader.FindKernel("CSMain");
        computeShader.SetBuffer(kernel, "inputVertices", inputBuffer);
        computeShader.SetBuffer(kernel, "outputVertices", outputBuffer);
    }

    void Update()
    {
        // 時間を送る
        computeShader.SetFloat("time", Time.time);

        // ディスパッチを実行
        computeShader.Dispatch(kernel, Mathf.CeilToInt(vertices.Length / 256.0f), 1, 1);

        // 結果を取得してメッシュに反映
        Vector3[] outputVertices = new Vector3[vertices.Length];
        outputBuffer.GetData(outputVertices);
        mesh.vertices = outputVertices;
        mesh.RecalculateNormals();
    }

    void OnDestroy()
    {
        // バッファの解放
        inputBuffer.Release();
        outputBuffer.Release();
    }
}

これを実行すると次のようにY軸基準に開店するアニメーションを実装できました。

本日は以上です。