Journey Sand Shader: Diffuse Colour

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

In this second post we will focus on the lighting model used in the game, and how to recreate it in Unity.

In the previous instalment of this series, we have laid the foundation for what is going to become our take on Journey’s sand shader. As previously discussed, the lighting function is used in surface shaders to calculate the light contribution, which results in a surface exhibiting shades and highlights. In Journey, we have identified several effects that fall into this category. We will start by tackling the most basic (and simple) effect that is at the very core of this shader: its diffuse lighting.

For now, let’s ignore all of the other effects and components, so that we can only focus on the sand lighting.

The custom lighting function seen in the previous post, called LightingJourney, is simply delegating the calculation of the sand’s diffuse colour to a function called DiffuseColor.

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);

    // Final color
    return float4(diffuseColor, 1);
}

By keeping each effect self-contained in its own function, we are programming in a more modular and clean way.

The Lambertian Reflectance

Before introducing Journey’s diffuse lighting, it is good to start showing what a “basic” diffuse lighting function looks like. The simplest lit shading technique for matte materials is based on a principle known as Lambertian reflectance.  It is a model that well approximate the look of most non-shiny, non-metallic surfaces. It is named after Swiss polymath Johann Heinrich Lambert, who introduced the concept in 1760.

There is a basic idea behind the concept of Lambertian reflectance: the brightness of a surface depends on the amount of light it receives. Geometrically, this can be seen in the diagram below, where a sphere is illuminated by a light source far away. While the red and green regions of the sphere receive the same amount of light, they have significantly different surface areas. If light on the red region is spread over a larger area, it means that each red unit receives less light, compared to the green ones.

Technically speaking, the Lambertian reflectance depends on the relative angle between the surface and the incoming light. Mathematically, we say that it is a function of the surface normal and the light direction. Those quantities are expressed using two vectors of length one (called unit vectors), N and L, respectively. Unit vectors are the standard way to represents directions in the context of shader coding.

❗ The conventions for N and L

❗ Lambertian reflectance in Unity

❗ Lambertian reflectance and climate

 

From a closer inspection, we can see that a surface receives the maximum amount of light when its normal is aligned with the light direction. Conversely, no light is received when the two unit vectors are orthogonal to each other.

It appears that the angle between N and L is critical for the Lambertian reflectance. Moreover, the intensity peaks to 100\% when the angle is 0, and it reaches a minimum of 0\% as the angle approaches 90^{\circ}. If you are familiar with vector algebra, you might have recognised that the quantity that represents the Lambertian reflectance I is N \cdot L (pronounced “N dot L“), where the operator \cdot is known as the dot product.

(1)   \begin{equation*} I = N \cdot L\end{equation*}

The dot product measures how “aligned” two vectors are to each other, and ranges from +1 (for two identical vectors) to -1 (for two opposite vectors). The dot product is at the heart of shading, and it has been covered extensively in Physically Based Rendering and Lighting Models.

Implementation

Both N and L are easily accessible in the lighting function of a surface shader as s.Normal and gi.light.dir. For simplicity, they will be renamed just N and L in the shader code.

float3 DiffuseColor(float3 N, float3 L)
{
    float NdotL = saturate( dot(N, L) );
    return NdotL;
}

The saturate function keeps the value clamped between 0 and 1. However, since the dot product ranges from -1 to +1, we only need to operate on its negative values. This is why the Lambertian reflectance is often implemented as:

float NdotL = max(0, dot(N, L) );

The Diffuse Contrast Reflectance

While the Lambertian reflectance shades most materials well, it is neither physically based nor photorealistic. Historically speaking, most 3D games from the older generation heavily relied on Lambertian shaders. Games which are relying on this technique often feel old, because they might unintentionally resemble the aesthetic of older titles. Unless this is your intention, the Lambertian reflectance should be avoided in favour of more modern approaches.

One such model is the Oren-Nayar reflectance model, which was originally discussed in Generalization of Lambert’s Reflectance Model in a paper published in 1994 by Michael Oren and Shree K. Nayarin. The Oren-Nayar model is a generalisation of the Lambertian reflectance, and is specifically designed for rough surfaces. The developers of Journey initially wanted to use Oren-Nayar reflectance as the base for their sand shader. However, that idea has been dropped, due to its computational cost.

In his talk from 2013, Technical Artist John Edwards explained that the actual reflectance model devised for Journey’s sand was based on a series of trials and errors, Their intention was not to recreate a photorealistic rendering of a desert, but to give life to a precise and immediately recognisable aesthetics.

Following his indication, the shading model they have devised follows this equation:

(2)   \begin{equation*} I = 4 * \left( \left(N\odot \left[1, 0.3, 1\right]\right) \cdot L\right)\end{equation*}

where \odot is the element-wise product between two vectors.

float3 DiffuseColor(float3 N, float3 L)
{
    N.y *= 0.3;
    float NdotL = saturate(4 * dot(N, L));
    return NdotL;
}

The reflectance model (2) was referred by John Edwards simply as diffuse contrast, so that is the name that will be used for the rest of this tutorial.

The animation below shows a comparison between a Lambertian shading (left) and Journey’s diffuse contrast (right).

 

❓ What is the purpose of 4 and 0.3?

From Black & White To Colours

All the animations seen so far are black and white because they were showing the values of their reflectance model, which ranges from 0 to 1. We can easily add colours by using NdotL as the coefficient to interpolate between two colours: one for the fully shaded and one for the fully lit sand.

float3 _TerrainColor;
float3 _ShadowColor;

float3 DiffuseColor(float3 N, float3 L)
{
    N.y *= 0.3;
    float NdotL = saturate(4 * dot(N, L));
	
    float3 color = lerp(_ShadowColor, _TerrainColor, NdotL);
    return color;
}

📰 Ad Break

What’s Next…

In this second part of the online series about the sand rendering in Journey, we focused on how the dunes were shaded, using a custom reflectance model.

In the next part, Journey Sand Shader: Sand Normal, we will explore how to give three-dimensionality to the dunes using bump mapping.

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.

Comments

8 responses to “Journey Sand Shader: Diffuse Colour”

  1. […] Part 2. Journey Sand Shader: Diffuse Colour […]

  2. […] Part 2. Journey Sand Shader: Diffuse Colour […]

  3. thanks for the turorial
    can i use the standard shader instead ?

  4. […] Part 2. Journey Sand Shader: Diffuse Colour […]

  5. […] Part 2. Journey Sand Shader: Diffuse Colour […]

  6. I’ve been trying to follow along, but I keep getting a “Surface shader lighting model ‘Journey’ is missing a GI function (Journey_GI)” error in Unity.

    Any ideas?

    1. Yes!

      Each part of the tutorial covers a new aspect of the effect. So obviously I am not showing a full shader yet.

      The error you have is because you need a GI function coupled with the lighting function of a Surface Shader. I talked about how to fix it in this tutorial: https://www.alanzucconi.com/2016/10/02/3d-printer-shader-effect-part-1/

  7. […] Part 2. Journey Sand Shader: Diffuse Colour […]

Leave a Reply

Your email address will not be published. Required fields are marked *