これちのPost-it

技術ネタをペラペラと

ジャックランタンをモチーフにしたキャラクターの 3D モデルを作った①

korechipostit.hatenablog.com
と、第3回に渡って Maya を勉強したのでさっそく実践してみたいと思います!
目指せ3Dモデラーエンジニア!

目的

  • 3Dモデルを作成する練習
    • 今後作るゲーム用素材を自前で用意したい
  • バーチャルYouTuberに使うアバターとして

環境

成果物

2Dイラスト

iPad Pro+Apple pencil を使って3D モデルの元となるコンセプト絵を描きました。製作時間は3時間ほど。
f:id:korechi:20180123093755p:plain:h300
陽気なイメージのキャラクターで、昔から大好きなジャックランタンをモチーフにしました。
Adobe Illustrator Drawは個人的にかなり使いやすかったです。
長押しで塗りつぶし機能が使いやすく、筆先の太さ調整もとても簡単なので、自分のような初めてお絵かきする人にも使いやすいアプリだなと感じました。
また、ズームイン・ズームアウトもしやすくてGoodです!しばらくお絵かきをする際にはこのアプリを使おうと思います。
ちなみにスケッチをするならAdobe Photoshop Sketchというアプリが無料であるのでいつか使ってみたいです。

3Dモデル

こちらは Maya を使って作りました。製作時間は5時間ほど。
f:id:korechi:20180123094841p:plain:h300
参考にしたサイトはこちらです。
maya.indyzone.jp
これを参考にしてかぼちゃ部を作成しました。
マスク・口元は頂点を気合で編集して作成しました。ここはもっと効率的な作り方があると思うので割愛。

学んだこと

上記にある Maya の公式ページで学んだこと

特殊な複製

編集->特殊な複製(オプション)でさまざまな複製ができる。例えば下のようなオブジェクトを複数個回転しながら複製することが可能となる。
f:id:korechi:20180123101000j:plain:h300 f:id:korechi:20180123101131j:plain:h300
f:id:korechi:20180123100917j:plain:h300

f:id:korechi:20180123101136j:plain:h300
y 軸(上から見て)を中心に45度回転しながら7個オブジェクトを複製すると、ぐるっと一周させることができる。

近接する頂点同士の結合

上の方法でオブジェクトを複製すると、境界部分のエッジ・頂点が重なってしまうという問題が残る。
アトリビュートエディタ->境界の表示をオンにし、境界幅を調整するとエッジが重なっている部分が太く表示される。
f:id:korechi:20180123101739j:plain:h300
メッシュの編集->マージのオプションしきい値を小さい数値に設定すると
f:id:korechi:20180123101847j:plain:h300

f:id:korechi:20180123101907j:plain:h300
うまく結合でき、境界線がなくなるはずです。

ベンド

折り曲げる機能。参考URL
f:id:korechi:20180123233159p:plain:h250
デフォーム->ノンリニア->ベンドを選択します。
すると折り曲げる線が表示されるので、その線を位置・回転させつつ、アトリビュートエディタにある曲率を変化させて対象のオブジェクトを折り曲げる。
f:id:korechi:20180123233452p:plain:h300
f:id:korechi:20180123233502p:plain:h300
この機能を使うことでキャラクターについている仮面や口元を曲げることができた。
f:id:korechi:20180123233540p:plain:h300

f:id:korechi:20180123233557p:plain:h300

今後の作業予定

  • ステッキ部の3Dモデルを作成
  • Unityへのインポート
  • Unityで撮影
  • 目と口のアニメーション(余力があれば)

絵心皆無のエンジニアの俺がMayaを勉強して3Dモデルを作ってみる【第3回】

前回の記事の続きです。
korechipostit.hatenablog.com

Macでの操作

Macにはスクロールマウスが存在しません。
Autodesk Maya オンライン ヘルプ
ここを参考にしたところ、Command+左クリック=スクロールマウス、となるようです。

11. 基本マテリアルとUVマッピング

球を作成するとlambert1というマテリアルが初めから設定されている。
f:id:korechi:20180105203112p:plain

マテリアル、シェーダー、テクスチャについて

  • マテリアル
    • どのようにモデル表面を描画するかを定義`
      • 使用するテクスチャ、そのタイリング設定や色調などへの参照をふくむ
  • シェーダー
    • ピクセルごとの描画色を計算するための数学的な計算とアルゴリズムが内包されたスクリプト
      • 光源入力とマテリアル設定をベース
    • オブジェクト表面の色を計算するのにテクスチャを使う
  • テクスチャ
    • ビットマップ画像
    • 反射率や粗さなどのマテリアル表面の様々な要素をテクスチャで置き換えることが可能

Lambert

光沢がないマテリアル。コンクリートやゴムなどマッドな質感に向いている

Blinn

ハイライトが設定できるマテリアル。金属の質感に向いている

Phong

シャープなハイライトが設定できるマテリアル。プラスチックや陶器に向いている

新しいマテリアルを設定

右クリック->新しいマテリアルの設定もしくは右クリック->お気に入りのマテリアルの割り当て

UVマッピング

UV=テクスチャの座標
ボールに世界地図を貼るに例えられる。3Dの球に、2Dの地図を貼り付けるような考え。 f:id:korechi:20180106093255p:plain

#### 平面マッピング プレーンを通ってメッシュ状にUVを投影する

円柱マッピング

メッシュの周囲にラップされた円柱シェイプに基づいてUVが生成

球面マッピング

メッシュの周囲にラップされた球シェイプに基づいてUVが生成

自動マッピング

周囲に配置された複数のプレーンから同時に投影し最適なUVの配置を探しポリゴンメッシュにUVを作成

手順

  1. プリミティブなボックスを作成
  2. メニュー->UV->UVメニュー
    • プリミティブなオブジェクトだと既にUV座標を持っている
  3. テクスチャ境界の表示を選択f:id:korechi:20180106094150p:plain 
    • ボックスにUVがどのように反映されているか見える
    • UVメニュー内で頂点を選択するとビューのどこの頂点に反映されるか確認できる
  4. アトリビュートエディタ->カラーの横の[f:id:korechi:20180106094606p:plain]を選択
  5. ファイルを選択
  6. イメージの名前の横にあるフォルダのアイコンからテクスチャファイルを選択
    • UV座標と一致している画像

12. 小物(剣)のUV展開

  1. 2ビュー表示
  2. ビューの中にあるパネル->パネル->UVエディタでUVエディタが開かれる
  3. オブジェクトを全選択し、メニュー->UV->自動で6面から投影・展開される
  4. シェル選択モードに変更し、ある程度ばらけさせる f:id:korechi:20180110001105p:plain
  5. エッジを縫合してまとめる
    f:id:korechi:20180110081344p:plain
  6. UVエディタのイメージ->UVのスナップショットを選択し、テクスチャを作成する
    • 今回は拡大・回転・移動を使ってU:1, V:1に合わせた
    • f:id:korechi:20180111124014p:plain
      • UVのスナップショットでUとVの値を変更すると複数枚にテクスチャを分割できる
    • 今回はjpegとしてテクスチャを保存
  7. 保存したテクスチャをPhotoshopなどを使って編集する
    • 自分はPhotoShopを持っていないため
  8. 編集したテクスチャをMayaに取り込みテクスチャをオブジェクトに反映させる
    • マテリアル->カラー->ファイルを選択し、テクスチャをロード
    • ペイントソフト等でテクスチャを反映しロードしたところなぜか反映されない。。なぜだ
      • 6キーを押すorシェーディング->ハードウェアテクスチャリングをクリックしないとテクスチャが表示されないらしいです!!これで1週間潰した。。

エッジの縫合

エッジ選択モードに変更し、shift+右クリックでエッジを移動して縫合を選択
- その後はG: 上の繰り返しである程度UVがまとまるまで作業を続ける

エッジを分離

エッジを選択し、エッジをカット

13. 頭部のUV展開

頭部のUV展開

  1. 頭部のバイザーを覗いた部分を選択し、UVエディタで確認。
    f:id:korechi:20180114220355p:plain
  2. メニュー->UV->円柱を選択すると下のようにUVが展開される
    f:id:korechi:20180114214708p:plain
  3. 切れ目が変な部分になっているためエッジの縫合を行い真ん中で分割されるように
    • UV->UVの縫合UV->UVのカットを使用
      f:id:korechi:20180114221720p:plain
    • 上の方はあまり綺麗ではないが切れ目は頭の後ろのラインになった。
  4. UV選択モードで全UVを選択し、修正->展開のオプションを選択
    • Unfold3Dにチェックが入っていることを確認して適用 f:id:korechi:20180114222513p:plain
      こんな感じ

バイザー部のUV展開

  1. まずは頭部を非表示にし、裏側のフェースを選択
  2. UV->平面のオプションを選択
    • 投影元をカメラに f:id:korechi:20180114224428p:plain
      裏側は出来たのでそれは端に避けておく
  3. UVシェル選択モードにして裏側以外を選択
  4. UV->平面のオプションを選択
    • 投影元をZ軸に f:id:korechi:20180114224730p:plain
      しかし、一部重なっているためそこを修正しなければいけない
  5. UV選択モードにして全UVを選択し、修正->展開
  6. それぞれ調整しつつまとめてUVのスナップショットを保存 f:id:korechi:20180114225221p:plain

オブジェクトの非表示

ctrl+H

  • 再表示はshift+H

最後に

これでキャラクターのモデルを作成し、テクスチャを作成する方法が分かりました。
基礎の基礎ですが、これらの知識を使って応用していきたいと思います。
Maya Learning Channelは14回以降はアニメーションの話になるのでここで一区切りとしたいと思います!

絵心皆無のエンジニアの俺がMayaを勉強して3Dモデルを作ってみる【第2回】

前回の記事の続きです。
korechipostit.hatenablog.com

6. ペアレントとグループ化

  • 階層構造を表示するためにはアウトライナーを表示する必要がある
    • 左メニューのf:id:korechi:20180103125634p:plain:w200これをクリックすると表示される
    • アウトライナーはシーン内のオブジェクトを一覧で表示したもの
      • Unity の Hirarchy っぽい
    • オブジェクトの階層構造を変更するには真ん中マウスをドラッグ
      • オブジェクトを一括選択し、pキー:最後に選択されたオブジェクトが親になるらしい(よくわからなかった)
      • shift+P:親子解除
  • グループ化
    • 親子関係じゃなくてまとめる
      • ctrl+G: グループ化f:id:korechi:20180103130644p:plain
      • スペース長押し→編集→グループ化解除

7. 小物(剣)のモデリング Part 1

  • 作成->ポリゴンプリミティブ->インタラクティブ作成にチェックを入れるとインタラクティブにオブジェクトが作成できる(めちゃめちゃ便利)
  • Mayaでは1=1cmのスケール
  • 右下にあるディスプレイ->レイヤ->選択項目からレイヤを作成でレイヤが作成できる
    • レイヤの中では、Visible, Template, Referenceの切り替えが可能
      • ただレイヤを作るだけだと現在見えているものがレイヤに適用されないため注意
      • Template: テンプレート化。ワークスペース内には表示されるが選択とスナップ先への指定ができなくなる
        • 今回は大まかな外枠の作成に使う
      • Reference: スナップ先にはできるが選択や修正はできない
  • 頂点の追加
    • スペース->メッシュツール->マルチカットにして頂点選択モード
    • Ctrl:エッジループ
    • 真ん中マウス: エッジの中点
    • shift+頂点のマージで頂点を一つに
  • ベベル
    • 新しいフェースに選択した各エッジを拡大し、ポリゴンメッシュのエッジを丸める
    • shift+右クリック長押し
    • 真ん中マウスを押すと割合が変化

頻繁に使った機能

  • 頂点スナップ(頂点を選択してV押しながら移動)
  • グリッドスナップ(頂点を選択してX押しながら移動)
  • G: 前回の命令を実行
  • spaceを一瞬押すと選択ビューが拡大表示される

8. 小物(剣)のモデリング Part 2

  • shift+右クリック:フェースの押し出し
  • チャネルボックス->入力->深度の分割数の数を変えると分割できる f:id:korechi:20180104101434p:plain:h300
    f:id:korechi:20180104101440p:plain:h300

  • エッジ選択モードにし、メッシュツール->ターゲットの連結

    • ただしマージ先がターゲットになっていたため設定からセンターに変更
  • ベベルエッジ
    • セグメント数は折り曲げる数(?)

完成物

f:id:korechi:20180104103539p:plain
剣が完成!これでHoloQuestの剣を自分で作れるようになった!笑
次はキャラクターのモデリング

9. キャラクタ(頭部)のモデリング

必要なもの

  • 描きたいキャラクターを前から見た画像
  • 描きたいキャラクターを横から見た画像

手順

  1. 作成->フリーイメージプレーン
  2. アトリビュートエディタ->イメージの名前のアイコンから画像を選択できるので、前から見た画像と横から見た画像を表示させる
    f:id:korechi:20180104195232p:plain
  3. 2枚のイメージプレーンをレイヤに登録してReferenceにして選択ができないようにする
  4. 球を作成し頭の大きさにする
  5. 球->入力->polySphere1->高さの分割数を変更してちょっと荒めにする
  6. 画面を4分割し、shift+Fで球を全ウインドウにフィット
  7. 頂点モードに切り替えて正面、横から見た画像と重ねて調整
    f:id:korechi:20180104200251p:plain
  8. エッジの押し出しをうまく使って首元を作成 f:id:korechi:20180104201830p:plain
  9. 顔の部分を少し凹ませる f:id:korechi:20180104203732p:plain

便利な機能

  • シンメトリ
    • ポリゴンモデルの片側のディテールを反対側に反映することができる
    • ツールボックスのシンメトリの軸をトポロジにし、軸にしたいエッジを選択するとその軸を対象としてエッジが選択可能になる

10. キャラクタ(バイザー部)のモデリング

手順

  1. カーブ->サーフェスから円を選択し大きさを調整
  2. 2点選択しデタッチすることで2つのオブジェクトに分割し後ろのを消す
  3. 右クリック長押し->CVでCV選択モードに切り替えてバイザーの上・下・真ん中3つを調整
    f:id:korechi:20180104210457p:plain
  4. 3つの円を下から選択し、メニュー->サーフェス->ロフトでカーブからなめらかなサーフェスが作成される
    f:id:korechi:20180104210753p:plain
  5. メニュー->修正->変換->NURBSをポリゴンにを選択してポリゴンモデルへ変換する
  6. UとVをうまいこと調整
  7. バイザーに厚みをつけるためフェースの押し出しで厚みを-1にして内側に引き伸ばす
  8. フェースの押し出しを使いまくって隙間を作成
  9. 右半分を作成したら左半分を一度消し、メニュー->メッシュ->ジオメトリのミラー

デタッチ

頂点を選択した場合、フェースによって共有されている頂点を複数の頂点に分離し結果としてオブジェクトが分割される。

  • space->カーブ->デタッチ

ノットの挿入

ノットが挿入される

  • space->カーブ->ノットの挿入

ポリゴンモデルとnurbsモデルの違い

構造が違う。現在(2017年)の主流は編集が楽なポリゴンモデル。

  • nurbs
    • カーブで形成されたモデル
    • 表面は滑らか
  • ポリゴン
    • 点と線で形成されたモデル
    • 表面は角ばっている

ヒストリの削除

今まで行った作業がチャネルボックスの入力に残っているので、編集->種類ごとに削除->ヒストリで削除できる

まとめ

ひとまず頭の部分が出来ました。完成物はこちら。
f:id:korechi:20180104215209p:plain
とても大変だ・・・

絵心皆無のエンジニアの俺がMayaを勉強して3Dモデルを作ってみる【第1回】

はじめに

最近3Dモデルを自分で作れたらなーと思う機会がひじょーーーに増えました。
HoloQuestで使うモンスターに3Dモデルが必要ですね。いつまでもスライムを使っていられません()
あと、最近流行っているバーチャルユーチューバーですね。すごくやってみたいんですが、3Dモデルがどうしても必要に。。
とは言え、AssetStoreからモデルを買ってもオリジナリティを出すのはなかなか難しいし、果たしてそれで良いのか感。
そこで!じゃあもう自分でやれるところまでやってみよう!と思い、今回3Dモデリングを1から勉強することにしました!
コードしか書いたことのない絵心皆無のエンジニアですが()、3Dモデルを1から勉強し始めてどこまで出来るようになるか、チャレンジしたいと思います!目指せバーチャル(イケメン)ユーチューバー!

モデリングソフト

  • Maya 2018
    • 初めは無料体験版を使う。期限が切れたらMayaLTを購入するかも

動作環境

勉強方法

よく分からないので、「Maya チュートリアル」と検索して出てきたMaya Learning Channelの動画を見て勉強します。
これ以降は進捗と、適宜自分用のメモを残すといった感じになります。
あくまで自分の勉強がメインなのでスクリーンショットとかはそんなに多く用意できないと思いますすみません。

1. Autodesk Maya のインタフェース概要

  • Fキーを使って「モデリング」「リギング」「アニメーション」・・・を切り替えできる

ふむふむとチュートリアルを進めていたら動画にはあるのに自分の画面にはツールボックスがない!
調べてもよくわからずいろいろ触っていたら、画面右下のf:id:korechi:20171228170937p:plainボタンを押したらプリファレンスが出てきて、インターフェース->UI要素->ツールボックスにチェックを入れたら無事ツールボックスが出てきた!
f:id:korechi:20171228171017p:plain:h250
こういうチュートリアルと画面が違うってパターンは心臓に悪いな。。
そしたらまた再生コントロール画面も無い。。同じようにツールボックスにてタイムスライダにチェックを入れたら出てきた!ぐぬぬ

2. ビューポートカメラの操作/ビューポートシェーディング

  • ビュー操作
    • 回転:Alt+左ドラッグ
      • Alt+Shiftで固定可
    • 並行移動:Alt+真ん中マウス
    • ズーム:Alt+右ドラッグ or スクロール
      • Alt+Ctrl+左ドラッグで指定区域にズーム
    • 画面フィット:オブジェクトを選択してF(例えば頂点のみ)
      • 全体をフィットさせるにはA
  • ビューを変えるにはパネル->正投影などから選択する
  • シェーディングのショートカット
    • 4: ワイヤーフレーム
    • 5: シェーディング
    • 6: テクスチャ付シェーディング f:id:korechi:20171228173638p:plainこれがそれに対応しているっぽい

3. ホットボックスとマーキングメニュー

Spaceを長押しするとホットボックスが出るらしいがなぜか出てこない・・・
そしたらサブディスプレイにMayaが置いてあるとホットボックスが表示されず、メインディスプレイにMayaが置いてあるとホットボックスが表示されていた・・・何だこの仕様。バグかな?気を取り直して

  • スペースキーを押すとホットボックスが表示され、さらに右ドラッグすると別のものも選択できる
    • 右ドラッグする開始位置によって4種類のショートカットホットボックスが表示される
      • 上:ワークスペースの画面分割
      • 右:インターフェースの表示非表示
      • 下:エディタへ切り替えるメニュー
      • 左:選択コンポーネントのマスクメニュー
      • センターのMaya部:カメラビュー(視点)切り替え
  • 作成項目からオブジェクトが作成できる
  • マーキングメニュー
    • オブジェクトを選択して右長押し
      • ctl+右長押しするとさらにメニューが切り替わる
      • shift+右長押しなどで押し出しとかもできる

4. 選択モードとマニピュレータ

  • f:id:korechi:20171228182331p:plain選択
    • 一括選択:左ドラッグ
    • 選択解除:ctrl押しながら選択
    • 追加選択:shift押して選択
    • 選択切り替え:shift+ドラッグ
  • f:id:korechi:20171228182358p:plain投げ縄選択
  • f:id:korechi:20171228182425p:plainペイント選択
    • ペイント範囲の調整の仕方は分からない・・・次必要になった時に改めて調べよう
  • f:id:korechi:20171228182905p:plainマニピュレータ
    • 移動:W
    • 回転:E
    • スケール:R
    • マニピュレータの大きさはテンキーの「+」「ー」で変更
    • 軸固定移動
      • 軸をドラッグor軸を選択して真ん中マウスをドラッグ
      • shift+真ん中マウスドラッグでも軸固定並行移動
  • ダブルクリックでループ選択

5. コンポーネント操作とスナップ機能

  • ポリゴンメッシュは頂点、エッジ、フェースで構成
    • 右長押しでモード切り替え
  • 頂点情報
  • 法線
    • 法線の向きで裏表が決まる
    • レンダリングする時にサーフェイスから光がどう反射するか計算
      • モデルをつくる時はすべて表が見えるように注意すべし
  • スナップ機能f:id:korechi:20171228184501p:plain
    • グリッドスナップ:最も近いグリッド(格子状)に沿って移動
      • X押しながら
    • ポイントスナップ:最も近いポリゴンの頂点に吸い付くように移動
      • V押しながら
    • カーブスナップ:カーブにそわせてスナップ
      • C押しながら
      • オブジェクトごとカーブにそわせることも可能

おわりに

Maya楽しいです!!
5回分、多分合計30分もない動画しか見ていませんが、分からないことだらけで調べながら進めてたら2時間以上かかりました。
Blenderはあまり自分にあっていなかったのか、3回チャレンジして全部1日で挫折しましたがMayaは続けられそう?かも。
ただ、分からないことがあって調べてもなかなか出てこない。僕の検索の仕方が悪いのもあると思いますが、参考ブログ的なのが少ないのかな。
モデラーさんはエンジニアみたいにブログにちょこちょこまとめることが多くはないのかも?
(MayaLTについて調べていたら、凹みTipsが出てきた時には笑いました)
分からないことがあったら自力で探すしかない。けどそれはそれで楽しい!
次回もちゃんと続きをやりたいと思います!
ではまた〜

iPhone をモーションコントローラとして使えるパッケージを作った【AR/VR向け】

はじめに

HoloQuestでも使用している、iPhoneの動きを別のアプリで取得してiPhoneと同じ位置に何かを表示するパッケージを作りました。
OculusやViveは専用モーションコントローラが存在しますが、HoloLensやGearVRには3次元位置が取得可能な専用モーションコントローラが存在しません。
そこで、こういったデバイスに対して汎用的に3次元位置や傾きを送信するアプリをiPhoneにインストールすることで、iPhoneを疑似モーションコントローラとして使うことが出来ます。

デモ

www.youtube.com

応用例

www.youtube.com

配布先

使用するアプリ

スマートフォン上で動作し、スマートフォンの動きを送信するARMotionControllerと、それを受信するARMotionReceiverを使用します。
ARMotionControllerは多くのアプリに簡単に組み込めるため、お好きなデバイス向けにビルドして使用して下さい。

前提

スマートフォンスマートフォンの位置を送信する相手(PC等)は同じネットワーク環境に繋がっている必要があります
同じwifiを使うなり、テザリングなどを使用して下さい!

使い方

現在ARMotionControllerはまだiPhoneにしか対応していないため、以降はiPhoneを使って説明します。
簡単のため、iPhoneの位置送信相手もPCとします。

手順

  1. iPhoneとPCを同じネットワークに接続
  2. iPhoneにARMotionControllerをインストールし、アプリ起動(Unityを使っても、AppStoreからインストールしてもどちらでも可)
  3. PCのIPアドレスを調べてアプリ内入力欄に記入して待機(まだStartボタンは押さない)
  4. ARMotionReceiver.unitypackageをここからDLし、自分のUnityアプリケーションにインポート
  5. Assets->Prefabs->HandTracker.prefabAssets->Prefabs->RightHandSample.prefabをHierarchyに配置
  6. Run
  7. iPhone側のアプリでStartボタンを押す
  8. 通信開始

今後

ARMotionControllerをARCore向けにも対応

WindowsでARKitRemoteを利用する時の注意

ARKitRemoteは、iOS端末上にARKitRemoteアプリがインストールされていれば基本Windowsからでも利用できます。
ただし、iTunesWindowsマシンに入っている必要があります。

docs.unity3d.com

ここの

iOSバイス (OS XiTUnes がインストールされた Windows とUSB を通して繋がっている iPhoneiPadiPod touchApple TV)

という部分ですね。

ARKit を Unity エディタ上で動作確認出来る、ARKitRemoteについて調査した

ARKitRemoteとは

Unity エディタ上で ARKit アプリの動作確認を行うための、iOS 端末上で動作するアプリです。

背景

本来、Unityで開発しているアプリを iOS 端末上で動作確認するためには、いちいち Unity のアプリを Xcode 用にビルドし、それを Xcode 上でビルドして iOS 端末にインストールする必要があります。
それはめんどくさいです。Unity エディタ上だけで動作確認が出来れば非常に便利ですよね。
しかし、ARKit の特性上 iOS 端末からのカメラ映像やセンサーの値が必要です。
それらを iOS 端末から Unity エディタに送信して、Unity 側でよしなに受け取った値をもとに動作をエミュレートさせよう、というのが ARKitRemote シーンの役割です。

動作するアプリ

なので、まずは大きく2つのアプリが動作することになります。

  1. iOS 上で動作する、カメラ映像などを Unity エディタに送信するアプリ
    • ARKitRemoteが動作
    • UnityARKitPlugin -> ARKitRemote -> ARKitRemote.Unityのシーンそのもの
    • 以降リモートアプリと呼ぶ
  2. Unity エディタ上で動作するアプリ
    • 本来作成したいアプリ
    • 以降Myアプリと呼ぶ

動作環境

  • Unity 2017.2.0f3
  • iPhone6s plus (iOS11.2)
  • Xcode 9.2

使い方

まずUnityRemoteKitの使い方は以下のサイトが参考になるので、読んで使ってみてください。
lilea.net
本記事では、ARKitRemote(と、Myアプリに何が必要か)について調査したいと思います。

ARKitRemote シーン

まず中のシーンを開いてみますと以下のようになっており、Main CameraとDirectional LightしかHierarchyにないことが分かると思います。
f:id:korechi:20171205175415p:plain
そして、この Main Camera にアタッチされている、ConnectToEditor.csUnityRemoteVideo.csが ARKitRemote シーンのみに存在していることが他サンプルシーンと比較すると分かります。
(UnityARVideo.cs は他の ARKit サンプルシーンでも使われているスクリプトです)
そのため、上記の2スクリプトを見ていきます。

ConnectToEditor.cs

大まかに言うとこのクラスは、リモートデバイスでの変化(フレームの更新やAnchorの更新)のタイミングのたびに情報を Myアプリ(Editor上で本来動作してる)に送信するクラスです。

PlayerConnection playerConnection;

void Start()
{
        Debug.Log("STARTING ConnectToEditor");
        editorID = -1;
        playerConnection = PlayerConnection.instance;
        playerConnection.RegisterConnection(EditorConnected);
        playerConnection.RegisterDisconnection(EditorDisconnected);
        playerConnection.Register(ConnectionMessageIds.fromEditorARKitSessionMsgId, HandleEditorMessage);
        m_session = null;
}

Start() 時に PlayerConnection インスタンスを生成しています。
これはエディタ、プレイヤー間の接続を扱うクラスです。 この接続はプロファイラとプレイヤーを接続することで確立されます。
インスタンス生成後、接続開始時・終了時のコールバックを設定し、大事なのはその後の処理。
playerConnection.Register(ConnectionMessageIds.fromEditorARKitSessionMsgId, HandleEditorMessage);の第1引数にはConnectionMessageIds.fromEditorARKitSessionMsgIdというのが含まれており、これはARKitRemote.unityと同じ階層にあるConnectionMessageIds.csにIDが設定されているのが確認できます。
このIDは、当然Myアプリ側から同じ値をリモートアプリに接続要求する際に送信されます。(詳しくは、ARKitRemoteConnection.cs参照)
第2引数には、コールバック関数が指定されるため、メッセージが届くたびに HandleEditorMessage () が呼ばれます。

void HandleEditorMessage(MessageEventArgs mea)
{
        serializableFromEditorMessage sfem = mea.data.Deserialize<serializableFromEditorMessage>();
        if (sfem != null && sfem.subMessageId == SubMessageIds.editorInitARKit) {
            InitializeARKit ( sfem.arkitConfigMsg );
        }
}

HandleEditorMessage 内では、メッセージをデシリアライズし、初期化関数が呼ばれます。

UnityARSessionNativeInterface m_session;

void InitializeARKit(serializableARKitInit sai)
{
        #if !UNITY_EDITOR

        //get the config and runoption from editor and use them to initialize arkit on device
        Application.targetFrameRate = 60;
        m_session = UnityARSessionNativeInterface.GetARSessionNativeInterface();
        ARKitWorldTrackingSessionConfiguration config = sai.config;
        UnityARSessionRunOption runOptions = sai.runOption;
        m_session.RunWithConfigAndOptions(config, runOptions);

        UnityARSessionNativeInterface.ARFrameUpdatedEvent += ARFrameUpdated;
        UnityARSessionNativeInterface.ARAnchorAddedEvent += ARAnchorAdded;
        UnityARSessionNativeInterface.ARAnchorUpdatedEvent += ARAnchorUpdated;
        UnityARSessionNativeInterface.ARAnchorRemovedEvent += ARAnchorRemoved;

        #endif
}

前半ではARKitに関する初期化を行い、後半ではフレームの更新や平面の検知・更新・削除の際のイベントに関数を追加しています。UnityARSessionNativeInterfaceクラスの中身の一部は以下のようになっています。

// UnityARSessionNativeInterface.cs

public class UnityARSessionNativeInterface {
//      public delegate void ARFrameUpdate(UnityARMatrix4x4 cameraPos, UnityARMatrix4x4 projection);
//        public static event ARFrameUpdate ARFrameUpdatedEvent;
    
    // Plane Anchors
    public delegate void ARFrameUpdate(UnityARCamera camera);
    public static event ARFrameUpdate ARFrameUpdatedEvent;

ARKitでは水平面を自動で認識しそこにARAnchorを設置してくれるようになっています。
Anchors 以外のイベント(例えばARFaceAnchorAdded)もとれるみたいですが、今回は使いません。(FaceID関係かな?)
あとは、イベントが発生したタイミングで MyアプリにplayerConnection.Send()を使って送信しています。

public void ARFrameUpdated(UnityARCamera camera)
{
    serializableUnityARCamera serARCamera = camera;
    SendToEditor(ConnectionMessageIds.updateCameraFrameMsgId, serARCamera);
}

public void ARAnchorAdded(ARPlaneAnchor planeAnchor)
{
    serializableUnityARPlaneAnchor serPlaneAnchor = planeAnchor;
    SendToEditor (ConnectionMessageIds.addPlaneAnchorMsgeId, serPlaneAnchor);
}

public void SendToEditor(System.Guid msgId, object serializableObject)
{
    byte[] arrayToSend = serializableObject.SerializeToByteArray ();
    SendToEditor (msgId, arrayToSend);
}

public void SendToEditor(System.Guid msgId, byte[] data)
{
    if (playerConnection.isConnected)
    {
        playerConnection.Send(msgId, data);
    }
}

送っている情報は、ARFrameUpdated()では serializableUnityARCamera のインスタンス、ARAnchorAdded()(と書いていないがARAnchorUpdated()、ARAnchorRemoved())ではserializableUnityARPlaneAnchor のインスタンスが送られる。何が送られているのかちょっと覗いてみると

// ARKitRemote -> SerializableObjects.cs

    [Serializable]  
    public class serializableUnityARCamera
    {
        public serializableUnityARMatrix4x4 worldTransform;
        public serializableUnityARMatrix4x4 projectionMatrix;
        public ARTrackingState trackingState;
        public ARTrackingStateReason trackingReason;
        public UnityVideoParams videoParams;
        public serializableUnityARLightData lightData;
        public serializablePointCloud pointCloud;
        public serializableUnityARMatrix4x4 displayTransform;
 [Serializable]  
    public class serializableUnityARPlaneAnchor
    {
        public serializableUnityARMatrix4x4 worldTransform;
        public SerializableVector4 center;
        public SerializableVector4 extent;
        public ARPlaneAnchorAlignment planeAlignment;
        public byte[] identifierStr;

serializableUnityARMatrix4x4というのはMatrix4x4クラスのようなものです。
全部の変数の型を調べてはいませんが、名前からなんとなく送られているデータを察することはできます。
pointCloud 変数には特徴点が含まれているのでしょうね

UnityRemoteVideo.cs

    public ConnectToEditor connectToEditor;
    private UnityARSessionNativeInterface m_Session;

    public void OnPreRender()
    {
        ARTextureHandles handles = m_Session.GetARVideoTextureHandles();
        if (handles.textureY == System.IntPtr.Zero || handles.textureCbCr == System.IntPtr.Zero)
        {
            return;
        }

        if (!bTexturesInitialized)
            return;
        
        currentFrameIndex = (currentFrameIndex + 1) % 2;

        Resolution currentResolution = Screen.currentResolution;

        m_Session.SetCapturePixelData (true, PinByteArray(ref m_pinnedYArray,YByteArrayForFrame(currentFrameIndex)), PinByteArray(ref m_pinnedUVArray,UVByteArrayForFrame(currentFrameIndex)));

        connectToEditor.SendToEditor (ConnectionMessageIds.screenCaptureYMsgId, YByteArrayForFrame(1-currentFrameIndex));
        connectToEditor.SendToEditor (ConnectionMessageIds.screenCaptureUVMsgId, UVByteArrayForFrame(1-currentFrameIndex));         
    }

UnityRemoteVideo.csクラス内では、OnPreRender() が呼ばれており、これはカメラがシーンのレンダリングを開始する前に呼び出されます。関数の実行順はここを参考。
最終的に、connectToEditor.SendToEditor() でbyte[] を送信しています。
詳しくないですが、ここで送信されている Y やら UV は、色空間を表すものらしいので、おそらくこれがビデオ映像の一部ではないかと思われます。

Unity エディタ上で動作するアプリ

ふぅ長くなりましたね。次は、Myアプリ上での処理について見ていきましょう。
今回使用するのはUnityARKitPlugin -> ARKitRemote -> EditorTestScene.unityです。
まずは、このシーン内で他の ARKit サンプルシーンと何が違うか見てみましょう。
f:id:korechi:20171205195956p:plain
ARKitRemoteConnection というプレハブと、HitCubeにEditorHitTest.csがアタッチされているのが分かると思います。それぞれ見ていきましょう。

ARKitRemoteConnection.cs

ARKitRemoteConnection プレハブは、ARKitRemoteConnection.cs がアタッチされているだけのオブジェクトです。
まずは、Start() の中の一部を見てみましょう。

// Start(): 
    editorConnection = EditorConnection.instance;
    editorConnection.Initialize ();
    editorConnection.RegisterConnection (PlayerConnected);
    editorConnection.RegisterDisconnection (PlayerDisconnected);

    editorConnection.Register (ConnectionMessageIds.updateCameraFrameMsgId, UpdateCameraFrame);
    editorConnection.Register (ConnectionMessageIds.addPlaneAnchorMsgeId, AddPlaneAnchor);
    editorConnection.Register (ConnectionMessageIds.updatePlaneAnchorMsgeId, UpdatePlaneAnchor);
    editorConnection.Register (ConnectionMessageIds.removePlaneAnchorMsgeId, RemovePlaneAnchor);
    editorConnection.Register (ConnectionMessageIds.screenCaptureYMsgId, ReceiveRemoteScreenYTex);
    editorConnection.Register (ConnectionMessageIds.screenCaptureUVMsgId, ReceiveRemoteScreenUVTex);

EditorConnection のインスタンスを取得し、コールバック関数を複数設定しています。
EditorConnection はEditor側からプレイヤー側に接続要求をするクラスです。
AddPlaneAnchor() を見てみましょう。

void AddPlaneAnchor(MessageEventArgs mea)
{
    serializableUnityARPlaneAnchor serPlaneAnchor = mea.data.Deserialize<serializableUnityARPlaneAnchor> ();

    ARPlaneAnchor arPlaneAnchor = serPlaneAnchor;
    UnityARSessionNativeInterface.RunAddAnchorCallbacks (arPlaneAnchor);
}

メッセージをデシリアライズし、それをUnityARSessionNativeInterfaceのコールバック関数に登録しています。
こうすることで、カメラフレームの更新があるたびにそれがそのままUnityARSessionNativeInterfaceに送られます

void ReceiveRemoteScreenYTex(MessageEventArgs mea)
{
    if (!bTexturesInitialized)
        return;
    remoteScreenYTex.LoadRawTextureData(mea.data);
    remoteScreenYTex.Apply ();
    UnityARVideo arVideo = Camera.main.GetComponent<UnityARVideo>();
    if (arVideo) {
        arVideo.SetYTexure(remoteScreenYTex);
    }

}

こっちはテクスチャの方で、最後のarVideo.SetYTexure(remoteScreenYTex);いかにもリモートアプリから送られてきたテクスチャを Editor 上に表示させているっぽいですね。(詳しくは分かりませんがおそらくそう)
と、まぁ大まかに、
- Editor側からプレイヤー側に接続要求 - リモートアプリから受信した情報をUnityARSessionNativeInterfaceに送る

でいいのかな?(ちょっと自信ない)

EditorHitTest.cs

こっちは非常に単純です。
Editor上で疑似タップ操作を行えるように、Input.GetMouseButtonDown を Update 関数の中で呼んでいるだけです。

#if UNITY_EDITOR   //we will only use this script on the editor side, though there is nothing that would prevent it from working on device
void Update () {
    if (Input.GetMouseButtonDown (0)) {
        Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
        RaycastHit hit;

        //we'll try to hit one of the plane collider gameobjects that were generated by the plugin
        //effectively similar to calling HitTest with ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent
        if (Physics.Raycast (ray, out hit, maxRayDistance, collisionLayerMask)) {
            //we're going to get the position from the contact point
            m_HitTransform.position = hit.point;
            Debug.Log (string.Format ("x:{0:0.######} y:{1:0.######} z:{2:0.######}", m_HitTransform.position.x, m_HitTransform.position.y, m_HitTransform.position.z));

            //and the rotation from the transform of the plane collider
            m_HitTransform.rotation = hit.transform.rotation;

つまり、この関数がないと Editor 上でタップができなくなるので、ただ眺めるだけのアプリになります。笑

自分のアプリをEditor上で動作確認するためには

  1. ARKitRemoteConnection プレハブをヒエラルキーに配置
    • これだけでも見た目は確認可能
  2. EditorHitTest.cs のような Input.GetMouseButtonDown (0) が書かれたスクリプトをどこかに記述する