Linked Listを利用したOIT - 3
[前回のつづき]
今回は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_RAWとDXGI_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を読んでソートとブレンディングを行うパスについて解説します。
[つづく]
