Achieving the icy wall effect in the Ice Cave demo
Ice Cave is ARM’s latest demo. It shows that great graphical quality can be obtained in mobile devices with the use of conventional highly optimised rendering techniques. The demo is visually stunning and full of life: there are reflections on the board, refractions within the phoenix statue and there is a feeling of time passing as the light changes with the sun moving, coupled with the patrolling tiger and the fluttering butterfly. All these elements are immediately evident, but there are a few others that are more subtle but add another layer of dynamism to the demo, such as the reflective icy walls of the cave. The purpose of this blog is to explain how we achieved this effect within the Ice Cave in a way that is easy to understand for everyone, in order for developers to be able to replicate the technique themselves, as this is a very performance-efficient technique that works wells in mobile.
Fig. 1: The Ice Cave [effect in the video can seen at 3:00 or by clicking here: https://youtu.be/gsyBMHJVhXA?t=180]
Ice is a tricky material to replicate due to its reflective and refractive properties. The way it refracts light gives it a particular hue of blue that is a bit difficult to pinpoint and texture effectively without having the asset appear excessively blue or completely washed out. Light scatters off ice in a certain way depending on the surface of the ice itself, which could mean the reflections on the surface can be anything from relatively clean to completely distorted and unrecognisable. On an environment such as the Ice Cave, one would expect to get uneven, relatively unclean reflections on the walls due to their irregular nature, and if you look closely when panning around the demo, you can see it is there. This effect is the result of a long effort in investigating and trying to achieve a parallax effect that made the ice appear thick and reflective.
I originally wanted a parallax effect on the walls, but previous attempts had failed to produce the kind of effect that we were after. The idea for the technique that is currently found in the Ice Cave originated after an accidental switch between the world space normals and tangent space normals. I noticed that the switch resulted in a strange, distorted effect on the walls that was highly reflective. These sorts of reflections were the type that we were after, but we needed to have control over them. Using that thought as an inspiration, I started looking into how to localise that reflective effect only to certain areas of the cave walls in order to get the parallax effect we were after.
Fig. 2: Close up of the reflective icy walls [effect in the video can seen at 3:16 or by clicking here: https://youtu.be/gsyBMHJVhXA?t=196]
The initial map used to produce the reflections was an object space normal map with sections that were greyed out, and as such contained no normal information (Fig. 3). Even though it worked, it was a complicated process to tweak the normal map information as it had to be done in Photoshop by manually adding and removing sections of the texture as we needed. That was when I had the first thought of using two separate maps in order to interpolate between them to obtain the reflections.
Fig. 3: The first modified normal map
The two main elements of the polished technique are the object space normal maps and the greyscale normal maps (Fig, 4). The white areas of the grey map remain unaffected and as such take on the information provided by the object space normal maps. It is the combination of the two which produces the icy, parallax-like reflections on the walls of the cave.
Fig. 4: Object space normals on the left and final greyscale normals on the right
The greyscale normals are made by removing the colour from the tangent space normal maps (fig. 5). This produces as a result a picture with small tone variation in the grey, in such a way that most of the values are in the range of 0.3 - 0.8.
Fig. 5: Tangent space normals and resulting greyscale normals
It is important to use the tangent space normal maps in order to produce a greyscale map, as the colour variation is minimal in them which will mean that, once the colour is removed, you will be left with a map that very clearly shows all the rough detail of the surface. On the other hand, if you use of the object space normal maps you will get an image that shows where the light hits as well as the rough detail due to the contrasting colours. (Fig. 6)
Fig. 6: Object space normals to the left and the resulting greyscale normals to the right,
on which the areas where the light hits is very evident
The grey normals are to only cause reflection on the walls of the cave, not on the snow. Therefore both the diffuse map and the greyscale normal map have to match, so that wherever there is white in the diffuse map the grey normal map is transparent, and where there is black in the diffuse map the grey normal map is opaque (fig. 7).
Fig. 7: Diffuse texture on the left and final greyscale normal maps. The transparent areas on the normal map match the black ice areas on the diffuse texture.
The grey normals are then combined with the true normals using a value proportional to the transparency value of the greyscale normals:
half4 bumpNormalGrey= lerp(bumpNorm, bumpGrey, amountGreyNormalMap);
As a result, in the dark, rocky parts of the cave, the main contribution will come from the greyscale normals and in the snowy part from the object space normals, which produces the effect we are looking for.
At this point the normals all have the same components with values between 0.3-0.8. It means the normals are pointing in the direction of the bisect of the first octant, as the normals have the same component values: (0.3, 0.3, 0.3), … , (0.8, 0.8, 0.8)
The shader then applies a transformation that you normally use to transform values in the interval [0, 1] to the interval [-1, 1]: 2 * value – 1. After applying this transformation part of the resulting normals will point to the bisect of the first octant and the other part to the opposite direction.
If the original normal has the components (0.3, 0.3, 0.3) then the resulting normal will have the value (-0.4, -0.4, -0.4). If the original normal has the components (0.8, 0.8, 0.8) then the resulting normal will have the value (0.6, 0.6, 0.6). So now the normals are pointing to two main opposite directions. Additionally when the reflection vector is calculated the built in reflect function is used. This function expects the normal vector to be normalized but what we are passing is a non-normalized normal.
As the normals are not normalized the length is less than 1. The reflect function is defined as:
R = reflect(I, N) = I– 2 * dot(I, N) * N
When you use the built in reflect function with a non-normalized normal with a length less than 1 the resulting reflection vector will have an angle relative to the provided normal higher than the incident angle.
The normal switching direction when the greyscale map has values around 0.5 means that totally different parts of the cube map will be read whenever the greyscale value changes between lower than, or higher than 0.5, creating the effect where there are uneven spots reflecting the white parts of the cube map right next to where it reflects the rocky parts. Since the areas of the greyscale map that gives switches between positive and negative normals is also the area that gives the most distorted angles on the reflected vectors, this gives makes the white reflected spots very uneven and distorted, giving the desired “swirly” effect.
If the shader outputs only the static reflection using non-normalized and clamped grey normals we get an effect like the one shown below in figure 8:
Fig. 8: This is the swirl effect that results in the icy effect that everybody likes
The clamping seems to be relevant to the result of the icy effect as it produces normals oriented mainly in two opposite directions, which is the main factor that defines the swirl-like pattern. However if we remove the clamping of the greyscale normals, the result produces normals in one main direction then we get the effect shown in figure 9, which results in a different visual effect:
Fig. 9: The removal of the clamp results in a slanted band pattern which is much more evident when the camera moves
The use of two normal maps is not the only thing that influences the reflections on the icy walls. Reflections in the Ice Cave are obtained through the use of local cubemaps, which is a very effective and low-cost way of implementing reflections in a scene. These cubemaps have local corrections applied to them, which ensure that reflections behave in a realistic way and changes as expected as one looks around the cave. Local correction is needed because the cave is a limited environment, which means the reflections inside of it should behave differently than those caused by an infinite skybox. Having the local correction makes the effect appear realistic; without it the reflections remain static and give the illusion that the geometry is sliding over the image instead of the reflections being within the geometry. There is no feeling of depth or feeling that the ice is thick without it.
More information as to how local cubemaps work can be found in this blog: http://community.arm.com/groups/arm-mali-graphics/blog/2014/08/07/reflections-based-on-local-cubemaps
It was an interesting journey to try and understand the workings behind this effect, as it was achieved through relatively unconventional means. The in-depth study helped us understand how the two normals behave together, and what exactly causes the result displayed in the demo. The icy wall reflections are very effective, inexpensive and perform as desired: making the cave seem as it is actually made of thick ice.