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

  • 16
    いいね
  • 0
    コメント

やりたいこと:

  • 各種モデリングソフトからエクスポートされた3Dモデルを、SceneKit (on iOS)で使いたい
  • モデルに紐づくアニメーションとかボーン(Rigged Models)とかもそのまま使いたい

ちなみになぜUnityじゃないかというと、非ゲームな、基本的には2Dのアプリで、一部で「3D的表現」を行いたい、という要件からです。

扱えるフォーマット

結論からいうと、.dae(Digital Asset Exchange / COLLADA)の一択

こんな感じで使います。

let url = Bundle.main.url(forResource: "filename", withExtension: "dae")!
let sceneSource = SCNSceneSource(url: url, options: nil)!
guard let modelNode = sceneSource.entryWithIdentifier("modelId", withClass: SCNNode.self) else {
    fatalError()
}
scene.rootNode.addChildNode(modelNode)

if let animation = sceneSource.entryWithIdentifier("animationId", withClass
: CAAnimation.self) {
    modelNode.addAnimation(animation, forKey: "someKey")
}

他のフォーマットがNGだった理由は以下の通り。

  • .fbxは配布されているSDKを使うことでインポートできるが、頻繁にメンテされてるわけでもなさそうなので、そこに依存したくない
  • .objはSCNSceneSourceインポートできるが、そのファイル形式自体がアニメーションをサポートしていない
  • .3ds, .max はSceneKitにインポートできない(サードパーティ製ライブラリとかはあるかもしれない)

そしてこのCOLLADAの.daeファイルは大抵のモデリングツールでサポートされているらしい。

モデルの構造が複雑な場合

複雑というか、むしろ大抵のモデルは複数のオブジェクトやボーンから構成されている。

例えば、AppleのScene Kit Animationsという
フリーのこのモデルの.daeファイルをXcodeで見てみると、こんな感じでメッシュやらボーンやらでモデルが構築されています。

Screen Shot 2017-01-12 at 12.50.13.png

上でやったように、SCNSceneSourceでIDを指定してSCNNodeをつくってはaddChildする、という方法はこういうケースでは得策ではありません。

ではどうするかというと、非常にシンプルで、dae から SCNScene をつくるだけ。

let url = Bundle.main.url(forResource: filename, withExtension: "dae")!
let modelScene: SCNScene
do {
    modelScene = try SCNScene(url: url, options: nil)
} catch {
    fatalError()
}

このrootNodeを別のシーンにaddChildしようとしたら "removing the root node of a scene from its scene is not allowed" って怒られてしまったので、以下のように新しいノードに付け替えました。

let modelNode = SCNNode()
for childNode in modelScene.rootNode.childNodes {
    modelNode.addChildNode(childNode)
}

applemodel.gif

テクスチャが読み込めない?

Animated Army Pilotというモデルで同様の手法を試したところ、ジオメトリとアニメーションは再現されるものの、テクスチャが適用されませんでした。(※テクスチャはちゃんとプロジェクトに追加してありました)

実行時にこういうエラーが出ていました。

[SceneKit] Error: Failed loading : <C3DImage src:file:///Users/shuichi/Library/Developer/CoreSimulator/Devices/B2AF078C-BF73-429A-B9C0-182717D92859/data/Containers/Bundle/Application/55163269-393C-4F75-B729-5AF746CFDCA7/SceneKitModel.app/ [0.000000x0.000000]

ん?パスがちょっとおかしい。テクスチャが見つからないならこういうエラーが出るはず。

[SceneKit] Error: Failed loading : <C3DImage src:file:///Users/shuichi/Library/Developer/CoreSimulator/Devices/B2AF078C-BF73-429A-B9C0-182717D92859/data/Containers/Bundle/Application/55163269-393C-4F75-B729-5AF746CFDCA7/SceneKitModel.app/male_large_01.png

.daeの中身を見てみると(XMLファイルなので普通に見れる)、

  <library_images>
    <image id="head01-image" name="head01"><init_from>file://C:\Users\Nathan\Documents\New Project\Assets\Textures\soldier\head01.png</init_from></image>
    <image id="body01-image" name="body01"><init_from>file://C:\Users\Nathan\Documents\New Project\Assets\Textures\soldier\body01.png</init_from></image>
    <image id="m4tex-image" name="m4tex"><init_from>file://C:\Users\Nathan\Documents\New Project\Assets\Textures\soldier\m4tex.png</init_from></image>
  </library_images>

なんか制作環境の絶対パスが入っています。。

大丈夫なのか、と思いましたが、うまくいっているAppleの方のモデルの中身を見てみると、

  <library_images>
    <image id="male_large_01_png">
      <init_from>file:///Z:/art_depot/quest/characters/male_large_01/male_large_01.png</init_from>
    </image>
  </library_images>

こちらも絶対パスになっていました。

じゃあパスの指定方法が間違ってるのか、と思い、以下のように直してみました。

  <library_images>
    <image id="head01-image" name="head01"><init_from>file///C:/Users/Nathan/Documents/New Project/Assets/Textures/soldier/head01.png</init_from></image>
    <image id="body01-image" name="body01"><init_from>file///C:/Users/Nathan/Documents/New Project/Assets/Textures/soldier/body01.png</init_from></image>
    <image id="m4tex-image" name="m4tex"><init_from>file///C:/Users/Nathan/Documents/New Project/Assets/Textures/soldier/m4tex.png</init_from></image>
  </library_images>

:///// に、\/ に。

→ うまくいきました

army.gif

同じ仕様に則っているとはいえ、各社が別々にモデリングツールを開発していて、しかもメインとするプラットフォームが違ったりする(たとえば3DS MAXはWindows版しかない)中で、こういうエクスポートしたファイルフォーマットの微妙な違いによるトラブルというのは往々にしてあると思います。

上記のようなパスの違いが何によって生じたのか、よくあることなのかわかりませんが、ひとつの事例として。