MRが楽しい

MRやVRについて学習したことを書き残す

hololensでMMDモデルにダンスを踊ってもらうのに適切なポリゴン数は?

前回記事の続きです。前回、ポリゴン数の増減がダンスのfpsに大きく影響していることが分かりました。
bluebirdofoz.hatenablog.com

次に気になるのは「じゃあダンスを踊ってもらうMMDモデルで適切なポリゴン数はいくつなの?」ってことです。

比較のため、以下の4種類のモデルを用意しました。

1.2万ポリゴン、剛体モーションありのモデル
2.1万ポリゴン、剛体モーションありのモデル
3.5千ポリゴン、剛体モーションありのモデル
4.2千ポリゴン、剛体モーションありのモデル

前回同様、ポリゴン数の削減についてはなるべく他の要素は等価になるよう、BlenderのDecimateモディファイアを利用しています。
シェーダについてはmmd_tools利用時デフォルトのStandardのシェーダを利用しています。
背景オブジェクト等は一切なしで、カメラとライトを準備し、取り込んだモデルにダンスを踊ってもらいます。

それぞれのモデルをhololens上で躍らせた結果、以下の通りとなりました。

1.2万ポリゴン、剛体モーションありのモデル
f:id:bluebirdofoz:20170717172918j:plain
15fps以下のまま(おそらく10fps以下)。かなりカクカク。
ただ動きの繋がりは分かるのでゲームモーションなど、動きそのもので魅せる訳でなければそれほど気になりません。

2.1万ポリゴン、剛体モーションありのモデル
f:id:bluebirdofoz:20170717173128j:plain
10fps~15fps辺り。2万ポリゴンよりマシだがやはり気になる。

3.5千ポリゴン、剛体モーションありのモデル
f:id:bluebirdofoz:20170717172932j:plain
10fps~20fps辺り。それなりに見れる印象。この辺りがギリギリラインかな。

4.2千ポリゴン、剛体モーションなしのモデル
f:id:bluebirdofoz:20170717173148j:plain
20~30fps辺り。快適と言っていいと思います。背景が普通に見えるからか、VRほど高fpsなくても脳が補正してくれるのかも。


結論としては、キャラクタに激しいモーションをさせる場合、2千ポリゴンが適正かなという考えに至りました。
プロジェクトには更に背景オブジェクトなども加わることを考えると5千ポリゴンはそれだけで一杯一杯になりそうです。
ただ、シェーダによってはもう少し改善も可能かもしれません。

これはhololensを使う一人のユーザとしては残念なことです。
どうやらまだまだMMDの世界をそのままに現実世界に呼び込むのは難しそうです。(飛び込むならハイエンドVRで実現できますが)

一方で、holoelnsの開発者としては朗報と言えます。
hololensアプリの3Dアプリは、インターネット上に溢れるMMDの高精細な3Dモデルと同じ土俵で戦うことにはならないということです。(ひとまず現状は……)

以前より、オリジナルキャラクタをhololensに登場させることを目標と掲げていましたが。
内心、MMDの3Dモデルと比較すると見劣りするのが分かり切っていることに頭を悩ませていました。
しかしhololensにおいては、3Dモデルをhololens向けに自作すると言う発想は計らずもアリな考えだった訳です。
bluebirdofoz.hatenablog.com

今後も継続して、blenderでの3Dモデル作成学習に力を入れていきます。


追記。
シェーダによる改善の可能性の有無だけは確認しておこうと、シェーダを差し替えたテストを実施しました。

5.2万ポリゴン、剛体モーションありのモデル(Unlit/Texture)

最も低コストであるはずのUnlit/Textureシェーダを利用しました。要はテクスチャをそのまま描画。
・Unityシェーダのパフォーマンス
 http://www.mikame.net/sample/unity_documentation/Components/shader-Performance.html

結果は。

5.2万ポリゴン、剛体モーションありのモデル(Unlit/Texture)
f:id:bluebirdofoz:20170717181804j:plain
15fps以下のまま(おそらく10fps以下)。という訳でStanderdシェーダとあまり変化なしでした。
シェーダの改造で詰めていくのは難しそうです。


最後に一点、ポリゴン数を削減したMMDモデルはより取り扱いに注意が必要です
モデルによってはボーンや衣装の変更を認めていても、ポリゴン数削減等の改造を禁止しているものがあります。
本記事でもポリゴン数を削減したモデルの実動作について、キャプチャを控えています。
人が描いた絵を解像度下げて使うようなものですので。

hololensでMMDモデルにダンスを踊ってもらう

今回はMMDモデルのダンス枠です。
前回ユニティちゃんにPerfumeを踊ってもらうことができたので、MMDモデルに踊ってもらうことに技術的な新要素はありません。
bluebirdofoz.hatenablog.com

(追記)
あわわ。本記事内の注意書きが足りないとのツッコミに気付いて追記です。
MMDの利用の注意点について以下の記事で述べています。MMD系の技術は利用前に必ずツール、モデル、モーション毎の利用規約の確認をお願いします。
bluebirdofoz.hatenablog.com

さて、それでもMMDを使いたいと考えるのはキャラクタの完成度、ひいてはダンスの完成度が非常に高いからです。
純粋な観客として見たとき、PerfumeのダンスとMMDのダンスのどちらに感動するかと自身に問いてみると、やはりMMDです。
技術的にどちらが凄いかなんて観客は知ったこっちゃありません。難儀です。

さて、話を戻します。

まず、MMD4Mecanimの使用ですが、やはり取りやめました。
理由としてはhololens向けにビルドしようとしたところ、大量のエラーが発生したためです。
f:id:bluebirdofoz:20170717061438j:plain
以下のメッセージを見ると、意図的に利用できないようにしているようです。
f:id:bluebirdofoz:20170717061446j:plain

よって今回のテストプレイにはBlenderのアドオンであるmmd_toolsを利用しています。
github.com

基本的に以下の記事で実施した内容の再実施ですが、新たに確認した情報を備忘録として残しておきます。
bluebirdofoz.hatenablog.com

まずmmd_tools取り込み後、以下の Build ボタンを押すことで剛体シミュレーションが行えるようになります。
f:id:bluebirdofoz:20170717061513j:plain
分かりやすく言えば、キャラクタの髪などの揺れ表現を実現します。
キャラクタをダンスで利用する際は有効にしておいた方がいいでしょう。

Unityに取り込むとColliderがセットで付いてきますが、Colliderは関連していないようなので消してしまっても剛体モーションは動作します。
f:id:bluebirdofoz:20170717061533j:plain

エクスポートしたFBXファイルを取り込む際の注意点です。
自分の場合そのまま新規プロジェクトに取り込むとキャラクタのテクスチャが取り込めていませんでした。
f:id:bluebirdofoz:20170717061542j:plain
予め、テクスチャファイルを用意した上でインポートする必要があるようです。
f:id:bluebirdofoz:20170717061550j:plain

次に、mmd_toolsで読み込んだキャラデータは1m=1posの大きさになっていません。
基準となるオブジェクトがないので目安ですが、大体サイズを0.4倍しておけばそれっぽいサイズになります。
f:id:bluebirdofoz:20170717061559j:plain

そうして、ようやくhololens上で動きを見ることができました……が。
動きがカックカックです。止まると言う程ではありませんが、大きな動きをするダンスだと流石に気になります。
録画なんぞしたらコマ送りになりそうな勢いです。

そもそもMMDモデルとモーションは元々高性能なPCで時間をかけてレンダリングすることを想定しています。
hololensの中でリアルタイムに演算させたらこうなるのは当然でした。

となれば、プログラムを軽量化するほかありません。
ツールが何であれ。結局のところ、調整が必要ということですね。
調整方針の決定のために、まずはhololens上での処理負荷の原因について探ります。

比較のため、以下の4種類のモデルを用意しました。

1.2万ポリゴン、剛体モーションありのモデル
2.2万ポリゴン、剛体モーションなしのモデル
3.2千ポリゴン、剛体モーションありのモデル
4.2千ポリゴン、剛体モーションなしのモデル

ポリゴン数の削減についてはなるべく他の要素は等価になるよう、BlenderのDecimateモディファイアを利用しています。
シェーダについてはmmd_tools利用時だとStandardのシェーダが利用されていたため、とりあえずそのままとしました。

これらのモデルでhololens上の動作確認を実施しします。早速、以下のチュートリアルで学んだProfilerが役立ちます。
bluebirdofoz.hatenablog.com

目安としてオブジェクトが何もない状態のPlofilerを貼付します。
f:id:bluebirdofoz:20170717163324j:plain
通常時は凡そ100fps。fpsが落ちている不安定な後半部分は顔振りを試しています。

それぞれのモデルをhololens上で躍らせた結果、以下の通りとなりました。

1.2万ポリゴン、剛体モーションありのモデル
f:id:bluebirdofoz:20170717163336j:plain
15fps以下のまま。

2.2万ポリゴン、剛体モーションありのモデル
f:id:bluebirdofoz:20170717163359j:plain
15fps以下のまま。

3.2千ポリゴン、剛体モーションありのモデル
f:id:bluebirdofoz:20170717163412j:plain
凡そ30fps。

4.2千ポリゴン、剛体モーションなしのモデル
f:id:bluebirdofoz:20170717163422j:plain
凡そ30fps。

ポリゴン数の変化は顕著に影響が出ます。逆に剛体モーションのありなしはそれほど問題ではないようです。
処理箇所を見比べると、Graphics.PresentAndSyncの処理が大きく変化しています。
合わせてCPUの方を確認すると、HololensWaitForGPUの処理時間が長いことが分かります。
f:id:bluebirdofoz:20170717163611j:plain
1.2万ポリゴン、剛体モーションありのモデル 実行時のCPU状況です

半ば分かっていたことですが、原因はGPU側やはり描画の問題の方が大きいということですね。
おいかけっこアプリで問題として検出できなかったのは簡易なゲームモーションではfpsの低下が気にならなかっただけのようです。

ついでにポリゴン数を色々変えてみました。記事が長くなったので分けます。
bluebirdofoz.hatenablog.com

ユニティちゃんにPerfumeを踊ってもらう

ひと昔前、話題になりましたが、インターネット上にPerfumeのダンスモーションデータが無償公開されています。
japanese.engadget.com

前回、MMDのダンスモーションのUnityへの取り込みに失敗しましたが、こちらはUnityに取り込めるのでしょうか。
bluebirdofoz.hatenablog.com

結論、あっさり取り込めました。
f:id:bluebirdofoz:20170715040918g:plain
以下に改めて手順を書き出しておきます。

まずは以下からダンスモーションデータをダウンロードします。
www.perfume-global.com

モーションデータの形式はBVH形式となります。この形式の特徴はダンスの動作データとボーン構造が一つのファイルに含まれる点です。
・MOCAPデータファイル - TMPSwiki
 http://mukai-lab.org/wp-content/uploads/2014/04/MotionCaptureDataFile.pdf

早速、Blenderでデータを読み込みます。デフォルトでインポートから読み込み可能です。
f:id:bluebirdofoz:20170715040938j:plain

前述の通り、BVHを読み込むだけで、ボーンの構造体とモーションデータの両方が取得できます。
因みにそのままだとボーン構造がかなり大きいのでサイズ調整しています。
f:id:bluebirdofoz:20170715040955j:plain
肝心のボーン構造は……下半身部分のボーンが下から上に伸びる形になっているのが分かります。
その他もHuman mecanimのRigify構造に近いです。これは行ける気がします。
因みに、キーフレームがめちゃくちゃ多いのはモーションキャプチャで取得したデータだからと思われます。

これをそのままFBX形式のファイルとして書き出します。
f:id:bluebirdofoz:20170715041006j:plain

因みにこのとき、エクスポートの際、「アーマチュア」のみを選択してエクスポートすると、より良いです。
f:id:bluebirdofoz:20170715041029j:plain
こうすると今回必要なデータである、ボーン構造とモーションのデータのみをファイル出力できます。

Unityのプロジェクトに作成したFBXファイルをインポートします。
f:id:bluebirdofoz:20170715041056j:plain

FBXファイルを確認します。
Rigタブを選択すると、デフォルトでは Animation Type が「Generic」に設定されていることが分かります。
f:id:bluebirdofoz:20170715041110j:plain
また、ボーン構造のオブジェクトとモーションデータが含まれているのが確認できます。

さて、MMDモデルの時と同様、Human mecanimの適用を試みます。
Animation Type に「Humanoid」を選択して Apply をクリックします。
f:id:bluebirdofoz:20170715041120j:plain

反映が完了したら Configure をクリックして適用状況を確認すると……。
f:id:bluebirdofoz:20170715041133j:plain
めちゃ綺麗に適用されてます。Perfume編、完。
まぁボーン構造を見るに、初めからRigifyの構造を意識しているものと思われます。無駄なボーンも一切ありませんし。
この辺り、流石というか。MMDが特殊すぎるのか。

さて、これで取り込んだモーションデータはHumanoidリグに関連付いたモーションデータとなりました。
早速ユニティちゃんに使ってみましょう。
f:id:bluebirdofoz:20170715041149j:plain

ダンスモーションのための Animator Controller を作成します。
Projectのフォルダ内で右クリック->Create->Animator Controllerで新規のコントローラを作成します。
f:id:bluebirdofoz:20170715041200j:plain
Perfume Controller」とでも名付けておきます。

Animator Controllerにダンスモーションを設定します。
Perfume Controller」をダブルクリックで開き、ダンスモーションファイルをドロップで設定するだけです。
f:id:bluebirdofoz:20170715041220j:plain

ユニティちゃんのAnimator Controllerとして「Perfume Controller」を設定します。
f:id:bluebirdofoz:20170715041231j:plain

作業完了です。ゲームを再生してみると、ユニティちゃんが踊ってくれました。
f:id:bluebirdofoz:20170715041242j:plain

MMDのモーションデータに比べると非常にスムーズに利用可能です。これはやはり元々のボーン構造がRigifyに合っているからです。
逆に言うと、Perfumeのモーションはボーン構造が異なるMMDモデルには流用が難しいことになります。

この記事を書く前、Perfumeのダンスモーションを使った動画を探してみたのですが、ほとんど見つかりませんでした。
これは3D動画作成ツールとしてはメジャーなMMDでモーションが利用できないためだったということでしょう。
メジャー(であるはずの)ボーン規格でモーションデータを作成すると、よりメジャーな3D動画作成ツールで流用できないため、データが3D動画に利用されない……難儀な話です。

blenderのモディファイアをまとめる(シミュレート)

本日は夏風邪気味なので書き溜めしていた記事です。
モディファイアの変形機能について、調べた結果をまとめます。
★マークのものは個人的によく使いそう、または、抑えておくべきと感じた機能です。

●シミュレート
・クロス
 平面メッシュなどに設定し、布の挙動を模倣する計算を行う。
 アニメーションだけでなく静止画の作成でも役立つ。
 - Blender物理シミュレーション(Cloth)
  http://cg.xyamu.net/Blender/entry219.html
 - クロス(Cloth)シミュレーション
  https://wiki.blender.org/index.php/Doc:JA/2.6/Manual/Physics/Cloth
 - 【Blender】基本的な『クロス』モディファイアーの使い方【覚書】
  http://madeinpc.blog50.fc2.com/blog-entry-1023.html

コリジョン
 障害物にしたいオブジェクトに設定できるシミュレーション機能。
 ソフトボディやクロスなどと合わせて物体が他の物体にぶつかるシーンを作成できる。
 - Blender物理シミュレーション(Collision)
  http://cg.xyamu.net/Blender/entry265.html
 - Blender でスカートや髪を物理で動かす方法まとめ
  http://dskjal.com/blender/control-hair-and-skirt-with-physics.html

・ダイナミックペイント
 他オブジェクトとの影響による形状変化などを加える機能。
 - Blender物理シミュレーション(Dynamic Paint)
  http://cg.xyamu.net/Blender/entry245.html
 - Dynamic Paint Canvas
  https://wiki.blender.org/index.php/Doc:JA/2.6/Manual/Physics/Dynamic_Paint/Canvas
 - ダイナミックペイントで爆発【Blenderの便利な機能を超簡単に紹介】
  http://ch.nicovideo.jp/Arasen/blomaga/ar590187

・爆発
 オブジェクトが爆発したように飛び散るモディファイア。
 パーティクルと併用して破片の動きを制御する。
 - Blenderモディファイア(Explode)
  http://cg.xyamu.net/Blender/entry263.html
 - Explode モディファイア
  https://blender.jp/modules/xfsection/index.php/content0221.html?page=print

・流体シミュレーション★
 水などの液体の挙動を模倣する機能。
 流体をシミュレーションする為の空間となるオブジェクトと、流体の初期形状となるオブジェクトが必要となるす。
 - Blender物理シミュレーション(Fluid)
  http://cg.xyamu.net/Blender/entry247.html
 - 流体ドメイン
  https://wiki.blender.org/index.php/Doc:JA/2.6/Manual/Physics/Fluid/Domain
 - グラスに水を注いでみる 〈流体シミュレーション〉
  http://3d-memo.blog.jp/archives/1004692853.html

・海洋
 海を素早く生成し、アニメーションを作成する事も可能なモディファイア。
 - Blenderモディファイア(Ocean)
  http://cg.xyamu.net/Blender/entry244.html
 - オーシャンシミュレーション
  https://wiki.blender.org/index.php/Doc:JA/2.6/Manual/Modifiers/Simulate/Ocean
 - blenderで海
  http://kurone.info/blog/archives/455

・パーティクルコピー
 オブジェクトをパーティクルの位置に合わせて複製するモディファイア。
 - Blenderモディファイア(ParticleInstance)
  http://cg.xyamu.net/Blender/entry266.html
 - パーティクルインスタンス(Particle Instance)モディファイア
  https://wiki.blender.org/index.php/Doc:JA/2.6/Manual/Modifiers/Simulate/Particle_Instance

・パーティクルシステム
 パーティクルは面や頂点から粒子(パーティクル)を放出し、
 さらに連続して描画する事でStrand(糸状)にしてHair(髪の毛など)を作成できる機能。
 - Blender物理シミュレーション(Particle)
  http://cg.xyamu.net/Blender/entry180.html
 - パーティクル(Particles)
  https://wiki.blender.org/index.php/Doc:JA/2.6/Manual/Physics/Particles
 - 海外チュート備忘録:芝生(パーティクル)を作成する~形状と色合い調節あれこれ~
  http://blender.trial.jp/archives/1737

・煙
 煙を発生させるシミュレーション機能。
 2.5から搭載された機能で、2.65からはパーティクル無しで煙を簡単に発生させられるようになっている。
 - Blender物理シミュレーション(Smoke)
  http://cg.xyamu.net/Blender/entry277.html
 - 煙(smoke)シミュレーション
  https://wiki.blender.org/index.php/Doc:JA/2.6/Manual/Physics/Smoke
 - ダイナミックペイントで爆発【Blenderの便利な機能を超簡単に紹介】
  http://ch.nicovideo.jp/Arasen/blomaga/ar590187

・ソフトボディ★
 ぶつかると形が変わるような柔らかい物体をシミュレートする機能。
 ソフトボディ用のオブジェクトと、衝突される側のオブジェクトが必要となる。
 - Blender物理シミュレーション(Soft Body)
  http://cg.xyamu.net/Blender/entry248.html
 - 柔体(Soft Body)シミュレーション
  https://wiki.blender.org/index.php/Doc:JA/2.6/Manual/Physics/Soft_Body
 - Blenderでソフトボディシミュレーションをするときの手順と注意点
  http://qcganime.web.fc2.com/BLENDER/SoftBody04.html

公式チュートリアル「HOLOGRAMS 230 3章」を試してみる

本日はチュートリアルお試し枠です。
いつも通り、以下ブログの記事を参考に実施します。
azure-recipe.kc-cloud.jp

今回はSpatialMappingの処理プログラムを確認します。
記事の通りアプリを修正してプログラムを実行してみます。

最初の起動時はマップオブジェクトがそのまま読み込まれます。
f:id:bluebirdofoz:20170713003228j:plain
そのまま10秒待機すると……。
f:id:bluebirdofoz:20170713003237j:plain
床と壁、テーブルと天井が認識されました。

床にはオブジェクトが配置されています。
f:id:bluebirdofoz:20170713003245j:plain
床、壁、天井の検出精度はなかなかのものですね。
テーブルについてはそれなりに大きなものしか認識されていません。

コードを確認します。以下が平面の検出処理です。
・SurfaceMeshesToPlanes.cs

        /// <summary>
        /// Iterator block, analyzes surface meshes to find planes and create new 3D cubes to represent each plane.
        /// </summary>
        /// <returns>Yield result.</returns>
        private IEnumerator MakePlanesRoutine()
        {
            // Remove any previously existing planes, as they may no longer be valid.
            for (int index = 0; index < ActivePlanes.Count; index++)
            {
                Destroy(ActivePlanes[index]);
            }

            // Pause our work, and continue on the next frame.
            yield return null;
            float start = Time.realtimeSinceStartup;

            ActivePlanes.Clear();

            // Get the latest Mesh data from the Spatial Mapping Manager.
            List<PlaneFinding.MeshData> meshData = new List<PlaneFinding.MeshData>();
            List<MeshFilter> filters = SpatialMappingManager.Instance.GetMeshFilters();

            for (int index = 0; index < filters.Count; index++)
            {
                MeshFilter filter = filters[index];
                if (filter != null && filter.sharedMesh != null)
                {
                    // fix surface mesh normals so we can get correct plane orientation.
                    filter.mesh.RecalculateNormals();
                    meshData.Add(new PlaneFinding.MeshData(filter));
                }

                if ((Time.realtimeSinceStartup - start) > FrameTime)
                {
                    // Pause our work, and continue to make more PlaneFinding objects on the next frame.
                    yield return null;
                    start = Time.realtimeSinceStartup;
                }
            }

            // Pause our work, and continue on the next frame.
            yield return null;

#if !UNITY_EDITOR && UNITY_METRO
            // When not in the unity editor we can use a cool background task to help manage FindPlanes().
            Task<BoundedPlane[]> planeTask = Task.Run(() => PlaneFinding.FindPlanes(meshData, snapToGravityThreshold, MinArea));
        
            while (planeTask.IsCompleted == false)
            {
                yield return null;
            }

            BoundedPlane[] planes = planeTask.Result;
#else
            // In the unity editor, the task class isn't available, but perf is usually good, so we'll just wait for FindPlanes to complete.
            BoundedPlane[] planes = PlaneFinding.FindPlanes(meshData, snapToGravityThreshold, MinArea);
#endif

            // Pause our work here, and continue on the next frame.
            yield return null;
            start = Time.realtimeSinceStartup;

            float maxFloorArea = 0.0f;
            float maxCeilingArea = 0.0f;
            FloorYPosition = 0.0f;
            CeilingYPosition = 0.0f;
            float upNormalThreshold = 0.9f;

            if (SurfacePlanePrefab != null && SurfacePlanePrefab.GetComponent<SurfacePlane>() != null)
            {
                upNormalThreshold = SurfacePlanePrefab.GetComponent<SurfacePlane>().UpNormalThreshold;
            }

            // Find the floor and ceiling.
            // We classify the floor as the maximum horizontal surface below the user's head.
            // We classify the ceiling as the maximum horizontal surface above the user's head.
            for (int i = 0; i < planes.Length; i++)
            {
                BoundedPlane boundedPlane = planes[i];
                if (boundedPlane.Bounds.Center.y < 0 && boundedPlane.Plane.normal.y >= upNormalThreshold)
                {
                    maxFloorArea = Mathf.Max(maxFloorArea, boundedPlane.Area);
                    if (maxFloorArea == boundedPlane.Area)
                    {
                        FloorYPosition = boundedPlane.Bounds.Center.y;
                    }
                }
                else if (boundedPlane.Bounds.Center.y > 0 && boundedPlane.Plane.normal.y <= -(upNormalThreshold))
                {
                    maxCeilingArea = Mathf.Max(maxCeilingArea, boundedPlane.Area);
                    if (maxCeilingArea == boundedPlane.Area)
                    {
                        CeilingYPosition = boundedPlane.Bounds.Center.y;
                    }
                }
            }

            // Create SurfacePlane objects to represent each plane found in the Spatial Mapping mesh.
            for (int index = 0; index < planes.Length; index++)
            {
                GameObject destPlane;
                BoundedPlane boundedPlane = planes[index];

                // Instantiate a SurfacePlane object, which will have the same bounds as our BoundedPlane object.
                if (SurfacePlanePrefab != null && SurfacePlanePrefab.GetComponent<SurfacePlane>() != null)
                {
                    destPlane = Instantiate(SurfacePlanePrefab);
                }
                else
                {
                    destPlane = GameObject.CreatePrimitive(PrimitiveType.Cube);
                    destPlane.AddComponent<SurfacePlane>();
                    destPlane.GetComponent<Renderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
                }

                destPlane.transform.parent = planesParent.transform;
                SurfacePlane surfacePlane = destPlane.GetComponent<SurfacePlane>();

                // Set the Plane property to adjust transform position/scale/rotation and determine plane type.
                surfacePlane.Plane = boundedPlane;

                SetPlaneVisibility(surfacePlane);

                if ((destroyPlanesMask & surfacePlane.PlaneType) == surfacePlane.PlaneType)
                {
                    DestroyImmediate(destPlane);
                }
                else
                {
                    // Set the plane to use the same layer as the SpatialMapping mesh.
                    destPlane.layer = SpatialMappingManager.Instance.PhysicsLayer;
                    ActivePlanes.Add(destPlane);
                }

                // If too much time has passed, we need to return control to the main game loop.
                if ((Time.realtimeSinceStartup - start) > FrameTime)
                {
                    // Pause our work here, and continue making additional planes on the next frame.
                    yield return null;
                    start = Time.realtimeSinceStartup;
                }
            }

            Debug.Log("Finished making planes.");

            // We are done creating planes, trigger an event.
            EventHandler handler = MakePlanesComplete;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }

            makingPlanes = false;
        }

おそらく数学的な平面推定のあれこれはPlaneFinding.FindPlanes関数に隠蔽されています。
処理を追っていると、MinArea という平面の最小規模を定義している変数がありました。

この変数の値を小さくして、再び動作確認をしてみます。
f:id:bluebirdofoz:20170713003311j:plain
結果はさほど変わらず。
意味合いが違うのかな?と逆に変数の値を大きくして動作確認をします。
f:id:bluebirdofoz:20170713003322j:plain
こちらでは想定通り、検出される平面の数が減りました。

一定以下のサイズのものはそもそも平面として検出しづらいのかもしれません。

公式チュートリアル「HOLOGRAMS 230 2章」を試してみる

本日はチュートリアルお試し枠です。
いつも通り、以下ブログの記事を参考に実施します。
azure-recipe.kc-cloud.jp


今回はUnity上で様々なシェーダの動作を確認するプロジェクトです。
一つ目「BlueLinesOnWalls」LineScale:0.1 LinesPerMeter:4
f:id:bluebirdofoz:20170712000804j:plain

二つ目「BlueLinesOnWalls」LineScale:0.5 LinesPerMeter:4
f:id:bluebirdofoz:20170712000812j:plain

三つ目「BlueLinesOnWalls」LineScale:0.5 LinesPerMeter:1
f:id:bluebirdofoz:20170712000820j:plain

シェーダのコードを確認してみます。
因みにシェーダの書き方については以前に記事を書いています。合わせて確認します。
bluebirdofoz.hatenablog.com


・BlueLinesOnWalls.shader

Shader "Custom/BlueLinesOnWalls"
{
  Properties
  {
    _LineScale("LineScale", Float) = 0.1
    _LinesPerMeter("LinesPerMeter", Float) = 4
  }

  SubShader
  {
    Tags{ "RenderType" = "Opaque"}

    Pass
    {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag
      
      #include "UnityCG.cginc"

      // These values map from the properties block at the beginning of the shader file.
      // They can be set at run time using renderer.material.SetFloat()
      // これらの値は、シェーダファイルの先頭にあるプロパティブロックからマップされます。
      // これらの値は、renderer.material.SetFloat()を使用して実行時に設定できます。
      float _LineScale;
      float _LinesPerMeter;
      
      // This is the data structure that the vertex program provides to the fragment program.
      // 頂点シェーダがフラグメントシェーダに提供するデータ構造
      struct VertToFrag
      {
        float4 viewPos : SV_POSITION;
        float3 normal : NORMAL;
        float4 worldPos: TEXCOORD0;
      };


      // This is the vertex program.
      // 頂点シェーダ
      VertToFrag vert (appdata_base v)
      {
        VertToFrag o;

        // Calculate where the vertex is in view space.
        // 頂点がビュー空間内のどこにあるかを計算します。
        o.viewPos = UnityObjectToClipPos(v.vertex);

        // Calculate the normal in WorldSpace.
        // WorldSpaceの法線を計算します。
        o.normal = UnityObjectToWorldNormal(v.normal);

        // Calculate where the object is in world space.
        // オブジェクトがワールド空間内のどこにあるかを計算します。
        o.worldPos = mul(unity_ObjectToWorld, v.vertex);

        return o;
      }

      // フラグメントシェーダ
      fixed4 frag (VertToFrag input) : SV_Target
      {
        // Check where this pixel is in world space.
        // wpmod is documented on the internet, it's basically a 
        // floating point mod function.
        // このピクセルがワールド空間内のどこにあるかを調べます。
        float4 wpmodip;
        float4 wpmod = modf(input.worldPos * _LinesPerMeter, wpmodip);

        // Initialize to draw black with full alpha. This way we will occlude holograms even when
        // we are drawing black.
        // 完全透過の黒を描画するために初期化します
        fixed4 ret = float4(0,0,0,1);

        // Normals need to be renormalized in the fragment shader to overcome 
        // interpolation.
        // 補間を克服するには、法線をフラグメントシェーダで再正規化する必要があります。
        float3 normal = normalize(input.normal);

        // If the normal isn't pointing very much up or down and the position in world space
        // is within where a line should be drawn, draw the line.
        // Since we are checking wpmod.y, we will be making horizontal blue lines.
        // If wpmod.y was replaced with wpmod.x or wpmod.z, we would be making vertical lines.
        // 法線が極端に上または下を指していない場合、かつ、
        // ワールド空間内の位置が線を描画する範囲内にある場合は、線を描画します。
        // wpmod.yをチェックして、水平の青い線を描きます。
        // wpmod.yをwpmod.xまたはwpmod.zに置き換えた場合、垂直の線を作成することになります。
        if (abs(normal.y) < 0.2f && abs(wpmod.y) < _LineScale* _LinesPerMeter)
        {
          ret.b = 1 - (abs(wpmod.y) / (_LineScale* _LinesPerMeter));
          ret.r = 0;
          ret.g = 0;
        }

        return ret;
      }
      ENDCG
    }
  }
}

公式チュートリアル「HOLOGRAMS 230 1章」を試してみる

本日はチュートリアルお試し枠です。
いつも通り、以下ブログの記事を参考に実施します。
azure-recipe.kc-cloud.jp

以下ページからプロジェクトを取得します。
・Holograms 230
 https://developer.microsoft.com/en-us/windows/mixed-reality/holograms_230

この章はアプリの実行だけではなく、UnityのProfiler機能による性能確認を実施します。
まずはSpatialMappingの配置と、パフォーマンスの確認を実施します。

手順に従い、アプリを作成して実行してみます。
f:id:bluebirdofoz:20170711031208j:plain
hololensでは毎度おなじみ、SpatialMappingによる空間マッピングが行われます。

この状態でProfilerを設定すると……。
f:id:bluebirdofoz:20170711031218j:plain
HoloLensのパフォーマンスが確認できます。
フレームのレンダリングにかかるミリ秒数などが見れるのでアプリの性能確認に役立ちそうです。

次にSpatialMappingの[Room Model]プロパティを試します。
Mapオブジェクトは以前にDevicePortalから取得したものがあるので、それを流用します。

通常、Unityの再生でSpatialMappingを動かすと、何のマップオブジェクトも表示されません。
f:id:bluebirdofoz:20170711032544j:plain

そこで[Room Model]プロパティに表示させたいMapオブジェクトを設定します。
f:id:bluebirdofoz:20170711032553j:plain

その状態で再生すると……。
f:id:bluebirdofoz:20170711032601j:plain
設定したMapオブジェクトが表示されました。Unity上でのデバッグに使えそうです。