04、線とスプライト表示



04章では線とスプライトの表示を実装します。
線はジョイントとジョイントの間にボーンのマークを描画するのに使います。
スプライトはジョイントの位置に丸マークを描画するのに使います。
この他、FileのOpenメニューの実装や
ボーンのあるデータと無いデータの表示の自動切換えの実装などもします。



ソースは下のページからダウンロードしてください。
OpenRDBダウンロードページ

04−01、ボーンのあるデータと無いデータ

ボーンの無いデータとあるデータは表示方法を変えるのが普通です。
ボーンのあるデータの表示については
これまでの章で解説しました。

ボーンの無いデータはもっと単純です。
declは以下のようになります。

D3DVERTEXELEMENT9 declnobone[] = {
//pos[4]
{ 0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },

//normal[3]
{ 0, 16, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 },

//uv
{ 0, 28, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },

D3DDECL_END()

};


これはつまり
ボーン有りの場合はPM3DISPVとPM3INFバッファの2つを組み合わせて頂点データを渡していたのを
ボーンなしの場合はPM3DISPVだけにすれば良いだけです。

ボーンの有り無しでバッファ全体を作り直す必要がないのは
複数ストリームを使ったおかげです。

C++での表示部分は

if( m_hasbone ){
m_pdev->SetVertexDeclaration( m_declbone );
m_pdev->SetStreamSource( 0, m_VB, 0, sizeof(PM3DISPV) );
m_pdev->SetStreamSource( 1, m_InfB, 0, sizeof(PM3INF) );
m_pdev->SetIndices( m_IB );
}else{
m_pdev->SetVertexDeclaration( m_declnobone );
m_pdev->SetStreamSource( 0, m_VB, 0, sizeof(PM3DISPV) );
m_pdev->SetIndices( m_IB );
}

hres = m_pdev->DrawIndexedPrimitive( D3DPT_TRIANGLELIST,
0,
0,
m_pm3->m_optleng,
currb->startface * 3,
curnumprim
);


のように、declの違いとストリーム指定が違うだけです。

ボーンなしのシェーダーは
座標変換の部分でボーンマトリックスを参照せずに
ワールド変換行列を参照して変換します。

プログラム中でボーンがあるかどうかを確かめる手段は
CModel::m_topbonenumが0であるかどうかを見ることです。


04−02、線

線の表示もdeclを定義し、シェーダーを書いてDrawIndexedPrimitiveをD3DPT_LINELISTタイプで呼び出すという
三角の描画の仕方とほとんど変わりません。

declは以下のようになります。

D3DVERTEXELEMENT9 declline[] = {
//pos[4]
{ 0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },

D3DDECL_END()

};



線の頂点は位置だけにします。
色はdiffuse用の定数を使います。

シェーダーは

struct VS_LINEOUTPUT
{
float4 Position : POSITION; // vertex position
float4 Diffuse : COLOR0; // vertex diffuse color (note that COLOR0 is clamped from 0..1)
};

VS_LINEOUTPUT RenderSceneLineVS( float4 vPos : POSITION )
{
VS_LINEOUTPUT Output;
float4 wPos;

float4x4 finalmat = g_mWorld;

wPos = mul( vPos, finalmat );
Output.Position = mul( wPos, g_mVP );


Output.Diffuse.rgb = g_diffuse.rgb;
Output.Diffuse.a = g_diffuse.a;

return Output;
}

PS_OUTPUT RenderScenePSLine( VS_LINEOUTPUT In )
{
PS_OUTPUT Output;
Output.RGBColor = In.Diffuse;
return Output;
}



のようになります。単純なので説明するまでもないですね。

OpenRDBでは1つのオブジェクトに線と三角が混ざっている場合にも対応します。
線と三角でCMQOObjectは分けませんが
CMQOObjectのCDispObjメンバに三角用のm_dispobjと線用のm_displineを持たせます。
このm_dispobjとm_displilneそれぞれに対して描画命令を呼びます。
オブジェクトに線が含まれていないときはm_displineを0にしておき
0の場合は描画命令を呼ばないようにします。


04−03、スプライト

D3DXにスプライト用の命令が用意されています。
しかしこれは何かと使い勝手が悪いので
スプライトクラスは自作しました。

スプライトなので2Dなの?と思うかもしれませんが
中身は3Dです。
頂点の座標指定の仕方がスクリーン座標系なだけで、
板のポリゴンにテクスチャを貼るという3Dの手法で実装します。

クラス名はCMySpriteでMySprite.hとMySprite.cppに記述しています。

メンバは以下のようです。

class CMySprite
{

...関数メンバ記述省略...

public:
LPDIRECT3DDEVICE9 m_pdev;
int m_texid;
SPRITEV m_v[4];
IDirect3DVertexDeclaration9* m_decl;

D3DXVECTOR3 m_pos;
D3DXVECTOR2 m_size;
D3DXVECTOR4 m_col;
};



テクスチャはCTexBankで作成し、作成したIDをm_texidに格納します。
m_v[4]は頂点データです。
この頂点の座標にはワールドビュープロジェクションの変換をした後の座標系で指定します。
つまりX,Yは中心が0.0で+1.0から-1.0の間の数値を取ります。
Zは画面の一番手前が0.0で、遠くて見えなくなる点が1.0になります。

declは

D3DVERTEXELEMENT9 decl[] = {
//pos[4]
{ 0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },

//uv
{ 0, 16, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },

D3DDECL_END()

};



のようになります。

描画部分は

int CMySprite::OnRender()
{
SPRITEV renderv[4];
int vno;
for( vno = 0; vno < 4; vno++ ){
renderv[vno].uv = m_v[vno].uv;
}

float maxx, minx, maxy, miny;
maxx = m_pos.x + 0.5f * m_size.x;
minx = m_pos.x - 0.5f * m_size.x;
maxy = m_pos.y + 0.5f * m_size.y;
miny = m_pos.y - 0.5f * m_size.y;

renderv[0].pos = D3DXVECTOR4( minx, maxy, m_pos.z, 1.0f );
renderv[1].pos = D3DXVECTOR4( maxx, maxy, m_pos.z, 1.0f );
renderv[2].pos = D3DXVECTOR4( maxx, miny, m_pos.z, 1.0f );
renderv[3].pos = D3DXVECTOR4( minx, miny, m_pos.z, 1.0f );


HRESULT hr;
hr = g_pEffect->SetValue( g_hdiffuse, &m_col, sizeof( D3DXVECTOR4 ) );
_ASSERT( !hr );


LPDIRECT3DTEXTURE9 disptex = 0;
if( m_texid >= 0 ){
map<int,CTexElem*>::iterator finditr;
finditr = g_texbank->m_texmap.find( m_texid );
if( finditr != g_texbank->m_texmap.end() ){
CTexElem* curte;
curte = finditr->second;
if( curte ){
disptex = curte->m_ptex;
}
}
}else{
disptex = 0;
}
hr = g_pEffect->SetTexture( g_hMeshTexture, disptex );
_ASSERT( !hr );


hr = g_pEffect->SetTechnique( g_hRenderSprite );
_ASSERT( hr == D3D_OK );
hr = g_pEffect->Begin( NULL, 0 );
_ASSERT( hr == D3D_OK );

hr = g_pEffect->BeginPass( 0 );
_ASSERT( hr == D3D_OK );

hr = m_pdev->SetVertexDeclaration( m_decl );
_ASSERT( hr == D3D_OK );

hr = m_pdev->DrawPrimitiveUP( D3DPT_TRIANGLEFAN
, 2, renderv, sizeof( SPRITEV ) );
_ASSERT( hr == D3D_OK );

hr = g_pEffect->EndPass();
_ASSERT( hr == D3D_OK );

hr = g_pEffect->End();
_ASSERT( hr == D3D_OK );


return 0;
}

のようになります。

m_posでスプライトの変換済み座標の中心点を指定している部分以外は
特に問題はないでしょう。

シェーダーは以下のようになります。

struct VS_SPRITEOUTPUT
{
float4 Position : POSITION; // vertex position
float4 Diffuse : COLOR0; // vertex diffuse color (note that COLOR0 is clamped from 0..1)
float2 TextureUV : TEXCOORD1; // vertex texture coords
};

VS_SPRITEOUTPUT RenderSceneSpriteVS( float4 vPos : POSITION,
float2 vTexCoord0 : TEXCOORD0 )
{
VS_SPRITEOUTPUT Output;
Output.Position = vPos;
Output.Diffuse.rgb = g_diffuse.rgb;
Output.Diffuse.a = g_diffuse.a;
Output.TextureUV = vTexCoord0;

return Output;
}

PS_OUTPUT RenderScenePSSprite( VS_SPRITEOUTPUT In )
{
PS_OUTPUT Output;
Output.RGBColor = tex2D(MeshTextureSampler, In.TextureUV) * In.Diffuse;
return Output;
}


頂点座標をシェーダーでは変換せずにそのまま返すところがミソです。
ピクセルシェーダーでテクスチャの色にdiffuseを掛けるので
テクスチャ色を明るくしたり暗くしたり、透明度を変えたり出来ます。


04−04、Fileメニュー

今までは読み込むデータは決め打ちでしたが
いろんなデータを読み込めるようにFileメニューを作りました。

まずresource.hに以下のように書きます。

#define IDR_MENU1 101
#define ID_FILE_OPEN40001 40001


そしてRokDeBoneOSS.rcに以下のように書きます。

IDR_MENU1 MENU
BEGIN
POPUP "File"
BEGIN
MENUITEM "Open", ID_FILE_OPEN40001
END
END



RDBMain.cppのWinMainでメニューを作成します。

s_mainmenu = LoadMenuW( (HINSTANCE)GetModuleHandle(NULL), MAKEINTRESOURCE(
      IDR_MENU1 ) );


そしてそれをDXUTに渡します。

DXUTCreateWindow( L"RokDeBoneOSS", 0, 0, s_mainmenu );


これでウインドウにメニューが表示されるようになります。

メニューを選択するとMsgProcコールバック関数が呼ばれるので
MsgProc内に

if( uMsg == WM_COMMAND ){
WORD menuid;
menuid = LOWORD( wParam );
if( menuid == ID_FILE_OPEN40001 ){
OpenMQOFile();
return 0;
}
}



のように書きます。
後はOpenMQOFile関数を書けば、メニュー選択時にファイルが読み込まれます。

アプリケーションの終了時にはメニューを削除するのを忘れずに!
OnDestroyDeviceで

if( s_mainmenu ){
DestroyMenu( s_mainmenu );
s_mainmenu = 0;
}


のように削除します。


オープンソースのトップに戻る

トップページに戻る