Microsoft Visual C++ 2010 Express Microsoft DirectX SDK (February 2010) Direct3D 11.0 |
■Direct3D 11.0 ポリゴン描画 | Prev Top Next |
関連ページ:Direct3D 11.0 初期化 |
|
今回は四角ポリゴン1個、描画します。 Direct3D 9 ではこの程度の描画だったら固定機能パイプラインを使用するところですが、Direct3D 10 から固定機能パイプラインが廃止されたため描画処理にはシェーダーを使用することが 必須となりました。まあこの辺の理由が初心者にはつらいところだと思います。シェーダーをやらないのであれば Direct3D 9 を使用したほうがいいと思う。
ではソースを見ていきます。前回解説したソースに修正を加えていきますので見てない人はDirect3D 11.0 初期化をまず参照してください。 今回は、自作クラス D3D11USER も修正しています。ですので既にDirect3D 11.0 初期化をコピペしている方も参照してください。 このページでは main.cpp のみ紹介します。
あとシェーダーについてですが、今回は Direct3D 9 のサンプル作成時に使用したエフェクトは使用しないで、頂点シェーダーとピクセルシェーダーを 個別にコンパイルし、個別にシェーダーオブジェクトを作成する方式をとります。
---main.cpp---
#include "../../USER/DX11User.h" #include "../../USER/D3D11User.h" // コンパイル済み頂点シェーダー #include "../../USER/HLSL/SimpleHLSL02_vs.h" // コンパイル済みピクセルシェーダー #include "../../USER/HLSL/SimpleHLSL02_ps.h" // シェーダーオブジェクトを作成するとき、ファイルから読むか、メモリから読むかを切り替える #if defined(DEBUG) || defined(_DEBUG) #define UNCOMPILED_SHADER // ファイルを読み込んでコンパイルする #endif // アプリケーション名 TCHAR* AppName = _T("DX11_Tutrial02"); // Direct3D関連の自作クラス D3D11USER* g_pD3D11User = NULL; // 頂点バッファ ID3D11Buffer* g_pVertexBuffer = NULL; // インデックスバッファ ID3D11Buffer* g_pIndexBuffer = NULL; // 頂点シェーダー ID3D11VertexShader* g_pVertexShader = NULL; // 入力レイアウト ID3D11InputLayout* g_pLayout = NULL; // ピクセルシェーダー ID3D11PixelShader* g_pPixelShader = NULL; // 頂点定義 struct VERTEX { // 頂点座標 D3DXVECTOR3 pos; // 頂点カラー D3DXCOLOR color; }; // 節電モードの制御に使用する変数。 bool Activate = true; // ウィンドウがアクティブか bool StandBy = false; // スタンバイ状態か // リソースの初期化 HRESULT Init() { HRESULT hr = E_FAIL; // 頂点座標と頂点カラーを設定 //VERTEX v[] = { D3DXVECTOR3( -5.0f, 5.0f, 0.0f ), 0xFFFF0000, // D3DXVECTOR3( 5.0f, 5.0f, 0.0f ), 0xFF00FF00, // D3DXVECTOR3( -5.0f, -5.0f, 0.0f ), 0xFF0000FF, // D3DXVECTOR3( 5.0f, -5.0f, 0.0f ), 0xFFFF00FF, //}; VERTEX v[] = { D3DXVECTOR3( 5.0f, 5.0f, 0.0f ), 0xFFFF0000, D3DXVECTOR3( -5.0f, 5.0f, 0.0f ), 0xFF00FF00, D3DXVECTOR3( 5.0f, -5.0f, 0.0f ), 0xFF0000FF, D3DXVECTOR3( -5.0f, -5.0f, 0.0f ), 0xFFFF00FF, }; // 頂点バッファを作成する hr = g_pD3D11User->CreateVertexBuffer( &g_pVertexBuffer, v, sizeof( v ), 0 ); if( FAILED( hr ) ) goto EXIT; // インデックスバッファを設定 UINT Index[] = { 0, 1, 2, 3 }; // インデックスバッファを作成する。 hr = g_pD3D11User->CreateIndexBuffer( &g_pIndexBuffer, Index, sizeof( Index ), 0 ); if( FAILED( hr ) ) goto EXIT; // 頂点シェーダーを作成する D3D11_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; #ifndef UNCOMPILED_SHADER hr = g_pD3D11User->CreateVertexShaderFromMemory( &g_pVertexShader, g_VS_Main, sizeof( g_VS_Main ), &g_pLayout, layout, _countof( layout ) ); if( FAILED( hr ) ) goto EXIT; #else hr = g_pD3D11User->CreateVertexShaderFromFile( &g_pVertexShader, _T("../../USER/HLSL/SimpleHLSL02.hlsl"), "VS_Main", "vs_4_0", &g_pLayout, layout, _countof( layout ) ); if( FAILED( hr ) ) goto EXIT; #endif // ピクセルシェーダーを作成する #ifndef UNCOMPILED_SHADER hr = g_pD3D11User->CreatePixelShaderFromMemory( &g_pPixelShader, g_PS_Main, sizeof( g_PS_Main ) ); if( FAILED( hr ) ) goto EXIT; #else hr = g_pD3D11User->CreatePixelShaderFromFile( &g_pPixelShader, _T("../../USER/HLSL/SimpleHLSL02.hlsl"), "PS_Main", "ps_4_0" ); if( FAILED( hr ) ) goto EXIT; #endif hr = S_OK; EXIT: return hr; } // メモリ開放 void Invalidate() { SAFE_RELEASE( g_pVertexShader ); SAFE_RELEASE( g_pPixelShader ); SAFE_RELEASE( g_pLayout ); SAFE_RELEASE( g_pIndexBuffer ); SAFE_RELEASE( g_pVertexBuffer ); SAFE_DELETE( g_pD3D11User ); } // 描画処理 HRESULT Render() { HRESULT hr = E_FAIL; D3DXMATRIX matWorld, matView, matProj, matWVP; float ClearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; // バックバッファをクリア // ID3D11DeviceContext::ClearRenderTargetView g_pD3D11User->m_D3DDeviceContext->ClearRenderTargetView( g_pD3D11User->m_RenderTargetView, ClearColor ); // 深度バッファをクリア // ID3D11DeviceContext::ClearDepthStencilView if( g_pD3D11User->m_DepthStencilView ) g_pD3D11User->m_D3DDeviceContext->ClearDepthStencilView( g_pD3D11User->m_DepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0 ); // 頂点バッファ設定 // ID3D11DeviceContext::IASetVertexBuffers UINT stride = sizeof( VERTEX ); UINT offset = 0; g_pD3D11User->m_D3DDeviceContext->IASetVertexBuffers( 0, 1, &g_pVertexBuffer, &stride, &offset ); // インデックスバッファ設定 // ID3D11DeviceContext::IASetIndexBuffer g_pD3D11User->m_D3DDeviceContext->IASetIndexBuffer( g_pIndexBuffer, DXGI_FORMAT_R32_UINT, 0 ); // レイアウト設定 // ID3D11DeviceContext::IASetInputLayout g_pD3D11User->m_D3DDeviceContext->IASetInputLayout( g_pLayout ); // プリミティブ タイプおよびデータの順序に関する情報を設定 // ID3D11DeviceContext::IASetPrimitiveTopology // D3D11_PRIMITIVE_TOPOLOGY g_pD3D11User->m_D3DDeviceContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP ); // 行列設定 D3DXMatrixPerspectiveFovLH( &matProj, 3.1415926f / 2.0f, 4.0f / 3.0f, 1.0f, 100.0f ); D3DXMatrixLookAtLH( &matView, &D3DXVECTOR3( 0.0f, 0.0f, -10.0f ), &D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), &D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) ); static float Y = 0.0f; Y += 0.01f; D3DXMatrixRotationY( &matWorld, D3DXToRadian( Y ) ); matWVP = matWorld * matView * matProj; // シェーダー内では列優先にしているので転置行列を作成する。 // 成分ごとの演算 (DirectX HLSL) D3DXMatrixTranspose( &matWVP, &matWVP ); ID3D11Buffer* Buffers = NULL; // 定数バッファを作成する。 hr = g_pD3D11User->CreateConstantBuffer( &Buffers, NULL, sizeof( D3DXMATRIX ), D3D11_CPU_ACCESS_WRITE ); if( FAILED( hr ) ) goto EXIT; // D3D11_MAPPED_SUBRESOURCE D3D11_MAPPED_SUBRESOURCE mappedResource; // サブリソースに格納されているデータへのポインターを取得して、そのサブリソースへの GPU のアクセスを拒否。 // ID3D11DeviceContext::Map hr = g_pD3D11User->m_D3DDeviceContext->Map( Buffers, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource ); if( FAILED( hr ) ) goto EXIT; ::CopyMemory( mappedResource.pData, &matWVP, sizeof( D3DXMATRIX ) ); g_pD3D11User->m_D3DDeviceContext->Unmap( Buffers, 0 ); // 頂点シェーダーをデバイスに設定する。 // ID3D11DeviceContext::VSSetShader g_pD3D11User->m_D3DDeviceContext->VSSetShader( g_pVertexShader, NULL, 0 ); // 頂点シェーダーに定数バッファを設定する。 // ID3D11DeviceContext::VSSetConstantBuffers g_pD3D11User->m_D3DDeviceContext->VSSetConstantBuffers( 0, 1, &Buffers ); // ハルシェーダーを無効にする。 // ID3D11DeviceContext::HSSetShader g_pD3D11User->m_D3DDeviceContext->HSSetShader( NULL, NULL, 0 ); // ドメインシェーダーを無効にする。 // ID3D11DeviceContext::DSSetShader g_pD3D11User->m_D3DDeviceContext->DSSetShader( NULL, NULL, 0 ); // ジオメトリシェーダーを無効にする。 // ID3D11DeviceContext::GSSetShader g_pD3D11User->m_D3DDeviceContext->GSSetShader( NULL, NULL, 0 ); // ピクセルシェーダーをデバイスに設定する。 // ID3D11DeviceContext::PSSetShader g_pD3D11User->m_D3DDeviceContext->PSSetShader( g_pPixelShader, NULL, 0 ); // コンピュートシェーダーを無効にする。 // ID3D11DeviceContext::CSSetShader g_pD3D11User->m_D3DDeviceContext->CSSetShader( NULL, NULL, 0 ); ID3D11RasterizerState* pRasterState = NULL; // ラスタライザの設定を取得。 g_pD3D11User->m_D3DDeviceContext->RSGetState( &pRasterState ); D3D11_RASTERIZER_DESC RasterDesc; pRasterState->GetDesc( &RasterDesc ); SAFE_RELEASE( pRasterState ); // 両面描画に変更 RasterDesc.CullMode = D3D11_CULL_NONE; g_pD3D11User->m_D3DDevice->CreateRasterizerState( &RasterDesc, &pRasterState ); g_pD3D11User->m_D3DDeviceContext->RSSetState( pRasterState ); SAFE_RELEASE( pRasterState ); // インデックスバッファを使用した描画 // ID3D11DeviceContext::Draw // g_pD3D11User->m_D3DDeviceContext->Draw( 4, 0 ); <- インデックスバッファを使用しない場合はこれ // ID3D11DeviceContext::DrawIndexed g_pD3D11User->m_D3DDeviceContext->DrawIndexed( 4, 0, 0 ); // レンダリングされたイメージをユーザーに表示。 hr = g_pD3D11User->m_SwapChain->Present( 0, 0 ); EXIT: SAFE_RELEASE( Buffers ); return hr; } // 節電処理および描画処理 // DXGI の概要 HRESULT PowerSavingAndRender() { HRESULT hr = E_FAIL; switch( StandBy ) { // スタンバイモード case true: // テストのみ行い、描画処理は行わない。 hr = g_pD3D11User->m_SwapChain->Present( 0, DXGI_PRESENT_TEST ); switch( hr ) { // いまだスタンバイ中。。。 case DXGI_STATUS_OCCLUDED: // 電源管理によるスリープ状態の場合ここにくる。 // フルスクリーンモード時にスクリーンセーバーが起動時した場合は、表示モードが強制的にウィンドウモードに変更されるためここにこない。 goto EXIT; break; case S_OK: // フルスクリーンモード時にスクリーンセーバーが起動時した場合は表示モードが強制的にウィンドウモードに変更される。 // ウィンドウモードの場合スタンバイから復帰してしまうため、ウィンドウがアクティブになったときに復帰するようにする。 if( Activate == true ) { // たまにウィンドウが表示されないときがあるので表示するようにする ::ShowWindow( g_pD3D11User->m_hWnd, SW_SHOW ); StandBy = false; } break; default: goto EXIT; break; } break; // スタンバイモードでない case false: // 描画処理 hr = Render(); if( FAILED( hr ) ) goto EXIT; switch( hr ) { case DXGI_STATUS_OCCLUDED: // スタンバイモードへ移行 // フルスクリーンモード時のスクリーンセーバー起動時、 // スリープ状態に移行した時に発生する。 StandBy = true; goto EXIT; break; case S_OK: break; default: goto EXIT; break; } break; } hr = S_OK; EXIT: return hr; } // ウィンドウプロシージャ LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, UINT wParam, LONG lParam ) { switch( msg ) { case WM_KEYUP: // アプリ終了 if( wParam == VK_ESCAPE ) ::DestroyWindow( hWnd ); // F2キーを押すと、ウィンドウモードを切り替える。 // 自動的にウィンドウモードを切り替える機能もあるが、ウィンドウスタイルを自由に変更するために自分で実装することにした。 if( wParam == VK_F2 ) { g_pD3D11User->ChangeWindowMode(); } break; case WM_ACTIVATE: Activate = true; break; // フルスクリーンからウィンドウモードに切り替えるとき WM_SIZE イベントが発生せず、結果的に IDXGISwapChain::ResizeBuffers がコールされない。 // 環境にもよるようだが、画面上に何も表示されない現象が発生する可能性があるので // D3D11USER::ChangeWindowModeOptimization() は D3D11USER::ChangeWindowMode() 内でコールするように修正し、ここでの処理は無効にする // case WM_SIZE: // g_pD3D11User->ChangeWindowModeOptimization(); // break; case WM_DESTROY: Invalidate(); ::PostQuitMessage(0); break; default: return ::DefWindowProc( hWnd, msg, wParam, lParam ); } return 0L; } // メイン関数 int APIENTRY _tWinMain( HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR /*lpCmpLine*/, INT /*nCmdShow*/ ) { HRESULT hr = E_FAIL; MSG msg; ::ZeroMemory(&msg, sizeof(MSG)); // 表示モードを記述するための構造体。 // DXGI_MODE_DESC DXGI_MODE_DESC sd; // Direct3D 関連自作クラスのインスタンスを作成 g_pD3D11User = new D3D11USER(); // ディスプレイモード一覧を取得する。 // 取得した値はクラス内部に保持される。 hr = g_pD3D11User->GetDisplayMode(); if( FAILED( hr ) ) { ::MessageBox( NULL, _T("ディスプレイモード取得エラー"), _T("初期化エラー"), MB_OK ); goto EXIT; } // とりあえず最初に見つかったディスプレイモードを選択する CopyMemory( &sd, &g_pD3D11User->m_DisplayModeDesc[0], sizeof( DXGI_MODE_DESC ) ); // ウィンドウの作成およびDirect3D の初期化 hr = g_pD3D11User->InitD3D11( AppName, hInstance, WndProc, &sd, TRUE, TRUE, TRUE, TRUE ); if( FAILED( hr ) ) { ::MessageBox( NULL, _T("Direct3D 11.0 初期化エラー"), _T("初期化エラー"), MB_OK ); goto EXIT; } // リソースの初期化 hr = Init(); if( FAILED( hr ) ) { ::MessageBox( NULL, _T("リソース初期化エラー"), _T("初期化エラー"), MB_OK ); goto EXIT; } ::ShowWindow(g_pD3D11User->m_hWnd, SW_SHOW); ::UpdateWindow(g_pD3D11User->m_hWnd); // メッセージループ do { if( ::PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } else { hr = PowerSavingAndRender(); if( FAILED( hr ) ) ::DestroyWindow( g_pD3D11User->m_hWnd ); } }while( msg.message != WM_QUIT ); EXIT: if( g_pD3D11User && g_pD3D11User->m_hWnd ) ::DestroyWindow( g_pD3D11User->m_hWnd ); ::UnregisterClass( AppName, hInstance ); return msg.wParam; }一応全部アップしました。ですが、前回のと比べて修正している箇所はヘッダのインクルード指定や変数の宣言、 Init()関数、Invalidate()関数、Render()関数だけです。
ポイントを順に説明していきます。
●コンパイル済みシェーダーのインクルード
// コンパイル済み頂点シェーダー #include "SimpleHLSL_vs.h" // コンパイル済みピクセルシェーダー #include "SimpleHLSL_ps.h"fxc.exeツールにてあらかじめコンパイルしてあるシェーダーのバイナリデータをインクルードします。 fxc.exeは DirectX SDK に添付されているコンパイラツールです。コマンドプロンプト上でコンパイルします。 頂点シェーダーをコンパイルする場合のコマンドのサンプルは次のようになります。
fxc /T vs_4_0 /E VS_Main /Fh "SimpleHLSL_vs.h" "SimpleHLSL.hlsl"
オプションについては構文を参照してください( Direct3D 10 だけど )。
注意点としてはまず /T オプション。これはシェーダーのバージョンを指定します。vs_4_0 としていますが、下位バージョンは使用できません。
正確にいうと、コンパイルはできるが、シェーダーオブジェクトの作成時にエラーになります。もちろん上位バージョンである vs_5_0 は使用できます。
下位バージョンでコンパイルしたものを使用する方法もあるようですが試していません。
あと Direct3D 9 のときのサンプルではエフェクトを使用して頂点シェーダーとピクセルシェーダーをまとめてコンパイルしたためパラメータに fx_ を使用していましたが、
今回は個別にコンパイルする方法をためすためにパラメータを fx_ から vs_ と ps_ に変更しています。
エフェクトが使用できるのかは調査していませんが、fx_4_0 があるので可能だと思います。
次に/Eオプション。どうも必須なようです。 エフェクトを使用したときはシェーダーファイル内で technique キーワードを指定し、その中にエントリーポイントを記述したため コンパイルオプションにエントリーポイントを指定する必要はなかったのですが、 個別にコンパイルする場合は、technique キーワード内で指定したエントリーポイントは無視されるようです。
最後にfxcコンパイラで作成されるヘッダファイル内にいつのまにかアセンブラコードも同時に出力されるようになりました。 最適化するためのいい判断材料になるのではないでしょうか?
●Init()関数
ここでは頂点バッファ、インデックスバッファ、頂点シェーダー、ピクセルシェーダー、入力レイアウト( Direct3D 9 のときは頂点シェーダ宣言といってたやつ。なぜ呼び方変える?)
の作成を行っています。
●Invalidate()関数
ただのメモリ開放。
●Render()関数
描画処理。メンバ関数名だけみると Direct3D 9 とまったく違うように見えますが、処理の大きな流れは同じです。
ポイントだけ説明します。
まず行列。行列作成に使用する関数は Direct3D 11 に存在しないので、Direct3D 9 のときに使用していた関数を使用します。 次に D3DXMatrixLookAtLH ですが第2、3、4引数でワーニングエラーになります。 うっとうしいので DX11User.h で #pragma warning(disable: 4238) を指定して抑制します。
次が重要。D3DXMatrixTranspose。これは転置行列を作成する関数です。転置行列は行と列を入れ替えた行列のことですが、詳細はネットで検索してください。
今回のサンプルでは転置行列で変換した行列をシェーダーにバインドする必要があります。
この辺の説明をする前にまず行列の順序について説明します。行列の順序には行優先と列優先があります。
詳細は成分ごとの演算 (DirectX HLSL)を参照してください。
ようするに行列にデータを格納するときに行と列のどちらから格納していくかのルールといった感じでしょうか。
行優先の場合、行方向に順にデータを格納していきます。(図1)
図1
これをみると D3DXMATRIX は行優先でレイアウトされていることがわかると思います。
次に、当サイトで解説したきた Direct3D 9 関連のサンプルの場合について説明します。
このサンプルでは、行列をシェーダーにバインドするときに ID3DXEffect::SetMatrix を使用していましたが、この関数のパラメータの行列は行優先で
設定することとなっています。そのため、転置行列を使用せずにそのままバインドします。
さらにシェーダー内で頂点座標を行列変換する mul 関数を使用する際、
行優先の行列を使用して変換するため、行ベクトルとして計算するように指定していました。
図2
今回解説するサンプルではエフェクトを使用しないため、コンパイル時の設定、またはシェーダー内の float4x4 型の変数の設定によって行列の順序が決定されます。 両方設定している場合、float4x4 型の変数の設定が優先されるようです。また設定していない場合デフォルト値となり列優先となります。 行列の順序はどちらにしてもいいのですが、D3D10_SHADER 定数に 列優先の場合、内積を使用してベクトルと行列の乗算が可能になるため効率がいいと記述されています。 ですので Direct3D11 のサンプルでは基本的に、列優先を使用するようにします。Direct3D 9 のときはぶっちゃけ行列の順序のことなど気にしてませんでしたけどね(笑)。
では今回使用する行列の使用方法を順を追って説明します。 まず上で説明したとおり D3DXMATRIX は行優先です。 これを転置行列変換するため、行と列が入れ替わります。 その行列をシェーダーにバインドします。このとき列優先でバインドするため、列から順に格納していきます。 つまりシェーダー内の行列のレイアウトは 行優先 となります。 行優先なので、mul 関数を使用して 頂点座標を行列変換するときはベクトルを行ベクトルとして計算するようにします。うーん…うまく説明できん。行列を紙に書いて処理を追っていったほうがいいかも。 あと説明これであってると思うけど、間違ってたら教えてくれ。
---SimpleHLSL02.hlsl---
// ワールド行列 × ビュー × 射影行列 cbuffer cbMatrixWVP : register( b0 ) { // 列優先 column_major float4x4 g_matWVP : packoffset( c0 ); }; // 頂点シェーダーの入力パラメータ struct VS_IN { float3 pos : POSITION; // 頂点座標 float4 color : COLOR; // 頂点カラー }; // 頂点シェーダーの出力パラメータ struct VS_OUT { float4 pos : SV_POSITION; float4 color : COLOR0; }; // 頂点シェーダー VS_OUT VS_Main( VS_IN In ) { VS_OUT Out; Out.pos = mul( float4( In.pos, 1 ), g_matWVP ); Out.color = In.color; return Out; } // ピクセルシェーダ float4 PS_Main( VS_OUT In ) : SV_TARGET { return In.color; }
頂点シェーダーとピクセルシェーダーです。ライティングしないテクスチャーマッピングしないシンプルなシェーダーです。
定数バッファについては説明の必要があるけど、疲れたのでとりあえずシェーダー定数 (DirectX HLSL)を参照してください。
セマンティクスが変更されています。 セマンティクス (DirectX HLSL)を参照してください。