I don’t normally jump on dot-zero versions this quickly, but my litmus test for UE5 was always “when they ship Fornight”, and well, that passed a while ago. I also pushed my project through EA2 and Preview 1 – very quickly – to get a view on the type, and severity, of breaks I was going to encounter, and tbh the worst thing always looked like being VFX.
I also spent a couple of weeks on a side-project, using GAS and other features that I know I’ll have to migrate to, and yeah, none of it looked like being an issue.
This week I made the jump.
Epic recommend converting as many assets to Nanite as possible. To help with that, there’s a tool that’ll go through every static mesh in the project, discarding any that use unsupported blend modes, or WPO. What’s left should be safe to convert.
And yeah, that works to get the bulk of the job done.
There’re new visualisation modes in the editor. Nanite Triangles is useful to find areas of the world that’re non-Nanite and dig into why.
In this example, the black areas are modular pieces – the raised bits in the game – and they’re not using nanite because of a single poly edge with a masked alpha, for a grass effect.
I’m not bothered about fixing these. Most of the env modules use less than 20 polys, so no big deal. If Nanite never supports masked alpha then I might go through and split the edges off, but it’s hard to see how much I’d gain.
What I did find, and fix, was that a lot of opaque objects in the world were using a Material Instance based on a masked alpha Master. The houses, for example, and a lot of the player’s props did this, and the new visualisation mode was a god-send here.
I didn’t push the project through 4.27 because, as I wrote some time ago, I couldn’t package the game because of a crash when cooking. As I hoped, none of the early versions of UE5 crashed in this way, but it did mean there were a lot of deprecated Niagara modules that needed fixing-up.
In the end I had to go through every single emitter, and then each system, re-compile or replace the out-dated modules, and make sure the VFX in-world wasn’t broken. Non-trivial, especially when derived emitters had a bunch of changes in; it wasn’t immediately clear which module had the changes I actually wanted, which made for a long and boring day.
Also, emitter lifetimes needed to be explicitly set, which took me a while to work out. Some stuff that shouldn’t loop was, and some stuff that should die with the last particle, was using a default lifetime. Tbh I have no idea why this wasn't broken in 4.26.
Although I’ve tidied up my Niagara assets, I’m not done. I also need to go through the old Cascade emitters and convert them, but, I think I’m going to do it piecemeal. When I see one close to something else I’m working on, I'll port it.
PhysX is no more!
This sounded like one of the scarier changes, but in the end, it wasn’t too bad.
The Good News: cloth simulation “Just Works” once you enable the Chaos plugins.
The Bad News: those old destructible meshes have gone the way of the Dodo, which for me, were things like breakable pots, skulls, and rocks.
I started having a look at the Chaos Destructible stuff, working my way through one of the tutorials on Epic’s site, and yeah. It’s a bit much for some props. And worse, the stuff I made fell through the floor. I never did work out how to break it in code, either. So, I went back to the Old School. I used the Cell Fracture extension in Blender, broke the props into 8 pieces, and now I spawn them around a radial force component.
It works, looks fine, and all told took a couple of hours.
This has been my only recurring issue with recent engine migrations: a good portion of the animations I’d made in Widget Blueprints were broken. They’d play, but not properly, or restore state / revert to frame one at the end of play, when I didn’t want them to.
Not a major issue; all of them were simple fades, seconds to re-make, but having to hunt them down and re-do them, AGAIN, is getting tiresome.
There’re also a few little changes to UMG properties. Rich Text widgets no longer have ColourAndOpacity, which is fine. Render Opacity was all I was after anyway. Didn’t take long to find and fix these.
FHitResult’s Actor is now hidden behind a getter, and IsPendingKill() is deprecated. In total this affected 8 lines of code in my project. It compiled clean after making those changes.
I was using Atmospheric Fog in a few interiors, and this seems to have been removed. I need to do a little more work, but I’ve not been able to get the same look with Sky Atmosphere. Exponential Height fog (with volumetric fog) is dependent on the angle of the directional light, so although I can approximate what I had before – a smokey environment in a pub – for certain times of day, the day-night cycle eventually breaks it.
I think, in those interiors, I’ll revert to some particles and/or a couple of angled fog sheets. Would be cheaper and the extra movement would probably look better.
Nvidia’s DLSS plugin is around the corner, so I’m currently running with Temporal Super Resolution.
For reference, I run at 4k resolution @ 120hz on a 2080. With TSR, r.ScreenPercentage is at 50 and r.SecondaryScreenPercentage.GameViewport is 83.3.
TSR is impressive. To my eye it’s softer than DLSS, and it’s definitely not as fast. Where I was running at a solid 120fps before, I’m now getting ~90-110. The framerate is less stable across the entire game.
But, the picture quality is impressive. This is a little section of the final 4k output (with jpeg artifacts).
If DLSS is available, I’ll use it, but TSR is a fantastic fallback.
Virtual Shadow Maps are enabled and god-damn they’re amazing, except in the shot above.
It looks like Point and Spotlights need a bit of tweaking to soften the shadow edges, something that’s really apparent in dungeons and interiors. I need to play with it.
But exteriors? Wheee!
Epic recommend converting raw pointers to TObjectPtr. Tbh, I really should have been a lot more rigorous with my pointer use. I tend to reach for a raw pointer instead of considering if it should be shared or weak, so having a bit of extra typing here is a useful forcing function for me.
There’s a commandlet to do the heavy lifting, going through your code and doing a lot of the conversion, but I decided to do it all manually. I’ve probably got a couple of hundred .CPP/.H files at this point, and the majority of it I’ve not even looked at in a couple of years. This was a good opportunity to do some tidy-ups, and I ran the static analyser over it all while I was in there.
One issue popped out: I was running check() over a pointer that needed .Get() adding. Everything else was fine.
I opted not to use World Partition. I’ve written about this before. Yeah, there’s a risk that the level streaming will be dropped, but I’ll cross, and swear at, that bridge when I come to it.
I’m also not using Lumen. Today at least. Technically, everything I’ve made suits it perfectly. My geometry is modular, and I don’t need to make many changes. Maybe put a roof on the interiors and dungeons to improve the bounce.
In the end it comes down to framerate. Right now 120 is more important to me than the GI & reflections. But let’s see. It can be turned on per post-processing volume, so maybe a few places, where FPS doesn’t matter, can go full beans. And I have the option of turning it on per-platform when I ship.
The quality-of-life changes to the editor are welcome. I like the new tray system. Everything’s clearer and easier to find, and I’m still uncovering little touches, here and there, that make a real difference. The perforce integration, despite the odd bug, is worth the price of admission alone. It’s a welcome upgrade.
But, it’s definitely more resource heavy, especially on my GPU. My little room’s got a little hotter the last few days, and summer is around the corner. Joy.
All-in this has taken about 4 days. I’ve lost a bit of perf in the packaged project, mainly due to the lack of DLSS, but I’ve gained elsewhere.
I’m looking forward to building stuff with it next week. :)