Stylized Water 2
The shader at its core is optimized through efficient use of resources and will typically outperform anything similar created using Shader Graph. Though, the performance impact largely depends on the amount of features enabled. Expecting desktop-level graphics on mobile devices is unfortunately not realistic.
Water is a complex phenomenon, and requires a lot of layered elements to achieve a believable effect. It is arguably one of the most complex shaders in a game. A notion worth keeping in mind.
Modern devices can run the shader in a simple scene with all features enabled and not notice a difference. If you’re targeting older devices, be sure to disable any features that do not contribute to the art direction.
- Always use the “Simple” shading mode, found under the “Lighting/Shading” tab in the material UI.
- If the water doesn’t take up a lot of screen space, fewer pixels have to be calculated. Meaning it’ll render faster.
- If the scene is not using any dynamic lighting (such as a time of day system), you can disable lighting on the material to save performance (under the Lighting/Shading tab). Reflections and translucency will still work in this case, if they’re enabled.
- Rendering the depth texture in URP essentially doubles the amount of draw calls for geometry. If your project isn’t already optimized for minimized draw calls, it likely has a negative impact. Consider disabling it in your URP asset and under the “Advanced” tab on the water material. Instead, use vertex colors to paint on foam effects.
- When having waves enabled, keep the “Count” parameter value as low as possible.
Relatively costly features:
- Normal map (per pixel matrix transformation + more data passed through shader stages)
- Waves (algorithm is run for both every vertex and water pixel on screen)
- Refraction (samples a full-screen texture + resamples depth to filter out objects above water)
This involves re-rendering the scene geometry from a mirrored perspective, so can potentially double the rendering cost of a scene.
It’s important to make use of the Culling Mask field, and only reflect layers that are important for the visual result. This would require you to create new layers, and set specific objects/prefabs to these layers.
A 2nd camera is used, that renders the scene from a mirrored perspective.
This camera (by default) also renders using the default “renderer asset”, which means any Render Features (eg. third-party post processing effects) will render for this camera as well. In most cases this is undesired for performance reasons.
Since the reflected image is applied directly into the water, effects may appear to be applied to the reflected image twice. This is wasteful…
The solution is to create a separate “empty” renderer, specifically for planar reflections. To do so:
In the Project window, right-click and go to Create->Rendering->Universal Render Pipeline->Forward Renderer (or Universal Renderer in 2021.2+).
Next, in your pipeline asset, add the new renderer to the Renderer List (repeat this for any other pipeline assets used in your Quality Settings).
On the Planar Reflection Renderer component, configure it to use this renderer, instead of the default one.
With this set up, you can ensure no unnecessary or unwanted rendering is performed for the reflections.
Because it’s common for a project to have varying quality scales, there’s a static function in place which can toggle planar reflections globally. This could be tied into something like an option, in a settings menu.
You can call the static function
StylizedWater2.PlanarReflectionRenderer.SetQuality(bool allowReflections, float renderScale = -1f, float renderRange = -1f, int maxLodLevel = -1) to change settings for all active Planar Reflection Renderer instances. The last 3 parameters are optional, if left untouched these values aren’t changed.