Improvements for Planar reflections.

I lately have used lot of time for making Planar reflection actors to perform better. At first pass I tuned screen percentage, LODDistance Factor and Max View Distance Override.

Then I started to disable features, basically all of them. I needed to hack some shaders to disable static shadow distance fading to allow disabling dynamic shadows.
List of all things that I disable with c++ code.
[SPOILER] const bool t = false;





Then I made some fixes:
Occlusion culling wasn’t working because it’s used wrong view.
Large planar reflection actors didn’t work with occlusion culling.
Actors behind planar reflections wasn’t culled. Frustum planes to enclose planar reflection actor.
Prefiltering skips pixels.
Occlusion culling use wrong number of buffered frames.

I am also working with some other optimizations. Like using tighter frustum for planar reflection that don’t cover whole screen.
So do others have some optimizations tips to share?

I dont have anything to add besides horizontal blur options XD But nevertheless…THIS IS AWESOME! Thank you so much man! I think these are great and important optimizations :slight_smile:


I forget to add this pull request.
Vertical blur skips every other pixel which cause visible streaks.

Just updated.
Figured way to calculate accurate planar reflection world space corners. These can be used to calculate 4 additional frustum planes that then enclose planar reflection actor. This way only objects that actually are reflected from planar reflection actor are rendered. For small reflection actors this will save huge amount of rendering time.

awesome work!
haven’t used planar reflections myself (I’ve had no need so far and they seem expensive anyway) but I hope your changes make it to the engine :slight_smile:

Made some performance testing.
Flying Template Scene with planar reflection added: 763 draw calls. With added frustum planes: 524 draw calls. Without planar reflections 474 draw calls.
So in this scene Planar reflections add 269 draw calls. With this single PR this drops to 50 draw calls. This is 81% saving. The saving amount is dependant how big portion planar reflection actor is covering. Savings from adding clip plane as frustum plane help always. Even when planar reflection actor cover whole screen.
Edit: PR ->

Without optimizations, with optimizations, planar reflection disabled.

Found that occlusion culling still has some issues. Only worked every other frame. This was caused by wrong number of buffered frames. So I made new PR to fix this.

Can some one confirm is this bug or not?

if (SceneRenderer->Views.Num() > 1)
    const FMirrorMatrix MirrorMatrix(MirrorPlane);
    for (int32 ViewIndex = 0; ViewIndex < SceneRenderer->Views.Num(); ++ViewIndex)
        FViewInfo& ReflectionViewToUpdate = SceneRenderer->Views[ViewIndex];
        const FViewInfo& UpdatedParentView = MainSceneRenderer->Views[ViewIndex];

        ReflectionViewToUpdate.UpdatePlanarReflectionViewMatrix(UpdatedParentView, MirrorMatrix);

This is either only for VR where you have more than one view or a bug. UpdatePlanarReflectionViewMatrix isn’t called from anywhere.

Maybe this is related to this bug I have reported, when you use split screen feature: Unreal Engine Issues and Bug Tracker (UE-59149)

basically when both views are showing the same reflection, changing camera position on one view was affecting the second view. I have not digged this issue, since it was reported for me and I have only tested how to reproduce and make the submission report.

@Kalle_H if I recall correctly you work alot with mobile (not sure if with vulkan) and I have found issues with Vulkan and planar reflections, so just for you to be aware these exists. I will link the issue number as soon as I find it:

[SPOILER]first video with planar reflection issue - YouTube

[SPOILER]second video showing the Planar Reflections issue - YouTube

[SPOILER]Third video with Planar Reflections issue - YouTube

Sorry but I don’t use Vulkan or openGl platforms at all. First one seems to be clearing issue. No idea about two others.

Great PRs Kalle, hope these get merged :slight_smile:

This is output of RenderDoc texture viewer of Planar reflection texture. Like you can see most of the stuff is out of bounds of actual planar reflection and never can be seen but these pixels are not free. All big objects like skyboxes etc are drawed to them. So my idea was to use Scissoring to cull those pixels early. Red rectangle is what I would use as scissor rect. But I am not sure can I set it globally from PlanarReflection rendering. Messing up with ViewRects didn’t work.

Nice! That’s a big reduction in draw calls.

Something I’ve noticed is that planar reflections (and scene captures) seem to do global distance field updates regardless of showflags, and translucency volume lighting is calculated even if translucency is disabled, if deferred lighting is enabled. Is this something that could be optimized? I assume this also applies to the main view.

I can try to make something about those when I am back from vacation.

I would to like to know @DanielW thoughts about these changes?

The fix with with adding planar reflection plane as clipping one wont work with 4.20, since in 4.20 there is a check that ViewFrustum.Planes.Num() == 5 in BuildLightViewFrustumConvexHull function - have you by chance managed to modify that function to take into account that Frustum.Planes.Num() == 10 for reflection captures ? (or I must frap my head around that fishy Edges table to fix this) ? (adding those additional clipping planes greatly improves performance, so I do not want to lose this change)

Good catch. You can change the assert to be like this.

check(Frustum.Planes.Num() >= PlaneCount);

But this will not be optimal for planar reflection dynamic shadows. But proper fix to get this optimization for dynamic shadows will be bit more complex and you would need to add more edges to that list. In our project we use static shadows only for planar reflections so I haven’t seen this assert my self.

Would this beauty work on mobile VR (Gear VR and Oculus Go) using ES2 (or ES3.1, although I am not sure Es3.1 is supported by Oculus) ?