スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

DirectX 描画方法まとめ(コードもあるよ)

【リンク】 ◆解説系TOP◆

今日はプログラミングのお話。

DirectXでは、ポリゴンなどを描画する関数は、
---------------------------------------------------
・DrawPrimitiveUP 関数
・DrawIndexedPrimitiveUP 関数
・DrawPrimitive 関数
・DrawIndexedPrimitive 関数

---------------------------------------------------
上記の4つ があります。
(まだ他にもあるかも?)


で、自分はその中のDrawPrimitiveUP関数しか使っておりませんでした。
(むしろ、描画 = DrawPrimitiveUP関数だと思ってた。)

さらに、最後にUPがつく関数は、
(DrawPrimitiveUP 関数 、DrawIndexedPrimitiveUP 関数 )
----------------------------------------------------------------------
バッファの作成 → 頂点データコピー → 描画 → バッファの開放
----------------------------------------------------------------------
毎回毎回このような手順を踏むため、遅いとの指摘を頂きました。
( そういえば、以前 "立体倉庫番"を作ったのですが 微妙に動きがカクカクしてました。
 絶対コレのせいだ・・・ Σ(・ω・`||)  )


まぁ、という訳で これからは快適な描画をする為に、
先日上記の4つの関数を理解し、実際にプログラムを組みました。

今日は、その4つの関数の使い方実際のソースコードを書きます。



DrawPrimitiveUP 関数


この関数を使う場合は、下記の様な流れになります。
-----------------------------------------
【1】頂点データの構造体を定義。
【2】構造体型の変数にデータを入れる。
【3】SetFVF関数を呼ぶ。
【4】DrawPrimitiveUP関数を呼ぶ。

-----------------------------------------

では、実際のコード。

【1】頂点データの構造体を定義する。
struct MY_VERTEX{
float px, py, pz;
DWORD color;
};
頂点座標頂点の色を持つ 頂点データです。)

【2】構造体型の変数(配列)に、データを入れる。
MY_VERTEX v1[] = {	{0, 2, 0, 0xffff0000},  //赤色
{2, 2, 0, 0xff00ff00}, //緑色
{0, 0, 0, 0xff0000ff}, //青色
{2, 0, 0, 0xffffffff},}; //白色

【3】SetFVF関数を呼ぶ。
g_pD3DDevice->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
※頂点フォーマットの内容を伝達する。
(「描画しようとしているのは、頂点座標頂点の色を持つデータですよ」、みたいな。)


※補足※
"D3DFVF_XYZ"等は、オブジェクト形式マクロです。
オブジェクト形式マクロ・・・特定の文字列を置換する。
-------------------------------------------
#define D3DFVF_XYZ 0x002
#define D3DFVF_DIFFUSE 0x040

-------------------------------------------
ヘッダファイル内では 上記のようになっているので、
それぞれ16進数の 0x002、0x040に置換されます。

また、SetFVF関数を呼ぶときにD3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_SPECULAR ・・・
と言う風にすると長くなるので、 大抵は更にオブジェクト形式マクロを使って簡便化します。
#define MY_FVF  (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_SPECULAR)
//中略//
g_pD3DDevice->SetFVF(MY_FVF);

【4】DrawPrimitiveUP関数を呼ぶ。

g_pD3DDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v1, sizeof(MY_VERTEX);


【具体的な引数の意味】
HRESULT DrawPrimitiveUP(
D3DPRIMITIVETYPE PrimitiveType,
UINT PrimitiveCount,
const void *pVertexStreamZeroData,
UINT VertexStreamZeroStride
);


PrimitiveType・・・プリミティブタイプ(頂点を結びつける順番を決めたりする)を指定する。
PrimitiveCount・・・三角ポリゴンの数。
*pVertexStreamZeroData・・・頂点データのポインタを渡す。  (今回は、【2】で定義した、v1を渡す)
VertexStreamZeroStride・・・頂点データ間のストライドを指定。

※補足※
4番目の引数のStride(ストライド)というのは"幅"という意味です。
今回の頂点と頂点色の配列を定義すると、メモリ上は下記の様になるのですが、

【参考画像】
Stride.jpg

この頂点データの先頭(px)から先頭までの幅がStrideになります。
要するに、sizeofで構造体の大きさを渡してやればOKです。

【ソースコード】Sample_PrimitiveUP.txt




DrawIndexedPrimitiveUP 関数


この関数を使う場合は下記の手順を踏みます。
-----------------------------------------
【1】頂点データの構造体を定義。
【2】インデックスの構造体を定義
【3】構造体型の変数にデータを入れる。
【4】SetFVF関数を呼ぶ。
【5】DrawIndexedPrimitiveUP関数を呼ぶ。

-----------------------------------------
※【1】、【3】、【4】は全く同じなので省略

【2】インデックスの構造体を定義
インデックスの構造体を定義します。
インデックスというのは、結ぶ頂点の順番を決めるための頂点番号の配列だと思ってください。

サンプルコードでは下記の様に定義してます。

WORD index[] = { 1, 2, 3, 0, 1, 2, 0, 1, 2, 3};
 
WORD型(unsigned short型)の一次元配列に なっており、
---------------------------------------
index[0]~index[2] までをインデックスA
index[3]~index[5] までをインデックスB
index[6]~index[9] までをインデックスC
---------------------------------------
として利用します。
※何故 「WORD型」の配列なのか?は後で説明します。

また、
【参考画像】
頂点
今回は 上記の様に4つの頂点で四角ポリゴンを定義しているので、
それぞれのインデックスを使った場合の描画されるポリゴンは下記の様になります。

【参考画像】
頂点2

【5】DrawIndexedPrimitiveUP関数を呼ぶ。
頂点データ、インデックス、そしてSetFVF関数の呼び出しが終わった後、
この関数を呼びます。

g_pD3DDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLESTRIP, 1, 3, 1, index, D3DFMT_INDEX16,v1,sizeof(MY_VERTEX));


【具体的な引数の意味】
HRESULT DrawIndexedPrimitiveUP(
D3DPRIMITIVETYPE PrimitiveType,
UINT MinVertexIndex,
UINT NumVertices,
UINT PrimitiveCount,
CONST void * pIndexData,
D3DFORMAT IndexDataFormat,
CONST void* pVertexStreamZeroData,
UINT VertexStreamZeroStride
);


PrimitiveType・・・プリミティブタイプ(頂点を結びつける順番を決めたりする)を指定する。
MinVertexIndex・・・最小のインデックス番号を指定します。 インデックスAは1(番)、Bは 0(番)、Cは 0(番)になります。
NumVertices・・・インデックスにより表示されるポリゴンの頂点の数を指定。インデックスAは3Bは 3Cは 4になります。
PrimitiveCount・・・三角ポリゴンの数を指定します。インデックスAは1Bは1Cは 2になります。
pIndexData・・・インデックス配列の(先頭の)ポインタを指定します。インデックスAは &index[0]Bは &index[3]Cは &index[6]になります。
ndexDataFormat・・・インデックスデータのフォーマットを定義します。有効なのはD3DFMT_INDEX16 と D3DFMT_INDEX32のみ。
pVertexStreamZeroData・・・頂点データのポインタを渡します。 今回はv1と指定。 (v1・・・頂点データの配列に与えた名前。)
VertexStreamZeroStride・・・頂点データ間のストライドを指定します。 "ストライド"の詳細は前述した通りです。


※補足※
ndexDataFormatで指定できるのは、 D3DFMT_INDEX16D3DFMT_INDEX32のみで、
それぞれ、「16 ビットですよ」「32ビットですよ」という意味です。

今回、インデックスの配列にWORD型を用いたのは、
上記の2種類の中からしか指定出来ないようになっていたからです。
WORD型は2バイト(16ビット)。)

【ソースコード】
Sample_IndexedPrimitiveUP.txt
※↑→← キーで、インデックスA,B,Cを切り替えられるようになってます。



DrawPrimitive 関数

この関数を使う場合は、下記の様な手順になります。
-----------------------------------------
【1】頂点データの構造体を定義。
【2】CreateVertexBuffer関数で、 バッファを作成。
【3】Lock関数を呼ぶ
【4】頂点データを入れて行く。
【5】Unlock関数を呼ぶ。
【6】SetStreamSource関数を呼ぶ

【7】SetFVF関数を呼ぶ。
【8】DrawPrimitive関数を呼ぶ。
【9】バッファの開放。 ←※重要!

-----------------------------------------
~UP関数より ちょっと手順が多いですが、一つ一つ確認していけば大丈夫でしょう。
(逆に言えば、~UP関数は上記の青字の部分の処理を隠蔽してくれているのです。

では、同様に青字の部分のみに絞って解説をば。


【2】CreateVertexBuffer関数を呼ぶ。

LPDIRECT3DVERTEXBUFFER9 g_vb = NULL;
 
g_pD3DDevice->CreateVertexBuffer(
4*sizeof(MY_VERTEX),
D3DUSAGE_WRITEONLY,
MY_VERTEX_FVF,
D3DPOOL_MANAGED,
&g_vb,
NULL);

まずCreateVertexBuffer関数を呼び、バッファを作成します。
※この関数を呼ぶ前にLPDIRECT3DVERTEXBUFFER9型の変数頂点データの構造体
定義しておく必要があります。

【具体的な引数の意味】
HRESULT CreateVertexBuffer(
UINT Length,
DWORD Usage,
DWORD FVF,
D3DPOOL Pool,
IDirect3DVertexBuffer9** ppVertexBuffer,
HANDLE* pHandle
);


Length・・・頂点バッファのサイズを指定。頂点の数×頂点データの構造体のサイズを指定すればOKです。 ※上記参照。
Usage ・・・使用方法の指定をする。上記の例では"D3DUSAGE_WRITEONLY"(バッファへの書込み操作しかしませんよ、の意)を指定。
FVF ・・・頂点フォーマットを指定。 頂点フォーマットについては、一番上の方の"オブジェクト形式マクロ"の部分に詳しく書いてあります。
Pool・・・バッファを保持するメモリ クラスをD3DPOOL 列挙型で指定する。
ppVertexBuffer ・・・作成した頂点バッファへのポインタのアドレスを指定。(LPDIRECT3DVERTEXBUFFER9型の変数のアドレスを渡す。)
pHandle・・・このパラメータは常にNULLでOKとのこと。
※補足※
Usage(使用法)とD3DPOOL 列挙型については下記に詳しく書いてあります。
【参考URL】D3DUSAGE
【参考URL】D3DPOOL 列挙型


【3】Lock関数を呼ぶ
バッファの作成が終わったら、CreateVertexBuffer関数に渡した
LPDIRECT3DVERTEXBUFFER9型の変数で Lock関数を呼びます。

MY_VERTEX* v;
g_vb->Lock( 0, 0, (void**)&v, 0);


Lock関数は、定義したい頂点データへのポインタのアドレス(上記のv)を
(void**)にキャストして渡します。

そして、他の引数は0で問題ないです。
(他の値を設定する事も出来ますが、特別な意図が無い限り0固定でOKかと。)
【参考URL】IDirect3DVertexBuffer9::Lock メソッド

また、Lock関数は
頂点バッファへ書き込み中にシステムが勝手にメモリを移動させたりしないようにする為に、
呼び出す必要があります。


【4】頂点データを入れて行く。
v[0].px = 0;	v[0].py = 2;	v[0].pz = 0;	v[0].color = 0xffff0000;
v[1].px = 2; v[1].py = 2; v[1].pz = 0; v[1].color = 0xff00ff00;
v[2].px = 0; v[2].py = 0; v[2].pz = 0; v[2].color = 0xff0000ff;
v[3].px = 2; v[3].py = 0; v[3].pz = 0; v[3].color = 0xffffffff;

Lock関数の呼び出しが終わったら、Lock関数に渡した頂点データの構造体のポインタを使って
データを定義していきます。

上記は、頂点座標と、頂点色を持っている構造体の場合の例です。


【5】Unlock関数を呼ぶ。
g_vb->Unlock();

頂点データの定義が終わった後、Unlock関数を呼びます。
(先ほどLock関数を呼んだLPDIRECT3DVERTEXBUFFER9型の変数から呼びます。)

引数は無いので単純に呼ぶだけです。
また、Unlock関数はLockした場所を解除する関数です。


【6】SetStreamSource関数を呼ぶ
先ほどまでバッファの作成を行う処理だったわけですが、
バッファを作成しただけでは描画出来ません。

「このバッファを描画対象としてセットしますよ」
デバイス側に通知する必要があります。 それがこのSetStreamSource関数です。


↓コード例
g_pD3DDevice->SetStreamSource( 0, g_vb, 0, sizeof(MY_VERTEX) );


【具体的な引数の意味】
HRESULT SetStreamSource( 
UINT StreamNumber,
IDirect3DVertexBuffer9 *pStreamData,
UINT OffsetInBytes,
UINT Stride
);


StreamNumber・・・データ ストリームの番号を指定する。
pStreamData・・・セットする頂点バッファを指定。(Lock関数などを呼び出したIDirect3DVertexBuffer9型の変数を渡す。)
OffsetInBytes・・・頂点データ先頭までのオフセット (バイト単位)を指定。 今回の構造体は、構造体の先頭 = 頂点データの先頭なので、オフセット(差分)はありません。
Stride・・・ストライド(一番上の方で説明済)を指定します。 sizeofを使って構造体の大きさを渡してやればOKです。


※補足※
データストリームについて。
まずDirectXでは、レンダリングパイプラインを通じて描画を行っています。
レンダリングパイプライン・・・座標の計算や、色を塗る等の加工を行う 一連の処理手順。

で、そのレンダリングパイプラインにデータを流し込む道のようなもの
複数 用意されているのです。
その道のようなものがデータストリームであり、図にすると下記の様な感じになります。

【参考画像】
レンダリングパイプライン

【8】DrawPrimitive関数を呼ぶ。
バッファの作成と、バッファのセット、
SetFVF関数の呼び出しが終われば、いよいよ描画です。
g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,4);


【具体的な引数の意味】
HRESULT DrawPrimitive(
D3DPRIMITIVETYPE PrimitiveType,
UINT StartVertex,
UINT PrimitiveCount
);


PrimitiveType・・・プリミティブタイプ(頂点を結びつける順番を決めたりする)を指定する。
StartVertex・・・描画を開始する最初の頂点の番号。
PrimitiveCount・・・三角ポリゴンの数。


引数が3つですし、簡単ですね。

通常はポリゴン全てを描画すると思いますが、2,3番目の引数を変更して
途中から描画したり最初から途中までを描画したりする事ができます。

【9】バッファの開放。 ←※重要!
最後は確保したバッファを開放します。
g_vb->Release();
g_vb = NULL;

※NULLを入れているのは、
開放した後バッファが開放されて何も無い場所にアクセスするのを防ぐためです。
(バッファに限らず、ポインタは使用前・後にNULLを入れたほうが良いでしょう。)

関係ないですが、自分は初め解放し忘れていた為、
------------------------------
・別ウィンドウが開かなくなる
・ペイントが起動出来なくなる

------------------------------
など酷い目に遭いました。

【ソースコード】Sample_DrawPrimitive.txt
↑・・・ポリゴンの最初から途中まで
→・・・ポリゴン全部
↓・・・ポリゴンの途中から最後まで

※上記のように、押したキーによって描画方法を分岐させてます。


DrawIndexedPrimitive 関数

この関数を使う際は、大体 下記の手順になります。
-----------------------------------------
【1】頂点データの構造体を定義。
【2】CreateVertexBuffer関数で、 頂点バッファを作成。
【3】Lock関数を呼ぶ
【4】頂点データを入れて行く。
【5】Unlock関数を呼ぶ。
【6】CreateIndexBuffer関数で、インデックスバッファを作成。
【7】Lock関数を呼ぶ
【8】インデックスのデータを入れて行く。
【9】Unlock関数を呼ぶ。

【10】SetStreamSource関数を呼ぶ。
【11】SetIndices関数を呼ぶ。
【12】SetFVF関数を呼ぶ。
【13】DrawIndexedPrimitive関数を呼ぶ。
【14】バッファの開放。 ←※重要!

-----------------------------------------

正直、この関数 めっさ面倒くさいです。

まぁでも さっきのDrawPrimitive関数の手順から
インデックスバッファの作成インデックスバッファのセットが増えただけです。

Lockの部分とかも同じなので、【11】と【13】に絞って説明します。

【11】SetIndices関数を呼ぶ。
Direct3DIndexBuffer9型の変数でインデックスバッファを作成した後、
頂点バッファと同様、インデックスバッファもセットする必要があります。
(このインデックスバッファを使いますよ、みたいな)
g_pD3DDevice->SetIndices(g_ib);
※単純に変数を渡せば終わりです。

【13】DrawIndexedPrimitive関数を呼ぶ。
この関数がまた面倒くさい。
後半に、5つの値を引数として渡すので、
何が何の値なのか分かりづらい のです。
g_pD3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLESTRIP,0,0,4,0,1);


【具体的な引数の意味】
HRESULT DrawIndexedPrimitive(          
D3DPRIMITIVETYPE Type,
INT BaseVertexIndex,
UINT MinIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount
);

PrimitiveType・・・プリミティブタイプ(頂点を結びつける順番を決めたりする)を指定する。
BaseVertexIndex・・・インデックス バッファの開始地点から最初のインデックスまでのオフセット(差分)。※通常は0で良いです。
MinIndex・・・描画で使用される最小の頂点番号。
NumVertices・・・最小インデックスから、最大インデックスまでの数。(頂点0 1 2の場合は0~2なので3。 頂点0 1 3の場合は0~3なので4。
StartIndex・・・インデックス配列の読み取りを開始する位置。
PrimitiveCount・・・三角ポリゴンの数。

サンプルでは、インデックスの一次元配列にインデックスA~Eを纏めて入れてます。↓
-------------------------------------------------------------------
i[0] = 0; i[1] = 1; i[2] = 3; //インデックスA
i[3] = 0; i[4] = 1; i[5] = 2; //インデックスB
i[6] = 0; i[7] = 3; i[8] = 2; //インデックスC
i[9] = 1; i[10] = 3; i[11] = 2; //インデックスD
i[12] = 0; i[13] = 1; i[14] = 2; i[15] = 3;//インデックスE

-------------------------------------------------------------------
そして、このA~Eインデックスを
十字キーとエンターキーで切り替えられるようになってますので、
上手く描画されているか確認してみてください。

ポリゴンの方は、頂点0番~3番で構成される単純な正方形のポリゴンです。
(既に上の方に 画像は貼ってあります)

【ソースコード】Sample_IndexedPrimitive.txt

長かったですが、以上!
まとめ -完-
スポンサーサイト

テーマ : ゲーム - ジャンル : ゲーム

コメント

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます

No title

返信が遅れました。申し訳ないです。

確かに、見返してみたら白地 + カラフル で目がチカチカとしますねv-292

無駄に色分けしても見づらくなりそうなので、
ちょっと記載方法を考えて見ます。v-293
(そもそも、プログラム関連の記事も もう1回纏めなおしたいな、と思っています)

No title

この記事見ながらMqoメッシュの表示をDrawIndexedPrimitiveですることが出来ました~v-290
分かりやすい記事ありがとうございます。

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます
コメントの投稿
管理者にだけ表示を許可する



上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。