読者です 読者をやめる 読者になる 読者になる

BlenderなどのDCCツールからVR空間に3Dモデルを転送して立体的に観察しながらモデリングする方法

はじめに

 その2くらいに書いた、『VRデスクトップ上でモデリングしたデータをVR空間にインポートして、立体的にモデルを確認する』機能ですが、OBJのインポートなどをするより簡単で、汎用性のある方法を思いついたので、作ってみました。

色々な手法

 VR空間(Unityのランタイム)にモデルをインポートする方法として、いろいろ考えました。

  1. FBXSDKを使ってFBXのインポート。労力的に無理。アニメーションも含めると何日かかるかわからない。そもそもBlenderファイルが直接読めない。
  2. OBJファイルならファイルフォーマットも簡単なので、インポート出来る。しかしマテリアルまで考慮し始めるとそこそこ面倒になるし、ボーン付きメッシュは無理。
  3. UnityEditorで実行しているランタイムをサーバーとして接続し、インポートされたモデルをネットワーク経由で転送する。ボーンなどは位置情報だけ転送する。この方法だとUnityEditorで見ている内容そのまま転送することが出来るが、ネットワーク転送に手間が掛かりそうなのと、Unity純正のVREditorが出てきたら全く無意味になってしまうので却下
  4. UnityEditorでインポートしたファイルをAssetBundleとして書き出して、ランタイムのフォルダにコピー、ランタイム側ではファイルの更新時間を監視して変更があったらAssetBundleを再読み込みする方法。インポート周りはすべてUnityがやってくれるので、オブジェクトを生成さえすれば他は何もやる必要がない。

 というわけで、最も手間のかからなさそうな4番をやってみました。

UnityEditor上でのフォルダの監視

 例えばBlenderで[Ctrl+S]でblendファイルをアセットフォルダの中に保存するとします。しかしそのままではUnityはインポートしてくれません。インポートするには、UnityEditorのウィンドウをクリックしてフォーカスを入れてあげる必要があります(人間が行う場合)。
 Blenderで保存してモデルを確認というイテレーションは何度も行うことを想定しているので、たったのワンクリックとは言え面倒くさいものです(しかもBlenderにまた戻る必要があります)

 なのでBlender等で保存を行ったら、その保存されたというイベントをUnity側で捕捉し、プログラムで自動的にインポート、アセットバンドル書き出し、アセットバンドルのコピー、まで行うことを考えます。

フォルダの監視は

    bool WatchDirectory(string path)
    {
        var time = Directory.GetLastWriteTime(path);
        
       
        if((time - LastChanged).Ticks > 0)
        {
            Debug.Log("更新されました : " + Time.frameCount);
            LastChanged = time;
            return true;
        }
        return false;
    }

の関数をEditorWindowを拡張したクラス内のUpdateから呼び出します。
 EditorWindowのUpdateはUnityEditorがバックグラウンドになっていてもちゃんと実行されるからです。

アセットバンドル書き出し

tsubakit1.hateblo.jp
ここら辺を参考にしました。

VR側でのフォルダ監視

 VR側でフォルダの最終更新時間を監視しても全くうまくいかなかったのですが、これはコピーではフォルダの最終更新時間は変わらないからのようです。
 なのでVR側ではフォルダではなくアセットバンドルのファイルそのものの最終更新時間を調べて、違ったらインポートしています。

ソースです。まだすべて検証していないので、いろいろ無駄なことをやってると思います。あと現状インポートできるのはmonkeyというファイルだけです。

using UnityEngine;
using System.Collections;
using System.IO;

public class AssetBundleImporter : MonoBehaviour
{
    private System.DateTime LastChanged;

    private AssetBundle currentBundle;

    void LoadAsset()
    {
        if (currentBundle != null)
        {
            currentBundle.Unload(true);
        }
        for(int i = 0;i < transform.childCount;i++)
        {
            GameObject.Destroy(transform.GetChild(i).gameObject);
        }
        if (File.Exists(Application.streamingAssetsPath + "\\pentavivedata"))
        {
            currentBundle = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "\\pentavivedata");

            if (currentBundle != null)
            {
                var resource = currentBundle.LoadAsset<GameObject>("monkey");

                var go = (GameObject)GameObject.Instantiate(resource);


                go.transform.parent = this.transform;
                go.transform.position = Vector3.zero;
                go.transform.rotation = Quaternion.identity;
            }
        }
    }

    void Start()
    {
        LastChanged = System.DateTime.Now;
        LoadAsset();
    }
	
	void Update ()
    {
        if (WatchDirectory(Application.streamingAssetsPath))
        {
            LoadAsset();
        }
	}

    bool WatchDirectory(string path)
    {
        var time = File.GetLastWriteTime(path + "\\pentavivedata");


        if (time.Ticks != LastChanged.Ticks)
        {
            LastChanged = time;
            return true;
        }
        return false;
    }
}

ペンタVive上でBlenderを起動するとカクつく

 Blenderはそれ自体GPUに負荷がかかるので、DesktopCaptureの更新頻度が3フレームに1回ではVRがカクつきました。(R9 390)
なので今回は5フレームに1回の更新にすると、カクつくことはなくなりました。

        if (Time.frameCount % 5 == 0)
            updateScreenCapture = true;
        else
        {
            // 更新していない時にコピー
            Graphics.Blit(tex, rt);
        }

DesktopCaptureのタイムアウト時間が長すぎた

 また前回の最後にDesktopCaptureのせいで画面が点滅する、と書きましたが、これはSteamVRはVRアプリが応答しなくなるとデフォルトVR空間に戻ろうとするためで、これはDesktopCaptureのタイムアウト時間が500msと長いことが原因でした。
タイムアウト時間を5msにしたら直りました。

DLL側のソース抜粋

	const UINT timeout = 5; // ms
	HRESULT hr = g_deskDupl->AcquireNextFrame(timeout, &frameInfo, &resource);

結果

 f:id:IARIKE:20160517012049p:plain
DirectionalLight一個だけなので暗いですが、大体こんな感じになりました。
(Application.CaptureScreenShotで撮影したものです)

利点

  • 実装が超簡単!
  • Unityがインポートできるあらゆる形式のファイルを自由に持ってこれる(スクリプト以外)
  • Unityをインストールさせ、触らせ、多少はUnity勢を増やすことが出来る

欠点

  • UnityEditorをインストール、起動している必要があるし、使い方も少しは覚える必要アリ

まとめ

 急いで実装したのでまだソースコードを全公開できる状態にはありません。(ハードコーディングしている部分が多々)

 保存してからVRに反映されるまで、ポリゴン数にもよりますが大体数秒~10秒程度でした。テクスチャまで含めるともう少し遅くなってしまうかも。
 前回まで考えていた、VR空間でのお絵描きは、モデルを参考にできるのは利点ですが、当然そのモデルはどこからか手に入れてインポートする必要があります。モデルを変えるたびにわざわざアプリ落としてUnityでビルドして~とするのは考えられないので、こちらもアセットバンドルを使用することになると思いますが、それも今回のやり方を使えば、フォルダに拾ってきたFBXを突っ込むだけでVR空間に入れられる、ということになります。

 あとはViveコントローラでライトを追加するなり、モデルを移動できるようにするなり、IBLを設定するなどの機能があれば、面白いかもしれませんね~

 最後に、今回のアセットフォルダを監視して自動インポートする機能、BeSyncという少し高め(75$)のアセットでも似たようなこと(ウィンドウ切り替え無しでインポート)をしていると思います。なのでまぁ、少し工夫すれば似たようなものは作れてしまうかもしれませんね(もちろん自分で開発するより買った方が安いですけどね)