iOS で SceneKit を試す(Swift 3) その60 - SCNMorpher でモーフィング
SceneKit では SCNNode に SCNMorpher が紐づけられており、1つまたは複数のジオメトリを配列として渡すと、各ジオメトリへ変形させることができるモーフィングというものが使える。
(3DCG のオーサリングツールでは Blend Shape と呼ばれているものある)
A のジオメトリから B のジオメトリへポリゴンの頂点が変形させられるため、ジオメトリの頂点の数が一致していないとアニメーションできない。
また、頂点に位置がおかしいとジオメトリの面がおかしくなるので注意。
SCNMorpher の振る舞い
A のジオメトリから B のジオメトリ変形させる際、
ジオメトリの配列の要素を指定する。
そして、weight という変化の比率の値を変更するとが変形する。
現状 SCNMorpher は以下の4つしかない。
- targets
- weight
- setWeght
- calculationMode
targets に変化させる SCNGeometory が配列になっているものを入れて、 setWeght で配列の番号と、変化させる度合いを数値で入れる。
weight は配列の番号で値の状態を調る。
calculationMode は Normalized と Additive があり、 Normalized は変化量を 0 〜 1 に正規化され、Additive 元の頂点の位置から加えて頂点が変形される。 デフォルト値は Normalized。
コードで試す
いつものように、 Xcede で SceneKit の Game テンプレートのプロジェクトを作成。
必要のないものを消す
今回は ship.scn を使用しないので SCNScene の記述を変更する。
let scene = SCNScene(named: "art.scnassets/ship.scn")!
let scene = SCNScene()
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)))
SCNBox をモーフィングしてみる
以下のコードをどこかに書く。
SCNBox のジオメトリを2つ作成して、box1 のノードにモーフィングさせるターゲットとして box2 を設定。
Core Animation で Key Path からモーフィングのアニメーションをしている。
let box1 = SCNBox(width: 1, height: 5, length: 1, chamferRadius: 0.01) let box1Node = SCNNode(geometry: box1) scene.rootNode.addChildNode(box1Node) let box2 = SCNBox(width: 5, height: 1, length: 1, chamferRadius: 0.5) let morpher = SCNMorpher() morpher.targets = [box2] box1Node.morpher = morpher let a2 = CABasicAnimation.init(keyPath: "morpher.weights[0]") a2.fromValue = 0.0 a2.toValue = 1.0 a2.duration = 0.5 a2.beginTime = 1.0 a2.autoreverses = true a2.repeatCount = .infinity box1Node.addAnimation(a2, forKey: nil)
SCNBox で chamferRadius を設定を行うと、ジオメトリの頂点の構造が変化するため 0 にすることができないので注意点。
オーサリングツールからを行う
Project Navigator (Command + 0) に オブジェクトファイルをドラッグ&ドロップし、ファイルを選択し Scene Editor で表示。
ジオメトリを選択し、Attribute Inspector (Command + Option + 4) の Geometory Morphers にターゲットが列挙されるので数値を変更する。
ちなみに、オーサリングツールから吐き出される dae は、
SCNMorpher 変換時におかしくなるので、
以下の処理をする。
Blender
普通に Shape Key を作成して dae に書き出し。 そのままでは、SCNMorpher に変換されないので
library_visual_scenes の instance_geometry を instance_controller に変更。
その上にある controller の名称を instance_controller に設定する。
例
<controller id=“o-morph” name=“o-morph”> …
<instance_controller name=“o-morph” url=“#o-morph”> …
ツールを公開されている方がいるようなのでこちらで変換するのも良いかと。
Maya
Blend Shape Deformer のものや、
ベースオブジェクトだけでやっているものでも可能。
Maya から dae に書き出して、Geometory Morphers から数値を変えると、 数値を変更した分だけジオメトリが大きくなってしまう。
morph の method が RELATIVE になっているので
NORMALIZED に変更する。
Maya LT > FBX Converter
変換不可。
Blend Shape があると dae の書き出しエラーが起こり 0KB のファイルができる。
正直、Maya LT はゲーム用として謳っているのに、
直接 dae に変換できない理由が不明。
このへんで心折れたので他のツールは試していない。
コードから操作する
書き方は SCNBox とほぼ同じ。
scn から o というジオメトリを調べて操作している。
let scene = SCNScene(named: "art.scnassets/monkey.scn")! let o1 = scene.rootNode.childNode(withName: "o", recursively: true)! let a2 = CABasicAnimation.init(keyPath: "morpher.weights[0]") a2.fromValue = 0.0 a2.toValue = 1.0 a2.duration = 0.5 a2.beginTime = 1.0 a2.autoreverses = true a2.repeatCount = .infinity o1.addAnimation(a2, forKey: nil)
ちなみにアニメーションの Key Path ではなく、設定したい場合はこちら。
let scene = SCNScene(named: "art.scnassets/monkey.scn")! let o1 = scene.rootNode.childNode(withName: "o", recursively: false)! o1.morpher?.setWeight(1.0, forTargetAt: 0)
target の最初のジオメトリである 0 番目に、1.0 の変化量の値を入れている。
iOS 11 での変更
モーフターゲットの名前から選択したり、現状できない morpher.weights[] の配列での呼び出し可能な模様。
unifiesNormals のパラメーターは何かわからず。
今回はここまで