Unity Rendering Principle (5) Shader Lab

The previous blog explored some of the concepts of Unity Shader, so go ahead and take a look at how to write a Shader.

Overview

When writing shaders for Unity, use the following languages:

  • A programming language called HLSL. Use it to write the shader program itself. For more information about HLSL, see HLSL in Unity.
  • A Unity-specific language called ShaderLab. Use it to define Shader objects, which act as containers for shader programs. See ShaderLab for more information about ShaderLab.
    No need to use different languages for different platforms; Unity compiles HLSL and ShaderLab code into different languages for different graphics APIs

You can also use it directly if needed

There are different ways to write shaders:

  • The most common approach is to write vertex and slice element shaders using HLSL. For more information, see Writing Vertex and slice element shaders.
  • In the built-in render pipeline, you can also write surface shaders. This is a simplified way to write shaders that interact with lighting. See Surface Shaders for more information.
  • Unity also supports the “fixed function style” ShaderLab command for backward compatibility reasons. Therefore, you can write shaders using ShaderLab without using HLSL. This is no longer a recommended method

Shader

ShaderLab is a declarative language used in shader source files. It uses nested brace syntax to describe Shader objects.

There are many things that can be defined in ShaderLab, but the most common are:

  • Defines the overall structure of the Shader object. See ShaderLab: Creating Shaders, ShaderLab: Creating Sub-Shaders, and ShaderLab: Creating Channels.
  • Use Code Block to add shader programs written in HLSL.
  • Use the command to set the rendering state of the GPU before executing the shader program or performing operations involving another channel.
    Exposes properties from shader code to edit them in Material Inspector and save them as part of the Material resource.
  • Specifying package requirements for SubShaders and Passes. This enables Unity to run certain SubShaders and Passes only when particular packages are installed in the Unity project. See ShaderLab: specifying package requirements.
  • Defines fallback behavior when Unity is unable to run any SubShader with a Shader object on the current hardware.

Defining Shader Objects

Shader objects are a Unity-specific concept; it is a wrapper for shader programs and other information. It allows you to define multiple shader programs in the same file and tell Unity how to use them.

Shader objects have a nested structure; it organizes information into structures called SubShader and Pass.

In the Shader Code Block, you can:

  • Use Properties Code Block to define material properties.
  • Use SubShader Code Block to define one or more sub-shaders.
  • Assign a custom editor, which determines how shader resources are displayed in the Unity editor. Alternatively, different custom editors can be assigned for different rendering pipelines.
  • Assign a fallback Shader object using Fallback Code Block.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Shader "Examples/ShaderSyntax"
{
CustomEditor = "ExampleCustomEditor"

Properties
{
//here is the material property declaration
}
SubShader
{
//Here is the rest of the code to define the sub-shader

Pass
{
//Here is the code that defines the channel
}
}

Fallback "ExampleFallbackShader"
}

In ShaderLab code, material properties can be defined. Material properties are properties that Unity stores as part of a material resource. This allows artists to create, edit, and share materials with different configurations.

If using material properties:
You can get or set the value of a variable in a Shader object by calling a function (e.g. Material. SetFloat) on a material.

  • Values can be viewed and edited using Material Inspector.
  • Unity saves changes made as part of the Material asset, so they persist between sessions.

All material property declarations follow the following basic format:

1
[optional: attribute] name("display text in Inspector", type name) = default value

The previous attribute is an attribute attribute, which can be absent or multiple at the same time.
Names usually start with an underscore

We can classify attributes from two perspectives:

  • Divided by data type: there are float, Texture2D, etc.
  • According to the characteristics of material properties, such as’ [MainTexture] ‘,’ [MainColor] ', etc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Shader "Examples/MaterialPropertyShaderLab"
{
Properties
{
//Change this value in Material Inspector to affect the value of the Offset command
_OffsetUnitScale ("Offset unit scale", Integer) = 1
}
SubShader
{
//Here is the code that defines the rest of SubShader

Pass
{
Offset 0, [_OffsetUnitScale]

//Here is the code that defines the rest of the Pass
}
}
}

In addition to defining material properties, we can also assign fallbacks and customize editors in the Shader object

Define subshader (SubShader)

In ShaderLab, sub-shaders can be defined by placing a SubShader Code Block in a Shader Code Block.

In the SubShader Code Block, you can:

  • Use LOD Code Block to assign LOD (level of detail) values to SubShader.
  • Use Tags Code Block to assign the Attribute-Value Pair of the data to the child shader.
  • Use the ShaderLab command to add GPU instructions or shader code to SubShader.
  • Use Pass Code Block to define one or more channels.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Shader "Examples/SinglePass"
{
SubShader
{
Tags { "ExampleSubShaderTagKey" = "ExampleSubShaderTagValue" }
LOD 100

//Here is the ShaderLab command applied to the entire child shader.

Pass
{
Name "ExamplePassName"
Tags { "ExamplePassTagKey" = "ExamplePassTagValue" }

//Here is the ShaderLab command applied to this channel.

Here is the HLSL code.
}
}
}

Sub-shader label

In ShaderLab, you can assign tags to child shaders by placing a Tags Code Block in a SubShader Code Block.

Note that both sub-shaders and channels use Tags Code Block, but they work differently. Assigning sub-shader tags to channels has no effect, and vice versa. The difference lies in where you place the Tags Code Block:

  • To define channel labels, place the Tags Code Block inside the Pass Code Block.
  • To define a subshader tag, place the Tags Code Block inside the SubShader Code Block, but outside the Pass Code Block.

Subshader tags can be read from a C # script using the Material. GetTag API as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using UnityEngine;

public class Example : MonoBehaviour
{
//attach this to a game object with a renderer component
string tagName = "ExampleTagName";

void Start()
{
Renderer myRenderer = GetComponent<Renderer>();
string tagValue = myRenderer.material.GetTag(ExampleTagName, true, "Tag not found");
Debug.Log(tagValue);
}
}

There are many sub-shader tags:
The RenderPipeline tag tells Unity if the subshader is compatible with the universal rendering pipeline (URP) or high definition rendering pipeline (HDRP).

  • The Queue tag tells Unity the render queue to use for the geometry it renders. The render queue is one of the factors that determines the order in which Unity renders geometry.
  • Use the RenderType tag to override the behavior of Shader objects.
  • ForceNoShadowCasting tag prevents geometry in sub-shaders from casting (and sometimes receiving) shadows. The exact behavior depends on the render pipeline and render path.
  • DisableBatching sub-shader tag prevents Unity from applying dynamic batching to geometry using this sub-shader.
  • In the built-in render pipeline, the IgnoreProjector sub-shader tag informs Unity if the geometry is affected by the projector. This is useful for excluding most translucent geometry that is incompatible with the projector.
  • The PreviewType Sub-Shader Tag tells the Unity editor how to display materials using this sub-shader in the Material Inspector.
  • Using this subshader tag in projects using Legacy Sprite Packer warns the user that shaders depend on raw texture coordinates and therefore should not be packaged into atlases.

Sub-shader LOD value

You can assign an LOD value to a child shader. This value indicates its computational requirements.

At runtime, you can set the value of the shader LOD for a single Shader object or for all Shader objects. Unity then prioritizes sub-shaders with lower LOD values. Information on how Unity chooses when to use sub-shaders

Sub-shader channel

The channel is the basic element of the Shader object. It contains instructions to set the state of the GPU, as well as a shader program to run on the GPU.

Simple Shader objects may contain only one channel, but more complex shaders can contain multiple channels. You can define separate channels for different parts of the Shader object to achieve different ways of working; for example, parts that need to change rendering state, different shader programs, or different LightMode tags.

To define a regular channel in ShaderLab, you need to place a Pass Code Block in a SubShader Code Block.

In the Pass Code Block, you can:

  • Use the Name Code Block to specify a name for the channel.
  • Use Tags Code Block to assign the Attribute-Value Pair of the data to the channel.
  • Use ShaderLab commands to perform operations.
  • Add shader code to the channel using Shader Code Block.

Channel label

Each channel can have its own label, we list a few built-in labels: such as LightMode, PassFlags, RequireOptions, etc., you can see: https://docs.unity3d.com/cn/current/Manual/shader-predefined-pass-tags-built-in.html

为Shader

In Unity, you usually use HLSL to write shader programs. To add HLSL code to your shader resources, you should place the code in a Shader Code Block.

This page contains information about using the Shader Code Block. Information about writing HLSL itself.

Note: Unity also supports writing shader programs in other languages, but this is usually not required or recommended.

Types of Shader Code Blocks

To add HLSL code, you can use the following type of shader Code Block:

HLSLPROGRAM
CGPROGRAM
HLSLINCLUDE
CGINCLUDE
To understand when to use which, you must understand their prefixes (HLSL or CG) and suffixes (PROGRAM or INCLUDE).

HLSL

The difference between blocks prefixed with HLSL or CG is:

The shader Code Block prefixed with CG is older. By default, they contain several of Unity’s built-in shader include files, which is handy if you need this feature. The built-in include files are only compatible with the built-in render pipeline.
Shader Code Blocks prefixed with HLSL are newer. By default, they do not contain Unity’s built-in shader include files, so you must manually include any library code you want to use. They are suitable for any rendering pipeline.
For information about Unity’s built-in shader include files, see Built-in shader include files.

PROGRAM

The difference between blocks suffixed with PROGRAM or INCLUDE is:

Shader Code Blocks suffixed with PROGRAM are called shader blocks. You can use them to write shader programs. You write HLSL shader code in these blocks and then put them in a Pass block in your ShaderLab code.
Shader Code Blocks that are suffixed with INCLUDE are called shader include blocks. You can use them to share common code between shader blocks in the same source file. You write HLSL shader code to share in these blocks, and then place them in a Pass, SubShader, or Shader block in your ShaderLab code. It works in a similar way to using include in HLSL code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Shader "Examples/ExampleShader"
{
SubShader
{

HLSLINCLUDE
//Write the HLSL code to be shared here
ENDHLSL

Pass
{
Name "ExampleFirstPassName"
Tags { "LightMode" = "ExampleLightModeTagValue" }

//Write the ShaderLab command to set the render state here

HLSLPROGRAM
//This HLSL shader program automatically contains the contents of the HLSLINCLUDE block above
//Write HLSL shader code here
ENDHLSL
}

Pass
{
Name "ExampleSecondPassName"
Tags { "LightMode" = "ExampleLightModeTagValue" }

//Write the ShaderLab command to set the render state here

HLSLPROGRAM
//This HLSL shader program automatically contains the contents of the HLSLINCLUDE block above
//Write HLSL shader code here
ENDHLSL
}

}
}

Shader

ShaderLab commands fall into the following categories:

  • Command for setting rendering state on GPU.
  • Used to create channels with specific purposes.
  • If you use the old “fixed function style” command, you can create a shader program without writing HLSL.

Command for setting rendering status
Use these commands in a Pass Code Block to set the render state for the Pass, or in a SubShader Code Block to set the render state for the SubShader and all passes in it.

  • AlphaToMask: Set alpha-to-coverage mode.
  • Blend: enable and configure alpha blending.
  • BlendOp: Set the operation used by the Blend command.
  • ColorMask: Set the color channel write mask.
  • Conservative: Enable and disable conservative grating.
  • Cull: Set polygon culling mode.
  • Offset: Set polygon depth offset.
  • Stencil: Configure template tests and what to write to template buffers.
  • ZClip: Set deep clip mode.
  • ZTest: Set deep test mode.
  • ZWrite: Sets deep buffer write mode.

Channel command
Use these commands in SubShader to define channels with specific purposes.

UsePass defines a channel that imports the contents of a specified channel from another Shader object.
GrabPass creates a channel that grabs the screen content into a texture for later use in the channel. -