Direct3D 11 再入門
★ 1.はじめに…
Direct3D 11.1からD3DXが廃止,またDXUTもなくなるということで,今までガッツリ使っていたのでこれらを使わないようにするプログラムを書く必要が出てきました。
また,自前ライブラリのテストプログラム用に少しフレームワーク周りを整えたいなぁ~という個人的事情もあり"Direct3D 11再入門"と題してD3D11の復習をしておこうかと思います。
まず開発環境の設定ですが,Visual Studio 2012がインストールされていれば問題ないと思います。…というのもDirectX SDKで単体で配布されることがなくなり,Windows Software Development Kit (Windows SDK)にDirectX SDKが含まれるようになりました。
Visual Studio 2012がインストールされていれば,Windows SDKに必要なコンポーネントがインストールされるそうです。逆に2012でない場合は,Windows SDKをダウンロードしてきて参照パスを設定する必要があるかと思います。
※2013/01/06 ソースコードと本文を一部修正しました。
★ 2.アプリの処理の流れ
ざっと,アプリケーションの処理フローをまとめておきましょう。
詳しい説明は,次のセクション以降でします。
★ 3.ウィンドウの生成
普通にウィンドウを生成します。
ウィンドウクラスの設定を行い,RegisteClassEx()関数で,設定を行ったウィンドウクラスの登録を行います。
次に,CreateWindow()関数を呼び出して,最後にShowWindow()関数を呼び出します。
お決まりの処理ですね。86行で行っている謎の処理"s_pThis = this"は,ウィンドウプロシージャから,DemoAppクラスにアクセスするための対応策です。
00077: //-----------------------------------------------------------------------------------
00078: // ウィンドウの初期化を行います.
00079: //-----------------------------------------------------------------------------------
00080: bool DemoApp::InitWnd()
00081: {
00082: // インスタンスハンドルを取得します.
00083: HINSTANCE hInst = GetModuleHandle( NULL );
00084:
00085: // 自分自身を設定.
00086: s_pThis = this;
00087:
00088: // 拡張ウィンドウクラスの設定.
00089: WNDCLASSEXA wc;
00090: wc.cbSize = sizeof( WNDCLASSEXA );
00091: wc.style = CS_HREDRAW | CS_VREDRAW;
00092: wc.lpfnWndProc = WndProc;
00093: wc.cbClsExtra = 0;
00094: wc.cbWndExtra = 0;
00095: wc.hInstance = hInst;
00096: wc.hIcon = LoadIcon( hInst, IDI_APPLICATION );
00097: wc.hCursor = LoadCursor( NULL, IDC_ARROW );
00098: wc.hbrBackground = (HBRUSH)( COLOR_WINDOW + 1 );
00099: wc.lpszMenuName = NULL;
00100: wc.lpszClassName = m_Title.c_str();
00101: wc.hIconSm = LoadIcon( hInst, IDI_APPLICATION );
00102:
00103: // ウィンドウクラスを登録します.
00104: if ( !RegisterClassExA( &wc ) )
00105: {
00106: // エラーログ出力.
00107: ELOG( "Error : RegisterClassEx() Failed.\n" );
00108:
00109: // 異常終了.
00110: return false;
00111: }
00112:
00113: // インスタンスハンドルを設定.
00114: m_hInst = hInst;
00115:
00116: // 矩形の設定.
00117: RECT rc = { 0, 0, m_Width, m_Height };
00118:
00119: // 指定されたクライアント領域を確保するために必要なウィンドウ座標を計算します.
00120: AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );
00121:
00122: // ウィンドウを生成します.
00123: m_hWnd = CreateWindowA(
00124: m_Title.c_str(),
00125: m_Title.c_str(),
00126: WS_OVERLAPPEDWINDOW,
00127: CW_USEDEFAULT,
00128: CW_USEDEFAULT,
00129: ( rc.right - rc.left ),
00130: ( rc.bottom - rc.top ),
00131: NULL,
00132: NULL,
00133: hInst,
00134: NULL
00135: );
00136:
00137: // 生成チェック.
00138: if ( m_hWnd == NULL )
00139: {
00140: // エラーログ出力.
00141: ELOG( "Error : CreateWindow() Failed." );
00142:
00143: // 異常終了.
00144: return false;
00145: }
00146:
00147: // ウィンドウを表示します.
00148: ShowWindow( m_hWnd, SW_SHOWNORMAL );
00149:
00150: // 正常終了.
00151: return true;
00152: }
00153:
★ 4.D3D11の初期化処理
続いて,D3D11の初期化処理を行います。
ここの説明が一番ヘヴィです…。
まずは,デバイスとスワップチェインの生成を行います。最初に生成する際に必要な変数を設定していきます。
生成関数呼び出し時に,デバイスのドライバータイプと生成試みる機能レベルが必要となります。
ドライバータイプは…
- D3D_DRIVER_TYPE_HARDWARE ⇒ ハードウェアドライバ
- D3D_DRIVER_TYPE_REFERENCE ⇒ リファレンスラスタライザ
- D3D_DRIVER_TYPE_WARP ⇒ ソフトウェアラスタライザ
- D3D_DRIVER_TYPE_NULL ⇒ NULLドライバ
などがあります。
機能レベルは,その名の通り GPUがサポートする機能セットの定義のことです。上位の機能レベルは下位の機能レベルの機能を包括します。
機能レベルは
D3D_FEATURE_LEVEL列挙型で定義されており,以下のようなものがあります。
- D3D_FEATURE_LEVEL_11_1 ⇒ Direct3D 11.1の機能をサポート
- D3D_FEATURE_LEVEL_11_0 ⇒ Direct3D 11の機能をサポート
- D3D_FEATURE_LEVEL_10_1 ⇒ Direct3D 10.1の機能をサポート
- D3D_FEATURE_LEVEL_10_0 ⇒ Direct3D 10の機能をサポート
- D3D_FEATURE_LEVEL_9_3 ⇒ Direct3D 9.3の機能をサポート
- D3D_FEATURE_LEVEL_9_2 ⇒ Direct3D 9.2の機能をサポート
- D3D_FEATURE_LEVEL_9_1 ⇒ Direct3D 9.1の機能をサポート
基本的にはD3D11で開発するので,D3D_FEATURE_LEVEL_11_0とかあたりが指定されていれば問題ないです。
下記のプログラムでは,一応Direct3D 10対応のグラフィックボードでも動くようにD3D_FEATURE_LEVEL_10_1とD3D_FEATURE_LEVEL_10_0を設定しています。
00186: // デバイス生成フラグ.
00187: UINT createDeviceFlags = 0;
00188: #if defined(DEBUG) || defined(_DEBUG)
00189: createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
00190: #endif//defined(DEBUG) || deifned(_DEBUG)
00191:
00192: // ドライバータイプ.
00193: D3D_DRIVER_TYPE driverTypes[] = {
00194: D3D_DRIVER_TYPE_HARDWARE,
00195: D3D_DRIVER_TYPE_WARP,
00196: D3D_DRIVER_TYPE_REFERENCE,
00197: };
00198: UINT numDriverTytpes = sizeof( driverTypes ) / sizeof( driverTypes[0] );
00199:
00200: // 機能レベル.
00201: D3D_FEATURE_LEVEL featureLevels[] = {
00202: D3D_FEATURE_LEVEL_11_0,
00203: D3D_FEATURE_LEVEL_10_1,
00204: D3D_FEATURE_LEVEL_10_0,
00205: };
00206: UINT numFeatureLevels = sizeof( featureLevels ) / sizeof( featureLevels[0] );
00207:
さて,続いてスワップチェインの構成設定を行います。設定の仕方ですが
DXGI_SWAP_CHAIN_DESC構造体に値を入れていきます。下記のような感じです。
00208: // スワップチェインの構成設定.
00209: DXGI_SWAP_CHAIN_DESC sd;
00210: ZeroMemory( &sd, sizeof(DXGI_SWAP_CHAIN_DESC) );
00211: sd.BufferCount = m_SwapChainCount;
00212: sd.BufferDesc.Width = w;
00213: sd.BufferDesc.Height = h;
00214: sd.BufferDesc.Format = m_SwapChainFormat;
00215: sd.BufferDesc.RefreshRate.Numerator = 60;
00216: sd.BufferDesc.RefreshRate.Denominator = 1;
00217: sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
00218: sd.OutputWindow = m_hWnd;
00219: sd.SampleDesc.Count = m_MultiSampleCount;
00220: sd.SampleDesc.Quality = m_MultiSampleQuality;
00221: sd.Windowed = TRUE;
上記のBufferCountはディスプレイバッファの数です。2ならばいわゆるダブルバッファで,3ならいわゆるトリプルバッファになります。特にこだわりがなければ,2でいいと思います。
BufferDesc.WidthとBufferDesc.Heightはディスプレイバッファの横幅と縦幅です。特にこだわりがなければウィンドウサイズを設定しておけばいいと思います。
BufferDesc.Formatはディスプレイバッファのフォーマットです。今回はDXGI_FORMAT_R8G8B8A8_UNORMを指定しています。DXGI_FORMATは色々とあって説明が大変なので,
MSDNのドキュメントをご参照ください。
BufferDesc.RefreshRate.Numeratorはリフレッシュレートの分子で,Denominatorは分母です。上記の設定例では, 60Hz毎で画面更新されるような設定にしています。続いてBufferUsageですが,これはディスプレイバッファの使われ方の指定です。
レンダーターゲットとして使用したいのでDXGI_USAGE_RENDER_TARGET_OUTPUTと,後でサンプルプログラムを作るときにレンダリングテクスチャとして使いたいという場面が出てくると思うので,DXGI_USAGE_SHADER_INPUTを指定しています。
OutputWindowは出力ウィンドウの指定です。ウィンドウハンドルを設定してあげてください。SampleDesc.Countはマルチサンプルのサンプリングカウント数で,SampleDesc.Qualityは品質を示す数値です。値が大きいほど品質が良いです。
フォーマットとサンプリングカウント数によって最大で設定できる品質の数値が異なるみたいなので,最大値を調べたい場合はID3D11Device::CheckMultiSampleQualityLevels()メソッドでチェックしてみてください。
尚,アンチエイリアス処理しない場合は,SampleDesc.Count = 1, SampleDesc.Quality = 0を設定しましょう。最後のWindowedは,画面モードの指定ですTRUEであればウィンドウモードで,FALSEであればフルスクリーンモードになります。
さて,いよいよメソッドを呼び出してデバイスとスワップチェインの作成を行います。デバイスとスワップチェインを個別に作成しても良いのですが,作成が面倒くさそうな感じがするので
D3D11CreateDeviceAndSwapChain()メソッドを使って同時作成を行っちゃいます。
処理は下記のようになります。
00223: for( UINT idx = 0; idx < numDriverTytpes; ++idx )
00224: {
00225: // ドライバータイプ設定.
00226: m_DriverType = driverTypes[ idx ];
00227:
00228: // デバイスとスワップチェインの生成.
00229: hr = D3D11CreateDeviceAndSwapChain(
00230: NULL,
00231: m_DriverType,
00232: NULL,
00233: createDeviceFlags,
00234: featureLevels,
00235: numFeatureLevels,
00236: D3D11_SDK_VERSION,
00237: &sd,
00238: &m_pSwapChain,
00239: &m_pDevice,
00240: &m_FeatureLevel,
00241: &m_pDeviceContext
00242: );
00243:
00244: // 成功したらループを脱出.
00245: if ( SUCCEEDED( hr ) )
00246: { break; }
00247: }
00248:
00249: // 失敗していないかチェック.
00250: if ( FAILED( hr ) )
00251: {
00252: ELOG( "Error : D3D11CreateDeviceAndSwapChain() Failed." );
00253: return false;
00254: }
上記のコードですが,ドライバータイプが速い順に生成を行っていて,成功したらループを脱出する処理になっています。
ループの実行順ですが,ハードウェアデバイス⇒ソフトウェアラスタライザ⇒リファレンスラスタライザの順になっています。
D3D11CreateDeviceAndSwapChain()メソッドについての詳しい説明がほしい方は
MSDNのドキュメントを参照してください。
ようやく,デバイスとスワップチェインの作成まで終わりました。
次は,レンダーターゲットの生成です。レンダーターゲットの生成は次のような感じで行います。
00380: HRESULT hr = S_OK;
00381:
00382: // バックバッファを取得.
00383: hr = m_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (LPVOID*)&m_pRTT );
00384: if ( FAILED( hr ) )
00385: {
00386: ELOG( "Error : IDXGISwapChain::GetBuffer() Failed" );
00387: return false;
00388: }
00389:
00390: // レンダーターゲットを生成.
00391: hr = m_pDevice->CreateRenderTargetView( m_pRTT, NULL, &m_pRTV );
00392: if ( FAILED( hr ) )
00393: {
00394: ELOG( "Error : ID3D11Device::CreateRenderTargetView() Failed." );
00395: return false;
00396: }
00397:
まず,先ほど生成したスワップチェインからGetBuffer()メソッドを用いてバックバッファを取得します。今回の例では,ID3D11Texture2Dであることが自明なので__uuidof( ID3D11Texture2D )を指定してID3D11Texture2Dとしてバックバッファを取得します。
次に取得したバックバッファを使用して,ID3D11Device::CreateRenderTargetView()メソッドを呼び出し,レンダーターゲットビューを生成します。
あと,先ほどスワップチェインを作成する際にDXGI_USAGE_SHADER_INPUTを指定したので,シェーダで利用するためのシェーダリソースビューも合わせて作っておきます。
00398: // レンダーターゲットのシェーダリソースビューを生成.
00399: hr = m_pDevice->CreateShaderResourceView( m_pRTT, NULL, &m_pRTSRV );
00400: if ( FAILED( hr ) )
00401: {
00402: ELOG( "Error : ID3D11Device::CreateShaderResourceView() Failed." );
00403: return false;
00404: }
レンダーターゲットの作成まで終わりました。続いて深度ステンシルバッファの作成です。
大体とレンダーターゲットの作成と同じノリで,
テクスチャ生成⇒深度ステンシルビュー生成(⇒シェーダリソースビューの生成)
…といった感じです。
00436: //------------------------------------------------------------------------------------
00437: // デフォルトの深度ステンシルバッファを生成します.
00438: //------------------------------------------------------------------------------------
00439: bool DemoApp::CreateDefaultDepthStencil()
00440: {
00441: HRESULT hr = S_OK;
00442:
00443: DXGI_FORMAT textureFormat = m_DepthStencilFormat;
00444: DXGI_FORMAT resourceFormat = m_DepthStencilFormat;
00445:
00446: // テクスチャとシェーダリソースビューのフォーマットを適切なものに変更.
00447: switch( m_DepthStencilFormat )
00448: {
00449: case DXGI_FORMAT_D16_UNORM:
00450: {
00451: textureFormat = DXGI_FORMAT_R16_TYPELESS;
00452: resourceFormat = DXGI_FORMAT_R16_UNORM;
00453: }
00454: break;
00455:
00456: case DXGI_FORMAT_D24_UNORM_S8_UINT:
00457: {
00458: textureFormat = DXGI_FORMAT_R24G8_TYPELESS;
00459: resourceFormat = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
00460: }
00461: break;
00462:
00463: case DXGI_FORMAT_D32_FLOAT:
00464: {
00465: textureFormat = DXGI_FORMAT_R32_TYPELESS;
00466: resourceFormat = DXGI_FORMAT_R32_FLOAT;
00467: }
00468: break;
00469:
00470: case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
00471: {
00472: textureFormat = DXGI_FORMAT_R32G8X24_TYPELESS;
00473: resourceFormat = DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
00474: }
00475: break;
00476: }
00477:
00478: // 深度ステンシルテクスチャの生成.
00479: D3D11_TEXTURE2D_DESC td;
00480: ZeroMemory( &td, sizeof( D3D11_TEXTURE2D_DESC ) );
00481: td.Width = m_Width;
00482: td.Height = m_Height;
00483: td.MipLevels = 1;
00484: td.ArraySize = 1;
00485: td.Format = textureFormat;
00486: td.SampleDesc.Count = m_MultiSampleCount;
00487: td.SampleDesc.Quality = m_MultiSampleQuality;
00488: td.Usage = D3D11_USAGE_DEFAULT;
00489: td.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
00490: td.CPUAccessFlags = 0;
00491: td.MiscFlags = 0;
00492:
00493: // 深度ステンシルテクスチャの生成.
00494: hr = m_pDevice->CreateTexture2D( &td, NULL, &m_pDST );
00495: if ( FAILED( hr ) )
00496: {
00497: ELOG( "Error : ID3D11Device::CreateTexture2D() Failed." );
00498: return false;
00499: }
00500:
00501: // 深度ステンシルビューの設定.
00502: D3D11_DEPTH_STENCIL_VIEW_DESC dsvd;
00503: ZeroMemory( &dsvd, sizeof( D3D11_DEPTH_STENCIL_VIEW_DESC ) );
00504: dsvd.Format = m_DepthStencilFormat;
00505: if ( m_MultiSampleCount == 0 )
00506: {
00507: dsvd.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
00508: dsvd.Texture2D.MipSlice = 0;
00509: }
00510: else
00511: {
00512: dsvd.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMS;
00513: }
00514:
00515:
00516: // 深度ステンシルビューの生成.
00517: hr = m_pDevice->CreateDepthStencilView( m_pDST, &dsvd, &m_pDSV );
00518: if ( FAILED( hr ) )
00519: {
00520: ELOG( "Error : ID3D11Device::CreateDepthStencilView() Failed." );
00521: return false;
00522: }
00523:
00524: // シェーダリソースビューの設定.
00525: D3D11_SHADER_RESOURCE_VIEW_DESC srvd;
00526: ZeroMemory( &srvd, sizeof( D3D11_SHADER_RESOURCE_VIEW_DESC ) );
00527: srvd.Format = resourceFormat;
00528:
00529: if ( m_MultiSampleCount == 0 )
00530: {
00531: srvd.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
00532: srvd.Texture2D.MostDetailedMip = 0;
00533: srvd.Texture2D.MipLevels = 1;
00534: }
00535: else
00536: {
00537: srvd.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMS;
00538: }
00539:
00540: // シェーダリソースビューを生成.
00541: hr = m_pDevice->CreateShaderResourceView( m_pDST, &srvd, &m_pDSSRV );
00542: if ( FAILED( hr ) )
00543: {
00544: ELOG( "Error : ID3D11Device::CreateShaderResourceView() Failed." );
00545: return false;
00546: }
00547:
00548: return true;
00549: }
00550:
シェーダリソースビューとして使わない場合は,テクスチャと深度ステンシルビューのフォーマットの値は同じで問題ないと思いますが,シェーダリソースビューとしても使う場合は適切なフォーマットを設定する必要があるみたいです。
適切なフォーマットのしない場合は生成に失敗するみたいです。そんなわけで適切なフォーマットに設定している箇所が447行目から476行目あたりの処理です。
あと気を付ける個所は,
マルチサンプルの数がゼロでないときは,
D3D11_DSV_DIMENSION_TEXTURE2DMSや
D3D11_SRV_DIMENSION_TEXTURE2DMSを設定しないといけない
…ということです。最初これがわからなくて,CEDECのデモプログラム作成を手伝うときにハマりました。そんなわけで注意してください。
さてさて,これでレンダーターゲットと深度ステンシルバッファの生成が終わったわけですが,生成しただけじゃうまく動作しないので,生成したこれら2つをデバイスコンテキストに設定しましょう。
出力マネージャに下記のような処理で,レンダーターゲットと深度ステンシルバッファを設定します。
00278: // デバイスコンテキストにレンダーターゲットを設定.
00279: m_pDeviceContext->OMSetRenderTargets( 1, &m_pRTV, m_pDSV );
上記の場合は,1枚のレンダーターゲットを設定するので第1引数が1になっているのですが,DeferredRenderingやLight Pre-Pass Renderingなど複数のレンダーターゲットを使用する場合は,レンダーターゲットビューを複数作って,4とか1以外の数値を設定すればよいです。
次はビューポートの設定を行いましょう。ビューポートの設定は
D3D11_VIEWPOT構造体に値を設定し,ID3D11DeviceContext::RSSetViewports()メソッドでラスタライザーにビューポートを設定します。
00281: // ビューポートの設定.
00282: D3D11_VIEWPORT vp;
00283: vp.Width = (FLOAT)w;
00284: vp.Height = (FLOAT)h;
00285: vp.MinDepth = 0.0f;
00286: vp.MaxDepth = 1.0f;
00287: vp.TopLeftX = 0;
00288: vp.TopLeftY = 0;
00289:
00290: // デバイスコンテキストにビューポートを設定.
00291: m_pDeviceContext->RSSetViewports( 1, &vp );
MSDNのドキュメントによるとMinDepthとMaxDepthの値の範囲は0.0f~1.0fとなっています。また,WidthとHeightは正の値を指定する必要があるそうです。まぁ,当たり前っちゃ~当たり前ですね。
特にこだわりがなければWidthとHeightにはウィンドウサイズを設定,MinDepthには0.0f, MaxDepthには1.0f, TopLeftX, TopLeftYには0を設定しておけば問題ないかと思います。
これでビューポートの設定がおわり,ようやく画面表示までの下準備が整いました。
次のセクションで描画データの初期化処理等についての説明を行います。
★ 5.描画データを用意する
このセクションでは描画データの初期化処理等についての説明を行います。
今回は,単純な三角形ポリゴンを描画するのに以下のデータを用意しました。
- シェーダファイル
- 頂点データ
- 変換行列
- 頂点バッファ
- 入力レイアウト
- 定数バッファ
シェーダファイルですが,今回は以下のような頂点シェーダ・ジオメトリシェーダ・ピクセルシェーダを全部1つのSimple.fxというファイルにまとめました。
シェーダのコードは以下のような単純な処理です。
00007: /////////////////////////////////////////////////////////////////////////////////////
00008: // CBPerFrame constant buffer
00009: /////////////////////////////////////////////////////////////////////////////////////
00010: cbuffer CBPerFrame : register( b0 )
00011: {
00012: matrix World; //!< ワールド行列.
00013: matrix View; //!< ビュー行列.
00014: matrix Proj; //!< 射影行列.
00015: };
00016:
00017: /////////////////////////////////////////////////////////////////////////////////////
00018: // VSInput structure
00019: /////////////////////////////////////////////////////////////////////////////////////
00020: struct VSInput
00021: {
00022: float3 Position : POSITION; //!< 位置座標です.
00023: };
00024:
00025: /////////////////////////////////////////////////////////////////////////////////////
00026: // GSPSInput structure
00027: /////////////////////////////////////////////////////////////////////////////////////
00028: struct GSPSInput
00029: {
00030: float4 Position : SV_POSITION; //!< 位置座標です.
00031: };
00032:
00033: //-----------------------------------------------------------------------------------
00034: //! @brief 頂点シェーダエントリーポイントです.
00035: //-----------------------------------------------------------------------------------
00036: GSPSInput VSFunc( VSInput input )
00037: {
00038: GSPSInput output = (GSPSInput)0;
00039:
00040: // 入力データをそのまま流す.
00041: output.Position = float4( input.Position, 1.0f );
00042:
00043: return output;
00044: }
00045:
00046: //-----------------------------------------------------------------------------------
00047: //! @brief ジオメトリシェーダエントリーポイントです.
00048: //-----------------------------------------------------------------------------------
00049: [maxvertexcount(3)]
00050: void GSFunc( triangle GSPSInput input[3], inout TriangleStream<GSPSInput> stream )
00051: {
00052: for( int i=0; i<3; ++i )
00053: {
00054: GSPSInput output = (GSPSInput)0;
00055:
00056: // ワールド空間に変換.
00057: float4 worldPos = mul( World, input[ i ].Position );
00058:
00059: // ビュー空間に変換.
00060: float4 viewPos = mul( View, worldPos );
00061:
00062: // 射影空間に変換.
00063: float4 projPos = mul( Proj, viewPos );
00064:
00065: // 出力値設定.
00066: output.Position = projPos;
00067:
00068: // ストリームに追加.
00069: stream.Append( output );
00070: }
00071: stream.RestartStrip();
00072: }
00073:
00074: //------------------------------------------------------------------------------------
00075: //! @brief ピクセルシェーダエントリーポイントです.
00076: //------------------------------------------------------------------------------------
00077: float4 PSFunc( GSPSInput output ) : SV_TARGET0
00078: {
00079: return float4( 0.25f, 1.0f, 0.25f, 1.0f );
00080: }
さて,このシェーダファイルのランタイム上でのコンパイルですが以下のように,CompileShaderFromFile()というメソッドを作って,コンパイルを行うようにしました。
00074: HRESULT hr = S_OK;
00075:
00076: // 頂点シェーダをコンパイル.
00077: ID3DBlob* pVSBlob = NULL;
00078: // ※ファイルパスは$(ProjectDir)からみた相対パスになっています.
00079: hr = CompileShaderFromFile( L"../res/Simple.fx", "VSFunc", "vs_4_0", &pVSBlob );
00080: if ( FAILED( hr ) )
00081: {
00082: assert( false && "CompileShaderFromFile() Failed" );
00083: return false;
00084: }
00085:
肝心のCompileShaderFromFile()メソッドの内部処理ですが,以下のようにしました。
00013: //---------------------------------------------------------------------------------------------
00014: // ファイルからシェーダをコンパイルします.
00015: //---------------------------------------------------------------------------------------------
00016: HRESULT CompileShaderFromFile
00017: (
00018: WCHAR* szFileName,
00019: LPCSTR szEntryPoint,
00020: LPCSTR szShaderModel,
00021: ID3DBlob** ppBlobOut
00022: )
00023: {
00024: // リターンコードを初期化.
00025: HRESULT hr = S_OK;
00026:
00027: // コンパイルフラグ.
00028: DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
00029:
00030: #if defined(DEBUG) || defined(_DEBUG)
00031: dwShaderFlags |= D3DCOMPILE_DEBUG;
00032: #endif//defiend(DEBUG) || defined(_DEBUG)
00033:
00034: #if defined(NDEBUG) || defined(_NDEBUG)
00035: dwShaderFlags |= D3DCOMPILE_OPTIMIZATION_LEVEL3;
00036: #endif//defined(NDEBUG) || defined(_NDEBUG)
00037:
00038: ID3DBlob* pErrorBlob = NULL;
00039:
00040: // ファイルからシェーダをコンパイル.
00041: hr = D3DCompileFromFile(
00042: szFileName,
00043: NULL,
00044: D3D_COMPILE_STANDARD_FILE_INCLUDE,
00045: szEntryPoint,
00046: szShaderModel,
00047: dwShaderFlags,
00048: 0,
00049: ppBlobOut,
00050: &pErrorBlob
00051: );
00052:
00053: // エラーチェック.
00054: if ( FAILED( hr ) )
00055: {
00056: // エラーメッセージを出力.
00057: if ( pErrorBlob != NULL )
00058: { OutputDebugStringA( (char*)pErrorBlob->GetBufferPointer() ); }
00059: }
00060:
00061: // 解放処理.
00062: if ( pErrorBlob )
00063: {
00064: pErrorBlob->Release();
00065: pErrorBlob = NULL;
00066: }
00067:
00068: // リターンコードを返却.
00069: return hr;
00070: }
冒頭に言ったように,D3DXが廃止されたので内部処理がD3DCompileFromFile()になっています。
第1引数はシェーダファイル名です。
第2引数はD3D_SHADER_MACRO構造体の配列を指定します。この第2引数はシェーダマクロを定義する際に設定するオプションで,各マクロ定義はは名前とNULL終端の定義を含みます。
使用しない場合はNULLを設定します。
第3引数は,コンパイラがインクルードファイルを取り扱うために使用するID3DIncludeインタフェースへのポインタです。
このパラメータにNULLを設定した場合にシェーダが"#include"を含む場合は,コンパイラはエラーとして処置します。D3D_COMPILE_STANDARD_FILE_INCLUDEマクロは,デフォルトのインクルードハンドらーへのポインタで,
このデフォルトインクルードハンドラーは,カレントディレクトに対して相対的にファイルをインクルードします。
第4引数は,エントリーポイントのメソッド名を指定します。
第5引数はコンパイルターゲットを指定します。
"vs_4_0"などのプロファイルを指定してください。
第6引数はシェーダのコンパイルオプションを指定します。
第7引数はエフェクトファイルのコンパイルオプションを指定します。エフェクトとして使用しないのであれば0を設定しておけばよいでしょう。
第8引数はコンパイルされたコードへアクセスするためのID3DBlobインタフェースのポインタを設定します。
第9引数は,コンパイルエラーメッセージへアクセスするためのID3DBlobインタフェースのポインタを設定します。
シェーダのコンパイルは上記のような感じです。
シェーダをコンパイルした後は,ID3D11Device::CreateVertexShader()やCreatePixelShader()などのメソッドを呼び出して各シェーダを生成します。
00076: // 頂点シェーダをコンパイル.
00077: ID3DBlob* pVSBlob = NULL;
00078: // ※ファイルパスは$(ProjectDir)からみた相対パスになっています.
00079: hr = CompileShaderFromFile( L"../res/Simple.fx", "VSFunc", "vs_4_0", &pVSBlob );
00080: if ( FAILED( hr ) )
00081: {
00082: assert( false && "CompileShaderFromFile() Failed" );
00083: return false;
00084: }
00085:
00086: // 頂点シェーダ生成.
00087: hr = pDevice->CreateVertexShader( pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &m_pVS );
00088: if ( FAILED( hr ) )
00089: {
00090: assert( false && "ID3D11Device::CreateVertexShader() Failed." );
00091: pVSBlob->Release();
00092: pVSBlob = NULL;
00093: return false;
00094: }
続いて,入力レイアウトの設定です。
D3D11からシェーダのセマンティクス名を自由につけられるようになりました。ただ動作させるために,どのような入力であるかを設定する必要があります。それが入力レイアウトです。
D3D11_INPUT_ELEMENT_DESC構造体に値を設定し,入力レイアウトの定義を行います。次にID3D11Device::CreateInputLayout()メソッドの呼び出しを行い,入力レイアウトを生成します。
続いて,ID3D11Device::IASetInputLayout()メソッドを使用して,入力アセンブラに入力レイアウトを設定させます。セマンティクス名は先ほど述べたように自由につけられますが,
C++のソースコードで定義するセマンティクス名とシェーダコードに書かれているセマンティクス名は一致していなければいけないです。
今回のサンプルでは,位置座標はPOSITIONとし,Direct3D 9で使っていたような慣習的な名前を付けておきました。入力レイアウト設定の処理コードは下記のようになります。
00096: // 入力レイアウトの定義.
00097: D3D11_INPUT_ELEMENT_DESC layout[] = {
00098: { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
00099: };
00100: UINT numElements = ARRAYSIZE( layout );
00101:
00102: // 入力レイアウトを生成.
00103: hr = pDevice->CreateInputLayout(
00104: layout,
00105: numElements,
00106: pVSBlob->GetBufferPointer(),
00107: pVSBlob->GetBufferSize(),
00108: &m_pIL
00109: );
00110: pVSBlob->Release();
00111: pVSBlob = NULL;
00112: if ( FAILED( hr ) )
00113: {
00114: assert( false && "ID3D11Device::CreateInputLayout() Failed." );
00115: return false;
00116: }
00117:
00118: // 入力アセンブラに入力レイアウトを設定.
00119: pDeviceContext->IASetInputLayout( m_pIL );
ID3D11Device::CreateInputLayout()メソッドを呼び終えた後は,頂点シェーダのコンパイルされたコードは必要なくなるので,110行目でRelease()を呼び解放処理を行っています。
次は頂点バッファの生成です。
まずは,頂点データを定義します。今回は,単純に位置座標のみの頂点データを定義しました。定義したら,次はD3D11_BUFFER_DESC構造体を用意して頂点データのサイズ等を設定していきます。
今は頂点バッファを作る目的で構造体を用意しているので,BindFlagsにはD3D11_BIND_VERTEX_BUFFERを設定します。続いてD3D11_SUBRESOURCE_DATA構造体を用意して,頂点データへのポインタを設定しておきます。
サブリソースの設定が済んだら,ID3D11Device::CreateBuffer()メソッドを呼び出して頂点バッファを生成します。頂点バッファを生成したら,ID3D11DeviceContext::IASetVertexBuffers()メソッドを用いて,忘れないうちに入力アセンブラに頂点バッファを設定しておきましょう。
IASetVertexBuffers()メソッドの第1引数は,頂点バッファを設定する最初の入力スロットの番号です。
第2引数は,設定する頂点バッファの配列の要素数を指定します。
第3引数は,設定する頂点バッファの配列を指定します。。
第4引数は,各頂点バッファの要素サイズ(バイト数)を指定する値の配列を指定します。
第5引数は,各頂点バッファにおいて,頂点バッファの最初の要素から最初に使われる要素までのオフセット(バイト数)を指定する値の配列になります。
そして,ID3D11DeviceContext::IASetPrimitiveTopology()メソッドを使用して,プリミティブの種類を設定しましょう。
設定できるプリミティブの種類には次のようなものがあります。
- D3D11_PRIMITIVE_TOPOLOGY_POINTLIST
- D3D11_PRIMITIVE_TOPOLOGY_LINELIST
- D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP
- D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST
- D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP
- D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ
- D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ
- D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ
- D3D11_PRIMITIVE_TOPOLOGY_TRINAGLESTRIPE_ADJ
さて,今説明したことをコードに落とすと下記のようになります。
00015: /////////////////////////////////////////////////////////////////////////////////////////
00016: // CustomVertex structure
00017: /////////////////////////////////////////////////////////////////////////////////////////
00018: struct CustomVertex
00019: {
00020: DirectX::XMFLOAT3 position; //!< 位置座標です.
00021: };
00176: // 頂点バッファの設定.
00177: {
00178: // 頂点の定義.
00179: CustomVertex vertices[] = {
00180: DirectX::XMFLOAT3( 0.0f, 0.5f, 0.0f ),
00181: DirectX::XMFLOAT3( 0.5f, -0.5f, 0.0f ),
00182: DirectX::XMFLOAT3( -0.5f, -0.5f, 0.0f ),
00183: };
00184:
00185: D3D11_BUFFER_DESC bd;
00186: ZeroMemory( &bd, sizeof( D3D11_BUFFER_DESC ) );
00187: bd.Usage = D3D11_USAGE_DEFAULT;
00188: bd.ByteWidth = sizeof( CustomVertex ) * 3;
00189: bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
00190: bd.CPUAccessFlags = 0;
00191:
00192: // サブリソースの設定.
00193: D3D11_SUBRESOURCE_DATA initData;
00194: ZeroMemory( &initData, sizeof( D3D11_SUBRESOURCE_DATA ) );
00195: initData.pSysMem = vertices;
00196:
00197: // 頂点バッファの生成.
00198: hr = pDevice->CreateBuffer( &bd, &initData, &m_pVB );
00199: if ( FAILED( hr ) )
00200: {
00201: assert( false && "ID3D11Device::CreateBuffer() Failed." );
00202: return false;
00203: }
00204:
00205: // 入力アセンブラに頂点バッファを設定.
00206: UINT stride = sizeof( CustomVertex );
00207: UINT offset = 0;
00208: pDeviceContext->IASetVertexBuffers( 0, 1, &m_pVB, &stride, &offset );
00209:
00210: // プリミティブの種類を設定.
00211: pDeviceContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
00212: }
次は定数バッファの設定と,変換行列の設定です。
定数バッファの生成は,先ほどの頂点バッファのようにD3D11_BUFFER_DESC構造体に値を設定していきます。今は定数バッファを設定するのでD3D11_BIND_CONSTANT_BUFFERをBindFlagsに指定しましょう。
ちなみに定数バッファのサイズは4096* 16以下で,更に16の倍数でないといけない…という縛りがありますので注意してください。
処理コードは以下のようになります。
00214: // 定数バッファの生成.
00215: {
00216: // 定数バッファの設定.
00217: D3D11_BUFFER_DESC bd;
00218: ZeroMemory( &bd, sizeof( D3D11_BUFFER_DESC ) );
00219: bd.ByteWidth = sizeof( ConstantBufferForPerFrame );
00220: bd.Usage = D3D11_USAGE_DEFAULT;
00221: bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
00222: bd.CPUAccessFlags = 0;
00223:
00224: // 定数バッファを生成.
00225: hr = pDevice->CreateBuffer( &bd, NULL, &m_pCB );
00226: if ( FAILED( hr ) )
00227: {
00228: assert( false && "ID3D11Device::CreateBuffer() Failed." );
00229: return false;
00230: }
00231: }
00232:
00233: // 行列の設定.
00234: {
00235: // カメラ設定.
00236: DirectX::XMVECTOR camPos = DirectX::XMVectorSet( 0.0f, 0.0f, -0.75f, 0.0f );
00237: DirectX::XMVECTOR camTarget = DirectX::XMVectorSet( 0.0f, 0.0f, 0.0f, 0.0f );
00238: DirectX::XMVECTOR camUpward = DirectX::XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );
00239:
00240: // ビュー行列を設定.
00241: m_View = DirectX::XMMatrixLookAtLH( camPos, camTarget, camUpward );
00242:
00243: // アスペクト比を算出.
00244: FLOAT aspectRatio = (FLOAT)width/(FLOAT)height;
00245:
00246: // 射影行列を算出.
00247: m_Proj = DirectX::XMMatrixPerspectiveFovLH( DirectX::XM_PIDIV2, aspectRatio, 0.01f, 1000.0f );
00248: }
とても長かったですが,これで初期化処理部分が終わりです。
次のセクションからは描画処理についての説明になります。
★ 6.描画処理
いよいよ描画処理ですが,初期化処理に比べればサクッと終わります。
まずは,出力マネージャに設定されているレンダーターゲットと深度ステンシルバッファのバッファクリアを行います。
00612: // ビューをクリアする.
00613: m_pDeviceContext->ClearRenderTargetView( m_pRTV, m_ClearColor );
00614: m_pDeviceContext->ClearDepthStencilView( m_pDSV, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0 );
次に,描画に使用するシェーダをデバイスコンテキストに設定します。
00328: // シェーダを設定して描画.
00329: pDeviceContext->VSSetShader( m_pVS, NULL, 0 );
00330: pDeviceContext->GSSetShader( m_pGS, NULL, 0 );
00331: pDeviceContext->PSSetShader( m_pPS, NULL, 0 );
上記のVSSetShader()などのメソッドですが,第1引数が設定するシェーダオブジェクトで,第2引数がクラスインスタンスインタフェースへの配列のポインタで,第3引数はその配列のサイズとなっています。
続いて,ジオメトリシェーダ内で位置座標の変換処理を行うので,そのための変換行列が入った定数バッファをジオメトリシェーダに設定します。
00333: // 定数バッファの設定.
00334: ConstantBufferForPerFrame cb;
00335: cb.world = m_World;
00336: cb.view = m_View;
00337: cb.proj = m_Proj;
00338:
00339: // サブリソースを更新.
00340: pDeviceContext->UpdateSubresource( m_pCB, 0, NULL, &cb, 0, 0 );
00341:
00342: // ジオメトリシェーダに定数バッファを設定.
00343: pDeviceContext->GSSetConstantBuffers( 0, 1, &m_pCB );
次に,ID3D11DeviceContext::Draw()メソッドを呼び出して描画コマンドを作成します。
00345: // 描画.
00346: pDeviceContext->Draw( 3, 0 );
最後に,IDXGISwapChain::Present()メソッドを呼び出して,描画コマンドを実行し,画面に表示を行います。
00626: // 描画結果を表示します.
00627: m_pSwapChain->Present( 0, 0 );
描画処理は以上で終了です。
★ 7.後始末
最後のセクションは終了処理についてです。
まず最初に,ID3D11DeviceContext::ClearState()を呼び出して,デフォルト状態に戻しましょう。
00302: // ステートをクリアし,デフォルト状態にします.
00303: if ( m_pDeviceContext )
00304: {
00305: m_pDeviceContext->ClearState();
00306: m_pDeviceContext->Flush();
00307: }
デフォルト状態にしたら,あとはひたすらRelease()していきましょう。
00280: // 頂点シェーダを解放.
00281: if ( m_pVS )
00282: {
00283: m_pVS->Release();
00284: m_pVS = NULL;
00285: }
00286:
00287: // ジオメトリシェーダを解放.
00288: if ( m_pGS )
00289: {
00290: m_pGS->Release();
00291: m_pGS = NULL;
00292: }
00293:
00294: // ピクセルシェーダを解放.
00295: if ( m_pPS )
00296: {
00297: m_pPS->Release();
00298: m_pPS = NULL;
00299: }
00300:
00301: // 頂点バッファを解放.
00302: if ( m_pVB )
00303: {
00304: m_pVB->Release();
00305: m_pVB = NULL;
00306: }
00307:
00308: // 入力レイアウトを解放.
00309: if ( m_pIL )
00310: {
00311: m_pIL->Release();
00312: m_pIL = NULL;
00313: }
00314:
00315: // 定数バッファを解放.
00316: if ( m_pCB )
00317: {
00318: m_pCB->Release();
00319: m_pCB = NULL;
00320: }
00409: //------------------------------------------------------------------------------------
00410: // デフォルトのレンダーターゲットを破棄します.
00411: //------------------------------------------------------------------------------------
00412: void DemoApp::ReleaseDefaultRenderTarget()
00413: {
00414: // レンダーターゲットのシェーダリソースビューを解放.
00415: if ( m_pRTSRV )
00416: {
00417: m_pRTSRV->Release();
00418: m_pRTSRV = NULL;
00419: }
00420:
00421: // レンダーターゲットビューを解放.
00422: if ( m_pRTV )
00423: {
00424: m_pRTV->Release();
00425: m_pRTV = NULL;
00426: }
00427:
00428: // レンダーターゲットのテクスチャを解放.
00429: if ( m_pRTT )
00430: {
00431: m_pRTT->Release();
00432: m_pRTT = NULL;
00433: }
00434: }
00534: //------------------------------------------------------------------------------------
00535: // デフォルトの深度ステンシルバッファを解放します.
00536: //------------------------------------------------------------------------------------
00537: void DemoApp::ReleaseDefaultDepthStencil()
00538: {
00539: // 深度ステンシルバッファのシェーダリソースビューを解放します.
00540: if ( m_pDSSRV )
00541: {
00542: m_pDSSRV->Release();
00543: m_pDSSRV = NULL;
00544: }
00545:
00546: // 深度ステンシルビューを解放します.
00547: if ( m_pDSV )
00548: {
00549: m_pDSV->Release();
00550: m_pDSV = NULL;
00551: }
00552:
00553: // 深度ステンシルテクスチャを解放します.
00554: if ( m_pDST )
00555: {
00556: m_pDST->Release();
00557: m_pDST = NULL;
00558: }
00559: }
00312: // レンダーターゲットを解放.
00313: ReleaseDefaultRenderTarget();
00314:
00315: // 深度ステンシルバッファを解放.
00316: ReleaseDefaultDepthStencil();
00317:
00318: // スワップチェインを解放します.
00319: if ( m_pSwapChain )
00320: {
00321: m_pSwapChain->Release();
00322: m_pSwapChain = NULL;
00323: }
00324:
00325: // デバイスコンテキストを解放します.
00326: if ( m_pDeviceContext )
00327: {
00328: m_pDeviceContext->Release();
00329: m_pDeviceContext = NULL;
00330: }
00331:
00332: // デバイスを解放します.
00333: if ( m_pDevice )
00334: {
00335: m_pDevice->Release();
00336: m_pDevice = NULL;
00337: }
あとは,最初にウィンドウを生成した際に登録したウィンドウクラスの登録を解除しておきましょう。
00154: //-----------------------------------------------------------------------------------
00155: // ウィンドウの終了処理を行います.
00156: //-----------------------------------------------------------------------------------
00157: void DemoApp::TermWnd()
00158: {
00159: if ( m_hInst != NULL )
00160: { UnregisterClassA( m_Title.c_str(), m_hInst ); }
00161:
00162: m_Title.clear();
00163: s_pThis = NULL;
00164: }
★ 8.最後に
いやぁ~,すごく長かったですが前回よりは,ちゃんと真面目に説明してみました。
多少端折ったところもありますが,MSDNのドキュメントと合わせてみてもらえれば問題ないかと思います。
ホームページの更新が滞っていたのは,実は今回の説明をするのが非常に面倒かつ手間になるため先延ばしにずっとしていたからだったりします。
まぁ,でもフォント描画とかのモデル描画も控えているので,まだまだ難所が色々とありそうです。
★ Download
本ソースコードおよびプログラムはMIT Licenseに準じます。
プログラムの作成にはMicrosoft Visual Studio 2012 Express Editionを用いています。