しがないエンジニアのブログ

技術的な内容をメモ代わりにつらつら

[Unite2018] 誘導ミサイル完全マスター

公式サイト

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session10

講演者

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

SlideShare

https://www.slideshare.net/UnityTechnologiesJapan/unite-2018-tokyo/UnityTechnologiesJapan/unite-2018-tokyo

動画

https://www.youtube.com/watch?v=dOA5MHNamnc

概要

  • 誘導ミサイルのゲーム性について
  • 誘導ミサイルの実装方法
  • 描画テクニック

内容

  • Frustum Culling (視錐台)
    • Frustum Cullingの外側の物体の描画を省略する
    • やり方は? f:id:turgure:20180525113949j:plain f:id:turgure:20180525114002j:plain f:id:turgure:20180525114011j:plain


  • 追尾レーザーの実装
    • 運動方程式
      • y = vt+\frac{1}{2}at^{2}
      • a = \frac{2(d-vt)}{t^{2}}
    • 目標は常に動いている
      • 加速度を常に計算しなおす
    • 必ず命中する
    • 非推奨:線形補間(Linear Interpolation)で実装
    • 応用
      • 初速を与える
      • スクロールを加える
      • 発射直後にゆらぎを加える
      • 着弾時間をずらす
    • 敵が発射する場合
      • 加速度に上限を加える
        • 回避可能
    • 必中とゲーム
      • ロックオンしたら必ず命中させる
      • ロックオンをする
        • 的に方向を向ける
          • ここで駆け引きを作る



  • トレイルの描画
    • 便利テクニック

      • テクスチャの縁の1ドットを透明にする f:id:turgure:20180610183753j:plain
    • トレイルねじれ問題

      • 起きやすい条件
        • ノード間の間隔が狭い
        • トレイル幅が広い
          • 暫定的解決としては幅を狭くすればいい
          • 根本的解決が難しい
            • 折り返しが激しいところを透明にしてしまう


  • 重い処理をなくす方法
    • 昔の話
      • 整数しか使えない
        • 4096を1に(固定小数点方式)
        • 掛け算のたびに4096で割る(12bitシフトは高速)
      • 割り算処理が重い
        • 使わない
    • 工夫

      • \Delta t = 1にする
        • その代わり1秒を60にする
          • 足し算のみで運動方程式が作れる!
            • v += a * Time.deltatime
            • pos += v * Time.deltatime
            • v += a
            • pos += v
      • 加速度計算
        • 常に計算し直す必要がある
          • →2のべき乗時刻のみで行う
            • 割り算をシフト演算で実行可能
          • 動きが変わっているため、最適化とは言わない
          • が、ほとんど見た目は変わらない f:id:turgure:20180610184618j:plain f:id:turgure:20180610184633j:plain
    • 神は二階微分に宿る

      • 加速度を非線形にしても見た目の問題は変わらない


  • iPhone6で動くデモ〜コンピュートシェーダ応用〜
    • コンピュートシェーダを事項する2つの関数
      • SetData   CPU -> GPU
      • Dispatch   GPU -> CPU
    • CPUとGPUの正しい理解
      • CPU
        • 発行:命令を発行したら仕事完了
          • バケツに詰め込んでGPUに送る
          • CPUはGPUの結果を待たない
      • GPU

        • 取得、計算→描画 f:id:turgure:20180610191246j:plain f:id:turgure:20180610191253j:plain f:id:turgure:20180610191259j:plain
      • CPUとGPUの計算のずれ

        • CPU:SetData, Dispatch
          • CPU
            • GPU:exec, shade
        • CPUで実行しても通常シェーダに渡されるデータは同じ
    • 記述
      • 512(8x8x8)並列で実行される
      • 3重のforループの実装想定
[numthreads(8, 8, 8)]
void exec(uint3 id)

1重ループならこうする

[numthreads(512, 1, 1)]
void exec(uint3 id)

f:id:turgure:20180610191705j:plain

  • コンピュートシェーダを使った誘導ミサイル
    • 課題1:ターゲット情報
      • CPU側で毎フレーム更新される
      • ターゲットバッファをCPUで更新
        • setDataで毎フレーム更新 f:id:turgure:20180610193759j:plain
    • 課題2:ミサイルは1つずつ発射される
      • しかしGPUは512並列で実行される
        • 2つの処理に分ける f:id:turgure:20180610194521j:plain
      • ミサイルの静止状態はCPUで管理せざるを得ない
        • 1フレームの最大発射可能数を32とする
          • 生成バッファに有効・無効フラグを追加 f:id:turgure:20180610194800j:plain f:id:turgure:20180610194807j:plain
      • Tips. 乱数もGPUバッファに追加して管理 f:id:turgure:20180610194853j:plain
    • 課題3:ミサイル(Mesh)の描画
      • 情報はGPUにしかない
        • ミサイルバッファをGPUで参照、IDリストのみ送る
      • ミサイル・トレイルを描画
        • Graphics.DrawMeshInstancedIndirect
        • ミサイルごとに軌跡をバッファ
        • 爆発してもミサイルは生存状態を維持
        • 完全に処理が終了するまで f:id:turgure:20180610195103j:plain
      • 実機確認
        • ○50マイクロ秒で動く
        • △最初の動作確認までが長い
          • 簡易シミュレータを作る
    • 課題4:ターゲットの消滅
      • ミサイルよりも先に敵が消滅する場合
        • ターゲットバッファに死亡時刻を追加
        • 現在時刻を毎フレーム送る f:id:turgure:20180610195431j:plain
      • 死亡時刻と現在時刻のズレ(死亡経過時刻>0)
        • 加速度を無効
      • Tips. 時刻は絶対時刻で管理する(並列処理向き) f:id:turgure:20180610195458j:plain
    • 課題5:ターゲットに命中を通知
      • どうしてもCPUにデータを戻す必要がある
        • ComputerBuffer.GetData を使う?
          • GPU処理が終了するまでCPUは処理を停止してしまう
        • AsyncGPUReadback (Unity2018.1, Win)
          • CPUを止めずに非同期リクエストでバッファを取得
          • 運動プログラムで結果バッファを作成
            • 死因・爆破距離なども記述
            • 結果バッファからCPUの状態バッファ更新
              • 死んだミサイルを再利用 f:id:turgure:20180610195512j:plain
    • 課題6:描画バウンド
      • 描画がボトルネックになる
        • コンピュートシェーダを活かす作戦
          • ミサイル総数を16倍の8192発
            • Dispatchの引数を16(GPUの処理速度16倍)
          • 表示は1024発
          • 優先度
            • Frustumの外側の優先度を最低に
            • 近くのミサイルの優先度を高く
              • 優先度でソート
              • Tips. ソートの高速化
                • メモリアクセスを減らす f:id:turgure:20180610195915j:plain
    • 課題7:GetDataの絶望
      • CPUのGPU待ち
      • 呼び出すタイミングをずらす
      • 2フレームに2フレーム分のデータを取得
        • 可能だが、安定させるのは難しい f:id:turgure:20180610195929j:plain f:id:turgure:20180610195933j:plain