14.1K Views
September 25, 19
スライド概要
2019/9/25-6に開催されたUnite Tokyo 2019の講演スライドです。
山村 達彦(ユニティ・テクノロジーズ・ジャパン合同会社)
こんな人におすすめ
・ECSに興味がある方
・大規模なステージのロード
受講者が得られる知見
・DOTSの内側
・レンダリングワークフロー
・DOTSのシリアライズ
Unityのイベント資料はこちらから:
https://www.slideshare.net/UnityTechnologiesJapan/clipboards
リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。
大量のオブジェクトを含む 広いステージでも大丈夫。 そう、DOTSならね Tatsuhiko Yamamura @ Unity
今回の話 • DOTSのやりたいこと • ECS移行ガイド • GameObject→ECS変換ガイド • データのロード • 描画処理で見る、DOTSの流れ 検証に使用したデモ「オープンワールドデモ」 23万Entity、 iPhoneXで30FPS、ロード時間短め
DOTSがやりたいこと
同じ処理を繰り返すと早い 1 2 Data Data 3 Data 4 Data 5 Data 6 Data 7 Data 1 Data 2 Data 3 Data 4 Data 5 Data 6 Data 7 Data
連続したメモリに連続してアクセスすると早い struct Data[] 入力 0 1 2 3 4 5 6 7 8 9 Data[] 入力 = … Data[] 出力 = … 処理 (in Data) struct Data[] 出力 0 1 2 3 4 5 6 7 8 9 for( int i=0; i<length; i++) { 出力[i] = 処理(in 入力[i]); }
データは塊で操作すると早い 0 1 2 3 4 5 6 Copy Copy Copy Copy Copy Copy Copy 0 1 2 3 4 5 6 0 1 2 3 4 5 6 3 4 5 6 Copy 0 1 2 メモリを塊でコピーしたり、処理を範囲でスキップしたり
DOTSがやりたいこと データを意識した設計
DOTSがやりたいこと • ECS : データを並べる・整理する・処理を実行 • Burst • JobSystem : 並列処理、Burstが生成したバイナリを実行する : 連続したデータを前提とした最適化
DOTSがやりたいこと リリース済みの機能 既に使用しているプロダクトも • ECS : データを並べる・整理する・処理を実行 • Burst • JobSystem : 並列処理、Burstが生成したバイナリを実行する : 連続したデータを前提とした最適化
DOTSがやりたいこと Previewの機能 今回メインとなる話 2020.1でやっとリリース(予定) • ECS : データを並べる・整理する・処理を実行 • Burst • JobSystem : 並列処理、Burstが生成したバイナリを実行する : 連続したデータを前提とした最適化
GameObjectとECSの移行ガイド 〜GameObjectとEntityの違い〜
GameObjectの動作イメージ(1/2) GameObject Object Name Layer Tag Active • GameObjectが コンポーネントを持つ。 • コンポーネントが GameObjectの 振る舞いを定義する。 • コンポーネントは 他のコンポーネントを操作 することもある。 Transform LocalPosition LocalScale LocalRotation Player Transform Speed Bullet 移動する処理等 Gun Transform Bullet 弾を発射する処理
GameObjectの動作イメージ(2/2) UnityPlayerLoop Update ( ) Update ( ) 処理 Data値を更新 • 各コンポーネントの処理は ゲームエンジンが実行。 • コンポーネントは自分もしく は他のコンポーネントの データを更新。 処理 Update ( ) Data値を更新 Update ( ) Data値を更新 Update ( ) 処理 Update ( ) 処理
ECSの動作イメージ(1/4) Entity Pos Player System Speed Bullet 処理 • Entity データと処理を 別々に定義する。 System B Pos Speed Entity Bullet Pos Speed Bullet 処理
ECSの動作イメージ(2/4) • • Entityがコンポーネントを持つ。 コンポーネントはデータを持つ。 Entity Pos Entity Speed Pos HP Speed Player Entity Pos Speed Bullet Bullet
ECSの動作イメージ(3/4) Entity Pos System A Speed NativeArray<Pos> positions = … NativeArray<Speed> speeds = … Bullet Entity for( int i=0; i<length; i++) { positions[i] = Utility.Do ( speeds[i] ); } Pos Speed Bullet Entity Pos • システムが操作したいコンポーネントを集める。 • 集めたコンポーネントを、連続で処理する。 Speed Bullet
ECSの動作イメージ(4/4) Entity Pos システム毎に同じ処理を連続して行う Speed System A Bullet Entity 座標を更新する処理 Pos Speed Bullet System B なにかの処理 Entity Pos Speed Bullet System C 弾を生成する処理
ECS GameObject GameObjectとECS • GameObjectはオブジェクト毎に動く。 • オブジェクトが何のデータをどう動かすのかはメソッド次第。 • ECSはデータをシステムが集めて動かす。 • 動作内容はシステムが指示する。 (入力を元に計算し、出力で値を更新する)
MonobehsviourをECSへ変換
MonoBehaviour
public class Updaown : MonoBehaviour
{
Transform _transform;
[SerializeField] float power;
void Start()
{
_transform = GetComponent<Transform>();
}
void Update()
{
UpdatePosition();
}
void UpdatePosition()
{
var y = math.sin(Time.time ) * power;
var pos = _transform.position;
pos.y = y;
_transform.position = pos;
}
}
「データ」だけをコンポーネントから抽出
MonoBehaviour
public class Updaown : MonoBehaviour
{
Transform _transform;
[SerializeField] float power;
void Start()
{
_transform = GetComponent<Transform>();
}
void Update()
{
UpdatePosition();
GameObject
}
Transform
void UpdatePosition()
Position
{
var y = math.sin(Time.time ) * power;
var pos = _transform.position;
Updown
pos.y = y;
Power
_transform.position
= pos;
}
}
ECS
struct Power : IComponentData
{
public float Value;
}
Entity
Translation
Power
// Transformで言う所のPosition(基本機能)
struct Translation : IComponentData
{
public float float3;
}
処理は入力・出力がハッキリしたものに変更
MonoBehaviour
public class Updaown : MonoBehaviour
{
Transform _transform;
[SerializeField] float power;
void Start()
{
_transform = GetComponent<Transform>();
}
void Update()
{
UpdatePosition();
}
void UpdatePosition()
{
var y = math.sin(Time.time ) * power;
var pos = _transform.position;
pos.y = y;
_transform.position = pos;
}
}
ECS
static float3 UpdatePosition(
in float power,
float3 position)
{
position.y = math.sin(Time.time) * power;
return position;
}
コンポーネントの組み合わせで操作対象を絞り込む
MonoBehaviour
public class Updaown : MonoBehaviour
{
Transform _transform;
[SerializeField] float power;
void Start()
{
_transform = GetComponent<Transform>();
}
void Update()
{
UpdatePosition();
}
Entities.Foreachは
組み合わせに対応するEntityを集めて、
連続でアクセスするAPI
void UpdatePosition()
ECS
public class UpdownSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach((
ref Translation position,
ref Power power)=>{
{
var y = math.sin(Time.time ) * power;
var pos Translation
= _transform.position;
Power
pos.y = y;
を持つEntityを対象に処理を実行
_transform.position
= pos;
}
}
});
}
}
コンポーネントの値を更新
MonoBehaviour
public class Updaown : MonoBehaviour
{
Transform _transform;
[SerializeField] float power;
void Start()
{
_transform = GetComponent<Transform>();
}
void Update()
{
UpdatePosition();
}
void UpdatePosition()
{
var y = math.sin(Time.time ) * power;
var pos = _transform.position;
pos.y = y;
_transform.position = pos;
}
}
ECS
public class UpdownSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach((
ref Translation position,
ref Power power)=>{
position.Value = UpdatePosition(
in power.Value,
position.Value);
});
}
}
結果
ECS
struct Power : IComponentData
{
public float Value;
}
public class UpdownSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach((
ref Translation position,
ref Power power)=>
{
position.Value = UpdatePosition(
in power.Value,
position.Value);
});
}
Entity
Translation
Power
UpdownSystem
* 9999
}
static float3 UpdatePosition(
in float power,
float3 position)
{
position.y = math.sin(Time.time) * power;
return position;
}
Entity は オブジェクト? Entity Translation Rotation Entity Translation Rotation Collider Player Entity Translation Rotation Collider Collider Entity Translation Rotation Collider
Entity毎にコンポーネントがまとめられるように見えるが Entity Entity Entity Entity Entity Entity Translation Translation Translation Translation Translation Translation Rotation Rotation Rotation Rotation Rotation Rotation Collider Collider Collider Collider Collider Collider Rig Rig Rig Rig Rig Rig
データは連続したメモリに格納される Translation Translation Translation Translation Translation Translation Rotation Rotation Rotation Rotation Rotation Rotation Collider Collider Collider Collider Collider Collider Rig Rig Rig Rig Rig Rig
連続したデータはリニアにアクセスできる Array [ ] Translation Translation Translation Translation Translation Translation Array [ ] Rotation Rotation Rotation Rotation Rotation Rotation Array [ ] Collider Collider Collider Collider Collider Collider Array [ ] Rig Rig Rig Rig Rig Rig
EntityはデータにアクセスするためのID Entity Array [ ] Translation Translation Translation Translation Translation Translation Array [ ] Rotation Rotation Rotation Rotation Rotation Rotation Array [ ] Collider Collider Collider Collider Collider Collider Array [ ] Rig Rig Rig Rig Rig Rig
配列にIDを渡して参照するイメージ Array [ ] Translation Translation Translation Translation Translation Translation Array [ ] Rotation Rotation Rotation Rotation Rotation Rotation Array [ ] Collider Collider Collider Collider Collider Collider Array [ ] Rig Rig Rig Rig Rig Rig
ECSはデータを構造的に整理してくれる Entity生成時は 新しい値で配列の 中身を埋める Translation Rotation Collider Rig Translation Translation Translation Translation Translation Rotation Rotation Rotation Rotation Rotation Collider Collider Collider Collider Collider Rig Rig Rig Rig Rig
Entityは色々な組み合わせがある 隙間があるので、リニアなアクセスができない。 Entity Entity Translation Translation Translation Rotation Rotation Rotation Collider Collider Collider Rig Player Entity Entity Entity Entity Translation Translation Translation Collider Collider Collider Rig Rig Rig Enemy Enemy Enemy
組み合わせ毎にアーキタイプを作り Translation Translation Translation Rotation Rotation Rotation Collider Collider Archetype Rig Player Collider Archetype Translation Collider Translation Collider Archetype Translation Collider Rig Rig Rig Enemy Enemy Enemy
アーキタイプと容量でChunkを分割 Chunk Chunk Chunk Translation Translation Translation Rotation Rotation Rotation Collider Collider Collider Rig Translation Translation Collider Collider Collider Rig Rig Rig Enemy Enemy Enemy Player Archetype Archetype Translation Archetype
特定の組み合わせの配列に素早くアクセス Chunk Chunk Chunk Translation Translation Translation Rotation Rotation Rotation Collider Collider Archetype Translation Collider Archetype Rig Player Collider Collider Archetype Translation Collider Rig Rig Rig Enemy Enemy Enemy 必要なコンポーネントを持つかは アーキタイプでフィルター 個別に聞くより圧倒的に早い Translation
特定の組み合わせの配列に素早くアクセス Chunkの分断で多少飛ぶが、リニアなアクセスができた! Chunk Chunk Chunk Translation Translation Translation Rotation Rotation Rotation Collider Collider Collider Rig Player Translation Translation Translation Collider Collider Collider Rig Rig Rig Enemy Enemy Enemy
ECSを使う側のまとめ • ECSの基本は「オブジェクトを集めて」「処理を実行」「データを更新」の流れ。 • コンポーネントやEntityの操作はECSのAPIに従っていれば、 概ね安全かつ効率的に操作させてくれる。
ECSのできること • ECSはリニアなメモリアクセスを実現し易い。 • ECSは同じ処理を連続して行い易い。 (コールバック等で処理が挟まらない) • ECSは大きい単位(Chunk単位)でまとめて処理し易い。 • ECSを使うとDOTSの目的を達成し易い。 (無くても何とかなる)
GameObject→Entity 変換ガイド 〜スクリプトでオブジェクト生成とかしたくない!〜
Entityの生成にシーンエディタを活用したい • プロシージャルで最後まで 作り切るのは少し面倒くさい。 • パラメーターはInspectorで 調整したい。 • スクリプトで全ての配置を 構築するのは嫌だ。
ECSとGameObjectベースではデータが異なる GameObject Entity Object Name Layer Tag Active Transform LocalPosition GOマン LocalScale LocalRotation MeshFilter Mesh 内容は概ね同じでも 内部のデータ構造が全く違う Light Settings Sphere Collider Mesh Value Rotation Value Eマン LocalToWorld RendererMesh MeshRenderer Material Translation でも、見た目は結構似てる Physics Shape Value Light Settings Mesh Material Value
ECSとGameObjectベースではデータが異なる GameObject Object Name Layer Tag Active そうだ Transform LocalPosition GOマン LocalScale LocalRotation MeshFilter Mesh MeshRenderer Material 変 換 Translation Value Rotation Value Eマン LocalToWorld RendererMesh Value Light Settings Mesh Material Light Settings Sphere Collider Mesh Entity Physics Shape しよう Value
ConvertToEntityで変換
Game Disk Editor ConvertToEntityワークフロー GameObjects Convert Entities/ Component GameObjects Scene GameObjects GameObjects Convert Entities/ Component • 実行時にGameObject毎に Componentを変換。 • 変換さえすれば、 メモリレイアウトはECSが なんとかしてくれる。
GameObjectとEntity、得意分野がある またはECSが現状機能不足とも言う。 GameObject キャラクター挙動 ステージロード プレイヤー オブジェクト配置 座標 物理演算 個別制御の 実装が簡単 ECS カメラ制御 UI 描画 大量のオブジェクト 制御に強力
GameObjectとEntity、得意分野がある またはECSが現状機能不足とも言う。 ECSとGameObject ECS GameObject 片方ではなく両方使う キャラクター挙動 そう・・・ ステージロード プレイヤー オブジェクト配置 座標 物理演算 ハイブリットだ! 個別制御の 実装が簡単 カメラ制御 UI 描画 大量のオブジェクト 制御に強力
EntityとGameObjectをセットで運用 当たり判定や座標の更新、 地面との接地判定等は 他のEntity同様 システムで一括処理 Entity Translation Rotation GameObject 同期 MeshRenderer Physics Shape Input (機能が無いとも言う) Animator LocalToWorld Physics Body Transform 描画等は今まで通り GameObjectを使用 Controller 更新 処理 Gun 処理 銃の射撃や入力操作等は MonoBehaviourで処理
ハイブリットで使用
GameObjectを残しつつ ECSの計算を使用して座標を更新
ハイブリットはEntity消し忘れに注意 Entity GameObject Translation Transform Rotation Animator LocalToWorld MeshRenderer Physics Shape Controller Physics Body Input 処理 Gun 処理 GameObjectを消してもEntityが消えない Entityを消してもGameObjectが消えない GameObjectかEntity削除時、 対応するオブジェクトを 削除するシステムが必要 https://gist.github.com/tsubaki/ 00469aeae95757dc225dd3e7deca9afc
ハイブリットでない場合、GOの生成と破棄が勿体ない 1 2 3 Scene GameObject GameObject GameObject GameObject GameObject GameObject GameObject GameObject GameObject Entity Entity SceneからGameObjectと Componentをデシリアライズ Entity Componentの情報を読んで Entityの生成 GameObject GameObject GameObject GameObjectの破棄
ConvertToEntityの問題 • ハイブリットでない場合、色々と勿体ない。 • GameObjectの生成と破棄が勿体ない。 • 逐次Entityへ変換するのは勿体ない。 • 処理性能が早くても初期化にかかるコストが勿体ない。
ConvertToEntityの問題 • ハイブリットでない場合、色々と勿体ない。 • GameObjectの生成と破棄が勿体ない。 • 逐次Entityへ変換するのは勿体ない。 せや! 事前にEntityに変換する仕組みを • 処理性能が早くても初期化にかかるコストが勿体ない。 用意したろ!
SubSceneワークフロー • SubScene内のGameObjectを 漏れなくEntityへ変換する。 • ゲーム実行時は変換したEntitiesをロード。 • LiveLinkで実行中に値を編集できる。 • SubScene変換前のGameObjectを 再編集することも可能。
SubSceneワークフロー(1/5) Editor GameObject関連 GameObjects GameObjects 通常のGameObjectベースの場合。 Disk ロード・更新 普通にシーンを編集したり、 シーンをゲームに展開する。 Scene Game ロード GameObjects ECS関連 GameObjects
SubSceneワークフロー(2/5) Game Disk Editor シーンを分割 GameObjects Scene GameObjects Scene New SubScene From Selectionで 指定されたGameObjectが 別シーンとして分離
Game Disk Editor SubSceneワークフロー(3/5) GameObjects Scene GameObjects Scene Entities/ Component SubScene 分離した新しいシーンは EntityとComponentに変換され SubSceneとして保存される。
SubSceneワークフロー(4/5) Editor GameObjects Scene Scene Game GameObjects Disk 変換したGameObjectは ロードしなくても良い Entities/ Component SubScene 以降はEntityとComponentを SubSceneから直接ロードされるように。 Entities/Components
Editor GameObjects Scene Scene Game GameObjects Disk SubSceneワークフロー(5/5) GameObjects 変換してないGameObjectはそのまま使える Entities/ Component SubScene ビルドしたゲームでも SubSceneのEntity/Componentを そのままロードできる。 Entities/Components Entities/ Component Entitiesを単純に読める
Editor GameObjects Scene Scene Game GameObjects Disk SubSceneワークフロー(おまけ) Entities/ Component SubScene SubSceneの編集は 変換前のSceneを編集するだけ (要:変更後にSubSceneのリビルド) Entities/Components
SubSceneの分割(通常) Scene Root(GameObject) Entities SubScene Section 0 家(GameObject) 変換 家(Entity) 家の外観(Entity) 家の外観(GameObject) 部屋 (Entity) 部屋(GameObject) Obj 0(Entity) Mesh 1(Entity) Obj 0(GameObject) Mesh 1(GameObject) Obj1(GameObject) Mesh 2(GameObject) Mesh 3(GameObject) Obj1(Entity) Mesh 2(Entity) Mesh 3(Entity)
SubSceneの分割(Sectionで分割) Scene Entities Root(GameObject) SubScene 変換 家(GameObject) Section 0 Section 0 家(Entity) 家の外観(Entity) 家の外観(GameObject) Entities 部屋(GameObject) Section Section 1 1 部屋 (Entity) Obj 0(GameObject) Mesh 1(GameObject) Obj1(GameObject) Mesh 2(GameObject) Mesh 3(GameObject) 変換 Obj 0(Entity) Mesh 1(Entity) Obj1(Entity) Mesh 2(Entity) Mesh 3(Entity)
Sectionは段階的なロードに有益 Section 0 Section 1 Section 0 Section 1 Near Far 遠くに居る時は家の外観だけロードして、 近くに来たら部屋の内装もロード
HLODでも便利 Near Far 結合してローポリ化した メッシュで表現 5000〜10000個のLODで表現 結合すると、視界外のモデルを常に描画する事になるので 近づいたときは結合したくない 遠くにいるときは結合したメッシュを描画 近づいたら個別のオブジェクトで描画
SubSceneのメリット・デメリット メリット • 大量にGameObjectがある時、エディター機能低下が起こりにくい。 • ゲーム再生までの時間がGameObject版と比較して短い。 • シーンをファイルで分割できるので、編集しても競合を起こしにくい。 (アンドゥ、値の変更、シーンの保存等) 約20万個のGameObjectを Entityに変換する前と後の ゲーム再生にかかる時間 (GameObject版の94秒スキップした版) • 全て Game Object 約 95秒 • Entity & Component 約 8秒
SubSceneのメリット・デメリット メリット • 大量にGameObjectがある時、エディター機能低下が起こりにくい。 • ゲーム再生までの時間がGameObject版と比較して短い。 • シーンをファイルで分割できるので、編集しても競合を起こしにくい。 (アンドゥ、値の変更、シーンの保存等) デメリット • GameObjectを含める事が出来ない。(ハイブリットが使えない) • SubSceneにPrefabを配置している場合、 Prefabの変更を反映するにはSubSceneのリビルドが必要。 • ComponentDataに変換できないコンポーネントは破棄される。
変換に対応していない コンポーネントを変換に対応させる
いくつかのPackageはコンバーターで変換作業を自動化 Hybrid Renderer UnityPhysics つまりコンバーターを定義してやれば、変換が可能になる UnityEngine ECS Component Rigidbody PhysicsBody Collider MeshRenderer LODGroup Light PhysicsShape RenderMesh RenderBounds MeshLODGroup Component LightComponent
コンポーネントを変換
対応するコンポーネントが変換対象にあれば自動的に変換する
GameObjectConversionを継承
[UpdateInGroup(typeof(GameObjectBeforeConversionGroup))]
public class LightConversion : GameObjectConversionSystem
{
protected override void OnUpdate()
{
Entities.ForEach((UnityEngine.Light light)=>
{
var entity = GetPrimaryEntity(light);
操作するコンポーネントを指定
コンポーネントからEntityを取得
var lightData = new LightComponent{
intensity = light.intensity
};
DstEntityManager.AddComponentData(entity, lightData);
});
}
}
対応するComponentData
を作成してEntityに登録
オーサリング用のコード Entityへ変換する前提のMonoBehaviour [DisallowMultipleComponent] [RequiresEntityConversion] public class InputDataAuthoring : MonoBehaviour, IConvertGameObjectToEntity { [SerializeField] GameObject Prefab; public void Convert( Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) { var inputData = new InputData { Target = conversionSystem.GetPrimaryEntity(Prefab) }; dstManager.AddComponentData(entity, inputData); } } お約束 コンポーネントからEntityを取得
変換時の工夫
[UpdateInGroup(typeof(GameObjectBeforeConversionGroup))]
class TransformConversion : GameObjectConversionSystem
{
private void Convert(Transform transform)
{
var entity = GetPrimaryEntity(transform);
(Transformの変換コードより)
設定に合わせて必要なコンポーネント調整
//@TODO: This is not a great dependency to introduce as it results in everything getting rebuilt when moving the root,
// although we only need to recompute localtoworld... (I predict this won't scale on megacity...)
DeclareDependency(transform, transform.parent);
StaticならWorld座標だけで終了
DstEntityManager.AddComponentData(entity, new LocalToWorld { Value = transform.localToWorldMatrix });
if (DstEntityManager.HasComponent<Static>(entity))
return;
var hasParent = HasPrimaryEntity(transform.parent);
if (hasParent)
{
DstEntityManager.AddComponentData(entity, new Translation { Value = transform.localPosition });
DstEntityManager.AddComponentData(entity, new Rotation { Value = transform.localRotation });
親子関係があるなら、親子関係を
コンポーネントに登録
if (transform.localScale != Vector3.one)
DstEntityManager.AddComponentData(entity, new NonUniformScale { Value = transform.localScale });
DstEntityManager.AddComponentData(entity, new Parent { Value = GetPrimaryEntity(transform.parent) });
DstEntityManager.AddComponentData(entity, new LocalToParent());
Scaleが1なら必要ないということで
Scaleのコンポーネントを削除
}
}
else
{
DstEntityManager.AddComponentData(entity, new Translation { Value = transform.position });
DstEntityManager.AddComponentData(entity, new Rotation { Value = transform.rotation });
if (transform.lossyScale != Vector3.one)
DstEntityManager.AddComponentData(entity, new NonUniformScale { Value = transform.lossyScale });
}
少し楽なオーサリング用コードの作成
[RequiresEntityConversion]
public class AuthoringBase<T> : MonoBehaviour,
IConvertGameObjectToEntity where T : struct, IComponentData
{
public T Value;
public void Convert(Entity entity,
EntityManager dstManager,
GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponentData(entity, Value);
}
}
public class VelocityComponent : AuthoringBase<Velocity>{}
ジェネリックにコンポーネントを
指定すればサクッと作れる
ComponentDataの定義から自動生成
[CreateAuthoringComponent]
public struct Enemy : IComponentData
{
// 初期値
public static Enemy Default => new Enemy() {
Health = 12, Mana = 13, Maybe = Maybe.No
};
[Tooltip("0になったら死ぬ")]
•
オーサリング用のコードを
自動生成するAttributeを
準備中(2019.3を予定)
•
Entityの参照も楽々
public int Health;
[Range(2, 10)]
public float Mana;
public Maybe Maybe;
[RestrictAuthoringInputTo(typeof(Light))]
public Entity LightsourceToPulseWhenAlmostDead;
}
自動生成されるコンポーネント(イメージ)
ここまでのまとめ • GameObject→ECSの変換は、SubSceneかConvertToEntityを使う。 このアプローチはUnityエディターをDOTSエディターにする側面もある。 • ConvertTnEntityはEntityとGameObjectとセットで運用出来る(Hybrid)。 • SubSceneはEntityのみを使用したいケースでは効率的。 • 独自コンポーネントを変換する場合、変換用コードを記述する。
データをロードする SubSceneを展開する
ECSのロードは結構早い Terrain&他 3.7秒 Terrain&他 GameObject 8.2秒 & SubScene 0.8秒
SubSceneのロード シーン再生時に 全Scrtionをロード
SubSceneのAPI public SubScene subScene; void Update() { var isLoad = Input.GetKey(KeyCode.Space); subScene.AutoLoadScene = isLoad; if( Input.GetKeyDown(KeyCode.Space) || Input.GetKeyUp(KeyCode.Space)) { subScene.UpdateSceneEntities(); } } ロードするかの設定 SubSceneの ロード状況を更新
SectionのAPI
public SubScene subScene;
[SerializeField] int sectionIndex = 0;
void Update()
{
var em = World.Active.EntityManager;
var entity = subScene._SceneEntities[sectionIndex];
if( Input.GetKeyDown(KeyCode.A))
{
if(! em.HasComponent<RequestSceneLoaded>(entity))
em.AddComponentData(entity, new RequestSceneLoaded());
}
if( Input.GetKeyDown(KeyCode.D))
{
if( em.HasComponent<RequestSceneLoaded>(entity))
em.RemoveComponent<RequestSceneLoaded>(entity);
}
}
Sectionに対応する
Entityを取得
RequestSceneLoaded
の有無でシーンを
ロード・アンロード
ECSのSystemでやる場合
SubSceneコンポーネントを見ていない。
SceneData全体に対してチェックする。
public class LoadSubSceneByDistance : ComponentSystem
{
EntityQuery playerQuery;
protected override void OnCreate()
{
playerQuery = GetEntityQuery(typeof(Player), ComponentType.ReadOnly<Translation>());
RequireSingletonForUpdate<Player>();
}
protected override void OnUpdate()
{
var targetPosition = playerQuery.GetSingleton<Translation>().Value;
var requests = GetComponentDataFromEntity<RequestSceneLoaded>(true);
各SceneDataに対して処理
Entities.ForEach((Entity entity, ref SceneData sceneData, ref SceneBoundingVolume volume)=>{
var center = (volume.Value.Min + volume.Value.Max) * 0.5f;
var distance = math.distance(targetPosition, center);
}
}
});
if( distance < 40 - sceneData.SubSectionIndex * 12)
{
if( !requests.Exists(entity))
{
PostUpdateCommands.AddComponent(entity, new RequestSceneLoaded());
}
}
if(distance > 40 - sceneData.SubSectionIndex * 12)
{
if( requests.Exists(entity))
{
PostUpdateCommands.RemoveComponent<RequestSceneLoaded>(entity);
}
}
SubSceneの範囲を元に
プレイヤーとの距離を計算
SubSectionIndexの値を元に
シーンのロードもしくは
アンロードの要求を
Entityに追加
Section0 球が近づいたら部屋をロード Section 1 もっと近づいたら部屋の中身をロード 離れたらアンロード
Archetype Archetype Chunkのロード(2/2) Chunk Chunk
Chunkのロード(2/2) 数百のEntityでも 数回コピーで済む(速い) Archetype Archetype Disk Chunk MemCopy Chunk Chunk Chunk
マネージドメモリを挟まずデータをロード バッファに直で書き込み
Entityのロード(1/2) Entities Staging World 使用できるEntityに間隔がある Main World Entities
Entityのロード(2/2) Entities Staging World Remap Main World Entities
アセットのロード SharedComponentData RenderMesh RenderMesh RenderMesh Prefab Assets SubScene • Shared Component Dataが参照する Prefabを経由してアセットを参照 • AsyncUploadPipelineで メインスレッドにかかる負荷は少ない • 通常と同様のアセット読込 (今後、独自の物に変わる予定)
実はメインスレッドには殆ど処理が走らない 約20万Entityあっても メインスレッドの負荷 約2ms以下 ※ShaderCompileを始めとしたアセットの初期化は除く
SubSceneを非同期でロード(1/4) Idling… Main World Staging World Idling… Staging World Idling… Staging Wor
SubSceneを非同期でロード(2/4) Working!! Load Request Main World Staging World メインスレッドから リクエストを投げる Staging World Staging Wor
SubSceneを非同期でロード(3/4) Main World Staging World Staging World Load Async ロード処理 Load Shared Components Load Async Load Entities メモリ的に独立してるので 別スレッドでも無問題 ECS Data Staging Wor
SubSceneを非同期でロード(4/4) Idling… Main World Staging World メインワールドに統合 マージだけなので 短期間で終わる Staging World Load Shared Components Load Entities Move ECS Data Staging Wor
ここまでのまとめ Requests Load • SubSceneのロードはEntityに リクエストを登録する形で行う。(基本非同期) • ロードが速いので、比較的近くでロードしても 間に合う。 (IOの速度に依存) • 段階的なステージのロードが楽。 • メインスレッドに負荷がかかりにくい。 移動方向 Loaded SubScene UnLoad
描画処理で見るDOTSの処理 少し面白いデータの動かし方
ハイブリットレンダラー • ECSで使えるレンダラー • 同じタイプのメッシュを連続して描画(バッチング) • カリング、LOD、HLOD付き • MeshRendererとMeshFilterからの変換 • 描画はBatchRendererGroupを使用
描画のバッチ処理
描画したいオブジェクトが交互に配置されている場合 w 草 RenderMesh w 草 w 異なるメッシュの草が交互に配置 …
通常の描画 バッチが切れる 青を描画 … 赤を描画 青を描画 赤を描画 青を描画 見える範囲
描画のバッチング 処理 Translation Translation Translation Translation Translation Translation Translation Translation Rotation Rotation Rotation Rotation Rotation Rotation Rotation Rotation Scale Scale Scale Scale Scale Scale Scale Scale Transform Matrix Transform Matrix Transform Matrix Transform Matrix Transform Matrix Transform Matrix Transform Matrix Transform Matrix Render Bounds Render Bounds Render Bounds Render Bounds Render Bounds Render Bounds Render Bounds Render Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds RendererMesh RendererMesh RendererMesh RendererMesh RendererMesh RendererMesh RendererMesh RendererMesh
描画のバッチング RenderMeshの種類でデータを整理・Chunkを分割。 処理 チャンク単位で処理される 処理 Chunk Chunk Translation Translation Translation Translation Translation Translation Translation Translation Rotation Rotation Rotation Rotation Rotation Rotation Rotation Rotation Scale Scale Scale Scale Scale Scale Scale Scale Transform Matrix Transform Matrix Transform Matrix Transform Matrix Transform Matrix Transform Matrix Transform Matrix Transform Matrix Render Bounds Render Bounds Render Bounds Render Bounds Render Bounds Render Bounds Render Bounds Render Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds RendererMesh RendererMesh
RenderBatchで描画している場合 Chunk RendererMesh(青) 青を描画 青を描画 Chunk RendererMesh(赤) … 青を描画 赤を描画 赤を描画 ※半透明の描画は 注意が必要 メッシュの種類ごとに描画
Instancingで描画している場合 Chunk RendererMesh(青) Chunk RendererMesh(赤) ※半透明の描画は 注意が必要 … 青をまとめて描画 (Instancing) 赤をまとめて描画 (Instancing) Instancingでより安く描画
高速なカリング
シーンには大量のオブジェクト 描画するかの判定を個別に 行うのは大変。
Chunk単位でバウンディングボックスを設定 Chunk Chunk Translation Translation Translation Translation Translation Translation Rotation Rotation Rotation Rotation Rotation Rotation Scale Scale Scale Scale Scale Scale Transform Matrix Transform Matrix Transform Matrix Transform Matrix Transform Matrix Transform Matrix Render Bounds Render Bounds Render Bounds Render Bounds Render Bounds Render Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds RendererMesh Chunkに含まれるEntity一覧から チャンクの影響範囲を計算 Bounding Box RendererMesh
ChunkBounds ChunkBounds ChunkBounds ChunkBounds ChunkBounds ChunkBounds Chunk毎に 描画範囲を持つ ChunkBounds
ChunkBounds ChunkBounds Chunk毎に 描画するか 判断できる ChunkBounds ChunkBounds ChunkBounds ChunkBounds ChunkBounds
ChunkBounds ChunkBounds ChunkBounds ChunkBounds ChunkBounds インスタンス毎のカリングで 画面外を除外 ChunkBounds ChunkBounds
ステージのオブジェクトは (ほぼ)動かない 動かない • 親子構造で動く事は無い • バウンディングボックスを 更新しなくても良い 動かない 動く 動かない
StaticOptimizeEntityを設定 Root Grid1 子オブジェクトに対して最適化 LOD LOD0 LOD1 LOD2 LOD LOD0 LOD1 LOD2 Grid2 BoundingBoxを更新しなくなる。
StaticOptimizeEntityを設定した結果 Chunk Chunk Translation Translation Translation Rotation Rotation Rotation Scale Scale Scale Transform Matrix Transform Matrix Transform Matrix Render Bounds Render Bounds Render Bounds WorldRender Bounds WorldRender Bounds WorldRender Bounds Transform Matrix Transform Matrix Transform Matrix WorldRender Bounds WorldRender Bounds WorldRender Bounds Bounding Box Bounding Box RendererMesh RendererMesh システムが要求する、計算に使うデータが無いので BoundingBoxの再計算の処理がスキップされる。
最後に・・・
最後に・・・ • 「ECSの使い方」「Conversion系コード」「変換方法」「ロード方法」の 4つだけ覚えておけば、ECSを使うだけなら十分なんとかなる。 • 単純にECSを使うだけでも、十分に早くなることもある。 • より高みを目指すならメモリの構造は頭に入れても良いかもしれない。 • ECSは機能が全然足りないので、限定的な使用もしくはハイブリットをお勧め。 BurstとJobSystemだけの採用は、現状は十分にアリ。
おわり