Unity ShaderLab 顶点-片段着色器基础
一、创建Shader
Shader创建方式:
Assets
->Create
->Shader
->Unlit Shader
,即可创建出一个顶点-片段着色器文件。
Unity中Shader的两种用途:
(1)指定给材质,用于物体渲染。
(2)指定给脚本,用于图像处理。
二、编写Shader方式
Unity渲染管道中的Shader编写:
(1)顶点-片段着色器(Vertex and Fragment Shader)
(2)表面着色器(Surface Shader)
(3)固定函数着色器(Fixed Function Shader)
三、CG语言基础
1.编译指令
CG程序片段通过指令嵌入到Pass中,夹在指令CGPROGRAM和ENDCG之间。
1 2 3 4 5 6 7 8 9 10 11 12 13
| Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag ENDCG }
|
在CG程序片段之前,通常需要先使用#pragma
声明编译指令。
编译指令 |
作用 |
#pragma vertex name |
定义顶点着色器的名称,通常会使用vert |
#pragma fragment name |
定义片段着色器的名称,通常会使用frag |
#pragma target name |
定义Shader要编译的目标级别,默认2.5 |
编译目标等级:
编写完Shader程序之后,其中CG代码可以被编译到不同的Shader Models中,为了能够使用更高级的GPU功能,需要对应使用更高等级的编译目标。
但并不是编译目标登记越高越好,高等级的编译目标可能会导致Shader无法在旧的GPU上运行。
2.着色器函数
2.1 无返回值的函数
语法结构:
1 2 3 4
| void name(in 参数,out 参数) { }
|
in
:输入参数,语法为:in+数据类型+名称
,一个函数可以有多个输入参数
out
:输出翻书,语法为:out+数据类型+名称
,一个函数可以有多个输出参数
2.2 有返回值的函数
语法结构:
1 2 3 4 5
| type name { return 返回值; }
|
2.3 顶点函数和片段函数中支持的数据类型
数据类型 |
描述 |
fixed,fixed2,fixed3,fixed4 |
低精度浮点值,使用11位精度进行存储,数值区间为[-2.0,2.0],用于存储颜色、标准化后的向量 |
half,half2,half3,half4 |
中精度浮点值,使用16位精度进行存储,数值区间为[-60000,60000] |
float,float2,float3,float4 |
高精度浮点值,使用32位精度进行存储,用于存储顶点坐标、为标准化的向量、纹理坐标等 |
struct |
结构体 |
3. 语义
3.1 顶点着色器的输入语义
在顶点着色器中,顶点数据是以输入参数的方式传递给顶点函数,每一个输入的参数都需要填充一个语义,用于表示所传递的链接。
语义 |
描述 |
POSITION |
顶点的坐标信息,通常为float3或float4类型 |
NORMAL |
顶点的法向信息,通常为float3类型 |
TEXCOORD0 |
模型的第一套UV坐标,通常为float2、float3或float4类型,TEXCOORD0到TEXCOORD3分别对应为第一到第四套UV坐标 |
TANGENT |
顶点的切向量,通常为float4类型 |
COLOR |
顶点的颜色信息,通常为float4类型 |
3.2 顶点着色器输出和片段着色器输入语义
顶点着色器需要输出顶点在裁切空间中的坐标。
在顶点函数中,这个输出参数需要使用float4类型的SV_POSITION语义进行填充。
顶点着色器的输出即为片段着色器的输入。
语义 |
描述 |
SV_POSITION |
顶点在裁切空间中的坐标,float4类型 |
TEXCOORD0、TEXCOORD1等 |
用于声明任意高精度的数据,例如纹理坐标、向量等 |
COLOR0、COlOR1等 |
用于声明任意低精度的数据,例如顶点颜色、数值区间[0,1]的变量 |
片段着色器会自动获取顶点着色器输出的裁切空间顶点坐标,所以片段函数输入的SV_POSITION可以省略。
3.3 片段着色器输出语义
片段着色器通常只会输出一个fixed4类型的颜色信息,输出的值会存储到渲染目标(Render Target)中,输出参数使用SV_TARGET语义进行填充。
4.在CG中调用属性变量
4.1 CG中声明属性变量
在Shader程序中访问Properties代码块声明开放出来的属性,需要在CG代码块中再次声明,语法为:
type
为变量的类型。
例如:
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
| Shader "Custom/Simple Shader" { Properties { _MyFloat("Float Property",Float)=1 _MyRange("Range Property",Range(0,1))=0.1 _MyColor("Color Property",Color)=(1,1,1,1) _MyVector("Vector Property",Vector)=(0,0,0,0) _MyTex("Texture Property",2D)="white"{} _MyCube("Cube Property",Cube)=""{} _My3D("3D Property",3D)=""{} } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
float _MyFloat; float _MyRange; fixed4 _MyColor; float4 _MyVector; sampler2D _MyTex; samplerCUBE _MyCube; sampler3D _My3D;
void vert() { }
void frag() { } ENDCG } } Fallback "Diffuse" }
|
开放属性与CG属性变量的对应关系:
开放属性的类型 |
CG中属性变量的类型 |
Float,Range |
浮点和范围类型的属性。可根据精度使用float,half,fixed声明 |
Color,Vector |
颜色和向量类的属性,可以使用float4,half4或fixed4声明 |
2D |
2D纹理贴图属性,使用sampler2D声明 |
Cube |
立方体纹理贴图属性,使用samplerCube声明 |
3D |
3D纹理贴图属性,使用sampler3D声明 |
4.2 在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
| Shader "Custom/Color Property" { Properties { _MainColor("MainColor",Color)=(1,1,1,1) } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
fixed4 _MainColor; void vert(in float4 vertex : POSITION,out float4 position : SV_POSITION) { position=UnityObjectToClipPos(vertex); }
void frag(out fixed4 color : SV_Target) { color=_MainColor; } ENDCG } } }
|
4.3 在Shader中使用贴图
与其他属性不同的是,纹理贴图需要在CG中额外声明一个变量用于存储贴图的其他信息,例如平铺(Tiling)和偏移(Offset)属性。
在CG中,声明一个纹理变量的Tiling和Offset的语法结构:
(1)TextureName
:纹理属性的名称。
(2)ST
:Scale和Transform的首字母,表示UV的缩放和平移。
该float4类型中的x和y分量分别代表Tiling的x和y值,z和w分量分别代表Offset的x和y值。
纹理坐标的计算公式:
texcoord=uv⋅{TextureName}.xy+{TextureName}.zw
纹理坐标计算时,一定是先放大,在平移,即先乘以平铺直再加偏移值。
修改上面的Shader代码,增加其对纹理资源的支持:
·Properties语句块掳爱中开放名称为_MainTex
的纹理属性,默认值为白色。
1
| _MainTex("MainTex",2D)="white"{}
|
·在CG中重新声明_MainTex
属性变量以及它的平铺偏移变量_MainTex_ST
。
1 2
| sampler2D _MainTex; float4 _MainTex_ST;
|
·在顶点函数中,添加一个名称为uv
的输入参数用于获取顶点的UV数据之后,有添加了一个名称为texcoord
的输出参数用于保存顶点函数中级算出来的纹理坐标。
1 2 3 4 5 6 7
| void vert(in float4 vertex : POSITION,in float2 uv : TEXCOORD0,out float4 position : SV_POSITION,out float2 texcoord : TEXCOORD0) { position=UnityObjectToClipPos(vertex);
texcoord = uv * _MainTex_ST.xy + _MainTex_ST.zw; }
|
·在片段着色器中添加一个名称为texcoord
的输入参数用于接收从顶点着色器传递过来的纹理变量。然后调用tex2D()
函数,使用纹理坐标texcoord
对纹理_MainTex
进行采用,最后将采样结果乘上_MainColor
进行输出。
1 2 3 4
| void frag(in float4 position : SV_POSITION,in float2 texcoord : TEXCOORD0,out fixed4 color : SV_Target) { color = tex2D(_MainTex,texcoord) * _MainColor; }
|
完整代码如下:
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
| Shader "Custom/Color Property" { Properties { _MainColor("MainColor",Color)=(1,1,1,1) _MainTex("MainTex",2D)="white"{} } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
fixed4 _MainColor; sampler2D _MainTex; float4 _MainTex_ST; void vert(in float4 vertex : POSITION,in float2 uv : TEXCOORD0,out float4 position : SV_POSITION,out float2 texcoord : TEXCOORD0) { position=UnityObjectToClipPos(vertex);
texcoord = uv * _MainTex_ST.xy + _MainTex_ST.zw; }
void frag(in float4 position : SV_POSITION,in float2 texcoord : TEXCOORD0,out fixed4 color : SV_Target) { color = tex2D(_MainTex,texcoord) * _MainColor; } ENDCG } } }
|
4.4 在Shader中使用立方体贴图
立方体贴图的采样函数为:texCUBE(Cube,r)
·Cube
:表示立方体的贴图
·r
:表示视线方向在物体表面上的反射方向
(1)公式1:r=2a−v
(2)公式2:a=v+b
(3)公式3:b=∣b∣⋅n=∣v∣cosα⋅n=cosα⋅n
(4)由于v是标准化向量,长度为1,在乘法中可以省略。
α计算:(−v)⋅n=∣v∣⋅∣n∣⋅cosα=cosα
因为−v 和 n 都是标准化向量,长度为1,则可以得到:
·b=[(−v)⋅n]n
·a=v+[(−v)⋅n]n
·r=2v+[(−v)⋅n]n−v=2[(−v)⋅n]n+v
修改代码使Shader添加对于立方体贴图的支持:
添加开放属性(Properties)。
1 2 3
| _Cubemap("Cubemap",Cube)=""{} _Reflection("Reflection",Range(0,1))=0
|
在CG中再次声明属性变量。
1 2 3
| samplerCUBE _Cubemap; fixed _Reflection;
|
在顶点函数的输入参数中,添加了模型的法线向量normal后,输出参数中添加了世界空间顶点坐标worldPos和世界空间法线向量worldNormal。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void vert(in float4 vertex : POSITION,in float2 uv : TEXCOORD0,in float3 normal : NORMAL, out float4 position : SV_POSITION,out float4 worldPos : TEXCOORD0, out float3 worldNormal : TEXCOORD1,out float2 texcoord : TEXCOORD2) { position=UnityObjectToClipPos(vertex);
worldPos=mul(unity_ObjectToWorld,vertex);
worldNormal=mul(normal,(fixed3x3)unity_WorldToObject); worldNormal=normalize(worldNormal);
texcoord = uv * _MainTex_ST.xy + _MainTex_ST.zw; }
|
将世界空间顶点坐标worldPos和世界空间法向worldNormal传入到片段函数之后,使用worldPos,xyz减去摄像机坐标得到摄像机指向顶点的方向向量viewDir并规范化。然后套用反射向量的公式计算反射向量refDir,使用texCUBE()
函数对其进行采样,得到反射颜色reflection。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void frag(in float4 position : SV_POSITION,in float4 worldPos : TEXCOORD0,in float3 worldNormal, in float2 texcoord : TEXCOORD1,out fixed4 color : SV_Target) { fixed4 main=tex2D(_MainTex,texcoord)*_MainColor;
float3 viewDir=worldPos.xyz-_WorldSpaceCameraPos; viewDir=normalize(viewDir);
float3 refDir=2*dot(-viewDir,worldNormal); refDir=normalize(refDir);
fixed4 reflection=texCUBE(_Cubemap,refDir);
color=lerp(main,reflection,_Reflection); }
|
最后完整的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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| Shader "Custom/Cubemap Property" { Properties { _MainColor("MainColor",Color)=(1,1,1,1) _MainTex("MainTex",2D)="white"{} _Cubemap("Cubemap",Cube)=""{} _Reflection("Reflection",Range(0,1))=0 } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
fixed4 _MainColor; sampler2D _MainTex; float4 _MainTex_ST;
samplerCUBE _Cubemap; fixed _Reflection; void vert(in float4 vertex : POSITION,in float2 uv : TEXCOORD0,in float3 normal : NORMAL, out float4 position : SV_POSITION,out float4 worldPos : TEXCOORD0, out float3 worldNormal : TEXCOORD1,out float2 texcoord : TEXCOORD2) { position=UnityObjectToClipPos(vertex);
worldPos=mul(unity_ObjectToWorld,vertex);
worldNormal=mul(normal,(fixed3x3)unity_WorldToObject); worldNormal=normalize(worldNormal);
texcoord = uv * _MainTex_ST.xy + _MainTex_ST.zw; }
void frag(in float4 position : SV_POSITION,in float4 worldPos : TEXCOORD0,in float3 worldNormal, in float2 texcoord : TEXCOORD1,out fixed4 color : SV_Target) { fixed4 main=tex2D(_MainTex,texcoord)*_MainColor;
float3 viewDir=worldPos.xyz-_WorldSpaceCameraPos; viewDir=normalize(viewDir);
float3 refDir=2*dot(-viewDir,worldNormal); refDir=normalize(refDir);
fixed4 reflection=texCUBE(_Cubemap,refDir);
color=lerp(main,reflection,_Reflection); } ENDCG } } }
|
4.5 结构体
在编写过程中,着色去需要输入输出多个参数,要简化代码编写,可以使用结构体。
4.5.1 结构体语法
(1)struct
:定义结构体的关键字。
(2)Type
:给当前结构体定义一个类型。
将Color Property改写成结构体形式:
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
| Shader "Custom/In Out Struct" { Properties { _MainColor("MainColor",Color)=(1,1,1,1) _MainTex("MainTex",2D)="white"{} } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; };
struct v2f { float4 position : SV_POSITION; float2 texcoord : TEXCOORD0; }; fixed4 _MainColor; sampler2D _MainTex; float4 _MainTex_ST;
void vert(in appdata v,out v2f o) { o.position=UnityObjectToClipPos(v.vertex);
o.texcoord = v.uv * _MainTex_ST.xy + _MainTex_ST.zw; }
void frag(in v2f i,out fixed4 color : SV_Target) { color = tex2D(_MainTex,i.texcoord) * _MainColor; } ENDCG } } }
|
4.5.2返回结构体的函数
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
| Shader "Custom/Return Struct" { Properties { _MainColor("MainColor",Color)=(1,1,1,1) _MainTex("MainTex",2D)="white"{} } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; };
struct v2f { float4 position : SV_POSITION; float2 texcoord : TEXCOORD0; }; fixed4 _MainColor; sampler2D _MainTex; float4 _MainTex_ST;
v2f vert(appdata v) { v2f o; o.position=UnityObjectToClipPos(v.vertex); o.texcoord = v.uv * _MainTex_ST.xy + _MainTex_ST.zw;
return o; }
fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex,i.texcoord) * _MainColor; } ENDCG } } }
|