Frostbiteの”Optimizing the Graphics Pipeline with Compute”を今まで見ていなかったので,資料見たら感銘を受けました。ただ,実際に実装するの怠いな~と思っていたのですが,ふと思い出して,ジオメトリシェーダでカリングを実装してみました。
下記のような感じ。
///////////////////////////////////////////////////////////////////////////////
// CbCulling constant buffer.
///////////////////////////////////////////////////////////////////////////////
cbuffer CbCulling : register(b0)
{
float4 Viewport : packoffset(c0); // xy:(width, height), zw:(topLeftX, topLeftY).
};
//-----------------------------------------------------------------------------
// メインエントリーポイントです.
//------------------------------------------------------------------
[maxvertexcount(3)]
void main
(
triangle VSOutput input[3],
inout TriangleStream< VSOutput > output
)
{
// Orientation Culling.
// ※ラスタライザーステートで時計回りを正とするため,マイナス倍になっていることに注意!
float det = -determinant(float3x3(
input[0].Position.xyw,
input[1].Position.xyw,
input[2].Position.xyw));
if (det <= 0.0f)
{ return; }
// 正規化デバイス座標系(NDC)に変換.
float3 pos0 = input[0].Position.xyz / input[0].Position.w;
float3 pos1 = input[1].Position.xyz / input[1].Position.w;
float3 pos2 = input[2].Position.xyz / input[2].Position.w;
// Frustum Culling.
float3 mini = min(pos0, min(pos1, pos2));
float3 maxi = max(pos0, max(pos1, pos2));
if (any(maxi.xy < -1.0f) || any(mini.xy > 1.0f) || maxi.z < 0.0f || mini.z > 1.0f)
{ return; }
// Small Primitive Culling.
float2 vmin = mini.xy * Viewport.xy + Viewport.zw;
float2 vmax = maxi.xy * Viewport.xy + Viewport.zw;
if (any(round(vmin) == round(vmax)))
{ return; }
// カリングを通過したものだけラスタライザーに流す.
[unroll]
for (uint i=0; i<3; i++)
{ output.Append(input[i]); }
}
RenderDocでキャプチャして,ジオメトリシェーダの出力をチェックしてみました。
正面から見た図。

一応ちゃんとカリングされているようです。
コンピュートシェーダで実装するのが怠い人は,プラットホームによっては全然違いますが,ジオメトリシェーダでパフォーマンス向上するモードがなにかしらあると思うので,そちらで動作させると幸せになれるかもしれません。
※ちなみに手元の環境で計測もしてみたんですが,あまり高速化は見られず処理が格段に重くなりました。環境やシーンにもよると思いますが、暴力的な数のポリゴン数が投入されるシーンでは使わない方が良さそうだなと実感しました。手元のシーンでやってみた感じだと,GSカリングを入れた処理が+3msほど重くなっていて,シーン全体で+12ms程度負荷が増えました。
