[iOS] SceneKit + MakeHuman + Blender で3Dの人体をグリグリ回してみた


とある案件のiOSアプリのデザイン案として、ゲームのキャラ詳細画面のような感じで人体をグリグリ3D的に指で回したいという要望があった。

ググったらSceneKitフレームワークが出て来た。
しかし、難しいのは3Dデータのモデリングだろうと思いつつ、同僚に相談してみたところ様々なフリーな3Dモデリングソフトがあることを教えて頂いた。

Google のSkecthUp は簡単にゼロからモデリングできて便利そうだけど、人体という有機的な形状には向かない。Blender は定番&無料で高機能。有機的な形も作れるといえば作れるけど、人体という神秘をゼロから作り上げなければならない。3Dモデラーとしてど素人の自分には正直厳しい。
Poser という手もあるけど、有料($129.99)。

さらに調べたところこのサイトでMakeHuman というオープンソースの人体モデリングソフトを発見。無料。

MakeHumanで人体を作ってSceneKitでグリグリ回すことにしてみた。


  • Xcode 8.2.1
  • MakeHuman v1.1.0
  • Blender 2.78

作業工程

  1. MakeHumanで人体を作る(dae形式の3Dデータを作る)
  2. Blenderでdaeデータをちょっと修正する
  3. Xcodeでscnファイルにdaeを突っ込む
  4. ちょっとアニメーションの挙動を変える(蛇足)

*Xcode/MakeHuman/Blenderアプリの基本操作方法は割愛します


1. MakeHuman で人体を作る(dae形式の3Dデータを作る)

まずは基本となる人体の3Dデータを作る。
MakeHumanでは、肌の色から目の色、身長や体重、体型、年齢、性別、輪郭、髪型から服装まで設定可能。体型の設定は肉付きや肩幅、指や腕の長さなどなど非常に細かく設定できる。UI的な癖はあるもののモデリング自体の機能は非常に良くできている。

出来上がったデータをdae形式でエクスポートする。

2. Blenderでdaeデータをちょっと修正する

このままのdaeファイルだと何故かテクスチャがうまく適用されていないため、Blenderを使ってdaeデータを修正。(今回ハマった部分)

まずBlenderを起動して新規ファイルを作り、最初のテンプレートからある立方体を削除し、
1で作ったdaeファイルをインポートする。

最初のテンプレートからあるCameraやLampも削除する。
(カメラや環境光はあとでXcodeでそれぞれSCNNodeオブジェクトとして生成するため)

本体、眉毛、まつげ、眼球、衣服それぞれのパーツにテクスチャを適用していく

Sceneパネルから各パーツの左の+を押してテクスチャを選択し、
中段にあるNewを押して新しくテクスチャを作る

TypeをImage or Movieにする。
Openファイルから対象となるpngファイルを選択。
*対象となるpngは1でエクスポートしたディレクトリのtexturesに入っています。

File > Export > Collada (Default)(dae)を選択してエクスポートする。

Blenderでの工程はおしまい。
よくよく調べるとMakeHumanは元々Blender用のスクリプトとして開発されていたらしい。どおりで相性がいいはず。

3. Xcodeでscnassetsにdaeを突っ込む

3.1. 新規プロジェクトの作成

  • Xcode > File > New > Project... > Game を選択しプロジェクトを作成。
  • 最初から含まれているart.scnassetsを削除。
  • Xcode > File > New > File... > Asset Catalogを選択し、適当にscnassets拡張子として保存する。

  • Xcode > File > New > File... > SceneKit Scene Fileを選択して scnassets にscnファイルを追加する。

Xcode左ペインのProject Navigator でscnassetsを選択したままFile > New > File...を選択するとなぜかファイル形式が選択できず空のファイルが自動的に追加されてしまうため注意!

3.2. scnファイルにdaeデータを追加

  • Finderでscnassets以下にdaeファイルとテクスチャイメージファイル群をコピー

  • scnファイルにdaeデータを入れる(ドロップします)

何故かX方向に90°回転しているのですが原因がいまいちわかっていません。Blender上やビルドしたアプリでは問題ないのですが...。なかなかシュールな画ですね。

3.3. コードを変更する

art.scnassetsを削除してWoman.scnassetsを追加したのでSCNSceneオブジェクトの初期化部分を変更。
Gameテンプレートで既に初期化コードが書かれているのでそのまま流用してnamedのみ変更する。

        let scene = SCNScene(named: "Woman.scnassets/Woman.scn")!

回転アニメーションもそのまま流用。

        // retrieve the ship node
        let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!

        // animate the 3d object
        ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))

が、identityが異なるのでscnファイルで定義を見てみる。

"Woman reference" に変更して変数名もwomanに変更する。

        // retrieve the ship node
        let woman = scene.rootNode.childNode(withName: "Woman reference", recursively: true)!

        // animate the 3d object
        woman.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))

scnファイルに含まれているカメラを削除してソースコードで生成しているカメラを使用するようにする。
cameraを選択してdeleteキーで消えます。

ここまでで一度ビルドして見てみる。
(回転しています)

距離感を感じます...。
もう少しお近づきになりたいのでカメラ位置を調整する。

        // create and add a camera to the scene
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)

        // place the camera
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)

色々調整した結果、これくらい。

        cameraNode.position = SCNVector3(x: -0.1, y: 1.2, z: 2.0)

4. ちょっとアニメーションの挙動を変える(蛇足)

このままだとグリグリ動かせるのですが、回転のアニメーションが走りっぱなしになっているので使いにくい。
アニメーション(SCNAction)をキーで取得できるように変更。

        // animate the 3d object
        woman.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))

        // animate the 3d object
        let turnAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1))
        woman.runAction(turnAction, forKey: "turnAction")

タップイベントのハンドラーにアニメーションのオンオフ切り替えを実装

    func handleTap(_ gestureRecognize: UIGestureRecognizer) {
        // retrieve the SCNView
        let scnView = self.view as! SCNView
        let scene = scnView.scene
        let woman = scene?.rootNode.childNode(withName: "Woman reference", recursively: true)!
        var turnAction = woman?.action(forKey: "turnAction")

        if turnAction != nil {
            woman?.removeAction(forKey: "turnAction")
        }
        else {
            turnAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1))
            woman?.runAction(turnAction!, forKey: "turnAction")
        }

これでタップするとアニメーションのオンオフが切り替わります。

360°どこからでも見ることができます。
ピンチして拡大縮小もできます。

おしまい。


フリーで使える3DCAD、3DCGモデリングソフトまとめ
http://mikaninc.com/free-cad-3d/
how to apply textures in blender 2.7x (beginners)
https://www.youtube.com/watch?v=6gRUUeFteQg
MakeHuman
http://www.makehuman.org
Blender
https://www.blender.org