iPhoneアプリ開発:AR 目次

 

 いかがですかラーマのお味は?、もとい、AR油圧ショベル(組み込み形状版)を動かしたご感想は?

 

 

 単純な形状だけど意外にね。意外に…

 ARなんで、近づいたり、見上げたり、大きさが実感できるのは、意外にアリなんじゃないかと…

 まーでも、やっぱとっても幾何学なわけですよ。

 もうちょっと、油圧ショベルらしくしたいと。

 そうなると、さすがに組み込みの形状では限界があるんですな。

 特にバケツ!てめーはダメだ!

 

 

 こういう時に出番なのがモデリングツールっす。いでよ〜

 Maya

 は、高いからやめておいて

 Blender

 使います。無料です。

 Mayaも学生さんはタダで使えるんだっけか。

 私は学生ではないのでBlenderで行きます。

 こいつは独立したアプリケーションでして、ClipStudioやPhotoshopが2Dの画像創作に使われるアプリなのに対し、Blenderは3Dの画像創作に使われるアプリって位置付けです。

 そんでもって3Dの画像製作には、大きく分けるとモデラー部、レンダラー部という2つのパートがあって、それぞれに担当アプリが用意されてる場合もあるんですが、Blenderの場合はどっちも兼ね備えてます。

  • モデラー:3D形状やマテリアルといった仮想体群を製作する
  • レンダラー:モデラー部で作った仮想体群を、どんな感じで配置するかとか、光源の配置とかを決めて、静止画や動画に落とし込む

  注意)マテリアルとか光源って何って人は「光あれ」ね。

 

 今回使うのは、Blenderのモデラーとしての機能。

 こいつを使うと多角形平面の組み合わせで自由に3D形状を作り出すことができるわけですよ。

 で、実際にBlenderで、ブーム、アーム、バケツの3D形状を用意したのがこれ。

 

  Blenderの画面↓

 

 これらの3D形状を、自分の油圧ショベルプロジェクトに組み込んでブーム、アーム、バケツノードのSCNGeometryとして使えれば、見た目が一気に華やかになるわけですわ。SCNGeometryがわからん人は「仮想体を置いてみる」ね。

 

 どうやって組み込むかというと、Blenderで用意した3D形状やマテリアル情報を、Xcodeが解釈できるファイル形式で書き出させて、そのファイルを読み込んで使います。

 

 

 書き出すファイルに指定するフォーマットはCOLLADA形式。

COLLADA

 こいつはPlaystation3の3Dアプリの開発用公式フォーマットとして策定されたものらしいっす。今はクロノス・グループ(OpenGLやWebGL管理してるとこね)が管理してるみたい。ファイルの拡張子は .dae (digital asset exchange)になります。

 で、上のBlenderで作ったブーム、アーム、バケツの3D形状を書き出したファイルがこれ。

 

COLLADAファイル:

 http://tetera.jp/xcc/book-sample/yumbo.dae.zip

 

 このyumbo.dae.zipファイルを解凍してできたyumbo.daeファイルは、Macのプレビューで内容を見ることもできるんで、ダウンロードしてダブルクリックもしくはQuickViewしてみてください。

 

プレビューアプリでの表示

 Blenderのインストール、3D形状の作り方は次回からゆっくりやるとして、とりあえず、今回はこのyumbo.daeファイルを使い、私がBlenderで作った3D形状ちゃんをSceneKitのSCNNodeに組み込んでみます。

 まずはiPhoneアプリでのファイルの扱いから。

iPhoneアプリへのファイルの組み込み

 今回のような3D形状やマテリアル情報の入ったファイルを、アプリで使いたい場合、使いたいファイルをワークスペースウィンドウのナビゲーションエリアにドラッグ&ドロップしてプロジェクトに加えてやることで、そのファイルはアプリと一緒にコピーされるようになって利用可能になります。

 

 ちなみに、音楽やムービーなんかも含め、こういったファイルをリソース(Resource:素材)と呼んだり、アセット(Asset:資産)と呼んだりします。

 Appleでは、こういったリソースファイル群とアプリとの結びつけを、バンドルという仕組みで実現してまして…

バンドル

 って何だ?っていうと、こいつは特定のルールに従って構成されたフォルダ階層群を意味してます。

 その最上位フォルダは、MacのFinderだとアイコンで表示され1つのファイルみたいに扱われることになってまして、MacのFinder上で見えるアプリアイコンなんかがこのバンドルです。

 実行可能ファイルだと思った?残念フォルダーちゃんです。

 実際のアプリ、つまり実行可能ファイルはこのバンドルフォルダーの中に入ってます。そのままダブルクリックしてもアプリが起動しちゃうだけので、内容を見たい場合はFinder上のアイコンを右クリックして、コンテキストメニューを出して「パッケージの内容を表示」を選びます。

 例えばSafariだとこんな感じ。

 

 こんな風に、アプリ本体と関連ファイル群はバンドルというフォルダ構成でひとまとめにされてまして、今回のように追加したファイルは、Macのアプリの場合だと、このバンドル内のResourcesて名前のフォルダにコピーされることになってるんですよ。ちなみにiPhoneアプリの場合はバンドル最上位フォルダ直下になる。

 

 で、こうやって関係付けたファイルをアプリから利用する場合、当然、使いたいファイルの置き場所を指定してやる必要があるわけでして、そのためには、まずファイルを格納してるフォルダの位置を知る必要があります。

 そのためにプログラムから利用するのがswiftだとBundleクラス。

Bundle

 こいつはバンドルを表現したクラスで、そのクラスオブジェクトが持つmainプロパティには、アプリを収納したバンドルがBundleオブジェクトとして設定されてます。

 で、そのオブジェクトにurl(forResource:withExtension:)メッセージを送ることで、バンドル内に配置されているファイルのURLが取り出せるようになってるんですよ。

URL

 URL(Uniform Resource Locator)は、これまでブラウザでさんざん見てきたと思うけど"https://aaa.bbb.com/ccc.html"といった文字列のことです。ファイル位置の場合は"https:"が"file:"になって、最上位のフォルダからファイルまでの階層を / (スラッシュ)で区切って書いていくことになる。

注意)細かく言えば"https:"や"file:"はscheme(スキーム)と呼ばれ、今だとURI(Uniform Resource Identifier)の一部として定義されてる。URIの意味は、統一された資源識別子って感じかな。ま〜、もともとWebで資源位置を示す統一された表現ってことでURLが作られ、それ見て、これって他にもいろいろ使いみちがありそうだねと、公式に定義を拡張したのがURIって感じで私は解釈してます。

 

 url(forResource:withExtension:)メッセージが戻すのは、このURLを表現するオブジェクト。クラス名は、まんまURLという名前で、swiftでは、こいつを使って利用したいファイルの位置を特定することになります。

url(forResource:withExtension:)メッセージ

 てなわけでBundleに対し、このメッセージを送ることで、そのバンドル内に配置されているファイルのURLがもらえます。アプリの入ったバンドルはBandleクラスオブジェクトのmainプロパティに設定されてるBundleなので、Bundle.mainに対してこのメッセージを送るわけですな。

 forResource:引数にファイル名、withExtension:に拡張子を指定する決まりなので、今回のyumbo.daeファイルなら

   let yumboURL = Bundle.main.url(forResource: "yumbo", withExtension: "dae")

て感じです。このメッセージ、ファイルが見つからない時はnilを戻すので注意が必要。使うときはguard文等でチェックするように。nilやguard文がわからん人は「アンラップしてチン♪」ね。

 うまくいけばyumboURLにyumbo.daeファイルの位置を示すURLが設定されます。

注意)実はUIImageでの画像ファイル読み込みも、内部でやってることは同じ。UIImageはイニシャライザのname:引数で指定された名前をもとに、バンドル最上位フォルダ直下や、その中に置かれた画像用Assetフォルダ内の画面解像度に適した画像ファイルを探すような仕組みができてる。ここら辺に興味がある人はAppleの「Xcode Overview」の「Adding Data Sets」や「Asset Catalog Format Reference」を読みましょう。
 
 あとは、どうやって、このyumboURLで示されるファイルを読み込んで、3D形状やマテリアル情報を取り出すかってことになるんだけど…
 SceneKitはCOLLADAファイルからSCNSceneを作り出せるようになってまして、url:引数を持つSCNSceneのイニシャライザに、読み込ませたいCOLLADAファイルのURLを渡しちゃえば、COLLADAファイルに記述される3D形状やマテリアル群は、SCNNodeの階層構造に変換され、作成されたSCNSceneのrootNodeに登録済みになっちゃうんですな。グレート。
 今回ならyumboURLを渡しちゃえばいい。
   let yumboScene = SCNScene(url:yumboURL)

 ファイルの読み込みに失敗した時は例外を投げるので注意するように。try?等で対応しましょう。try?がわからん人は「キャッチして」ね。

 

 Blender自体はオランダの人が作ったオープンソースのアプリで、Appleとは無関係なんだけど、詳細が公開されてるCOLLADAフォーマットでファイルを書き出すことで、3D形状やマテリアル情報をXcodeも解釈できるようになるわけですな。

 フォーマット大事。

 

 というわけでファイルに記述された3D形状群は、それぞれがSCNGeometory派生オブジェクトになり、そのオブジェクトを持つSCNNodeが用意されることになります。
 さらには、あらかじめBlenderで、作った3D形状に名前をつけておいてやれば、その3D形状をSCNGeometoryとして持つSCNNodeにも、その名前がつくようになっているので、読み込んで出来上がったSCNSceneのrootNodeに対し、その名前で検索かければ、Blenderで作った3D形状をgeometoryプロパティに持つSCNNodeが手に入るってわけですよ。
 例えばBlenderでブームの3D形状に"boom"って名前を付けてたならこうやる。回転の時と同じです。
   let node = yumboScene.rootNode.childNode(
   		withName: "boom", recursively: true)
 この見つけたSCNNodeをそのまま利用できるわけです。
 BlenderでSCNGeometoryごとの原点位置は調整してるので、pivotの設定は無用。マテリアルもSCNGeometoryに設定済み。あとはaddChildNodeで順に親子関係を結んで、親側での位置を指定していけばいい。
 前回のExcavatorのイニシャライザとの違いはこんな感じ。
class Excavator {
	・・・
    init() {
        土台・肩はこれまでどおり
        let base = SCNBox()
        let baseNode = SCNNode(geometry: base)
        let shoulder = SCNSphere()
        let shoulderNode = SCNNode(geometry: shoulder)
        shoulderNode.name = Excavator.sholderID
        baseNode.addChildNode(shoulderNode)
        
        COLLADAファイルを読み込みSCNSceneオブジェクト作成
        guard let yumboURL = Bundle.main.url(
        	forResource: "yumbo", withExtension: "dae") else {
            return
        }
        guard let yumboScene = try? SCNScene(url:yumboURL) else {
            return
        }
        
        作成したSCNSceneオブジェクトから
        ブームを取り出し、子ノードとして登録
        let boom = SCNCone(
        	topRadius: 0.3, bottomRadius: 0.4, height: 3)
        let boomNode = SCNNode(geometry: boom)
        boomNode.name = Excavator.boomID
        guard let boomNode = yumboScene.rootNode.childNode(
        	withName: Excavator.boomID, recursively: true) else {
            return
        }
        shoulderNode.addChildNode(boomNode)
        
        アーム・バケットも同様
        
        ・・・
            
        ブーム・アーム・バケットのpivotは設定不要
        baseNode.pivot = SCNMatrix4MakeTranslation(
        	0, baseNode.geometry!.boundingBox.min.y, 0)
        shoulderNode.position = SCNVector3(
        	x:0, y:baseNode.geometry!.boundingBox.max.y, z:0)
        boomNode.pivot = SCNMatrix4MakeTranslation(
        	0, boomNode.geometry!.boundingBox.min.y, 0)
        boomNode.position = SCNVector3(
        	x:0, y:shoulderNode.geometry!.boundingBox.max.y - 0.3, z:0)
        ・・・
        
        ブーム・アーム・バケットのfirstMaterialは設定不要
        base.firstMaterial?.diffuse.contents = UIColor.red
        shoulder.firstMaterial?.diffuse.contents = UIColor.yellow
        boom.firstMaterial?.diffuse.contents = UIColor.blue
        ・・・

        root = baseNode
    }

 やろうと思えばBlender側で親子関係自体も構築可能と思われ。

 この点は次回試してみます。

 という具合に、Blenderの3D形状をSceneKitで使うのは結構簡単なんですが、1点だけ注意する点があるんすよ。

3D形状モデリングにBlenderを使う注意点

 それはBlenderの3D空間が上下の基準にz軸を使っている点。

 ここで3D形状を作成して、そのまま3D空間の上下の基準にy軸を使っているSceneKitで利用すると、形状が横になっちゃうんですな。

 

 

 これが全て仮想空間で完結してるなら、SceneKit側の仮想カメラ視線を変えるって方法を取れなくもないけど、SceneKitを使うARKitの仮想カメラは、現実のカメラの向きと連動してて、実空間の上下基準にもy軸を使ってるんで変更できないのよ。

 実際どうなるか興味がある人のために、単にファイルをプロジェクトに登録して利用した場合のサンプルを置いておきます。

 

サンプル:

 http://tetera.jp/xcc/book-sample/yumbo-flip.zip

 

 ご覧の有様だよ。

 

 そこらへんを吸収するために必要なのが、3D形状を構成する頂点群の、yとz座標の入れ替えなんですが、ここら辺を一気にSceneKitにお任せする方法としてSecneKit Catalogフォルダを作って、その中にCOLLADAファイルを置くという方法があるっす。

SecneKit Catalogフォルダ

 新規に作る場合はFile>New>File…メニューを選んで表示される画面からSecneKit Catalogを選ぶとプロジェクトに追加できます。

 

 

 

 これで指定した名前のSecneKit Catalogがナビゲーションエリアに追加されます。

 

 この、追加されたSecneKit Catalog項目を選ぶと右に表示される内容が、SecneKit Catalog内に置かれたファイルに対するSecneKitがおこなう調整内容っす。このうちのAlway uses the Y-up axisが、今回のy軸が上下軸に設定されてない問題の解決用。「いつでもy軸を3D空間の上下軸として使うよ〜」て感じかな?

 もっとも、Blenderで書き出したCOLLADAファイルは、ファイルをこのSecneKit Catalog内に入れさえすれば、Alway uses the Y-up axisがチェックになってなくても補正してくれます。多分COLLADAファイル内に記述されてる

 

  <up_axis>Z_UP</up_axis>

 

を解釈してるんだろうと思われ。

 おそらく、修正をかけたいという意思を示すために、SecneKit Catalog内に入れる必要があるんでしょう。

注意)COLLADAファイルはXMLで記述されるので、内容をテキストとして読むことも可能。ナビゲーションエリアのCOLLADAファイル項目を右クリックしコンテキストメニューからOpen As>Source Codeを選べば確認できる。元の表示に戻すには同じようにメニューを出させOpen As>SceneKit Documentを選べば良い。元の表示であるSceneKit Document表示については次回以降に説明。

 

 とにかく、あとは、このSecneKit Catalogフォルダにさっきのyumbo.deaファイルを置くだけ。ナビゲーションエリアのSecneKit Catalogの項目にドロップでもいいし、FinderでSecneKit Catalogのフォルダに直接ドロップでもいいです。

 

 FinderでSecneKit Catalogのフォルダに直接ドロップと書いたように、SecneKit Catalogはフォルダとしても存在します。

 ナビゲーションエリアでSecneKit Catalog項目を右クリックして出てくるメニューからShow in Finderを選べば、そのフォルダを表示してくれるんで、その中にyumbo.deaファイルをドロップしてもいい。

 

 

既存のSecneKit Catalogフォルダの登録

 軸変換からは話がそれるけど、Xcodeは.scnassets拡張子が付いた名前のフォルダならSecneKit Catalogのフォルダだと解釈するようで、File>New>File…メニューを使わなくても、.scnassets拡張子のフォルダをナビゲーションエリアにドロップすることでもSecneKit Catalogをプロジェクトに登録できるみたい。

 この時に重要なのは、フォルダのドロップ直後に出てくる設定画面でAdded folders:をCreate folder referencesにすること。

 

 

 こっちにしておくと、登録されたフォルダの内部は、そのまま丸ごとバンドルにコピーされることになるっす。

Create groupsとCreate folder referencesの違い

 もう1つの方のCreate groupsを選ぶと、ドロップしたフォルダ名を使ったグループ(Group)を作り、そのグループ中に、ドロップした時点でのフォルダ内のファイル群をそれぞれ登録することになる。以後、Finder上でフォルダ内を変更してもナビゲーションエリアに反映されることはない。

 

 

 これに対し、Create folder referencesだと、ドロップしたフォルダそのものがフォルダ参照として登録されることになり、以後、そのフォルダの内容物の変化がそのままナビゲーションエリアにも連動して反映されることになる。

 

 

 File>New>File…メニューでSecneKit Catalogを指定した場合も、このフォルダ参照の設定になってて、フォルダ自体を作るまでをやってくれる。

 

 話を軸変換に戻す。

 メニューだろうとドラッグ&ドロップだろうといいので、とにかく用意したSecneKit Catalogフォルダ内に、Blenderで作ったCOLLADAファイルを置きましょう。そうするとyとz座標の入れ替えを自動でやってくれます。

 気をつける点として、バンドルにはSecneKit Catalogフォルダごとコピーされるので、読み込み時のURLとしては、このフォルダ名(拡張子含む)をファイルの親フォルダとして指定すること。

 そのためにはurlメッセージのforResource:に渡す名前を、フォルダ名とファイル名を /(スラッシュ)で結んだものにすればいいです。

    let yumboURL = Bundle.main.url(
    	forResource: "SceneKit Asset Catalog.scnassets/yumbo", 
		withExtension: "dae")

 

注意)url(forResource:withExtension:subdirectory:)メッセージを使ってもいい。というか、こっちの方が正当な気もする。

 

 それ以外の作業は横向きになっちゃうよ版と同じです。

 Runするとこんな感じ。

 

 

 

サンプル:

 http://tetera.jp/xcc/book-sample/yumbo-blender.zip

 

 いや〜、一気に雰囲気上がりますな。
 こんな風に3D形状を自由に作れるなら、アイマスとか初音ミクとかも作れんじゃね?と思った人。

 大正解。

 次回からBlenderの操作も含め、そこらへんを調査していく。