前回に引き続き、ComputeShaderを使っていろいろ試行錯誤していきます。
前回は最終的にGPUまで登場させてUnity関係なく四則演算をちょっとやっただけだったので、今回はオブジェクトを1つ回転させて見るところから始めます。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CubeComputeShader : MonoBehaviour { public ComputeShader Shader; public Transform Target; private int _kernelIndex; private ComputeBuffer _buffer; void Start() { _kernelIndex = Shader.FindKernel("RotateTarget"); _buffer = new ComputeBuffer(1, sizeof(float)); Shader.SetBuffer(_kernelIndex, "floatBuffer", _buffer); } void Update() { Shader.Dispatch(_kernelIndex, 1, 1, 1); var data = new float[1]; _buffer.GetData(data); // Unityで回転の代入は難しいです(よく理解していないという意味で) Target.eulerAngles = new Vector3(data[0], 0, 0); } private void OnDestroy() { _buffer.Release(); } }
続いてシェーダ側。
#pragma kernel RotateTarget RWStructuredBuffer<float> floatBuffer; [numthreads(10, 1, 1)] void RotateTarget(uint id : SV_DispatchThreadID) { floatBuffer[id] += 1.0f; }
実際に試してみましょう。
1フレームごとに1度ずつキューブを回転させることが出来ました。(後続の実験のためにnumthreadsをあえて大きくしています。)
GPU「わざわざ仕事で呼び出されて、キューブをたった1つ回してお終いかい?」
では、たくさん回してもらいましよう。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CubeComputeShader : MonoBehaviour { public ComputeShader Shader; public int Count; private int _kernelIndex; private ComputeBuffer _buffer; private GameObject[] _cubes; private int _num = 10; void Start() { _cubes = new GameObject[Count]; for(int i = 0; i < Count; i++) { var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f); cube.transform.localPosition = new Vector3(i%_num, (i/_num)%_num, i/(_num*_num)); _cubes[i] = cube; } _kernelIndex = Shader.FindKernel("RotateTarget"); _buffer = new ComputeBuffer(Count, sizeof(float)); Shader.SetBuffer(_kernelIndex, "floatBuffer", _buffer); } void Update() { // 1000(= 10000 / 10)グループの各10スレッド(numthreads)で並列処理を実行 int groupX = (Count / _num); Shader.Dispatch(_kernelIndex, groupX, 1, 1); var data = new float[Count]; _buffer.GetData(data); for(int i = 0; i < Count; i++) { _cubes[i].transform.localEulerAngles = new Vector3(data[i], 0, 0); } } (~以下同じ~) }
シェーダ側は最初の例と同じです。
では、実際に試してみましょう。
本筋とは関係ないところですが、
cube.transform.localPosition = new Vector3(i%_num, (i/_num)%_num, i/(_num*_num));
ここが少しややこしいかもしれません。今回は大量のキューブということで10×10×10 = 10000個を等間隔で並べる都合でXYZ軸それぞれで計算しています。
少し凡例を出すとすぐに分かると思います。(たとえば250個目のキューブの座標は?)
実行結果は問題なさそうですが、果たしてこれで効率的なのかと言われると少し不安です。
// 1000(= 10000 / 10)グループの各10スレッド(numthreads)で並列処理を実行 int groupX = (Count / _num); Shader.Dispatch(_kernelIndex, groupX, 1, 1);
コメントにあるとおりなのですが、今回の例であればグループ10000でスレッド1でも結果は変わらないということになるのでしょうか?
グループ数よりはスレッド数を増やすので良い?ケースバイケース?ちょっとこのあたりはまだわかっていません。
GPU「なんだかたくさん回しているけどどれも条件が一緒で単調だな!?」
そうですね、せっかくですので最後に初期の回転をずらしてぐちゃぐちゃにしてみましょう。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CubeComputeShader : MonoBehaviour { public ComputeShader Shader; public int Count; private int _kernelIndex; private ComputeBuffer _buffer; private GameObject[] _cubes; private float[] _angles; private int _num = 10; void Start() { _cubes = new GameObject[Count]; _angles = new float[Count]; for(int i = 0; i < Count; i++) { var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f); cube.transform.localPosition = new Vector3(i%_num, (i/_num)%_num, i/(_num*_num)); _cubes[i] = cube; _angles[i] = Random.Range(-90, 90); _cubes[i].transform.localEulerAngles = new Vector3(_angles[i], 0, 0); } _kernelIndex = Shader.FindKernel("RotateTarget"); _buffer = new ComputeBuffer(Count, sizeof(float)); Shader.SetBuffer(_kernelIndex, "floatBuffer", _buffer); _buffer.SetData(_angles); } (~以下同じ~)
シェーダ側は引き続き一緒です。
実際に試してみましょう。
できました。コードは、ほとんど変わっていません。
各キューブの角度を随時保存するための_angles
という配列を導入しました。それと、
_buffer.SetData(_angles);
で初期角度を代入しているところだけです。
実は最初戸惑ったのですが、この時点ですでに_buffer
はシェーダ側のfloatBuffer
と紐付いているのでSetDataでそちらに初期角度を送ることがこれで出来ています。
まとめ
今回はUnityのコンピュートシェーダを使って大量のオブジェクトの回転を実験しました。
まだまだ手探りですが、前回と違ってUnityで実際に物が動いているのが見れたので、楽しかったです。
しかし、先の例で上げたスレッドとグループの効率化みたいな部分はよくわかっていません。その3ではそのあたりを学んでいきたいです。
それでは。
参考
近況
10月から大きいプロジェクトに異動しました。相変わらずゲームを作っています。
PENTAX SPを買ったので昼休みや休日はひたすら散歩しています。
それと、寒くなってきて腰がまた痛みだしました。はー、早く引っ越ししたい。