Elliot Gray talked about his amazing Unified Interactive Water System developed for UE: real-time fluid interaction, caustics, reflections, performance.
Hi! I’m Elliot and I’m a Gameplay Programmer and Designer from Australia. Tech art is a hobby and interest of mine on the side because I love pretty games, and I’ve been known to frolic around in interactive water and foliage for inordinately long periods of time since I can remember. So it’s fitting that I’m now after graduating university at the end of 2016. I shipped my first major title, Despoiler, on Steam earlier this year.
I’ve been fascinated by game development for a long time and love both digital art and technology. Game development is where both of those interests converge so it’s just the perfect industry for me really. At the moment I’m working on my next game (an exciting VR project that I can’t wait to be ready to show off) which has been put aside for a couple of weeks while I turn the side experiment I’ll talk about below into a fully functional product.
Unified Interactive Water System
Funnily enough, I started my water project because I’d just been re-playing Crysis 3 and got really jealous of CryEngine‘s water systems (that they’ve had for years). I’m a huge fan of Unreal, but UE4 is yet to see an as designer-friendly and unified Water Body system as Crytek’s one, so I thought I’d see if I could do it myself.
Now don’t get me wrong, it’s not all that difficult to get interactive fluid up and running in Unreal these days, there’s even a content example from Epic that has a simple fluid sim using Blueprint and render targets (which was actually my starting point). However, my goal was to create water bodies that were drag-and-drop, designer-friendly, and 100% automatic. I’m happy to say I’ve been able to achieve that! Here’s a list of the main features of my system:
- Infinite water body size, infinite body count per level
- Drag, drop, scale and play – no setup required
- Player local simulation
- Interactive water and caustics sim
- Auto collision and damage particle spawning
- Optional full manual control via BP and C++
There are a few steps going on in the water sim but it’s all pretty straightforward so if someone wanted to create their own version of it with stock Unreal or any engine really, they could (shameless note warning: or they could buy my plugin).
While in the past some have approached the problem with a code-based ‘stamping’ approach where you might line trace to the surface from an interactor and splat a ‘force’ onto its material that can then be propagated as ripples, I wanted to allow any object to interact without that object ever running any logic of their own. So, instead of splatting (I do support that method for the sake of manual interaction), the primary simulation propagates ripples from a scene capture at the water surface’s height that renders only custom depth with a very short draw distance. This way the water surface each frame knows exactly what’s interacting with it, where, and even the size and shape of the colliding objects. This way ripples even propagate out from the shape of a colliding object, not just in a circle as you can see in the gifs below.
Here you can see the ripple propagating from the UIWS text mesh:
Here you can see the weapon interacting as well as the character when it touches the water:
Even works with particles (possibly my favorite thing ever)!
The scene capture approach was actually inspired by Colin Barré-Brisebois‘ talk from the GDC vault on the snow in Batman (check it here). Since I don’t worry about accumulation for my sim, it’s much less interesting than their work). This capture is done in world position at the player’s location and then the ripples are propagated in world space from the captured data each frame. Getting everything to work in world space was a challenge and I felt like I was fumbling around in the dark for some time but I made it to the end. World space capture and simulation were key to my goals though because it allows a designer to make a water body any size they like, and the simulation remains at a uniform resolution. It also means that the sim data can be used for caustic reflections and refractions!
The gif of a debug view mode below gives a bit of insight into what’s happening: the large water body in this level is a single actor scaled to cover the level, the pink area is the masked out non-interactive area so you can see how the sim is moving around with the player location (it can also be centered on the player if desired).
Now, before I go on: when I first saw the Crysis 3 caustics ‘simulation’ back in the day, my mind was blown. It was amazing. How did they simulate caustics in real-time that looked so nice but didn’t make the game run slow? But upon revisiting I realized, they’re kind of ‘fake’, which ruined the magic for me a bit as a gamer (but as a developer, I found it even cooler!). The cool thing is that our dumb brains believe them anyway. They look real enough and add so much to the overall interaction between the player and the world that it doesn’t matter if they’re not really physically accurate.
So you’ve probably worked out what I’m getting at here: my caustics are ‘fake’, too. Read on if you want the magic to be ‘ruined’.
Since the water simulation is in world space, you end up with a set of render targets that, because they’re being generated anyway, can be used anywhere else at negligible extra cost. Which is how foliage interaction and caustics came to be! I created a material function that handles all of this that can be easily added to any material. The function outputs caustic reflection/refraction lighting (typically to be plugged into a material’s emissive channel) as well as world position offset if you want to allow ripples from the water bodies to move your reed’s around. The scalar input is basically just a multiplier for the caustic brightness, that also takes into account the color of the object they’re lighting so you don’t end up with unrealistic ‘lighting’ interactions. You can also feed your existing WPO into the function and let it handle the addition of ripple interactions if you’re doing player interaction or just wind in your shader already.
Caustics & Reflections
The trick to all of this is that all the material functions have access to the same data. The water surface itself isn’t reflecting or refracting light to generate caustics, it is just created from data that the caustics can also access, so they line up perfectly and give the illusion of reflection and refraction. Below you can see a shot of the water material, it’s pretty standard stuff.
The main things unique to this shader are the LakeRipples material function and the InteractiveRange material function. The former is where the tiled ripples are generated, they’re in a material function so that the dynamic caustics can easily work with the same data. The latter is just to keep the water interaction from tiling outside the configured interaction distance.
Performance is actually one of my favorite parts to discuss in this project! Naturally, my earlier prototypes were done with a blueprint which made the process of having no idea what I was doing at first much less painful because at least I could fail fast. I always knew I was going to need to move the system over to C++ if it was going to be viable in real-world conditions but in case you’re wondering, the performance cost of the blueprint implementation was about 7.5ms of CPU time per frame on the game thread for a single interactive body which was horrendous.
Now that it’s all C++, it’s running beautifully! There’s still room to optimize (in particular I’m working on a better culling system that removes performance overhead for any distant/culled bodies etc.) but as it stands, on high-end, a water body costs approx 0.7ms on the CPU. On the GPU most of the cost comes from the shader itself rather than the actual simulation, meaning it should scale really well to low end with a simpler surface shader. Basically, I’ve been, and will always be extremely committed to performance for this and any of my future endeavors because a) I love smooth frame-rates, and b) I want as many developers as possible to have access to nice interactive water, whether they’re on high-end pc, VR or even Nintendo Switch (fingers crossed for the last one).
Something cool I discovered while developing this plugin was that you can get away with some really low resolution render targets for both interaction capture AND simulation. Currently, the interaction capture is only 256×256 stretched across the interaction distance (60 meters in these videos and gifs) and the ripple height and normal render targets are a higher but still very performant: 1024×1024. I’m going investigate changing these resolutions at distance too for even more optimization.
Below is a performance comparison video I made in a level with 2 water bodies comparing the performance at 4k epic settings and 4k low settings. My water system isn’t even doing any internal scaling based on scalability settings yet so there’s still room to improve at both high and low end! (I’ll be adding bespoke handling for the low spec to deal with the artifacts seen at distance in this video as well).
The UIWS is already available on Unreal Engine Marketplace! The plugin is super easy to use with almost no setup required, so you can use it right away after purchase.
If you want to keep an eye on new projects (or just have some glorious gifs in your feed), follow me on twitter. Thanks for taking an interest!
If you found this article interesting, below we are listing a couple of related Unity Store Assets that may be useful for you.