In a game set in the ocean, water plays a huge part in the graphics as it is what you see a lot.
Unfortunately, ocean water is a tricky thing to replicate in games for a few reasons.
- Its surface needs to not look flat
- It is transparent
- It needs to have reflections and ideally glimmer in sunlight
- It can refract objects underwater
- The deeper you see into the water, the more ‘foggy’ it appears. (Shallow water and deep water display differently)
- It needs to be easy for the GPU to render, as this is a game and while important, water should not be heavy to process on the device.
I played around with different shaders I found online.
My favourite part about this water is it has foam around the intersections where objects touch the water. It’s a really sweet effect but without any surface detail, it looks really bland. Even if I add a texture, it’s still evenly flat throughout.
It is a standard shader, but with a few tricks. To create a believable effect, I used two normal maps animated in different directions, perpendicular to each other (e.g. one normal map scrolls northeast, the other northwest). Not only does having two layers of normal maps overlapping making it look really cool, but what the perpendicular angles does is it makes it look like it’s really flowy. In my opinion, this is a great balance of visuals with performance.
The only catch is that I could not get reflections to work as intended.
Screen-space reflections was used above. It has too many artifacts and gets dark when approaching the horizon, which is not supposed to be the case. As for planar reflections, it is restricted to Unity’s HDRP, a different render pipeline in the engine that I use and an existing solution requires a custom shader which does not have the overlapping normal map support.
Look at those reflections! This one has dual layered normal maps, but the intensity of the surface bumps cannot be varied. Nonetheless, the reflections look really pretty at night when the city lights up.
These reflections are not screen space, therefore you can actually see the underneath of things that aren’t visible to the camera. What it does is there is a second camera that captures the objects in the world and projects it as reflection. It sounds intensive and it does indeed almost double the processing time requires because it has to draw everything twice. Which is why the resolution of the reflections is capped at 256 by default. I don’t need the reflections to be super crisp and sharp or detailed. This makes it a lot more manageable to calculate reflections.
A different mode of the water above allows it to not only be translucent, but also refract things underneath it. This is really cool, and I submerged my city even more in the above screenshot to really see the effect. It’s really cool as I can use it to illustrate shallow and deep water with a few more tricks, but out of all the water shaders, this is the most EXPENSIVE for the device to render because the water now has to calculate how to refract things underneath it.
But what if I could push graphical fidelity to the highest? What would be the best looking water?
The best looking water of all! No trickery with normal or height maps but actual waves being simulated! There is a foam, refraction, and you can even dive underwater! There is underwater ‘fog’, and even caustics (refraction of light rays causing the rainbow colours on the sand!)
This is truly amazing but I’m holding off implementing this in my game for now. The above was made possible with Crest, an ocean plugin that I sourced from Github.
Ultimately, I used both water 2a and 3a in my game. I offer a high quality toggle mode that allows people to turn off the advanced water effects of 3a, and use water 2a instead. By default, mobile devices will use 2a, and desktops use 3a.
Wait did I just mention mobile?
Yes I did a test of this game and it runs on my android device!
Experimental Mobile Build
I wanted to be able to play my game and show it off while on the move, so I built a mobile version of my game!
It actually looked better than I expected, but because it was a very quick test, it is not optimized and will drain quite some battery.
Mobile is still experimental, so it is still being tried out as a low priority. I had to adapt the controls for mobile from arrow keys to click to move. I don’t want a keypad or joystick yet as I think it takes up a lot of screen space.
While playing with materials, I ended up accidentally making what looks like a winter DLC for my game!
In here, I am trying out performance improvements if I were to switch to using a technique called Color Atlasing where I can use a single material shared between different 3d models. This greatly reduces the work the GPU needs to do to draw multiple objects on screen, as it allows the engine to ‘batch’ render.
Batching reduces the work needed by your computer, similar to how you only need to make one phone call to order 10 foods from your supermarket.
Without batching, it may be like having to make 10 phone calls to order food from 10 different supermarkets. It is much more work to do and time consuming. Batching them means if certain conditions are met, you only need to issue a single set of instructions to execute multiple tasks.
It is not automatic and my 3D assets have to be created in a way that allows the engine to perform optimization.
The above was a test to see what difference it makes, so I can evaluate the ratio of the effort required to work on it versus the outcomes of this optimization technique as compared to other methods.