Additive Substrate UI Materials rendered in HDR with r.HDR.UI.CompositeMode enabled are not properly composited onto screen

SDR compensation for UI fixes the UI but results in the main scene no longer being visible.

We have a fullscreen UI element with a substrate material using additive blend and that particular combination seems to always write an output alpha of 1. This is not an issue in normal rendering because the UI is rendered directly onto the opaque scene but when SDR UI compensation is enabled the UI is rendered onto a transparent intermediate target where this bug results in writing opaque black. The opacity of the intermediate target is then used when compositing onto the scene, resulting in the scene being overwritten.

It looks like this stems from the GetMaterialColor() function in SlateElementPixelShader.usf, where the substrate version of additive blend outputs an alpha of 1 and the non-substrate version of additive blend outputs an alpha of 0 (see code snippet). Setting the output alpha to 0 in the substrate branch seems to resolve the problem.

Is there a reason for the substrate additive blend to use an alpha of 1?

half4 GetMaterialColor( VertexToPixelInterpolants InVertex, float2 MeshUV, float4 InFontSignedDistanceData = float4(0.0, 0.0, 0.0, 0.0) )
{
// ... code omitted for space

#elif MATERIALBLENDING_MODULATE
  OutColor = half4(UnlitTransmittance, 0.0f);
#elif MATERIALBLENDING_ALPHAHOLDOUT
  OutColor = half4(0.0f, 0.0f, 0.0f, UnlitGreyCoverage);
#elif MATERIALBLENDING_ADDITIVE
  OutColor = half4(UnlitLuminance, 1.0); // <------------- this is 1, should it be 0?
#else // MATERIALBLENDING_TRANSLUCENT
  OutColor = half4(UnlitLuminance, UnlitBSDF.Coverage);
#endif // MATERIALBLENDING_xxx

#endif// SUBSTRATE_OPAQUE_DEFERRED

#else // SUBSTRATE_ENABLED

  half Opacity = GetMaterialOpacity(PixelMaterialInputs);
  half3 Color = GetMaterialEmissive(PixelMaterialInputs);

#if MATERIALBLENDING_ADDITIVE
  OutColor = half4(Color * Opacity, 0.0f); // <------------- this is the non-substrate path
#else
  OutColor = half4(Color, Opacity);
#endif

#endif // SUBSTRATE_ENABLED



[Attachment Removed]

Steps to Reproduce

  • Using an HDR supported hardware setup, Enable HDR.
  • create a fullscreen UI element using UMG
  • setup that UI element to use a Substrate material with the additive blend mode
  • run the game
  • enable the SDR ui compensation for HDR (r.HDR.UI.CompositeMode 1)
  • notice that the UI is no longer faded but that the scene is now black

[Attachment Removed]

Hi there,

You probably want to take a look at this changelist from UE6 that addresses issues with this mode - and might be something you want to integrate:

UE6 CL#56622786 (d413b0) HDR: Blend Modulate and Additive UI materials against the scene in composite mode

In HDR composite mode (r.HDR.UI.CompositeMode) the UI is drawn into a separate SDR texture, so Modulate (dst = src * dst) and Additive (dst = src + dst) UI material draws blend against the near-empty UI texture instead of the scene and render incorrectly.

Route such material batches through the same composite / hole-punch pattern the background blur uses (“SceneTint”):

- Composite the HDR scene + UI-so-far of the batch’s region into a region-sized linear intermediate (reuses the CompositeUIForBlur shader).

- Render the material into a region-sized tint texture with a replace blend, cleared to the blend identity (white for modulate, black for additive), using an offset viewport so the window-space region lands at the target origin.

- Extend the Slate post-process upsample pass with a SCENE_TINT_BLEND permutation that combines base (*|+) tint in-shader in the composite blend space (r.HDR.UI.CompositeEOTF / r.HDR.UI.CompositeBlend), re-encodes into the scene, and punches the region out of the UI texture so the final UI composite does not draw it a second time. Additive rescales the base by UILuminance / UE_SCRGB_WHITE_NITS around the sum so both operands share a luminance scale.

Batches are routed by the blend mode of the material that will actually render (fallback resolve included), so a material whose shaders are still compiling draws directly as its Opaque fallback instead of through the tint path.

The write-back’s clip scissor is clamped to the output rect. The screen pass draws an oversized single triangle, and on some hardware the viewport does not bound its rasterization - only the scissor does - so an unclipped widget’s fullscreen clip scissor otherwise lets the write-back tint and punch outside its region.

Write-back clipping (scissor and stencil methods) verified against the SDR render path as ground truth, including frames with multiple interleaved, overlapping, and animating rotated (stencil) clips; test harness in the HDR test shelf CL.

There have been numerous improvements and fixes to HDR including UI by James Park in 5.8+ so I also recommend looking at the recent history of SlateRHIRenderer.cppin (specifically changes by James Park tagged with HDR UI composite: etc.) such as:

UE6 CL#56031488 (886ee9) HDR UI composite: skip work for pixels the UI left fully transparent and read the scene directly instead of copying the backbuffer on the pixel-shader path.

UE6 CL#55912771 (53d198) HDR UI composite: clamp scene and UI to display max luminance

CL#52912687 (a9e380) Fix HDR alpha blending in Slate post-process upsample

[Attachment Removed]