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

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).