Hair Groom DoF Regression in 5.8

Depth of Field blurs foreground hair as if in the background.

From a quick look through graphics captures it appears that this was introduced with the changes to support Single Layer Water (SLW) for Hair Grooms in 5.8. There is a comment in “AddHairVisibilityComposeSamplePass()” immediately prior to disabling depth write to the effect of “don’t write depth on the second pass because SLW has already written the depth”. SLW *does* appear to output the correct depth, but at a later point in DeferredShadingRenderer.cpp we override SceneTexture Depth to point at the SLW DepthPrepass Texture instead of the Main Scene Depth. We do this so water is added to depth scene, but it means our hair depth is no longer in the “main” depth. When DoF uses this new texture, it doesn’t know about the hair.

I’ve found a few work arounds, none of which is an ideal fix:

  • r.HairStrands.ComposeAfterTranslucency=2
    • This pushes the composite of hair later in the frame, which resolves the issue since it is now after water entirely, and the single path writes depth.
    • This approach has risks of knock ons for compositing hair with other translucents
  • in AddHairVisibilityComposeSamplePass, if you re-enable the depth write from the main hair pass things work properly as well
    • this writes hair depth to the new SLWDepthPrepass texture
    • this is a redundant depth write, which is not ideal for performance
  • In DeferredShadingRenderer.cpp if we remove the logic updating SceneTextures.Depth to point at SLWDepthPrepass then DoF works correctly
    • This means SLW is no longer in the depth buffer which seems likely to cause knock-ons
  • We can invert the depth writes, so we dont write hair depth in the SLW pass, but we do in the main Groom pass
    • This seems to work, but means hair depth is not being written in the SLW depth, which may have impacts on SLW interaction for grooms
  • We can pass the DepthPrepass texture into the RenderHairCompositionForSLW which does the initial depth write.
    • This ensures proper depth is used for DoF
    • Hair gets desaturated for some reason
    • means that hair is not output to FSceneWithoutWaterTextures which is used by a great number of passes so very likely knockons

From what I can tell there is no nice solution here. The most correct solution seems to be to just write depth a second time.

[Attachment Removed]

Steps to Reproduce

  • Set up a character with a groom that extends reasonably far from their head
  • Set up a reasonably aggressive DoF so the character’s face is in the foreground and everything else is in the background
  • ensure CVar r.HairStrands.ComposeAfterTranslucency=1
    • (this is the default value which properly captured hair in 5.7)
  • Watch as the hair outside the silhouette of the player mesh is blurred.
    [Attachment Removed]

Hi,

Thank you for catching and reporting this! I agree that there is no nice solution, and rewriting the depth a second time seems the simplest solution for that. This should now be implemented in our mainline at CL 58502473.

Thank you again!

/Charles.

[Attachment Removed]

Here is the GitHub link if needed: https://github.com/EpicGames/UnrealEngine/commit/0082b4ecbe16d2630a7ad46a597f244a67c7bc56

/Charles.

[Attachment Removed]