《Unity Shader 入门精要 Chapter 5》Reading Note

《Unity Shader 入门精要》Chapter 5 — Reading Note

Basic Structure of Vertex/Fragment Shader

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
Shader "MyShaderName"{
Properties{
// Properties
...
}
SubShader{
// SubShader for video card A
Pass{
// States and Tags
...

// Begin Cg code
CGPROGRAM
// Compile code
// It tells Unity which function corresponds to Vertex Shader, which function corresponds to Fragment Shader and so on.
#pragma vertex vert
#pragma fragment frag

// Real Cg code
...

// End
ENGCG

// Other settings
...
}
}
SubShader{
// SubShader for video card B
}
// Unity shader for fallback if all SubShader fail
Fallback "VertexLit"
}

Basic Vertex Shader and Fragment Shader

1
2
3
4
5
6
float4 vert(float4 v : POSITION) : SV_POSITION{
return mul(UNITY_MATRIX_MVP, v);
}
fixed4 frag() : SV_Target{
return fixed4(1.0, 1.0, 1.0, 1.0);
}

Semantics

Semantics is a special string corresponded to the input and output of the Shader, which tells Shader where to read data and where to write data. For example, POSITION tells Unity to fill the parameter with vertex coordinate.

Semantics supported by Unity: POSITION, TANGENT, NORMAL, TEXCOORD0, COLOR

So where is these data from? In Unity, they are provided by the Mesh Renderer which is using this material. At each frame calling the Draw Call, Mesh Render will send the model data rendered by it to Unity Shader. As we know, a model usually contains a set of triangular facets. Each facets is composed by three vertex, and each vertex contains some other data like vertex position, tangent, normal, texture coordinate and color. By this way, we can access these model data in Vertex Shader.

Communication between Vertex Shader and Fragment Shader

We can construct a common structure as the output of Vertex Shader and the input of Fragment Shader.

Code will be shown at the bottom.

Properties

Properties allow us to easily control the parameters in Shader. We can modify these parameter in the materials which are using the Shader.

1
2
3
4
5
6
7
8
9
10
11
12
13
Shader "MyShader"{
Properties{
_Color("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader{
Pass{
...

// In Cg code, we need to define a variable having the same name and type of the property
fixed4 _Color;
}
}
}

Debug

Ways to debug Shader:

  1. False-color image: Map the debug variables to [0, 1] and display it on the screen.
  2. Visual Studio - Graphics Debugger
  3. Unity - Frame Debugger

Some tips

float, half or fixed

  1. float: 32 bits, used for most of the desktop graphics.
  2. half: 16 bits, used for mobile graphics.
  3. fixed: 11 bits, used for some old graphics.

Although now most of the graphic devices can deal with float computation in a good perfromance, it’s a good habit to use float precision as low as possible (especially for mobile games).

Standardlize Syntax

DirectX platform has stricter requirement for Shader syntax, which means we should pay attention to syntax if we are going to publish our game on DirectX platform. For example, initialize variables using the right number of parameters.

Avoid Unnecessary Calculation

For different Shader Target and different Shader, the number of temporary registers and instructions we can use is different. One possible way is to use higher level Shader Target, but a better approach is to try using less calculation in Shader, or instead use pre-calculation.

Branch and Loop

Brach and loop is supported in Shader, but these will cause a significant performance reduction (GPU has a different realization for branch and loop). Therefore, it’s a good habit to use less branch and loop in Shader. However, if it’s unavoidable, there are some advices:

1. Use constant condition variables
2. Use less instructions in branch and loop
3. Use less nest

Don’t Divided by Zero

The result is unpredictable.

Two possible ways to solve the problem:

  1. Use a IF statement
  2. Replace zero by a tiny number (e.g. 0.000001)

Complete 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
38
39
40
41
42
43
44
45
46
47
48
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 5/Simple Shader" {
Properties{
// 申明一个Color类型的属性
_Color("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0)
}

SubShader{
Pass{
CGPROGRAM

#pragma vertex vert
#pragma fragment frag

// 在Cg代码中,我们需要定义一个与属性名称和类型都匹配的变量
fixed4 _Color;

// 使用一个结构体来定义顶点着色器的输入
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD;
};

// 使用一个结构体来定义顶点着色器的输出
struct v2f{
float4 pos : SV_POSITION;
float3 color : COLOR0;
};

v2f vert(a2v v){
v2f o;

o.pos = UnityObjectToClipPos(v.vertex);
o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5);

return o;
}

fixed4 frag(v2f i) : SV_Target{
return fixed4(i.color, 1.0);
}

ENDCG
}
}
}