[PR] マンスリーマンション

DirectXにおける2D描画

実はヘルプを見れば問題なし。

はじめに

DirectXの初期化云々などの話はgoogle先生に譲るとして。

ここではDirect3Dを用いて2D描画を行う時の罠、「ピクセルの不一致」を紹介します。

問題になっている事


ディスプレイ座標にトランスフォーム済み頂点を描画することを考えます。
その際、DirectXのポリゴンラスタライズルールが影響して、ピクセル単位で綺麗に2D画像が表示されません。
これがちょっと曲者で、ドット絵等の精密な画素表示が要求されるリソースがぼやけて表示されてしまう可能性があります。

実は冒頭でも述べたように、DirectXSDKに付属してくるヘルプに同じ問題が取り扱われています。
上記題名ではなく、DirectGraphicsのテクスチャカテゴリに
「テクセルとピクセル間の直接マッピング(DirectX8.1bSDK)」
もしくは
Rasterization Rules (Direct3D 9)
があるので、参照してみてください。


どうすれば解決できるか?

ディスプレイ座標に直接テクセルをマッピングする場合は、

「描画座標(ポリゴンの頂点座標)のX,Yへ -0.5f を足す」

即ち、トランスフォーム済み頂点をラスタライズする際の頂点座標、x, yそれぞれに対し -0.5f を足して(0.5引く)きれいに表示することができます。


それでもダメな場合

実はこの問題は原因の候補が一杯あります。
原因の候補は以下のとおりです。

1.D3DCreateTextureFromFileExにて、サイズを明示的に指定しない場合、テクスチャがサンプリングされてぼやけます
2.SetSamplerStateのフィルタ設定がPOINTになっていない
3.デバイス作成時にマルチサンプルが有効になっている。
4.グラボの特性
5.実は読み込むテクスチャが既にぼやけている状態で描画されている

たぶん他にもあるはず。
上記一つだけが原因ではなく、組み合わされて発生している場合も…。
面倒でも一つ一つ見ていきましょうか。


D3DCreateTextureFromFileExにて、サイズを明示的に指定しない場合

DirectX9なら、面倒でも以下のようにサイズを取得してから(もしくはリソースのサイズが分かるなら決めうち)で
作成するテクスチャのサイズを明示的に指定してください。
指定しない場合、2べきのサイズに丸められてぼやけたりリサイズされてしまいます。

サンプルソース
//
// LPDIRECT3DTEXTURE9をファイルから作成する
//
LPDIRECT3DTEXTURE9 CreateTextureFromFile(LPSTR szName)
{
  HRESULT             hRet;
  
  //戻り値のテクスチャ
  LPDIRECT3DTEXTURE9  lpTex = NULL;
  if(lpD3DDevice) {

    //D3DXGetImageInfoFromFileで使用する画像ファイルの情報
    D3DXIMAGE_INFO      iinfo;
    
    //画像ファイルの情報を取得
    hRet = D3DXGetImageInfoFromFile(szName, &iinfo);
    if( hRet == D3D_OK)
    {
      //テクスチャをファイルから作成
      hRet = D3DXCreateTextureFromFileEx(
        lpD3DDevice,
        szName,
        iinfo.Width, iinfo.Height, //★取得した画像ファイルのサイズを指定する
        1, 0,
        D3DFMT_A8R8G8B8,
        D3DPOOL_MANAGED,
        D3DX_FILTER_NONE,
        D3DX_FILTER_NONE,
        0xFF000000,
        NULL, NULL,
        &lpTex);
    }
  }
  return lpTex;
}
重要なのはD3DXGetImageInfoFromFileで画像のサイズを取得して設定すること。
他のカラーキーだったりフィルタ指定は
それぞれの用途にしたがって設定してください。

※2009/09/20 : Twitterとかいろいろ見てて追記
グラボの種類、もしくはデバイス作成時にPURE_DEVICEで作成していると2べきの「長方形」だとだめな場合があるようです。
なので確実に生成したいリソースは「正方形(256x256, 512x512…)」で作成したほうが良さそうです。

SetSamplerStateのフィルタ設定がPOINTになっていない場合

サンプリングがLINEARになっている場合にぼやけが発生します。
ドライバやDirectXを使用するアプリ実行後によっては状態が元に戻らない場合があり、
その場合でもぼやけてしまいます。
面倒でも描画前にSetSamplerStateでポイントサンプリングしてください。

hRet = lpD3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
hRet = lpD3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
hRet = lpD3DDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);

毎回コールするとパフォーマンスが落ちるので一度に描画する(バッチ処理)など工夫してみてください。
stl::vectorで頂点を蓄えて一気に描画後、.clear()でもいい感じです。
ただしこれも場合によります。
デバイス消失後とかも気をつけること。

デバイス作成時にマルチサンプルが有効になっている場合

DirectXの初期化時にデバイスを作成しています。
その際に、デバイスの特性を定義するD3DPRESENT_PARAMETERS の メンバ、MultiSampleTypeに
D3DMULTISAMPLE_NONEが指定されていない場合、ビデオカードの性能によっては
ポリゴンの淵が滑らかになるようにサンプリングされてぼやけます。
くっきり表示することが目的ならば、初期化時にD3DMULTISAMPLE_NONEを設定してDirectXの初期化を行ってください。

サンプル

D3DPRESENT_PARAMETERS d3dpp;
:
:
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
lpD3D->CreateDevice(...);

まとめ

べたな手順は以下のとおりとなります。

0) DirectXの初期化時、MultiSampleTypeにD3DMULTISAMPLE_NONEを設定して初期化する
1) D3DXCreateTexture系でテクスチャを読み込む場合サイズを必ず指定する。
2) SetSamplerStateでD3DTEXF_POINTを使用してポイントサンプリングを行うようにする
3) ポリゴンをXYZW(トランスフォーム済み)で作成する
4) 3)で作ったポリゴンのx, y座標を0.5引く
5) DrawPrimitiveUPで描画する(これはどれでもOK)

4)の手順が面倒ですが、ゲームなどで使用するなら、キャラクタから受け取ったx, y座標をあらかじめ0.5引けば
ポリゴン全部に適用できます(当たり前か…)

やっぱりダメな場合

割り切るか、最初から状態を追ったりして原因を解析してください。
特定の環境でしか再現しないかもしれないので、不毛な解析にならないようにしないと。
→ぼやけることよりも他に問題が一杯出るんだよな…

いまはSDKにDirectXの描画の状態を解析するPixもあるので便利。


再掲になりますが、もともとの読み込んだ画像がぼやけてたら意味が無いので再度確認すること。

Back

2009/05/25 Gyabo