Unity Shader入门

本篇介绍的是编写着色器的基础示例

最简单的顶点/片元着色器

hello world

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Shader "Unlit/Chapter5-SimpleShader"
{
SubShader{
pass{
CGPROGRAM
//告诉Unity哪个函数包含了顶点着色器代码
#pragma vertex vert
//告诉Unity哪个顶点包含了片元着色器代码
#pragma fragment frag
//逐顶点执行,通过POSITION语义指定了输入v包含了顶点的位置,语义告诉系统用户需要哪些输入值和用户的输出
//SV_POSITION告诉Unity,顶点着色器的输出时裁剪空间中的顶点坐标
float4 vert(float4 v:POSITION):SV_POSITION{
return mul(UNITY_MATRIX_MVP,v);
}
//SV_TARGET语义告诉渲染器把用户的输出颜色存储到一个渲染目标中
fixed4 frag():SV_TARGET{
return fixed4(1.0,1.0,1.0,1.0);
}
ENDCG
}

}
}

获取更多数据

可以定义结构体,包含模型数据

格式:

1
2
3
4
5
struct StructName{
Type Name: Semantic;
Type Name: Semantic;
......
};

Unity支持的语义有:POSITION,TANGENT,NORMAL,TEXCOORD0,TEXCOORD1,TEXCOORD2,TEXCOORD3,COLOR等

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
Shader "Unlit/Chapter5-SimpleShader"
{
SubShader{
pass{
CGPROGRAM
//告诉Unity哪个函数包含了顶点着色器代码
#pragma vertex vert
//告诉Unity哪个顶点包含了片元着色器代码
#pragma fragment frag

struct a2v{
//用模型空间的顶点填充vertex变量
float4 vertex:POSITION;
//用模型空间的法线方向填充normal变量
float3 normal:NORMAL;
//用模型的第一套纹理坐标填充texcoord变量
float4 texcoord:TEXCOORD0;
}
//逐顶点执行
//SV_POSITION告诉Unity,顶点着色器的输出时裁剪空间中的顶点坐标
float4 vert(a2v v):SV_POSITION{
return mul(UNITY_MATRIX_MVP,v.vertex);
}
//SV_TARGET语义告诉渲染器把用户的输出颜色存储到一个渲染目标中
fixed4 frag():SV_TARGET{
return fixed4(1.0,1.0,1.0,1.0);
}
ENDCG
}
}
}


数据来源:使用该材质的Mesh Render组件,在每帧调用Draw Call的时候,Mesh Render会把它负责渲染的模型数据发送给Unity Shader。一个模型通常包含了一组三角面片,每个三角面片由3个顶点组成,每个顶点包含了一些数据:顶点位置、法线、切线、纹理坐标、顶点颜色等。

着色器通信

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
Shader "Unlit/Chapter5-SimpleShader"
{
SubShader{
pass{
CGPROGRAM
//告诉Unity哪个函数包含了顶点着色器代码
#pragma vertex vert
//告诉Unity哪个顶点包含了片元着色器代码
#pragma fragment frag

struct a2v{
//用模型空间的顶点填充vertex变量
float4 vertex:POSITION;
//用模型空间的法线方向填充normal变量
float3 normal:NORMAL;
//用模型的第一套纹理坐标填充texcoord变量
float4 texcoord:TEXCOORD0;
}
;
struct v2f{
//pos中包含了顶点在裁剪空间中的位置信息
float4 pos:SV_POSITION;
//COLOR0语义可以用于存储颜色信息
fixed3 color:COLOR0;
}
;
//逐顶点执行
//SV_POSITION告诉Unity,顶点着色器的输出时裁剪空间中的顶点坐标
//顶点着色器的输出结构必须包含一个语义为SV_POSITION的变量,否则渲染器
//无法得到裁剪空间中的顶点坐标
v2f vert(a2v v){
v2f o; //声明输出结构
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
//v.normal包含了顶点的法线方向,分量范围在【-1,1】
//现映射到【0,1】
//存到o.color中传到片元着色器
o.color=v.normal*0.5+fixed3(0.5,0.5,0.5);
return o;
}
//SV_TARGET语义告诉渲染器把用户的输出颜色存储到一个渲染目标中
fixed4 frag(v2f i):SV_TARGET{
return fixed4(i.color,1.0);
}
ENDCG
}
}
}

使用属性

在材质面板上展示,方便调整

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
49
50
51
52
53
54
55
Shader "Unlit/Chapter5-SimpleShader"
{
Properties{
//声明一个Color类型的属性
_Color ("Color Tint", Color)=(1.0,1.0,1.0,1.0)
}
SubShader{

pass{
CGPROGRAM
//告诉Unity哪个函数包含了顶点着色器代码
#pragma vertex vert
//告诉Unity哪个顶点包含了片元着色器代码
#pragma fragment frag

//在CG代码中,我们需要定义一个与属性名称和类型都匹配的变量
uniform fixed4 _Color;
struct a2v{
//用模型空间的顶点填充vertex变量
float4 vertex:POSITION;
//用模型空间的法线方向填充normal变量
float3 normal:NORMAL;
//用模型的第一套纹理坐标填充texcoord变量
float4 texcoord:TEXCOORD0;
};
struct v2f{
//pos中包含了顶点在裁剪空间中的位置信息
float4 pos:SV_POSITION;
//COLOR0语义可以用于存储颜色信息
fixed3 color:COLOR0;
};
//逐顶点执行
//SV_POSITION告诉Unity,顶点着色器的输出时裁剪空间中的顶点坐标
v2f vert(a2v v){
v2f o; //声明输出结构
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
//v.normal包含了顶点的法线方向,分量范围在【-1,1】
//现映射到【0,1】
//存到o.color中传到片元着色器
o.color=v.normal*0.5+fixed3(0.5,0.5,0.5);
return o;
}
//SV_TARGET语义告诉渲染器把用户的输出颜色存储到一个渲染目标中
fixed4 frag(v2f i):SV_TARGET{
fixed3 c=i.color;
//使用Color属性来控制输出颜色
c*=_Color.rgb;
return fixed4(c,1.0);
}
ENDCG
}
}
}


为了在CG代码中可以访问到定义的属性,必须在CG代码片段提前定义一个新的变量,这个变量的名称和类型必须与Properties语义块中的属性定义相匹配。

匹配关系

uniform关键词时CG中修饰变量和参数的一种修饰词,仅仅提供一些关于该变量的初始值时如何指定和存储的相关信息。可以省略(仅限unity)

Unity内置文件和变量

Unity内置了提前定义的函数、变量和宏辅助编程

内置的包含文件

#include 文件后缀是.cginc

CGincludes文件夹包含了所有的内置包含文件。

主要文件和用处:

UnityCG.cginc是最常接触的一个包含文件,可以使用其提供的结构体和函数为编写提供方便。

Unity还提供了用于访问时间、光照、雾效和环境光等目的的变量,这些变量大多位于UnityShaderVariables.cginc中,与光照有关的内置变量还会位于Lightning.cginc,AutoLight.cginc中

Unity提供的CG/HLSL语义

语义:一个赋给Shader输入和输出的字符串,可以让Shader知道从哪里读取数据,并将数据输出到哪里

系统数值语义(system-value semantics):以SV开头,带渲染流水线中有特殊的含义,不可以随便赋值

Unity支持的语义

一个语义可以使用的寄存器只能处理4个浮点值。

如果想定义矩阵变量就需要使用更多的空间。一种方法是拆分。

float4x4拆成4个float4类型的变量,每个变量存储了矩阵中的一行数据。

一些补充

注意语法差异:

  • 使用SV_POSITION来描述顶点着色器输出的顶点位置

  • 使用SV_TARGET来描述片元着色器的输出颜色

  • 尽量不要使用分支语句:

  • 分支判断语句中使用的条件变量最好是常数,即在Shader运行过程中不会发生变换;

  • 每个分支中包含的操作指令数尽可能少;

  • 分支的嵌套层数尽可能少

  • 不要除以0


Unity Shader入门
https://shanhainanhua.github.io/2023/04/23/Unity-Shader入门/
作者
wantong
发布于
2023年4月23日
许可协议