ShaderQuest Part 3: Shaders and Materials

Patrons

This ShaderQuest post is brought to you by these awesome Patrons:

  • Not Invader Zim
  • Tiph’ (DN)
  • orels1
  • raingame

Introduction

In this part of ShaderQuest, we’ll take a look at how shaders are created and handled in the context of game engines; specifically Unity and UE4. Once again, we won’t be actively diving in shader authoring techniques and concepts, but this part should help you get a better idea of how the stuff we mentioned in the previous parts and the stuff we’ll mention in future parts work in the context of the game engine.

Materials

As we mentioned in the previous part shaders define how 3D objects get rendered onto our screen. But, if you’re familiar with the use of game engines, you usually don’t assign shaders onto objects, but, instead, you assign materials.

Materials are essentially instances of a shader. They are defined by the shader they’re using and they expose properties that affect the overall visual result. Think of materials as objects of a class in conventional programming; the class defines the properties and functionality of the objects and many objects can be defined by the same one class.

As I’ve mentioned a lot of times already, shaders are pretty much the same anywhere; they follow the same logic and the hand-coded ones are already pretty low level with not a huge amount of abstractions, making the process of writing shader code similar across different platforms and environments. But, as you can imagine, game engines and other rendering environments implement their own wrappers around shaders in order to make them properly communicate with the rest of the engine’s components.

Let’s take a look at the interfaces that Unity and UE4 present us with for their materials:

Unity

Assuming you have used Unity in some capacity, if you have put any game object in a scene, you would see that it has a material on its renderer (whether that’s its mesh, skinned mesh or sprite renderer). The materials define how said object will look in the scene and have specific properties exposed that you can use to adjust the end result. You probably have already seen the standard material that get’s set up when you create a material:

As I already said, how materials work is defined by the shader of which they’re instances, and that’s more obvious when you look at the top of the inspector:

This material, for example, is defined by Unity’s internal “Standard” shader, which provides physically-based visual results, reacting to a bunch of different factors that come from the environment around the object and from the object itself. For example, materials using the “Standard” shader get their color changed based on the lights in the scene, the ambient lighting, reflection probes, light probes etc. We can also assign textures on the material or change its colors, which allows different materials to react differently to the same environmental factors.

If we change the shader on that material to, say, Unity’s internal “Unlit/Color” shader, we’d see just these properties instead:

This shader only outputs a solid color and doesn’t take lighting and other environmental factors into account, so the main property it needs is just the color it outputs.

Going back to the standard shader, below you can find a quick rundown of the different exposed properties we can change on the material. I won’t go into too much detail as most of that stuff will be revisited when we make our own shaders.

NameDescription
AlbedoThe base color of the material. The output of the albedo texture is multiplied by the color next to it.
MetallicDefines which areas of the object will be treated as metallic and which will be considered to be non-metallic (or dielectric). The metallic texture can be a grayscale texture the values of which define how metallic the corresponding area is.
SmoothnessDefines the glossiness of certain areas of the material. While in the “Standard” shader this is just a slider, you can use the values of the alpha channel of the metallic texture to define which areas will be glossier than others. You can see that this is determined by the “Source” dropdown.

Alternatively, you can use the values from the alpha channel of the albedo texture.
Normal mapA texture used to fake more surface detail, like smaller bumps and dents. Normal maps play a huge role in terms of lighting, and we’ll come back to them later in much more detail.
Height mapA texture used for an effect called parallax offset, used to fake depth within a surface without the need for additional geometry. Unity is using the texture’s green channel to get the height map information.
OcclusionA texture used to fake ambient occlusion, darkening specific areas of the mesh where light wouldn’t easily escape. Unity is using the texture’s green channel to get the occlusion information.
Detail maskA texture used to define where the secondary textures will be applied. Unity is using the texture’s alpha channel to get the masking information.
Detail texturesAn extra albedo and normal map texture used for extra, smaller scale details.

In URP a material with the Standard shader looks like this:

You can pretty much tell that the main properties are working the same way.

Unreal Engine 4

UE4 is a bit different in terms of terminology, because “materials” in UE4 kind of double as both shaders and materials (as we know them in Unity). As mentioned in the previous part UE’s material editor is a visual node-based shader authoring system, but it outputs materials that can be assigned to objects like in Unity.

However, while in Unity duplicating a material just makes a new material that’s referencing the same shader, duplication a UE4 material basically creates an identical shader, which is not ideal as it creates a lot of redundancy. That’s why UE4 also has material instances which behave more closely to Unity materials. In the material editor we can have properties like textures and values that we can choose to expose in order for them to be overridden in different material instances.

Opening a material instance will present you with an interface like the one above, where you can choose to override exposed parameters on the top right corner of the “Details” tab. That way, you can have a lot of different instances with different properties referencing the same original material.

Creating and assigning shaders

This section won’t actually go through details on shader creation; it will instead cover the subject in its more literal sense which is creating the actual shader files/objects and assigning them to materials.

Unity

Built-in pipeline

A shader file in Unity can be created in your project window by selecting “Create > Shader” and choosing the shader file that best suits your needs.

We’ll go into details on the different types of shaders in later parts but in a very surface level:

  • Standard Surface Shader: Choose this if you want to make a shader that implements Unity’s standard shading system, allowing your objects to react to lighting.
  • Unlit Shader: Choose this for a more barebones, low-level shader, with no regards to lighting, shading or shadow casting/receiving out of the box. The “Unlit” part might be a bit misleading, because you can in fact implement lighting in these shaders yourself. I usually refer to these shaders as vertex/fragment shaders, as they have distinct vertex and fragment functions.
  • Image Effect Shader: Choose this shader if you want to make an effect that gets applied to everything the main camera outputs. Image effect shaders are basically a slightly simpler version of the “Unlit Shader” and with minor changes they’re basically interchangeable.

At this stage you shouldn’t worry about the other two shader types (compute and ray tracing), as we’ll mostly be focusing on the aforementioned three.

URP

URP and HDRP shaders are created in the form of Shader Graphs:

We’ll be mostly interested in Lit Shader Graphs and Unlit Shader Graphs, as we probably won’t be looking into sprite shaders. The reason is that while there might be some special approaches around sprite shaders, the techniques used in other shader graphs will be easily transferrable.

As you can imagine, Lit Shader Graphs produce shaders that will be reacting to lighting information, while Unlit Shader Graphs produce shaders that don’t handle lighting calculations.

Assigning shaders

The name of your newly created shader will match the name of your shader file and, depending on the type of shader you created, it will be in a specific category.

In Unity you can assign a shader to a material in three ways:

  1. Dragging the shader onto an open material inspector

2. Right-clicking onto a shader file and making a new material

3. Finding your shader through the “Shader” menu in the material inspector

Note: Built-in surface shaders are by default in the “Custom” category, built-in unlit shaders are by default in the “Unlit” category and URP/HDRP shader graphs are under the “Shader Graphs” category.

UE4

In UE4, creating a new material is fairly straightforward:

In order to create a material instance of a material, we can just right-click on it and select “Create material instance”:

Conclusion

This part might not have been that exciting; in fact I bet you knew most of that stuff. But for the sake of moving one step at a time, I felt that I should get that stuff out of the way before we get into more fun stuff!

In the next part we’ll actually open up some shaders and take a look at their different components and setups. I hope that sounds a bit more interesting!

See you in the next part of ShaderQuest




Disclaimer

The code in the shader tutorials is under the CC0 license, so you can freely use it in your commercial or non-commercial projects without the need for any attribution/credits.

These posts will never go behind a paywall. But if you really really super enjoyed this post, consider buying me a coffee, or becoming my patron to get exciting benefits!

Become a Patron!

Or don't, I'm not the boss of you.

Comments

    1. Post
      Author

Comments are closed.