Sky reflections and Fresnel

The next part of creating the shader was implementing reflections. Reflections have a great impact on the perceived realism of the water, due to the intense specular reflections of the smooth water surface. However, they can also have a very high performance impact, depending on the method and accuracy of the reflections.

To produce complete, high quality reflections, the scene essentially has to be rendered twice. This has a great performance cost, especially if there are multiple water planes at different heights (since that would require rendering the scene again for each additional water plane). In real-time applications where a complete reflection approach is used, there is often only one “real” water plane, which is reserved for large bodies of water such as lakes and oceans.

A more performance-friendly approach is to use screen space reflections. This approach bases the reflection image on the pixels that have already been rendered on the screen. Since the rendered pixels are reused (although there are still many calculations to make in order to reflect the image correctly), this is usually much faster than complete reflections. It is especially useful for applications where there are multiple water planes at different heights. A drawback of this method is that it can’t reflect geometry that is not seen by the camera.

However, due to time constraints, I wanted to focus less on what was reflected, and more on how it was reflected. I had to settle with only reflecting the skybox, which yielded surprisingly good results when combined with a few tricks.

A very important phenomena in reflections is the Fresnel effect. The Fresnel effect is what causes the amount of light reflected to depend on the viewing angle. When the camera is facing straight down into the water, the fraction of light reflected is very low, while the fraction of light refracted (transmitted through the water) is very high. When the camera direction is almost orthogonal to the water normal, the fraction of light reflected is very high.

DSC_0024.JPG
Photograph showcasing the Fresnel effect.

The Fresnel effect was implemented by calculating the dot product between the view direction and the water normal. Instead of just using the plane normal (which would be quite uninteresting, especially since the surface normals have not been recalculated after the wave displacement), I used the same normal map as for the distortion effect. I also combined this with a normal map corresponding to the wave height map, to make the reflection better match the actual shape of the water.

half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.worldRefl);
half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR);
half reflectionFactor = dot( i.viewDir, bump );
col.rgb = lerp( col.rgb, skyColor, reflectionFactor);
fresnelNoTweak.JPG
Water shader with sky reflections and Fresnel.

At this stage, the water didn’t look particularly glossy. To make the reflection more interesting, I tested using the combined normal maps as normals for the reflection, instead of just the plane normals. However, although this seemed to be a step forward (and it was more realistic), the reflection didn’t look very good, partly due to quality issues. I am also not entirely sure that the implementation was correctly done, since I don’t think the grey parts of the skybox, which are below the horizon, should be reflected.

half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, reflect(-i.viewDir.xzy, bump.xzy));
alternative reflection.JPG
Using the water normal maps for the reflection direction.

A different approach to a achieve glossy look was to leave the reflection as it was (using only the plane normal), but adjusting the reflection intensity instead. Rather than using the reflection factor from the dot product directly, it was used as the x-coordinate to grab from an image describing the behaviour of the glossiness. Depending on the image used, this could result in much sharper reflections, which made the surface look significantly more glossy. This approach and its implementation was inspired by the FXWaterPro-shader in Unity’s standard assets.

half reflectionFactor = tex2D( _FresnelPattern, reflectionFactor );
fresnel.JPG
Water shader with glossier reflections.

Which one of these is best is up to personal taste and surrounding graphical style. If you are looking for realism, the first one seems best, but if you want a bit of extra glossiness (which can be a good idea when you are only reflecting the skybox), the third one also looks quite good.

Leave a comment