in Programming, Shaders, Unity

Physically Based Rendering and lighting models in Unity3D

Part 1, Part 2, Part 3, Part 4, Part 5, [download the Unity3D package]

Why is it colder at the poles and hotter on the equator? This question, which seems completely unrelated to shaders, is actually fundamental to understand how lighting models work. As explained in the previous part of this tutorial, surface shaders use a mathematical model to predict how light will reflect on triangles. Generally speaking, Unity supports two types of shading techniques, one for matte and one for specular materials. The former ones are perfect for opaque surfaces, while the latter ones simulate objects which reflections. The Maths behind these lighting models can get quite complicated, but understanding how they work is essential if you want to create your own, custom lighting effect. Up to Unity4.x, the default diffuse lighting model was based on the Lambertian reflectance.

Diffuse surfaces: the Lambertian model

Going back to the initial question, the reason why the poles are colder, is because they receive less sunlight compared to the equator. This happens because of their relative inclination from the sun. The following diagram shows how the polar edges of the octagon receive sensibly less light compared the frontal one:

Light Geometry2

The blue line represents the normal of the face, which is an orthogonal vector of unit length. The orange one represents the direction of the light. The amount of light I on the fade depends on the angle between the normal N and the light direction L. In the Lambertian model, this quantity is equal to the vertical component of the incident light ray:

light cos

Which can be expressed as:

    \[I= \left \| L \right \| \, cos \alpha = cos \alpha\]

where \left \| L \right \| is the length of L (which is one by definition) and \alpha is the angle between N and L. This operation, in vector algebra, is known as dot product as was briefly introduced in the previous post. Formally, it is defined as the follow:

    \[A \cdot B = \left \| A \right \|  \, \left \| B \right \| \, cos \alpha\]

and is available in Cg / HLSL using the function dot. It returns a number ranging from -1 to +1 which is zero when the vectors are orthogonal, and \pm1 when they are parallel. We’ll use it as a multiplier coefficient to determine how much light triangles receive from a light source.

The Lambertian shader

We now have all the necessary background to understand how a Lambertian model can be implemented in a shader. Cg / HLSL allows to replace the standard Lambertian model with a custom function. In line 8, using SimpleLambert in the directive #pragma surface forces the shader to search for a function called LightingSimpleLambert:

Shader "Example/SimpleLambert" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType" = "Opaque" }
		CGPROGRAM
		#pragma surface surf SimpleLambert

		struct Input {
			float2 uv_MainTex;
		};

		sampler2D _MainTex;
		void surf (Input IN, inout SurfaceOutput o) {
			o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
		}

		half4 LightingSimpleLambert (SurfaceOutput s, half3 lightDir, half atten) {
			half NdotL = dot (s.Normal, lightDir);
			half4 c;
			c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten * 2);
			c.a = s.Alpha;
			return c;
		}

		ENDCG
	} 
	Fallback "Diffuse"
}

Lines 19-25 shows how the Lambertian model can be naively re-implemented in a surface shader. NdotL represents the coefficient of intensity, which is then multiplied to the colour of the light. The parameters atten is used to modulate the intensity of the light. The reason why it is multiplied by two is… a trick initially used by Unity3D to simulate certain effects. As explained by Aras Pranckevičius, it has been kept in Unity4 for backward compatibility. This has been finally fixed in Unity5, so if you’re reimplementing a Lambertian model for Unity5, just multiply by one.

Understanding how the standard lighting model works is an essential step if we want to change it. Many alternative shading techniques, in fact, still use the Lambertian model as their first step.

⭐ 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.

Toon shading

One of the most used styles in games lately is the toon shading (also known as cel shading). It’s a non photorealistic rendering style which changes the way light reflects on a model to give the illusion it has been hand drawn. To implement this style, we need to replace the standard lighting model used so far with a custom one. The most common technique to achieve this style is to use an additional texture, called _RampTex in the shader below.

Shader "Example/Toon Shading" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
		_RampTex ("Ramp", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType" = "Opaque" }
		CGPROGRAM
		#pragma surface surf Toon

		struct Input {
			float2 uv_MainTex;
		};
		sampler2D _MainTex;
		void surf (Input IN, inout SurfaceOutput o) {
			o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
		}

		sampler2D _RampTex;
		fixed4 LightingToon (SurfaceOutput s, fixed3 lightDir, fixed atten)
		{
			half NdotL = dot(s.Normal, lightDir); 
			NdotL = tex2D(_RampTex, fixed2(NdotL, 0.5));

			fixed4 c;
			c.rgb = s.Albedo * _LightColor0.rgb * NdotL * atten * 2;
			c.a = s.Alpha;

			return c;
		}

		ENDCG
	} 
	Fallback "Diffuse"
}

toon

The LightingToon model calculates the Lambertian coefficient of intensity NdotL and uses the ramp texture to re-map it onto a different set of values. In this case, to restrict the intensity to four values only. Different ramp textures will achieve slightly different variants of toon shading.

ramp

Specular surfaces: the Blinn-Phong model

The Lambertian model cannot simulate materials which have specular reflections. For them, another technique is necessary; Unity4.x adopts the Blinn-Phong model. Rather than calculating the dot product between the normal N and the light direction L, it uses H which is the vector halfway between L and the view direction V:

BlinnPhong

    \[\mathrm{Lambertian\,model:} \,\,\,\     I = N \cdot L\]

    \[\mathrm{Blinn-Phong\,model:} \,\,\,\     I = \left ( N \cdot  H \right )^{specular} \cdot gloss\]

    \[H = \frac{L+V}{\left| L +V\right|}\]

The quantity N \cdot H is then processed further using the specular and gloss settings. If you need more information on how Unity calculates its lighting models, you can download the source for its built-in shaders. Both the Lambertian and Blinn-Phong surface functions are calculated in the file Lighting.cginc. In Unity5 they’re available as Legacy shaders.

Physically Based Rendering in Unity5

As mentioned at the beginning of this post, Uniy4.x was using the Lambertian lighting model as its default shader. Unity5 has changed that, introducing the Physically Based Rendering (PBR). The name sounds very intriguing, but is nothing more then another lighting model. Compared to the Lambertian reflectange, PBR provides a more realistic interaction between lights and objects. The term physically refers to the fact that PBR takes into account physical properties of materials, such as conservation of energy and light scatter. Unity5 provides two different ways for artists and developers to create their PBR materials: the Metallic workflow and the Specular workflow. In the first one, the way a material reflects light depends on how metallic it is. A cheap explanation is that light is an electromagnetic wave, and it behaves differently when in contact with a conductor or an insulator. In the Specular workflow, a specular map is provided instead. Despite being presented as two different things, Metallic and Specular materials are actually different ways to initialise the same shader; Marmoset has a very well done tutorial in which it shows how the same material can be created both with the Metallic and Specular workflows. Having two workflows for the same thing is one of the main sources of misunderstanding when approaching Unity5 shaders for the first time. Joe Wilson made an incredibly clear tutorial oriented to artists: it’s a good starting point if you want to learn how to use PBR to create highly realistic materials. If you need some more technical information, there’s a very well done primer on PBR on the Unity5 blog.

metallic

The name of Unity5’s new lighting model is, simply, Standard. The reason behind this name is that PBR is now the default material for every new object created in Unity3D. Moreover, every new shader file created is automatically configured as a PBR surface shader:

Shader "Custom/NewShader" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_Glossiness ("Smoothness", Range(0,1)) = 0.5
		_Metallic ("Metallic", Range(0,1)) = 0.0
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Standard fullforwardshadows

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		half _Glossiness;
		half _Metallic;
		fixed4 _Color;

		void surf (Input IN, inout SurfaceOutputStandard o) {
			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			// Metallic and smoothness come from slider variables
			o.Metallic = _Metallic;
			o.Smoothness = _Glossiness;
			o.Alpha = c.a;
		}
		ENDCG
	} 
	FallBack "Diffuse"
}

soldier standard

Line 14 tells Unity3D that this surface shader will use the PBR lighting model. Line 17 signals that advanced features are being used in this shader, hence it won’t be able to run on outdated hardwares. For the same reason, SurfaceOutput can’t be used with PBR; SurfaceOutputStandard must be used instead.

PBR surface outputs

Along Albedo, Normal, Emission and Alpha, there are three new properties available in SurfaceOutputStandard:

  • half Metallic: how metallic the object is. It’s usually either 0 or 1, but intermediate values can be used for bizarre materials. It will determine how light reflects on the material;
  • half Smoothness: indicates how smooth the surface is, from 0 to 1;
  • half Occlusion: indicates the amount of ambient occlusion.

If you want to use the Specular workflow, you should use SurfaceOutputStandardSpecular which replaces half Metallic with float3 Specular. Note that while the Lambertian reflectance has a specular field which is half, the specular property in PBR is a float3. It corresponds to the RGB colour of the specularly reflected light.

Shading technique used in Unity

So far, four different shading techniques have been introduced. To avoid confusion, you can refer to the table below which indicates, in order: shading technique, surface shader name, surface output structure name and the name of the respective built-in shader.

Unity4 and below Unity5 and above
Diffuse

Lambertian reflectance

Lambert, SurfaceOutput

Bumped Diffuse

    \[I = N \cdot L\]

Physically Based Rendering (Metallic)

Standard, SurfaceOutputStandard

Standard

Specular

Blinn–Phong reflection

BlinnPhong, SurfaceOutput

Bumped Specular

    \[I = \left ( N \cdot  H \right )^{specular} \cdot gloss\]

Physically Based Rendering (Specular)

StandardSpecular, SurfaceOutputStandardSpecular

Standard (Specular setup)

The equations behind PBR are rather complicated. If you are interested in understanding the Maths behind it, both the Wikipedia page for Rendering equation and this article are good starting points.

If you imported the Unity3D package (which includes the shader used in this tutorial), you’ll notice how the built-in “Bumped Diffuse” shader yields a very different result compared to its naive implementation “Simple Lambert”. This is because Unity3D’s shader adds additional features, such as normal maps.

Conclusion

This post introduced custom lighting models for surface shaders. The Lambertian and Blinn-Phong models are briefly explained, with a real example of how they can be changed to obtain different effects. It is important to notice that purely diffuse materials don’t really exist in real life: even the most dull material you can think of will have some specular reflection. Diffuse materials were very common in the past, when calculating specular reflections was too expensive.

The post also shows what physically based rendering is, and how it can be used in Unity5. PBR shaders are nothing more then surface shaders with a very advanced lighting model.


💖 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

  1. These tutorials are great! Learning a ton.

    May I make a nit-picky comment about your grammar though? You seem to keep using “then” when you mean “than”. This has happened in both this post and the previous.

    than = comparison

    then = sequence

    For example, this is incorrect, and should be “than”

    “Rather then calculating the dot product”

    This is correctly using “then”:

    “The quantity N \cdot H is then processed”

    I tried to find a way to notify you in private, but couldn’t seem to. So feel free to delete this once you see it. 🙂

    • Hey! Thank you for the comment, I always appreciate people who are willing to spend their time with constrictive criticism! I’m not a native speaker, and despite I’m very well aware of the difference between thEn and thAn, I keep mixing them when I’m writing haha! 😀 Thank you again!

  2. Hi Alan, thanks for providing these great tutorials!
    I have a issue with the toon shader. It looks like the ramp texture is “overshooting” .. means, after it gets complete dark it suddenly gets white after that. sorry for bad explanation, maybe this screenshot helps: https://prnt.sc/qvocns
    I’m using unity 2019.2. on windows.
    I had the idea to clamp the result of NdotL with saturate() between 0 – 1 and that helped a bit, but didnt fix it completely..

    • half NdotL = (dot(s.Normal, lightDir)+1)/2;
      If you do this you will raise the value by 1 and then clamp it between 0 and 1.
      Hope it helps.

Webmentions

  • Journey Sand Shader: Ripples - Alan Zucconi April 5, 2021

    […] pow is often used to modulate their sharpness and contrast. We have how and why this works in an earlier tutorial dedicated to Physically Based Rendering. […]

  • Journey Sand Shader: Specular Reflection - Alan Zucconi April 5, 2021

    […] 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 […]

  • [Перевод] Исследование шейдера песка игры Journey | INFOS.BY April 5, 2021

    […] Скалярное произведение является мерой «совпадения» двух векторов относительно друг друга, и изменяется в интервале от (у двух идентичных векторов) до (у двух противоположных векторов). Скалярное произведение — это фундамент затенения, который я подробно рассматривал в туториале Physically Based Rendering and Lighting Models. […]

  • Journey Sand Shader: Diffuse Colour - Alan Zucconi April 5, 2021

    […] The dot product measures how “aligned” two vectors are to each other, and ranges from (for two identical vectors) to (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. […]

  • CD-ROM Shader: Diffraction Grating - Part 1 - Alan Zucconi April 5, 2021

    […] the lighting function of a Standard Surface shader. If you are unfamiliar with the procedure, Physically Based Rendering and Lighting Models provides a good […]

  • Fast Subsurface Scattering in Unity (Part 1) - Alan Zucconi April 5, 2021

    […] kind of reasoning should sound familiar. We have encountered something similar in the tutorial on Physically Based Rendering and Lighting Models in Unity 5, where we showed how such a behaviour can be obtained using a mathematical operator called the dot […]

  • 3D Printer Shader Effect - Part 1 - Alan Zucconi April 5, 2021

    […] a previous tutorial, PBR and Lighting Models, we have explored how to create custom lighting models for surface shaders. An unlit shader […]

  • Unity3D: Tutoriais e Documentação de Shaders | April 5, 2021

    […] Physically Based Rendering and lighting models […]

  • Arrays & shaders: heatmaps in Unity3D - Alan Zucconi April 5, 2021

    […] Part 3. Physically Based Rendering and Lighting Models […]

  • Postprocessing and image effects in Unity - Shader Tutorial April 5, 2021

    […] 1, Part 2, Part 3, Part 4, Part 5, [download the Unity3D […]

  • Vertex and fragment shaders in Unity3D | Alan Zucconi April 5, 2021

    […] 1, Part 2, Part 3, Part 4, Part 5, [download the Unity3D […]

  • Surface shaders in Unity3D | Alan Zucconi April 5, 2021

    […] 1, Part 2, Part 3, Part 4, Part 5, Part […]

  • A gentle introduction to shaders in Unity3D | Alan Zucconi April 5, 2021

    […] 1, Part 2, Part 3, Part 4, Part 5, Part […]