本文同时发布在我的个人博客上:https://dragon_boy.gitee.io
Unity版本:2019.3.10
一个简单的顶点\片元着色器
基本结构
Shader "MyShaderName"
{
Propertices
{
// 属性
}
SubShader
{
Pass
{
//设置渲染状态和标签
CGPROGRAM
// 代码片段编译指令
#pragma vertex vert
#pragma fragment frag
//Cg代码
ENDCG
//其他设置
}
// 其它Pass
}
SubShader
{
}
Fallback "VertexLit"
}
有了基本结构,下面创建一个最简单的顶点\片元着色器。
创建一个球体,创建一个材质,创建一个Unlit Shader,将Shader赋予材质,材质赋予球体,Shader代码如下:
Shader "Unlit/SimpleShader"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert (float4 v : POSITION) : SV_POSITION
{
return UnityObjectToClipPos(v);
}
fixed4 frag () : SV_Target
{
return fixed4(1.0, 1.0, 1.0, 1.0);
}
ENDCG
}
}
}
效果如下:
注意,POSITION是一个语义,用来指定顶点的位置,同样还有SV_POSITION。POSITION告诉Unity将模型的顶点坐标填充到输入参数v中,SV_POSITION告诉Unity顶点着色器的输出是裁剪空间中的顶点坐标。
SV_TARGET也是一个系统语义,它告诉渲染器,将用户的输出颜色存储到一个渲染目标中,这里将输出到默认的帧缓冲中。
模型数据
在Shader中,我们可以声明一个结构体来整合一个顶点的输入,如下:
Shader "Unlit/SimpleShader"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
float4 vert (a2v v) : SV_POSITION
{
return UnityObjectToClipPos(v.vertex);
}
fixed4 frag () : SV_Target
{
return fixed4(1.0, 1.0, 1.0, 1.0);
}
ENDCG
}
}
}
NORMAL和TEXCOORD0都是可以使用的语义,Unity会根据语义填充结构体。可以使用的语义由:POSITION,TANGENT,NORMAL,TEXCOORDX,COLOR等。
在Unity中,填充到语义中的数据由材质所在的Mesh Renderer组件提供。每帧调用Draw Call是,Mesh Renderer会将负责渲染的模型数据发送到Unity Shader。
顶点着色器和片元着色器的通信
为将顶点着色器的输出传递给片元着色器,我们可以再定义一个顶点着色器到片元着色器的结构体:
Shader "Unlit/SimpleShader"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
fixed3 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
}
}
}
效果如下:
注意,顶点着色器是逐顶点调用,片元着色器是逐片元调用。片元着色器中的输入实际上是把顶点着色器的输出进行插值后的结果。
使用属性
下面我们声明一个颜色属性,可以在材质面板直接拾取颜色:
Shader "Unlit/SimpleShader"
{
Properties
{
_Color("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
//定义一个与属性名相同的变量
fixed4 _Color;
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
{
fixed3 c = i.color;
c *= _Color.rgb;
return fixed4(c, 1.0);
}
ENDCG
}
}
}
效果如下,拾取红色:
ShaderLab中的属性类型与CG语言变量类型的对应关系:
Unity内置文件和变量
包含文件在CG代码块里按正常类C++代码写即可,如:
CGPROGRAM
//..
#include "UnityCG.cginc"
//..
ENDCG
更多信息查看https://docs.unity3d.com/Manual/SL-BuiltinIncludes.html。
或者查看Unity目录下的CGIncludes中的所有包含文件。
Unity语义
注意,一个语义使用的寄存器只能处理4个浮点值,所以对于矩阵类型要拆分开。