目录
2、Properties语句块——材质和Unity Shader的桥梁
一、shader代码基本结构
1、Shader的名字
Shader "MyShader/5_SimpleShader"{ }
shader文件的第一行都要指定shader的路径还有名字。如图,这个shader就是在MyShader路径下的一个名叫5_SimpleShader的一个shader。当然这个路径可以不止一层,比如说可以写成 MyShader/Charpt5/5_SimpleShader,表示在MyShader下的Charpt5下的一个shader。
我们知道,一般shader的使用要创建一个Material(材质),然后给材质赋予一个shader。上面提到的路径就是用来控制shader在Material面板中出现的位置。如下图,我可以在指定的路径里面找到对应的shader。
然后剩下的代码都是在后面的大括号里面完成。
2、Properties语句块——材质和Unity Shader的桥梁
Properties{
_Color("Color Tint", Color) = (1, 1, 1, 1)
}
Properties语句块包含了一系列属性,如上图就包括了一个Color的属性。在Properties语句块中声明的属性是为了在材质面板中能够方便地调整各种材质属性。比如我做了一个效果,但是不知道用什么颜色比较好,我就可以在里面声明一个Color类型的属性,这样我在Unity的材质面板就可以随时更改,而不用跑到shader里面来更改。如下图我们可以直接在材质面板更改颜色。
Properties语句块的定义模板通常如下:
Properties{
Name ("display name" ,PropertyType) = DefaultValue
Name ("display name" ,PropertyType) = DefaultValue
//更多属性
}
Name是属性的名字,一般以下划线开头;display name 是我要在材质面板中显示的名字;PropertyType是属性的类型;DefaultValue是该属性的默认值;
除了Color也有很多其它属性,如 Int、Float、Range(min, max)、Vector、2D、Cube、3D等。Properties语句块该语句是非必需的,可以选择不声明任何材质属性。
3、SubShader 语句块——重量级成员
一个Unity Shader文件包含至少一个SubShader语句块,通常定义如下:
SubShader{
//可选的
[Tags]//标签
//可选的
[RenderSetup]//状态设置
Pass{
}
// 其它的Pass语句块
}
Tages(标签)是用来告诉引擎,我们希望怎样,以及何时渲染这个对象。可以通过Tages做到控制渲染顺序、预览材质的方式、着色器分类等。
状态设置(RenderSetup)。Shader Lab提供了一 系列渲染状态的设置指令, 这些指令可以设置显卡的各种状态, 例如是 否开启混合/深度测试等。
Pass语句块定义了完整的渲染流程。一个SubShader语句块可以有多个Pass语句。Pass语句里面也可以设置标签,但标签的类型和在SubShader里面声明的标签类型不一样。Pass语句也可以进行状态设置,在SubShader里面的状态设置会影响所有的Pass语句,在Pass语句里面的状态设置只会影响该Pass语句。
4、Fallback
不同的显卡配置、能力不一样,有些显卡可能不支持一些SubShader的内容,如果所有的SubShader都不支持的话,Unity就会使用Fallback语义指定的Unity Shader。
Fallback "name"//模板
二、一个简单的顶点/片元着色器
1、顶点/片元着色器的结构
Shader "MyShaderName " {
Properties {
//属性
}
SubShader {
//针对显卡A的SubShader
Pass {
//设置渲染状态和标签
//开始 CG 代码
CGPROGR
//该代码片段的编译指令,例如
#pragma vertex vert
#pragma fragment frag
// CG 代码写在这里
//结束CG代码
ENDCG
//其他设置
}
//其他需要的 Pass
}
SubShader {
// 针对显卡B的SubShader
}
// 上述 SubShader 都失败后用于回调的 Unity Shader
Fallback "VertexLit "
}
2、一个简单的顶点/片元着色器
Shader "MyShader/5_SimpleShader"
{
Properties{
//声明一个Color类型的属性,在后面的cg代码中要声明一个与之相对应的变量
_Color("Color Tint", Color) = (1, 1, 1, 1)
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert //告诉Unity顶点着色器的函数为vert
#pragma fragment frag //告诉Unity片元着色器的函数为frag
//在cg代码中,我们需要定义一个与Properties里的属性名称和类型都匹配的变量
fixed4 _Color;
//fixed是浮点型的一种,取值范围为[-2,2],4代表由4个这样的fixed组成的变量
// 使用一个结构体来定义顶点着色器的输入
struct a2v {
// POSITION 语义告诉UNITY,用模型空间的顶点坐标填充vertex变量
float4 vertex : POSITION;
// NORMAL 语义告诉unity,用模型空间的法线方向填充normal变量
float3 normal: NORMAL;
// TEXCOORD0 语义告诉unity,用模型空间的第一套纹理坐标填充texcoord变量
float4 texcoord : TEXCOORD0;
};
// 使用一个结构体来定义顶点着色器的输出
struct v2f {
// SV_POSITION 语义告诉Unity,pos里面包含了顶点在裁剪空间的位置信息
float4 pos: SV_POSITION;
// COLOR0 语义可以用于储存颜色信息
fixed3 color : COLOR0;
};
//顶点着色器的函数,输入一个a2v的结构体
v2f vert(a2v v) {
//声明输出结构
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);
//输出一个a2f的结构体,传给片元着色器(frag)
return o;
}
//片元着色器的函数,输入一个a2f的结构体,也就是顶点着色器最后的返回值。
float4 frag(v2f i) : SV_Target{
fixed3 c = i.color;
// 使用Color属性来控制输出颜色
c *= _Color.rgb;
return fixed4(c, 1.0);
}
ENDCG
}
}
}
虽然在Pass语句里面只有定义函数的代码,不用调用的代码。我们以及告诉了Unity哪个是顶点着色器的函数和片元着色器的函数,Unity会自动帮我们调用顶点着色器函数,然后用顶点着色器函数的返回值调用片元着色器。
使用上面这个shader我们就可以得到一个五颜六色的球: