r.SSR.Stencilが有効のケースでロジックの不具合が見受けられます。

期待している動作:

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]

再現手順
再現方法:

添付した​【SSR_StencilTest.zip】をUE5.6のプロジェクトに展開、起動したあとコンソールコマンドでr.SSR.Stencil 1を入力することで確認できます。

[Attachment Removed]

(下記リンクは、Epic Games のサポートが内部的に使用するリンクですので、ユーザーの方が下記リンクを利用する必要はございません。回答はこの日本語スレッドに日本語として表示されることになります。)

[When r.SSR.Stencil is enabled, there is wrong logic [Content removed]

[Attachment Removed]

[mention removed]​ 様

(以下は、サポート担当の Peterson Alex によるコメントを翻訳したものです。回答が遅くなりまして、申し訳ございません。)

本件問題をご報告いただきまして、ありがとうございます。また、詳細な説明と修正案をご提供いただきまして助かりました。

本件問題の解決に向けて、以下の Issue を起票し、進捗状況を確認できるようにしました。

Epic は来週 2025年12月22日から 2026年1月5日まで休暇期間に入るため、その間は公開/非公開チケットのいずれについても Epic からの回答はございません。ただし、公開チケットについてはコミュニティからの回答が届く可能性があります。

良い休日をお過ごしください。

[Attachment Removed]