simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

UnityでMMDのPMXを表情モーフ付きでダンスするFBXとしてアセット利用する話

今回のトピックは Unity で MMD モデルにモーション付けて再生するというものです。

みなさんご存じ(?)のMikuMikuDance(MMD)と呼ばれるソフトウェアは、VPVPにて公開されているの3DCG動画作成ツールです。

表情豊かに歌って踊る3DCGコンテンツを探すと、ほとんどMMDに関わるものであり、データの形式はPMX, VMDです。
これらを Unity で扱う FBX 形式のデータに変換して再生する方法を紹介します。

まずは Unity 5.5.0f2 (64-bit) リリース: November 25, 2016 を起動します。
何もアセットが無い状態で、そのままだと空のシーンとなっています。

まず初めに行うのが Stereoarts Homepage さんのページに行き、MMD4Mecanim (Beta) の最新版を入手し
解説にあるチュートリアル(基本編)に従って操作し、任意の PMX ファイルを FBX に変換してシーンに配置します。

今回は任意の PMX に
3d.nicovideo.jp
からダウンロードしてきた、ぽんぷ長式の「村雨」を使うことにします。

チュートリアルを間違えなければ、このようにモデルを配置できると思います。

f:id:simplestar_tech:20161127160653j:plain

次に、踊ってもらいたい楽曲と楽曲用のモーションを選びます。

今回は Girls(EasyPop) にしようと思います。
楽曲を購入し(iTunesだと200円でした、やしぃー!)
iTunesの編集→設定メニューからインポート設定をいじってmp3に変換できるようにし、mp3ファイルを用意します。
これを Unity の Audio Source に設定すれば Audio Clip の完成です。

再生すると、ゲーム内で楽曲が響き渡ります。

次はモーションの VMD ファイルです。
bowlroll.net
こちらからダウンロードしてきました。

MMD4Mecanimのチュートリアルに従い、VMDを読み込ませてFBXのアニメーションとします。
変換にけっこう時間かかります。
出来上がったFBXにはアニメーションが含まれるようになっていて、これを Unity の Animator Controller のステートに設定すれば
ステート遷移したときに、踊ってくれるようになります。

表情やリップのVMDも一緒に変換していればFBXにアニメーションとして入っているのですが、モーフは再生されません。
f:id:simplestar_tech:20161127180733j:plain

最後に表情モーフをつける方法を紹介します。
対象となるキャラクターオブジェクトのコンポーネントに次のスクリプトコンポーネントを追加します。
MMD4MecanimAnimMorphHelper.cs
表情やリップのVMDの変換を終えていれば.animファイルが作られていますので、これらをそれぞれのMMD4MecanimAnimMorphHelperに設定します。
f:id:simplestar_tech:20161127181801j:plain
再生するアニメーション名の欄が空欄になっているので、それぞれ「表情」「リップ」を書き込みます。
(アニメーション名を任意に変更しているのであれば、その名前を使います)

※公式の記述を見つけていませんが、スクリプトコンポーネントを追加する順番でモーフは上書きされる仕様のようです。
リップが再生されない場合は、後から追加した AnimMorphHelperコンポーネントの方にリップを追加しましょう。
リップが表情に上書きされるようになり、口パクしてくれるようになると思います。(私の環境では、実際になりました。)

これで、全身のモーションに加えて、表情とリップのモーフが付くようになります。
f:id:simplestar_tech:20161127181655j:plain

おまけ:
購入した楽曲と、モーション付けに参照された曲がちょっとずれていました。
え、今までの人たちはオリジナルの曲を使っていない!?
仕方なく、私は購入した楽曲の余白を調整して、音ズレを回避しています。

参考までに mp3 ファイルの余白調整には
mp3DirectCut - Fast MP3 and AAC editor and MP3 recorder
を使いました。

UnrealEngine4.13で頂点シェーダー

頂点シェーディング方法を探して次のプロジェクトを見つけたのですが
github.com

確かにコンピュートシェーダーとピクセルシェーダは機能していました。
描画ターゲットに結果を書き出して利用する分にはこれが良いかもしれません。

しかし、ずいぶんとトライ&エラーしづらい作りのようです。

ふと World Position Offset に頂点座標ごとのオフセット量を与えられたら
自分の目的が達成できるのではないかと思い、確かめてみました。

こちら、自身の頂点座標(ローカル)の値を使って頂点位置をずらすマテリアルを適用した結果です。

f:id:simplestar_tech:20160912002916j:plain

できたじゃないですか。

どうやって?
こうやって↓

f:id:simplestar_tech:20160912003444j:plain

位置と法線が得られつつ、頂点位置を変更できるとなると
Shape Matching 法とか、弾性体表現が実装できそう。

今日は良い技法を発見しました。

VRHMDのポジショントラッキングについて

VRHMDといっても、今の時代
HTC Vive ですか?
Oculus Rift ですか?
PSVR ですか?
と大きく三つのVRHMDを想像できる時代となりました。
(え、そんなの知らない?そんな方は、この記事は読めないかも。)

今回は Oculus Rift や PSVR で使われている HMD のトラッキングシステムについて
実際に作れるところまで原理を詳しく解説していきたいと思います。

HMD をIR成分も撮像できるカメラ越しに見てみましょう。
すると、次のように小さなLEDが何個も光っていることが確認できます。

f:id:simplestar_tech:20160828175047j:plain
url:http://virtuix1.rssing.com/chan-14788923/all_p2.html

上の写真は Oculus DK2 の頃の様子
現在リリースされている最新の製品版 Rift では次のような配置になっています。

f:id:simplestar_tech:20160828175434j:plain
f:id:simplestar_tech:20160828175442j:plain
url:https://tinhte.vn/threads/ben-trong-oculus-rift-cv1-de-sua-chua-day-gon-gang-cham-diem-7-10.2566840/

これらの LED をカメラで追跡することで、HMDの位置姿勢を求めています。
ん?どのカメラ?

それは、以下のようなカメラです。

f:id:simplestar_tech:20160828175957j:plain
url:http://www.pcworld.com/article/2138461/eyes-on-oculus-rifts-position-tracking-dev-kit-2-vr-headset-blows-the-first-dev-kit-away.html

こちらは DK2 時代のものですね。

f:id:simplestar_tech:20160828180053j:plain
url:http://www.snapvrs.org/how-to-set-up-the-oculus-rift/

そしてこちらが Rift 製品版のカメラ

これらカメラなしでポジショントラッキングを実現しようとしても、できないことに気付くでしょう。
そう、先ほども述べたように、このカメラで HMD の複数の LED を追跡することで HMD の位置姿勢を求めています。

カメラで HMD の複数の LED を追跡することってどういうこと?
とイメージできない人が多いと思いますので、実際にそれを行っている様子が載ったページを以下に紹介します。

www.roadtovr.com

ここまでの話をまとめると、HMD の LED をカメラで追跡しているということです。
あとはこの追跡情報を使って HMD の位置姿勢を求める計算の部分に焦点を当てて、説明していきます。

f:id:simplestar_tech:20160828181651g:plain
url:http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/MARBLE/high/pia/solving.htm

イメージ平面に投影された影から元の三次元空間のオブジェクトの位置姿勢を求める計算をすることになります。
ここに良い例が示されていますが、3点の対応だけでは、姿勢が一意に定まらないことがイメージできます。

数学についての知識を得るときは、図形的解釈を伴うように、つねに右脳を働かせて問題をイメージすることが大切です。(たぶん)

そして、問題を具体的に解くためには定式化しなければなりません。
ここで現在のオブジェクトの位置姿勢から考えられるイメージ平面での投影点と、実際に観測されたイメージ平面での投影点とのイメージ平面での距離(イメージスペースエラー)を小さくするように、位置姿勢を修正していくことで問題を解きたいと思います。

上の言葉で示したのが、右脳を働かせて得たイメージです。
そして、そのイメージスペースエラーは式に落とし込めます。

最終的に E = Σe^2 の誤差二乗和で評価関数を定義して
これの多次元でのお椀の底を目指して最適化問題を解きます。

この式、よく見ると求めるパラメータに対して非線形となっているため
非線形最小化問題を解くアプローチを取ることになります。

非線形最適化問題を解く方法として、さまざまなものがあります。
勾配降下法、ニュートン法ガウス・ニュートン法、レーベンバーグ・マーカート法など
微分できない場合についても含めると、もっとあるのですが、私の勉強不足のため今はこれくらいしかうまく説明できません。

さて、ポジショントラッキングは最初にある程度正しい姿勢を求めることができたなら、その後はワープでもしない限り前回の計算結果に似た姿勢となることが期待できます。
そこで、ある程度解に近いことを条件として与えられますので、ガウス・ニュートン法を選択すると効率的に答えに安定してたどり着けると考えられます。
そのガウス・ニュートン法を用いて得られるのは、姿勢情報の更新量です。
得られた更新量を初期姿勢に適用して、解となる HMD の位置姿勢を得るのです。

はい、ここまでの話をすべてまとめると

1.LEDを4点以上カメラで追跡する
2.イメージスペースエラーの式を立てる
3.最適化数学を解く

どんな問題も、最後は誤差関数の最小化に帰着されるそうです。
数学は基礎ですが、訓練が必要なので、いきなり超えられる壁ではありません。
なので、先にわかっている人からイメージを聞き出して、まずは同じイメージを持つことが大切です。

わかっている人が周りにいない場合は、理系の大学に行きましょう。
大学の准教授や教授はこうしたイメージをもっています。
お金を払って聞きましょう。

さて、書きたいことを書いたので、ここで終わりにします。

Windowsから重力方向を求める

加速度センサの情報を拾うコードは次の通りらしい
Using Windows 8* WinRT API from desktop applications | Intel® Software

Unreal Engine 4 からの取得方法を知りたかったのですが
すでにサポート済みであり
そこは Motion をキーワードにすると良いらしいです。

docs.unrealengine.com

最初のアプローチが分かったので
サンプルコードが実際にデバイス情報を返したら、追記したいと思います。

Direct Method:またCVネタのメモ

SVO という技術をご存知でしょうか?
www.youtube.com

去年の SSII 2015 のチュートリアルによると SVO は近年CV研究界(一般人は簡単に踏み込めないので、どこぞの異界を想像して良いかも)で関心が高まってきているとのこと。
確かに Direct Method を使った SVO の論文の被引用件数はすごい勢いで伸びていた。

Direct Method はよく Feature Based Method と対比されます。
手法としては KLT に似ていて、ルーツをたどると1999年ころに論文がある。
3D座標が同じなら輝度も同じだろうという強い仮定のもと、拘束式をならべて画像のマッチングやカメラの位置・姿勢推定を行う手法。
画像全体の画素で拘束式を立てて、解くことから
画像全体の画素を直接使う、ゆえに Direct Method と呼ばれるわけ。

OpenCV にも Direct Method サポートしてないかなと調べてみたら
OpenCV: Image Registration
これかな?
サンプルを動かしたという記事を今、探しています。

先にドキュメントを読みましたが、使い方としては
Mapper なるものがあるので、そこに画像を2枚渡して、ここにある Map オブジェクトを得ます。
あとは Map オブジェクトからシフト量を求めるだけとな
Mapper には Pyramid 対応もある(Coarse-to-Fine処理のこと)

画像処理メモ

画像処理の顔検出技術において
高速化するうえで便利な技法に積分画像というものが知られています。

Haar-like 特徴量とかSURFというこれまた有名な局所特徴量記述形式などにも利用され
コンピュータビジョンを仕事にしている人で、知らない人はいないほど(趣味でやっている人は知らないかも)

何がうれしいって
例えば画像の平均輝度を計算したいとき、すべての画素の値を参照して合計輝度値を出し、合計ピクセル数で割ればよいではないですか
でも、もし画像領域を十字の境界線を引いて4等分に割って、それぞれの領域の平均輝度を計算したいときがあった場合
もう一回、すべての画素値を参照して計算します?
なんだか無駄に計算時間を使っていそうですね

そう

この処理、積分画像を使うととても少ない計算量で同じ答えが出せるようになります。

どうやってやるか、理解するかはここが参考になります。

Webcam画像を表示

EWCLIBヘルプ
というものを使いました。

下記コード(これだけではビルドが通りませんが…)の要領で Webcam 画像を表示してみました。

// OpenCVCameraViewer.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
/**
* @file Threshold.cpp
* @brief Sample code that shows how to use the diverse threshold options offered by OpenCV
* @author OpenCV team
*/

#include "opencv2/highgui/highgui.hpp"
#include "ewclib/ewclib.h"

using namespace cv;
const char* window_name = "Threshold Demo";

/**
* @function main
*/
int main(int, char** argv)
{
	EWC_Open(0, 640, 480, 60);
	EWC_Run(0);
	void* pBuffer = nullptr;
	namedWindow(window_name, WINDOW_AUTOSIZE);
	EWC_GetBuffer(0, &pBuffer);

	Mat dst = Mat(480, 640, CV_8UC4, pBuffer);
	while (true)
	{
		imshow(window_name, dst);

		int c;
		c = waitKey(20);
		if ((char)c == 27)
		{
			break;
		}
	}

	destroyWindow(window_name);
	EWC_Close(0);
}

Visual Studio 2015 でビルドしようとすると qedit.h が足りないと怒られたので、次の記事を参考に解決してみました。
失った時を数えて: #include "qedit.h"におけるエラーの解決方法

実行すると Webcam の映像がウィンドウに表示されます。


この ewclib の中を勉強してみたいと思います。
f:id:simplestar_tech:20160719002833j:plain