Direct3D 11 / DirectX 11 UAV を使った書き込みとブレンド

前回説明したとおり、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 がプリミティブの単位で座標をいじれたり増やしたり出来るのに似ています。

例えばスクリーン座標で左右反転したり、画像を複製したり。

dx11_uav_blend01.jpg

↑これは実際に 1枚のプリミティブを書き込んでいますが、Pixel Shader 内で複製して
100 ピクセル離れた場所にも同時に書き込んでいます。
わかりにくいけど、半透明の背景が同じものになっています。

(2) は現在書き込んでいるフレームバッファの値を事前に読み取って演算出来ることを
意味しています。うまく活用すれば Alpha Blend を自由にシェーダーでプログラム
出来るかもしれません。

結果だけ述べると、さすがに少々無理があったようです。

dx11_uav_blend03.jpg

↑ちらつき

この 4枚のポリゴンは一度の Draw() で描画しています。
Reference Device では期待通り動きますが、RADEON HD 5870 の場合は自分自身との
重なりなど、直前に書き込んだピクセルの値が反映される場合とされない場合があります。
おそらく書き込みバッファがフラッシュされる前に読み込んでいるのだと思います。
プリミティブが大きくて一度の描画面積が大きい場合はおそらくバッファ容量を超えているため
うまく動いているように見えます。

dx11_uav_blend02.jpg

↑自分自身との合成でもうまくいくシーン

以下実際に試したプログラムと解説。

フレームバッファ用のテクスチャを 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 関連