Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

5,829 views

Published on

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

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

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

Published in: Technology

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

  1. 1. 誘導ミサイル完全マスター ユニティ・テクノロジーズ・ジャパン 2018/05/08DAY2 安原 祐二
  2. 2. “ ” 問題
  3. 3. Frustum(視錐台)の外側の物体の描画を省略する やりかたわかりますか? 通常はUnityがやってくれる 〜Frustum Culling〜
  4. 4. 平面平面と点の関係 点 x, y, z
  5. 5. 平面の式 例 a b cx + y + z+ = 0d で平面が定まる, , ,a b c d 2x + y + z+ = 03 4 5
  6. 6. 左辺は平面からの距離を意味する 「平面からの距離がゼロ」を満たす の集合x, y, z 平面の式とは ただしこの距離は正規化されていない 2x + y + z+ = 03 4 5
  7. 7. は平面に垂直なベクトル で両辺を割って正規化 a b cx + y + z+ = 0d ( , , )a b c p 2 + 2 + 2a b c
  8. 8. 例 は平面に垂直なベクトル で両辺を割って正規化 p 22 + 32 + 42 = p 29 a b cx + y + z+ = 0d 2x + y + z+ = 03 4 5 ( , , )a b c p 2 + 2 + 2a b c x + y + z+ = 0 2 p 29 3 p 29 4 p 29 5 p 29
  9. 9. 点と平面の距離 平面 距離 点
  10. 10. 点と平面の距離 p(5, 6, 7) 平面 x + y + z+ = 0 2 p 29 3 p 29 4 p 29 5 p 29 距離 2 p 29 3 p 29 4 p 29 5 p 29 = ⇥5 + ⇥6 + ⇥7+ 距離 点
  11. 11. 距離には符号がある +- 平面の表にあるか裏にあるかがわかる p(5, 6, 7) 平面 距離 点 表裏 x + y + z+ = 0 2 p 29 3 p 29 4 p 29 5 p 29
  12. 12. 球の場合 点と平面の距離が「-r以上」か +- -r 表裏
  13. 13. 視錐台(Frustum)の6つの平面を得るには プロジェクション行列から取得
  14. 14. 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 } 参考:プロジェクション行列から6平面を取得(正規化は省略)
  15. 15. 6平面のひとつでも裏側にあれば、描画の必要ナシ Frustum Culling 完成
  16. 16. “ ” 誘導ミサイル完全マスター
  17. 17. 神は○○○○に宿る
  18. 18. 誘導ミサイル 追尾レーザー
  19. 19. 追尾レーザー実装例
  20. 20. 追尾レーザー実装例その2
  21. 21. 誘導ミサイル実装例 https://github.com/Unity-Technologies/MissilesPerfectMaster
  22. 22. “ ” 追尾レーザーの実装
  23. 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. 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. 25. 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. 26. ttd v a= 2 + 1 2 加速度・速度・時間・距離の関係
  27. 27. tt t d v v a= 2 + 1 2 :距離 :速度 :時間 :加速度a d 加速度・速度・時間・距離の関係
  28. 28. = 2( )d tv a t2 ttd v a= 2 + 1 2 速度 の物体が 秒後に 進むための加速度v t d a
  29. 29. 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; = 2( )d tv a t2
  30. 30. = 2( )d tv a t2 v d a 目標は動いているので・・ 常に加速度を計算しなおす
  31. 31. 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. 32. 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. 33. 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; } 非推奨:線形補間(Linear Interpolation)で実装
  34. 34. 発射する方向 v 初速を与える
  35. 35. 応用:初速を加える
  36. 36. 応用2:スクロールを加える
  37. 37. 応用3:発射直後に揺らぎを加える
  38. 38. 応用4:着弾時刻をずらす
  39. 39. 敵が発射する場合 回避不能!
  40. 40. 回避可能 public class Missile : MonoBehaviour { Vector3 velocity; Vector3 position; Transform target; void Update() { … if (acceleration.magnitude > 100f) { acceleration = acceleration.normalized * 100f; } … 加速度に上限を設ける
  41. 41. 必中とゲーム ・敵が避けると・・  →ではどうすればよかったのか(不満) ・ロックオンした時点で駆け引きは終わりにしよう ロックオンは駆け引きになるが 命中でゲームを作るのは難しい
  42. 42. “ ” 誘導ミサイルの実装
  43. 43. 角度制御がキモ 前進は等速でもよい
  44. 44. void FixedUpdate() { var diff = target_position - transform.position; var target_rot = Quaternion.LookRotation(diff); transform.rotation = Quaternion.Lerp(transform.rotation, target_rot, 0.1f); } 非推奨:Quaternion.Lerp で対応 target_position:目標位置 ほぼ同じ動作を実現できる が応用が狭い
  45. 45. 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); } バネトルク target_position:目標位置 torque:角度差に比例したトルク (厳密には比例ではない) ratio:バネ係数
  46. 46. バネトルク https://www.slideshare.net/UnityTechnologiesJapan/unite-2017-tokyo3d-76689196 https://www.youtube.com/watch?v=6EtTI5xC524&feature=youtu.be 講演資料 講演動画 詳しくは  [Unite 2017 Tokyo]  スマートフォンでどこまでできる?  3Dゲームをぐりぐり動かすテクニック講座
  47. 47. バネトルク適用例
  48. 48. 主要パラメータ バネ係数 角度ドラッグ 速度
  49. 49. “ ” トレイルの描画
  50. 50. 〜便利なテクニック〜 アルファゼロ アルファゼロ テクスチャの縁の1ドットは完全透明にする
  51. 51. アルファゼロありアルファゼロなし
  52. 52. トレイルねじれ問題
  53. 53. トレイルねじれが起きやすい条件 根本的な解決はかなり難しい ノード間の間隔が狭い トレイルの幅が広い
  54. 54. 実装時のメモ スクリーンに 投影された 2D座標の内積を 折り返し度とみなし 透明度に反映する
  55. 55. スクリーン上の状態を調査 折り返しが発生する度合いで透明度を与える スクリーン z x 射影行列の 理解が必要
  56. 56. トレイルねじれ問題 対策前 対策後
  57. 57. “ ” 昔々、あるところに・・・
  58. 58. 厳しい制約のハードウェア 整数しか使えない! 割り算が重すぎる!
  59. 59. 厳しい制約のハードウェア 整数しか使えない! 割り算が重すぎる! 4096を1.0として扱う (固定小数点方式) 掛け算の度に4096で割る (12bitシフトは高速)
  60. 60. 厳しい制約のハードウェア 整数しか使えない! 割り算が重すぎる! 4096を1.0として扱う (固定小数点方式) 掛け算の度に4096で割る (12bitシフトは高速) 使わない
  61. 61. 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; } } 基本:運動方程式の実装例 再掲
  62. 62. 第一の工夫 Δt は 1 でいい そのかわり1秒を60とする
  63. 63. 第一の工夫 Δt は 1 でいい そのかわり1秒を60とする 「秒」は地球の自転が基準 どうでもいい!
  64. 64. velocity += acceleration * Time.deltaTime; position += velocity * Time.deltaTime; 第一の工夫 Δt は 1 でいい そのかわり1秒を60とする 「秒」は地球の自転が基準 velocity += acceleration; position += velocity; 足し算のみで運動方程式 どうでもいい!
  65. 65. = 2( )d tv a t2 ttd v a= 2 + 1 2 速度 の物体が 秒後に 進むための加速度v t d a 再掲
  66. 66. = 2( )d tv a t2 diff = target_position - position; acceleration += (diff - velocity*period)*2 /(period*period); 我々には知恵がある 割り算の絶望
  67. 67. = 2( )d tv a t2 v d a 常に加速度を計算しなおす 再掲 は到達までの時間t
  68. 68. 時間は1フレームで1変化する 到達時刻現在時刻 32 0 常に加速度を計算しなおす
  69. 69. 常に加速度を計算しなおす 第二の工夫 32 到達時刻 0 現在時刻 16 8 4 2 1 加速度を計算しなおすのは2のべき乗時刻のみ
  70. 70. 常に加速度を計算しなおす 第二の工夫 32 到達時刻現在時刻 16 8 4 2 1 割り算をシフト演算で実行可能 0 加速度を計算しなおすのは2のべき乗時刻のみ
  71. 71. diff = target_position - position; acceleration += (diff - velocity*period)*2 /(period*period); diff = target_position - position; acceleration += (diff - velocity<<shift)<<1 >>(shift+shift); シフト演算子で置き換える
  72. 72. 命中の直前は同等だが動作は変わっている 32 016 8 4 2 1 追尾性能低 追尾性能高 最適化とは呼べない
  73. 73. 毎フレーム計算 間引き計算
  74. 74. 神は二階微分に宿る
  75. 75. “ ” iPhone6で動くデモを作ろう 〜コンピュートシェーダ応用〜
  76. 76. CPU GPU コンピュートシェーダはGPUにある計算資源
  77. 77. CPU GPU コンピュートシェーダを実行するふたつの関数 SetData Dispatch 材料はこれです 実行よろしくです 基本はこれだけ
  78. 78. CPU GPU よろしく りょうかい できたよ ども まちがった理解 こういう仕組みも作れるが、普通はこうしない
  79. 79. CPU GPU 正しい理解(特にゲームエンジン) 発行 取得 計算命令を発行したら CPUの仕事は完了 命令を取得して GPUの仕事開始 CPUはGPUの結果を待たない
  80. 80. CPU GPU 通常のシェーダも同じ流れ 発行 取得 描画 計算 CPUはGPUの結果を待たない 命令を発行したら CPUの仕事は完了 命令を取得して GPUの仕事開始
  81. 81. CPU GPU 計算のタイミングはズレている 発行計算 計算 描画 取得 大丈夫なのか?
  82. 82. CPU CPU 1フレーム GPU コンピュートシェーダの実行 SetData Dispatch CPUで登録した順番で実行 RenderThread 描画 実行
  83. 83. CPU CPU 1フレーム GPU 実行タイミングは通常シェーダの前 SetData Dispatch 通常シェーダ 描画 実行
  84. 84. CPU CPU 1フレーム GPU CPUで実行しても通常シェーダに渡されるデータは同じ けっきょくCPUでもGPUでも同じ 描画 通常シェーダ 実行
  85. 85. [numthreads(8, 8, 8)] void exec() { // 処理 } コンピュートシェーダの記述 8x8x8=512並列で実行される ややこしい!
  86. 86. for (int x = 0; x < 8; ++x) { for (int y = 0; y < 8; ++y) { for (int z = 0; z < 8; ++z) { // 処理 } } } そもそも、同じ処理の繰り返しを並列化したい この処理中、x, y, zが使われる「だろう」
  87. 87. [numthreads(8, 8, 8)] void exec(uint3 id : SV_DispatchThreadID) { // 処理 } 登録した関数は並列実行される x, y, z に相当する情報を引数idから取得できる
  88. 88. [numthreads(512, 1, 1)] void exec(uint3 id : SV_DispatchThreadID) { // id から実行中のスレッドがわかる } やりたいことが一重ループなら2番目3番目を1に 並列上限はデバイスによる(iPhoneは512が多い)
  89. 89. GPU 要するにコンピュートシェーダは 関数が512並列で動く … 512
  90. 90. 誘導ミサイルを実装しよう! GPU 目標:ミサイルの情報をCPUに置かない … 512
  91. 91. ミサイルバッファ GPU ミサイル 512 位置(x, y, z) 姿勢(Quaternion) 角速度(x, y, z) 並列で動作するぶんのデータ ミサイルごとに 異なる値
  92. 92. ミサイルバッファ GPU ミサイル 512 位置(x, y, z) 姿勢(Quaternion) 角速度(x, y, z) ミサイル(512) 位置(x, y, z) 姿勢(Quaternion) 角速度(x, y, z) } 並列で動作するぶんのデータ
  93. 93. 〜課題その1〜 ターゲットの位置情報が必要 それはCPUで毎フレーム更新される
  94. 94. ターゲットバッファ追加 GPU ターゲット 256 位置(x, y, z)ミサイル(512) 位置(x, y, z) 姿勢(Quaternion) 角速度(x, y, z) + NEW!
  95. 95. ターゲットバッファへのIDを追加 GPU GPUでターゲットの位置がわかる ミサイル(512) 位置(x, y, z) 姿勢(Quaternion) 角速度(x, y, z) ターゲットID[0-255] ターゲット 256 位置(x, y, z) NEW!
  96. 96. CPU ターゲットバッファをCPUで更新 SetDataで毎フレームGPUに送る ターゲット 256 GPSetData 位置(x, y, z)
  97. 97. 〜課題その2〜 ミサイルはひとつずつ発射される が、GPUは常に512並列で計算する
  98. 98. 生成処理をGPUで記述する GPU SetData Dispatch ミサイル生成 SetData Dispatch ミサイル運動 NEW! 別プログラムも同じバッファにアクセス可 1 2
  99. 99. ミサイル生成バッファ GPU 未使用のミサイルIDを渡す 生成 32 初期位置(x, y, z) 初期角度(Quaternion) ミサイルID[0-511]
  100. 100. CPU ミサイル状態バッファをCPUで管理 生死はCPUで管理せざるを得ない ミサイル状態 512 生死
  101. 101. CPU 発射命令が来た 1フレームの最大発射可能数が32 使用可能なIDを検索 512 ミサイル状態 生成 32 生成バッファに充填
  102. 102. 生成バッファに無効フラグ SetData して生成プログラムを Dispatch 生成 有効 有効 無効 無効 無効 無効 無効 無効 無効 32 CPU GPSetData 初期位置 初期角度 ミサイルID[0-511] 有効/無効
  103. 103. 〜便利なテクニック〜 生成時に 乱数を格納しておく さまざまな場面で活躍 生成 初期位置 初期角度 ミサイルID[0-511] 有効/無効 乱数 32 NEW!
  104. 104. 〜課題その3〜 ミサイル(Mesh)の描画 情報はGPUにしかない
  105. 105. Graphics.DrawMeshInstancedIndirectで描画 CPU GPSetBuffer 512 ミサイル状態 512 生存ミサイル 15 210 5 33 45 ミサイルバッファをGPUで参照、IDリストのみ送る
  106. 106. Graphics.DrawMeshInstancedIndirectで描画 CPU GPSetData 512 ミサイル状態 512 生存ミサイル 15 210 5 33 45 ミサイルバッファがGPUから参照可能、IDリストのみ送る 注:ここの実装は最終的にソートで置き換えられます
  107. 107. GPU ミサイルごとに軌跡バッファを保持 ミサイル(512) 位置(x, y, z) 姿勢(Quaternion) 角速度(x, y, z) 軌跡(512) 位置(x, y, z)×32 軌跡インデクス(512) インデクス[0-31] NEW! NEW! 32 トレイルもGraphics.DrawMeshInstancedIndirect
  108. 108. トレイルが完全消滅するまで処理は続く 全期間で「ミサイル生存」とみなす
  109. 109. 〜ここらで確認〜 ミサイル×512の運動(含む物理シミュ)を 計測してみた
  110. 110. 〜ここらで確認〜 ミサイル×512の運動(含む物理シミュ)を 計測してみた GPUヤバイ iPhone6で約50マイクロ秒
  111. 111. コンピュートシェーダの難しさ 最初の動作確認までが長い 簡易シミュレータを作りましょう
  112. 112. 〜課題その4〜 ターゲットの消滅 ミサイルよりも先に消滅する可能性
  113. 113. ターゲットバッファに死亡時刻を追加 GPU (再)ターゲットバッファはCPUからSetDataされる ターゲット 256 位置(x, y, z) 死亡時刻 NEW! 現在時刻を毎フレーム送る 現在時刻ー死亡時刻=死亡経過時間
  114. 114. ターゲットが死亡時の処理 GPU 死亡経過が正なら加速度を無効にする ターゲット 256 位置(x, y, z) 死亡時刻 NEW!
  115. 115. 〜便利なテクニック〜 絶対時刻で管理する 多くの場面でカウンタよりも便利
  116. 116. class Foo { float time; void Update() { time += Time.deltaTime; if (time > 10f) { // 処理 } } } class Foo { float time; void Update() { if (Current - time > 10f) { // 処理 } } } 〜通常の記述〜 〜並列処理向きの記述〜 ・最初に現在時刻を入れておく ・現在時刻(Current)を参照する ・現在時刻は毎フレーム更新  →ReadOnlyにできる ・最初にゼロを入れておく ・増加して処理 ・それぞれ更新が必要  →ReadOnlyにできない
  117. 117. 〜課題その5〜 ターゲットに命中を通知 どうしてもCPUにデータを戻す必要がある
  118. 118. データを取得するのはこれ
  119. 119. データを取得するのはこれ ちょっと待て
  120. 120. CPU CPU 1フレーム GPU (再掲)コンピュートシェーダの実行タイミング SetData Dispatch 実行 GPUからデータを取得するGetDataは何をするのか
  121. 121. CPU CPU GPU GPUは前のフレームのコマンドを実行中
  122. 122. CPU CPU GPU GetDataを呼ぶ GPUは実行中 GetData
  123. 123. CPU CPU GPU GPUが完了するのを待って取得 その間CPUは停止! GetData
  124. 124. CPU CPU GPU GPUが完了するのを待って取得 GetData 絶望! その間CPUは停止!
  125. 125. 2018.1&Windowsにて(他のプラットフォームは順次) CPUを止めずに非同期リクエストでバッファを取得
  126. 126. 運動プログラムで結果バッファを作成 GPU 死因など必要な情報を格納 ミサイル(512) 位置(x, y, z) 姿勢(Quaternion) 角速度(x, y, z) 軌跡(512) 位置(x, y, z)×32 時刻×32 軌跡インデクス(512) インデクス[0-31] 結果(512) packed(4bytes) NEW! 爆発距離 ターゲットID ターゲット消失 命中 自然消滅
  127. 127. 運動プログラムで結果バッファを作成 GPU 死因など必要な情報を格納 ミサイル(512) 位置(x, y, z) 姿勢(Quaternion) 角速度(x, y, z) 軌跡(512) 位置(x, y, z)×32 時刻×32 軌跡インデクス(512) インデクス[0-31] 結果(512) packed(4bytes) NEW! 爆発距離 ターゲットID ターゲット消失 命中 自然消滅 カメラからの距離を格納して効果音再生に使用
  128. 128. CPU 結果バッファを使用して状態バッファを更新 死んだミサイルを再利用可能に ミサイル状態 512 生死 GP 結果 512 結果 NEW!
  129. 129. 〜課題その6〜 描画バウンド どうせ描画がボトルネックになる
  130. 130. ミサイルの総数を16倍の8192発にする 表示は1024発に限定 〜コンピュートシェーダの能力を活かす作戦〜
  131. 131. ミサイルの総数を16倍の8192発にする 表示は1024発に限定 優先度を計算 Frustumの外側の優先度を最低に 近くのミサイルの優先度を高く 〜コンピュートシェーダの能力を活かす作戦〜
  132. 132. 優先度に従ってソートする ミサイルの総数を16倍の8192発にする 表示は1024発に限定 優先度を計算 Frustumの外側の優先度を最低に 近くのミサイルの優先度を高く 〜コンピュートシェーダの能力を活かす作戦〜
  133. 133. GPU処理時間は16倍になる(と予想される) GPUのnumthreadsは上げられない ミサイルの総数を16倍にする cshader.Dispatch(kernel, 16, 1, 1); Dispatchの引数で16倍に CPU
  134. 134. 軌跡を含むすべての点で Frustum6平面の内外判定する GPU
  135. 135. 平面 ax + by + cz + d = 0 すべての点が平面の裏側
  136. 136. 平面 ax + by + cz + d = 0 すべての点で が負ax + by + cz + d すべての点が平面の裏側
  137. 137. 平面 ax + by + cz + d = 0 すべての点で が負ax + by + cz + d ax + by + cz + d の最大値が負 すべての点が平面の裏側
  138. 138. 平面 ax + by + cz + d = 0 すべての点で が負ax + by + cz + d ax + by + cz + d の最大値が負 すべての点が平面の裏側 ax + by + cz + d の最大値を出せば良い
  139. 139. 平面 ax + by + cz + d = 0 すべての点で が負ax + by + cz + d ax + by + cz + d の最大値が負 すべての点が平面の裏側 ax + by + cz + d の最大値を出せば良い ax, by, cz それぞれの最大値を足せば良い
  140. 140. ax + by + cz + d axxa 0 のとき: の最大値で が最大 axxの最小値で が最大a < 0 axつまり の最大値は xの最大値)a⇥ の大きい方 のとき: ( xの最小値)a⇥( by, czも同様
  141. 141. x xの最大値xの最小値
  142. 142. 内外判定のやりかた 1・点群のxyzの最大値・最小値を調査 xmax, xmin, ymax, ymin, zmax, zmin, 2・平面式と乗算して各項の最大値を得る d = ax + by + cz + plane.w 3・平面との距離の最大値を得る ax = max(xmax*plane.x, xmin*plane.x); by = max(ymax*plane.y, ymin*plane.y); cz = max(zmax*plane.z, zmin*plane.z); (これが Bounding Box)
  143. 143. ソートバッファを新設 GPU ミサイル(8192) 位置(x, y, z) 姿勢(Quaternion) 角速度(x, y, z) 軌跡(8192) 位置(x, y, z)×32 時刻×32 軌跡インデクス(8192) インデクス[0-31] 結果(8192) packed(4bytes) NEW! ソート 優先度 ミサイルID[0-8192] 8192
  144. 144. ソートをGPUで記述する GPU SetData Dispatch ミサイル生成 SetData Dispatch ミサイル運動 NEW! bitonic sortを使用 Dispatch ソート 1 2 3
  145. 145. #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/参考: 参考
  146. 146. ソートの高速化 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; // 交換 } メモリアクセスを可能な限り減らす
  147. 147. ソートされたシーン
  148. 148. 〜最終課題〜 GetData おまえiPhone6で動かすゆうたやろ 絶望!
  149. 149. CPU CPU GPU (再掲)GetDataの絶望 我々には知恵がある GetData
  150. 150. 待ちが発生ピッタリ GetData呼ばない GetData呼ぶ Instrumentsで調査
  151. 151. ガクガク
  152. 152. CPU CPU GPU GetData
  153. 153. CPU CPU GPU 呼び出すタイミングをずらせば・・・ 待ちを最小限にできるはず GetData
  154. 154. 絶妙のタイミング!
  155. 155. CPU CPU GPU 可能は可能だが、安定させるのは難しい デモでは2フレーム毎に2フレーム分のデータを取得 GetData
  156. 156. おしまい

×