in shader, tutorial, Unity3D

Journey Sand Shader: Diffuse Colour

Share Button

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.

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
By convention, the normal to a surface N is a unit vector that points away from the surface itself.

By using this analogy, one would imagine that the light direction L points away from the light source, following the direction travelled by the light. That is not the case, as the light direction is the unit vector that points towards the direction the light is coming from.

This can be confusing, especially if you are new to shader coding. However, using this convention actually makes the equations easier.

 

❗ Lambertian reflectance in Unity
Before the introduction of the Standard Shader in Unity 5, the Lambertian reflectance was the model of choice for shading lit surfaces.

It is still accessible from the material inspector, under “Legacy shader“, as “Diffuse“.

If you are writing your own surface shader, instead, the Lambertian reflectance is available as lighting function called Lambert:

Its implementation can be found in the function LightingLambert, defined in the file  CGIncludes\Lighting.cginc.

 

❗ Lambertian reflectance and climate
The Lambertian reflectance is a rather old model, but it provides an insightful understanding of something so complex like surface shading. It can also be used to explain many other phenomena. For instance, that very same diagram explains why the poles are colder than the equator.

 

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.

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:

[IMAGE COMPARISON]

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.

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?
Even though the diffuse contrast reflection was not developed to be physically correct, we can still try to understand what it does.

At its core, it still used the Lambertian reflectance. The first obvious change is the fact that the overall result is multiplied by 4. This means that all pixels that would have normally received 25\% of light, now shine as if they were receiving 100\%. Multiplying everything by 4 makes the gentle Lambertian shading much stronger and its transition from darkness to light becomes smaller. This suits t is sharpening the shadow.

The effect of multiplying the y component of the normal direction by 0.3 is much trickier to explain. Changing the components of a vector changes the overall direction it points at. By reducing the  y component to only 30\% of its original value, the diffuse contrast reflectance forces the shadows to be more vertical.

As a side note, the dot product directly measures the angle of two vectors only when they both have length 1. The change that has been introduced shortens the length of N, which is not a unit vector any more.

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.

 

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

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.

📧 Stay updated

A new tutorial is released every week.

💖 Support this blog

This websites 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.

Paypal
Ko-fi
Twitter_logo

Write a Comment

Comment

  1. 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?

Webmentions

  • A Journey Into Journey's Sand Shader - Alan Zucconi October 30, 2019

    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/