Linked Listを利用したOIT - 3

 カテゴリー: OIT(Japanese) — Kaori @ 5 月 7th, 2010

[前回のつづき]


今回はLinked List OITの1パスめ、Linked Listの作成パスの実装について解説します。


2.Linked Listの作成パスの実装

OIT11サンプルでは3つのバッファと1つの2Dテクスチャを利用していますが、Linked List OITでは2つのバッファを使用します。

・Fragment Link バッファ
全てのフラグメントデータを格納するためのStructured バッファ。
各フラグメントデータはカラー、深度値に加えて次のフラグメントデータのインデックスを持っています。次のフラグメントデータがない場合、リストの終わりを示す特定の値を格納することになります。今回の実装では0xffffffffを使います。
ピクセルシェーダにおけるフラグメントデータの宣言は以下のようになります。カラーはuintにパックしています。


struct SFragment {
    uint uColor;
    float fDepth;
};

struct SFragmentLink {
    SFragment fragment;
    uint uNext;
};


・Start Offset バッファ
各ピクセルにおけるリストの先頭を指すuintバッファ。毎フレーム描画の前に0xffffffffで初期化されます。


Linked Listの作成パスでは、追加するフラグメントデータをリストの先頭として挿入していきます。つまり、追加されるフラグメントデータが指す次のインデックスにはStart Offsetバッファに含まれていた値を入れ、Start Offset バッファには追加されたフラグメントデータのインデックスを入れます。この部分はスライドのアニメーション"Linked List Creation(2a)~(2d)"を見ると非常にわかりやすいと思います。 


2-a. Linked List作成シェーダの実装

Constant Bufferがそのまま使えるので、Linked Listの作成パスはOIT_PS.hlslを改造して実装することにします。Fragment Link バッファとStart Offsetは以下のように宣言します。両方とも書き込みが必要なのでRW…がつきます。

// Fragment And Link Buffer
RWStructuredBuffer FLBuffer<SFragmentLink> : register( u0 );
// Start Offset Buffer
RWByteAddressBuffer StartOffsetBuffer : register( u1 );

Fragment Linkバッファは構造体のバッファなので、RWStructuredBufferとなります。
Start OffsetバッファはRWByteAddressBufferとして宣言します。描画対象ピクセルの現在の値(リストの先頭のインデックス)を読み、追加するフラグメントデータのインデックスを書き込む(更新する)際にRWByteAddressBuffer::InterlockedExchangeを利用するためです。

つづいてエントリポイント関数の実装です。

[earlydepthstencil]
void StoreFragments( SceneVS_Output input )
{
    uint x = input.pos.x;
    uint y = input.pos.y;

    // Create fragment data.
    uint4 ucolor = saturate( input.color ) * 255;
    SFragmentLink element;
    element.fragment.uColor = (ucolor.x) | (ucolor.y << 8) | (ucolor.z << 16) | (ucolor.a << 24);
    element.fragment.fDepth = input.pos.z;

    // Increment and get current pixel count.
    uint uPixelCount = FLBuffer.IncrementCounter();

    // Read and update Start Offset Buffer.
    uint uIndex = y * g_nFrameWidth + x;
    uint uStartOffsetAddress = 4 * uIndex;
    uint uOldStartOffset;
    StartOffsetBuffer.InterlockedExchange(
        uStartOffsetAddress, uPixelCount, uOldStartOffset );

    // Store fragment link.
    element.uNext = uOldStartOffset;
    FLBuffer[uPixelCount] = element;
}

Fragment Linkバッファの書き込み先のインデックスは、カウンターサポートを利用して計算しています。
また、Start Offset バッファはByteAddressBufferなので、インデックスはバイトオフセットで指定する点に注意が必要です。


2-b. バッファの作成

シェーダができたので、このシェーダに必要なバッファを新たに作成します。バッファ本体とバッファのViewを作成します。RW…バッファはUnordered Access Viewとなります。

以下のようなコードをOIT::OnD3D11ResizedSwapChainに実装して各バッファとUAVを作成します。

// Create Fragment and Link buffer.
descBuf.StructureByteStride = sizeof(float) + sizeof(BYTE) * 4 * 2;
descBuf.ByteWidth = pBackBufferSurfaceDesc->Width * pBackBufferSurfaceDesc->Height * 8 * descBuf.StructureByteStride;
descBuf.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
V_RETURN( pDevice->CreateBuffer( &descBuf, NULL, &m_pFragmentLinkBuffer ));

// Create Start Offset buffer
descBuf.StructureByteStride = 4 * sizeof(BYTE);
descBuf.ByteWidth = pBackBufferSurfaceDesc->Width * pBackBufferSurfaceDesc->Height * descBuf.StructureByteStride;
descBuf.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;
V_RETURN( pDevice->CreateBuffer( &descBuf, NULL, &m_pStartOffsetBuffer ));

// Create Unordered Access Views
D3D11_UNORDERED_ACCESS_VIEW_DESC descUAV;
descUAV.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
descUAV.Buffer.FirstElement = 0;

descUAV.Format = DXGI_FORMAT_UNKNOWN;
descUAV.Buffer.NumElements = pBackBufferSurfaceDesc->Width * pBackBufferSurfaceDesc->Height * 8;
descUAV.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_COUNTER;
V_RETURN( pDevice->CreateUnorderedAccessView( m_pFragmentLinkBuffer, &descUAV, &m_pFragmentLinkUAV ) );

descUAV.Format = DXGI_FORMAT_R32_TYPELESS;
descUAV.Buffer.NumElements = pBackBufferSurfaceDesc->Width * pBackBufferSurfaceDesc->Height;
descUAV.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW;
V_RETURN( pDevice->CreateUnorderedAccessView( m_pStartOffsetBuffer, &descUAV, &m_pStartOffsetUAV ) );

Fragment Linkバッファは全てのフラグメントデータを含まなければならないので、スクリーンサイズよりも大きなサイズが必要です。ここではもともとのOIT11サンプルと同じ、スクリーンサイズの8倍のフラグメントデータを格納できるようにしています。また、Fragment LinkバッファはStructured Bufferなので、作成時にD3D11_RESOURCE_MISC_BUFFER_STRUCTUREDを指定します。

Start Offsetバッファはスクリーンサイズと同じです。また、ByteAddressBufferとして使用するため、D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWSフラグが必要です。

Fragment LinkバッファのUAVはカウンターサポートが必要なので、D3D11_BUFFER_UAV_FLAG_COUNTERを指定します。さらに、StructuredBufferのUAVフォーマットはDXGI_FORMAT_UNKNOWNでなければなりません。

同様に、Start OffsetバッファのUAVはByteAddressBufferとなるのでD3D11_BUFFER_UAV_FLAG_RAWDXGI_FORMAT_R32_TYPELESSを指定します。


2-c. Linked List作成関数の実装

Linked List作成関数はOIT::FillDeepBufferを改造して実装します。Constant Bufferをそのまま利用するためです。
Start Offsetバッファを0xffffffffで初期化し、2つのUnordered Access Viewをシェーダに記述した順にセットします。
カラーレンダーターゲットをセットする必要はありません。

// Clear the start offset buffer by magic value.
// Start Offsetバッファを初期化
static const UINT clearValueUINT[1] = { 0xffffffff };
pD3DContext->ClearUnorderedAccessViewUint( m_pStartOffsetUAV, clearValueUINT );

// Bind UAVs.
// No render target is required.
// UAVのバインド。レンダーターゲットは不要
ID3D11UnorderedAccessView* pUAVs[] = {
m_pFragmentLinkUAV,
m_pStartOffsetUAV,
};
// Initialize the counter with 0.
// カウンターを0で初期化.
UINT anInitIndices[] = { 0, 0 };
pD3DContext->OMSetRenderTargetsAndUnorderedAccessViews( 0, NULL, pDSV, 0, sizeof(pUAVs)/sizeof(pUAVs[0]), pUAVs, anInitIndices );

// Set Pixel Shader and shader constants.
pD3DContext->PSSetShader( m_pCreateFragmentLinkPS, NULL, 0 );

HRESULT hr;
D3D11_MAPPED_SUBRESOURCE MappedResource;
V( pD3DContext->Map( m_pPS_CB, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource ) );
PS_CB* pPS_CB = ( PS_CB* )MappedResource.pData;
pPS_CB->nFrameWidth = m_nFrameWidth;
pPS_CB->nFrameHeight = m_nFrameHeight;
pD3DContext->Unmap( m_pPS_CB, 0 );
pD3DContext->PSSetConstantBuffers( 0, 1, &m_pPS_CB );

pScene->D3D11Render( mWorldViewProjection, pD3DContext );

// Unbind UAVs.
ID3D11UnorderedAccessView* pUAVsNULL[] = { NULL, NULL, NULL, NULL };
pD3DContext->OMSetRenderTargetsAndUnorderedAccessViews( 0, NULL, pDSV, 0, sizeof(pUAVs)/sizeof(pUAVs[0]), pUAVsNULL, NULL );

Unordered Access Viewのセットの際に、カウンターを0で初期化するのを忘れないでください。また、シェーダコードで[earlydepthstencil]を指定した場合、描画の前にdepth writeをOFFにする必要があります。これをしないと一部のフラグメントデータがZテストではじかれてしまい、正しく描画できません。



これでLinked Listの作成ができました。次回は2パス目、Linked Listを読んでソートとブレンディングを行うパスについて解説します。


[つづく]

ホットワード 利用 カテゴリー 前回 作成 実装
割引クーポンまとめ情報 - クー割