Tutti Lab

元シリコンバレー在住のおっさん技術者、モバイルVRアプリ開発に挑戦中

プリレンダリングの360度立体視CGとカメラ入力映像を重畳し、エンコードする

前回は、Maker Faire Tokyoに出展したVR撮影システムについて、360度立体視CGのプリレンダリングのやり方について書きました。
今回は、その360度立体視CGと、カメラ(魚眼レンズを付けたiPhone2台)入力画像を重畳し、mp4動画ファイルとしてエンコードする方法について書きたいと思います。

【第1回】VR撮影システムのハードウェア構成
【第2回】360度立体視CG映像を作る
【第3回】カメラ入力映像と重畳しエンコードする(今回)

左右眼用のカメラ入力映像をFisheye形式からEquirectangular形式に変換

基本的なやり方は、Theta二台から立体視用Equirectangular映像をつくるにて書いた内容と同じです。しかし、Theta Shader Packをそのまま利用することはできません。iPhoneからのカメラ入力映像は確かにFisheye形式なのですが、Thetaからの入力映像と異なり、左右が欠けてしまっています。
以下の図はMaker Faire Bay Area時のカメラ入力映像の取り込み・変換の仕組みです。左右眼用のThetaからの入力映像のうち、前方のFisheye映像をTheta Shader Packを利用してEquirectanguler形式に変換する、という方法をとっていました。
f:id:tuti107:20160810144242p:plain
今回のMaker Faire Tokyoでのカメラ入力映像の取り込み・変換の仕組みは以下のとおりです。
f:id:tuti107:20160810174152p:plain
iPhoneには魚眼レンズが取り付けられているため、カメラ入力映像はFisheyeのように歪んだ状態となっています。ただiPhone縦持ちで撮影する際、縦方向と比較して横方向の画角は狭くなってしまいます。iPhoneSE + Gizcamの場合、図の緑の破線(画角180度を示す)がカメラ入力映像からはみ出してしまいます。つまり、iPhoneからのカメラ入力映像の中にはThetaのように完全なFisheye形式の画像は含まれません。iPhoneからの入力画像を、上記Theta Shader Packの要領でEquirectanguler形式に変換すると図のように、ひょうたん(?)のような形になります。このため、大半はTheta Shader Packの処理のままで良いのですが、

  • はみ出ている部分は透明にする
  • Fisheye部の径を適切な値とする

2点の修正が必要となります。

フルHDサイズのオフスクリーンテクスチャを60fpsでエンコードする

前回のMaker Faire Bay Areaでは、ffmpegでリアルタイムエンコードするスクリプトを生成し、それを使用しました。しかし、その方法では12-15fpsが限界であり、せっかく搭載されている GeForce970の力を生かしたハイパフォーマンスなエンコードをできるようにしたいと思っていました。
そこで見つけたのが、GPU Video Encoderです。$100とかなり高価なアセットですが、複数のFull HDサイズのRenderテクスチャを同時に60fpsでエンコードできるスグレモノです。
本アセットの利用手順は次の通りです。
※本アセットは、Windows x86_64 DX11環境かつ、NVIDIAのHWエンコーダ(nvenc)が利用できる環境でのみ動作します。それ以外の環境では使用できませんのでご注意を(高価ですし)

  • Spicy Pixel Concurrency Kitをダウンロード・インポートする(本アセットと依存関係にあります)
  • 1920x1080のRender Textureを左右眼用作成する(それぞれの名前を"LeftTex", "RightTex"とする。Render Textureは、Projectにて右クリック→Create→Render Textureで作成、インスペクタのSizeにて1920x1080を設定できる)
  • それぞれのRender Textureに描画するCameraについて、Clear FlagsをSolid Color、Backgroundを緑(R=0, G=255, B=0)とする。のちほどffmpegでクロマキー処理をする際に「抜く色」として緑を使用するため
  • GameObjectを二つ生成する(ぞれぞれの名前を"Left", "Right"とする)。それぞれにMovie Record Cameraスクリプトを追加し、それぞれインスペクタより、Textureにはぞれぞれ上記で作成したRender Texture(LeftTex, RightTex)、Output File Path/Output File Titleにはそれぞれ生成するファイルのパスと名称を設定する

f:id:tuti107:20160810182039p:plain
あとは、エンコードを開始したいタイミングで、MovieRecordCamera#StartMovieRecord()を、終了したいタイミングでMovieRecordCamera#EndMovieRecord()を呼ぶだけです。スクリプトとしては以下のような感じで、上記生成したGameObject left, rightのMovieRecordCameraコンポーネントをインスペクタで下記leftCam, rightCamに設定しておき、開始・終了のタイミング(以下の例ではRキー押下)でそれぞれのメソッドを呼び出します。

using UnityEngine;
using System.Collections;
using GPUVideoEncoder;

public class CameraController : MonoBehaviour {

	public MovieRecordCamera leftCam;
	public MovieRecordCamera rightCam;

	private bool isRecording;

	// Use this for initialization
	void Start () {
		isRecording = false;
	}
	
	// Update is called once per frame
	void Update () {
	
		if (Input.GetKeyUp (KeyCode.R)) {
			isRecording = !isRecording;
			if (isRecording) {
				leftCam.StartMovieRecord ();
				rightCam.StartMovieRecord ();
			} else {
				leftCam.EndMovieRecord ();
				rightCam.EndMovieRecord ();
			}
		}
	}
}

すると、左右それぞれ用の.h264ファイルが、それぞれ指定したパス・ファイル名にて生成されます。このファイルをffmpegにてmp4に変換すれば、完成です。

ffmpeg.exe -i movieLeft.h264 -vcodec copy movieLeft.mp4

左目用・右目用の撮影映像を縦に並べる

次に上記生成した、左目用・右目用のmp4ファイルを縦に並べたmp4ファイルを生成します。以下のコマンド一発で変換可能です。

ffmpeg.exe -i movieLeft.mp4 -i movieRight.mp4 -vcodec nvenc -filter_complex vstack -b:v 8M movie.mp4

プリレンダリングの360度立体視CGとカメラ入力映像を重畳し、エンコードする

最後に、上記縦に並べた左右眼用の撮影映像と、前回生成したプリレンダリングの360度立体視CGを重畳します。これもffmpegのコマンド一発でオッケーです。

ffmpeg.exe -i movie.mp4 -f lavfi -t 10 -i \"movie = filename = a.mp4:loop = 0, setpts = N / (FRAME_RATE * TB)\" -filter_complex [0:0]colorkey=0x00ff00:0.5:.2[a1];[1:0][a1]overlay out.mp4
  • "t"オプションには作成する映像の時間(秒)を設定します
  • "movie=..."部分は重畳する360度立体視CG映像についての設定です。"loop = 0, setpts = N / (FRAME_RATE * TB)"とすることで、360度立体視CG映像の再生時間が-tで指定した時間より短い場合、これをループ再生します
  • "filter_complex"オプションには、クロマキーと映像重畳を設定します。colorkeyに緑色(0x00ff00)を指定することで緑色が透過します。なお":"で句切られた他の2つの数値は、クロマキーする範囲について指定しています。この2つの数字は試行錯誤で設定したものであり、数値とクロマキーの度合いの関係はいまいちわかっておりません。

これで、プリレンダリングの360度立体視CGとカメラ入力映像を重畳した動画ファイルが完成です!

終わりに

3回に渡り、Maker Faire Tokyoに出展した「スマホ2台を利用したVR映像作成システム」について書きました。記載量が莫大になってしまうため、実装詳細については触れませんでしたが、ご興味おありの方はその旨いろいろご質問いただけると嬉しいです。

Unityで360度立体視CG映像を作る

前回は、Maker Faire Tokyoに出展したVR映像撮影システムのハードウェア構成について書きました。
今回は、背景となる360度立体視CG映像をUnityで作成する方法について書きたいと思います。

【第1回】VR撮影システムのハードウェア構成
【第2回】360度立体視CG映像を作る(今回)
【第3回】カメラ入力映像と重畳しエンコードする

360 Panorama Capture

360 Panorama Captureを利用することで、Unityで構成したシーンから簡単に360度立体視映像を作成することができます(ただしWindowsのみ、DirectX 11以上、ハイスペックなGPU必要)。
本アセットの利用方法は非常にシンプルです。まず、本アセットをインポートし、Edit->Project Setting->PlayerでPCのPlayer Settingsを表示、Other Settings->OptimizationのAPI Compatibility Levelを".NET 2.0 subset"から".NET 2.0"に変更します。これで、Editorでの実行でキャプチャーが可能となります
次にシーン内に適当なGameObjectを配置し、Project/Capture Panorama/CapturePanorama.csをそのGameObjectへドラッグ&ドロップします。
するとインスペクタに以下のようにたくさんの設定項目が表示されますが、360度立体視映像の生成に必要なのは、下記の設定のみです。

  • Panorama Name: 生成するイメージファイルの接頭語を設定します。例えば"Img"を設定すると、本アセットは"Img****_000001.png", "Img****_000002.png"の名前の画像を出力します。
  • Image Format: 出力するイメージのフォーマットを指定します。JPG, PNG, BMPを指定できます
  • Capture Stereoscopic: これをチェックすることで立体視用の画像が出力されます
  • Panorama Width: 生成するイメージの幅を指定します。デフォルトで8096とかなり大きな数字となっているため、4096, 2048等、必要に応じて低い値を設定すると良いです
  • Save Image Path: イメージを出力するフォルダ名を指定します。大量のイメージが出力されることになるため、本ファイル出力用に新たにフォルダを生成する方が良いです
  • Capture Every Frame: これをチェックすることで、全フレームイメージとして出力されます(その代わりよほどハイスペックなPCでない限りパフォーマンスが大幅に低下します。私の環境だと60fps 10秒分のファイルを出力するために5分程度かかりました)
  • Frame Rate: フレームレートを設定します。私は60としましたが、用途に応じて30や15を設定すると良いです

その他の設定項目については、Project/Capture Panorama/READMEを確認ください。なお、古いOculus Pluginを使用している場合は、一部スクリプトの変更が必要のようです。

上記設定したら、あとは(Editor上で)実行し、pキーを押下します。これでキャプチャーが開始されます(上記の通り非常に動作が重くなります)。再度pキーを押下することでキャプチャーは終了します(しばらくpキーを押しっぱなしにしないと、動作が重すぎて当方環境ではキャプチャー終了となりませんでした)。

次に上記生成したイメージファイルをmp4等の動画に変換します。動画への変換は、ffmpegを利用して、コマンドラインから行います。なお、入力すべき内容は、Project/Panorama Capture/assemble.cmdとして用意されています。しかし、シンプルに以下の入力で問題ありません。

ffmpeg -f image2 -r 60 -i ****_%06d.png -r 60 -an -vcodec nvenc -b:v 8M -y -pix_fmt yuv420p -s 1920x2160 a.mp4

生成されるファイルの名前は、例えばImg_2016-08-06_10-34-06-377_000001.png, Img_2016-08-06_10-34-06-377_000002.pngのように、6桁の数字の前部分は全て同一となえいます。****には、この6桁の数字より前の部分を指定します。

Urban City 1.0

Maker Faire Tokyoでのデモでは、背景の360度立体視映像として、Urban City 1.0を利用しました。
本アセットのデモシーンProject/City/Scene.unity内の適当なビル間に長細いCubeを配置して橋にして、その橋の上にカメラを設置、まるで橋の上にいるような360度立体視映像としました(Maker Faire Tokyoでのデモで一番人気でした)。
f:id:tuti107:20160808182858p:plain
なお、本アセットには車等の3Dモデルも含まれております。Maker Faire Tokyoのデモでは、車道に車を走らせるアニメーションを追加し、橋の下を見ると、遠くに小さな車が走っている、それが「高さ」を感じさせて足がすくむ、という効果を狙いました。
youtu.be
※iPhone/Android PhoneのYoutube Appにて本ビデオを視聴することで、360度立体視視聴が可能です(こちらをクリック)。ただし立体視をするためにはGoogle Cardboardが必要です。


今回は、360度立体視映像をプリレンダリングする方法について書きました。次回は、このプリレンダリングした360度立体視映像と、前回説明したiPhone 2台による撮影映像を合成し、最終的なコンテンツを生成する方法について書こうと思います。

Maker Faire Tokyoに出展

8/6-7、東京ビックサイトにて開催されたMaker Faire TokyoにVR撮影システムを出展しました。この準備に多忙だったため、前回のブログからかなり期間が開いてしまいましたが。。
出展内容は、Maker Faire Bay Areaに出展した内容と基本同じですが、以下の点で変更・パワーアップを加えました。

  1. カメラをTheta S→スマホ+アタッチメント型魚眼レンズに変更
  2. 撮影映像の解像度・フレームレートを1280x1440・15fps→1920x2160・60fpsに変更
  3. 背景CGをリアルタイムレンダリング→プリレンダリングに変更

です。
1)は、Theta Sは素晴らしい360度カメラなのですが、本システムの用途上360度は不要(前方140〜150度程度で十分)であること、また将来的にたくさんの人に本システムを利用してもらいたいと考えており、ユーザがお金をかけずにVR撮影システムを作れる(すでに所有のスマホ+数千円のアタッチメント型魚眼レンズで撮影出来る)ようにすべく、泣く泣くTheta Sの利用を諦めました。

2)については、前回Maker Faire Bay Area時は開発が間に合わず、HDMIではなくUSB 2.0でカメラ-PCを接続、このためカメラからの入力画像の1280x720・15fpsでした。このため、出来上がったVR映像はドットが目立ち・若干パラパラマンガ的な感じになっていましたが、今回はHDMIで1920x1080・60fpsで左右眼それぞれの映像をPCへ入力し、1920x2160(左右360度パノラマ映像を縦に積んだ形式)にて出力するようにしました。

3)については、前回はUnityシーン内に2つのカメラ(右目・左目用)を配置して、リアルタイムで左右眼用の360度パノラマを生成・Theta Sで撮影した左右眼映像とリアルタイム重畳をしましたが、私のPC(Oculus PCなのでかなりハイスペックのはずですが)では、12-15fpsでのエンコードが限界でした(私のffmpegの使い方がまずかった、ということもありますが)。またカメラ二つでは前方百数十度程度には立体感がつくものの、それ以外は立体感なし・遠近感が逆転(後ろ方向)となるため、360度立体視はできていませんでした。そこで今回はリアルタイムレンダリングは諦め、プリレンダリングの360度立体視映像を事前に生成しておき、左右カメラ映像をキャプチャー後、プリレンダリングのCG映像と合成する、という形式にしました。

今回から数度にわたり、本技術について紹介をしていきたいと思います。

【第1回】VR撮影システムのハードウェア構成(今回)
【第2回】360度立体視CG映像を作る
【第3回】カメラ入力映像と重畳しエンコードする

システム構成

本システムのハードウェア構成は以下の通りです。
f:id:tuti107:20160808095133p:plain
まずはiPhoneSEを2台用意、それぞれの背面カメラに魚眼レンズ(Gizcam)を取り付けます。
f:id:tuti107:20160808100158j:plain
なお、うまく背面カメラの上にレンズを取り付けないと、映像がにじむ・暗くなる(何も見えない)ため、レンズ中央に背面カメラがくるよう、うまく位置調整をする必要があります。コツとしては、レンズの中央が完全に透けて向こう側が見えるため、背面カメラが中央にくるよう位置調整をすればオッケーです。知っている方にとっては当たり前の話なのでしょうが、私は最初これが分からず、撮影映像を見ながらカンで上下左右レンズを動かして調整していました。
次に、それぞれにiPhoneに、Lightning - Digital AVアダプタをつなげます。iPhoneの純正アクセサリは高いです。。
次に、Lightning - Digital AVアダプタとHDMIケーブルを接続、さらにHDMIケーブルのもう一端にFEBON 198を接続します。
f:id:tuti107:20160808101159j:plain
FEBON 198は、Theta SをPCにつなぐ際の必須アイテム(?)で、実例はこちら等で取り上げられております。
HDMI映像出力をUSB 3.0のUVCへ60fps・低レイテンシで変換してくれるスグレモノです。値段は¥19,800と結構高価ですが、HDMI->USB3.0変換器としては破格の安さです。
最後に、USBケーブルにてFEBON 198をPCのUSB3.0ポートにつなぎます。これでiPhone画面の表示されている内容が1920x1080 60fpsの高画質で、UVCとしてPC側で利用可能となります。
UVCなので、UnityではWebCamTextureにて簡単に本映像をテクスチャとして貼り付け・利用可能です(WebCamTextureについてはこちらをご参照ください)。

余談ですが、当初はiPhoneではなくNexus 5を使用する予定でした。上記Lightning - Digital AVアダプタの代わりにSlimport→HDMI変換ケーブルでNexus 5とつなごうと色々頑張ってみたのですが、FEBON 198が信号をうまく認識できず結局諦めました(FEBON 198の代理店である緋斐B級技研さんには親身に相談に乗っていただけました。ありがとうございました!)。

iPhone側カメラ出力アプリ

iPhone側では、全画面カメラ映像を出力するアプリを動かします。このアプリは非常にシンプルで、Canvasに全画面のPanelを、そしてPanelいっぱいの大きさのRawImageを配置し、こちらでご紹介したWebCamControllerを貼り付けだだけのシンプルなものです。プリインのカメラアプリだとメニュー等、色々邪魔なものが表示されてしまうため、このようなアプリを用意しました。
f:id:tuti107:20160808103219p:plain

次回は

これで、左右両目用のカメラ映像を二台のiPhoneからUVC形式でPCへ入力することが可能となりました。
次回は、本映像から「360度立体視パノラマ映像との合成」映像を生成する方法について書きたいと思います。

Google Cardboard的AR端末 Seebright Rippleが来ました(2)

前回組立のみ完了したSeebright Rippleですが、たまたまSeebright社のJohnさんとお会いする機会があり、SDKへアクセスする方法を教えていただきました(公式のHPではまだ公開はされていないようです)。
早速このSDKを利用して、簡単なテストアプリを作成してみました。
f:id:tuti107:20160712041651p:plain
なお、SDKのダウンロード等の手順は、公式HPからアクセスが可能になりましたら、順次公開していこうと思います。

Seebright SDK

Seebright SDKはUnity用Assetとして提供されています。SDKは非常にシンプルで、ヘッドトラッキングや左右立体視制御を行うSBCamera、及びボタンなどのUIを表示するためのSeebright UIがPrefabとして利用可能であり、基本これらをシーンに設置するだけでオッケーです。
f:id:tuti107:20160712035401p:plain
ボタンが左右眼の位置ではなく、真ん中に表示されてしまっておりますが、実行すると以下のとおり、左右眼の位置に表示されます。
f:id:tuti107:20160712035611p:plain
SBCameraの設定項目は以下のとおりです。
f:id:tuti107:20160712035758p:plain
デフォルトからの要変更箇所は、

  • Copy CameraをチェックをOnとし、CameraのField Of Viewを36.75とする
  • Current HMDをRippleとする
  • Current ModeをVRとする→ヘッドトラッキングが可能となる。ARとすると、読み取ったARマーカに基づいてカメラ姿勢が計算されるようになる

次にSeebrightUIについて、
f:id:tuti107:20160712040309p:plain
デフォルトからの要変更箇所は、

  • Current HMDをRippleとする

だけです。

Androidデバイスでテスト

  • File→Build SettingsにてPlatformをAndroidとし、
  • Player Settingsボタンを押下して、Bundle Identifierに任意の文字列を設定、Minimum API Levelを21とし、
  • Build And Runボタンを押下すれば、

ビルド→Androidデバイスにアプリがインストールされ、冒頭のイメージのような感じで実行されます(端末の傾きに応じて地平線が動く)。
ただし、以下の点に注意が必要です。

  • SeebrightInput.dllのコンフリクトを解消する→同名のファイルが、Assets/、Assets/iOS、Assets/Android、それぞれに存在し、デフォルトではそれらのplatformがAny Platformとなっている。このままビルドするとコンフリクトによるエラーが発生するため、それぞれAny Platform→それぞれのPlatformに変更する

f:id:tuti107:20160712041431p:plain

  • Android M以降のデバイスを利用する際は、Android SDK Managerより、API Level 23のインストールを忘れずに。インストールされているSDKのバージョンがAPI Level 22以下の場合は、以下のようなエラーが発生します

f:id:tuti107:20160712041611p:plain

なお、本件とは関係ありませんが、Mac+Android Mで、Android File Transferがうまく動かない(Can’t access device storage)問題が発生します。こちらに本問題の対処方法が記載されています。

本領発揮はAR

今回は、テストということでまずはVRモードで動かしました。ほぼGoogle Cardboardと一緒という感じです。
Seebright SDKは上記の通り非常にシンプルな作りであり、ARマーカ認識やARマーカ認識に基づくカメラ姿勢制御等は含まれておりません。しかし、Vuforia等のサードパーティ製ARエンジンと組み合わせることで、ARアプリを開発可能です。
次回は、Vuforiaを組み込み、簡単なARアプリを作成してみたいと思います。

Google Cardboard的AR端末 Seebright Rippleが来ました(1)

以前ご紹介した、たった$39の、Google Cardboard的AR端末、Seebright Rippleが届きました。f:id:tuti107:20160629124500j:plain
開封すると、プラスチック等のパーツがたくさん。
f:id:tuti107:20160629124603j:plain
うーん、面倒くさそう。組立説明書は同梱されておりません。Seebright RippleのWEBサイトにある、組立説明の動画を見ながら組立ます。
本動画では簡単そうにどんどんと組み立てていきますが、実際はそう簡単にはいきません。特に手順5(レンズと鏡をセット)〜手順6(大きなひし形のパーツで全体を固定する)で、各パーツが思うように固定されず、かなり手間取りました。
ちなみに、組立説明の動画の「組立者」が手順5〜手順6で男性から女性に入れ替わります(自然にでは無く、一部手順をすっ飛ばして急に入れ替わります)。意図は謎ですが、きっと手順6で男性が手こずり、女性にバトンタッチしたのでしょう。
結局30分以上かかってしまいましたが、なんとか完成。
f:id:tuti107:20160629125223j:plain
透明なパーツが3つ残ってしまいました。組立説明の動画にも、これらパーツは登場しません。一体何に使用するのでしょうか?
また、手順6で力んでしまったせいで、パーツの一部が壊れてしまいました。ただ、特に無くても大きな影響はなさそうなので、とりあえず良しとします。
なお、この手順説明動画では、重要なステップが抜けております。パーツ群の左上二番目と三番目(鏡面のパーツ)には保護用のフィルムが貼り付けられており、これを剥がしてから組立る必要があります。組み立ててしまった後にこれを剥がすのは非常に困難です(左上二番目上のメガネ型のパーツはこのフィルムに気づき事前に剥がしたのですが、左上三番目の大きなパーツのフィルムには気付かず、完成後剥がす羽目になってしまいました。非常に剥がしづらかったです)。

アプリやSDKは未だ

現状、残念ながら端末が完成しても、利用できるコンテンツが一切ありません。近日中にアプリとSDKが公開される、とのこと(Seebright RippleのWEBサイトにて、アプリ公開のお知らせ設定、及び開発者登録が可能です)。
とりあえず手元のGoogle Cardboard用のコンテンツで試したところ、AR感(現実世界にCGが重畳している感覚)はないものの、現実世界とCardboardコンテンツの両方が見える感じは体験できました。どうやら組立は間違えていなかったようです。
アプリ・SDKが公開され次第、続報をお伝えしていきたいと思います。

【Unity開発Tips】ButtonでInputField的なGUIコンポーネントを作る

前回書いた通り、iOS/Androidでは、uGUIのInputFieldを選択すると、OS固有のタッチスクリーンキーボードが立ち上がってしまいます。このため、iOS/Androidでは、uGUIのInputFieldと連動したバーチャルキーボードの実現は困難です。
今回は、InputFieldの使用を諦め、uGUIのButtonコンポーネントを改造して、InputField風に仕上げる方法についてご紹介します。かなりマニアックな用途ですが。。

ButtonとInputField

ButtonはInputField同様、uGUI用のGUIコンポーネントであり、その名前の通り「ボタン」の機能を提供します。Canvas上に貼り付けて使用し、見た目は凸型・文字列で「どのように機能するか」が書かれており、クリックするとOnClick()イベントが発生、文字列の機能の通りの実施(するように実装)します。
一方で、InputFieldは、テキスト入力を受け付ける機能を提供するものです。Canvas上に貼り付けて使用するのはButtonと一緒ですが、見た目は凹型・ユーザがキーボード等で入力した文字列が表示され、文字列が変化した時はOnValueChanged()イベントが、文字列の入力が完了した際はOnEndEdit()イベントがそれぞれ発生します。
f:id:tuti107:20160627023215p:plain
このように、ButtonとInputFieldは異なる見た目・機能を持つコンポーネントですが、見た目に関しては、

  • 枠のイメージをUISpriteからInputFieldBackgroundに変更
  • 文字列が見入力かつ未選択時はEnter text...を表示

すると、ButtonとInputFieldの見た目はほぼ一緒になります。
f:id:tuti107:20160627023353p:plain

Buttonなら、当然選択時にOS固有のタッチスクリーンキーボードが立ち上がることもありません。そこで、Buttonクラスを拡張して、InputField風のコンポーネントを作ってみました。

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;

public class VRInputField : Button {

	public Text textComponent;
	public string text;

	public Text placeFolder;

	public void NotifyKeyPressed(string keyStr) {

		text += keyStr;
		UpdateLabel ();
	}

	private void UpdateLabel() {

		if (placeFolder != null) {
			placeFolder.gameObject.SetActive (text == "" && this != selectedInputField);
		}

		textComponent.text = text;
	}

	public KeyboardTranform keyboardTransform = new KeyboardTranform();

	public void NotifyKeyboardHidden() {
		OnDeselect ();
	}

	private static VRInputField selectedInputField = null;

	public override void OnSelect(BaseEventData eventData) {

		base.OnSelect (eventData);

		Board.isShown = true;

		if (this != selectedInputField && selectedInputField != null) {
			selectedInputField.OnDeselect ();
			Board.ResetKeyboardTransform (this);
		}
		selectedInputField = this;

		UpdateLabel ();
	}

	public void OnDeselect() {
		selectedInputField = null;
		UpdateLabel ();
	}
}

適当なButtonコンポーネントを作成ののち、Button(Script)を削除して、代わりにこのスクリプトVRInputFieldをAdd Componentします。
合わせて、エディタ拡張を適用し、上記スクリプトの内容に応じたインスペクタ表示を行えるようにします(下記のスクリプトVRInputFieldInspectorをEditor配下におきます)。

using UnityEngine;
using UnityEditor;
using System.Collections;
using UnityEngine.UI;

[CustomEditor(typeof(VRInputField))]
public class VRInputFieldInspector : UnityEditor.UI.ButtonEditor {

	public override void OnInspectorGUI() {

		VRInputField f = (VRInputField)target;

		EditorGUILayout.LabelField ("Virtual Keyboard transform:");

		EditorGUI.BeginChangeCheck ();

		EditorGUI.indentLevel++;

		f.keyboardTransform.keyboardPosition = EditorGUILayout.Vector3Field ("LocalPosition", f.keyboardTransform.keyboardPosition);

		EditorGUILayout.BeginHorizontal ();
		EditorGUILayout.PrefixLabel ("Scale");
		f.keyboardTransform.keyboardScale = EditorGUILayout.FloatField (f.keyboardTransform.keyboardScale);
		EditorGUILayout.EndHorizontal ();

		EditorGUI.indentLevel--;

		EditorGUILayout.Separator ();

		f.textComponent = EditorGUILayout.ObjectField ("TextComponent", f.textComponent, typeof(Text), true) as Text;
		f.text = EditorGUILayout.TextField ("Text", f.text);

		EditorGUILayout.Separator ();

		f.placeFolder = EditorGUILayout.ObjectField ("PlaceFolder", f.placeFolder, typeof(Text), true) as Text;

		if (EditorGUI.EndChangeCheck ()) {
			EditorUtility.SetDirty (target);
		}

		base.OnInspectorGUI ();
	}
}

これにより、スクリプトVRInputFieldを貼り付けたButtonのインスペクタは下記の通りとなります。
f:id:tuti107:20160627030012p:plain

PlaceFolderはEnter Text..の文字列を持つTextです。このText、自作しても良いのですが、InputField生成時にInputFieldの子要素として自動生成されるもの(下図)をコピーし、Button配下にペーストする方が楽です。
f:id:tuti107:20160627025217p:plain
ペースト後、PlaceFolderのText(Script)を、VRInputFieldのPlaceFolderへドラッグ&ドロップして設定します。合わせて、Button配下のTextのText(Script)をText Componentへドラッグ&ドロップします。
VRInputFieldのUpdateLabel()メソッドは、これらPlaceFolder及びTextComponentを利用して、テキスト未入力・入力中ではない場合はPlaceFolderを、それ以外(テキスト入力中・入力済)ならTextComponentを表示します。これにより、InputFieldのような見た目が実現されます。

Boardクラスはバーチャルキーボードの本体機能の実装であり、Board.isShownをtrue/falseとして表示/非表示の切り替え、Board.ResetKeyboardTransform ()にて、バーチャルキーボードの表示位置・サイズを切り替え(前々回ご紹介の通り、keyboardTransformの設定内容に応じて表示)ます。
ボタン押下時にはOnSelect()が呼び出されるので、そこでバーチャルキーボードを表示します。なお、すでに他のボタンがバーチャルキーボードを表示している場合は、バーチャルキーボードの表示位置だけ切り替えます。
f:id:tuti107:20160627031148p:plain

まだ色々足りない

今回は、Buttonクラスを拡張し、InputField風のGUIコンポーネントを作ってみました。一見InputFieldと同じ感じなのですが、実はまだ色々と見た目・機能的に足りていません。
列挙してみると、

  • キャレット(入力位置に点滅表示されるカーソル)が表示されない。当然入力位置の変更もできず、現状文字列末尾の文字追加・削除のみ
  • 表示内容がスクロールしない。例えば複数行の文字列を入力しても最初の行しか表示されない等
  • OnValueChanged()/OnEndEdit()イベントが発生しない

その他色々・・。完全なInputFieldの実現まで行かないとしても、最低限上記は実装したいと考えています。
次回はこの辺りを実装しようと思います。

【Unity開発Tips】InputFieldクリック時にバーチャルキーボードを表示【モバイル未解決】

前回少し触れましたが、VR向けバーチャルキーボードを開発しています。
入力したいInputFieldの方を向いてクリック(Cardboard右上のボタンを押下)すると、バーチャルキーボードが現れ、任意の文字を入力、最後にキーボード右下の「キーボードを隠す」ボタンをクリックして、入力終了、という具合に利用するものです。
f:id:tuti107:20160625095117p:plainf:id:tuti107:20160625095127p:plainf:id:tuti107:20160625095139p:plain
バーチャルキーボードの表示については、InputFieldを継承したクラス(VRKeyInputField)にて、OnSelect()、及びOnDeselect()メソッドをそれぞれオーバーライドし、

  • InputFieldクリック時に、オーバーライドしたOnSelect()メソッドにて、バーチャルキーボードを表示
  • OnDeselect()メソッドをオーバーライドして「何もしないようにする」→そうしないと、InputFieldからGaze(赤丸)が外れると、InputFieldからフォーカスが外れてしまう
  • 「キーボードを隠す」ボタンがクリックされたら、InputFieldのOnDeselect()を呼び出して、フォーカスを外す

としています。以上でEditor上では、上記写真のようにうまく動作します。

iOS/Androidではうまく動かない

ただ、これをiOS/Androidで動かすと、以下のようにOS固有のタッチスクリーンキーボードが表示されてしまいます。
f:id:tuti107:20160625100159j:plain
これをどうやって表示しないようにするか、タッチスクリーンキーボード表示後に「TouchScreenKeyboard#active = false」する等、色々トライしましたが、うまくいかず、ネット上の先人の知恵を探すも、解決したという情報を見つけられませんでした。
とあるサイトで、InputField、TouchScreenKeyboardのソース(と思われるもの)を発見したのですが、本ソースコードを見ると、

  • InputFieldは、InputFieldが入力可能状態となった際(クリックされる等)、TouchScreenKeyboard.isSupportedがtrueなら、TouchScreenKeyboard.Open()を呼び出して、OS固有のタッチスクリーンキーボードを表示
  • TouchScreenKeyboard.isSupportedは、RuntimePlatform(= Application.platform)が、RuntimePlatform.IPhonePlayer又はRuntimePlatform.Androidなら、trueとなる

となっている模様。設定などでタッチスクリーンキーボードを表示しない様にはできない感じです。
仕方がないので、MyInputFieldを開発するか、という状況です。。

本件、解決の方法をご存知の方おりましたら、ぜひ教えていただければ、と思います。