开始学习Unity Shader之旅
最简单的片元着色器
#pragma vertex vert
#pragma fragment frat
告诉Unity那个函数包括顶点着色器的代码,哪个函数包括片元着色器的代码。
float4 vert(float4 v : POSITION) SV_POSITION{
return mul(UNITY_MATRIX_MVP,v);
}
vert 中包含了这个顶点的位置,这是通过 POSITION 语义定义的。
输出值为裁切空间中的顶点坐标,这是通过 SV_POSITION 语义定义的。
//使用一个结构体来定义顶点着色器的输入
struct a2v {
//POSITION 语义告诉 Unity,用模型空间的顶点坐标填充vertex变量
float4 vertex : POSITION;
//NORMAL 语义告诉Unity,用模型空间的法线方向填充normal变量
float3 noremal : NORMAL;
//TEXCOORD0语义告诉Unity,用模型的第一套纹理坐标填充texcoord变量
float4 texcoord : TEXCOORD0;
};
float4 vert(a2v v) : SV_POSITION {
return UnityObjectToClipPos(v.vertex);
}
需要获取多个顶点信息,需要创建一个结构,并且作为参数传递给顶点着色器代码块。
顶点着色器与片元着色器通信
在顶点着色器代码块中,返回值输出一个与片元着色器参数类型相同的结构体。
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);
}
如何使用属性
Properties语义块中可以定义一些参数由编辑器的材质面板调节。
- 首先我们添加了Properties语义块,并在其中声明一个属性_Color,它的类型是Color,初始值是(1.0,1.0,1.0,1.0);
- 为了在CG代码中访问他,我们需要在CG代码中提前定义一个变量,变量名称和类型必须与Properties语义块中的属性定义相匹配。
ShaderLab属性类型和CG变量类型的匹配关系
| ShaderLab属性类型 | CG变量类型 |
|---|---|
| Color,Vector | float4,half4,fixed4 |
| Range,Float | float,half,fixed |
| 2D | sampler2D |
| Cube | samplerCube |
| 3D | sampler3D |
Unity 提供的内置文件和变量
包含文件,类似c++中的头文件的一种。在Unity中它们的后缀为.cginc。使用#include可以把它们包含进来。
这些文件可以在官方网站上选择下载->内置着色器来直接下载。这些文件是非常好的参考资料,在我们想要学习内置着色器的实现或是寻找内置函数的实现时,都可以在这里找到内部实现。
我们可以直接在Unity的应用程序中找到
Mac:/Application/Unity/Unity.app/Content/CGIncludes
Windows:Unity安装路径/Data/CGIncludes
Unity中一些常用的包含文件
| 文件名 | 描述 |
|---|---|
| UnityCG.cginc | 包含了最常使用的帮助函数、宏和结构体 |
| UnityShaderVariables.cginc | 在编译Unity Shader时,会被自动包含进来。包含了许多内置的全局变量,如UNITY_MATRIX_MVP等 |
| Lighting.cginc | 包含了各种内置的光照模型,如果编写的是Surface Shader的话,会自动包含进来 |
| HLSLSupport.cginc | 在编译Unity Shader时,会自动包含进来。声明了很多用于跨平台编译的宏和定义 |
UnityCG.cginc 是最常接触的一个包含文件。
UnityCG.cginc中一些常用的结构体
| 名称 | 描述 | 包含的变量 |
|---|---|---|
| appdata_base | 可用于顶点着色器的输入 | 顶点位置、顶点法线、第一组纹理坐标 |
| appdata_tan | 可用于顶点着色器的输入 | 顶点位置、顶点切线、顶点法线、第一组纹理坐标 |
| appdata_full | 可用于顶点着色器的输入 | 顶点位置、顶点切线、顶点法线、四组(或更多)纹理坐标 |
| appdata_img | 可用于顶点着色器的输入 | 顶点位置、第一组纹理坐标 |
| v2f_img | 可用于顶点着色器的输出 | 裁剪空间中的位置、纹理坐标 |
UnityCG.cginc中一些常用的帮助函数
| 函数名 | 描述 |
|---|---|
| float3 WorldASpaceViewDir(float4 v) | 输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向 |
| float3 ObjSpaceViewDir(float4 v) | 输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向 |
| float3 WorldSpaceLightDir(float4 v) | 仅可用于前向渲染。输入一个模型空间中的额顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化。 |
| float3 ObjSpaceLightDir(float4 v) | 仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。没有被归一化。 |
| float3 UnityObjectToWorldNormal(float3 norm) | 把法线方向从模型空间转换到世界空间中 |
| float3 UnityObjectToWorldDir(in float3 dir) | 把矢量方向从模型空间变换到世界空间中 |
| float3 UnityWorldToObjectDit(float3 dir) | 把矢量方向从世界空间变换到模型空间中 |
Unity 提供的CG/HLSL语义
什么是语义
语义可以让Shader知道从哪里读取数据,并把数据输出到哪
之前代码中的POSITION NORMAL SV_POSIONT 等就是语义
Unity 支持的语义
从应用阶段传递模型数据给顶点着色器时Unity支持的常用语义
| 语义 | 描述 |
|---|---|
| POSITION | 模型空间中的顶点位置,通常是float4类型 |
| NORMAL | 顶点法线,通常是float3类型 |
| TANGENT | 顶点切线,通常是float4类型 |
| TEXCOORDn,如TEXCOORD0、TEXCOORD1 | 该顶点的纹理坐标,TEXCOORD0表示第一组纹理坐标,以此类推。通常是float2或float4类型 |
| COLOR | 顶点颜色,通常是fixed4或float4类型 |
从顶点着色器传递数据给片元着色器时Unity使用的常用语义
| 语义 | 描述 |
|---|---|
| SV_POSITION | 裁剪空间中的坐标顶点,结构体中必须包含一个用该语义修饰的变量。等同于DirectX9中的POSITION,但最好使用SV_POSITION |
| COLOR0 | 通常用于输出第一组顶点颜色,但不是必须的 |
| COLOR1 | 通常用于输出第二组顶点颜色,但不是必须的 |
| TEXCOORD0~TEXCOORD7 | 通常用于输出纹理坐标,但不是必须的 |
片元着色器输出时Unity支持的常用语义
| 语义 | 描述 |
|---|---|
| SV_Target | 输出值将会存储到渲染目标(render target)中。等同于DirexX 9中的COLOR语义,但最好使用SV_Target |
调试
可以将法线,切线向量值对应到RGB颜色值,然后在画面颜色显示中来判断。
使用Unity中的帧调试器(Window->Frame Debugger)可以更方便进行调试。
帧调试器
- 最上面的区域可以控制帧调试器开关,开启时滑动条可以重放渲染事件。如果选中的Draw Call是对一个纹理的渲染操作,这个渲染纹理就会显示在Game视图中。而且右侧面板上方可以选择只显示R、G、B、A之一。
- 左侧的区域显示了所有事件的树状图,这个树状图中,每个叶子节点就是一个事件。我们可以从事件的命名了解这个事件的操作,例如Draw开头的事件通常就是一次Draw Call。
- 右侧窗口显示事件的细节,例如几何图形的细节以及使用了哪个shader。
如果想要得到更多信息,还是需要使用外部工具。
渲染平台的差异
Unity的有点之一就是跨平台性很强,写一份代码就可以在多平台上运行。但绝大多数情况下,Unity为我们隐藏了这些细节,但有时候我们还是需要自己处理。
渲染纹理的坐标差异
- OpenGL和DirectX中的屏幕坐标存在差异,OpenGL中坐标(0,0)点在左下角,而DirectX中为左上角。
大多数情况下,这并不会对我们造成影响。当我们开启了抗锯齿并且需要处理多张渲染图像时。在DirectX中,我们就需要在顶点着色器中翻转某些渲染纹理,例如:
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y < 0){
uv.y = 1 - uv.y;
}
#endif
其中UNITY_UV_STARTS_AT_TOP用来判断当前平台是不是DirectX类型的平台。通过判断_MainTex_TexelSize.y是否小于0来判断是否打开了抗锯齿。