期待している動作:
r.SSR.Stencilを有効にすると、計算しない領域にStencilをマークする処理が追加、その後SSR処理本体 に入りEarlyStencilにより重いPixelShader処理をする動作が本来期待している動作になります。
ロジック的なミスと思われるコードに関する説明:
Engine\Shaders\Private\SSRT\SSRTReflections.usf
のStencilSetupの動作に関してですが
void ScreenSpaceReflectionsStencilPS(内の
#if !defined(TILE_COMPUTE_SSR)
if (RoughnessFade > 0.0 && bNoMaterial)
{
// we are going to compute SSR for this pixel, so we discard this
// pixel shader invocation to not overwrite the stencil buffer and
// tehrefore execute ScreenSpaceReflectionsPS() for this pixel.
discard;
}
#endif
// we are not going to compute SSR for this pixel, so we clear the color
// since ScreenSpaceReflectionsPS() won’t be executed in this pixel.
OutColor = 0;
#if SSR_OUTPUT_FOR_DENOISER
OutClosestHitDistance = DENOISER_INVALID_HIT_DISTANCE;
#endif
コメントを読んで頂くと、出力した部分が計算を行わない領域になります。
出力されたStencilは以下のようになっており、結果が逆のように見えます。
また本来roughnessが高いところは棄却されるべき部分なため空以外が完全にきれいにこの状態になるのも動作としておかしい状態です。
[Image Removed]
これがSSR本体時のStencilTestになります。
実際棄却されている部分は出力された側の空になっております。
[Image Removed]
ではSSR本体を呼び出す際のRenderStateを設定している部分のソースを読んでみます。
Engine\Source\Runtime\Renderer\Private\ScreenSpaceRayTracing.cpp
if (SSRStencilPrePass)
{
// Clobers the stencil to pixel that should not compute SSR
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always, true, CF_Equal, SO_Keep, SO_Keep, SO_Keep>::GetRHI();
}
StencilTest部分にCF_Equalが設定されておりますが、計算しない部分がマークされているわけですからここはCF_NotEqualが正しいと思われます。
単純に逆にすると空が計算対象になってしまうことからStencilSetUpの段階からおかしいことが分かります。
ScreenSpaceReflectionsStencilPSシェーダーでStencilをマークしてない側、途中で棄却されている側がScreenSpaceReflectionsシェーダーで計算される領域です。
条件をもう一度、もう少し手前のコードから見てみます。
const float Roughness = GetRoughness(GBuffer);
const bool bNoMaterial = GBuffer.ShadingModelID == 0;
#endif // SUBTRATE_GBUFFER_FORMAT==1
const float RoughnessFade = GetRoughnessFade(Roughness);
#if !defined(TILE_COMPUTE_SSR)
if (RoughnessFade > 0.0 && bNoMaterial)
{
// we are going to compute SSR for this pixel, so we discard this
// pixel shader invocation to not overwrite the stencil buffer and
// tehrefore execute ScreenSpaceReflectionsPS() for this pixel.
discard;
}
#endif
bNoMaterialはUnlitなため計算しない領域になるはず
if (RoughnessFade > 0.0 && bNoMaterial)
StencilTestを意図通りに修正した場合、この条件だとUnlitマテリアルかつroughnessFadeが高いところが計算する領域 という形になってしまいます。
またScreenSpaceReflectionsシェーダー側で棄却するコードは
float RoughnessFade = GetRoughnessFade(Roughness);
// Early out. Useless if using the stencil prepass.
BRANCH if( RoughnessFade <= 0.0 || bNoMaterial)
{
return;
}
と記述されているため、ScreenSpaceReflectionsStencilPSでは判定を完全に逆にする必要があります。
そのためScreenSpaceReflectionsStencilPSの条件は正しくは
if (RoughnessFade > 0.0 && bNoMaterial==false)
{
discard;
}
こういう判定になるかと思われます。
ややこしいですがdiscardされる側がSSRで計算する領域なため、Unlitはこの条件をパスしてよいですし、またRoughnessFadeが0以下の場合もこの条件をパスしてよいことになります。
RoughnessFadeはUnlit以外のものが対象になるため、このような判定になると思われます。
修正したコードを適応したStencilが添付画像の以下のような形になります。
空とroughnessの高いところマークされている事がわかります。
この塗られた場所が本来棄却されるべき場所になる との認識です。
[Image Removed]
以下の画像はScreenSpaceReflections側のStencilTestです。
[Image Removed]
元のコードに比べてStencilTestでFailする領域に変化がでたことが確認できました。
恐らくこちらが期待するStencilTestの結果になるかと思います。
補足になりますが5.7のコードも同様のコードになっていることを確認致しました。
以上になります。こちらお手数ですがご確認いただければと思います。
[Attachment Removed]