Water plays an important visual role in Everend. There are plenty of really nice looking shaders already made, but to get the kind of look that cave water has we needed our own. We didn’t need anything too complicated so our water includes only the basics like distortion, waves made up of normal maps, and color change based on water depth.
Waves
To get the wave effect we simply used two normal maps scrolling in opposite directions. The immediate effect is an invisible plane, but with the addition of a specular reflection the waves start to show when a light is reflecting off of them.
Refraction
The water surface at this point is still mostly invisible except for the specular reflection so we added a distortion effect to the water. The refraction part of the shader uses the same normals as the specular reflection. To get this effect we used Unity’s GrabPass to grab what would have been displayed and grab either a pixel offset to the current one based on the color of the normal map. The method to create the distortion is fairly straightforward. The shader needs to grab the red and green color of the normal texture which are between 0 and 1 and transform the values so they fall between -1 and 1. Then it needs to get the position of the current pixel and add the offset to it. Finally use that value as the location of a pixel on the GrabPass texture and return its color. As long as the normal map has smooth transitions so will the distortion. The intensity of the effect can be altered by multiplying the transformed red and green values, and if you pause the scrolling normals then the effect works well for ice.
Depth
The last two effects are both based on camera depth. The surface of the water should remain clear, but as its depth increases the color should change and the water get murky. This effect can be achieved by getting the distance between the water plane and the pond bottom and linearly interpolating between clear and colored based on the difference. To get the distance to the surface of the water the shader should multiply the vertex position by the model view projection matrix, and then in the fragment function all you need to do is grab the positions z value. To get the Depth of the pond floor you just need the LinearEyeDepth of the camera depth texture. After that subtract the plane depth from the floor depth and you have the difference (You can divide this by a value that will then control how deep the color starts to change). Then linearly interpolate between your surface color and underwater color based on the difference and return the color.