Linked Listを利用したOIT - 4
[前回のつづき]
今回は2パス目、フラグメントのソートと描画パスの実装についての解説です。
3. ソートと描画パスの実装
このパスではピクセルごとにLinked Listをたどって全てのフラグメントデータを取り出し、深度値でソートして奥のフラグメントから順にブレンディング、結果をフレームバッファに出力します。
3-a. シェーダの実装
新しいhlslファイルを作成します。今回はピクセルシェーダだけでなくフルスクリーンを描画するための頂点シェーダも必要です。
ピクセルシェーダでは全てのフラグメントデータをテンポラリ配列に一度コピーし、それから深度値でソートします。これはメモリアクセスを抑えるためです。
StructuredBuffer<SFragmentLink> FragmentLinkSRV : register(t0);
Buffer StartOffsetSRV : register(t1);
struct QuadVS_Output
{
float4 pos : SV_POSITION;
};
float4 SortFragmentsPS( QuadVS_Output _input ) : SV_Target0
{
uint uIndex = (uint)_input.pos.y * g_nFrameWidth + (uint)_input.pos.x;
// Read and store linked list data to the tempolary buffer.
SFragment aData[32];
int anIndex[32];
uint uNumFragment = 0;
uint uNext = StartOffsetSRV[uIndex];
while ( uNext != 0xFFFFFFFF ) {
SFragmentLink element = FragmentLinkSRV[uNext];
aData[uNumFragment] = element.fragment;
anIndex[uNumFragment] = uNumFragment;
++uNumFragment;
uNext = element.uNext;
}
uint N2 = 1 << (int)(ceil(log2(uNumFragment)));
// fill initial data
for(int i = uNumFragment; i < N2; i++)
{
anIndex[i] = i;
aData[i].fDepth = 1.1f;
}
// Bitonic sort. copied from OIT_CS.hlsl
for( int k = 2; k >1; j > 0 ; j = j>>1 )
{
for( int i = 0; i i )
{
float dixj = aData[ anIndex[ ixj ] ].fDepth;
if ( ( i&k ) == 0 && di > dixj )
{
int temp = anIndex[ i ];
anIndex[ i ] = anIndex[ ixj ];
anIndex[ ixj ] = temp;
}
if ( ( i&k ) != 0 && di < dixj )
{
int temp = anIndex[ i ];
anIndex[ i ] = anIndex[ ixj ];
anIndex[ ixj ] = temp;
}
}
}
}
}
// Output the final result to the frame buffer
// Accumulate fragments into final result
float4 result = 0.0f;
for( int x = uNumFragment-1; x >= 0; x-- )
{
uint uColor = aData[ anIndex[ x ] ].uColor;
float4 color;
color.r = ( (uColor >> 0) & 0xFF ) / 255.0f;
color.g = ( (uColor >> 8) & 0xFF ) / 255.0f;
color.b = ( (uColor >> 16) & 0xFF ) / 255.0f;
color.a = ( (uColor >> 24) & 0xFF ) / 255.0f;
result = lerp( result, color, color.a );
}
result.a = 1.0f;
return result;
}
Start Offset バッファはここではふつうにuintバッファとして参照されます。OIT11サンプルでは不透明プリミティブが存在しません。なので、ブレンディングの初期値はクリアカラー=0となっています。実際には不透明プリミティブを描画したテクスチャを初期値とするべきです。また、テンポラリ配列を使用しているため、1つのピクセルが対処できるフラグメントの数に制限がかかります。つまりこの例だと32個しかフラグメントを持つことはできません。このプログラムはかなり適当なのですが、本当はフラグメントデータのコピー時にセーフティーを入れる必要があります。でないとオーバーランする可能性があります。
3-b. Shader Resource Viewの追加
このパスでは2つのバッファはShader Resource Viewとして参照されます。OIT::OnD3D11ResizedSwapChain関数に以下のコードを追加して、SRVを作っておきます。
// Create Shader Resource Views
D3D11_SHADER_RESOURCE_VIEW_DESC descSRV;
descSRV.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
descSRV.Buffer.FirstElement = 0;
descSRV.Format = DXGI_FORMAT_UNKNOWN;
descSRV.Buffer.NumElements = pBackBufferSurfaceDesc->Width * pBackBufferSurfaceDesc->Height * 8;
V_RETURN( pDevice->CreateShaderResourceView( m_pFragmentLinkBuffer, &descSRV, &m_pFragmentLinkSRV ) );
descSRV.Format = DXGI_FORMAT_R32_UINT;
descSRV.Buffer.NumElements = pBackBufferSurfaceDesc->Width * pBackBufferSurfaceDesc->Height;
V_RETURN( pDevice->CreateShaderResourceView( m_pStartOffsetBuffer, &descSRV, &m_pStartOffsetSRV ) );
ついでに、フルスクリーン描画のための頂点バッファと入力レイアウトをOIT::OnD3D11CreateDevice関数内で作っておきましょう。
3-c.ソートと描画関数の実装
OIT::SortAndRender関数に作成した頂点バッファと入力レイアウト、Shader Resource Viewをセットして描画するコードを実装します。
ID3D11ShaderResourceView* ppSRVs[] = {
m_pFragmentLinkSRV,
m_pStartOffsetSRV,
};
pD3DContext->PSSetShaderResources( 0, sizeof(ppSRVs)/sizeof(ppSRVs[0]), ppSRVs );
pD3DContext->VSSetShader( m_pSortAndRenderVS, NULL, 0 );
pD3DContext->PSSetShader( m_pSortAndRenderPS, NULL, 0 );
// Draw a screen quad by a large triangle.
pD3DContext->IASetInputLayout( m_pIL );
UINT uStrides = sizeof( SQuadVertex );
UINT uOffsets = 0;
pD3DContext->IASetVertexBuffers( 0, 1, &m_pVB, &uStrides, &uOffsets );
pD3DContext->IASetIndexBuffer( NULL, DXGI_FORMAT_R32_UINT, 0 );
pD3DContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP );
pD3DContext->Draw( 3, 0 );
// Unbind SRVs
ID3D11ShaderResourceView* ppSRVNULL[] = {
NULL,
NULL,
};
pD3DContext->PSSetShaderResources( 0, sizeof(ppSRVs)/sizeof(ppSRVs[0]), ppSRVNULL );
あともちろんこのパスではZテストを無効にしましょう。
4. まとめ
Linked List OITは高速でしかも実装が簡単でした。ただし、今回の実装はOIT11との速度比較が目的だったこともあり、あまり実用的とはいえません。たとえば不透明プリミティブを無視している、ブレンディングモードが1種類に限定されている、アンチエイリアシングはサポートしていないなどです。今後時間があればもうすこし実用的な実装も試してみたいです。
