空間のオブジェクトを自然に見せるには?
XR技術の中でも拡張現実(AR)、複合現実(MR)では現実空間にオブジェクトを出力することになります。この際、オブジェクトの表示に関する表現方法をより自然に見せたいと思ったことはないですか?
例えば、何かものを動かすシチュエーションで物体を移動させる際に、デジタルコンテンツを操作してもきっちりと操作した分だけ移動することになります。操作方法によってはそのままでも問題ない場合が多いですが、自分の操作に対してオブジェクトの動きに遊びがあるとデジタルコンテンツ独特の唐突感が和らぐ印象があると思います。
また、物体を表示される際にデジタルコンテンツを空間に出力する際も、いきなり表示されるよりも徐々に大きくなるなどの表現を入れる方が自然に見えるといった要素も出てくると思います。
このような動きに遊びを加えようとするとそのための計算や処理が必要になります。特に凝った動きではそれ自体かなり大変な実装になることが多いと思います。
Mixed Reality Toolkit V2.5.0からは上記のような”遊び”を表現することができるElastic Systemが試験的な機能として提供されています。
今回はこれらを利用した実装に関する情報を共有したいと思います。
Elastic Systemとは
Elastic=弾力性という名前の通り、弾性表現を可能にするための一連のシステムになります。現時点では試験的な機能として提供されています。このElastic Systemの特徴は、移動/サイズ調整/回転などのオブジェクトの操作に弾性表現を加えることができます。先ほどの動画でもわかる通り、移動の場合であれば操作時の勢いによって、物体がばねの惰性を受けたような動きを加えることが可能になります。これにより物体の動きに”遊び”を入れることで動きを自然に見せることができます。
Mixed Reality Toolkitで提供されるElastic Systemは大きくは以下の2つの機能が提供されています。
種類 | 内容 |
---|---|
Object Manipulator | オブジェクトを操作時にElastic Systemを導入することで動きに弾性表現を加えることができます。 |
ユーティリティ | 現在の状態から弾性表現であらわされる"次の状態"を返すユーティリティ。上記以外の用途で活用したい場合に計算値のみ利用することができます。 |
いずれの場合も共通してばね定数などの設定があります。
Elastic Systemのパラメータ
弾力性を制御するためにElastic Systemにはいくつかのパラメータを設定することができます。
- Mass
- Hand K
- End K
- Snap K
- Drag
Mass
物体の重さ。慣性力に影響します。重い方が動き始めが緩慢になり、操作後は慣性によって勢いがついているため、操作とやめた地点を超えて物体が移動します。その後以下のばね定数に従って減衰していくことになります。
![]() |
![]() |
---|---|
Mass : 0.02 | Mass : 0.2 |
Hand K
手の動きに対するばね定数。手の操作に対して物体に力が加わるまでの強さを調整する。数値が小さい方がバネの力が弱く、手の動きに対して力が遅れて徐々にかかるようになります。あまり大きい値を入れると発振したようにブルブル震える感じで不安定になります。
![]() |
![]() |
---|---|
Hand K : 0.1 | Hand K : 10 |
End K
Object ManipulatorでUse Boundを利用する際に有効になるばね定数。Use Boundが有効になるとオブジェクトが初期位置から一定の範囲内に固定されます。動画の青枠の立方体がその範囲になります。オブジェクトを動かすことは可能ですが、操作後Boundで指定された範囲の空間に引き戻されます。数値が小さい方がバネの力が弱く、より遠くまで引っ張れます。ばね定数が大きくても勢いよく引っ張ると遠くまで引くことはできますが、戻るときもかなりの勢いになります。
![]() |
![]() |
---|---|
End K : 1 | End K : 9 |
Snap K
スナップポイントのばね定数。スナップポイントは不可視の座標で物体が磁石で引っ付くように固定される位置になります。このスナップポイントから物体が抜け出す際のばね定数を指定することができます。小さい値を設定するとスナップポイントからすぐに抜け出せますが、ばね定数を大きくすると、スナップポイントから抜け出すためには勢いよく操作するか、かなり引っ張らないと抜け出せなくなります。
![]() |
![]() |
---|---|
Snap K : 2 | Snap K : 9 |
Drag
ドラッグ/ダンパー係数。数値が大きい程減衰が早くなる。例えば1を指定すると移動に対する減衰がむしろ抵抗となり移動に対してかなりゆっくりと物体が追従するような動きになる。逆に0.01等小さい値を設定すると、移動に対する勢いが減衰するまで時間が非常に長くかかる。
![]() |
![]() |
---|---|
Drag : 0.1 | Drag : 1 |
このように色々な設定を調整することで動きを調整することができます。
実装
次は実際にMixed Reality ToolkitのElastic Systemを利用して実装を行っていきたいと思います。
実装についてはObjectManipulatorで利用する方法と個別に研鑽する方法と2つあるのでそれらを紹介したいと思います。
開発環境
開発環境、利用するソフトウェアは以下の通りです。
- Windows 10 Pro バージョン1909
- Visual Studio 2019 バージョン 16.8.3
- Mixed Reality Toolkit V2.5.3(Elastic SystemはV2.5.0 later)
- Windows SDK 10.0.18362.0
MRTKの導入と初期設定
MRTKを利用するために導入と初期設定、プロジェクトの細々した設定をおこないます。
パッケージの導入
[プロジェクトフォルダ]\Packages\manifest.jsonに以下の記述を追加して、Unity Package Manager経由でセットアップを行ってください。
{
"scopedRegistries": [
{
"name": "Microsoft Mixed Reality",
"url": "https://pkgs.dev.azure.com/aipmr/MixedReality-Unity-Packages/_packaging/Unity-packages/npm/registry/",
"scopes": [
"com.microsoft.mixedreality",
"com.microsoft.spatialaudio"
]
}
],
"dependencies": {
"com.microsoft.mixedreality.toolkit.examples": "2.5.3",
"com.microsoft.mixedreality.toolkit.foundation": "2.5.3",
"com.microsoft.mixedreality.toolkit.tools": "2.5.3",
...
また、unitypackageも提供されています。URLを載せておきます。ここからダウンロードしてカスタムインポートでも同じことが可能です。
manifest.jsonを変更して保存するとMRTKがインポートされます。
インポート後はBuild SettingsでHoloLensにデプロイするための設定を行っておきます。
今回は軽いサンプルなので[Sample.scene]をbuildシーンとして追加しておきます。それ以外は以下のように設定。設定後「Switch Platform」で確定します。
設定 | 値 |
---|---|
Platform | Universal Windows Platform |
Target Device | HoloLens |
Architecture | ARM |
Build Type | D3D Project |
Target SDK Version | 10.0.18362.0 |
minimum Platform Version | 10.0.10240.0 |
Visual Studio Version | Lasted installed(Visual Studio 20919) |
最後にメニューから[Mixed Reality Toolkit]-[Utilities]-[Configure Unity Project]を選択し表示されたダイアログで[Apply]を押下します。
ダイアログ上にボタンが表示されていない場合はすでに正しい設定が完了しているのでそのまま閉じてください。
シーンの設定
次にMRTKの設定を有効にするためにメニューから[Mixed Reality Toolkit]-[Add Scene And Configure...]を選択しシーンにMRTKに必要な設定を取り込みます。
実行すると上記のようにオブジェクトが設定されます。
次にMRTKの設定を行います。MRTKではProfileで設定を切り替える形になっています。この際SDKをいくつか選択できるのですが、レガシーな手段とXRSDKを使うパターンです。
レガシーな方法は将来のUnityのバージョンで廃止になる予定の機能です。Unity 2019では、いずれのパターンでも問題なく使うことができます。
XRSDKを使う方法
[MixedRealityToolkit]オブジェクトを選択しProfileに[DefaultXRSDKConfigrationProfile]を設定します。
次に追加で以下のパッケージをインストールします。
- Windows XR Plugin
- XR Plugin Management
メニューから[Edit]-[Project Settings...]を開きます。サブメニューから[XR Plug-in Management]を選択しWindowsのplug-in Providerとして[Windows Mixed Reality]を設定します。
windwos Mixed Realityの設定はデフォルトのままでOKです。
legacyな方法(Player Settingsから行う以前からの方法)
[MixedRealityToolkit]オブジェクトを選択しProfileに[DefaultHoloLens2ConfigrationProfile]を設定します。
メニューから[Edit]-[Project Settings...]を開きます。サブメニューから[Player]を選択し[XR Settings]を設定します。
[Virtual Reality Supported]をチェックしSDKに[Windows Mixed Reality]を追加します。
Object ManipulatorでElastic Systemを利用する
次にObject ManipulatorでElastic Systemを利用するための実装をいくつかしたいと思います。最初に対象のオブジェクトにコンポーネントを設定します。
Hierarchyに操作対象となるGameObjectを生成します。
GameObjectを選択しInspector内の[Add Component]を押下して以下の3つのコンポーネントを追加します。
- ObjectManipulator
- NearInteractionGrabbable
- ElasticManager
追加後に[ObjectManipulator]内のパラメータ[Elastics]-[Elastics Manager]にこのGameObjectを割り当てます。
コンポーネントの追加は以上で完了です。
次にElastic Systemに必要なパラメータを定義するための設定を用意します。
Projectパネルの任意の場所で右クリックを押し[Create]-[Mixed Reality Toolkit]-[Experimental]-[Elastic]-[Elastic Configuration]を選択すると[ElasticConfiguration]が追加されます。この設定を利用することでElastic Systemに必要な設定を共通で行うことができます。
実際の操作に対して適用する際には[ElasticManager]の[Manipulation types using elastic feedback]パラメータに適用したい操作を割り当てます。例えば、移動時に弾性表現を加えたい場合は[Move]を選択します。設定は排他ではないので複数の操作を同時に設定可能です。Object Manipulator扱えるMove/Rotate/Scaleの3種類に対してElastic Systemを利用することが可能です。
上記の設定を行えば弾性表現をObject Manipulatorに対して導入することが可能になります。さらに、Elastic SystemではObject Manipulatorの操作に対して以下のパターンを構成することが可能です。
- 制約なし
- 範囲固定
- スナップ動作
(注意)現時点のElastic Managerを設定する際に以下の3つのパラメータの設定については一度値を変更しないとNullReference例外が発生しObjectr Manipulatorが無効になります。Elastic Managerの設定変更を行った際には以下の値の中にあるばね定数等を一度変更して元に戻す or 共通の設定を割り当てるなどしてオブジェクトがインスタンス化されている状況を作るようにしてください。
- Transition Elastic
- Rotation Elastic
- Scale Elastic
制約なし
すべての操作に適用できる設定です。
通常のObject Manipulatorの操作に弾性表現が入る形になります。この設定を有効にするためには[Manipulation types using elastic feedback]で有効にしたい操作(Move/Rotation/Scale)を設定するのみで利用できます。設定としてはExtentの設定をすべて行っていない状態で利用する形になります。
範囲固定
MoveおよびScale操作に適用できる設定です。
Object Manipulatorに対して制約を与えます。一定の範囲でオブジェクトを固定します。
例えばmoveの場合は指定の座標周辺でオブジェクトを固定することができ、操作しようとしても引き戻されるような効果を加えることができます。
この設定を有効にする場合はExtentの[Stretch Bounds]の値と[Use Bounds]を有効にする必要があります。
パラメータ | 説明 |
---|---|
Stretch Bounds - Center | オブジェクトを固定する空間の中心座標を設定します。World座標で設定します。 |
Stretch Bounds - Extent | オブジェクトを固定する空間の範囲を指定します。この範囲からはみ出たオブジェクトは引き戻されこの空間内に戻ってくるようになります。オブジェクト自体はこの設定値の範囲の中に納まるように動きます。 |
Use Bounds | 範囲固定の有効/無効を設定します。有効にすると上記の設定値の範囲にオブジェクトがバネで固定された可能ような動きになります。 |
スナップ動作
Object Manipulatorに対して制約を与えます。操作を連続的ではなく、一定の範囲で刻んだスナップポイントに従って動作します。スナップポイントは空間の座標に対して一定間隔で用意できるポイントでオブジェクトの操作時にポイント間で操作を行うことが可能になります。上記の動画(Snap K)のように格子状の点上を伝って動くような表現が可能になります。
この設定を有効にする場合はExtentの[Snap Points]と[Repeat Snap Points]および[Snap Radius]を設定する必要があります。
パラメータ | 説明 |
---|---|
Snap Points | スナップポイントとして指定する座標を設定します。スナップポイントは複数設定することができます。例えば、時計の文字盤状にポイントを構成すれば、文字盤の数字間を移動するオブジェクトなどが作成可能になります。 |
Repeat Snap Points | 設定したスナップポイントを繰返しで設定するかを設定します。スナップポイントの1点を使い、上記のような格子点として空間上に展開することができます。複数のスナップポイントが設定された状態でも利用可能ですが、スナップポイントの展開が複雑になるため意図した形にはなりにくいです。Snap Pointsを1点だけ登録し、それをリピートして格子状での利用するか、リピートなしで複数のSnap Pointsを割り当てる形のいずれかにしましょう。 |
Snap Radius | これはスナップポイントを中心にこの設定値を半径として、オブジェクトが吸い付くようになります。この半径はスナップポイント同士で重ならないように調整します。例えば、スナップポイント間が1となる場合はこの値を0.5にすることで操作時にどちらかのポイントに吸い付くようになります。 |
設定例1.範囲固定(Move)
範囲固定の場合は以下のパラメータを設定します。この例ではワールド座標(0,0,1)を中心とした10cm四方の立方体の範囲内でオブジェクトを固定する設定になります。
- Stretch Bounds : (0,0,1)
- Extent : (0.1,0.1,0.1)
- Use Bounds : true
設定例2.スナップ動作(Move) - 三角形の頂点移動
スナップ動作として三角形の頂点についつくような動きをする設定例です。この例では三角形の頂点にあたる座標をSnap Pointsとして登録しています。繰返しは無しで、スナップの半径は0.2と指定します。頂点を中心に0.2の範囲に入るとオブジェクトが吸い込まれて所定の位置に固定されます。
- Snap Points : (0,0.2,1), (0.2,-0.2,1), (-0.2,-0.2,1)
- Repeat Snap Points : false
- Snap Radius : 0.2
設定例3.スナップ動作(Move) - 格子状の頂点移動
スナップ動作として格子状に動く設定例です。0.1間隔でスナップポイントが設定されまる。スナップの半径は0.1です。格子状の点を辿っていきながら動くような操作を実現できます。
- Snap Points : (0.1,0.1,0.1)
- Repeat Snap Points : true
- Snap Radius : 0.1
移動時の例を紹介しましたが、ほかの操作でも同じように設定することが可能です。
Elastic Systemの計算値のみ利用する。
Object Manipulatorに利用するElastic Managerも内部ではばね定数の情報と現在のオブジェクトの状態を基に次のフレームでの状態を計算しています。この計算を行うためのユーティリティを利用することでObject Manipulator以外の独自の処理に弾性表現を加えることが可能です。
ユーティリティでは以下の3つのデータ型を扱うことが可能です。
データ型 | 使用するクラス | 概要 |
---|---|---|
float | LinearElasticSystem | float値を用いて設定されたばね定数に従って現在の値から次のfloat値を計算して返します。 |
Vector3 | VolumeElasticSystem | Vector3 値を用いて設定されたばね定数に従って現在の値から次のVector3 値を計算して返します。 |
Quaternion | QuaternionElasticSystem | Quaternion 値を用いて設定されたばね定数に従って現在の値から次のQuaternion 値を計算して返します。 |
今回はこの中でオブジェクトの移動と表示にElastic Systemを導入して表現を変えてみたいと思います。
オブジェクトの移動にElastic Systemを導入する
基本オブジェクトの移動につ入れてはTransformに加算していくだけになります。今回はシンプルに左から右に移動する物体にElastic Systemを入れてみたいと思います。
1方向だけの移動であれば、LinearElasticSystem でもいいですし、VolumeElasticSystem でやってもいいと思います。今回はVolumeElasticSystem を使ってみましょう。
オブジェクトを用意する
まず、Elastic Systemを使う場合と使わない場合でどういう動きになるかを比較できる形にしたいと思います。まず任意の空オブジェクトを作成します。その子要素として、空間に任意のオブジェクト(cube等)を2つ配置します。座標は(-0.5,0,0.5), (-0.5,0.2,0.5)にしましょう。
次にProjectパネルで新しいスクリプト[ElasticTransformSample.cs]を作成します。作成したら、最初に作った空オブジェクト(上図のGameObject)にドラッグ&ドロップでコンポーネントとしてセットします。
次に[ElasticTransformSample.cs]の実装を行いたいと思います。今回は作成した2つのオブジェクトを5秒おきに左右に移動させ一方にElasticSystemを導入して動きの違いを視覚的に確認します。まずコンポーネントに必要なフィールドを作成します。
[SerializeField]
private GameObject linearMoveObject;
[SerializeField]
private GameObject elasticMoveObject;
// The elastic properties of our springs.
private ElasticProperties elasticProperties = new ElasticProperties
{
Mass = 0.02f,
HandK = 4.0f,
EndK = 3.0f,
SnapK = 1.0f,
Drag = 0.2f
};
private VolumeElasticSystem elasticSystem;
private Vector3 initialEPos;
private Vector3 initialLPos;
private Vector3 elasticGoal;
private Vector3 linearGoal;
private float timer;
コンポーネントのパラメータとしては移動対象となるGameObject2つをInspectorで設定できるようにします。
Elastic SystemのパラメータについてはObject Manipulatorと同様です。VolumeElasticExtentクラスでスナップポイント周りの設定を行い、ElasticPropertiesクラスでばね定数を定義します。
次に開始時の初期設定を行いましょう。初期設定では各オブジェクトの始点(初期位置)と終点と計算します。今回は1m間隔を取ります。
また、Elastic SystemはVolumeEralticsSystemを利用します。
private void Start()
{
linearGoal = linearMoveObject.transform.position + new Vector3(1f, 0f, 0f);
initialLPos = linearMoveObject.transform.position;
elasticGoal = elasticMoveObject.transform.position + new Vector3(1f, 0f, 0f);
initialEPos = elasticMoveObject.transform.position;
elasticSystem = new VolumeElasticSystem(elasticMoveObject.transform.position, Vector3.zero, volumeExtent,
elasticProperties);
}
VloumeElasticSystemクラスはコンストラクタでいくつかの引数を持っています。
バネの基点はこのコンストラクタで設定された値になります。
elasticSystem = new VolumeElasticSystem([初期位置], [初期速度], [ElasticProperties設定したスナップポイント], [ElasticPropertiesで設定したばね定数]);
後はオブジェクトを移動する実装を行います。ElasticSystemでは最終状態と経過時間を与えることで次の状態を取得できます。これをUpdateメソッド内で実装します。
elasticMoveObject.transform.localScale = elasticSystem.ComputeIteration([終点], [経過時間(Time.deltaTime)]);
完全なコードは以下の通りです。
// Copyright (c) 2021 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php
using Microsoft.MixedReality.Toolkit.Experimental.Physics;
using UnityEngine;
public class ElasticTransformSample : MonoBehaviour
{
[SerializeField]
private GameObject linearMoveObject;
[SerializeField]
private GameObject elasticMoveObject;
// The elastic properties of our springs.
private ElasticProperties elasticProperties = new ElasticProperties
{
Mass = 0.02f,
HandK = 4.0f,
EndK = 3.0f,
SnapK = 1.0f,
Drag = 0.2f
};
private VolumeElasticSystem elasticSystem;
private Vector3 initialEPos;
private Vector3 initialLPos;
private Vector3 elasticGoal;
private Vector3 linearGoal;
private float timer;
// The elastic extent for our backplate scaling.
private VolumeElasticExtent volumeExtent = new VolumeElasticExtent
{
SnapPoints = new Vector3[] { },
RepeatSnapPoints = false,
SnapRadius = 0
};
// Start is called before the first frame update
private void Start()
{
linearGoal = linearMoveObject.transform.position + new Vector3(1f, 0f, 0f);
initialLPos = linearMoveObject.transform.position;
elasticGoal = elasticMoveObject.transform.position + new Vector3(1f, 0f, 0f);
initialEPos = elasticMoveObject.transform.position;
elasticSystem = new VolumeElasticSystem(elasticMoveObject.transform.position, Vector3.zero, volumeExtent,
elasticProperties);
}
// Update is called once per frame
private void Update()
{
if (timer < 5)
{
elasticMoveObject.transform.position = elasticSystem.ComputeIteration(elasticGoal, Time.deltaTime);
linearMoveObject.transform.position = linearGoal;
}
else if (timer < 10)
{
elasticMoveObject.transform.position = elasticSystem.ComputeIteration(initialEPos, Time.deltaTime);
linearMoveObject.transform.position = initialLPos;
}
else
{
timer = 0;
}
timer += Time.deltaTime;
}
}
最後にUnity Editorで[ElasticScaleSample]コンポーネントに、2つのオブジェクトを[linearMoveObject], [elasticMoveObject]に割り当てます。
後は再生ボタンを押すと、上記のようなオブジェクトが左右に動きます。
オブジェクトの表示にElastic Systemを導入する
オブジェクトの表示に利用する際も基本的には移動の場合と変わりません。positionの値を変更していた代わりに、localScaleの値をVolumeElasticSystemを使って変更することになります。実装方法は上記と変わりません。空オブジェクトの作成、Scale変更を行う2つのオブジェクトを追加します。スクリプトとして[ElasticScaleSample.cs]を追加し、以下の通り実装します。
// Copyright (c) 2021 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php
using Microsoft.MixedReality.Toolkit.Experimental.Physics;
using UnityEngine;
public class ElasticScaleSample : MonoBehaviour
{
private Vector3 elasticGoal;
// The elastic properties of our springs.
private ElasticProperties elasticProperties = new ElasticProperties
{
Mass = 0.02f,
HandK = 4.0f,
EndK = 3.0f,
SnapK = 1.0f,
Drag = 0.2f
};
[SerializeField]
private GameObject elasticScaleObject;
private VolumeElasticSystem elasticSystem;
private Vector3 linearGoal;
[SerializeField]
private GameObject LinearScaleObject;
private float timer;
// The elastic extent for our backplate scaling.
private VolumeElasticExtent volumeExtent = new VolumeElasticExtent
{
SnapPoints = new Vector3[] { },
RepeatSnapPoints = false,
SnapRadius = 0
};
// Start is called before the first frame update
private void Start()
{
linearGoal = LinearScaleObject.transform.localScale * 3;
elasticGoal = elasticScaleObject.transform.localScale * 3;
elasticSystem = new VolumeElasticSystem(elasticScaleObject.transform.position, Vector3.zero, volumeExtent,
elasticProperties);
}
// Update is called once per frame
private void Update()
{
if (timer < 5)
{
elasticScaleObject.transform.localScale = elasticSystem.ComputeIteration(elasticGoal, Time.deltaTime);
LinearScaleObject.transform.localScale = linearGoal;
}
else if (timer < 10)
{
elasticScaleObject.transform.localScale = elasticSystem.ComputeIteration(Vector3.zero, Time.deltaTime);
LinearScaleObject.transform.localScale = Vector3.zero;
}
else
{
timer = 0;
}
timer += Time.deltaTime;
}
}
最後にUnity Editorで[ElasticScaleSample]コンポーネントに、2つのオブジェクトを[linearScaleObject], [elasticScaleObject]に割り当てます。
後は再生ボタンを押すと、上記のようなオブジェクトが表示/非表示(サイズ変更)が交互に実行されます。その違いを確認してみてください。
まとめ
Elastic Systemどうですか?いくつかサンプルや動きを見せましたが、派手に使うのではなくさりげないところにこういうギミックを仕込むことでコンテンツの与える印象が大きく変わると思います。特に、オブジェクトの出現のさせ方など、徐々に出したい場合などの実装が格段に楽なるのがわかるかと思います。他にもいろいろな用途が考えられるので一度活用してみてください。