10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Unity】Compute Shaderでライフゲーム

Last updated at Posted at 2018-10-02

UnityのCompute Shaderでライフゲームの実装です。

lifegame.gif

LifeGame.cs
using UnityEngine;

public class LifeGame : MonoBehaviour
{
    [SerializeField] GameObject plane;
    [SerializeField] ComputeShader computeShader;
    private RenderTexture stateTexture, drawTexture;

    private int kernelInitialize, kernelUpdate, kernelDraw;
    private ThreadSize threadSizeInitialize, threadSizeUpdate, threadSizeDraw;

    struct ThreadSize
    {
        public int x;
        public int y;
        public int z;

        public ThreadSize(uint x, uint y, uint z)
        {
            this.x = (int)x;
            this.y = (int)y;
            this.z = (int)z;
        }
    }

    private void Start()
    {
        // カーネルIdの取得
        kernelInitialize = computeShader.FindKernel("Initialize");
        kernelUpdate = computeShader.FindKernel("Update");
        kernelDraw = computeShader.FindKernel("Draw");

        // 状態を格納するテクスチャの作成
        stateTexture = new RenderTexture(512, 512, 0, RenderTextureFormat.RInt);
        stateTexture.wrapMode = TextureWrapMode.Repeat;
        stateTexture.enableRandomWrite = true;
        stateTexture.Create();
        // レンダリング用のテクスチャの取得
        drawTexture = new RenderTexture(512, 512, 0, RenderTextureFormat.ARGB32);
        drawTexture.filterMode = FilterMode.Point;
        drawTexture.enableRandomWrite = true;
        drawTexture.Create();

        // スレッド数の取得
        uint threadSizeX, threadSizeY, threadSizeZ;
        computeShader.GetKernelThreadGroupSizes(kernelInitialize, out threadSizeX, out threadSizeY, out threadSizeZ);
        threadSizeInitialize = new ThreadSize(threadSizeX, threadSizeY, threadSizeZ);
        computeShader.GetKernelThreadGroupSizes(kernelUpdate, out threadSizeX, out threadSizeY, out threadSizeZ);
        threadSizeUpdate = new ThreadSize(threadSizeX, threadSizeY, threadSizeZ);
        computeShader.GetKernelThreadGroupSizes(kernelDraw, out threadSizeX, out threadSizeY, out threadSizeZ);
        threadSizeDraw = new ThreadSize(threadSizeX, threadSizeY, threadSizeZ);

        // LifeGameの状態の初期化
        computeShader.SetTexture(kernelInitialize, "stateTexture", stateTexture);
        computeShader.Dispatch(kernelInitialize, Mathf.CeilToInt(stateTexture.width / threadSizeInitialize.x), Mathf.CeilToInt(stateTexture.height / threadSizeInitialize.y), 1);
    }

    private void Update()
    {
        // LifeGameの状態の更新
        computeShader.SetTexture(kernelUpdate, "stateTexture", stateTexture);
        computeShader.Dispatch(kernelUpdate, Mathf.CeilToInt(stateTexture.width / threadSizeUpdate.x), Mathf.CeilToInt(stateTexture.height / threadSizeUpdate.y), 1);

        // LifeGameの状態をもとにレンダリング用のテクスチャを作成
        computeShader.SetTexture(kernelDraw, "stateTexture", stateTexture);
        computeShader.SetTexture(kernelDraw, "drawTexture", drawTexture);
        computeShader.Dispatch(kernelDraw, Mathf.CeilToInt(stateTexture.width / threadSizeDraw.x), Mathf.CeilToInt(stateTexture.height / threadSizeDraw.y), 1);
        plane.GetComponent<Renderer>().material.mainTexture = drawTexture;
    }
}
LifeGame.compute
#pragma kernel Initialize
#pragma kernel Update
#pragma kernel Draw

RWTexture2D<int>  stateTexture;
RWTexture2D<float4> drawTexture;

float random(float2 x)
{
    return frac(sin(dot(x.xy, float2(12.9898, 78.233))) * 43758.5453);
}

[numthreads(8, 8, 1)]
void Initialize(uint3 dispatchThreadId : SV_DispatchThreadID)
{
	stateTexture[dispatchThreadId.xy] = random(dispatchThreadId.xy) < 0.5 ? 0 : 1;
}

[numthreads(8, 8, 1)]
void Update(uint3 dispatchThreadId : SV_DispatchThreadID)
{

	int state = stateTexture[dispatchThreadId.xy];

	int neighbors = 0;
	neighbors += stateTexture[dispatchThreadId.xy + uint2(-1, -1)];
	neighbors += stateTexture[dispatchThreadId.xy + uint2(-1,  0)];
	neighbors += stateTexture[dispatchThreadId.xy + uint2(-1,  1)];
	neighbors += stateTexture[dispatchThreadId.xy + uint2( 0, -1)];
	neighbors += stateTexture[dispatchThreadId.xy + uint2( 0,  1)];
	neighbors += stateTexture[dispatchThreadId.xy + uint2( 1, -1)];
	neighbors += stateTexture[dispatchThreadId.xy + uint2( 1,  0)];
	neighbors += stateTexture[dispatchThreadId.xy + uint2( 1,  1)];

	if ((state == 0 && neighbors == 3) || (state == 1 && (neighbors == 2 || neighbors == 3))) {
		stateTexture[dispatchThreadId.xy] = 1;
	} else {
		stateTexture[dispatchThreadId.xy] = 0;
	}
}

[numthreads(8, 8, 1)]
void Draw(uint3 dispatchThreadId : SV_DispatchThreadID)
{
	drawTexture[dispatchThreadId.xy] = stateTexture[dispatchThreadId.xy] == 1 ? float4(0, 1, 0, 1) : float4(0, 0, 0, 1);
}

LifeGame.cs内のInitializeメソッドでは、まずRenderingTextureの作成を行っています。今回はライフゲームの状態を格納するstateTextureとマテリアルにテクスチャとして渡すdrawTextureを作成しています。ライフゲームの状態は0と1の2値で管理するのでstateTextureにはRenderTextureFormat.RIntを使用しています。
また、Initializeメソッドではライフゲームの状態の初期化をComputeShaderで行っています。Initializeカーネルを用いてランダムに0と1を設定しています。

Updateメソッド内では、ライフゲームの状態の更新をUpdateカーネルで、状態をもとにしたレンダリング用のテクスチャへの描画をDrawメソッド内で行っています。

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?