|
|
【Vocaloid初音ミクとMikuMikuDanceという驚愕のソフトウェア】 |
||
【リンクフリー】 私設研究所ネオテックラボ Neo-Tech-Lab.co.uk 【記載者】 私設研究所Neo-Tech-Lab.com 上田智章 |
|
|
ここにチェックボックス型外部コンテンツ・メニューが入ります。 | ||
|
|
【メニュー】 [index] ■メモ [PMD1] ●【VBA】ポリゴン・フィルとテクスチャー・マッピング [PMD2] ●【VBA】透視変換 [PMD3] ●【VBA】光源計算 [Sensor]★【NyARToolkitCS】拡張現実センサ【ソース公開】C# [PMD6] ■【NyARToolkitCS】MMDのPMDで拡張現実(モデル描画)C# [PMD4] ■【NyARToolkitCS】MMDのPMDで拡張現実(表情処理)C# [PMD5] ●IK bone制御 [Kinect]★【Kinect Sensor】拡張現実センサ2【ソース公開】C# [TTS] ★【iSpeech APIの音声合成TTS】【ソース公開】JavaScript [シンセ]★『JavaScriptでWebシンセサイザ/ボーカロイド(なんちゃって版)を自作してみた』 |
|
【Webシンセサイザ/ボーカロイドのページ】JavaScriptだけでシンセサイザやボーカロイドの歌唱/音声合成を実装してみようとする試みを始めました。【対象ブラウザ】Google Chrome12, Mozilla FireFox5, Apple Safari ●『なんちゃって初音ミク』NTL版Text-To-Speech API [JavaScript Ver.0.06] ●『なんちゃって初音ミク』Ieavan Polkka [JavaScript Ver.0.06] スタンダード・ドラム・セット付 音が出るまでに10~30秒を要します。気長にお待ちください。 【WebGLのページ】JavaScriptでリアルタイム3次元グラフィックスが楽しめる時代の到来までもうすぐです。現在、Google Chrome12、Mozilla FireFox5、及び開発版のSafari、Operaで使うことができます。 WebGLは、JavaScriptエンジンv8とDirectXを利用して高速化したOpenGLを融合して構成されたものです。 2011年5月に指摘されたセキュリティー問題はありますが、処理速度の高速性から利便性も高いので、将来的には普及するのではないでしょうか? ●WebGLとは? ●事前準備:利用できるウェブブラウザは? ●事前準備:FireFox5でWebGLが動作しない場合には? ●WebGLのサンプル ●WebGLのデモ ●クロスドメインテクスチャーを将来も利用できる方法 ●ローカルのPMDファイルを読み込んでJSON形式に変換し、3次元モデルを表示 ●物理エンジンBullet.jsのWebGLデモの紹介(Pl4n3氏のragdollデモの修正版)[btBoxShapeとbtCapsuleShape] |
|
■■■記載日2009年10月9日■■■ ■■■記載日2011年01月30日■■■ 【表情のためのPMDデータ構造とその処理内容】昔、私が3Dグラフィック・アクセラレータ回路の研究・開発をしていた(1984年~1988年)頃には3D CADのレベル(静止画を座標回転させる程度)であったため、SkinやBone等は存在しなかった。粗末な数100ポリゴンのロボットにフラフープをリアル・タイムで回させれば最先端だった時代だった。 というような事情で現在の3D-CGの基本原理は詳しく知りはしないのだが、かと言って『人の書いた本は1分で寝れる睡眠剤』状態なので読んで勉強する気にはならない。困った性格。 でもソフトウェアならデータ構造からその処理内容も推察できるはず。というわけで、PMDデータをじっと眺めたら、頂点バッファのインデックス値とベース位置ベクトル、表情インデックスと表情ベクトルからなるバッファがあった。下図に示す通り、ベース位置ベクトルに、表情係数SkinEffect(0.0~1.0)を表情ベクトルに乗じたものを加算して、VertexBufferの頂点座標に入れていた。表情データはベース位置ベクトルがあるのでオリジナルの頂点座標データを上書きしても問題ないわけだ。 つまり、全体の処理の流れとしては ①オリジナル頂点データに表情ベクトルを上書きする。 ②全ての頂点座標データにボーン処理に伴う頂点座標変換処理を施し、別バッファに格納する。 ③材質単位に描画する。 (Sphere MappingはTu, Tvを破棄するので同期処理も面倒なので別バッファを用意する。) という感じの処理になる。 NyARToolkitCSでこの表情データを利用する場合の注意事項は、右手系から左手系に変換するために、base位置ベクトルと表情ベクトルのZ成分は-Zとすること、さらにミクセル単位系からmm単位系に大きさを変更する必要があるので、base位置ベクトルと表情ベクトルもその対象であるという点だ。 実際にコーディングして確認したところ、頂点座標の移動はリニア補間(直線補間)で間違いないことを確認した。下に表情デモのコードを掲載する。 【図1】表情(Skin)について 【表情テストのプロジェクトファイル】【まずはNyARToolkitCSをダウンロードしよう】【NyARToolkitCS】 ホームページNyARToolkitCSトップページ 【MikuMikuDanceの最新版をダウンロードしよう】【MikuMikuDanceのマルチモデルの最新版をダウンロード】【C#プロジェクトファイル(ARToolKit_PMD_Load)のダウンロード】V002版はMikuMikuDanceの標準モデルである『初音ミクmetal』のSphere Mapping描画にも対応しています。V003,V004版は表情を加えたのですが、Sphere Mapping使用モデルではデフォルトの表情が残りうまく表示できません。 V004版では表情の指定を番号だけでなく、名称(笑い、にこり、あ など)でできるようにしました。これによりモデルが違っても可能な限り同じ表情に設定できるというMikuMikuDanceの特徴に少しだけ近づけた気がします。 と書いてるそばから、Sphere Mappingの表情バグがわかった。表情はOriginalをいじるようにしないとダメだった。 V005版はV002版と同様に『初音ミクmetal』のSphere Mapping描画にも対応しました。 但し、bmpとsphまたはspaの混在仕様やjpgテクスチャー仕様には対応していません。 【ダウンロード】プロジェクトファイル一式(Version0.05 Sphereバグ対応) 表情を表情番号ではなく、表情名称で探して設定できるようにしたバージョンです。 異なるモデルでも、同一名称の表情を設定してそれなりの表情にできます。 Sphere Mappingの『初音ミクmetal』にも対応。 【ダウンロード】プロジェクトファイル一式(Version0.03 表情処理のテスト版) 【ダウンロード】プロジェクトファイル一式(Version0.02 Sphere Mapping対応版) 【ダウンロード】プロジェクトファイル一式(Version0.01) 【注意事項】当然のことながらバイナリー(実行形式ファイル)が添付されていませんので、動作させるにはビルドさせるためにVisual C# 2010 Express Edition(無料)が必要です。 [ARToolKit_PMD_Load] +-[PMD] 【MikuMikuDanceのモデルデータ】 | MikuMikuDanceのUserFileフォルダの下のModelフォルダの中身を全てここにコピーします。 +-[AR_Data] 【NyARToolkitCS】camera_para.dat, patt.hiroをここに格納 +-[Library] 【NyARToolkitCS】 | DirectShowLib-2005.dll, NyARToolkitCS.dll, NyARToolkitCSUtils.dllをここに格納 +-[Marker] 【印刷用マーカー】hiroパターン2.docの一番小さなマーカーがお勧め +-ソース一式とsln(ソリューション) |
【C# & Managed DirectX & NyARToolkitCSでPMDを表示する】表情処理のように動的でリアルタイム性の高い処理はやはりExcel VBAでは遅すぎて使い物にならない。拡張現実アプリケーション開発キット『NyARToolkitCS』で遊んでみて案外理解しやすかったので方針を変更した。 C# & Managed DirectX & NyARToolkitCSでMikuMikuDanceのPMDデータ形式でSphere Mappingも含め標準モデルを表示することに成功したので、表情処理のコーディングもこのままC#で記述する。 PMDデータ読み込み部分(PMD_Load.cs)に以下のようにBone, IK_Bone, Skinの情報読み込み部分を追記した。 現時点で一応データズレを起こさずに読み込めることを4個のPMDで確認したのみだ。 Bone情報部分には、short型のIK接続情報が存在しているらしいこと、IK_Bone情報部分に一部長さが違う部分が存在していることなどを確認した。この部分の意味はまだ理解していない。 表情データは、基準点の位置ベクトルであるbaseと表情制御に伴って相対的に偏移させる方向ベクトルが定義されており、直線補間を行うようだ。 予想と違ってbase部分は全表情分まとまって定義されていた。 以下にV005のソースの一部を掲載します。(上にC#プロジェクトファイルのダウンロードリンクもあります。詳細はそちらを参考の事。) |
【PMD_Load.cs】【注意1】Google Chromeには『<pre>』タグ処理のバグでリストがちゃんと反映できない可能性があります。プロジェクトファイルを参照してください。【注意2】下記ソースでは、まだ物理演算係数ブロック部分は読み込んでいません。PMD1.htmを参照してください。 using System; using System.IO; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace PMDMikuDirect3d { public partial class SimpleLiteD3d : IDisposable { public struct PMD_Header //【PMDファイル ヘッダー情報】 { public string ID; //【ID】3バイト固定長 "P","m","d" public float vNum; // バージョン番号 public string ModelName; //【モデル名称】20バイト固定長 public string CopyRight; //【著作権情報】256バイト固定長 } public struct PMD_vertex //【PMD_頂点データ】 4*3+4*3+4*2+2*2+2 = 12+12+8+4+2 = 38bytes/頂点 { public int nPoints; //【頂点個数】 public CustomVertex.PositionNormalTextured[] OriginalVertex; //【頂点座標】【法線ベクトル】【uv座標】オリジナル格納 public CustomVertex.PositionNormalTextured[] UserVertex; //【頂点座標】【法線ベクトル】【uv座標】Bone処理後 public CustomVertex.PositionNormalTextured[] UserVertex2; //【頂点座標】【法線ベクトル】【uv座標】Sphere Mapping用 public short[] Bone1Num; //【Bone番号】Bone番号1 モデル変形(頂点移動)時に影響 public short[] Bone2Num; //【Bone番号】Bone番号2 : public Byte[] BoneWeight; //【Bone重み係数】Bone1に与える影響度 0-100 Bone2への影響度は(100-bone_weight)で与えられる。 public Byte[] EdgeFlag; //【エッジ・フラグ】0:通常、1:エッジ無効 輪郭線が有効の場合 } public struct PMD_index //【PMD_インデックスデータ(面情報)】MikuMikuDanceのモデルではtriangle(三角形)しか使っていません。 { public int nIndex; //【配列要素数】=面数(三角形の個数)*3 public int nPolygons; //●三角形の個数 public short[] IndexBuffer; //【頂点番号リスト】3個/面 } //注)本質的にはGPU側の制約によるが、この配列のためにMMDでは最大65535頂点までのモデルしか取り扱えない。 public struct PMD_material //【PMD_材質データ】 { public int nMaterial; //【材質個数】 public Material[] Material; //【材質データ】DirectXの材質データ構造体と同じ public byte[] ToonIndex; //【トゥーン・インデックス番号】toon??.bmp 0.bmp:0xFF, 1.bmp:0x00, ・・・10.bmp:0x09 public byte[] EdgeFlag; //【エッジ・フラグ】輪郭、影 public int[] nPoints; //【面構成要素数】面数*3 = 面頂点数 実際のindexに直すには材質0から累積加算する。 public string[] TextureFileName; //【テクスチャ・ファイル名】20bytesギリギリまで使える 20bytes時は0x00がなくても可。' public Texture[] Texture; //●【テクスチャ】 public bool[] SphereMap; //●【SphereMapフラグ】 public bool SphereFlag; //●【描画時フラグ】Sphere無しなら高速に描画 } public struct PMD_bone //【PMD_Boneデータ】 { public short nBone; //【Bone数】 public string[] BoneName; //【Bone名称】PMDファイルでは固定長(20bytes) public short[] ParentBoneIndex; //【親Bone頂点番号】(無い場合は0xFFFF) public short[] ChildBoneIndex; //【子Bone頂点番号】(chain末端は0xFFFF)親:子は1:多なので位置決め用 public byte[] BoneType; //【Boneの種類】0:回転のみ 1:回転と移動 2:IK 4:影響下 5:回転影響下 6:IK接続先 8:捻り 9:回転運動 public short[] Dummy; //【不明】 public Vector3[] BoneHead; //【Boneのヘッド座標】(x,y,z)位置ベクトル } public struct PMD_IKChain { public short[] Index; //【IK影響下のBone番号】 } public struct PMD_IKbone //【PMD_IKBoneデータ】 { public short nIKBone; //【IKBone数】 public short[] IKBoneIndex; //【IK Bone番号】 public short[] IKTargetBoneIndex; //【IK接続先Bone番号】 public byte[] IKChainLength; //【IK鎖の長さ】子の数 public short[] Iterations; //【再帰演算回数】 public float[] ControlWeight; //【重み係数】IKの影響度 public PMD_IKChain[] IKChildBoneIndex; //【IK影響下のBone番号】 } public struct PMD_Vectors //【PMD_表情頂点データ】 { public int[] Index; //【頂点番号】 public Vector3[] Position; //【頂点位置ベクトルまたは表情ベクトル】 } public struct PMD_Skin //【PMD_Skinデータ】 { public short nSkin; //【Skin数】 public string[] SkinName; //【表情名】 public int[] nSkinPoints; //【表情用頂点数】 public byte[] SkinType; //【表情の種類】 0:base 1:眉 2:リップ 4:その他 public PMD_Vectors[] SkinPointVector; //【表情用頂点座標】位置ベクトル public PMD_Vectors SkinSetData; //●最終的にUserVertexBufferにセットする頂点座標 public float[] SkinEffect; //●各表情ベクトルの係数値(nSkin個) } public PMD_Header PMDh; //【PMDヘッダー情報】 public PMD_vertex PMDv; //【PMD頂点バッファ】 public PMD_index PMDi; //【PMDインデックス】 public PMD_material PMDm; //【PMD材質情報】 public PMD_bone PMDb; //【PMDボーン】 public PMD_IKbone PMDIKb; //【PMDIKボーン】 public PMD_Skin PMDs; //【PMD表情】 public string TextBuffer; //【テキストの読み込みに使うバッファ】 public const float Zoom = 20.0f; //【変換倍率】座標系をMMD⇒ARに変換する際、1ミクセルを20倍にして表示。 public const string PMDPath = "../../PMD/"; //【PMDファイルの相対パス】 //*********************************************************************************************** //*** FileStreamとBinaryReaderを使ってPMDファイルからMMD構造体に情報を読み込む //*********************************************************************************************** public void ReadPMDFile(string PMDFilePath) //【MikuMikuDanceのPMDファイルからHeader, Vertex, Index, Meterialを読み込む】 { FileStream PMDFile = new FileStream(PMDFilePath, FileMode.Open); //【PMDファイル読み込みの為のFileStream】 BinaryReader bRD = new BinaryReader(PMDFile); //【PMDファイル読み込みの為のBinaryReader】 //【PMDヘッダー情報を読み込む】 //●文字列は改行や空白等の不必要な領域も含めて読み込んでいる。 PMDh.ID = Encoding.GetEncoding(932).GetString(bRD.ReadBytes(3)); //【IDを読み込む】固定長3バイト "P","m","d" PMDh.vNum = bRD.ReadSingle(); //【バージョン番号を読み込む】 PMDh.ModelName = Encoding.GetEncoding(932).GetString(bRD.ReadBytes(20)); //【モデル名称を読み込む】固定長20バイト PMDh.CopyRight = Encoding.GetEncoding(932).GetString(bRD.ReadBytes(256)); //【著作権情報を読み込む】固定長256バイト //【PMD頂点情報を読み込む】 PMDv.nPoints = bRD.ReadInt32(); // PMDv.OriginalVertex = new CustomVertex.PositionNormalTextured[PMDv.nPoints]; //【Original】 PMDv.UserVertex = new CustomVertex.PositionNormalTextured[PMDv.nPoints]; // Tu,TvはSphereMapで変更されることがある。 PMDv.Bone1Num = new short[PMDv.nPoints]; //【Bone番号】Bone番号1 モデル変形(頂点移動)時に影響 PMDv.Bone2Num = new short[PMDv.nPoints]; //【Bone番号】Bone番号2 : PMDv.BoneWeight = new Byte[PMDv.nPoints]; //【Bone重み係数】Bone1に与える影響度 0-100 Bone2への影響度は(100-bone_weight)で与えられる。 PMDv.EdgeFlag = new Byte[PMDv.nPoints]; //【エッジ・フラグ】0:通常、1:エッジ無効 輪郭線が有効の場合 for (var i = 0; i < PMDv.nPoints; i++) //【右手系から左手系に】Z⇒-Z, Nz⇒-Nz { PMDv.OriginalVertex[i].Position = new Vector3(bRD.ReadSingle() * Zoom, bRD.ReadSingle() * Zoom, -bRD.ReadSingle() * Zoom); //【位置ベクトル】X,Y,Z PMDv.OriginalVertex[i].Normal = new Vector3(bRD.ReadSingle(), bRD.ReadSingle(), -bRD.ReadSingle()); //【法線ベクトル】Nx, Ny, Nz PMDv.OriginalVertex[i].Tu = bRD.ReadSingle(); //【テクスチャーUV空間】u PMDv.OriginalVertex[i].Tv = bRD.ReadSingle(); //【テクスチャーUV空間】v PMDv.Bone1Num[i] = bRD.ReadInt16(); PMDv.Bone2Num[i] = bRD.ReadInt16(); PMDv.BoneWeight[i] = bRD.ReadByte(); PMDv.EdgeFlag[i] = bRD.ReadByte(); } //【PMD面(インデックス)情報を読み込む】 PMDi.nIndex = bRD.ReadInt32(); PMDi.nPolygons = PMDi.nIndex / 3; PMDi.IndexBuffer = new short[PMDi.nIndex]; for (var i = 0; i < PMDi.nIndex; i++) { PMDi.IndexBuffer[i] = bRD.ReadInt16(); } //【PMD材質情報を読み込む】 PMDm.nMaterial = bRD.ReadInt32(); PMDm.Material = new Material[PMDm.nMaterial]; PMDm.ToonIndex = new byte[PMDm.nMaterial]; PMDm.EdgeFlag = new byte[PMDm.nMaterial]; PMDm.nPoints = new int[PMDm.nMaterial]; PMDm.TextureFileName = new string[PMDm.nMaterial]; PMDm.SphereMap = new bool[PMDm.nMaterial]; PMDm.SphereFlag = false; for (var i = 0; i < PMDm.nMaterial; i++) { PMDm.Material[i].AmbientColor = new ColorValue(bRD.ReadSingle(), bRD.ReadSingle(), bRD.ReadSingle(), bRD.ReadSingle()); //【環境色】Red,Green,Blue,Alpha PMDm.Material[i].SpecularSharpness = bRD.ReadSingle(); //【鏡面反射強度】 PMDm.Material[i].SpecularColor = new ColorValue(bRD.ReadSingle(), bRD.ReadSingle(), bRD.ReadSingle()); //【鏡面反射色】Red, Green, Blue PMDm.Material[i].DiffuseColor = new ColorValue(bRD.ReadSingle(), bRD.ReadSingle(), bRD.ReadSingle()); //【拡散光色】 Red, Green, Blue PMDm.Material[i].EmissiveColor = new ColorValue(0.0F, 0.0F, 0.0F); //【発光色はMikuMikuDanceでは未使用】 PMDm.ToonIndex[i] = bRD.ReadByte(); PMDm.EdgeFlag[i] = bRD.ReadByte(); PMDm.nPoints[i] = bRD.ReadInt32(); PMDm.TextureFileName[i] = ""; //【テクスチャーファイル名読み込み】固定長20バイト TextBuffer = Encoding.GetEncoding(932).GetString(bRD.ReadBytes(20)); for (var k = 0; k < 20; k++) //【nullコード直前まで読み込む】 { if (TextBuffer[k] == 0) { break; } else { PMDm.TextureFileName[i] += TextBuffer[k]; } } PMDm.SphereMap[i] = false; if (PMDm.TextureFileName[i] != "") { string[] p = PMDm.TextureFileName[i].Split(new char[] { '.' }); if (p[1] == "sph") { PMDm.SphereMap[i] = true; PMDm.SphereFlag = true; } //【ファイル名がsph形式の場合】 PMDm.TextureFileName[i] = PMDPath + PMDm.TextureFileName[i]; } } //【Sphere Mappingを含んだモデルの場合】変更したTu,Tvを格納するSphereMap用のUserVertexを用意する if (PMDm.SphereFlag) { PMDv.UserVertex2 = new CustomVertex.PositionNormalTextured[PMDv.nPoints]; } //【PMDボーン情報を読み込む】 PMDb.nBone = bRD.ReadInt16(); PMDb.BoneName = new string[PMDb.nBone]; //【Bone名称】20bytes PMDb.ParentBoneIndex = new short[PMDb.nBone]; //【親Bone頂点番号】(無い場合は0xFFFF) PMDb.ChildBoneIndex = new short[PMDb.nBone]; //【子Bone頂点番号】(chain末端は0xFFFF)親:子は1:多なので位置決め用 PMDb.BoneType = new byte[PMDb.nBone]; //【Boneの種類】0:回転のみ 1:回転と移動 2:IK 4:影響下 5:回転影響下 6:IK接続先 8:捻り 9:回転運動 PMDb.Dummy = new short[PMDb.nBone]; //【不明】IK接続先? PMDb.BoneHead = new Vector3[PMDb.nBone]; //【Boneのヘッド座標】(x,y,z)位置ベクトル for (var i = 0; i < PMDb.nBone; i++) { PMDb.BoneName[i] = ""; //【ボーン名読み込み】固定長20バイト TextBuffer = Encoding.GetEncoding(932).GetString(bRD.ReadBytes(20)); for (var k = 0; k < 20; k++) //【nullコード直前まで読み込む】 { if (TextBuffer[k] == 0) { break; } else { PMDb.BoneName[i] += TextBuffer[k]; } } PMDb.ParentBoneIndex[i] = bRD.ReadInt16(); PMDb.ChildBoneIndex[i] = bRD.ReadInt16(); PMDb.BoneType[i] = bRD.ReadByte(); PMDb.Dummy[i] = bRD.ReadInt16(); PMDb.BoneHead[i] = new Vector3(bRD.ReadSingle(), bRD.ReadSingle(), bRD.ReadSingle()); } //【PMD IKボーン情報を読み込む】 PMDIKb.nIKBone = bRD.ReadInt16(); //【IKBone数】2bytes? 1bytesでは? PMDIKb.IKBoneIndex = new short[PMDIKb.nIKBone]; //【IK Bone番号】 PMDIKb.IKTargetBoneIndex = new short[PMDIKb.nIKBone]; //【IK接続先Bone番号】 PMDIKb.IKChainLength = new byte[PMDIKb.nIKBone]; //【IK鎖の長さ】子の数 PMDIKb.Iterations = new short[PMDIKb.nIKBone]; //【再帰演算回数】 PMDIKb.ControlWeight = new float[PMDIKb.nIKBone]; //【重み係数】IKの影響度 PMDIKb.IKChildBoneIndex = new PMD_IKChain[PMDIKb.nIKBone]; //【IK影響下のBone番号】IKChainLength=0ならなし for (var i = 0; i < PMDIKb.nIKBone; i++) { PMDIKb.IKBoneIndex[i] = bRD.ReadInt16(); PMDIKb.IKTargetBoneIndex[i] = bRD.ReadInt16(); PMDIKb.IKChainLength[i] = bRD.ReadByte(); PMDIKb.Iterations[i] = bRD.ReadInt16(); PMDIKb.ControlWeight[i] = bRD.ReadSingle(); if (PMDIKb.IKChainLength[i] != 0) { PMDIKb.IKChildBoneIndex[i].Index = new short[PMDIKb.IKChainLength[i]]; for (var j = 0; j < PMDIKb.IKChainLength[i]; j++) { PMDIKb.IKChildBoneIndex[i].Index[j] = bRD.ReadInt16(); } } } //【PMDスキン情報を読み込む】 PMDs.nSkin = bRD.ReadInt16(); //【Skin数】 PMDs.SkinName = new string[PMDs.nSkin]; //【表情名】 PMDs.nSkinPoints = new int[PMDs.nSkin]; //【表情用頂点数】 PMDs.SkinType = new byte[PMDs.nSkin]; //【表情の種類】 0:base 1:眉 2:リップ 4:その他 PMDs.SkinPointVector = new PMD_Vectors[PMDs.nSkin]; //【表情用頂点座標】位置ベクトル PMDs.SkinEffect = new float[PMDs.nSkin]; //【各表情ベクトルの係数】0.0~1.0 for (var i = 0; i < PMDs.nSkin; i++) { PMDs.SkinName[i] = ""; //【表情名】固定長20バイト TextBuffer = Encoding.GetEncoding(932).GetString(bRD.ReadBytes(20)); for (var k = 0; k < 20; k++) //【nullコード直前まで読み込む】 { if (TextBuffer[k] == 0) { break; } else { PMDs.SkinName[i] += TextBuffer[k]; } } PMDs.nSkinPoints[i] = bRD.ReadInt32(); //【表情用頂点数】 PMDs.SkinType[i] = bRD.ReadByte(); //【表情の種類】 0:base 1:眉 2:リップ 4:その他 PMDs.SkinPointVector[i].Index = new int[PMDs.nSkinPoints[i]]; PMDs.SkinPointVector[i].Position = new Vector3[PMDs.nSkinPoints[i]]; for (var j = 0; j < PMDs.nSkinPoints[i]; j++) { PMDs.SkinPointVector[i].Index[j] = bRD.ReadInt32(); //【表情用頂点インデックス】 //【表情用頂点位置ベクトル/表情ベクトル】表示倍率Zoomをかけておく。Zは頂点と同様反転するので注意。 PMDs.SkinPointVector[i].Position[j] = new Vector3(Zoom * bRD.ReadSingle(), Zoom * bRD.ReadSingle(), -Zoom * bRD.ReadSingle()); } PMDs.SkinEffect[i] = 0.0f; //【各表情ベクトル係数の初期化】 } PMDs.SkinSetData.Index = new int[PMDs.nSkinPoints[0]]; //【表情設定用Indexデータ】表情ベクトルを設定するVertexのIndex値格納用 PMDs.SkinSetData.Position = new Vector3[PMDs.nSkinPoints[0]]; //【表情設定用Vertexデータ】各表情ベクトルを加算したVertex値格納用 for (var i = 0; i < PMDs.nSkinPoints[0]; i++) //【表情ベクトルの頂点番号リストの初期化】Indexは1回コピーしておけばよい { PMDs.SkinSetData.Index[i] = PMDs.SkinPointVector[0].Index[i]; } //【PMDファイルを閉じる】 bRD.Close(); PMDFile.Close(); } //*********************************************************************************************** //*** 表情ベクトルの初期化を行う //*********************************************************************************************** public void InitializeFacialExpression() { for (var i = 0; i < PMDs.nSkinPoints[0]; i++) //【表情ベクトル頂点座標初期化】 { PMDs.SkinSetData.Position[i] = PMDs.SkinPointVector[0].Position[i]; } } //*********************************************************************************************** //*** 表情名称で表情ベクトルを探し、発見できればbaseに加算する //*********************************************************************************************** public void SetFacialExpression(string SkinName, float SkinEffect) { int SkinNumber = 0; for (var i = 1; i < PMDs.nSkin; i++) { if (PMDs.SkinName[i] == SkinName) { SkinNumber = i; } } if (SkinNumber != 0) //【同じ名称の表情が見つかった場合、表情ベクトルをbaseに加える】 { for (var i = 0; i < PMDs.nSkinPoints[SkinNumber]; i++) // { PMDs.SkinSetData.Position[PMDs.SkinPointVector[SkinNumber].Index[i]] += SkinEffect * PMDs.SkinPointVector[SkinNumber].Position[i]; } } } //*********************************************************************************************** //*** 指定表情番号の表情ベクトルをbaseに加算する //*********************************************************************************************** public void SetFacialExpression(int SkinNumber, float SkinEffect) { if (SkinNumber > 0 && SkinNumber < PMDs.nSkin) //【0はbaseなので不可】 { for (var i = 0; i < PMDs.nSkinPoints[SkinNumber]; i++) // { PMDs.SkinSetData.Position[PMDs.SkinPointVector[SkinNumber].Index[i]] += SkinEffect * PMDs.SkinPointVector[SkinNumber].Position[i]; } } } //*********************************************************************************************** //*** 表情ベクトルをUserVertexBufferに設定する //*********************************************************************************************** public void TransferFacialExpression() { for (var i=0; i < PMDs.nSkinPoints[0]; i++) { PMDv.OriginalVertex[PMDs.SkinSetData.Index[i]].Position = PMDs.SkinSetData.Position[i]; } } } } |
【SimpleLiteDirectD3d】今回は表情処理が機能することを確認するためのコードとした。今後、Web Agentに適用するために複数の表情ベクトルを連携して感情ベクトル化するつもりだ。 |
using System; using System.IO; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Threading; using System.Windows.Forms; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; using NyARToolkitCSUtils.Capture; using NyARToolkitCSUtils.Direct3d; using NyARToolkitCSUtils.NyAR; using jp.nyatla.nyartoolkit.cs; using jp.nyatla.nyartoolkit.cs.core; using jp.nyatla.nyartoolkit.cs.detector; namespace PMDMikuDirect3d { public partial class SimpleLiteD3d : IDisposable, CaptureListener { private const int SCREEN_WIDTH=640; private const int SCREEN_HEIGHT=480; private const String AR_CODE_FILE = "../../AR_Data/patt.hiro"; private const String AR_CAMERA_FILE = "../../AR_Data/camera_para.dat"; private CaptureDevice _cap; //DirectShowからのキャプチャ private NyARSingleDetectMarker _ar; //NyARToolkit private DsBGRX32Raster _raster; //NyARToolkit private NyARD3dUtil _utils; //NyARToolkit private NyARSurface_XRGB32 _surface; //背景テクスチャ private Device _device = null; // Direct3D デバイス private NyARTransMatResult __OnBuffer_nyar_transmat = new NyARTransMatResult(); private bool _is_marker_enable; //NyARToolkit private Matrix _trans_mat; //NyARToolkit private VertexBuffer _vertexBuffer = null; //【GPU側VertexBuffer】 private IndexBuffer _indexBuffer = null; //【GPU側IndexBuffer】 private float rot3 = 0.0f; //【モデルを回転表示するために使用】 private int SkinNumber = 1; //【表示する表情の番号】0はbaseデータなので表情としては表示できない private float SkinEffect; //******************************************************************************************* //【非同期イベントハンドラ】 CaptureDeviceからのイベントをハンドリングして、バッファとテクスチャを更新する。 //******************************************************************************************* public void OnBuffer(CaptureDevice i_sender, double i_sample_time, IntPtr i_buffer, int i_buffer_len) { int w = i_sender.video_width; int h = i_sender.video_height; int s = w * (i_sender.video_bit_count / 8); NyARTransMatResult nyar_transmat = this.__OnBuffer_nyar_transmat; //テクスチャにRGBを取り込み() lock (this) { //カメラ映像をARのバッファにコピー this._raster.setBuffer(i_buffer, i_sender.video_vertical_flip); //マーカーは見つかったかな? bool is_marker_enable = this._ar.detectMarkerLite(this._raster, 120); if (is_marker_enable) { //あればMatrixを計算 this._ar.getTransmationMatrix(nyar_transmat); this._utils.toD3dMatrix(nyar_transmat, ref this._trans_mat); } this._is_marker_enable=is_marker_enable; this._surface.CopyFromXRGB32(this._raster); //テクスチャ内容を更新 } return; } //【キャプチャを開始する関数】 public void StartCap() { this._cap.StartCapture(); return; } //【キャプチャを停止する関数】 public void StopCap() { this._cap.StopCapture(); return; } //******************************************************************************************* //【Direct3Dデバイスを準備する関数】 //******************************************************************************************* private Device PrepareD3dDevice(Control i_window) { PresentParameters pp = new PresentParameters(); pp.Windowed = true; pp.SwapEffect = SwapEffect.Flip; pp.BackBufferFormat = Format.X8R8G8B8; pp.BackBufferCount = 1; pp.EnableAutoDepthStencil = true; pp.AutoDepthStencilFormat = DepthFormat.D16; CreateFlags fl_base = CreateFlags.FpuPreserve; try{ return new Device(0, DeviceType.Hardware, i_window.Handle, fl_base|CreateFlags.HardwareVertexProcessing, pp); }catch (Exception ex1){ Debug.WriteLine(ex1.ToString()); try{ return new Device(0, DeviceType.Hardware, i_window.Handle, fl_base | CreateFlags.SoftwareVertexProcessing, pp); }catch (Exception ex2){ // 作成に失敗 Debug.WriteLine(ex2.ToString()); try{ return new Device(0, DeviceType.Reference, i_window.Handle, fl_base | CreateFlags.SoftwareVertexProcessing, pp); }catch (Exception ex3){ throw ex3; } } } } //******************************************************************************************* //【アプリケーションの初期化を行う】 //******************************************************************************************* public bool InitializeApplication(Form1 topLevelForm, CaptureDevice i_cap_device) { topLevelForm.ClientSize=new Size(SCREEN_WIDTH,SCREEN_HEIGHT); //キャプチャを作る(QVGAでフレームレートは30) i_cap_device.SetCaptureListener(this); i_cap_device.PrepareCapture(SCREEN_WIDTH, SCREEN_HEIGHT, 15); this._cap = i_cap_device; //ARの設定 //ARラスタを作る(DirectShowキャプチャ仕様)。 this._raster = new DsBGRX32Raster(i_cap_device.video_width, i_cap_device.video_height, i_cap_device.video_width * i_cap_device.video_bit_count / 8); //AR用カメラパラメタファイルをロードして設定 NyARParam ap = new NyARParam(); ap.loadARParamFromFile(AR_CAMERA_FILE); ap.changeScreenSize(SCREEN_WIDTH, SCREEN_HEIGHT); //AR用のパターンコードを読み出し NyARCode code = new NyARCode(16, 16); code.loadARPattFromFile(AR_CODE_FILE); //1パターンのみを追跡するクラスを作成 this._ar = new NyARSingleDetectMarker(ap, code, 80.0, this._raster.getBufferType(), NyARSingleDetectMarker.PF_NYARTOOLKIT); //Direct3d用のユーティリティ準備 this._utils = new NyARD3dUtil(); //計算モードの設定 this._ar.setContinueMode(true); //3dデバイスを準備する this._device = PrepareD3dDevice(topLevelForm); this._device.RenderState.ZBufferEnable = true; // 陰面処理をzバッファ方式で行う this._device.RenderState.ZBufferFunction = Compare.LessEqual; //.LessEqual; this._device.RenderState.Lighting = true; // 光源計算処理を行う true; // 平行光源(無限遠に設定される方向を持った光源。太陽光の様な光源) this._device.Lights[0].Type = LightType.Directional; this._device.Lights[0].Ambient = Color.FromArgb(255, 200, 200, 200); this._device.Lights[0].Diffuse = Color.White; this._device.Lights[0].Direction = new Vector3(-0.57735f, -0.57735f, -0.57735f); this._device.Lights[0].Enabled = true; // 環境光(全ての物体を均一に照らす光源) this._device.RenderState.Ambient = Color.FromArgb(0x202020); //カメラProjectionの設定 Matrix tmp = new Matrix(); this._utils.toCameraFrustumRH(ap, ref tmp); this._device.Transform.Projection = tmp; // ビュー変換の設定(左手座標系ビュー行列で設定する) (0,0,0)からZ+方向を向いてY軸が上方向 this._device.Transform.View = Matrix.LookAtLH( new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.0f, 0.0f, 1.0f), new Vector3(0.0f, 1.0f, 0.0f)); Viewport vp = new Viewport(); vp.X = 0; vp.Y = 0; vp.Height = ap.getScreenSize().h; vp.Width = ap.getScreenSize().w; vp.MaxZ = 1.0f; //ビューポート設定 this._device.Viewport = vp; //【頂点バッファの準備】頂点データ(X,Y,Z,Nx,Ny,Nz,Tu,Tv)を登録するバッファの準備 this._vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionNormalTextured), PMDv.nPoints, this._device, Usage.None, CustomVertex.PositionNormalTextured.Format, Pool.Managed); //【インデックスバッファの準備】第2引数はバッファの総バイト数=(三角ポリゴンの数)*(ひとつの三角ポリゴンの頂点数)*(Int16は2bytes) this._indexBuffer = new IndexBuffer(this._device, PMDi.nPolygons * 3 * 2, Usage.WriteOnly, Pool.Managed, true); //【インデックスバッファをロックする】 //●インデックスバッファは途中で変更することがないので初期化時にのみ転送 //●頂点バッファは、表情/Bone処理で頂点座標を、Sphere MappingでTu,Tvを変更するのでMainLoop内で転送 using (GraphicsStream data = this._indexBuffer.Lock(0, 0, LockFlags.None)) { data.Write(PMDi.IndexBuffer); // インデックスデータをインデックスバッファにコピーします this._indexBuffer.Unlock(); // インデックスバッファのロックを解除します } //【テクスチャーの準備】PMDは材質ごとにTextureFileName[i]でテクスチャー使用の有無を判定 PMDm.Texture = new Texture[PMDm.nMaterial]; for (var i = 0; i < PMDm.nMaterial; i++) { if (PMDm.TextureFileName[i] != "") { PMDm.Texture[i] = TextureLoader.FromFile(this._device, PMDm.TextureFileName[i]); } else { PMDm.Texture[i] = null; } } //背景サーフェイスを作成 this._surface = new NyARSurface_XRGB32(this._device, SCREEN_WIDTH, SCREEN_HEIGHT); this._is_marker_enable = false; return true; } //******************************************************************************************* //【メインループ処理】 //******************************************************************************************* public void MainLoop() { //ARの計算 lock (this) { // 背景サーフェイスを直接描画 Surface dest_surface = this._device.GetBackBuffer(0, 0, BackBufferType.Mono); Rectangle src_dest_rect = new Rectangle(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); this._device.StretchRectangle(this._surface.d3d_surface, src_dest_rect, dest_surface, src_dest_rect, TextureFilter.None); //【3Dオブジェクトの描画初期化】 this._device.BeginScene(); //【レンダリングのシーン作成開始】 this._device.Clear(ClearFlags.ZBuffer, Color.DarkBlue, 1.0f, 0); //【フレームバッファ・クリア】Zバッファも含む //マーカーが見つかっていて、0.4より一致してたら描画する。 if (this._is_marker_enable && this._ar.getConfidence()>0.4) { this._device.SetStreamSource(0, this._vertexBuffer, 0); //【頂点バッファをデバイスのデータストリームにバインド】 this._device.VertexFormat = CustomVertex.PositionNormalTextured.Format; //【頂点バッファ書式をセット】 this._device.Indices = this._indexBuffer; //【インデックスバッファをセット】 this._device.RenderState.Lighting = true; //【光源計算処理を行う】 this._device.Lights[0].Enabled = true; //【光源設定を有効にする】 this._device.RenderState.CullMode = Cull.None; //【袖やスカートが透明にならないようにカーリングを行わない】 Matrix transform_mat2 = Matrix.Translation(0.0f, -250.0f, 0.0f); //【PMDのマーカーからの平行移動距離を設定】 //【PMDをx軸回りに回転表示させる処理】 Matrix transform_rot3 = Matrix.RotationYawPitchRoll( ( rot3 / 180.0f) * 3.141592f, 0.0f, 0.0f); rot3 += 2.0f; //【2°ずつ回転させる。1回転は180フレーム】 if (rot3 >= 360.0f) { rot3 = 0.0f; SkinNumber++; //【1周ごとに表情を変更】 if (SkinNumber >= PMDs.nSkin) { SkinNumber = PMDs.nSkin; } //【baseは表情ではないので表情番号1から】 } SkinEffect = (float)((int)rot3 % 180)/180.0f; //【表情を表示してみる】 if (rot3 > 180.0f && rot3 <= 270.0f) { SkinEffect = 0.0f; } if (rot3 > 270.0f && rot3 <= 360.0f) { SkinEffect = 1.0f; } //【表情関連処理】 InitializeFacialExpression(); //【表情baseデータを表情ベクトルバッファSkinSetData.Positionに初期化】 if (SkinNumber < PMDs.nSkin) { transform_mat2 *= this._trans_mat; //【表情デモ用】【座標変換マトリックスを計算】 SetFacialExpression(SkinNumber, SkinEffect); //【すべての表情をデモ】 } else { //【にっこりと笑う】 transform_mat2 *= transform_rot3 * this._trans_mat; //【回転デモ用】【座標変換マトリックスを計算】 SkinEffect = 0.4f; SetFacialExpression("にこり", SkinEffect); //【表情】 3:(眉) SkinEffect = 1.0f; SetFacialExpression("笑い", SkinEffect); //【表情】 8:(眼) SkinEffect = 0.35f; SetFacialExpression("あ", SkinEffect); //【表情】19:(リップ) } TransferFacialExpression(); //【表情ベクトルをPMDv.OriginalVertexに転送】 //【ここにBone制御処理を入れる】 //●OriginalVertexから座標変換した結果を、UserVertexに入れる //********************************************************************************** for (var i = 0; i < PMDv.nPoints; i++) { PMDv.UserVertex[i].Position = PMDv.OriginalVertex[i].Position; //【Bone操作なしなのでそのままコピーする】 PMDv.UserVertex[i].Normal = PMDv.OriginalVertex[i].Normal; //【Bone操作なしなのでそのままコピーする】 PMDv.UserVertex[i].Tu = PMDv.OriginalVertex[i].Tu; //【そのままコピーする】 PMDv.UserVertex[i].Tv = PMDv.OriginalVertex[i].Tv; //【そのままコピーする】 } //********************************************************************************** //【Bone制御】ここまで this._device.SetTransform(TransformType.World, transform_mat2); //【上記マトリックスで座標変換を行う】 if (PMDm.SphereFlag) { //【Sphere Mappingを使っているモデル】 for (var i = 0; i < PMDv.nPoints; i++) { PMDv.UserVertex2[i] = PMDv.UserVertex[i]; } //【Sphere Mapping用にコピー】 int j = 0; int k; int sw; //【Traverse関連】[sw] 0:通常 1:テクスチャー 2:Sphere Matrix matWorld, matView, matWV; //【SphereMap関連】 float m11, m12, m13, m21, m22, m23, m31, m32, m33; //【SphereMap関連】 float nx, ny, nz; //【SphereMap関連】 for (int i = 0; i < PMDm.nMaterial; i++) { this._device.Material = PMDm.Material[i]; k = PMDm.nPoints[i]; this._device.SetTexture(0, null); sw = 0; if (PMDm.TextureFileName[i] != "") { sw = 1; if (PMDm.SphereMap[i]) { sw = 2; } } switch (sw) { case 0: //【Polygon Fill】通常のポリゴン描画 this._device.SetTexture(0, null); //【頂点バッファをロックする】 //●インデックスバッファは途中で変更することがないので初期化時にのみ転送 //●頂点バッファは、表情/Bone処理で頂点座標を、Sphere MappingでTu,Tvを変更するのでMainLoop内で転送 using (GraphicsStream data = this._vertexBuffer.Lock(0, 0, LockFlags.None)) { data.Write(PMDv.UserVertex); // 頂点データを頂点バッファにコピーします this._vertexBuffer.Unlock(); // 頂点バッファのロックを解除します } break; case 1: //【Texture Mapping】 this._device.SetTexture(0, PMDm.Texture[i]); //【頂点バッファをロックする】 //●インデックスバッファは途中で変更することがないので初期化時にのみ転送 //●頂点バッファは、表情/Bone処理で頂点座標を、Sphere MappingでTu,Tvを変更するのでMainLoop内で転送 using (GraphicsStream data = this._vertexBuffer.Lock(0, 0, LockFlags.None)) { data.Write(PMDv.UserVertex); // 頂点データを頂点バッファにコピーします this._vertexBuffer.Unlock(); // 頂点バッファのロックを解除します } break; case 2: //【Sphere Mapping】 this._device.SetTexture(0, PMDm.Texture[i]); // 現在のワールドビュー座標を取得 matView = this._device.GetTransform(TransformType.View); matWorld = this._device.GetTransform(TransformType.World); matWV = matWorld * matView; // 処理速度を上げるためにワールドビュー座標の要素を抽出 m11 = matWV.M11; m21 = matWV.M21; m31 = matWV.M31; m12 = matWV.M12; m22 = matWV.M22; m32 = matWV.M32; m13 = matWV.M13; m23 = matWV.M23; m33 = matWV.M33; // 各頂点に対してをループし、トランスフォームと正しいテクスチャ座標の計算を行う int jj; for (int ii = j; ii < (j + k); ii++) { jj = ii % PMDv.nPoints; //【VertexBufferを何回もなめているようだ。MMDはAlphaBlendしてる?】 nx = PMDv.UserVertex[jj].Normal.X; ny = PMDv.UserVertex[jj].Normal.Y; nz = PMDv.UserVertex[jj].Normal.Z; // zコンポーネントをチェックして後ろ側を向いている頂点をスキップ if (nx * m13 + ny * m23 + nz * m33 > 0.0f) { // スフィアマップのテクスチャ座標を割り当てる PMDv.UserVertex2[jj].Tu = 0.5f * (1.0f + (nx * m11 + ny * m21 + nz * m31)); PMDv.UserVertex2[jj].Tv = 0.5f * (1.0f - (nx * m12 + ny * m22 + nz * m32)); } } //【頂点バッファをロックする】 //●インデックスバッファは途中で変更することがないので初期化時にのみ転送 //●頂点バッファは、表情/Bone処理で頂点座標を、Sphere MappingでTu,Tvを変更するのでMainLoop内で転送 using (GraphicsStream data = this._vertexBuffer.Lock(0, 0, LockFlags.None)) { data.Write(PMDv.UserVertex2); // 頂点データを頂点バッファにコピーします this._vertexBuffer.Unlock(); // 頂点バッファのロックを解除します } break; } this._device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, PMDv.nPoints - 1, j, k); j += k; } } else //【通常のポリゴン・フィルとテクスチャーマッピングだけのモデル】 { //【頂点バッファをロックする】 //●インデックスバッファは途中で変更することがないので初期化時にのみ転送 //●頂点バッファは、表情/Bone処理で頂点座標を、Sphere MappingでTu,Tvを変更するのでMainLoop内で転送 using (GraphicsStream data = this._vertexBuffer.Lock(0, 0, LockFlags.None)) { data.Write(PMDv.UserVertex); // 頂点データを頂点バッファにコピーします this._vertexBuffer.Unlock(); // 頂点バッファのロックを解除します } int j = 0; int k; //【Traverse関連】 for (int i = 0; i < PMDm.nMaterial; i++) { this._device.Material = PMDm.Material[i]; k = PMDm.nPoints[i]; if (PMDm.TextureFileName[i] != "") { this._device.SetTexture(0, PMDm.Texture[i]); } else { this._device.SetTexture(0, null); } this._device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, PMDv.nPoints - 1, j, k); j += k; } } } this._device.EndScene(); //【レンダリングのシーンここまで】 this._device.Present(); //【実際のディスプレイに描画】 } return; } // リソースの破棄をするために呼ばれる public void Dispose() { lock (this) { // 頂点バッファを解放 if (this._vertexBuffer != null) { this._vertexBuffer.Dispose(); } // インデックスバッファを解放 if (this._indexBuffer != null) { this._indexBuffer.Dispose(); } if (this._surface != null) { this._surface.Dispose(); } // Direct3D デバイスのリソース解放 if (this._device != null) { this._device.Dispose(); } } } } } |