About the project
For our third game project as a technical artist I wanted to focus more on environmental effects. One of those things were water, or rather an ocean. It uses gerstner waves for the movement, tessellation for “optimization” and refraction and depth fade as some of the techniques in the pixel shader. This was all made from scratch in HLSL, and I had to implement all of those features, since our engine didn’t have them before.
Tessellation has been a bit of a side project of mine since the first game project. At that time, it was only used for asteroids, but in this game I extended the shader solution to be available easily to every mesh shader. I added some preprocessor directives to handle whether to use it, and what parameter values to use, and then I can just replace the regular vertex shader with a domain shader, and the setup will handle the rest. It makes it a lot friendlier to use, and has made iteration times faster.
When I had started making the ocean shader I realized I would have to make the stylized shorelines using separate meshes with a different shader. They still needed to have the same domain shader so that their movement would match. I created a HLSLI file that just contained the domain shader and its prerequisites, and included it in both of the ocean shaders. This lets me control everything easily, even when we use a file structure with all of a mesh’s shaders in a single file.
The refraction setup is pretty basic, and not the most optimal implementation, but it works pretty well for what it is. I get the scene color of all opaque objects in a texture, and offset the UVs when I sample it using a tangent space normal map applied to the surface.
Depth fade is a really important component of a lot of effects, yet it’s very simple to implement. I take the difference between the current transparent depth, and the previous opaque depth, and divide it by the falloff distance. It also has a contrast function that gives me more control over the look of the edge.
Bringing it all together
The water was initially based on Rime’s water, and I looked a lot at how they had done it. The shader uses depth to isolate different areas of the ocean, shallow water is greener, deeper water is bluer, and the water far away is more cyan. I also implemented Unreal’s ChaosMapping material function, which added a lot of interest to the textures that use it, even though it’s just panning the chosen texture diagonally four times. Overall, I am fairly happy with the result, considering the limitations of the engine, and the limited time I had to develop it.