1.3K Views
September 25, 19
スライド概要
2019/9/25-6に開催されたUnite Tokyo 2019の講演スライドです。
安原 祐二(ユニティ・テクノロジーズ・ジャパン合同会社)
こんな人におすすめ
・そろそろDOTSを学んでおきたい方
・DOTSに取り組む余裕はないが現状を確認しておきたい方
・Unity.Physicsの概要を知りたい方
受講者が得られる知見
・DOTSの基本
・DOTSの内部構造
・Unity.Physicsの概要
Unityのイベント資料はこちらから:
https://www.slideshare.net/UnityTechnologiesJapan/clipboards
リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。
たのしいDOTS 〜初級から上級まで〜 ユニティ・テクノロジーズ・ジャパン 安原 祐二
DOTSとは Data Oriented Technology Stack Unityの新しい基盤
DOTSとは Data Oriented Technology Stack Unityの新しい基盤 なぜ今なのか? データ指向って?
DOTSとは Data Oriented Technology Stack Unityの新しい基盤 なぜ今なのか? 現在はプレビュー版 データ指向って? これから使いやすくなる
DOTSとは Data Oriented Technology Stack Unityの新しい基盤 なぜ今なのか? 現在はプレビュー版 データ指向って? これから使いやすくなる いますぐ始めなくていい あとで見返そう
DOTSとは Data Oriented Technology Stack Unityの新しい基盤 なぜ今なのか? 現在はプレビュー版 データ指向って? これから使いやすくなる いますぐ始めなくていい あとで見返そう この技術は おもしろい!
前半: メモリの戦い 後半: 驚異のUnity Physics
メモリの戦い 〜その歴史〜
限られた土地を使いまわそう
役所 確保(allocation) 役所は確保状況を把握 ただし中身は関知しない
役所 確保(allocation)
役所 解放を報告 解放(free)
役所 再利用
メモリも ストレージも 事情は同じ
メモリも ストレージも 事情は同じ メモリは 格段に複雑な事情 を抱えている 利用者にアドレスを渡してしまっているので・・・
アドレスが埋まっている 問題は解放
解放! !? 事故発生 歴史上、さまざまな取り組みが
C#「GCにお任せする」 解放!
C#「GCにお任せする」 解放! マネージドメモリ (managed memory) 解放を呼ばせない
ポインタとはこれ のこと マネージドメモリ (managed memory)
ポインタとはこれ のこと int* a; int マネージドメモリ (managed memory)
ゲームでこんな複雑な必要あるだろうか? マネージドメモリ (managed memory)
Unmanaged Memoryが要求される Chunk Chunk NativeContainer NativeContainer NativeContainer Chunk マネージドメモリ (managed memory) Chunk Unmanaged Memory
ネイティブコンテナのトリック 〜Unmanagedに挑戦〜
〜ネイティブコンテナ(NativeContainer)とは〜 Chunk NativeContainer Unmanagedメモリを保持する機構 ただし自分で解放する必要 Unmanaged Memory
〜ネイティブコンテナ(NativeContainer)とは〜 Chunk NativeContainer Unmanagedメモリを保持する機構 ただし自分で解放する必要 Unmanaged Memory オブジェクトではなく、アルゴリズムで確保する (生存期間をアルゴリズムで決める) 秩序は守られる!
代表例:NativeArray NativeArray structの配列 必ずstruct(値型)! class(参照型) はC#の機構により マネージドメモリになってしまう
代表例:NativeArray NativeArray structの配列 必ずstruct(値型)! class(参照型) はC#の機構により マネージドメモリになってしまう var a = NativeArray<float>(8, Allocator.Persistent) structならメモリがひとかたまりに! C#の特徴を利用
ネイティブコンテナもstruct リファレンスマニュアルより ところで struct って恐くないですか?
structの罠 var a = new Vector3(1, 2, 3); func(a); aはコピーされてfuncに渡される funcの中で変更してもaに影響なし
structの罠 var a = new Vector3(1, 2, 3); func(a); aはコピーされてfuncに渡される funcの中で変更してもaに影響なし var a = new NativeArray<float>(32); func(a); aはコピーされてfuncに渡される あれ? これは大丈夫?
ネイティブコンテナの実装は必ずポインタ! unsafe struct NativeArray<T> { void ptr; } * ポインタをコピーしても参照先は同じ var a = new NativeArray<float>(32); func(a); Unmanaged Memory funcの変更が伝わる!
ベテランでも引っかかるstructの罠 よーし ネイティブコンテナ 自作しちゃうぞー struct MyNativeArray<T> { NativeArray<T> na; int Length; }
ベテランでも引っかかるstructの罠 よーし ネイティブコンテナ 自作しちゃうぞー struct MyNativeArray<T> { NativeArray<T> na; int Length; } これは大失敗! なぜでしょう?
struct MyNativeArray<T> { NativeArray<T> na; int Length; } var a = new MyNativeArray<float>(32); func(a); Length Length Unmanaged Memory
struct MyNativeArray<T> { NativeArray<T> na; int Length; } var a = new MyNativeArray<float>(32); func(a); Length Length Lengthが 共有されてない!! Unmanaged Memory コンパイルエラーも出ないし プログラムは正しいけれど 100%バグ になる
struct MyNativeArray<T> { NativeArray<T> na; int Length; } Lengthも ポインタの先に置く Length Unmanaged Memory unsafe struct MyNativeArray<T> { void ptr; * } ネイティブコンテナ の中は必ずポインタ!
最重要!チャンクの設計 〜ゲームのためのメモリ〜
ゲームのオブジェクトを Unmanaged Memory Chunk Chunk で扱いたい NativeContainer NativeContainer NativeContainer Chunk マネージドメモリ (managed memory) Chunk Unmanaged Memory
現代はメモリが広い! Unityの登場時(2005)とは事情が異なる Chunk Chunk NativeContainer NativeContainer NativeContainer Chunk マネージドメモリ (managed memory) Chunk Unmanaged Memory
現代はメモリが広い! Unityの登場時(2005)とは事情が異なる Chunk Chunk NativeContainer NativeContainer NativeContainer Chunk マネージドメモリ Chunk (managed memory) Unmanaged Memory 増えたメモリは主にアセットに利用される ゲームロジックのメモリは大きく変わっていない メモリの再利用をギリギリまで追求する必要はない
ところで・・・ もし、確保するサイズが一律で同じだったら 確保・解放にまつわる 多くの問題が解決
ある物体は3つのコンポーネントで構成されているとする A B C ABCはサイズが異なる
ある物体は3つのコンポーネントで構成されているとする A B C ABCはサイズが異なる この物体専用の領域を用意できれば・・・
ある物体は3つのコンポーネントで構成されているとする A B C ABCはサイズが異なる この物体専用の領域を用意できれば・・・ A B C 確保単位が一定に! ある程度のメモリの無駄は許容する
A B C コンポーネント種別に並べる A struct Entity B C 必ず16KiB これが チャンク の姿
別の物体は4つのコンポーネントで構成 A B C D チャンクサイズ16KiBは変わらない 割り振りがさっきと異なる 格納できる数が変わる このチャンクには3つ格納できる
組み合わせをアーキタイプ(Archetype)と呼ぶ A B C A B C アーキタイプABC D アーキタイプABCD アーキタイプABC用チャンク アーキタイプABCD用チャンク
生成 A B C 専用チャンクに格納(満杯ならもう一本追加) A B C このチャンクはABCD用なので無関係!
あるオブジェクトを削除する場合 削除 削除 削除 削除 たったの2ステップ
ステップ1 チャンクに格納されているオブジェクト数を減らす 3→2
ステップ2 最後の要素を消したい場所に上書きコピー 削除完了!
ステップ2 最後の要素を消したい場所に上書きコピー 削除完了! 決して歯抜けにならない 順序は保たれていない
ねえねえ マネージャ 通して 他のオブジェクトに直接参照させない
ねえねえ マネージャ 通して 他のオブジェクトに直接参照させない Chunk NativeContainer Chunk NativeContainer NativeContainer Chunk マネージド Chunk Unmanaged
Systemの概念 〜データ指向とは〜
データは用意できた Chunk Chunk Chunk Chunk Chunk Chunk Chunk Chunk System どうやって処理させよう?
“System”を記述する Chunk Chunk “System”の3つの働き • チャンクを選ぶ • 入力と出力を決める • 処理する Chunk Chunk Chunk Chunk Chunk Chunk System
使用コンポーネントを決める
条件:BDをもつチャンク
アーキタイプABC用チャンク
query = GetEntityQuery(new EntityQueryDesc() {
All = new ComponentType[] {
ComponentType.ReadOnly<B>(),
ComponentType.ReadWrite<D>(),
}, });
アーキタイプBD用チャンク
アーキタイプABCD用チャンク
使用コンポーネントを決める
条件:BDをもつチャンク
アーキタイプABC用チャンク
query = GetEntityQuery(new EntityQueryDesc() {
All = new ComponentType[] {
ComponentType.ReadOnly<B>(),
ComponentType.ReadWrite<D>(),
}, });
アーキタイプBD用チャンク
アーキタイプABCD用チャンク
使用コンポーネントを決める
条件:BDをもつチャンク
アーキタイプABC用チャンク
query = GetEntityQuery(new EntityQueryDesc() {
All = new ComponentType[] {
ComponentType.ReadOnly<B>(),
ComponentType.ReadWrite<D>(),
}, });
アーキタイプBD用チャンク
アーキタイプABCD用チャンク
Bと Dを 処理する System
Bと Dを 処理する System
処理する System バラバラに処理しているように見えるが きっちりオブジェクト単位で渡っている
Jobを発行 Job Job Job System メモリの連続が考慮されている
Job Job Job B:入力(書き込まない) D:出力(書き込む) 入出力を明示 System
データの流れ B System D
データの流れ B System D System A ネイティブコンテナは アルゴリズムで使う Native Container
データの流れ B System D System A ネイティブコンテナは アルゴリズムで使う Native Container System Rotation
いつもの書き方 Update { } if (position.y > 0f) { rotation = objA.func(1f); return; } else if (position.y > 4f) { if (position.z > 0f) { m_C = objB.func(2f); } else m_C = objB.func(-1f); } rotation = m_C; データに注目 B A System D System Native Container System Rotation
いつもの書き方 Update { } if (position.y > 0f) { rotation = objA.func(1f); return; } else if (position.y > 4f) { if (position.z > 0f) { m_C = objB.func(2f); } else m_C = objB.func(-1f); } rotation = m_C; B A System テストが 容易! D System Native Container System Rotation
いつもの書き方 Update { } if (position.y > 0f) { rotation = objA.func(1f); return; } else if (position.y > 4f) { if (position.z > 0f) { m_C = objB.func(2f); } else m_C = objB.func(-1f); } rotation = m_C; B A テストが 容易! System D System Native Container System Rotation
いつもの書き方 Update { } if (position.y > 0f) { rotation = objA.func(1f); return; } else if (position.y > 4f) { if (position.z > 0f) { m_C = objB.func(2f); } else m_C = objB.func(-1f); } rotation = m_C; データに注目 B A System D System Native Container System Rotation
参照はエンティティ 〜ポインタの代わりに〜
参照の問題をいかにして解決するか マネージドメモリ (managed memory)
オブジェクトには必ずEntityがある Entity チャンクにも入っている Entity
他のオブジェクトはEntityで参照する でも、Entityはポインタ ではない
5 8 8 4 5 2 5 Entityの正体はインデクス(ある配列中の位置)
5 8 8 4 5 2 5 Entityの正体はインデクス(ある配列中の位置) Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン 1 2 3 4 5 EntityManagerが持つテーブル
5 8 8 4 5 2 5 Entityの正体はインデクス(ある配列中の位置) →ポインタ同様に高速 Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン 1 2 3 4 5 EntityManagerが持つテーブル
削除は? 5 8 8 4 5 2 5 削除されたインデクスが次の生成で使いまわされたらアウトだよね Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン 1 2 3 4 5
削除は? 5 8 8 4 5 2 Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン+1 1 2 3 4 5 削除! 5
実はEntityはインデクスとバージョン(計64bit) 5 1 インデクス(Index) バージョン(Version)
生成時に与えられた バージョン1が・・・ 4 5 0 1 Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン+1 1 2 3 4 5 5 8 1 1 8 0 2 5 4 1 削除時に増加して2になっているので・・
このEntityの 情報ください 4 5 1 1 Entity 5 の情報 チャンクポインタ チャンク内インデクス バージョン+1=2 1 2 3 4 5 5 8 1 1 8 1 2 5 1 1 バージョン1? あなたの知っている5番はもういません!
Unmanagedな世界でも・・・ Chunk Chunk NativeContainer NativeContainer NativeContainer Chunk マネージドメモリ (managed memory) Chunk Unmanaged Memory 速度を兼ね備えた見事な管理システムを実現
驚異のUnity Physics
C#で物理シミュレーションを フル実装しちゃおうぜ 正気かしら
Unity Physics 誕生 ・決定的(deterministic) ・ステートレス ・シミュレーションループ独立 こういうのを 待っていた!
キューブ13824個 on high-end PC 通常の物理エンジン 約15fps Unity Physics 約60fps
ワールドを分離して自在にループ実行
https://github.com/Unity-Technologies/DOTS-Shmup3D-sample
Unity Physicsは素晴らしい! が、現時点(Ver.0.2.4)では気配りが足りない けっこう突き放してくる! これから使いやすくなっていきます
注意点その1 静的なコライダーをスクリプトから動かす場合 コリジョンの更新は PhysicsVelocity があることが条件 AddComponentでPhysicsVelocityを付加しよう
注意点その2 外力 (AddForce) がない その代わり力積 (ApplyLinearImpulse) がある dv F= m 外力 dt F dt = mdv 力積 外力にdtを掛けて力積 (ApplyLinearImpulse) を呼べばいい
実装の確認は避けられない(現時点では) public static void ApplyLinearImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Linear += impulse * massData.InverseMass; } 1 つまり dv = Fdt m
実装の確認は避けられない(現時点では) public static void ApplyLinearImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Linear += impulse * massData.InverseMass; } ForceModeの対応: Force Impulse Acceleration VelocityChange 1 つまり dv = Fdt m velocityData.ApplyLinearImpulse(force*deltaTime); velocityData.ApplyLinearImpulse(impulse); velocityData.Linear += acceleration*deltaTime; velocityData.Linear += velocity;
注意点その3 AddTorqueは? public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; } massData.InverseInertia は行列ではなくベクトル
注意点その3 AddTorqueは? public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; } massData.InverseInertia は行列ではなくベクトル 直交座標系の回転要素がない慣性テンソル (対角化された状態)
注意点その3 AddTorqueは? public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; } massData.InverseInertia は行列ではなくベクトル 直交座標系の回転要素がない慣性テンソル (対角化された状態) 慣性テンソル空間でトルクを与える必要
注意点その3 AddTorqueは? public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; } massData.InverseInertia は行列ではなくベクトル 直交座標系の回転要素がない慣性テンソル (対角化された状態) 慣性テンソル空間でトルクを与える必要 特殊な形状でなければAddRelativeTorqueと考えて良い
参考:慣性テンソルについての解説動画 〜Unity道場〜 物理シミュレーション完全マスター https://learning.unity3d.jp/1167/ 34:20から
注意点その4 軸単位の拘束(Constraint)の実現方法 拘束しないとこうなるよね
物体の位置・姿勢の更新は すべてApply*Impulse経由 という実装になってるので public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; }
物体の位置・姿勢の更新は すべてApply*Impulse経由 という実装になってるので public static void ApplyAngularImpulse(ref this PhysicsVelocity velocityData, PhysicsMass massData, float3 impulse) { velocityData.Angular += impulse * massData.InverseInertia; } InverseInertia の設定で回転拘束を実現 例: new PhysicsMass { … InverseInertia = new float3(0, 1, 0); … } Y軸以外の角速度を 発生させない 慣性テンソルに回転がある場合は注意
まとめ
DOTSの必然性 • ゲーム実装を強く意識 汎用的だったメモリ管理をゲームの特徴に合わせる
DOTSの必然性 • ゲーム実装を強く意識 汎用的だったメモリ管理をゲームの特徴に合わせる • 現代のCPUを強く意識 64bit CPUを前提にできつつある
DOTSの必然性 • ゲーム実装を強く意識 汎用的だったメモリ管理をゲームの特徴に合わせる • 現代のCPUを強く意識 64bit CPUを前提にできつつある • テスタビリティ・スケーラビリティを強く意識 データの一意性+マルチコア実行
DOTSの必然性 • ゲーム実装を強く意識 汎用的だったメモリ管理をゲームの特徴に合わせる • 現代のCPUを強く意識 64bit CPUを前提にできつつある • テスタビリティ・スケーラビリティを強く意識 データの一意性+マルチコア実行 DOTSはどんどん使いやすくなっていく そのときに始めれば十分 ご期待ください!
おしまい