AGENDA
|
説明に使用した PowerPoint はこちらからどおぞ。
|
デモパッケージ
|
デモに使用したパッケージはこちらからどおぞ。
巨大(78MB)なので、ご注意ください。
また、グラフィックスアクセラレータがないと、厳しいです。
使用したなソフトウェア
注意
このデモで使用している 3D データは下記のものを使用しています。使用条件は下記 URL より確認してください。
|
C#, PowerShell プログラミングの覚書、雑記など
AGENDA
|
説明に使用した PowerPoint はこちらからどおぞ。
|
デモパッケージ
|
デモに使用したパッケージはこちらからどおぞ。
巨大(78MB)なので、ご注意ください。
また、グラフィックスアクセラレータがないと、厳しいです。
使用したなソフトウェア
注意
このデモで使用している 3D データは下記のものを使用しています。使用条件は下記 URL より確認してください。
|
場所:渋谷駅東口からすぐの貸し会議室 (ご登録後お知らせいたします)
募集締め切り:2008年4月27(日)23時59分59秒
24名まで
無料
宇宙仮面のC#プログラミング(http://uchukamen.com/)を主催されている
宇宙仮面様にご登壇いただき、XAMLでの3Dプログラミングについて解説して
いただけることになりました。
この貴重な機会に、ぜひご参加ください。
ZAM3D で簡単3D XAML プログラミング
– ZAM3D の機能と使い方を紹介 20分
– ZAM3D, Expression Blend, Visual Studio の連携 20分
– 3Dデータのインポート 20分
– 3Dの回転について20分
– ZAM3D の問題点、その他注意点など 20分
– Q&Aなど 20分
口を動かすには、モーフィングをプログラムでやればできないことはないと思うけど、そこまでやる元気はない。とりあえず六角のデータで複数の顔の表情をインポートして、そのWPFの切り替える方法でどこまでいくか試してみた。
テスト実行用クリックワンス
http://uchukamen.com/WPF/MikuClickOnce/publish.htm
操作方法
実装的には、こんな感じで、いくつかの表情を遠くの空間に置いておく。表情のTransform を切り替えてあげるだけで、一瞬で顔を切り替えてしまう方法。ここで、 off は、遠くの空間、trans は顔の場所の Transform。
private void Grid_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.A)
{
faceNow.Transform = off;
faceNow = faceA;
faceA.Transform = trans;
}
・・・以下、同様
}
やっていることは単純だけれど、とりあえず何らかの事象を受けて、表情などを変えることができる。モーフィングに比べればかなり硬いけれど、使えないことはない。
ということで、WPF をつかった新時代にふさわしいインターフェースの名前は・・・
そうそう、これをつくりながら、Nexus 6 を作りたかったんだよな…と・・・
もともとの V6 の XAML データだけでも、 8MB もあるので、Visual Studio 2008 が
と 悲鳴を上げている。
やっぱりここまでくると、しゃべらせないとね・・・ということで、顔をモーフィングしたデータをいくつかあわせてエクスポートしてみた。XAML データはさらに重くなって、19MB。Visual Studio 2008 が開くには開くが、Grid を追加したら、Visual Studio 2008 がハンドルされていない例外を発生しましたということで、種類 ‘System.OutOfMemoryException’ の例外が発生して、アウト!
少しミクをダイエットさせなければ・・・・
呼び出しのターゲットが例外をスローしました。
場所 System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
場所 System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
場所 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
場所 System.Delegate.DynamicInvokeImpl(Object[] args)
場所 System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Boolean isSingleParameter)
場所 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)
種類 ‘System.OutOfMemoryException’ の例外がスローされました。
場所 MS.Internal.Host.Isolation.Protocols.ITextReadCursor.Read(Int32 length)
場所 MS.Internal.Host.Isolation.Adapters.ToTextReadCursor.Read(Int32 length)
場所 MS.Internal.Host.MarkupSourceProvider.SourceReader.Read(Int64 location, Int32 length)
場所 Microsoft.Windows.Design.SourceUpdate.TextRangeManager.TextRangeSourceReader.Read(Int64 location, Int32 length)
場所 MS.Internal.Xaml.Parser.ScannerProviderAdapter.Read(Int32 location, Int32 length)
場所 MS.Internal.Xaml.Scanner.ReadSource(Int32 current)
場所 MS.Internal.Xaml.Scanner.GetNextToken(Boolean stopAtEndOfLine)
場所 MS.Internal.Xaml.Parser.ParseXmlDocument(Int32 elements)
場所 MS.Internal.DocumentTrees.Markup.XamlSourceDocument.ParseElementFromSkeleton(XamlParseContext context, SkeletonNode node, XamlElement parent, Boolean fullElement)
場所 MS.Internal.DocumentTrees.Markup.XamlSourceDocument.UpdateSkeleton(IDamageListener listener, Boolean force)
場所 Microsoft.Windows.Design.Documents.Trees.MarkupDocumentTreeManager.Update()
場所 Microsoft.Windows.Design.Documents.MarkupDocumentManager.Update()
場所 Microsoft.Windows.Design.Documents.MarkupDocumentManager.AutoUpdater.DoUpdate()
さて、クオータニオンの原理がわかったところで、Trackball.cs を組み込みます。MSDN の Cube Animation Demo の trackball.cs は、http://msdn2.microsoft.com/en-us/library/ms771572.aspxにありますので、ダウンロードしておきます。必要なファイルは、trackball.cs です。このファイルは、これまで書いたように、回転に関して示唆に富むソースコードです。3D が初めての人は、ぜひ一読して、理解することをお勧めします。わずか200行程度なのですが、ベクトルの外積、クオータニオンを凝縮したコードです。また、簡単に組み込むことも意識して作られており、参考になります。
private void UpdateSlaves(Quaternion q, double s, Vector3D t)
{
////// カメラバージョン
if (_slaves != null)
{
foreach (Viewport3D i in _slaves)
{
Transform3D t3dg = i.Camera.Transform; ScaleTransform3D scaleTransform = new ScaleTransform3D();
scaleTransform.ScaleX = s;
scaleTransform.ScaleY = s;
scaleTransform.ScaleZ = s; Rotation3D rotation = new AxisAngleRotation3D(q.Axis, q.Angle);
TranslateTransform3D trtf = new TranslateTransform3D(-t.X * 10, -t.Y * 10, -t.Z * 10);
Transform3DGroup tg = new Transform3DGroup();
tg.Children.Add(scaleTransform);
tg.Children.Add(new RotateTransform3D(rotation));
tg.Children.Add(trtf); i.Camera.Transform = tg;
}
}
}
これで、次のように ミクをグリングリン回すことができるようになります。テスト用 ClickOnce操作方法
注意: 原点を中心に回転するようになっていますので、ミクを原点に配置しないとうまく回転してくれません。六角、ZAM3D、VS2008の パッケージ一式はこちら注意: キオ式 初音ミクの XAML 化データに関しては、本家 キオ式アニキャラ3D の “当方の3Dデータ、動画の使用条件について” を参照してください。注意: 今回使用したミクのデータは、V1.1-1.5 ぐらいで、少々古いです。また、ごみが混じっていたり、消し忘れの球が見えていたりしますが、ご愛嬌ということで w
http://www-sens.sys.es.osaka-u.ac.jp/users/kanaya/Documents/VCQ/kanaya-handai-quaternion.pdf
を勉強してみたところ、クオータニオンの積は、2つの回転を含むベクトルの合成であると書いてある。クオータニオン L, R, Res を適当に取り、Res = L * R を図解すると、下の図のように、クオータニオン L の軸に対して、クオータニオン Rののベクトルを、クオータニオン Lの回転角度だけ回転したものが クオータニオン Res となる。
右項のクオータニオンの回転が0の場合は上の絵のように回転面に対して、右項Rを回転させるだけでわかりやすいが、右項のクオータニオンの回転が0ではない場合に、Rにさらに回転が加わったものに対して、左項のクオータニオンの回転が合成されるため、先の資料を読んでいても、よくわからない。
そこで、XAML でプログラムを作って、実際に動作を確かめてみた。Form 左のスライダーが、左項クオータニオンの X, Y, Z, W、右側のスライダーが 右項のクオータニオンの X, Y, Z, W。ピンクのベクトルが 左項のクオータニオン、黄色のベクトルが右項のクオータニオン、黒のベクトルが積のクオータニオン。スライダーで、左項、右項のクオータニオンを動かすと、結果としての積のクオータニオンを表示するWPFアプリケーション。
スライダーの値を読み取って、クオータニオンの積を計算し、表示する処理の一部抜粋。
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
try
{
// 左項
Vector3D leftAxis = new Vector3D(lx.Value, ly.Value, lz.Value);
Quaternion leftQuaternion = new Quaternion(leftAxis, lrot.Value);
QuaternionRotation3D qrot3d = new QuaternionRotation3D(leftQuaternion);
RotateTransform3D leftRot3D = new RotateTransform3D(qrot3d);
Transform3DGroup leftT3DG = (Transform3DGroup)(this.LeftOR23.Transform);
leftT3DG.Children[2] = leftRot3D;
this.LeftOR23.Transform = leftT3DG;
this.textBlock1.Text = “Left:”
+ lx.Value.ToString(“f2”) + “:”
+ ly.Value.ToString(“f2”) + “:”
+ lz.Value.ToString(“f2”) + “:”
+ lrot.Value.ToString(“f2”);
// 右項
Vector3D rightAxis = new Vector3D(rx.Value, ry.Value, rz.Value);
Quaternion rightQuaternion = new Quaternion(rightAxis, rrot.Value);
QuaternionRotation3D rightQRot3D = new QuaternionRotation3D(rightQuaternion);
RotateTransform3D rightRot3D = new RotateTransform3D(rightQRot3D);
Transform3DGroup rightT3DG = (Transform3DGroup)(this.RightOR26.Transform);
rightT3DG.Children[2] = rightRot3D;
this.RightOR26.Transform = rightT3DG;
this.textBlock1.Text += “\nRight:”
+ rx.Value.ToString(“f2”) + “:”
+ ry.Value.ToString(“f2”) + “:”
+ rz.Value.ToString(“f2”) + “:”
+ rrot.Value.ToString(“f2”);
// クオータニオンの積
Quaternion resultQuaternion = leftQuaternion * rightQuaternion;
QuaternionRotation3D resultQR3D = new QuaternionRotation3D(resultQuaternion);
RotateTransform3D resultRot3D = new RotateTransform3D(resultQR3D);
Transform3DGroup resultTr = (Transform3DGroup)(this.ResultOR29.Transform);
resultTr.Children[2] = resultRot3D;
this.ResultOR29.Transform = resultTr;
this.textBlock1.Text += “\nResult:”
+ resultQuaternion.X.ToString(“f2”) + “:”
+ resultQuaternion.Y.ToString(“f2”) + “:”
+ resultQuaternion.Z.ToString(“f2”) + “:”
+ resultQuaternion.W.ToString(“f2”);
}
catch
{
}
}
これで、スライダーを動かして確かめてみると・・・なるほど・・・こうなるのか・・・。
これを数式から読み取るのは不可能だな・・・・・
こんな3次元のモデルも XAML で簡単にできてしまうあたりに感動してしまった。
ということで、回転させたい対象のクオータニオンをR、回転させる軸と回転角度を表わすクオータニオンLとすると、回転後のクオータニオン RES = L*R でとても簡単に計算ができる。ここで求められた回転後のクオータニオンRESから、RotateTransform3D を回転させたい対象に適用してあげれば、回転してくれるということだ。
クオータニオンがなければ、マトリックス計算の地獄になるところだけれど、クオータニオンの積一発。おまけに、WPF は、このクオータニオンの積も提供してくれているので、とても処理が楽。素晴らしい。
カメラの移動方法を調べていたら、そのものずばりの MSDN キューブ アニメーションのデモ を発見。これを組み込めば簡単に視点の変更ができる。
おお、もうひとりのおやじもこれを組み込んだのね。さすがだ。
と思い、デモソフトの Trackball.cs のコードをながめてみたら・・・外積を使っている?と不思議に思い、ソースを読んでみた。
次の図のようにマウスで左から右へドラッグし、ミクを中心にカメラを右方向に動かす場合、Y軸を中心にカメラを右方向に回転させることになります。では、どのようにY軸を中心にと判断しているかというと、カメラの視線ベクトルとカメラの移動ベクトルの外積から回転軸を求めています。外積を使えば、マウスの移動角度に応じて、斜めに回転する回転軸も簡単に求めることができます。
なるほど・・・外積ってこういう利用方法があるだぁ・・・と感動。ちなみに下が外積を簡単に説明した図。なつかしい・・・
と、さらに ソースを読んでいったところ、回転軸が求まったところで、クオータニオンの計算があり、意味不明 orz。クオータニオン自体初めて耳にするが、3D-CG をする人たちには常識らしい。ところが、にわか ミク3D野郎には難しすぎる・・・
ということで、次の資料があったので、一から勉強中。
http://www-sens.sys.es.osaka-u.ac.jp/users/kanaya/Documents/VCQ/kanaya-handai-quaternion.pdf
まさか、この歳でエルミート行列、オイラー角、テンソル、などなどと、どこかで聞いたことのあることをもう一度ひっくり返すことになるとは・・・・。一通り読んでみたのだが、外積、内積なら、物理的な意味が直感的にわかるのだが、クオータニオン同士の積の物理的な意味が直感的にわからない。ミクをグリングリン回すのにも、修行が足りない orz。
ちなみにクオータニオン(超複素数 or 四元数)は、大学では習わなかった。当時、3Dグラフィックスはなかったので w。
いろいろ試してみたが、マテリアルの色がうまく乗らない。たとえば、髪飾りのピンクと目の色が同じマテリアルにマップされており、髪飾りをピンクにすると、白眼もピンクになってしまう。しょうがないので、六角上でさらに細かく分割しました。あまり細かくすると、ZAM 3D へのインポートが大変になるので、顔(目、上の歯、下の歯、口の中)、ヘッドセットや、髪飾りは、各1つにグループ化しました。ようやくこれで ZAM 3D上で色が重ならずに、着色ができた。ということで、現在のデータ(六角、ZAM 3D、Visual Studio 2005) をまとめてアップしておきます。http://uchukamen.com/WPF/MikuData/Miku%20Kio%20V5.zipあとは、ZAM 3D でアニメーション機能があるので、下の図のように時間ごとにパーツを動かして、アニメーションを作成する。アニメーションが作成できたところで、Export to XAML で Visual Studio 20005 のプロジェクトに書き出す。このときに、アニメーションは、OnLoaded の ストーリーボードとして作成されます。したがって、このままVS 2005 でコンパイル&実行してあげると、そのまま踊りだします。ZAM 3Dでは、左下にある球体でトラックボールのようにオブジェクトの方向を変更することができ、かなり簡単にアニメーションを作成することができます。ただし、ZAM 3D の制限として、1つのアニメーションしか作成できないので、ちょっと不便です。複数のアニメーションを設定できると楽なんですけど・・・それから、ちょっとまえのデータですが、MikuMikuDance のスクリーンショットと実行ファイルをさらしておきますw。なお、うしろのビデオは、WMV を WPF でリアルタイムに変形&再生するように、Expression Blend で追加したものです。WPFすげー。それから、これはカメラのパン、ズーム、それから回転を行うデモ。http://uchukamen.com/WPF/MikuData/KioVS.exeコード的には、ズームはとても簡単。FieldOfView の値を変更してあげればよい。private void sliderZoom_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
this.Target_CameraOR20.FieldOfView = this.slider.Value;
}
マウスホィールを使うのであれば、マイナスとか大きすぎる場合のリミッターを入れて、たとえば次のようにすればよい。
private void ZAM3DViewport3D_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
this.Target_CameraOR63.FieldOfView += e.Delta / 100;
if (this.Target_CameraOR63.FieldOfView < 10)
this.Target_CameraOR63.FieldOfView = 10;
else if (this.Target_CameraOR63.FieldOfView > 100)
this.Target_CameraOR63.FieldOfView = 100;
}
カメラの方向を変更するのは、こんな感じ。
private void SliderH_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Transform3DGroup tg = new Transform3DGroup();
tg.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1, 0), this.SliderH.Value)));
tg.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(1, 0, 0), this.SliderV.Value))); this.Target_CameraOR20.Transform = tg;
}
private void SliderV_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Transform3DGroup tg = new Transform3DGroup();
tg.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1, 0), this.SliderH.Value)));
tg.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(1, 0, 0), this.SliderV.Value))); this.Target_CameraOR20.Transform = tg;
}もう少しスマートにするには、ZAM 3D のトラックボールのようなインターフェースがよいでしょう。これはもう一人のオヤジから展開されると思いますw
ちょっと前の話だが、MIX08 – Mobile Devices and Microsoft Silverlight のデモビデオがアップされている。
http://visitmix.com/blogs/2008Sessions/T12/
3Dはハードウェアアクセラレータが乗らないだろうから、厳しいだろうな・・・
でも、ビデオなど、同じXAMLでかけるのはでかい。
今の予定では、次の通り。Windows CE 6 ベースになる。