Chapter5 开始Unity Shader
一、最简单的顶点/片元着色器
1.第一个简单代码
Shader "Custom/Chapter5_SimpleShader"
{
SubShader{
Pass{
CGPROGRAM //由CGPROGRAM和ENDCG包含的CG代码片段
#pragma vertex vert
#pragma fragment frag
//这两行告诉Unity,哪个函数包含了顶点着色器的代码,哪个函数包含了片元着色器的代码
//#pragma vertex name —— name即为函数名
//#pragma fragment name
float4 vert(float4 v : POSITION) : SV_POSITION{ //vert函数输入v包含了这个顶点的位置,是通过POSITION这个语义指定的,返回float4类型的值,是该顶点在裁剪空间中的位置
//POSITION 和 SV_POSITION都是不可省略的,告诉用户输入输出是什么
//POSTION告诉unity把模型顶点坐标填充到输入参数中
//SV_POSITION告诉输出是裁剪空间中的顶点坐标
return mul (UNITY_MATRIX_MVP, v); //把顶点坐标从模型空间坐标转换到裁剪空间
}
fixed4 frag():SV_Target{ //SV_Target告诉用户输出颜色存储到一个渲染目标中(这里输出到默认的帧缓存)
return fixed4(1.0, 1.0, 1.0, 1.0); //白色的fixed4类型的变量
}
ENDCG
}
}
}
- 可以得到一个白色的球
2.想要得到顶点纹理坐标和法线方向
定义一个结构体
Shader "Custom/Chapter5_SimpleShader"
{
SubShader{
Pass{
CGPROGRAM //由CGPROGRAM和ENDCG包含的CG代码片段
#pragma vertex vert
#pragma fragment frag
//这两行告诉Unity,哪个函数包含了顶点着色器的代码,哪个函数包含了片元着色器的代码
//#pragma vertex name —— name即为函数名
//#pragma fragment name
//使用一个结构体来定义顶点着色器的输入
struct a2v{ //a2v表示把数据从应用阶段传递到顶点着色器中
//POSITION告诉unity,用模型空间的顶点坐标填充vertex变量
float4 vertex : POSITION;
//NORMAL告诉unity,用模型空间的法线方向填充normal变量
float3 normal : NORMAL;
//TEXCOORD0告诉unity,用模型的第一套纹理坐标填充texcoord
float4 texcoord : TEXCOORD0;
};
float4 vert(a2v v) : SV_POSITION{ //vert函数输入v包含了这个顶点的位置,是通过POSITION这个语义指定的,返回float4类型的值,是该顶点在裁剪空间中的位置
//POSITION 和 SV_POSITION都是不可省略的,告诉用户输入输出是什么
//POSTION告诉unity把模型顶点坐标填充到输入参数中
//SV_POSITION告诉输出是裁剪空间中的顶点坐标
return UnityObjectToClipPos (v);
}
fixed4 frag():SV_Target{
return fixed4(1.0, 1.0, 1.0, 1.0);
}
ENDCG
}
}
}
- POSITION、TANGENT、NORMAL这些语义是由使用该材质的Mesh Render组件提供的,每帧调用Draw Call的时候,Mesh Render就会把负责渲染的模型数据发给Unity Shader
3.顶点着色器和片元着色器直接如何通信
把法线、纹理坐标等传递给片元着色器
Shader "Custom/Chapter5_SimpleShader"
{
SubShader{
Pass{
CGPROGRAM //由CGPROGRAM和ENDCG包含的CG代码片段
#pragma vertex vert
#pragma fragment frag
//这两行告诉Unity,哪个函数包含了顶点着色器的代码,哪个函数包含了片元着色器的代码
//#pragma vertex name —— name即为函数名
//#pragma fragment name
//使用一个结构体来定义顶点着色器的输入
struct a2v{ //a2v表示把数据从应用阶段传递到顶点着色器中
//POSITION告诉unity,用模型空间的顶点坐标填充vertex变量
float4 vertex : POSITION;
//NORMAL告诉unity,用模型空间的法线方向填充normal变量
float3 normal : NORMAL;
//TEXCOORD0告诉unity,用模型的第一套纹理坐标填充texcoord
float4 texcoord : TEXCOORD0;
};
struct v2f{ //v2f定义顶点着色器的输出
//SV_POSITION告诉unity,pos里包含了顶点在裁剪空间中的位置信息
float4 pos : SV_POSITION;
//COLOR0语义可以用于存储颜色信息
fixed3 color : COLOR0;
};
v2f vert(a2v v){ //vert函数输入v包含了这个顶点的位置,是通过POSITION这个语义指定的,返回float4类型的值,是该顶点在裁剪空间中的位置
//POSITION 和 SV_POSITION都是不可省略的,告诉用户输入输出是什么
//POSTION告诉unity把模型顶点坐标填充到输入参数中
//SV_POSITION告诉输出是裁剪空间中的顶点坐标
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//v.normal包含顶点的法线方向,范围在(-1.0,1.0)
//下面代码把分量范围映射到 [0.0,1.0)
//存储到o.color传递给片元着色器
o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5);
return o;
}
fixed4 frag(v2f i) : SV_Target{
//将插值后的i.color显示到屏幕上
return fixed4(i.color , 1.0);
}
ENDCG
}
}
}
- 片元着色器的输入实际上是把顶点着色器的输入进行插值后的结果
4.如何使用属性
- 通过在材质中调节参数,可以达到随时调整材质的效果
- 参数需要写在Properties语义块中
Shader "Custom/Chapter5_SimpleShader"
{
Properties{
_Color ("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0)
//见第24行
}
SubShader{
Pass{
CGPROGRAM //由CGPROGRAM和ENDCG包含的CG代码片段
#pragma vertex vert
#pragma fragment frag
//这两行告诉Unity,哪个函数包含了顶点着色器的代码,哪个函数包含了片元着色器的代码
//#pragma vertex name —— name即为函数名
//#pragma fragment name
fixed4 _Color; //需要在CG片段中提前定义一个新变量,名称与Properties的属性相同
//使用一个结构体来定义顶点着色器的输入
struct a2v{ //a2v表示把数据从应用阶段传递到顶点着色器中
//POSITION告诉unity,用模型空间的顶点坐标填充vertex变量
float4 vertex : POSITION;
//NORMAL告诉unity,用模型空间的法线方向填充normal变量
float3 normal : NORMAL;
//TEXCOORD0告诉unity,用模型的第一套纹理坐标填充texcoord
float4 texcoord : TEXCOORD0;
};
struct v2f{ //v2f定义顶点着色器的输出
//SV_POSITION告诉unity,pos里包含了顶点在裁剪空间中的位置信息
float4 pos : SV_POSITION;
//COLOR0语义可以用于存储颜色信息
fixed3 color : COLOR0;
};
v2f vert(a2v v){ //vert函数输入v包含了这个顶点的位置,是通过POSITION这个语义指定的,返回float4类型的值,是该顶点在裁剪空间中的位置
//POSITION 和 SV_POSITION都是不可省略的,告诉用户输入输出是什么
//POSTION告诉unity把模型顶点坐标填充到输入参数中
//SV_POSITION告诉输出是裁剪空间中的顶点坐标
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//v.normal包含顶点的法线方向,范围在(-1.0,1.0)
//下面代码把分量范围映射到 [0.0,1.0)
//存储到o.color传递给片元着色器
o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5);
return o;
}
fixed4 frag(v2f i) : SV_Target{
fixed3 c = i.color;
//使用_Color属性来控制输出颜色
c *= _Color.rgb;
return fixed4(c , 1.0);
}
ENDCG
}
}
}
- 在Properties声明一个color类型的属性_Color,类型是Color,初始值是 (1.0, 1.0, 1.0, 1.0)白色
- 需要在CG片段中提前定义一个新变量,名称与Properties的属性相同
二、内置文件和变量
三、Unity CG/HLSL的语义
1.语义的定义
- 定义:用于描述着色器输入和输出数据类型的字符串。
- 作用:告诉着色器输入输出的数据类型及其在图形管线中的用途
2.Unity支持的语义
- 从 应用阶段 传递模型数据 给顶点着色器
- POSITION:表示模型空间中的顶点位置,通常类型为 float4。
- NORMAL:表示顶点法线,通常类型为 float3。
- TANGENT:表示顶点切线,通常类型为 float4。
- TEXCOORDn:表示纹理坐标,TEXCOORD0 表示第一组纹理坐标,通常类型为 float2 或 float4。
- COLOR:表示顶点颜色,通常类型为 fixed4 或 float4。
- 从 顶点着色器 传递数据给 片元着色器
- SV_POSITION:表示裁剪空间中的顶点位置,结构体中必须包含一个用该语义修饰的变量。
- COLOR0, COLOR1:通常用于输出额外的颜色信息。
- TEXCOORD0 到 TEXCOORD7:通常用于输出纹理坐标数据。
- 片元着色器输出 语义
- SV_Target:表示渲染目标
3.如何定义复杂的变量类型
struct v2f {
float4 pos : SV_POSITION;
fixed3 color0 : COLOR0;
fixed4 color1 : COLOR1;
half value0 : TEXCOORD0;
float2 value1 : TEXCOORD1;
};
- pos 用 SV_POSITION修饰,表示这是一个顶点位置数据
- color0、color1 用 COLOR0、COLOR1修饰,表示是颜色数据
- value0、value1 用 TEXCOORD0、TEXCOORD1修饰,表示是纹理坐标数据
四、Debug
1.方法1:使用假彩色图像
- 主要思想:把需要调试的变量映射到 [0, 1] 之间,把它们作为颜色输出到屏幕上,通过屏幕上的颜色来判断这个值是否正确
2.方法2:Visual Studio
3.方法3:帧调试器
- unity提供的针对渲染的调试器,打开:Window->Frame Debugger
- 用于查看渲染该帧时进行的各种渲染事件(包含了Draw Call序列、清空帧缓存等操作)
- 仅仅是使用停止渲染的方法来查看该帧的渲染结果
五、平台的差异
1.渲染纹理坐标差异
- OpenGL和DirectX 在屏幕空间坐标上存在差异,OpenGL的(0,0)点在左下角,而DirectX在左上角
- 会导致渲染纹理时出现图像翻转差异
2.Shader语法差异
- DirectX对语法要求更加严格
3.Shader语义差异
- 顶点着色器输出的顶点位置应该使用 SV_POSITION 语义,而不是 POSITION 语义(在PS4或使用了细分着色器的情况下
- 片元着色器的输出颜色应该使用 SV_Target 语义,而不是 COLOR 或 COLOR0 语义(PS4)
解决方法
- unity提供了一些内置函数,例如 UNITY_UV_STARTS_AT_TOP 用于判断平台类型,UNITY_MATRIX_MVP 用于坐标变换
- 使用预处理指令,判断平台类型,针对不同平台进行不同处理