WWDC24に参加してVision Proを買ってきた。

いよいよ今週末日本を含む8ヶ国でも発売されるApple Vision Pro。その直前に、先日アメリカで購入してしばらく使用してみた所感を記しておこうと思います。

Vision Proが発表されて以降、よく「Vision Pro買わないんですか?」と聞かれてその度に「買うためだけにアメリカには行かないけど、行く機会があれば買いたい」と答えていました。今回幸運にもWWDC24のスペシャルイベントに現地で参加できる機会があったので有言実行、WWDC24でアメリカ以外での発売が発表されるという噂もありましたが、それも承知の上で「WWDC基調講演前日に会場のApple Park向かい側のVisitor CenterでVision Proを買う」という実績を解除しに行ってきたのでした。きっと日本での販売価格はレートが高いに違いないと信じて…。

購入〜ピックアップ

まず渡航前に、アメリカのAppleのサイトからApple Vision Pro 512GBとTravel Caseをオーダー、ピックアップの予約をしました。ZEISS Optical Inserts - Prescriptionはピックアップできずデリバリーのみなので、日本国内で眼鏡を作った時の数値を元にVisiblyというサイトで処方箋を発行(PDF)、配送まで7〜10日という表示だったので逆算して現地滞在中にホテルに届くようにオーダーしました。オーダーに際して、日本で普段使っているApple IDでも問題ありませんでした。

渡航翌日、Apple Park Visitor Centerのオープンに合わせて予約をしていたので、その時間にピックアップに行きました。ピックアップ自体は、メールで届いていたチェックイン用のQRコードを提示するだけでとてもスムーズで、注意書きされていた写真付きIDを求められることもありませんでした(対応してくれたスタッフのせいかもしれない)。アメリカ国内では発売されてからしばらく経っていたせいなのか、スタッフのテンションも淡々としていてあっさりと購入完了しました(そのスタッフのせいかもしれない)。

Apple Park Visitor CenterでVision Proをピックアップ

一旦ホテルに戻って開封の儀

iPhoneを使ってVision Proのセットアップ

WWDCの会場で使ってみた

この日はWWDC24のレセプションもあったので、セットアップしたVision Proも持って行きました。やはり全世界からデベロッパーが集まるイベントのせいか、Vision Proを付けて会場をうろついているとそれをきっかけに声をかけられて話が弾んで楽しかったです。

この時の様子は @shu223 さんに撮られていた

翌日、基調講演当日もVision Proを持っていき、会場でSpatial PhotoやSpatial Videoを撮りまくりました。Spatial VideoはiPhone 15 ProとPro Plusでも撮影できますが、あとでVision Proで見るとその場にいたそのままの感覚で見られるので、これは新しい思い出の残し方ではないかと思います。

基調講演当日に、ホテルにZEISS Optical Insertsも届いていました。しかしこの日はディナーの後ホテルに戻るまでに自転車でこけて歯が折れるという事態になっていたので、翌日のDeveloper Sessionが終わって一息ついてから開封

当然ながら、普段使っている眼鏡に合わせて作っているのでピントはバッチリです。

価格差

日本での販売価格も発表されているので、アメリカでの購入価格と比較してみます。

USD(税抜) JPY(税込)
Vision Pro 512GB $3,699 ¥634,800
Travel Case $199 ¥34,800
ZEISS Optical Inserts - Prescription $149 ¥24,800

日本での販売価格を書き出していて恐ろしくなってしまいましたが、こう見るとやはりなかなかの値段です。

Vision Pro 512GBとTravel Caseは同時にオーダーしたため合計でしか分かりませんが、$3,699+$199にRecycle feeが$4かかって$3,902、そこに受け取り場所であるカリフォルニア州の消費税が$355.69かかって合計$4,257.69でした。これをクレジットカードの明細で確認すると換算レートは$1=¥159.11で¥677,440、さらにVision Pro本体のみ入国時に関税が¥34,700かかり、合計で¥712,410になりました。

Vision Pro 512GBとTravel Caseの日本での販売価格の合計は¥669,600と、アメリカで買った方がレートがいいに違いないという目論見はあっさり崩れ、関税分を引いても若干割高という結果になりました。

なお、ZEISS Optical Inserts - Prescriptionはなぜか消費税がかかっておらず$149のままで、$1=$160.631のレートで¥23,934と若干お得に購入することができました。

しばらく使ってみた所感

  • 解像度の高さとトラッキング性能が秀逸で、FPSでは酔いまくりの自分でもまったく酔わない
  • 重さはある程度あるが、使っている間はあまり気にならない。外した時に重かったんだと気づく
  • ソロニットバンドのおかげで着脱が非常に簡単。しっかり締めればズレも気にならないし、バンドが広いからかそれだけ締めても締め付け感はなかった
  • プリインストールされているデモ「Encounter Dinosaurs」の出来がとてもいい。3分くらいのうちにAR〜イマーシブな体験が盛り込まれていて、人にVision Proを体験してもらうのに最適
  • Vision ProでMacBookを見るとそこから接続して仮想ディスプレイとして表示できる体験がシームレスすぎて良い。visionOS 2.0で実装予定のウルトラワイド仮想ディスプレイもいいけど、個人的にはMacアプリをウィンドウごとにVision Proの空間内に並べて使いたい。現状はできないけど、visionOS対応のアプリ(iOS/iPadOSの互換アプリを含む)があれば一応できる。例えばSafariやZoomはvisionOS版があるし、SlackやX(旧Twitter)も動くのでそれらはvisionOS側で起動すれば空間に並べられる
  • ZoomのvisionOS版はPersona(beta)に対応しているので、ウェブカメラで撮る代わりにアバターで参加できる。ただPersonaはパスコードをオフにしたりすると消えてしまうので、そうなったら撮り直し
  • アプリによっては長く使っているとファンが回る(あったのか!)
  • バッテリーの減りが、外付けなのに結構早い気がする。まだ完全に切れたことはないが、4、5時間も続けて使えばなくなりそう。ただそれだけ使えば目もだいぶ疲れて休憩が必要なので問題ないかもしれない
  • ライトシーリングクッションやソロニットバンドなど直接肌に触れる部分は手洗いできるので、汗をかいても安心。
  • Travel Caseは一度使ってみて普段はいらないなと思った。かなり大きいので嵩張るし、出し入れも大変。本体にカバーが付属しているのでそれで十分。家での保管用にする

こちらからは以上です。

第二種電気工事士試験に挑戦してみた

近年、エンジニア界隈で取得する人が増えている第二種電気工事士

この資格を取得すれば、普段からSwitchBotやSESAMEなどを使ってやっている、おうちハック(これとかこれとか)の一環としてできることが増えるのではと思い1、僕も取得に挑戦することにしました。

その結果、令和4年度上期の第二種電気工事士試験に合格したので、自分でやった試験対策をまとめてみました。

第二種電気工事士試験

第二種電気工事士試験は年2回、上期(5月〜7月)と下期(10月〜12月)に実施されています。それぞれ筆記試験と技能試験(実技)があり、まず筆記試験に合格しないと技能試験を受験することができません2

第二種電気工事士試験に関する情報は電気技術者試験センター(以下、公式サイト)にまとまっています。公式サイトは、試験の申し込みや日程などの情報だけでなく、筆記試験・技能試験の過去問や、技能試験での欠陥例も写真付きで解説されているので、必読です。

筆記試験対策

令和4年度の上期は、受験の申し込みが3月18日〜4月7日、筆記試験が5月29日でした。筆記試験は50問出題されます(マークシート式、120分)。そのうち30問が一般問題、20問が配線図問題(配線図を見ながら解く)で、60点以上で合格となります。一般問題のうち10問程度が計算問題となっているほかは、基本的には暗記が必要となります。

僕は申し込み期間の終了間際に申し込み、Kindle版があってすぐ勉強を始められるこちらの書籍をひとまず購入しました(筆記試験まで7週間)3

いちばんやさしい 第2種電気工事士【筆記試験】 最短テキスト&出る順過去問集 改訂新版アソシエイトリンク

合間合間の時間にiPadで1章読んだあと、章末の過去問をスクショして写真アプリのマークアップで回答、という使い方で約1ヶ月かけて終えました。

しかしこの書籍、覚えることに特化していてあまり体系的にまとまってなく、解説がコンパクトで必要最小限になっていて、理論や背景まで把握しないと身につかない自分にはあまり合わなかったため、追加で以下の書籍を購入しました(筆記試験まで3週間)。

2022年版 第二種電気工事士試験 完全攻略 筆記試験編アソシエイトリンク

こちらの書籍は解説が丁寧で、所々に理解を助ける補助的な図や解説も挿入されていてわかりやすく、疑問のまま残るということがありませんでした。巻末には令和3年度の過去問が上期・下期のそれぞれ午前・午後で計4回分が丸ごと掲載されていたので、筆記試験直前に1回分をコピーして本番さながらに模試をしました。ここで80点以上取れたので、安心して本番に臨むことができました。

技能試験対策

筆記試験に合格すると、次は技能試験です。

筆記試験結果の発表(ウェブ)は6月中旬で技能試験は7月下旬です。筆記試験の結果を待ってから技能試験対策を始めるのでは期間が1ヶ月ほどしかありません。試験翌日の5月30日に公表された筆記試験の問題及び解答で自己採点をして86点だったので、技能試験対策を始めることにしました(技能試験まで7週間)。

工具と部材

何はともあれ、工具と練習用部材です。Amazonで検索するといくつかヒットしますが、僕はこちらのHOZANさんのものを購入しました(実際にはDK-28とDK-51のセットが売り切れだったので、それぞれ別で購入)。

ホーザン(HOZAN) 電気工事士技能試験工具セット 基本工具+P-958VVFストリッパー DK-28 &ホーザン(HOZAN) 第二種電気工事士技能試験 練習用部材 DK-51 1回セット ハンドブック付アソシエイトリンク

この工具セットには合格クリップという圧着する電線同士をまとめて置ける便利ツールが付属しているのですが、試験会場に持ち込んで却下されたという数年前の記事を見て(実際には使えるが試験官の判断でダメだったらしい)、使わないで練習した結果その方が速くなってしまったので僕は使いませんでした。

電工試験の虎

技能試験の候補問題は例年1月に全13問が公表され、そのうち1問が出題されます。そのため、どの問題が出題されても対応できるようにすべての問題を練習しておく必要があります。

ありがたいことに、HOZANさんがYouTubeチャンネルでとてもわかりやすい第二種電気工事士試験対策の解説動画を多数公開しています4

www.youtube.com

ひとまずここから、2022年度版 P-958の使い方・VVFケーブルのストリップノーカット版 (2021~2017年度対応)を見て工具の使い方を覚えたあと、各候補問題の解説動画を見て練習をしていきました。

www.youtube.com

解説動画は、まず複線図パートは見ずに複線図を書き起こしたあと答え合わせをして、それから残りのパートを通して見て全体を把握したあと、実際に作業してみるという使い方をしました5

時間との戦い

技能試験は制限時間40分で、とにかく時間との戦いになります。そこで、候補問題の練習も時間を測ってやりました。

問題No. 1回目 2回目 3回目
1 0:54:04 0:29:07 0:23:54
2 0:46:48 0:26:51
3 0:36:03 0:27:03 0:19:30
4 0:37:00 0:28:47 0:23:47
5 0:35:27 0:25:45
6 0:36:18 0:26:00
7 0:40:29 0:28:39 0:27:07
8 0:34:32 0:22:32
9 0:29:46 0:23:40
10 0:27:57 0:26:00
11 0:34:52 0:21:34
12 0:30:13 0:22:50
13 0:25:34 0:24:13

一番初めにやったNo.1の1回目の時点では14分オーバーでこれはヤバいぞと感じたのですが、それでも練習を続けていくうちに作業にも慣れ、追加部材を購入しての2回目が終わる頃には全問30分を切れるようになっていました。最後に、余った部材で時間が長めにかかっていた問題を再度練習しました(技能試験まで2日)。

今回技能試験の本番ではNo.1が出題されたのですが、過去最速の約18分で作品を完成させることができ、残り20分強を欠陥の確認に充てることができました。

欠陥対策

制限時間と同様に技能試験で重要なのが、欠陥がないように作品を完成させることです。欠陥があると作品が完成していても、一発でアウトです。欠陥については公式サイトで公開されている欠陥の判断基準と技能試験の概要と注意すべきポイントや、HOZANさんの工具セットなどに付属しているハンドブックを参照して、練習で作品が完成したら一つずつチェックすることで間違いをなくすことができました。

技能試験のTips

そのほか技能試験で役に立ったTipsとしては以下のような感じです。

  • マスキングテープや絆創膏(念のため)を持っていく
  • 試験会場に到着したら工具を使いやすいように並べて布尺をマスキングテープで貼っておく
  • 作業開始前の部材確認の時間で部材を確認したら部材が入っていたビニール袋を屑入れとしてマスキングテープで貼っておく

最後に

こうして、筆記試験と技能試験合わせて約4ヶ月の時間をかけて対策した結果、無事合格することができました。公式サイトによると、今回・令和4年度上期の合格率は全国で、筆記試験が58.2%、技能試験が74.3%だったそうです。

ちなみに、かかった費用は総額約5万円(受験手数料9,300円+書籍、工具、練習部材費)でした。このあと免状発行に5,300円かかります。

皆さんもぜひ、挑戦してみてはいかがでしょうか。


  1. とはいえ賃貸マンションなのでできることは限られているけど。

  2. https://www.shiken.or.jp/flow/construction02.html

  3. この時点での電気関係の知識はというと、学生時代は一応理系でしたが、覚えていたのはオームの法則くらい…。

  4. 解説のハシモトさんの声がいい。

  5. 候補問題の練習2周目以降は作品を完成させたあと、気になるポイントを拾って見てた。

マンションのオートロックをSwitchBotとSiri Shortcutsでリモートでも解錠できるようにした話

先日引っ越して初めてオートロック付きのマンションに住み始めたのですが、万が一部屋に鍵を置いたまま出かけてしまうと共同玄関から先に入れなくなってしまうと思い、なんとかリモートで解錠できないかと試行錯誤した話です。 (ポスト確認やゴミ出しで施錠せずに出たりすることもあるし、自宅の玄関ドアは以前から使っているSESAME miniをつけてアプリで施錠・解錠できるようにしているので、うっかり鍵を置いたまま出かけてしまうことはあり得る)

完成形はこちら。

まず最初は単純に、SwitchBotでインターホンの解錠ボタンを押せるようにしました。

f:id:mybdesign:20200315102516j:plain

SwitchBotはアプリから操作して物理スイッチを押すことができるデバイスです。

www.switchbot.jp

SwitchBot単体ではBluetooth経由でのみ操作できますが、ハブ(Hub Mini、Hub Plus)を使用してWi-Fiに接続することで外部から操作できるようになります。

しかし、インターホンの解錠ボタンを押しただけでは解錠されませんでした。自宅のインターホンは受話器しかないタイプで、解錠するにはどうも以下の手順が必要だったようです。

  1. 共同玄関の端末から番号を入力して呼び出す
  2. 受話器で受ける
  3. 解錠ボタンを押す

1.は致し方ないとして、問題は2.です。SwitchBotにはスイッチを「押す」モードのほかに、ONとOFFをトグルできる「壁スイッチ」モードがあります。もう1つSwitchBotを用意してこのモードを使えば、受話器をかける部分にあるフックスイッチを押したり放したりして解決できそうです。実際そのようにしている方もいました。

blog-hello-world.web.app

しかし、在宅時に宅配便が来たときなど、受話器を使用するケースもあります。その場合、いちいちアプリからSwitchBotを操作してフックスイッチを放してから通話するのはどうも面倒な感じがします。そこでどうにか受話器の上げ下げと受話器での通話を両立できる方法を考えます。

SwitchBotには「壁スイッチ」モードで使用するためのパーツが付属しています。これにフックを付けて受話器側には紐を付けて引っ掛ければ、通話時にはフックを外せば良いので、これはいけそうだと脳内シミュレーションしました。

SwitchBot側のフックには、余っていたケーブルクリップがちょうどいい大きさだったので、引っ掛け・取り外しが簡単にできるように一部をカットして使用しました。

f:id:mybdesign:20200315102355j:plain

受話器側の紐は、伸び縮みがあると引っ張り上げる高さが足りないのでこれも余っていたケーブルタイを貼り付けました。

これらをテープで仮止めして構成した第二形態がこちら。

f:id:mybdesign:20200315104018j:plain

インターホン下部のゴムバンドは、受話器を下ろすときにうまく元の位置に収まらなかったのでガイドとして付けました。

うまく行ったように見えたので、仮止めのテープを外して両面テープで固定して試してみると、スイッチをONにした(受話器を上げた)とき、SwitchBotのロボットアーム(でっぱり)が受話器を引っ張り上げた後その位置にとどまっているわけではなく、少し戻ることに気がつきました。そのため、一瞬受話器が上がりますが、解錠ボタンを押す間もなく受話器を下ろしてしまう状態でした。

受話器側の紐をタイトにしてもっと高い位置まで上げようとしましたが、そうするとフックを外しづらくなるというジレンマもあったり、あれこれ試行錯誤していたところSwitchBotのMode設定のところにPress-hold Timeという設定を発見しました。

f:id:mybdesign:20200317183503p:plain:w240

これを設定するとスイッチをON(またはOFF)にしたときロボットアームが限界まで動いたあと設定した時間分その場で待機して、それから少し戻るという動作になりました。

こうすると頑張って受話器を上げる必要もなく最小限で済むようになり、下ろしたときもスッと元の位置に収まるようになったのでゴムバンドも撤去しました。

最終形態。

f:id:mybdesign:20200317120523j:plain

フック部分。

f:id:mybdesign:20200317182549j:plain

着脱も可能なので、受話器でサッと応対できます。

これで動作するようになったので、Siri Shortcutsで一連の動作を自動的におこなうようにします。そのままではShortcutsでアクションを追加しようとしてもSwitchBotが見つからないので、SwitchBotアプリの各ボットの設定でCloud Serviceを有効にしてSiri Shortcutsにアクションを追加します(受話器側ボットのON・OFF、解錠ボタン側ボットのPress、計3つ)。

f:id:mybdesign:20200317181542p:plain

これであとはタイミングよく実行されるよう間に適当にWaitを挟んで完成です。

f:id:mybdesign:20200317181749p:plain:w240

一度共同玄関の端末から部屋番号を入力して呼び出してショートカットを実行する必要はありますが、その後は冒頭の動画のように自動的に解錠されます。

こちらからは以上です。

全身3Dスキャンで"フリー素材"化した自分とARで共演してみた話

前回の記事「全身3Dスキャンで"フリー素材"化して、踊ったりしてみた話」で、自分自身をアニメーション付きの3Dモデルデータにしたところまで書きました。その後ARKit 3を使ってARコンテンツ化したので、だいぶ時間が経ってしまいましたが記事にしてみます。

完成したものはこんな感じ。

ソースコードはこちら。

https://github.com/miyabi/dancing-ar

やりたいこと

ARKitで3Dオブジェクトを描画するには、SceneKit、SpriteKit、iOS 13から追加されたRealityKitを使うか、あるいはMetalを使って独自に描画します。今回は手軽にARコンテンツを作成するために、SceneKitを使用します。

また、画面のタップした位置に3Dモデルを配置する仕様にします。リアルな3DモデルなのでARで表示するときもリアルに表示したいと思い、以下の実装もおこないます。

  • 3Dモデルに当たる照明と実際の照明をなじませる(Light estimation)
  • 影を描画する
  • 3Dモデルで手前の人物が隠れてしまうのを防ぐ(People occlusion)

準備

mixamoでアニメーションを付けた3DモデルはFBX(.fbx)かCollada(.dae)形式でダウンロードできます。.dae形式は特別なライブラリが不要で、Xcodeがビルド時に自動的にSceneKitで扱える形式に変換してくれるので、この形式でダウンロードしておきます。

SceneKitで扱える3Dモデルのフォーマット/アニメーションつき3DモデルをSceneKitで使う

XcodeのNewメニュー→Projectから「Augmented Reality App」を選択し、Content Technologyを「SceneKit」にします。

以下、コードはすべてViewController.swiftに実装しています。

モデルの配置

.dae形式の3Dモデル(とテクスチャー)をリソースとしてプロジェクトに追加します。viewDidLoadで3Dモデルを読み込みますが、あとで画面のタップした位置に配置したいのでシーンには追加せずにSceneKitのノードとして保持しておきます。

var baseNode: SCNNode!

override func viewDidLoad() {
    // ...

    baseNode = SCNScene(named: "Samba Dancing.dae")!.rootNode.childNode(withName: "Base", recursively: true)
}

ARWorldTrackingConfiguration.planeDetectionに.horizontalを指定して、平面を検出できるようにします。

override func viewWillAppear(_ animated: Bool) {
    // ...

    configuration.planeDetection = [.horizontal]

    // ...
}

Main.storyboardにTap Gesture Recognizerを追加し、画面のタップを受け取るアクションを追加します。

タップした位置に対応する現実空間の座標を取得するにはARFrame.hitTestを使用しますが、今回はiOS 13から追加されたARSCNView.raycastQueryとARSession.raycastを使って実装します。

@IBAction func screenDidTap(_ sender: UITapGestureRecognizer) {
    guard let view = sender.view else { return }
    
    if sender.state == .ended {
        let location = sender.location(in: view)
        guard let raycastQuery = sceneView.raycastQuery(from: location, allowing: .estimatedPlane, alignment: .horizontal) else { return }
        guard let raycastResult = sceneView.session.raycast(raycastQuery).first else { return }

        let position = SCNVector3Make(
            raycastResult.worldTransform.columns.3.x,
            raycastResult.worldTransform.columns.3.y,
            raycastResult.worldTransform.columns.3.z
        )

        let newBaseNode = baseNode.clone()
        newBaseNode.position = position
        sceneView.scene.rootNode.addChildNode(newBaseNode)
    }
}

まずraycastQueryにSCNView上での座標、raycastをヒットさせる対象とそのalignment(今回は検出された水平面にするのでそれぞれ.estimatedPlaneと.horizontal)を指定してクエリを作ります。そのクエリをARSession.raycastに渡すことによって、現実空間の座標を取得できます。ただしsimd_float4x4なので、これをSCNVector3に変換します。

保持しておいた3Dモデルのノードをcloneして、得られた座標を設定し、シーンに追加します。

Light estimation

Light estimationはキャプチャしたシーンの画像から照明を推定する機能で、初期のARKitから利用可能です。

まず、Light estimationを反映させるための環境光を追加します。

var ambientLightNode: SCNNode!

override func viewDidLoad() {
    // ...

    let ambientLight = SCNLight()
    ambientLight.type = .ambient
    ambientLight.shadowMode = .deferred
    ambientLightNode = SCNNode()
    ambientLightNode.light = ambientLight
    scene.rootNode.addChildNode(ambientLightNode)
}

ARWorldTrackingConfigurationのisLightEstimationEnabledとenvironmentTexturingを有効にします。

override func viewWillAppear(_ animated: Bool) {
    // ...

    configuration.isLightEstimationEnabled = true
    configuration.environmentTexturing = .automatic

    // ...
}

ARSCNViewを使用している場合、これだけで現実空間の照明の変化は自動的にシーンの照明に反映されます。

影の描画

単に3Dモデルを表示するだけでは"浮いて"見えてしまいます。そこで影を描画します。

まず、光源となるDirectional lightを追加します。この照明で影を描画するのでcastsShadowをtrueにします。また、レンダリングパスの最後に描画するのでshadowModeを.defferedに、shadowColorは半透明の黒にします。

少し影をぼかしてよりリアルにするため、shadowSampleCountとshadowRadiusの値を8に設定します。

最後に、Light estimationでは光源の方向を推定できないため、真上から光が当たるようにして、シーンに追加します。

var directionalLightNode: SCNNode!

override func viewDidLoad() {
    // ...

    let directionalLight = SCNLight()
    directionalLight.type = .directional
    directionalLight.intensity = 1000
    directionalLight.castsShadow = true
    directionalLight.shadowMode = .deferred
    directionalLight.shadowColor = UIColor.black.withAlphaComponent(0.5)
    directionalLight.shadowSampleCount = 8
    directionalLight.shadowRadius = 8
    directionalLightNode = SCNNode()
    directionalLightNode.light = directionalLight
    directionalLightNode.rotation = SCNVector4Make(1.0, 0.0, 0.0, -Float.pi / 2.0)
    scene.rootNode.addChildNode(directionalLightNode)
}

このままでは影が投影される平面がシーン上にないので、検出した現実空間の平面のジオメトリから作ります。このジオメトリのマテリアルのcolorBufferWriteMaskを空にしておくことで、透明で影だけ投影できる平面が作れます。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
    let device = MTLCreateSystemDefaultDevice()!
    let planeGeometry = ARSCNPlaneGeometry(device: device)!
    planeGeometry.update(from: planeAnchor.geometry)
    planeGeometry.firstMaterial?.colorBufferWriteMask = []
    node.geometry = planeGeometry
}

People occlusion

通常ARコンテンツにおける3Dモデルは、現実空間をキャプチャした画像の上に描画されるため、手前にある現実の物体の奥に3Dモデルを表示することができず、違和感の元となっていました。

iOS 13から使用可能になったPeople occlusionによって、手前の人物をマスクすることができるようになり、この違和感を低減させることができます。

Occluding Virtual Content with People | Apple Developer Documentation

ただし、この機能にはA12 Bionic以降に搭載されている8コアのニューラルエンジンが必要なため、すべてのデバイスで使用することはできません。対応しているデバイスは、XR以降のiPhoneと、第3世代以降の12.9インチまたは11インチのiPad Pro、第3世代以降のiPad Air、第5世代以降のiPad miniとなっています。

People occlusionを有効にするには、ARWorldTrackingConfiguration.supportsFrameSemanticsで対応しているデバイスか確認したあと、ARWorldTrackingConfiguration.frameSemanticsに.personSegmentationWithDepthを設定します。

override func viewWillAppear(_ animated: Bool) {
    // ...

    if ARWorldTrackingConfiguration.supportsFrameSemantics(.personSegmentationWithDepth) {
        configuration.frameSemantics.insert(.personSegmentationWithDepth)
    }

    // ...
}

ARSCNViewを使用している場合は、これで3Dモデルより手前の人物がマスクされます。

ARCoachingOverlayView

最後に本筋ではありませんがiOS 13で追加されたARCoachingOverlayViewを実装しておきます。

ARCoachingOverlayView - ARKit | Apple Developer Documentation

バイスに現実空間を認識させるためには、その仕組み上、デバイスを少し動かすなど一定の手順があります。これまでは開発者自身がそれをユーザーに伝える工夫をする必要がありましたが、それがOSの標準機能で可能になりました。

実装はこんな感じです。sessionに使用するARSession、goalに.horizontalPlaneを設定して(今回は水平面の検出なので)、ARSCNViewに追加します。

var coachingOverlayView: ARCoachingOverlayView!

override func viewDidLoad() {
    // ...

    coachingOverlayView = ARCoachingOverlayView()
    coachingOverlayView.session = sceneView.session
    coachingOverlayView.delegate = self
    coachingOverlayView.activatesAutomatically = true
    coachingOverlayView.goal = .horizontalPlane
    
    sceneView.addSubview(coachingOverlayView)

    coachingOverlayView.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        coachingOverlayView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        coachingOverlayView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        coachingOverlayView.widthAnchor.constraint(equalTo: view.widthAnchor),
        coachingOverlayView.heightAnchor.constraint(equalTo: view.heightAnchor),
    ])
}

最後に

実際のプロジェクトでは、スケールを設定するスライダーとPeople occlusionの有効/無効を切り替えるスイッチを実装しています。

スケールを小さくしてたくさん3Dモデルを配置するとわらわらして楽しい感じになります。

スイッチの方は、People occlusionを有効にすると(ARWorldTrackingConfiguration.frameSemanticsに.personSegmentationWithDepthを設定すると)、なぜか平面に影が描画されないという現象があったので実装しました。
3Dモデル上には影が投影されているので、colorBufferWriteMaskあたりの問題なのかなあとは思っています。

こちらからは以上です。

全身3Dスキャンで"フリー素材"化して、踊ったりしてみた話

先日9月25・26日に開催されたUnite Tokyo 2019のブース展示で、Digital Artisanさんが全身3Dスキャナー「3D GATEWAY」を展示、3Dスキャン体験をおこなっていたので、スキャンしてもらってきました(3Dスキャン体験は他のイベントなどでも実施されているようです)。

スキャンしたデータはSketchfab上で公開され、FBXかglTFでダウンロードが可能です。

sketchfab.com

ポリゴン数は20万。

f:id:mybdesign:20191002151235p:plain

せっかくなので動かしてみたいと思い、Adobemixamoでリギングしてみました。mixamoはAdobeのアカウントで使用できます。

元のFBXをそのままmixamonにアップロードしたらエラーになってしまったので、ポリゴンを減らしてみます。

f:id:mybdesign:20191002151722p:plain

Cheetah 3DのTools→Polygon→Simplifyでは微妙な感じだったので、一旦OBJに変換してMeshmixerを使うことにしました。

f:id:mybdesign:20191002151855p:plain

Selectから全部選択してEdit→Reduce、デフォルトで50%になっているのでそのままAccept、再びOBJでエクスポートします。

これをマテリアル、テクスチャーと一式zipにしてmixamoにアップロードしてみましたが、真っ黒になってしまいました。

f:id:mybdesign:20191002152055p:plain

Cheetah 3Dで開いてみると余計なマテリアルがあったので、これを削除して再びzipにしてアップロードすると、今度はうまくいきました。

f:id:mybdesign:20191002152306p:plain

mixamoでは手首、肘など8箇所をマークするだけで、自動的にリギングしてくれます。とっても簡単!

f:id:mybdesign:20191002152630p:plain

リギングしたモデルには、2,000以上あるアニメーションを割り当ててその場でプレビューできます。これで色々試しているだけでもだいぶ楽しい!

アニメーションを割り当てたモデルはFBXなどでダウンロードできます。もちろんUnityのアセットとして使用できますが、マテリアルがない状態になるので割り当て直す必要があります。

f:id:mybdesign:20191002170929p:plain

こちらからは以上です。

iPhone XR/XS (Max) のeSIMを使うと海外でのデータ通信が超絶便利だった話

とてつもなく久しぶりに記事を書きます。

海外でデータ通信を利用する際、国内通信事業者のデータローミングプランやレンタルのWi-Fiルーターでは割高だったり通信量に不満があるので、僕はよく現地通信事業者のプリペイドプランを使っていました。

現地のプリペイドプランは割安なのがとても魅力的です。よく使っていたアメリカのAT&Tでも8GBが$40、昨年ポーランドに行ったときにはOrangeの6GBのLTE通信プランが5ズウォティ(日本円にして約150円ほど)で利用できました。

f:id:mybdesign:20190529173854j:plain:h320

しかし、現地のプリペイドプランを利用するには、到着後にSIMを入手しなければならなかったり、事前にSIMを入手していても空港などWi-Fiの繋がるところでアクティベートする必要があります。何をするにもデータ通信に依存している身としては、勝手の分からない海外で一刻も早くライフラインを獲得したいのに、なかなか繋がらないと焦りと不安を感じてしまいます…。

今回、iPhone XR/XS (Max) に搭載されたeSIMが超絶便利と聞き、ちょうど海外に行く機会もあったので早速使ってみました!

eSIMとは

iPhone XR、XSとXS Maxには物理的なnano-SIMのほかに電子的に書き換え可能なeSIMが内蔵されています。これらのSIMは、それぞれ別の回線として使用することができます。

support.apple.com

また、多くの通信事業者がeSIMに対応したモバイル通信プランを提供しています。各通信事業者はこれらのプランを利用するためのアプリをApp Storeで提供しているため、面倒な手続きをすることなく利用できます。

support.apple.com

先日ハワイへ家族旅行した際は、このAppleが公開しているeSIMサービス提供事業者リストにもあるT-Mobileを利用しました。T-Mobileは、以前Apple SIMが内蔵された9.7インチのiPad Proで利用したことがありましたが、そのときはアクティベーションを設定アプリからおこない、また支払いに必要なリフィルカードを入手するのに手間取った記憶があります。今回はT-Mobileが提供している専用アプリがあり、日本のクレジットカードでも決済ができたので非常にスムーズでした。

5/30 追記:

T-Mobile eSIM

T-Mobile eSIM

eSIMを使ってみる

そして今回、韓国への出張で再びeSIMを利用してみたので、その手順をまとめてみます。

上記のeSIMサービス提供事業者リストには韓国の事業者が入っていません。しかしグローバルなサービスプロバイダーのGigSkyが韓国にも対応しているので、こちらを利用することにします。
利用を開始するための設定は出発前、日本国内にいるうちにできます。まずはApp StoreからGigSkyのアプリをインストールします。

GigSky Global Mobile Data

GigSky Global Mobile Data

  • GigSky, Inc.
  • 旅行
  • 無料

アプリを起動したらまず目的地を選択します。

f:id:mybdesign:20190529174747p:plain:w240

続いてデータ通信プランを選択します。韓国向けには5種類のプランが提供されていました。今回は3泊4日の出張だったので2GB/¥3,600のプランを選択します。

f:id:mybdesign:20190529175421p:plain:w240

プランを選択するとログイン画面になるので、必要があればアカウントを作成してログインします。

f:id:mybdesign:20190529175516p:plain:w240

続いて支払い。Apple Payにも対応しているのでとてつもなく簡単です。

f:id:mybdesign:20190529175542p:plain:w240

あとは手順に従ってポチポチとボタンを押していくだけでモバイル通信プランが追加されます。他と区別するための回線名だけは自動的に設定されないので、手順に従ってコピーしておいたラベルを貼り付けます。

f:id:mybdesign:20190529175609p:plain

これで利用できる状態になりました。アンテナピクトが二重に表示されているのが確認できます(片方がいつも使っている主回線で、もう片方がGigSky)。

f:id:mybdesign:20190529175819p:plain:w240

まだ日本国内にいるので、モバイルデータ通信/デフォルトの音声回線とも主回線にしておきます。
現地に到着したらモバイルデータ通信をGigSkyに切り替えます。

f:id:mybdesign:20190529180045p:plain:w240

これでもう現地回線につながります!簡単すぎて怖いレベル。飛行機が着陸後、モバイル機器が利用になってすぐ利用できるので、入国に時間がかかってもその間にホテルまでの道のりを調べたり、現地のレストランを検索するなどの余裕ができます。

ちなみに今回はSNSへの写真のアップロードやGoogle Mapsを主に利用して、トータル1.5GBほどの通信量でした。

f:id:mybdesign:20190529180141p:plain:w240

いかがでしょうか?みなさんも海外に行く機会があるときはぜひ使ってみてください!

UnityでGoogle Play Game Servicesとmobile backendを導入してハマった話

UnityでGoogle Play Game Servicesを導入済みのプロジェクトにmobile backendを導入した際、Androidビルドでハマったので解決に至るまでをメモとして残します。

環境

  • Unity 5.3.4f1

序章

Google Play Game Services

Google Play Game Services(以下GPGS)のプラグインは、以下のリポジトリから現時点での最新版0.9.32をプロジェクトに導入済みでした。

playgameservices/play-games-plugin-for-unity: Google Play Games plugin for Unity

mobile backendのインポート

プッシュ通知に対応するため、NIFTY Cloudのmobile backendのUnity SDK(2.1.1)を導入しました。

NIFTYCloud-mbaas/ncmb_unity: ニフティクラウド mobile backend Unity SDK

このとき、android-support-v4.jarとgoogle-play-services.jar(と、res/values/version.xmlも)はGPGSのプラグインによりプロジェクトにインポート済みだったので、外しました。

起動、そしてクラッシュ

この時点でビルドしてAndroidの実機上で起動してみると、クラッシュします。OMG!

Android Studioのlogcatを見ると、以下のようなログが出ています。

04-10 00:48:34.984 24899-24988/? E/AndroidRuntime: FATAL EXCEPTION: IntentService[RegIntentService]

Process: com.mybdesign.mobilebackendexample, PID: 24899

java.lang.Error: FATAL EXCEPTION [IntentService[RegIntentService]]

Unity version : 5.3.4f1

Device model : LGE Nexus 4

Device fingerprint: google/occam/mako:5.1.1/LMY48T/2237560:user/release-keys

04-10 00:48:34.989 567-1033/? W/ActivityManager: Force finishing activity 1 com.mybdesign.mobilebackendexample/com.nifty.cloud.mb.ncmbgcmplugin.UnityPlayerNativeActivity

どうやらmobile backendのプッシュ通知のデバイス登録をおこなうタイミングでクラッシュしているようでした。

GPGSのダウングレード

経験上、GPGSのインポート、セットアップで追加されるandroid-support-v4とgoogle-play-servicesがほかの広告SDKと競合を起こすことがよくあったので、まずこれを疑ってGPGSのダウングレードを試みました。

プロジェクトにインポート済みのGPGSを一旦削除して(GooglePlayGamesフォルダ、PlayServicesResolverフォルダ、Plugins/Androidのlibsフォルダ、MainLibProjフォルダをまず削除、Unityを再起動してからplay-services-.aarとsupport-.aarを削除)、old-builds/GooglePlayGamesPlugin-0.9.21.unitypackageをインポート、Windowメニュー→「Google Play Games」→「Setup」→「Android setup...」から再度セットアップをしました。

するとConsoleに大量のエラーが出力されます。

Assets/GooglePlayGames/ISocialPlatform/PlayGamesPlatform.cs(94,21): error CS0104: `Logger' is an ambiguous reference between `UnityEngine.Logger' and `GooglePlayGames.OurUtils.Logger'

Unity 5.3以降でUnityEngine.Loggerが定義されたので、こいつが曖昧だということですね。

ここは面倒ですが、GPGSでGooglePlayGames.OurUtils.Loggerクラスを使っているファイル全部に以下を追加して解決してやります。

using Logger = GooglePlayGames.OurUtils.Logger;

これで再度ビルドして実機で実行してみます。

04-10 01:29:21.040 28391-28411/? I/Unity: OnRegistrationSucceeded

(Filename: ./artifacts/generated/common/runtime/UnityEngineDebugBindings.gen.cpp Line: 37)

今度はmobile backendのデバイス登録に成功しているようですが...。

今度はGPGSが...

logcatをよく見ると、以下のようなログが出ています。

04-10 01:29:16.432 28391-28411/? D/GamesUnitySDK: Performing Android initialization of the GPG SDK

04-10 01:29:16.447 28391-28411/? E/GamesNativeSDK: Can't register class com/google/android/gms/appstate/AppStateManager: an exception occurred.

04-10 01:29:16.515 28391-28411/? E/GamesNativeSDK: Could not register one or more required Java classes.

04-10 01:29:16.688 28391-28411/? I/Unity: InvalidOperationException: There was an error creating a GameServices object. Check for log errors from GamesNativeSDK

at GooglePlayGames.Native.PInvoke.GameServicesBuilder.Build (GooglePlayGames.Native.PInvoke.PlatformConfiguration configRef) [0x00000] in <filename unknown>:0

at GooglePlayGames.Native.NativeClient.InitializeGameServices () [0x00000] in <filename unknown>:0

at GooglePlayGames.Native.NativeClient.Authenticate (System.Action`1 callback, Boolean silent) [0x00000] in <filename unknown>:0

at GooglePlayGames.PlayGamesPlatform.Authenticate (System.Action`1 callback, Boolean silent) [0x00000] in <filename unknown>:0

at GooglePlayGames.PlayGamesPlatform.Authenticate (System.Action`1 callback) [0x00000] in <filename unknown>:0

at GooglePlayGames.PlayGamesLocalUser.Authenticate (System.Action`1 callback) [0x00000] in <filename unknown>:0

at MobileBackendExample.Start () [0x00000] in <filename unknown>:0

(Filename: Line: -1)

どうやらGPGSが機能していないようです。そういえばGPGSのログイン画面が出なかったような...。

エラーの内容を見てみると、AppStateManagerクラスが登録できない様子。ググってみると、あるバージョンのgoogle-play-servicesからAppStateManagerクラスがなくなっているらしいことがわかりました。

Error Creating a GameServices object. ? Issue #884 ? playgameservices/play-games-plugin-for-unity

とりあえず旧バージョンのgoogle-play-servicesが入手できないか探してみます。するとStack Overflowにまんま直球の質問が。

java - How to download older google play services? - Stack Overflow

ここからひとつずつダウンロードしてAppStateManagerクラスが含まれているバージョンのgoogle-play-servicesを探します。

jarの中身を確認するのにはJava Decompilerを使いました。GUIもあって便利です。

r26のgoogle-play-servicesにはAppStateManagerクラスがあったので、これをプロジェクトにインポート済みのものと差し替えます1

Finally it works!

再度ビルドして動作を確認すると、GPGSのログイン画面も表示され実績やリーダーボードも機能していました。mobile backendの方も、正常にプッシュ通知が機能していました。

ライブラリのソースコードを書き換えたり、古いバージョンを使ったりしているので正直おすすめはできませんが、同様にハマった人の参考になればと思います。

オマケ

実はあと2つほどハマったポイントがあります。

  • Player SettingsのStripping LevelをDisabledにしておかないとmobile backendのデバイス登録が機能しなかった。
  • 使っているAndroidプラグインが多すぎて、ビルド時にToo many method referencesが発生→不要なjarを削除。


  1. ここで再度GPGSのセットアップをおこなうと、google-play-servicesを元に戻されてしまうので注意。 ?



from mybdesignの投稿 - Qiita