UnityのCompute Shaderでライフゲームの実装です。
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
メソッド内で行っています。