★ DirectX Class ★ * Chapter 10: DDSファイルの利用 *
< 画像の不要な部分を透過する >
突然ですが、諸事情により私の環境が Windows Vista + DirectX 10 になってしまいました。
環境の違いが心配ですが、DirectX 9前提で解説していきますので、ご安心ください。
今までの章と同じ環境で正常に動作するはずです。

今までの章で、2Dでは画像を表示し、また3Dでは画像をテクスチャとして3D形状に貼り付けてきました。
しかし、どれも四角形ばかりです。
これでは問題が発生します。
例えば、キャラクターを表示したいとき、今までのやり方では以下のように表示されてしまうでしょう。

理想とは程遠い例

一応キャラクターは表示されています。
しかし、大半の方はこのような状態に満足感を得られず、人によっては非常に耐え難い苛立ちを覚えることでしょう。
そのような症状が当てはまる方は、本当はこう表示したいはずです。

理想像

これはDirectX独特の形式である、『 DDS 』と呼ばれるフォーマットの画像を利用することで簡単に実現できます。
DDSファイルの構造はBMPファイルの構造に非常に似ていて、BMPのRGBに透明度のAを加えたような形式になっています。
DDSファイルの構造を勉強したい方はBMPファイルの勉強を先にしたほうが、ドキュメントも豊富で理解しやすいと思います。
時間があればこの講座でも取り上げます。

この章ではDDSファイルの作成方法及び使い方を説明致します。
< DDSファイルを作ろう >
『 DDS 』とは、『 DirectDraw Surface 』の略だそうです。

DDSファイルを作成するには、元画像を2枚用意します。
『 表示する画像 』及び、
『 透明度に関する画像 』

の2種類です。

今回はキャラクターを例にとって、以下のように画像を2枚作成します。

表示する画像 透明度の画像
表示する画像 表示する画像

左が表示する画像で、右の白黒の画像が透明度に関する画像です。
表示する画像は通常通り作成してもらって構いませんが、透明度に関する画像はモノクロで作成する必要があります。
白が完全に不透明で、黒が完全に透明となります。
表示したいところは白く、表示したくないところは黒くします。
また、グレーを使用した場所は半透明になります。
グレーの濃度によって透明度が変わりますので、細かく調整することが可能です。

表示する画像のファイル名はそのままで良いのですが、透明度の画像は『表示するファイル名_a.拡張子』という名前で保存する必要があります。
例えば表示する画像のファイル名が『test.bmp』だとしたら、透明度の画像は『test_a.bmp』となります。
2枚の画像は同じフォルダに保存してください。

さて、ここまで来たらいよいよこのファイルをDDS形式に変換します。
実は、変換するソフトはDirectXSDKにおまけとして付いてきていますので、それを使用します。
『 スタート 』→『 すべてのプログラム 』からDirectXSDKを選択し、
『 DirectX Utilities 』→『 DirectX Texture Tool 』を選択すれば起動します。

DirectX Texture Tool

非常にシンプルなツールなので軽くて使いやすいです。
このツールで先ほど作成した、表示する画像を開いてください。
ドラッグアンドドロップで開けます。

DirectX Texture Toolで開いたところ

表示する画像を読み込むと、自動で透明度の画像が自動で読み込まれ、反映されます。
画像の透明にしたい部分が水色がかっていますが、これは透明度が適用されていることを意味します。
それではこの画像を、『 File 』→『 Save As 』からddsという拡張子で保存してください。

以上で作業は完了です。
< 必要な処理 >
今回は2Dですので、変更箇所は1箇所だけです。
今まで『 NULL 』を渡していた以下の関数において、
『 NULL 』『 D3DXSPRITE_ALPHABLEND 』に変更するだけです。

g_pSprite->Begin( D3DXSPRITE_ALPHABLEND );

さて突然ですが、ここで非常に重大な注意事項をお伝えしなくてはなりません。
非常に残念なお知らせなので、とても心苦しいのですが、
半透明を扱う場合、『第4章』で解説した『Zバッファ』が使えません。

Zバッファが使えないというのは、ルール等ではなく、見た目に関する理由からです。
実際に下のサンプルをZバッファに対応したものに書き換えて、

1.キャラクター
2.背景

の順番で描画してみてください。
キャラクターの半透明になるはずのマントの部分に、背景が描かれなくなります。

これはZバッファの原理からいえば当然の現象です。
Zバッファによる描画というのは、今から描画しようとしているものが、先に描画されているものより浅いかどうかの判定に基づいて、
描画するか否かを判断しているということを思い出してください。
つまり、キャラクターのマントの位置に背景を描画しようとしたとき、
マントは背景より浅いことを判定して、そこには背景を描画しないのです。
私たちの希望としてはマントの向こうに背景が見えて欲しいのですが、深度で判定するとこうなるのはしかたありません。
完全に透明な場合は描かれてないことになりますので、Zバッファは効いてくれます!

では、「半透明の画像を扱うソフトはZバッファ使用禁止なの?」と誤解される方もおられるかと思いますが、
そういうわけではありません。
半透明を含む画像のみにZバッファが適用されませんので、半透明画像を最後に描画するようにすれば問題ありません。
実際に半透明を含むゲームを開発するときには、
半透明を含むものと含まないものに分け、半透明を含まないものを描画した後、
半透明を含む画像を重なり順に並び替え、奥から描画していくようにすれば問題ありません

< サンプル >
このサンプルを実行すると、クライアント領域に背景とキャラクターが描画されます。
基盤としたプログラムは第3章のものと同じです。
マントが透けていることまで確認してみてください。

サンプルプログラムをご利用の場合は、この画像を実行ファイルと同じディレクトリに入れて実行してください。

★キャラクター画像はこちら★

背景

ポイントはピンク色で示してあります。

//-----------------------------------------------------------------
//
//    DirectX 3D Sprite Sample.
//
//-----------------------------------------------------------------
#define sqrtf sqrt
#define sinf  sin
#define cosf  cos
#define tanf  tan

#include <stdio.h>
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>

#pragma comment( lib , "d3d9.dll" );
#pragma comment( lib , "d3dx9.dll" );



//-----------------------------------------------------------------
//    Grobal Variables.
//-----------------------------------------------------------------
LPDIRECT3D9        g_pD3D            = NULL;
LPDIRECT3DDEVICE9  g_pd3dDevice      = NULL;
LPD3DXSPRITE       g_pSprite         = NULL;
LPDIRECT3DTEXTURE9 g_pBGTexture      = NULL;
LPDIRECT3DTEXTURE9 g_pCharaTexture   = NULL;



//-----------------------------------------------------------------
//    Prototypes.
//-----------------------------------------------------------------
HWND    InitApp( HINSTANCE , int );
BOOL    InitDirect3D( HWND );
BOOL    CleanupDirect3D();
BOOL    RenderDirect3D();
LRESULT CALLBACK WndProc( HWND , UINT , WPARAM , LPARAM );



//-----------------------------------------------------------------
//    Main.
//-----------------------------------------------------------------
int WINAPI WinMain( HINSTANCE hInst , HINSTANCE hPrevinst , LPSTR nCmdLine , int nCmdShow )
{
	MSG msg;
	HWND hWnd;
	
	hWnd = InitApp( hInst , nCmdShow );
	if ( !hWnd ) return FALSE;
	
	if ( !InitDirect3D( hWnd ) ) return FALSE;
	
	while( msg.message != WM_QUIT ){
		if ( PeekMessage( &msg , NULL , 0 , 0 , PM_REMOVE ) ){
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}else{
			RenderDirect3D();
		}
		Sleep( 1 );
	}
	
	return msg.wParam;
}



//-----------------------------------------------------------------
//    Initialize Application.
//-----------------------------------------------------------------
HWND InitApp( HINSTANCE hInst , int nCmdShow )
{
	WNDCLASS wc;
	HWND hWnd;
	char szClassName[] = "Direct3D Test";
	
	wc.style         = CS_HREDRAW | CS_VREDRAW;
	wc.hInstance     = hInst;
	wc.hCursor       = LoadCursor( NULL , IDC_ARROW );
	wc.hIcon         = LoadIcon( NULL , IDI_APPLICATION );
	wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
	wc.lpszClassName = szClassName;
	wc.lpszMenuName  = NULL;
	wc.lpfnWndProc   = WndProc;
	wc.cbWndExtra    = 0;
	wc.cbClsExtra    = 0;
	if ( !RegisterClass( &wc ) ) return FALSE;
	
	hWnd = CreateWindow( szClassName , "Direct3D Test" , WS_OVERLAPPEDWINDOW ,
						CW_USEDEFAULT , CW_USEDEFAULT , 640 , 480,
						NULL , NULL , hInst , NULL );
	if ( !hWnd ) return FALSE;
	
	ShowWindow( hWnd , nCmdShow );
	UpdateWindow( hWnd );
	
	return hWnd;
}



//-----------------------------------------------------------------
//    Initialize Direct3D.
//-----------------------------------------------------------------
BOOL InitDirect3D( HWND hWnd )
{
	D3DPRESENT_PARAMETERS d3dpp;
	HRESULT hr;
	
	if ( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ){
		MessageBox( hWnd , "Can't create Direct3D." , "Error" , MB_OK );
		return FALSE;
	}
	ZeroMemory( &d3dpp , sizeof( d3dpp ) );
	d3dpp.Windowed         = TRUE;
	d3dpp.SwapEffect       = D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
	
	hr = g_pD3D->CreateDevice( D3DADAPTER_DEFAULT , D3DDEVTYPE_HAL , hWnd ,
	                           D3DCREATE_SOFTWARE_VERTEXPROCESSING ,
	                           &d3dpp , &g_pd3dDevice );
	if ( FAILED( hr ) ){
		MessageBox( hWnd , "Can't create device." , "Error" , MB_OK );
		return FALSE;
	}
	
	D3DXCreateSprite( g_pd3dDevice , &g_pSprite );
	D3DXCreateTextureFromFile( g_pd3dDevice , "sample0010_1.bmp" , &g_pBGTexture );
	D3DXCreateTextureFromFile( g_pd3dDevice , "sample0010.dds" ,   &g_pCharaTexture );
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Cleanup Direct3D.
//-----------------------------------------------------------------
BOOL CleanupDirect3D()
{
	if ( g_pBGTexture != NULL )
		g_pBGTexture->Release();
	
	if ( g_pCharaTexture != NULL )
		g_pCharaTexture->Release();
	
	if ( g_pSprite != NULL )
		g_pSprite->Release();
	
	if ( g_pd3dDevice != NULL )
		g_pd3dDevice->Release();
	
	if ( g_pD3D != NULL )
		g_pD3D->Release();
	
	return TRUE;
}



//-----------------------------------------------------------------
//    Window Proc.
//-----------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hWnd , UINT msg , WPARAM wp , LPARAM lp )
{
	switch( msg ){
		case WM_DESTROY:
			CleanupDirect3D();
			PostQuitMessage( 0 );
			break;
		default:
			return DefWindowProc( hWnd , msg , wp , lp );
	}
	
	return 0L;
}



//-----------------------------------------------------------------
//    Render Direct3D.
//-----------------------------------------------------------------
BOOL RenderDirect3D()
{
	RECT rc;
	D3DXVECTOR3 center , position;
	
	g_pd3dDevice->Clear( 0 , NULL , D3DCLEAR_TARGET ,
	                     D3DCOLOR_XRGB( 0 , 0 , 0 ) , 1.0f , 0 );
	
	g_pd3dDevice->BeginScene();
	
	g_pSprite->Begin( D3DXSPRITE_ALPHABLEND );
	
	rc.left    = 0;
	rc.top     = 0;
	rc.right   = 256;
	rc.bottom  = 256;
	center.x   = 0;
	center.y   = 0;
	position.x = 20;
	position.y = 10;
	
	g_pSprite->Draw( g_pBGTexture , &rc , &center , &position , 0xFFFFFFFF );
	
	rc.left    = 0;
	rc.top     = 0;
	rc.right   = 128;
	rc.bottom  = 128;
	center.x   = 0;
	center.y   = 0;
	position.x = 84;
	position.y = 74;
	
	g_pSprite->Draw( g_pCharaTexture , &rc , &center , &position , 0xFFFFFFFF );
	
	g_pSprite->End();
	
	g_pd3dDevice->EndScene();
	
	g_pd3dDevice->Present( NULL , NULL , NULL , NULL );
	
	return TRUE;
}

実行結果

クリックすると実物大で表示されます。
執筆: 2009/07/14 (TUE)