in Shaders, Tutorial, Unity

Journey Sand Shader: Specular Reflection

This is the fourth part of the online series dedicated to Journey Sand Shader.

In this fourth post, we will focus on the specular reflections that make the dunes look like an ocean of sand.

One of the most intriguing effects of Journey‘s sand rendering, is the way dunes shine in the light. Such reflection is called specular, from the Latin speculum, which means mirror. Specular reflection is an umbrella term that includes all those types of interactions in which light is strongly reflected in one direction, instead of being scattered and diffuse. It is because of specular reflections that both water and polished surfaces appear to shine at certain angles.

Journey features three different types of specular reflections: rim lighting, ocean specular and glitter reflections, as seen in the diagram below. In this lecture, we will address the first two.

Rim Lighting

You might have noticed that each level of Journey features a limited set of colours. While this adds to its strong and clean aesthetic, it is rather problematic for the sand rendering. Dunes are only rendered using a handful of shades, so it might be impossible to distinguish where one ends and another starts in the far distance.

To compensate for this, the edge of each dune presents a subtle shimmering effect, which highlights its contours. This prevents dunes from disappearing into the horizon, and gives the illusion of a much larger and complex environment.

Before exploring how such an effect can be achieved, let’s extend the lighting function presented in the first lecture to include both the diffuse colour (previously discussed in Journey Sand Shader: Diffuse Colour) and a new generic specular component.

float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi)
{
    // Lighting properties
    float3 L = gi.light.dir;
    float3 N = s.Normal;

    // Lighting calculation
    float3 diffuseColor	= DiffuseColor (N, L);
    float3 rimColor     = RimLighting  (N, V);

    // Combining
    float3 color = diffuseColor + rimColor;

    // Final color
    return float4(color * s.Albedo, 1);
}

In the snippet above we can see that the specular component of the rim lighting, called rimColor, is simply added to the original diffuse colour.

❗ High Dynamic Range and Bloom Effects
Both the diffuse component and the rim lighting are RGB colours ranging from 0 to 1. The final colour is given by their sum. This means that, potentially, it might be greater than 1.

If you are new to shader coding, you might know that colours should be clamped between 0 and 1. However, there are cases in which we want colours to be greater than 1. If your camera is set to support High Dynamic Range, pixels with an intensity greater than 1 will “leak” light onto nearby pixels. This technique is used to simulate bloom effects, halos and specular highlights. However, it requires a post-processing effect to perform this operation.

Fresnel Reflectance

There are many ways in which a rim lighting can be achieved. The most common in shader coding relies on the well-known Fresnel reflectance model.

❓ How to pronounce Fresnel?
It’s /frəˈnɛl/, which sounds a bit like fruh-nel.

If you have learnt shaders from written tutorials, you might have never heard the correct pronunciation of Fresnel. As a form of respect towards those researchers which works we are using every day, I believe we should all make an effort to pronounce their names correctly.

 

To understand the equation behind the Fresnel reflectance, it is helpful to visualise where it occurs. The diagram below shows a dune seen by a camera (in blue). The red arrow indicates the surface normal to the top of the dune, which is where we want the specular reflection to be. It is easy to see that all edges of the dune share a similar property: their normal (N, in red) is orthogonal to the view direction (V, in blue).

Similarly to what we have done in  Journey Sand Shader: Diffuse Colour, we can use the dot product between N and V to get a measure of their alignment. In this case, the N \cdot V is 0 when the two unit vectors are orthogonal; we can use 1- N \cdot V instead, to measure how misaligned they are instead.

Using 1- N \cdot V directly would not yield good results, as it reflects way too much. If we want to make the reflection sharper, we can simply take its power. The power of a value between 0 and 1 remains bounded within the same range, but the transition between light and dark becomes sharper.

The Fresnel reflectance model states that the intensity of light I is given by:

(1)   \begin{equation*}  I = \left(1 -N \cdot V\right)^\mathit{power} * \mathit{strength} \end{equation*}

where \mathit{power} and \mathit{strength} are two parameters that can be used to control the contrast and the strength of the effect. \mathit{power} and \mathit{strength} are sometimes called specular and gloss, although naming conventions may vary.

Equation (1) translates very easily to code:

float _TerrainRimPower;
float _TerrainRimStrength;
float3 _TerrainRimColor;

float3 RimLighting(float3 N, float3 V)
{
    float rim = 1.0 - saturate(dot(N, V));
    rim = saturate(pow(rim, _TerrainRimPower) * _TerrainRimStrength);
    rim = max(rim, 0); // Never negative
    return rim * _TerrainRimColor;
}

Its result can be seen in the animation below.

 

Ocean Specular

One of the most peculiar aspects of Journey‘s gameplays is that, at times, the player is literally surfing on the dunes. Lead Engineer John Edwards explained how thatgamecompany indeed wanted the sand to feel more like a fluid, than a solid.

This is not entirely incorrect, since sand can be thought of as a rough approximation of a fluid. And under certain circumstances, for instance in an hourglass, it does behave like one.

To reinforce the idea that sand could have a fluid component, Journey adds a second specular effect, which is often seen on liquid bodies. John Edwards referred to this as ocean specular, and the idea is to get the same type of reflection that you would see on an ocean or lake at sunset (below).

As before, let’s change the lighting function LightingJourney to include a new type of specular reflection.

float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi)
{
    // Lighting properties
    float3 L = gi.light.dir;
    float3 N = s.Normal;
    float3 V = viewDir;

    // Lighting calculation
    float3 diffuseColor	= DiffuseColor  (N, L);
    float3 rimColor     = RimLighting   (N, V);
    float3 oceanColor   = OceanSpecular (N, L, V);

    // Combining
    float3 specularColor = saturate(max(rimColor, oceanColor));
    float3 color = diffuseColor + specularColor;

    // Final color
    return float4(color * s.Albedo, 1);
}
❓ Why do we take the maximum of the two specular components?
It is likely there will be an overlap between rim lighting and ocean specular. Certain parts of the dune, especially at glazing angles, might exhibit both Fresnel and Blinn-Phong reflectance. Summing both contributions would make dune too shiny.

Taking the maximum is an efficient, yet effective way to avoid this problem.

⭐ Suggested Unity Assets ⭐
Unity is free, but you can upgrade to Unity Pro or Unity Plus subscriptions plans to get more functionality and training resources to power up your projects.

Specular reflections on water are often implemented using the Blinn-Phong reflectance, which is an inexpensive solution for shiny materials. It was first described by James F. Blinn in 1977 (paper: “Models of Light Reflection for Computer Synthesized Pictures“), as an approximation of an earlier shading technique developed by Bùi Tường Phong in 1973 (paper: “Illumination for Computer Generated Pictures“).

Using Blinn-Phong shading, the luminosiry I of a surface is given by the following equation:

(2)   \begin{equation*}  I = \left(N \cdot H\right)^\mathit{power} * \mathit{strength} \end{equation*}

where

(3)   \begin{equation*}  H = \frac{V + L}{\left \| V+L \right \|} \end{equation*}

The denominator of (3) divides the vector V+L by its length. This ensures that H has length 1. The equivalent shader function to perform this operation is normalize. Geometrically speaking, H represents the vector “in between” V and L, which is why is called the half vector.

❓ Why is H in between V and L?
It might not be intuitive to understand why H is the vector in between V and L.

To understand why, lets, we should recall the Parallelogram law, which gives a geometrical interpretation to the sum between two vectors. The sum of V and L can be found by translating L at the end of V, and drawing a new vector from the base of V to the tip of L.

The second step involved is to understand that the two quantities V+L and L+V are the same. This means that the following two geometrical constructions are the same:

We can join them to form a parallelogram, which sides share the same length. Because of this, we have the guarantee that the diagonal (which is V+L) bisects the angle. This means that the blue and yellow angles are equal (below, left).

Now we know that V+L is a vector in between V and L, but its length is not guaranteed to be 1. Normalising means stretching it until it reaches length 1, making it into a unit vector (above, right).

 

For a more detailed description of the Blinn-Phong reflectance, you can read Physically Based Rendering and Lighting Models. Below, you can see a simple implementation in shader code.

float _OceanSpecularPower;
float _OceanSpecularStrength;
float3 _OceanSpecularColor;

float3 OceanSpecular (float3 N, float3 L, float3 V)
{
    // Blinn-Phong
    float3 H = normalize(V + L); // Half direction
    float NdotH = max(0, dot(N, H));
    float specular = pow(NdotH, _OceanSpecularPower) * _OceanSpecularStrength;
    return specular * _OceanSpecularColor;
}

The following animation provides a comparison between a traditional diffuse Lambertian shading and a specular Blinn-Phong one:

What’s Next…

In this fourth part of the online series about the sand rendering in Journey, we focused on the shimmering reflections that make Journey’s dunes appear almost like an ocean.

In the next part, Journey Sand Shader: Glitter Reflection, we will continue working on the specular reflections, adding tiny glitters to the dunes to make it even more realistic.

Credits

The videogame Journey is developed by Thatgamecompany and published by Sony Computer Entertainment. It is available for PC (Epic Store) and PS4 (PS Store).

The 3D models of the dunes, backgrounds and lighting settings were made by Jiadi Deng.

The 3D model of the Journey’s player was found on the (now closed) FacePunch forum.

Download Unity Package

Become a Patron!
If you want to recreate this effect, the full Unity package is available for download on Patreon. It includes everything needed, from the shaders to the 3D models.

💖 Support this blog

This website exists thanks to the contribution of patrons on Patreon. If you think these posts have either helped or inspired you, please consider supporting this blog.

Patreon Patreon_button
Twitter_logo

YouTube_logo
📧 Stay updated

You will be notified when a new tutorial is released!

📝 Licensing

You are free to use, adapt and build upon this tutorial for your own projects (even commercially) as long as you credit me.

You are not allowed to redistribute the content of this tutorial on other platforms, especially the parts that are only available on Patreon.

If the knowledge you have gained had a significant impact on your project, a mention in the credit would be very appreciated. ❤️🧔🏻

Write a Comment

Comment

Webmentions

  • A Journey Into Journey's Sand Shader - Alan Zucconi

    […] Part 4. Journey Sand Shader: Specular Reflection […]

  • Journey Sand Shader: Ripples - Alan Zucconi

    […] Part 4. Journey Sand Shader: Specular Reflection […]

  • Journey Sand Shader: Diffuse Colour - Alan Zucconi

    […] Part 4. Journey Sand Shader: Specular Reflection […]

  • Journey Sand Shader: Glitter Reflection - Alan Zucconi

    […] Part 4. Journey Sand Shader: Specular Reflection […]