【Unite Tokyo 2018】誘導ミサイル完全マスター

4.4K Views

May 08, 18

スライド概要

講演者:安原 祐二(ユニティ・テクノロジーズ・ジャパン合同会社)

こんな人におすすめ
・これから誘導ミサイルを学んでみたい方
・そろそろコンピュートシェーダの使い方を学んでおきたい方
・誘導ミサイルが好きな方

受講者が得られる知見
・誘導ミサイルの実装方法
・誘導ミサイルのゲームデザイン上の特徴
・コンピュートシェーダをエフェクト以外で利用する際の注意点

profile-image

リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

DAY2 2018/05/08 誘導ミサイル完全マスター 安原 祐二 ユニティ・テクノロジーズ・ジャパ ン

2.

“ 問題 ”

3.

〜Frustum Culling〜 Frustum(視錐台)の外側の物体の描画を省略する 通常はUnityがやってくれる やりかたわかりますか?

4.

平面と点の関係 平面 点 x, y, z

5.

平面の式 ax + by + cz+ d= 0 a, b, c, d で平面が定まる 例 2 x + 3y + 4z+ 5= 0

6.

2 x + 3y + 4z+ 5= 0 左辺は平面からの距離を意味する 平面の式とは 「平面からの距離がゼロ」を満たす x, y, z の集合 ただしこの距離は正規化されていない

7.

ax + by + cz+d= 0 ( a, b, c) は平面に垂直なベクトル p a + b +c 2 2 2 で両辺を割って正規化

8.

ax + by + cz+d= 0 ( a, b, c) は平面に垂直なベクトル p a + b +c 2 2 例 p 2 で両辺を割って正規化 2 x + 3y + 4z+ 5= 0 2 2 2 p x 29 + 2 3 + + 2 4 = p 3 p y 29 29 + 5 4 p z+ p = 29 29 0

9.

点と平面の距離 平面 点 距離

10.

点と平面の距離 平面 2 p x 29 点 + 3 p y 29 + 5 4 p z+ p = 29 29 p(5, 6, 7) 距離 4 2 3 5 p p p p 距離 = ⇥5 + ⇥6 + ⇥7+ 29 29 29 29 0

11.

距離には符号がある 平面 2 p x 29 + 3 p y 29 + 点 p(5, 6, 7) 距離 裏 - + 表 平面の表にあるか裏にあるかがわかる 5 4 p z+ p = 29 29 0

12.

球の場合 裏 - -r + 点と平面の距離が「-r以上」か 表

13.

視錐台(Frustum)の6つの平面を得るには プロジェクション行列から取得

14.

参考:プロジェクション行列から6平面を取得(正規化は省略) void GetPlanesFromFrustum(Vector4[] planes, ref Matrix4x4 p) { planes[0] = new Vector4(p.m30+p.m00, p.m31+p.m01, p.m32+p.m02, p.m33+p.m03); // left planes[1] = new Vector4(p.m30-p.m00, p.m31-p.m01, p.m32-p.m02, p.m33-p.m03); // right planes[2] = new Vector4(p.m30+p.m10, p.m31+p.m11, p.m32+p.m12, p.m33+p.m13); // bottom planes[3] = new Vector4(p.m30-p.m10, p.m31-p.m11, p.m32-p.m12, p.m33-p.m13); // top planes[4] = new Vector4(p.m30+p.m20, p.m31+p.m21, p.m32+p.m22, p.m33+p.m23); // near planes[5] = new Vector4(p.m30-p.m20, p.m31-p.m21, p.m32-p.m22, p.m33-p.m23); // far }

15.

6平面のひとつでも裏側にあれば、描画の必要ナシ Frustum Culling 完成

16.

“ 誘導ミサイル完全マスター ”

17.

神は○○○○に宿る

18.

追尾レーザー 誘導ミサイル

19.

追尾レーザー実装例

20.

追尾レーザー実装例その2

21.

誘導ミサイル実装例 https://github.com/Unity-Technologies/MissilesPerfectMaster

22.

“ 追尾レーザーの実装 ”

23.

基本:運動方程式の実装例 public class Missile : MonoBehaviour { Vector3 velocity; Vector3 position; void Update() { var acceleration = Vector3.zero; } } velocity += acceleration * Time.deltaTime; position += velocity * Time.deltaTime; transform.position = position;

24.

基本:運動方程式の実装例 public class Missile : MonoBehaviour { Vector3 velocity; Vector3 position; void Update() { var acceleration = Vector3.zero; /* ここで与えたい外力を記述する(AddForce) */ } } velocity += acceleration * Time.deltaTime; position += velocity * Time.deltaTime; transform.position = position;

25.
[beta]
public class Missile : MonoBehaviour {
Vector3 velocity;
Vector3 position;
void Update() {
var acceleration = Vector3.zero;
acceleration += new Vector3(0f, -9.8f, 0f);

}

}

velocity += acceleration * Time.deltaTime;
position += velocity * Time.deltaTime;
transform.position = position;

26.

加速度・速度・時間・距離の関係 1 2 d = v t + at 2

27.

加速度・速度・時間・距離の関係 v:速度 d:距離 1 2 d = v t + at 2 a:加速度 t :時間

28.

1 2 d = v t + at 2 a= 速度 v の物体が vt ) 2(d 2 t t 秒後に d 進むための加速度a

29.
[beta]
public class Missile : MonoBehaviour {
Vector3 velocity;
Vector3 position;
2(d
Transform target;
a=
2
t
float period;
void Update() {
var acceleration = Vector3.zero;

vt )

var diff = target.position - position;
acceleration += (diff - velocity*period)*2f
/(period*period);
period -= Time.deltaTime;
if (period < 0f) {
return;
}
velocity += acceleration * Time.deltaTime;
position += velocity * Time.deltaTime;
transform.position = position;

30.

目標は動いているので・・ v a d a= 常に加速度を計算しなおす vt ) 2(d 2 t

31.
[beta]
public class Missile : MonoBehaviour {
Vector3 velocity;
Vector3 position;
Transform target;
float period;
void Update() {
var acceleration = Vector3.zero;
var diff = target.position - position;
acceleration += (diff - velocity*period)*2f
/(period*period);
period -= Time.deltaTime;
if (period < 0f) {
return;
}
velocity += acceleration * Time.deltaTime;
position += velocity * Time.deltaTime;
transform.position = position;

32.
[beta]
public class Missile : MonoBehaviour {
Vector3 velocity;
Vector3 position;
Transform target;
float period;
void Update() {
var acceleration = Vector3.zero;

必ず命中する
コリジョンはいらない
命中時刻が決まっている

var diff = target.position - position;
acceleration += (diff - velocity*period)*2f
/(period*period);
period -= Time.deltaTime;
if (period < 0f) {
return;
}
velocity += acceleration * Time.deltaTime;
position += velocity * Time.deltaTime;
transform.position = position;

33.
[beta]
非推奨:線形補間(Linear Interpolation)で実装
public class Missile : MonoBehaviour {
Vector3 position;
Transform target;
float period;
float total_period;
void Update() {
position = Vector3.Lerp(
target_tfm_.position,
initial_position,
period/total_period);
period -= Time.deltaTime;
if (period < 0f) {
return;
}

34.

発射する方向 v 初速を与える

35.

応用:初速を加える

36.

応用2:スクロールを加える

37.

応用3:発射直後に揺らぎを加える

38.

応用4:着弾時刻をずらす

39.

敵が発射する場合 回避不能!

40.

加速度に上限を設ける public class Missile : MonoBehaviour { Vector3 velocity; Vector3 position; Transform target; void Update() { … if (acceleration.magnitude > 100f) { acceleration = acceleration.normalized * 100f; } … 回避可能

41.

必中とゲーム ・敵が避けると・・ →ではどうすればよかったのか(不満) ・ロックオンした時点で駆け引きは終わりにしよう ロックオンは駆け引きになるが 命中でゲームを作るのは難しい

42.

“ 誘導ミサイルの実装 ”

43.

角度制御がキモ 前進は等速でもよい

44.
[beta]
非推奨:Quaternion.Lerp で対応
target_position:目標位置
void FixedUpdate() {
var diff = target_position - transform.position;
var target_rot = Quaternion.LookRotation(diff);
transform.rotation = Quaternion.Lerp(transform.rotation,
target_rot, 0.1f);
}

ほぼ同じ動作を実現できる
が応用が狭い

45.
[beta]
バネトルク

target_position:目標位置
ratio:バネ係数

void FixedUpdate() {
var diff = target_position - transform.position;
var target_rot = Quaternion.LookRotation(diff);
var q = target_rot * Quaternion.Inverse(transform.rotation);
var torque = new Vector3(q.x, q.y, q.z) * ratio;
GetComponent<Rigidbody>().AddTorque(torque);
}

torque:角度差に比例したトルク
(厳密には比例ではない)

46.

バネトルク 詳しくは [Unite 2017 Tokyo] スマートフォンでどこまでできる? 3Dゲームをぐりぐり動かすテクニック講座 講演資料 https://www.slideshare.net/UnityTechnologiesJapan/unite-2017-tokyo3d-76689196 講演動画 https://www.youtube.com/watch?v=6EtTI5xC524&feature=youtu.be

47.

バネトルク適用例

48.

主要パラメータ バネ係数 角度ドラッグ 速度

50.

“ トレイルの描画 ”

51.

〜便利なテクニック〜 アルファゼロ アルファゼロ テクスチャの縁の1ドットは完全透明にする

52.

アルファゼロなし アルファゼロあり

53.

トレイルねじれ問題

54.

トレイルねじれが起きやすい条件 ノード間の間隔が狭い トレイルの幅が広い 根本的な解決はかなり難しい

55.

スクリーンに 投影された 2D座標の内積を 折り返し度とみなし 透明度に反映する 実装時のメモ

56.

z スクリーン上の状態を調査 スクリーン 射影行列の 理解が必要 折り返しが発生する度合いで透明度を与える x

57.

トレイルねじれ問題 対策前 対策後

58.

“ 昔々、あるところに・・・ ”

59.

厳しい制約のハードウェア 整数しか使えない! 割り算が重すぎる!

60.

厳しい制約のハードウェア 整数しか使えない! 4096を1.0として扱う (固定小数点方式) 掛け算の度に4096で割る (12bitシフトは高速) 割り算が重すぎる!

61.

厳しい制約のハードウェア 整数しか使えない! 4096を1.0として扱う (固定小数点方式) 掛け算の度に4096で割る (12bitシフトは高速) 割り算が重すぎる! 使わない

62.

再掲 基本:運動方程式の実装例 public class Missile : MonoBehaviour { Vector3 velocity; Vector3 position; void Update() { var acceleration = Vector3.zero; } } velocity += acceleration * Time.deltaTime; position += velocity * Time.deltaTime; transform.position = position;

63.

第一の工夫 Δt は 1 でいい そのかわり1秒を60とする

64.

第一の工夫 Δt は 1 でいい そのかわり1秒を60とする 「秒」は地球の自転が基準 どうでもいい!

65.

第一の工夫 Δt は 1 でいい そのかわり1秒を60とする 「秒」は地球の自転が基準 どうでもいい! velocity += acceleration * Time.deltaTime; position += velocity * Time.deltaTime; velocity += acceleration; position += velocity; 足し算のみで運動方程式

66.

再掲 1 2 d = v t + at 2 a= 速度 v の物体が vt ) 2(d 2 t t 秒後に d 進むための加速度a

67.

割り算の絶望 a= vt ) 2(d 2 t diff = target_position - position; acceleration += (diff - velocity*period)*2 /(period*period); 我々には知恵がある

68.

再掲 v a d a= vt ) 2(d 2 t t は到達までの時間 常に加速度を計算しなおす

69.

時間は1フレームで1変化する 現在時刻 32 常に加速度を計算しなおす 到達時刻 0

70.

第二の工夫 現在時刻 32 常に加速度を計算しなおす 16 到達時刻 8 4 加速度を計算しなおすのは2のべき乗時刻のみ 2 1 0

71.

第二の工夫 現在時刻 32 常に加速度を計算しなおす 16 到達時刻 8 4 加速度を計算しなおすのは2のべき乗時刻のみ 割り算をシフト演算で実行可能 2 1 0

72.

シフト演算子で置き換える diff = target_position - position; acceleration += (diff - velocity*period)*2 /(period*period); diff = target_position - position; acceleration += (diff - velocity<<shift)<<1 >>(shift+shift);

73.

命中の直前は同等だが動作は変わっている 追尾性能低 32 追尾性能高 16 最適化とは呼べない 8 4 2 1 0

74.

毎フレーム計算 間引き計算

75.

神は二階微分に宿る

76.

“ iPhone6で動くデモを作ろう 〜コンピュートシェーダ応用〜 ”

77.

コンピュートシェーダはGPUにある計算資源 CPU GPU

78.

コンピュートシェーダを実行するふたつの関数 材料はこれです SetData CPU GPU Dispatch 実行よろしくです 基本はこれだけ

79.

まちがった理解 よろしく CPU りょうかい できたよ GPU ども こういう仕組みも作れるが、普通はこうしない

80.

正しい理解(特にゲームエンジン) CPU GPU 命令を発行したら 発行 CPUの仕事は完了 CPUはGPUの結果を待たない 取得 計算 命令を取得して GPUの仕事開始

81.

描画 通常のシェーダも同じ流れ CPU GPU 命令を発行したら 発行 CPUの仕事は完了 CPUはGPUの結果を待たない 取得 計算 命令を取得して GPUの仕事開始

82.

描画 計算のタイミングはズレている CPU GPU 計算 発行 取得 大丈夫なのか? 計算

83.

コンピュートシェーダの実行 1フレーム CPU SetData Dispatch RenderThread 描画 CPU GPU 実行 CPUで 登録した順番 で実行

84.

実行タイミングは通常シェーダの前 1フレーム SetData Dispatch CPU 描画 CPU GPU 実行 通常シェーダ

85.

CPUで実行しても通常シェーダに渡されるデータは同じ 1フレーム CPU 実行 描画 CPU GPU 通常シェーダ けっきょくCPUでもGPUでも同じ

86.

コンピュートシェーダの記述 [numthreads(8, 8, 8)] void exec() { // 処理 } 8x8x8=512並列で実行される ややこしい!

87.

そもそも、同じ処理の繰り返しを並列化したい for (int x = 0; x < 8; ++x) { for (int y = 0; y < 8; ++y) { for (int z = 0; z < 8; ++z) { // 処理 } } } この処理中、x, y, zが使われる「だろう」

88.

登録した関数は並列実行される [numthreads(8, 8, 8)] void exec(uint3 id : SV_DispatchThreadID) { // 処理 } x, y, z に相当する情報を引数idから取得できる

89.

やりたいことが一重ループなら2番目3番目を1に [numthreads(512, 1, 1)] void exec(uint3 id : SV_DispatchThreadID) { // id から実行中のスレッドがわかる } 並列上限はデバイスによる(iPhoneは512が多い)

90.

要するにコンピュートシェーダは GPU 関数が512並列で動く 512 …

91.

誘導ミサイルを実装しよう! GPU 512 … 目標:ミサイルの情報をCPUに置かない

92.

ミサイルバッファ 位置(x, y, z) ミサイル 姿勢(Quaternion) 角速度(x, y, z) GPU ミサイルごとに 異なる値 512 並列で動作するぶんのデータ

93.

ミサイルバッファ 位置(x, y, z) ミサイル 姿勢(Quaternion) 角速度(x, y, z) GPU 512 } 並列で動作するぶんのデータ ミサイル(512) 位置(x, y, z) 姿勢(Quaternion) 角速度(x, y, z)

94.

〜課題その1〜 ターゲットの位置情報が必要 それはCPUで毎フレーム更新される

95.

ターゲットバッファ追加 ミサイル(512) 位置(x, y, z) GPU 姿勢(Quaternion) 角速度(x, y, z) NEW! ターゲット + 256 位置(x, y, z)

96.

ターゲットバッファへのIDを追加 ターゲット ミサイル(512) 位置(x, y, z) GPU 姿勢(Quaternion) 角速度(x, y, z) 256 ターゲットID[0-255] NEW! GPUでターゲットの位置がわかる 位置(x, y, z)

97.

ターゲットバッファをCPUで更新 ターゲット 位置(x, y, z) CPU 256 SetData SetDataで毎フレームGPUに送る GP

98.

〜課題その2〜 ミサイルはひとつずつ発射される が、GPUは常に512並列で計算する

99.

生成処理をGPUで記述する NEW! GPU 1 SetData ミサイル生成 Dispatch 2 SetData ミサイル運動 Dispatch 別プログラムも同じバッファにアクセス可

100.

ミサイル生成バッファ 生成 初期位置(x, y, z) 初期角度(Quaternion) ミサイルID[0-511] GPU 32 未使用のミサイルIDを渡す

101.

ミサイル状態バッファをCPUで管理 ミサイル状態 生死 CPU 512 生死はCPUで管理せざるを得ない

102.

発射命令が来た ミサイル状態 CPU 512 使用可能なIDを検索 生成バッファに充填 1フレームの最大発射可能数が32 生成 32

103.

生成バッファに無効フラグ 初期位置 CPU 32 生成 有効 有効 無効 無効 無効 無効 無効 無効 初期角度 ミサイルID[0-511] 有効/無効 SetData 無効 SetData して生成プログラムを Dispatch GP

104.

〜便利なテクニック〜 初期位置 生成 初期角度 ミサイルID[0-511] 生成時に 乱数を格納しておく 有効/無効 32 さまざまな場面で活躍 乱数 NEW!

105.

〜課題その3〜 ミサイル(Mesh)の描画 情報はGPUにしかない

106.

Graphics.DrawMeshInstancedIndirectで描画 生存ミサイル 15 210 5 33 45 ミサイル状態 CPU 512 512 SetBuffer ミサイルバッファをGPUで参照、IDリストのみ送る GP

107.

Graphics.DrawMeshInstancedIndirectで描画 ミサイル状態 CPU 生存ミサイル 15 210 5 33 45 SetData 注:ここの実装は最終的にソートで置き換えられます 512 512 ミサイルバッファがGPUから参照可能、IDリストのみ送る GP

108.

トレイルもGraphics.DrawMeshInstancedIndirect ミサイル(512) 位置(x, y, z) 姿勢(Quaternion) 角速度(x, y, z) GPU NEW! 軌跡インデクス(512) NEW! インデクス[0-31] 軌跡(512) 位置(x, y, z)×32 32 ミサイルごとに軌跡バッファを保持

109.

トレイルが完全消滅するまで処理は続く 全期間で「ミサイル生存」とみなす

110.

〜ここらで確認〜 ミサイル×512の運動(含む物理シミュ)を 計測してみた

111.

〜ここらで確認〜 ミサイル×512の運動(含む物理シミュ)を 計測してみた iPhone6で約50マイクロ秒 GPUヤバイ

112.

コンピュートシェーダの難しさ 最初の動作確認までが長い 簡易シミュレータを作りましょう

113.

〜課題その4〜 ターゲットの消滅 ミサイルよりも先に消滅する可能性

114.

ターゲットバッファに死亡時刻を追加 ターゲット 位置(x, y, z) 死亡時刻 GPU 256 NEW! 現在時刻を毎フレーム送る 現在時刻ー死亡時刻=死亡経過時間 (再)ターゲットバッファはCPUからSetDataされる

115.

ターゲットが死亡時の処理 ターゲット 位置(x, y, z) 死亡時刻 GPU NEW! 256 死亡経過が正なら加速度を無効にする

116.

〜便利なテクニック〜 絶対時刻で管理する 多くの場面でカウンタよりも便利

117.

〜通常の記述〜 ・最初にゼロを入れておく ・増加して処理 ・それぞれ更新が必要 →ReadOnlyにできない class Foo { float time; void Update() { if (Current - time > 10f) { // 処理 } } } class Foo { float time; void Update() { time += Time.deltaTime; if (time > 10f) { // 処理 } } } 〜並列処理向きの記述〜 ・最初に現在時刻を入れておく ・現在時刻(Current)を参照する ・現在時刻は毎フレーム更新 →ReadOnlyにできる

118.

〜課題その5〜 ターゲットに命中を通知 どうしてもCPUにデータを戻す必要がある

119.

データを取得するのはこれ

120.

データを取得するのはこれ ちょっと待て

121.

(再掲)コンピュートシェーダの実行タイミング 1フレーム SetData Dispatch CPU CPU GPU 実行 GPUからデータを取得するGetDataは何をするのか

122.

GPUは前のフレームのコマンドを実行中 CPU CPU GPU

123.

GetData GetDataを呼ぶ CPU CPU GPU GPUは実行中

124.

GetData GPUが完了するのを待って取得 CPU CPU GPU その間CPUは停止!

125.

GetData GPUが完了するのを待って取得 CPU CPU GPU その間CPUは停止! 絶 ! 望

126.

2018.1&Windowsにて(他のプラットフォームは順次) CPUを止めずに非同期リクエストでバッファを取得

127.

運動プログラムで結果バッファを作成 ミサイル(512) 軌跡(512) 位置(x, y, z) 位置(x, y, z)×32 姿勢(Quaternion) 時刻×32 軌跡インデクス(512) インデクス[0-31] GPU ターゲット消失 命中 自然消滅 角速度(x, y, z) NEW! 結果(512) packed(4bytes) 爆発距離 死因など必要な情報を格納 ターゲットID

128.

運動プログラムで結果バッファを作成 ミサイル(512) 軌跡(512) 位置(x, y, z) 位置(x, y, z)×32 姿勢(Quaternion) 時刻×32 軌跡インデクス(512) インデクス[0-31] GPU ターゲット消失 命中 自然消滅 角速度(x, y, z) NEW! 結果(512) packed(4bytes) 爆発距離 ターゲットID カメラからの距離を格納して効果音再生に使用 死因など必要な情報を格納

129.

結果バッファを使用して状態バッファを更新 NEW! ミサイル状態 結果 生死 CPU 512 結果 512 死んだミサイルを再利用可能に GP

130.

〜課題その6〜 描画バウンド どうせ描画がボトルネックになる

131.

〜コンピュートシェーダの能力を活かす作戦〜 ミサイルの総数を16倍の8192発にする 表示は1024発に限定

132.

〜コンピュートシェーダの能力を活かす作戦〜 ミサイルの総数を16倍の8192発にする 表示は1024発に限定 優先度を計算 Frustumの外側の優先度を最低に 近くのミサイルの優先度を高く

133.

〜コンピュートシェーダの能力を活かす作戦〜 ミサイルの総数を16倍の8192発にする 表示は1024発に限定 優先度を計算 Frustumの外側の優先度を最低に 近くのミサイルの優先度を高く 優先度に従ってソートする

134.

ミサイルの総数を16倍にする GPUのnumthreadsは上げられない CPU cshader.Dispatch(kernel, 16, 1, 1); Dispatchの引数で16倍に GPU処理時間は16倍になる(と予想される)

135.

軌跡を含むすべての点で GPU Frustum6平面の内外判定する

136.

平面 ax + by + cz + d = 0 すべての点が平面の裏側

137.

平面 ax + by + cz + d = 0 すべての点が平面の裏側 すべての点で ax + by + cz + d が負

138.

平面 ax + by + cz + d = 0 すべての点が平面の裏側 すべての点で ax + by + cz + d が負 ax + by + cz + d の最大値が負

139.

平面 ax + by + cz + d = 0 すべての点が平面の裏側 すべての点で ax + by + cz + d が負 ax + by + cz + d の最大値が負 ax + by + cz + d の最大値を出せば良い

140.

平面 ax + by + cz + d = 0 すべての点が平面の裏側 すべての点で ax + by + cz + d が負 ax + by + cz + d の最大値が負 ax + by + cz + d の最大値を出せば良い ax, by, cz それぞれの最大値を足せば良い

141.

ax + by + cz + d a 0 a<0 x の最大値で axが最大 のとき: x の最小値で axが最大 のとき: (xの最大値) a⇥ つまり axの最大値は の大きい方 a⇥(xの最小値) by, cz も同様

142.

xの最小値 xの最大値 x

143.

内外判定のやりかた 1・点群のxyzの最大値・最小値を調査 xmax, xmin, ymax, ymin, (これが Bounding Box) zmax, zmin, 2・平面式と乗算して各項の最大値を得る ax = max(xmax*plane.x, xmin*plane.x); by = max(ymax*plane.y, ymin*plane.y); cz = max(zmax*plane.z, zmin*plane.z); 3・平面との距離の最大値を得る d = ax + by + cz + plane.w

144.

ソートバッファを新設 ソート GPU NEW! 優先度 ミサイルID[0-8192] 軌跡インデクス(8192) インデクス[0-31] 軌跡(8192) 位置(x, y, z)×32 時刻×32 ミサイル(8192) 位置(x, y, z) 8192 姿勢(Quaternion) 角速度(x, y, z) 結果(8192) packed(4bytes)

145.

ソートをGPUで記述する GPU 1 SetData ミサイル生成 Dispatch 2 SetData ミサイル運動 Dispatch 3 Dispatch ソート bitonic sortを使用 NEW!

146.
[beta]
参考

#pragma kernel missile_sort
RWStructuredBuffer<SortData> cbuffer_missile_sort_key_list;
#define MISSILE_NUM

( 512*16 )

// must be more than THREAD_X*2

[numthreads(512, 1, 1)]
void missile_sort(uint gidx : SV_GroupIndex) {
for (uint block = 2; block <= MISSILE_NUM; block <<= 1) { // major step
for (uint step = block >> 1; step > 0; step >>= 1) { // minor step
uint maskL = (uint)step - 1;
uint maskH = 0xFFFFFFFF ^ maskL;
for (uint i = gidx; i < MISSILE_NUM >> 1; i += 512) {
uint idx = ((i&maskH)<<1) | (i&maskL);
SortData v1 = cbuffer_missile_sort_key_list[idx];
SortData v2 = cbuffer_missile_sort_key_list[idx + step];
int isAscend = (idx&block) != 0 ? 1 : 0;
int isBigger = (v1.packed_>>16) > (v2.packed_>>16) ? 1 : 0;
if (isAscend ^ isBigger) {
cbuffer_missile_sort_key_list[idx] = v2;
cbuffer_missile_sort_key_list[idx+step] = v1;
}
}
DeviceMemoryBarrierWithGroupSync();
}
}
}

参考: https://shobomaru.wordpress.com/2012/11/27/parallel-processing-of-bitonic-sort/

147.
[beta]
ソートの高速化
for (uint i = gidx; i < MISSILE_NUM >> 1; i += 512) {
uint idx = ((i&maskH)<<1) | (i&maskL);
SortData v1 = cbuffer_missile_sort_key_list[idx];
SortData v2 = cbuffer_missile_sort_key_list[idx + step];
int isAscend = (idx&block) != 0 ? 1 : 0;
int isBigger = (v1.packed_>>16) > (v2.packed_>>16) ? 1 : 0;
if (isAscend ^ isBigger) {

// 条件一致で

cbuffer_missile_sort_key_list[idx] = v2; // 交換
cbuffer_missile_sort_key_list[idx+step] = v1; // 交換
}

メモリアクセスを可能な限り減らす

148.

ソートされたシーン

149.

〜最終課題〜 GetData 絶 ! 望 おまえiPhone6で動かすゆうたやろ

150.

GetData (再掲)GetDataの絶望 CPU CPU GPU 我々には知恵がある

151.

Instrumentsで調査 GetData呼ばない GetData呼ぶ ピッタリ 待ちが発生

152.

ガクガク

153.

GetData CPU CPU GPU

154.

GetData 呼び出すタイミングをずらせば・・・ CPU CPU GPU 待ちを最小限にできるはず

155.

絶妙のタイミング!

156.

GetData 可能は可能だが、安定させるのは難しい CPU CPU GPU デモでは2フレーム毎に2フレーム分のデータを取得

158.

おしまい