タグに「SceneKit」を持つ
1〜10件目 / 18件
1 2 次へ

 MMDSceneKitをCocoaPodsに対応させた / magicien 

1ヶ月ぶりです。2月はにわかに忙しくなってアプリ開発が全然進まず。3月はペースを戻していきたい所存。
それはさておき、MMDSceneKitをCocoaPodsに対応させました

なぜCocoaPodsに対応させたかというと、今作っているiOSアプリにMMDSceneKitを組み込んだところ、ダイナミックライブラリを入れないで!とAppleから怒られたから。
それならソースからコンパイルすれば良いのだろう、ということでMMDSceneKitのプロジェクトを丸ごとワークスペースに取り込んでみたものの、細々とエラーが出て面倒くさい。 なぜだ!CocoaPodsで入れた他のライブラリは問題ないのに・・・と考えた結論は、MMDSceneKitだけCocoaPodsを使っていないから(思考停止)。

頑張れば手動でもうまく組み込めるんだろうけど、頑張らないと使えないフレームワークとはいったい・・・うごごご!
そんな感じです。そのうちCarthageにも対応するかもしれない。

2018/03/04(Sun) 21:21:13

 MMDSceneKitに物理演算を追加した / magicien 

image 寝癖すごいことになってますよ?

作ろうと思って放置していた物理演算を追加した。pmd、pmx単体で読み込む分には、剛体が正しく読み込まれるはず。モデルをcloneした時の処理が未修正のため、pmmは今のところ未対応。

問題は、SceneKitにbtGeneric6DofSpringConstraintに該当するSCNPhysicsBehaviorが存在しないこと。代わりにSCNPhysicsBallSocketJointを使ってみたけれど、回転角度に制限が入れられないので、髪がねじれ放題。そして、なぜか前髪もあらぬ方向へ。
SceneKitは明らかにBullet Physicsを使っているので、6DoFのAPIが裏に隠れているはず。ぜひ使わせて欲しいのだけれど、なぜか少しずつ小出しにされるので、まだ使えません!
場合によっては見た目が残念なことになるので、MikuMikuDanceQuickLookに物理演算を組み込むのは、まだやめておこう。

別途Constraintを追加してあげれば良いのかなぁ...そうするくらいなら、自分でBullet Physicsを組み込む方が早い気がする。

2017/12/18(Mon) 06:46:05

 SwiftのフレームワークをObjective-Cで使えるようにするには / magicien 

各クラスの前に @objcMembers を付けるだけ!
ただ、[MyClass?] みたいに、OptionalのArrayはObjective-Cに変換できないようだ。
ファイルサイズが大きくなってしまう点も気にかけておこう。


image ファイルのプレビュー機能である QuickLook のプラグインは、CかObjective-Cで書かなければならない。プラグインがカーネル側で動くことに関係していそうだが、理由はいまいちよくわからない。
MMDSceneKitはSwiftで書いているので、MMD用のQuickLookを作るには、フレームワークをObjective-Cで書き直さないといけない、と思い込んでいたけれど、@objcMembers を付けたら、Objective-Cからでも問題なく使えた。

モデルが赤紫色で表示されるのは、シェーダでエラーが発生したとき。別ファイルのテクスチャが読み込めていないのかもしれない。いずれにせよ、QuickLook用プラグインでSwiftのフレームワークが使えることに気づいたのは、自分にとって大きな収穫だった。

そういえば、SCNSkinnerのバグレポートはサポートから返信があった。次のバージョンでは直っているといいなぁ。

2017/11/18(Sat) 04:49:22

 GLTFSceneKit: SceneKit用glTFローダ / magicien 

image作った。ソースとサンプルアプリはGithubにあります。

未完成な部分もあるけど、基本的な機能は大体出来たかなぁというところ。
SceneKitデフォルトのライトだと、isDoubleSided が機能しない問題があって、どうしようかと考え中。
現状 macOS版だけなので、iOS等にも対応するのと、technique拡張に対応するのが次の課題。
JSceneKitに移植するのがその次の課題。

実際にglTFのパーサを書いてみて感じた利点・欠点はこんな感じ。

利点

  • ロイヤリティフリー
  • JSON形式なので構文解析は自前で作らなくて済む
  • パラメータがOpenGL/WebGLと同じ定数を使っているので、値の変換をせずにそのまま使える(WebGLで使うなら)
  • 上手くやればバイナリデータをそのままGPUに転送できる(WebGLで使うなら)
  • 物理ベースレンダリングに対応している
  • 拡張性が考慮されている

欠点

  • まだ仕様が定まってなさそうな箇所が多い(特に拡張仕様)
  • データ間の関係性が曖昧(一つのデータを違う用途で使い回せたりしてパースし辛いとか、weightsとprimitives/targetsの対応付けとか)
  • WebGL以外で対応しようとするとちょっと大変(特にユーザ定義のシェーダ対応)
  • モーフィングのアニメーションデータが膨大になる(キーフレーム毎に全モーフィングのウェイトを記録する仕様なので、MMDみたいに表情がたくさんあるモデルだと、データ量が増える)
  • MMDのようにモデルとモーションの組み合わせを変えるのが困難(MMDが特殊なのかも)
  • IK・物理演算には対応していない
あとこれ → "Positive rotation is counterclockwise."
OpenGLは回転方向右ねじだったのに逆になったのかな?と思って逆回転で実装したけど、やっぱり右ねじじゃないか!
どっちの方向から見た時に反時計回りなのか書いておくれよ!案の定サンプルモデルのGIF画像が仕様と逆回転しちゃってるでしょうが!

仕様は未完成っぽいけど、今後使いやすくなっていきそう。SceneKitはそのうちglTFを正式サポートするかも?

2017/08/27(Sun) 04:59:04

 SceneKit でテクスチャが赤くなる問題 / magicien 

imageSceneKitでモデルに適用したテクスチャが赤くなる問題があった。

問題が発生するのは、indexed-colorのPNGファイルの一部。原因はよく分からない。
青と緑の成分が無視されている上に、本来sRGBなのにリニアRGBとして処理されているようだ。
モデル自体を変更できるなら、画像ファイルを別のフォーマットに変換すれば良いけれど、できることならプログラム側で対処したい。

で、色々試した結果、NSImageで読み込むと赤くなるが、CGImageで読み込むと大丈夫だということに気づいた。何が何やら...

追記:iOSで試したらUIImageでもCGImageでも色が赤くなってしまった...BugReport送ってみたけど対応してもらえるかなぁ。

2017/08/23(Wed) 07:56:58

 SceneKitの物理ベースレンダリング(PBR) / magicien 

SceneKitで物理ベースレンダリングを使うには、SCNMaterial.lightingModel = .physicallyBased と設定すれば良い。
つまり、材質によって物理ベースにしたりしなかったりできる。ややこしいね!

physicallyBased の説明によると、
  • 描画に使う SCNMaterial の主なパラメータは、diffuse、roughness、metalness の3つ。
  • 細かい表現には、normal、ambientOcclusion、selfIllumination を使う。
  • ambient、specular、reflective、shininess、fresnelExponent、locksAmbientWithDiffuse は無視される。
  • 他に SCNScene.lightingEnvironment、SCNCamera.wantsHDR を設定すると良い感じになる。
ということらしい。言及すらされない emission さんかわいそう。
ただし、renderingAPI が Metal でないとPBRが有効にならない。GLSLでも実装上問題無いと思うんだけど、シェーダを二種類書くのが面倒だったのかな。

物理ベースでない場合、水面に映り込む背景等は、reflectiveに設定していたけれど、物理ベースの場合は、SCNScene.lightingEnvironment を使うようだ。材質毎に背景を変える理由もないし、こっちの方が良い気がする。
個別に設定したい場合は、selfIllumination を使うと、diffuseの計算に使うテクスチャを変えることができるらしい。
PBRでemissionを使いたい場合、emissionを設定した上で、selfIllumination.content を nil に設定すれば良いようだ。

JSceneKitでシェーダを書くと、上記情報だけでは、フレネル項のパラメータが一つ足りないことに気づく。物理ベースじゃなくてもフレネル反射はしていたので、これまで適当に固定値を割り当てて計算していたけれど、SceneKitではどう計算しているのだろう。
試しにFox2をPBRで描画してみたら、地面が暗すぎてよく見えなくなった。というわけでHDR(トーンマッピング)にも対応せねばなるまい。

2017/08/16(Wed) 04:10:41

 SCNShadable.shaderModifiers の色に要注意 / magicien 

SCNShadable.shaderModifiers でシェーダを書くと、なぜか想定より色が暗くなってしまう。丸一日悩んだ結果、内部で sRGB→RGB の変換がされていることがわかった。
(もしかしたら SceneKitのバージョンによって挙動が違うかもしれないし、macOS/iOS/tvOS/watchOS でそれぞれ挙動が違うことが多々あるので要注意。)

例えば、SCNMaterial の ambient.contents に NSColor(0.5, 0.5, 0.5, 1.0) を設定すると、shaderModifiers の _surface.ambient は float4(0.214, 0.214, 0.214, 1.0) ぐらいの値になる。
逆に、_output.color を float4(0.214, 0.214, 0.214, 1.0) にすると、画面出力は (0.5, 0.5, 0.5, 1.0) になる。
ので、あまり気にしなくても良いかもしれないが、シェーダ内でライトの色と物体の色を掛けたり足したりすると、どんどん値が想定とずれていってしまう…
真面目にやるなら、RGBからsRGBに変換して色を計算、最後にsRGBからRGBに戻すことになる。二度手間だなぁ。
計算式は、Cygames Engineers' Blogを参考に近似式を使ってこんな感じにした。

inline float3 linearToSrgb(float3 c) {
    return pow(c, float3(1.0/2.2));
}

inline float4 linearToSrgb(float4 c) {
    return pow(c, float4(1.0/2.2));
}

inline float3 srgbToLinear(float3 c) {
    return pow(c, float3(2.2));
}

inline float4 srgbToLinear(float4 c) {
    return pow(c, float4(2.2));
}

#pragma arguments

...

#pragma transparent
#pragma body

float4 materialDiffuse = linearToSrgb(_surface.diffuse);
…
float3 lightAmbient = linearToSrgb(_lightingContribution.ambient);
…

_output.color = srgbToLinear(_output.color);
Metalはinline命令使えないと思ったけど使えた。ちゃんとインライン展開されているのかな?
float4の場合はアルファ値を変換しないのが正しい気がする。後でSceneKitの挙動を確認しよう。
SceneKit側は厳密な変換をしているようなので、上の方法だとわずかにずれるけど実用上問題ないと思う。
SCNShadable のページで、自作関数を #pragma arguments の下に書いてある例があるけど、関数は #pragma arguments の前に書かないと、うまく動作しなかった。

2017/08/03(Thu) 00:22:54

 scnファイルのデータ構造 / magicien 

JSceneKitを実装する上で難しかった点の一つは、scnファイルの解析である。とはいえ、pmmファイルの解析で鍛えられたバイナリ読解力を以ってすれば造作もない。

試しに ship.scn をバイナリエディタで開いてみると、"bplist00" という文字列が最初に入っていることがわかる。
これは、binary property list形式ということなので、plutilコマンドで中身を見ることができる。
$ plutil -p ship.scn

実行結果はこんな感じ。
{
  "$version" => 100000
  "$objects" => [
    0 => "$null"
(中略)
    81 => {
      "$classname" => "SCNScene"
      "$classes" => [
        0 => "SCNScene"
        1 => "NSObject"
      ]
    }
  ]
  "$archiver" => "NSKeyedArchiver"
  "$top" => {
    "root" => <CFKeyedArchiverUID 0x7f90efd00480 [0x7fffbe406da0]>{value = 1}
  }

JSONのような形式で内容が表示されるので、これを元にどこにどのデータが入っているかを解析する。
すると、ファイルの末尾26バイトにパースに必要なデータが入っていることがわかる。
  • オフセットデータのサイズ:unsigned byte
  • オブジェクトのindexのサイズ:unsigned byte
  • オブジェクト数:unsigned long long
  • 最初のオブジェクトのindex:unsigned long long
  • オブジェクトデータの場所:unsigned long long
次に、オブジェクトデータの場所(ファイル先頭からのバイト数)からオブジェクト数の数だけ、整数(バイト数は、オフセットデータのサイズ)を読み込む。これは、各オブジェクトのデータの場所を表している。

最初に読み込むべきオブジェクトは、「最初のオブジェクトのindex」で与えられている。
各オブジェクトデータの先頭1バイトに、データの種類が格納されている。
0x00 なら null、0x08 なら false、 0x09 なら true、0x10〜0x1F なら整数、等。

最初のオブジェクトは、$version、$objects、$archiver、$top というデータの入ったDictionary型になっている。
次に読み込むべきオブジェクトは、$objects[$top.root] のオブジェクト。このオブジェクトが本当に必要なデータということになる。
オブジェクトが何らかのインスタンスである場合は、Dictionary型になっていて、"$class" にどのクラスのデータかが記録されている。
scnファイルの場合は、$class.$classname が SCNScene となっているはず。
Dictionary内の残りのデータはクラスによって異なるので、どれをどう読み込むかはクラス毎に実装が必要。
インスタンスのデータ全てが出力されているかといえばそういうわけではなく、デフォルト値と同じ場合は出力されていないこともある。

文字で書くと意味不明ですね。OmniGraffleの試用期間が終わったので製品版買おうかと思ったけど、Standardでも思ったより値が張るなぁ。
2017/06/28(Wed) 04:03:27

 SceneKit:主な構成要素、その関連性 / magicien 

を読み進めていたあなたは、SceneKitについて書かれたページを見つけました。

image そのページには、挿絵とともに、次のような文言が記載されていました。
・全体の見た目を変えるなら、SCNView.technique を設定すること。
・ノード毎の見た目を変えるなら、SCNGeometry.shaderModifiers または SCNGeometry.program を設定すること。
・材質毎の見た目を変えるなら、SCNMaterial.program を設定すること。
空間制御の術式に僅かながら心得のあったあなたは、ふとある噂を思い出します。

SceneKitが生み出された当時、UnityやUnreal Engineといった別の術式が隆盛を極めており、SceneKitのような特殊な環境でのみ発動できる術式を好き好んで使う者は少なかったという。その特殊性から、SceneKitに関する資料も少なく、それ故に術者も減少の一途を辿り、現代では術者も資料も失われてしまったとされている。しかし、SceneKitに関する本を書庫に保管している図書館が世界にいくつかあるという噂がある。理由は分からないが、その本は禁書とされ、貸し出しはされていないという。


2017/06/25(Sun) 02:40:34

 SceneKitの系 / magicien 

人間てぇものは、物事を二つに分類したがる嫌いがあるようでして、「私の彼氏が理系で」「俺の彼女が肉食系で」なんて言ったりするわけでございます。そうやって分類することで、物事をちょっぴり分かった気がするんでしょうな。3Dグラフィックスライブラリなんてわけのわからないものに出くわしたときは、「おや、これは何系かな?」と言っておけば、「おっ、お客さん通だねぇ」なんてことになるわけでございます。


左手系・右手系

3Dのライブラリを分類するとすれば、まずは「左手系」「右手系」だろう。
X軸を右、Y軸を上とした時に、Z軸が手前に来るのが右手系、Z軸が奥に行くのが左手系ということらしい。電磁気学とかだとここら辺が重要かもしれないけれど、3Dだと正直どうでも良い。
OpenGLは右手系、DirectX、MMD、Unityは左手系。SceneKitは内部でOpenGLを使うことがあるので右手系になっている。

行列

SceneKitでは、SCNMatrix4という4x4行列用の構造体がある。
各成分は、m11、m12、...、m44 という名前になっている。配列ではないので、ループで一気に計算、というわけにはいかない。配列にしたらしたでやれ列優先だ、行優先だという話になるので、これはこれで良いと思う。
新APIだと、simd系と相互変換できるようになったので、計算が必要な場合は、一度simdに変換すると良いだろう。
let m1: SCNMatrix4 = ...
let m2: SCNMatrix4 = ...
let m3: SCNMatrix4 = ...
let answer = SCNMatrix4Mult( SCNMatrix4Mult( m1, m2 ), m3 )

let f1 = float4x4(m1).transpose   // float4x4(m1) とすると、行と列が逆になるので転置する
let f2 = float4x4(m2).transpose
let f3 = float4x4(m3).transpose
let f = f1 * f2 * f3

let answer2 = SCNMatrix4( f.transpose )   // answerとanswer2の結果はほぼ一致する

if( Float( m1.m23 ) == f1.cmatrix.columns.2.y ){
    // 上記 if文は概ねtrueになる。が、計算精度の問題で一致しないこともあるだろう。
    // float4x4の場合、列は0123、行はxyzwでアクセスする。列・行の順でわかりづらい。
    // SCNMatrix4.m23 は CGFloat 型なので Float 型にキャストしてから比較している。型の違いは「色」の項目で。
}

回転

3Dで一番厄介なのが、この回転である。どの軸をどの順番でどの方向に回すかで挙動が変わる。各ライブラリがその個性を遺憾無く発揮する場であり、3Dデータ・APIの相互変換を難しくする主因となっている。

SceneKitの場合、SCNNodeの回転を設定するには、変換行列である transformを直接設定する方法以外に、rotation、orientation、eulerAngles の3つの変数を設定する方法がある。どれか一つを変更すれば、他の変数も全て変更される。

rotation: SCNVector4 回転軸(xyz)と回転量(w、ラジアン)を指定
orientation: SCNVector4 いわゆるクォータニオン
eulerAngles: SCNVector3 xyz各軸の回転量をそれぞれラジアンで指定
回転方向は、回転軸の+方向に対して右回り。

回転行列はこんな感じ。

※rotationのxyz軸は大きさ1に正規化される。 \( rotation = (x, y, z, w) \Rightarrow \left( \begin{array}{cccc} x^2 (1-\cos w) + \cos w & xy(1-\cos w) + z\sin w & xz(1-\cos w) - y\sin w & 0 \\ yx(1-\cos w) - z\sin w & y^2(1-\cos w) + \cos w & yz(1-\cos w) + x\sin w & 0 \\ zx(1-\cos w) + y\sin w & zy(1-\cos w) - x\sin w & z^2 (1-\cos w) + \cos w & 0\\ 0 & 0 & 0 & 1 \end{array} \right) \)

\( eulerAngles = (x, 0, 0) \Rightarrow \left( \begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & \cos x & \sin x & 0 \\ 0 & -\sin x & \cos x & 0 \\ 0 & 0 & 0 & 1 \end{array} \right) \)

\( eulerAngles = (0, y, 0) \Rightarrow \left( \begin{array}{cccc} \cos y & 0 & -\sin y & 0 \\ 0 & 1 & 0 & 0 \\ \sin y & 0 & \cos y & 0 \\ 0 & 0 & 0 & 1 \end{array} \right) \)

\( eulerAngles = (0, 0, z) \Rightarrow \left( \begin{array}{cccc} \cos z & \sin z & 0 & 0 \\ -\sin z & \cos z & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{array} \right) \)

eulerAnglesのxyz回転を全て指定した場合は、z軸・y軸・x軸の順番で回転する。
\( eulerAngles = (x, y, z) \Rightarrow \left( \begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & \cos x & \sin x & 0 \\ 0 & -\sin x & \cos x & 0 \\ 0 & 0 & 0 & 1 \end{array} \right) \left( \begin{array}{cccc} \cos y & 0 & -\sin y & 0 \\ 0 & 1 & 0 & 0 \\ \sin y & 0 & \cos y & 0 \\ 0 & 0 & 0 & 1 \end{array} \right) \left( \begin{array}{cccc} \cos z & \sin z & 0 & 0 \\ -\sin z & \cos z & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{array} \right) \)

orientationはrotationとほぼ同じ。関係は次の通り。

\( rotation.xyz = normalize( orientation.xyz ) \)
\( rotation.w = 2 \arccos (orientation.w) \)
※\( \arccos (orientation.w) \) が NaN の場合は、\( rotation.w = 0 \)


ちなみに、MMDのモーションデータの場合、モデルモーションはクォータニオン、カメラモーションはオイラー角で保存されている。MMDのクォータニオンからSceneKitのクォータニオンへの変換は、xとyの符号を反転すれば良い。MMDのオイラー角をSceneKitのオイラー角で表すとすれば、Z軸・-X軸・-Y軸の順で回転するのと同等。

macOS版とiOS版の両方を作る場合は、色にも気をつけないといけない。

SCNMaterialで色を設定する場合、macOSでは、NSColor、iOS/tvOS/watchOSではUIColorを使う。インタフェースはほぼ同じなので、
#if os(macOS)
    typealias Color = NSColor
#else
    typealias Color = UIColor
#endif
という感じで共通化して使ってもいいかもしれない。このとき注意が必要なのは、NSColor・UIColor共通でRGBA成分を表すのに使われているCGFloatという型である。CGFloatは、macOSだと64bit(Double)、iOSだと32bit(Float)になっている。なので、値を代入する時に型変換が必要になったりならなかったりして煩わしい。
実は自前でtypealiasを作らなくても、CGColorやSKColorといった共通クラスが用意されているけれど、CGFloatの差異は吸収されないので煩わしい。
元の型がFloatでもDoubleでもCGFloatにキャストするよう気を付ければ、コンパイラから文句を言われずに済むが、代入のたびにCGFloatとか書かないといけないのがやっぱり煩わしい。

shaderModifiersでシェーダを書く場合、materialに設定した色とシェーダで扱う色で数値が異なるので要注意。シェーダに渡される値は、materialに設定した色を sRGB→RGB 変換したものになるので、数値が小さく(暗く)なる。

2017/06/19(Mon) 02:15:57