前回説明したとおり、Direct3D 11 の Pixel Shader 5.0 は UAV (Unordered Access View)
を使った書き込みが出来ます。
UAV を RTV (Render Target View) の代わりに用いる利点は次の通り。
(1) 書き込み座標を任意に指定できる
(2) 読み込める
本来 Render Target に書き込む座標はラスタライズ時に決定します。
このスクリーン座標値は Shader Model 5.0 (ps_5_0) の場合 SV_Position で受け取る
ことができます。
UAV は書き込むアドレスを直接指定できるため、ラスタライズ座標以外の場所に点を打てるし
一度に複数の点を書き込むことも可能です。
Geometry Shader がプリミティブの単位で座標をいじれたり増やしたり出来るのに似ています。
例えばスクリーン座標で左右反転したり、画像を複製したり。
↑これは実際に 1枚のプリミティブを書き込んでいますが、Pixel Shader 内で複製して
100 ピクセル離れた場所にも同時に書き込んでいます。
わかりにくいけど、半透明の背景が同じものになっています。
(2) は現在書き込んでいるフレームバッファの値を事前に読み取って演算出来ることを
意味しています。うまく活用すれば Alpha Blend を自由にシェーダーでプログラム
出来るかもしれません。
結果だけ述べると、さすがに少々無理があったようです。
↑ちらつき
この 4枚のポリゴンは一度の Draw() で描画しています。
Reference Device では期待通り動きますが、RADEON HD 5870 の場合は自分自身との
重なりなど、直前に書き込んだピクセルの値が反映される場合とされない場合があります。
おそらく書き込みバッファがフラッシュされる前に読み込んでいるのだと思います。
プリミティブが大きくて一度の描画面積が大きい場合はおそらくバッファ容量を超えているため
うまく動いているように見えます。
↑自分自身との合成でもうまくいくシーン
以下実際に試したプログラムと解説。
フレームバッファ用のテクスチャを DXGI_FORMAT_R32_FLOAT で作っておきます。
SRV, RTV, UAV 全部作ります。
R8G8B8 のカラー値を変換して 32bit 整数値にして R32_FLOAT に書き込んでいます。
asfloat()/asint() を使っているため実質 R32_UINT でも同じです。
モデルデータを描画する場合
1. フレームバッファの値を読み込む
2. 自前でブレンド計算する
3. UAV に書き込む
最後に R32_FLOAT で書き込まれたフレームバッファを RGB に戻して描画します。
モデルを描画するシェーダー。四角形 1枚のみ。
// draw.hlsl
struct VS_INPUT {
uint vIndex : SV_VertexID;
};
struct VS_OUTPUT {
float4 vPos : SV_Position;
float2 vTexcoord : TEXCOORD;
};
struct PS_INPUT : VS_OUTPUT {
float2 uv : TEXCOORD;
};
cbuffer g_buf : register( c0 ) {
float4x4 ViewProjection;
};
// 頂点は VS で生成
VS_OUTPUT vmain_Plane( VS_INPUT vin )
{
float4 vlist[4]= {
{ -1.0f, 1.0f, 1.0f, 1.0f },
{ 1.0f, 1.0f, 1.0f, 1.0f },
{ -1.0f, -1.0f, 1.0f, 1.0f },
{ 1.0f, -1.0f, 1.0f, 1.0f },
};
float2 texlist[4]= {
{ 0.0f, 0.0f, },
{ 1.0f, 0.0f, },
{ 0.0f, 1.0f, },
{ 1.0f, 1.0f, },
};
VS_OUTPUT vout;
float4 pos= vlist[ vin.vIndex ];
vout.vPos= mul( pos, ViewProjection );
vout.vTexcoord= texlist[ vin.vIndex ];
return vout;
}
// パックされたカラーを展開する
float3 pf_to_float3( float pf )
{
uint data= asint( pf );
float3 rcol;
const float tof= 1.0f/255.0f;
rcol.x= ( data & 255) * tof;
rcol.y= ((data>> 8) & 255) * tof;
rcol.z= ((data>>16) & 255) * tof;
return rcol;
}
// カラーを float1 に圧縮する
float float3_to_pf( float3 color )
{
uint3 bcol= (uint3)( color * 255.0f ) & 255;
return asfloat( (bcol.z << 16) + (bcol.y << 8) + bcol.x );
}
SamplerState sample0 : register( s0 );
Texture2D tex0 : register( t0 );
RWTexture2D tex1 : register( u1 );
Texture2D tex2 : register( t1 );
// UAV へレンダリング
float4 pmain_Plane( PS_INPUT pin, float4 fscrpos : SV_POSITION ) : SV_TARGET
{
// 書き込むデータのテクスチャ
float3 data= tex2[ pin.uv ];
// フレームバッファの座標
int2 scrpos= (int2)( fscrpos.xy );
// フレームバッファの内容を読み出す
float3 scrdata= pf_to_float3( tex1[ scrpos ] );
// 適当にブレンドしてみる
data= saturate( data * 0.7f + scrdata * 0.3f );
// UAV へ書き込んでいる
tex1[ scrpos.xy + int2( 100, 0 ) ]= float3_to_pf( data );
tex1[ scrpos.xy + int2( 0, 0 ) ]= float3_to_pf( data );
// RTV へは出力しない
return float4( 0,0,0,0 );
}
draw.hlsl の続き
VS_OUTPUT vmain_Render( VS_INPUT vin )
{
float4 vlist[4]= {
{ -1.0f, 1.0f, 0.0f, 1.0f },
{ 1.0f, 1.0f, 0.0f, 1.0f },
{ -1.0f, -1.0f, 0.0f, 1.0f },
{ 1.0f, -1.0f, 0.0f, 1.0f },
};
float2 texlist[4]= {
{ 0.0f, 0.0f, },
{ 1.0f, 0.0f, },
{ 0.0f, 1.0f, },
{ 1.0f, 1.0f, },
};
VS_OUTPUT vout;
vout.vPos= vlist[ vin.vIndex ];
vout.vTexcoord= texlist[ vin.vIndex ];
return vout;
}
// フレームバッファの値は圧縮されているので展開する
float4 pmain_Render( PS_INPUT pin ) : SV_TARGET
{
float2 size;
tex0.GetDimensions( size.x, size.y );
int2 loc= (int2)( pin.uv * size );
float3 data= pf_to_float3( tex0[ loc ] );
return float4( data, 1.0f );
}
C言語側。リソースの解放、BG の描画は省略しています。
CreateShader(), LoadTexture() の中身も略。
ID3D11Device* iDevice= NULL;
struct BufferSet {
ID3D11Texture2D* iTexture;
ID3D11ShaderResourceView* iSRV;
ID3D11RenderTargetView* iRTV;
ID3D11UnorderedAccessView* iUAV;
public:
BufferSet() :
iTexture( NULL ),
iSRV( NULL ),
iRTV( NULL ),
iUAV( NULL )
{
}
void Create( int width, int height, DXGI_FORMAT buffer_format );
void Release();
};
void BufferSet::Create( int width, int height, DXGI_FORMAT buffer_format )
{
// Texture CD3D11_ ~ は D3D11.h に定義されている
CD3D11_TEXTURE2D_DESC tdesc( buffer_format, width, height, 1, 1,
D3D11_BIND_SHADER_RESOURCE
|D3D11_BIND_RENDER_TARGET
|D3D11_BIND_UNORDERED_ACCESS,
D3D11_USAGE_DEFAULT, 0, 1, 0, 0 );
iDevice->CreateTexture2D( &tdesc, NULL, &iTexture );
// ShaderResourceView も作る
CD3D11_SHADER_RESOURCE_VIEW_DESC vdesc(
D3D11_SRV_DIMENSION_TEXTURE2D, buffer_format, 0, 1, 0, 1, 0 );
iDevice->CreateShaderResourceView( iTexture, &vdesc, &iSRV );
// RenderTargetView も作る
CD3D11_RENDER_TARGET_VIEW_DESC rdesc(
D3D11_RTV_DIMENSION_TEXTURE2D, buffer_format, 0, 0, 1 );
iDevice->CreateRenderTargetView( iTexture, &rdesc, &iRTV );
// UnorderedAccessVieww も作る
CD3D11_UNORDERED_ACCESS_VIEW_DESC uodesc( iTexture,
D3D11_UAV_DIMENSION_TEXTURE2D, DXGI_FORMAT_UNKNOWN, 0, 0, 1 );
iDevice->CreateUnorderedAccessView( iTexture, &uodesc, &iUAV );
}
BufferSet Screen;
ID3D11DeviceContext* iContext= NULL;
ID3D11VertexShader* iVS_Render= NULL;
ID3D11PixelShader* iPS_Render= NULL;
ID3D11VertexShader* iVS_Plane= NULL;
ID3D11PixelShader* iPS_Plane= NULL;
ID3D11ShaderResourceView* iInputImage= NULL;
ID3D11ShaderResourceView* iBGImage= NULL;
void Initialize()
{
Screen.Create( ScreenWidth, ScreenHeight, DXGI_FORMAT_R32_FLOAT );
iVS_Render= CreateShader( "draw.hlsl", "vs_5_0", "vmain_Render" );
iPS_Render= CreateShader( "draw.hlsl", "ps_5_0", "pmain_Render" );
iVS_Plane= CreateShader( "draw.hlsl", "vs_5_0", "vmain_Plane" );
iPS_Plane= CreateShader( "draw.hlsl", "ps_5_0", "pmain_Plane" );
iInputImage= LoadTexture( "plane.bmp" );
iBGImage= LoadTexture( "bgimage.bmp" );
}
// モデル描画
void Draw()
{
CD3D11_VIEWPORT viewp( 0.0f, 0.0f, ScreenWidth, ScreenHeight );
iContext->RSSetViewports( 1, &viewp );
// ブレンドするので Zバッファは使わない
iContext->OMSetDepthStensilState( iZDisable, 0 );
// Unordered Access View を設定する。RTV は NULL
ID3D11RenderTargetView* ZERO_RTV= NULL;
iContext->OMSetRenderTargetsAndUnorderedAccessViews(
1, // NumViews
&ZERO_RTV,
NULL, // depth buffer
1, // UAVStartSlot
1, // NumUAVs
&Screen.iUAV,
NULL
);
iContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP );
iContext->VSSetShader( iVS_Plane, NULL, 0 );
iContext->PSSetShader( iPS_Plane, NULL, 0 );
iContext->PSSetShaderResources( 0, 1, &iInputImage );
iContext->PSSetSamplers( 0, 1, &iSampler );
iContext->Draw( 4, 0 );
}
// フレームバッファ (Screen) の内容を実際に描画する。
// 本来のフレームバッファに転送
void Flush()
{
CD3D11_VIEWPORT viewp( 0.0f, 0.0f, ScreenWidth, ScreenHeight );
iContext->RSSetViewports( 1, &viewp );
iContext->OMSetDepthStensilState( iZDisable, 0 );
iContext->OMSetRenderTargets( 1, &iDefaultRenderTarget, NULL );
iContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP );
iContext->VSSetShader( iVS_Render, NULL, 0 );
iContext->PSSetShader( iPS_Render, NULL, 0 );
iContext->PSSetShaderResources( 0, 1, &Screen.iSRV );
iContext->Draw( 4, 0 );
}
関連エントリ
・Direct3D 11 / DirectX 11 UnorderedAccessView と RenderTarget の関係
・DirectX 11 / Direct3D 11 と RADEON HD 5870 の caps
・Direct3D11/DirectX11 (7) テセレータの流れの基本部分
・その他 Direct3D 11 関連