In Unity, when we create a GameObject, we attach additional functionality through the use of components. Every GameObject is required to have a Transform component; there are several components included in Unity already, and we create components of our own when we write scripts that extend from MonoBehaviour
.
All the objects that are part of a game contain several components that affect their look and behavior. While scripts determine how objects should behave, renderers decide how they should appear on the screen. Unity comes with several renderers, depending on the type of object that we are trying to visualize; every 3D model typically has a MeshRenderer
component attached to it. An object should have only one renderer, but the renderer itself can contain several materials. Each material is a wrapper for a single shader, which is the final ring in the food chain of 3D graphics. The relationships between these components can be seen in the following diagram:
Figure 2.1 – Relationship between shaders, models, materials, and objects
Note that the preceding diagram mentions Cg code. Cg is only the default for the built-in renderer. URP/HDRP defaults to using HLSL code.
Understanding the difference between these components is essential for understanding how shaders work.
Getting ready
To get started with this recipe, you will need to have Unity running and must have a project opened using the built-in renderer. (In my case, I am using the 3D template. If you're using Unity Hub 3, go to Core | 3D.) As we mentioned previously, a Unity project has been included with this cookbook, so you can use that one as well and simply add custom shaders to it as you step through each recipe. Once you've done this, you will be ready to step into the wonderful world of real-time shading!
Note
If you are using the Unity project that came with this cookbook, you can open the Chapter 2
| Scenes
| Starting Point
scene instead of completing the Getting ready section as it has been set up already.
Before we create our first shader, let's create a small scene for us to work with:
- Let's create a scene by navigating to File | New Scene. A dialog window will appear, asking what template should be used. Select 3D and then click on the Create button:
Figure 2.2 – New Scene window
- Once you've created the scene, create a plane that will act as the ground by going to the Unity Editor and selecting GameObject | 3D Object | Plane from the top menu bar:
Figure 2.3 – Creating a Plane
- Next, select the object in the Hierarchy tab and then go into the Inspector tab. From there, right-click on the Transform component and select the Reset Property | Position option:
Figure 2.4 – Resetting the position of the object
This will reset the Position property of the object to 0
, 0
, 0
, which is the center of our game world.
- To make it easier to see what our shaders will look like when applied, let's add some shapes to visualize what each of our shaders will do. Create a sphere by going to GameObject | 3D Object | Sphere. Once created, select it and go to the Inspector tab. Next, change Position to
0
, 1
, 0
so that it is above the origin of the world (which is at 0
, 0
, 0
) and our previously created plane:Figure 2.5 – Adding a sphere
- Once this sphere has been created, create two more spheres and place them to the left and right of the sphere at
-2
, 1
, 0
and 2
, 1
, 0
, respectively:Figure 2.6 – Completing our scene
Note
One quick way to do this is to duplicate the objects by hitting the Ctrl + D keys while having the object selected in the Hierarchy window. You can rename the objects via the top textbox in the Inspector window.
- Confirm that you have directional light (it should be in the Hierarchy tab). If not, you can add it by selecting GameObject | Light | Directional Light to make it easier to see your changes and how your shaders react to light.
- The example code for this book can be found in a folder called
Chapter 2
. This folder holds all the code for this chapter. To organize your code, create a folder by going to the Project tab in the Unity Editor, right-clicking, and selecting Create | Folder. Rename the folder Chapter 2
.
How to do it...
With our scene generated, we can start writing the shader:
- In the Project tab in the Unity Editor, right-click on the
Chapter 2
folder and select Create | Folder.
- Rename the folder that you created to
Shaders
by right-clicking on it and selecting Rename from the drop-down list, or by selecting the folder and hitting F2 on the keyboard.
- Create another folder and rename it
Materials
. Place this folder inside the Chapter 2
folder as well.
- Right-click on the
Shaders
folder and select Create | Shader | Standard Surface Shader. Then, right-click on the Materials
folder and select Create | Material.
- Rename both the shader and material to
StandardDiffuse
.
- Launch the
StandardDiffuse
shader by double-clicking on the file. This will automatically launch a scripting editor for you (Visual Studio, by default) and display the shader's code.Note
You will see that Unity has already populated our shader with some basic code. This, by default, will get you a basic shader that accepts one texture in the Albedo (RGB)
property. We will be modifying this base code so that you can learn how to quickly start developing custom shaders.
- Now, let's give our shader a custom folder that it can be selected from. The very first line of code in the shader is the custom description that we have to give the shader so that Unity can make it available in the shader drop-down list when assigning it to materials. We have renamed our path to
Shader "CookbookShaders/Chapter 02/StandardDiffuse"
, but you can name it whatever you want and rename it at any time, so don't worry about any dependencies at this point.
- Save the shader in your script editor and return to the Unity Editor. Unity will automatically compile the shader when it recognizes that the file has been updated. This is what the top of your shader file should look like at this point:
Shader "CookbookShaders/Chapter 02/StandardDiffuse"
{
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
{
// Rest of file…
- Technically speaking, this is a Surface Shader based on physically based rendering (PBR). As the name suggests, this type of shader achieves realism by simulating how light physically behaves when hitting objects.
- Once you've created your shader, you need to connect it to a material. Select the
StandardDiffuse
material that we created in Step 4 and look at the Inspector tab. From the Shader drop-down list, select CookbookShaders/Chapter 02/StandardDiffuse (your shader path might be different if you chose to use a different pathname):Figure 2.7 – The StandardDiffuse shader has been set as the shader to use on this material
- This will assign your shader to your material so that you can assign it to an object.
Note
To assign a material to an object, you can simply click and drag your material from the Project tab to the object in your scene. You can also drag the material to the Inspector tab of an object in the Unity Editor to assign it.
The following screenshot shows what we have done so far:
Figure 2.8 – The created material
There's not much to look at at this point, but our shader development environment has been set up, which means we can start to modify the shader so that it suits our needs.
How it works...
Unity has made the task of getting your shader environment up and running very easy. It is simply a matter of a few clicks and you are good to go. There are a lot of elements working in the background concerning the Surface Shader itself. Unity has taken the Cg shader language and made it more efficient to write by doing a lot of the heavy Cg code lifting for you. The Surface Shader language is a more component-based way of writing shaders. Tasks such as processing texture coordinates and transformation matrices have already been done for you, so you don't have to start from scratch anymore. In the past, we would have to start a new shader and rewrite a lot of code over and over again. As you gain more experience with Surface Shaders, you will want to explore more of the underlying functions of the Cg language and how Unity is processing all of the low-level graphics processing unit (GPU) tasks for you.
Note
All the files in a Unity project are referenced independently from the folder that they are in. We can move shaders and materials from within the editor without the risk of breaking any connections. Files, however, should never be moved from outside the editor as Unity will not be able to update their references.
So, by simply changing the shader's pathname to a name of our choice, we have got our basic diffuse shader working in the Unity environment, along with lights and shadows, just by changing one line of code!
There's more...
The source code of the built-in shaders is typically hidden in Unity. You cannot open this from the editor as you do with your own shaders. For more information on where to find a large portion of the built-in Cg functions for Unity, go to your Unity install directory (visible in the Installs section of Unity Hub, if you have it installed; select the three dots next to Installs (a gear icon in Unity Hub 3) and select the Show in Explorer option):
Figure 2.9 – The Show in Explorer option
From the installation location, navigate to the Editor
| Data
| CGIncludes
folder:
Figure 2.10 – The location of the CGIncludes folder
In this folder, you can find the source code for the shaders that were shipped with Unity. Over time, they have changed a lot; you can visit the Unity download archive (https://unity3d.com/get-unity/download/archive) if you need to access the source code of a shader that's been used in a different version of Unity. After choosing the right version, select Built in shaders from the drop-down list, as shown in the following screenshot:
Figure 2.11 – Unity download archive
There are three files that that are important at this point: UnityCG.cginc
, Lighting.cginc
, and UnityShaderVariables.cginc
. Our current shader is making use of all these files at the moment. In Chapter 12, Advanced Shading Techniques, we will explore how to use CGInclude for a modular approach to shader coding.