
前回、前々回で紹介した、コンピュートシェーダとハードウェアインスタンシングで大量のパーティクルを動かしてみます。
[Unity]コンピュートシェーダーで画面を動かす | Puzzle & Programing
コンピュートシェーダの使い方
[Unity]ハードウェアインスタンシングで1万の板ポリゴンを描画 | Puzzle & Programing
ハードウェアインスタンシングの使い方
基本的には前に説明したとおりです。
ふたつの技術を組み合わせてパーティクルを表現しています。
パーティクルの画像はこちら。
C#スクリプト
新しい物として、構造化バッファ(コンピュートバッファ)を使っています。
シェーダから、読み書き可能な構造体の配列として使うことが出来ます。
シリアライズに対応していないらしく、開放時に明示的にReleaseを呼び出してやる必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
using UnityEngine; using System.Runtime.InteropServices; /// <summary> /// 弾の構造体 /// </summary> struct Bullet { /// <summary> /// 座標 /// </summary> public Vector3 pos; /// <summary> /// 速度 /// </summary> public Vector3 accel; /// <summary> /// 色 /// </summary> public Color color; /// <summary> /// コンストラクタ /// </summary> public Bullet(Vector3 pos, Vector3 accel, Color color) { this.pos = pos; this.accel = accel; this.color = color; } } /// <summary> /// 沢山の弾を管理するクラス /// </summary> public class MenyBullets : MonoBehaviour { /// <summary> /// 弾をレンダリングするシェーダー /// </summary> public Shader bulletsShader; /// <summary> /// 弾のテクスチャ /// </summary> public Texture bulletsTexture; /// <summary> /// 弾の更新を行うコンピュートシェーダー /// </summary> public ComputeShader bulletsComputeShader; /// <summary> /// 弾のマテリアル /// </summary> Material bulletsMaterial; /// <summary> /// 弾のコンピュートバッファ /// </summary> ComputeBuffer bulletsBuffer; /// <summary> /// 破棄 /// </summary> void OnDisable() { // コンピュートバッファは明示的に破棄しないと怒られます bulletsBuffer.Release(); } /// <summary> /// 初期化 /// </summary> void Start () { bulletsMaterial = new Material(bulletsShader); InitializeComputeBuffer(); } /// <summary> /// 更新処理 /// </summary> void Update() { bulletsComputeShader.SetBuffer(0, "Bullets", bulletsBuffer); bulletsComputeShader.SetFloat("DeltaTime", Time.deltaTime); bulletsComputeShader.Dispatch(0, bulletsBuffer.count / 8 + 1, 1, 1); } /// <summary> /// コンピュートバッファの初期化 /// </summary> void InitializeComputeBuffer() { // 弾数は1万個 bulletsBuffer = new ComputeBuffer(10000, Marshal.SizeOf(typeof(Bullet))); // 配列に初期値を代入する Bullet[] bullets = new Bullet[bulletsBuffer.count]; for(int i = 0; i < bulletsBuffer.count; i++) { bullets[i] = new Bullet( new Vector3(Random.Range(-10.0f, 10.0f), Random.Range(-10.0f, 10.0f), Random.Range(-10.0f, 10.0f)), new Vector3(Random.Range(-1.0f, 1.0f), Random.Range(-1.0f, 1.0f), Random.Range(-1.0f, 1.0f)) * 0.5f, new Color(Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f))); } // バッファに適応 bulletsBuffer.SetData(bullets); } /// <summary> /// レンダリング /// </summary> void OnRenderObject() { // テクスチャ、バッファをマテリアルに設定 bulletsMaterial.SetTexture("_MainTex", bulletsTexture); bulletsMaterial.SetBuffer("Bullets", bulletsBuffer); // レンダリングを開始 bulletsMaterial.SetPass(0); // 1万個のオブジェクトをレンダリング Graphics.DrawProcedural(MeshTopology.Points, bulletsBuffer.count); } } |
シェーダー
テクスチャを貼り、ビルボード(カメラの方を向くオブジェクト)を実装しています。
頂点IDから構造化バッファにアクセスし、位置に加算しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
Shader "Custom/MenyBulletsShader" { SubShader { // アルファを使う ZWrite On Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM // シェーダーモデルは5.0を指定 #pragma target 5.0 // シェーダー関数を設定 #pragma vertex vert #pragma geometry geom #pragma fragment frag #include "UnityCG.cginc" // テクスチャ sampler2D _MainTex; // 弾の構造体 struct Bullet { float3 pos; float3 accel; float4 col; }; // 弾の構造化バッファ StructuredBuffer<Bullet> Bullets; // 頂点シェーダからの出力 struct VSOut { float4 pos : SV_POSITION; float2 tex : TEXCOORD0; float4 col : COLOR; }; // 頂点シェーダ VSOut vert (uint id : SV_VertexID) { // idを元に、弾の情報を取得 VSOut output; output.pos = float4(Bullets[id].pos, 1); output.tex = float2(0, 0); output.col = Bullets[id].col; return output; } // ジオメトリシェーダ [maxvertexcount(4)] void geom (point VSOut input[1], inout TriangleStream<VSOut> outStream) { VSOut output; // 全ての頂点で共通の値を計算しておく float4 pos = input[0].pos; float4 col = input[0].col; // 四角形になるように頂点を生産 for(int x = 0; x < 2; x++) { for(int y = 0; y < 2; y++) { // ビルボード用の行列 float4x4 billboardMatrix = UNITY_MATRIX_V; billboardMatrix._m03 = billboardMatrix._m13 = billboardMatrix._m23 = billboardMatrix._m33 = 0; // テクスチャ座標 float2 tex = float2(x, y); output.tex = tex; // 頂点位置を計算 output.pos = pos + mul(float4((tex * 2 - float2(1, 1)) * 0.2, 0, 1), billboardMatrix); output.pos = mul (UNITY_MATRIX_VP, output.pos); // 色 output.col = col; // ストリームに頂点を追加 outStream.Append (output); } } // トライアングルストリップを終了 outStream.RestartStrip(); } // ピクセルシェーダー fixed4 frag (VSOut i) : COLOR { // 出力はテクスチャカラーと頂点色 float4 col = tex2D(_MainTex, i.tex) * i.col; // アルファが一定値以下なら中断 if(col.a < 0.3) discard; // 色を返す return col; } ENDCG } } } |
コンピュートシェーダー
コンピュートシェーダは簡単。
構造化バッファから値を取り出し、移動ベクトルを加算して再代入しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#pragma kernel CSMain // 弾の構造体 struct Bullet { float3 pos; float3 accel; float4 col; }; // 前回の更新からの経過時間 float DeltaTime; // 弾の構造化バッファ RWStructuredBuffer<Bullet> Bullets; // (8, 1, 1)のスレッドで回す [numthreads(8,1,1)] void CSMain (uint3 id : SV_DispatchThreadID) { // それぞれの位置情報に移動ベクトルを加算 Bullets[id.x].pos += Bullets[id.x].accel * DeltaTime; } |
結果
大量のパーティクルが動きました!
Pingbacks/Trackbacks
[…] ▼前回 [Unity]コンピュートシェーダ(GPGPU)で1万個のパーティクルを動かす | notargs.co… […]
[…] http://notargs.com/blog/blog/2015/01/27/unity%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%83%88%E3%82%B7%E3%82%A7%E3%83%BC%E3%83%80%E3%81%A8%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B7%E3%83%B3%E3%82%B0%E3%81%A71%E4%B8%87/ […]