In the last articles, we have seen some essential details about the HLSL language and shaders.
You can find previous articles at these links:
- Introduction Part in Part One – https://molo17.com/2019/05/dont-let-the-shaders-scare-you/
- Basic Concepts in Part Two – https://molo17.com/2019/05/dont-let-the-shaders-scare-you-part-2/
- Language Basics in Part Three – https://molo17.com/2019/06/dont-let-the-shaders-scare-you-part-3/
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


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


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.