⏱ Reading time: 5 min

In the last articles, we have seen some essential details about the HLSL language and shaders.

You can find previous articles at these links:

All the things we’ve seen so far can be handy if we have to start writing a shader from scratch, which will undoubtedly happen, but Unity also provides us with a completely customized class of shaders that can perform complex lighting calculations.
These Shaders are called “Surface Shaders”.

Surface Shaders

When we use one of these “Surface Shaders” we work quite differently from the normal one, so it may seem that some things happen by magic, in reality, Unity performs a series of hidden operations to make sure that the “Surface Shaders” work.

Unity provides several types of “surface shaders” some very basic and other more complex with support for physically based lighting models (PBR) or specular effects.

Standard output structure of surface shaders is this:

struct SurfaceOutput
{
    fixed3 Albedo;  // diffuse color
    fixed3 Normal;  // tangent space normal, if written
    fixed3 Emission;
    half Specular;  // specular power in 0..1 range
    fixed Gloss;    // specular intensity
    fixed Alpha;    // alpha for transparencies
};

Built-in PBR Surface Shader and Specular Lighting models use these output structures.

struct SurfaceOutputStandard
{
    fixed3 Albedo;      // base (diffuse or specular) color
    fixed3 Normal;      // tangent space normal, if written
    half3 Emission;
    half Metallic;      // 0=non-metal, 1=metal
    half Smoothness;    // 0=rough, 1=smooth
    half Occlusion;     // occlusion (default 1)
    fixed Alpha;        // alpha for transparencies
};
struct SurfaceOutputStandardSpecular
{
    fixed3 Albedo;      // diffuse color
    fixed3 Specular;    // specular color
    fixed3 Normal;      // tangent space normal, if written
    half3 Emission;
    half Smoothness;    // 0=rough, 1=smooth
    half Occlusion;     // occlusion (default 1)
    fixed Alpha;        // alpha for transparencies
};

Lighting Properties

To better understand how these different structures work, let’s try to analyze the various parameters together.

  • Albedo, is the base color of the material. Evolution of the old base texture map and diffuse map. It receives colour from lights that illuminate it and is naturally dark in the shadows. The Albedo colour does not affect specular light. It’s stored as a 3-dimensional vector.
  • Normal, this is the base Normal of the material. All of the normals by default must be expressed in “tangent space”. It means that the normal points not in a specific direction but away from the surface. Normals are stored as a 3-dimensional vector.
  • Emission, makes materials glow. Lights or shadows do not affect emissive colours. The Emission parameter could have a value higher than 1 ( with HDR colours). Bloom post processing effect works in conjunction with this parameter. Emission is also stored as a 3-dimensional Color Vector.
  • Metallic, makes objects reflect in different ways. The Metallic value is stored as a scalar ( 1- dimensional ) value, where 0 represents a non-metallic material and 1 an entirely metallic one.
  • Smoothness, this value is a scalar value that represents how smooth a material is.
    A material with a 0 smoothness looks rough, the light seems to be reflected in all directions and we can’t see any specular or environmental reflection.
    On the other hand, when we put a smoothness of one the material appears too polished. In addition, smoothness is also a one-dimensional scalar value.
  • Occlusion, removes the light from the material. It’s a not very used parameter. Further, it is stored as a parameter but differently to others, 1 means the pixel has a full brightness, and 0 means it’s always dark.
  • Alpha, gets transparency out of material. If a Material is not defined as “Opaque”, by setting the Alpha to 0, any of its part can be turned invisible by setting the Alpha to 0. In addition, Alpha is also stored as a one-dimensional scalar value.

Unity has created some beautiful diagrams to understand each parameter of the various surface shaders and how they influence the final yield of the materials

Unity shared calibration scene - specular value charts
Unity shared calibration scene – Specular value charts
Unity shared calibration scene - metallic value charts
Unity shared calibration scene – Metallic value charts

Time to practice

Let’s start, as always, with creating a new shader in Unit, let’s call it “Shader_02” and let’s use this base template to initialize it.

Shader "Shader_02"{
	
	Properties{
		
	}

	SubShader{

		Tags{ "RenderType"="Opaque" "Queue"="Geometry"}

		Pass{
			CGPROGRAM


                #pragma surface surf Standard fullforwardshadows
		#pragma target 3.0



			ENDCG
		}
	}
}

First, we add these parameters. Note in particular the [HDR] tag which allows us to be able to specify a colour beyond the normal range

		_Color ("Tint", Color) = (0, 0, 0, 1)
		_MainTex ("Texture", 2D) = "white" {}
                [Normal] _Normal ("Normal", 2D) = "white" {}
		_Smoothness ("Smoothness", Range(0, 1)) = 0
		_Metallic ("Metalness", Range(0, 1)) = 0
		[HDR] _Emission ("Emission", color) = (0,0,0)

The we define all the variables in the CGPROGRAM to get the proprieties , as always after the pragma declarations.

		
		fixed4 _Color;
                sampler2D _MainTex;
                sampler2D _Normal;
		half _Smoothness;
		half _Metallic;
		half3 _Emission;

So we define the struct Input that will use the uv

struct Input {
			float2 uv_MainTex;
                    		};

and as last thing we finally add the surf function that will include the Surface Output Standard Shader built into Unity.

		void surf (Input i, inout SurfaceOutputStandard o) {

			fixed4 col = tex2D(_MainTex, i.uv_MainTex);
			col *= _Color;
			o.Albedo = col.rgb;
                        fixed4 nor = tex2D(_Normal, i.uv MainTex);
                        o.Normal = nor;
			o.Metallic = _Metallic;
			o.Smoothness = _Smoothness;
			o.Emission = _Emission;
		}
MOLO17's logo as a texture map
Use as always this as texture map
MOLO17's logo as a normal shader map
and this as normal map

Put all together

If everything is working perfectly you should be able to assign a texture and a normal map to the shader and enjoy a more realistic shader than the previous article.

What’s next?

In the next article we will see another way to work with shaders in Unity and why LWRP and HDRP arent so different.