ホーム < ゲームつくろー! < DirectX10技術編
その7 Direct3D10でのテクスチャの作り方
テクスチャはポリゴンの表面に貼り付いて色を付けるバッファです。またシェーダの出力先(レンダーターゲットテクスチャ)もテクスチャの一つですし、画面に出ている絵(バックバッファ)もテクスチャの部類に入ります。
これらのテクスチャは色々な作り方があります。この章ではDirect3D10におけるテクスチャの作り方、またその相互変換について見ていく事にしましょう。
@ 画像ファイルからテクスチャを作成
多分最も簡単で使用頻度の高い方法が画像ファイルからテクスチャを作成する事かなと思います。これはD3DX10CreateTextureFromFile関数という専用の関数が用意されています:
D3DX10CreateTextureFromFile関数 HRESULT D3DX10CreateTextureFromFile(
ID3D10Device *pDevice,
LPCSTR pSrcFile,
D3DX10_IMAGE_LOAD_INFO *pLoadInfo,
ID3DX10ThreadPump *pPump,
ID3D10Resource **ppTexture,
HRESULT *pHResult
);
pDeviceは描画デバイスです。
pSrcFileは画像ファイルへのパスです。
pLoadInfoには画像ファイルを読み込む方法に関する情報を記述します。ここにNULLを入れると画像ファイルから情報を解析し単純なテクスチャが作成されます。
pPumpには画像読み込みをスレッドで行う場合のスレッドポンプオブジェクトを渡します。同期読みで良いならNULLでもOKです。
ppTextureには作成したテクスチャリソースを受け取るID3D10Resourceポインタのポインタを渡します。
pHResultはpPumpがNULLの場合はNULLで構いません。pPumpを使用している場合はpHResultは有効なメモリを指している必要があります。
ID3DX10ThreadPumpを用いた非同期読みについてはここでは割愛します。この関数を使って最も単純に画像ファイルからテクスチャを作るには以下のような呼び出し方をします:
ID3D10Resource *fileResource = 0;
D3DX10CreateTextureFromFileA( device, "sample01.png", 0, 0, &fileResource, 0 );
読み込みに成功した場合fileResourceに有効なポインタが返り、関数はS_OKを返します。
この関数はD3D10_IMAGE_FILE_FORMAT列挙型にあるファイルフォーマットに対応しています:
D3D10_IMAGE_FILE_FORMAT typedef enum D3DX10_IMAGE_FILE_FORMAT {
D3DX10_IFF_BMP = 0,
D3DX10_IFF_JPG = 1,
D3DX10_IFF_PNG = 3,
D3DX10_IFF_DDS = 4,
D3DX10_IFF_TIFF = 10,
D3DX10_IFF_GIF = 11,
D3DX10_IFF_WMP = 12,
D3DX10_IFF_FORCE_DWORD = 0x7fffffff,
} D3DX10_IMAGE_FILE_FORMAT, *LPD3DX10_IMAGE_FILE_FORMAT;
有効なID3D10Resourceから2Dテクスチャへは次のようにキャストすればOKです:
D3D10_RESOURCE_DIMENSION resDim;
fileResource->GetType( &resDim );
ID3D10Texture2D *fileResTex = 0;
if ( resDim == D3D10_RESOURCE_DIMENSION_TEXTURE2D ) {
fileResTex = (ID3D10Texture2D*)fileResource;
}
fileResourceのGetTypeメソッドでリソースのタイプを取得します。これがD3D10_RESOURCE_DIMENSION_TEXTURE2D(2Dテクスチャ)だったらID3D10Texture2Dとしてダイレクトにキャストしてしまって構いません。
pLoadInfo(D3DX10_IMAGE_LOAD_INFO)にNULLを与えて128×128のアルファ付きpngファイルを読み込むと次のような特性のテクスチャになるようです:
Width 128
Height 128
MipLevels 8
ArraySize 1
Format DXGI_FORMAT_R8G8B8A8_UNORM
SampleDesc.Count 1
SampleDesc.Quality 0
Usage D3D10_USAGE_DEFAULT
BindFlags D3D10_BIND_SHADER_RESOURCE
CPUAccessFlags 0
MiscFlags 0
気を付けたいのがミップマップレベルです。上の例だと「8」、つまりフルでミップマップが作成されています。例えば2DゲームでZ軸方向を使わない板ポリを使うならばミップマップは不要です。ですから上の簡単な方法を用いるとGPUメモリをちょっと損します。
ミップマップは1枚で良いとか、幅と高さを変更したいなど、ファイル情報とは異なる方式でテクスチャを作りたい場合にはpLoadInfo(D3DX10_IMAGE_LOAD_INFO)にその情報を与えます:
D3DX10_IMAGE_LOAD_INFO typedef struct D3DX10_IMAGE_LOAD_INFO {
UINT Width;
UINT Height;
UINT Depth;
UINT FirstMipLevel;
UINT MipLevels;
D3D10_USAGE Usage;
UINT BindFlags;
UINT CpuAccessFlags;
UINT MiscFlags;
DXGI_FORMAT Format;
UINT Filter;
UINT MipFilter;
D3DX10_IMAGE_INFO* pSrcInfo;
} D3DX10_IMAGE_LOAD_INFO;
Width, Height, Depthはそれぞれ作成するテクスチャの幅、高さ、深度です。深度については2Dテクスチャの場合は0になります。
FirstMipLevelとMipLevelsはミップマップの最初のレベルとミップマップ数を指定します。FirstMipLevelはほとんどの場合0になると思います。ミップマップを分割して読み込みたいような特殊な場合などで利用します。
Usageはテクスチャの使われ方で多くはD3D10_USAGE_DEFAULT(GPUからの読み書きのみを許可)です。
BindFlagはリソースの使われ方を指定するフラグです。テクスチャの場合はD3D10_BIND_SHADER_RESOURCE(シェーダの入力テクスチャとして使用)、D3D10_BIND_RENDER_TARGET(レンダーターゲットとして使用)のどちらかになりますが、普通は前者のみです。
CpuAccessFlagsはこのテクスチャのCPUからアクセスを許可するかを指定します。D3D10_CPU_ACCESS_READかD3D10_CPU_ACCESS_WRITEもしくは0(アクセス不可)の何れかになります。書き込みアクセスを許可する場合UsageにはD3D10_USAGE_DYNAMIC(GPU読み込みとCPU書き込みを許可)、読み込みアクセスを許可する場合はD3D10_USAGE_STAGINGにする必要があります。
MiscFlagsはオプションで0でOKです。
Formatは作成するテクスチャのフォーマットをDXGI_FORMATで与えます。
Filterはテクスチャを作成する際の拡大縮小のフィルタリングをD3DX10_FILTER_FLAG列挙型で指定します。
MipFilterはミップマップを作成する時のフィルタを同じくD3DX10_FILTER_FLAG列挙型で指定します。
pSrcInfoは読み込む元画像の情報を与えます。NULLにすると関数が勝手に元画像からその情報を判断します。
典型的な設定例はたとえばこういう感じになります:
D3DX10_IMAGE_LOAD_INFO loadInfo;
loadInfo.Width = 256;
loadInfo.Height = 256;
loadInfo.CpuAccessFlags = 0;
loadInfo.BindFlags = D3D10_BIND_SHADER_RESOURCE;
loadInfo.Depth = 0;
loadInfo.Filter = D3DX10_FILTER_LINEAR;
loadInfo.MipFilter = D3DX10_FILTER_LINEAR;
loadInfo.Usage = D3D10_USAGE_DEFAULT;
loadInfo.FirstMipLevel = 0;
loadInfo.MipLevels = 1;
loadInfo.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
loadInfo.MiscFlags = 0;
loadInfo.pSrcInfo = 0;
幅と高さは読み込むテクスチャによってもちろん異なる値になります。ミップマップを最大レベルまで作成したい場合はMipLevelに0を与えます。元画像に対して拡縮及びミップマップを使う場合はフィルターの設定に注意して下さい。例えばD3DX10_FILTER_POINTと設定すると補間が行われないため画像がジャギジャギにになります。多くは線形補間(D3DX10_FILTER_LINEAR)で十分かと思いますが、高品質にしたい場合はD3DX10_FILTER_TRIANGLE(高品質だが非常に遅いフィルタ)などにします。
画像の情報を先に取得して上の設定をしたい場合、D3DX10GetImageInfoFromFile関数を用いて画像ファイルから情報を抜き出すのが楽です:
D3DX10GetImageInfoFromFile関数 D3DX10GetImageInfoFromFile(
LPCSTR pSrcFile,
ID3DX10ThreadPump* pPump,
D3DX10_IMAGE_INFO* pSrcInfo,
HRESULT* pHResult
);
pSrcFileは画像ファイルへのパスです。
pPumpは非同期で情報を取得する時にID3DX10ThreadPumpオブジェクトを設定します。同期読みする場合はNULLでOKです。
pSrcInfoに画像情報が返ります。
pHResultは非同期読み込みをする場合に有効なメモリを指しておく必要があります。
取得されるD3DX10_IMAGE_INFO構造体は以下のようなメンバを持っています:
D3DX10_IMAGE_INFO typedef struct D3DX10_IMAGE_INFO {
UINT Width;
UINT Height;
UINT Depth;
UINT ArraySize;
UINT MipLevels;
UINT MiscFlags;
DXGI_FORMAT Format;
D3D10_RESOURCE_DIMENSION ResourceDimension;
D3DX10_IMAGE_FILE_FORMAT ImageFileFormat;
} D3DX10_IMAGE_INFO;
WidthからMiscFlagsまではD3D10_IMAGE_LOAD_INFO構造体と一緒なので割愛します。
Formatは画像のフォーマットです。
ResourceDimensionはテクスチャの次元です。通常は2DなのでD3D10_RESOURCE_DIMENSION_TEXTURE2Dになりますが、.ddsファイルの場合は別次元の画像ファイルを格納可能なのでここでそれを判別出来ます。
ImageFileFormatは画像ファイルのフォーマットです。
A GPUアクセスのみ許可する動的なテクスチャを作成
Direct3D10のテクスチャは「アクセス」についてプログラマが少し意識しなければならなくなりました。テクスチャは一般にはシェーダで利用されます。シェーダはGPU上で動く物なので、テクスチャもGPUから読み書きできなければなりません。これがGPUアクセスです。一方で、例えばGPU上でシミュレーションをした結果をテクスチャに穿ったとすると、その色を抽出出来なければ解析や統計情報を収集できません。そういう時はCPUからもテクスチャにアクセスできる必要があります。「別にいつでもGPU・CPU双方からアクセスできるようにしておけばいいじゃん」と思うかもしれませんが、CPUからテクスチャにアクセスできるようにするには特別な処理を走らせなければならないためGPU側のパフォーマンスが大きく落ちます。特にCPUでアクセスする必要の無いテクスチャはGPUのみアクセス権を与えた方がパフォーマンスや安全上最適なんです。そういうテクスチャのアクセス最適化をプログラマが操作できるようになったのがDirect3D10のテクスチャの大きな進歩です。
@のD3DX10CreateTextureFromFile関数で何気なく作成できるテクスチャはCPUからのアクセスが禁止されているためGPU上で高速に動作します。ただ、この関数はファイルフォーマットが分っている画像しか扱えません。例えばオリジナルな形式で画像データをアーカイブ化している場合、メモリ上にある色データからテクスチャを作る必要があります。そういうテクスチャは別の方法で作成する事になります。
まずオリジナルなテクスチャをメモリ上に用意します。これは作成するテクスチャのフォーマットに従って色を並べます。例えばDXGI_FORMAT_R8G8B8A8_UNORMであれば4バイト単位で「RGBA」と並べていきます:
メモリ上に画像データを用意 uint32_t texData[4 * 4] = {
0xffffffff, 0xff0000ff, 0xff0000ff, 0xffffffff,
0xffffffff, 0xff00ff00, 0xff00ff00, 0xffffffff,
0xffffffff, 0xffff0000, 0xffff0000, 0xffffffff,
0xffffffff, 0xffff00ff, 0xffff00ff, 0xffffffff,
};
4×4の小さな画像データです。uint32_tで上のように指定する場合、リトルエンディアンなので0x[A][B][G][R]になるのに注意して下さい。
次にこれを「テクスチャサブリソースデータ」として認識させます:
テクスチャサブリソース D3D10_SUBRESOURCE_DATA subResData;
subResData.pSysMem = texData;
subResData.SysMemPitch = sizeof( uint32_t ) * 4;
subResData.SysMemSlicePitch = subResData.SysMemPitch * 4;
pSysMemにはリソースの生データを指すポインタを渡します。今はtexDataですね。
SysMemPitchはテクスチャの1ラインのバイト数です。横幅を4ピクセルにしていて1ピクセルが4バイト(=sizeof(uint32_t))なので上のような式になっています。
SysMemSlicePitchは複数の同サイズテクスチャがある時に1枚のテクスチャサイズを渡します。1ピッチがすでに計算済みなので高さ4ピクセルをそれに掛け算しています。
もう一つテクスチャその物の属性を定義してあげる必要もあります。これは2Dテクスチャの場合はD3D10_TEXTURE2D_DESC構造体で定義します:
D3D10_TEXTURE2D_DESC D3D10_TEXTURE2D_DESC texDesc;
texDesc.Width = 4;
texDesc.Height = 4;
texDesc.CPUAccessFlags = 0;
texDesc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
texDesc.Usage = D3D10_USAGE_DEFAULT;
texDesc.MipLevels = 1;
texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
texDesc.MiscFlags = 0;
texDesc.ArraySize = 1;
texDesc.SampleDesc.Count = 1;
texDesc.SampleDesc.Quality = 0;
メンバの意味は@で出てきたD3DX10_IMAGE_LOAD_INFO構造体のそれと一緒です。ArraySizeは複数の同サイズテクスチャがある場合にそのテクスチャ枚数を指定します。BindFlagsはシェーダの入力テクスチャとして使用する場合はD3D10_BIND_SHADER_RESOURCEを指定します。GPUのみアクセスするのでCPUAccessFlagsには何も設定しません。
サブリソースデータとD3D10_TEXTURE2D_DESCを用意するとGPUアクセスのみのテクスチャをID3D10Device::CreateTexture2Dメソッドから作る事ができます:
ID3D10Device::CreateTexture2Dメソッド HRESULT CreateTexture2D (
const D3D10_TEXTURE2D_DESC *pDesc,
const D3D10_SUBRESOURCE_DATA *pInitialData,
ID3D10Texture2D **ppTexture2D
);
メソッドが成功するとppTexture2Dに有効なテクスチャオブジェクトが返ります。このテクスチャはGPUのみアクセス権があるので、CPUからのアクセスはできません。具体的にはID3D10Texture2D::Mapメソッドのようなアクセスメソッドが失敗します。
B CPUからもアクセス可能なシェーダに渡せるテクスチャを作成
AではGPUのみアクセス権のあるテクスチャを作成しましたが、時にはCPUでアクセス可能なテクスチャを作成しなければならない事もあります。しかもそれをシェーダの入力として使いたい。割と特殊ケースではありますが、そういうテクスチャも作成可能です。
多くはAのGPUアクセスのみのテクスチャと作り方は一緒なのですが、パラメータの設定が異なります:
D3D10_TEXTURE2D_DESC D3D10_TEXTURE2D_DESC texDesc;
texDesc.Width = 64;
texDesc.Height = 64;
texDesc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
texDesc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
texDesc.Usage = D3D10_USAGE_DYNAMIC;
texDesc.MipLevels = 1;
texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
texDesc.MiscFlags = 0;
texDesc.ArraySize = 1;
texDesc.SampleDesc.Count = 1;
texDesc.SampleDesc.Quality = 0;
ポイントはCPUからの書き込みアクセスを許可するためCPUAccessFlagsをD3D10_CPU_ACCESS_WRITEに、そしてUsageをD3D10_USAGE_DYNAMICにする事です。Usageのパラメータは4つありまして、それぞれアクセスできる範囲や出来る事が決まっています:
DEFAULT DYNAMIC IMMUTABLE STAGING GPU読み込み 〇 △ 〇 △ GPU書き込み △ - - △ CPU読み込み - - - △ CPU書き込み - 〇 - △
上表は〇と△がアクセス可能である事を表しています。〇はダイレクトにメモリを触れるのに対し、△はID3D10Device::CopySubresourceRegionメソッド及びID3D10Device::CopyResourceメソッドでコピーする事によって間接的にアクセスできる事を表しています。DYNAMICだけがCPUによる直接アクセスを許可しているのがわかると思います。STAGINGは唯一CPU/GPUで読み書きできるのですが、すべて間接アクセスなので処理が一番遅くなります。
後はAと一緒です。もし作成時に初期化したいのであればサブリソースを作ってID3D10Device::CreateTextue2Dメソッドに渡します。後で初期化したい場合はサブリソースはNULLでも大丈夫です。
作成後に色を変更するにはID3D10Texture2D::Mapメソッドでメモリをロックします:
テクスチャの色を更新 D3D10_MAPPED_TEXTURE2D map;
if ( SUCCEEDED( tex->Map( 0, D3D10_MAP_WRITE_DISCARD, 0, &map ) ) ) {
uint32_t *p = (uint32_t*)map.pData;
// (x, y) = (10, 14)の色を赤色に変更
uint32_t *c = p + map.RowPitch * 14 + 10;
*c = 0xff0000ff;
// アンロック
tex->Unmap( 0 );
}
Mapメソッドの第4引数にD3D10_MAPPED_TEXTUR E2D構造体のオブジェクトを渡すと、メソッドが成功した場合にテクスチャデータへのポインタを得られます。
色を上書きする時に注意しなければならないのは、テクスチャデータは「ピクセルアライン」になっている点です。一ラインのサイズが特定の単位になっているという事です。例えば4×4のテクスチャを作った場合、私の環境ではmap.RowPitchは128バイトでした。4ピクセルなので本来は16バイトで良いはずなのですが、アクセス効率のためそういうアラインメントが入っているんです。ですから、システムメモリに4×4のメモリを確保しておいて、memcpy関数でドカっと更新する…という事は出来ません。
ちなみに、実はドカッとコピーするメソッドは用意されています。ID3D10Device->UpdateResourceメソッドという「CPUからアクセスできないGPUリソースを上書きコピーできる」という裏技的メソッドがあるのですが、このメソッドはDEFAULT属性のリソースのみ有効で、DYNAMICリソースには適用できないんです…残念。
ポストエフェクトをする場合、シェーダの出力先をバックバッファではなく自前で用意したレンダーターゲットにする必要があります。そういうレンダーターゲットテクスチャは次のように作成します。
レンダーターゲットテクスチャを作成 D3D10_TEXTURE2D_DESC rtDesc;
memset( &rtDesc, 0, sizeof( rtDesc ) );
rtDesc.Width = 1024;
rtDesc.Height = 1024;
rtDesc.MipLevels = 1;
rtDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
rtDesc.SampleDesc.Count = 1;
rtDesc.Usage = D3D10_USAGE_DEFAULT;
rtDesc.ArraySize = 1;
rtDesc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE;
rtDesc.CPUAccessFlags = 0;
ID3D10Texture2D *renderTex = 0;
device->CreateTexture2D( &rtDesc, 0, &renderTex );
UsageはGPUのみアクセスになるのでDEFAULTを、そしてBindFlagsにD3D10_BIND_RENDER_TARGETを追加するのがポイントです。
作成したレンダーターゲットテクスチャをシェーダの出力先として適用するにはレンダーターゲットビューを作り登録しなければなりません:
レンダーターゲットビューに登録 ID3D10RenderTargetView *renderTargetView = 0;
device->CreateRenderTargetView( renderTex, 0, &renderTargetView );
またレンダーターゲットをシェーダの入力テクスチャとして扱う場合にはシェーダリソースビューも作成し登録する必要があります:
レンダーターゲットテクスチャを登録するシェーダリソースビューを作成 ID3D10ShaderResourceView *rtShaderResView = 0;
device->CreateShaderResourceView( renderTex, 0, &rtShaderResView );
これでレンダーターゲットに描画してそれを再利用する事が出来るようになります。レンダーターゲットを具体的にどう活用するかは本章の趣旨と少し外れてしまいますので、また別の機会で見ていこうと思います。
D バックバッファを画像データとして保存する
バックバッファやレンダーターゲットの結果を画像ファイルとして保存する方法が提供されています。D3DX10SaveTextureToFile関数を用いると指定のテクスチャリソースを指定の画像ファイルで保存してくれます:
D3DX10SaveTextureToFile関数 D3DX10SaveTextureToFile(
ID3D10Resource *pSrcTexture,
D3DX10_IMAGE_FILE_FORMAT DestFormat,
LPCSTR pDestFile
);
pSrcTextureは保存したいテクスチャリソースです。バックバッファやレンダーターゲットなど指定可能です。
DestFormatは保存したい画像ファイルのフォーマットをD3DX10_IMAGE_FILE_FORMAT列挙型で指定します。
pDestFileは保存するファイル名です。
D3DX10_IMAGE_FILE_FORMAT列挙型は以下の通りです:
D3DX10_IMAGE_FILE_FORMAT列挙型 typedef enum D3DX10_IMAGE_FILE_FORMAT {
D3DX10_IFF_BMP = 0,
D3DX10_IFF_JPG = 1,
D3DX10_IFF_PNG = 3,
D3DX10_IFF_DDS = 4,
D3DX10_IFF_TIFF = 10,
D3DX10_IFF_GIF = 11,
D3DX10_IFF_WMP = 12,
D3DX10_IFF_FORCE_DWORD = 0x7fffffff
} D3DX10_IMAGE_FILE_FORMAT;
画像ファイルではなくて画像データとしてメモリ上に保存するにはD3DX10SaveTextureToMemory関数を用います:
D3DX10SaveTextureToMemory関数 D3DX10SaveTextureToMemory(
ID3D10Resource* pSrcTexture,
D3DX10_IMAGE_FILE_FORMAT DestFormat,
LPD3D10BLOB* ppDestBuf,
UINT Flags
);
ppDestBufは作成された画像データを受け取るメモリブロック(ID3D10Blobのポインタへのポインタ)です。
Flagsは使用されていないようです。0にしておきます。
保存された画像データへはID3D10Blob::GetBufferPointerメソッドでアクセス出来ます。
この章ではDirect3D10におけるテクスチャの作成方法についてあれこれ見てきました。DirectX9の時より面倒にはなっているのですが、その分細かな制御ができるようにもなっています。上手に使っていきたいものです(^-^)。この章の内容を踏まえたサンプルプログラムも公開しておりますので、宜しければご参照下さい。