目录
一、材质和UnityShader
二、Unity中的Shader
三、Unity Shader的基础:ShaderLab
1.给Shader命名
2.材质和Unity Shader的桥梁:Properties
属性类型
3.SubShader
状态设置
SubShader标签
Pass语义块
特殊的Pass
UsePass
GrabPass
4.FallBack
四、Unity Shader的形式
1.表面着色器
2.顶点/片元着色器
3.固定函数着色器
4.选择合适的Unity Shader形式
总体来说,在Unity中我们需要配合使用材质(Material)和UnityShader才能达到需要的效果一般流程如下:
1)创建一个材质
2)创建一个UnityShader,并赋给上一步创建的材质上
3)把材质赋给渲染的对象
4)在材质面板中调整UnityShader的属性
UnityShader定义了渲染所需要的各种代码(如顶点着色器和片元着色器)。属性(如纹理等)和指令(如渲染和标签设置等),而,材质允许我们调节这些属性并其最终赋予给相应的模型。
Unity提供了4种UnityShader模板——Standard Surface Shader(标准光照模型),Unlit Shader(不包含光照但包含雾效的基本的顶点/片元着色器),Image Effect Shader(实现各种屏幕的后处理效果的模板),Compute Shader(会产生一种特殊的Shader文件,旨在利用GPU的并行性进行一些与常规渲染流水线无关的计算)。
ShaderLab:一种专门为Unity Shader服务的语言。
ShaderLab是Unity提供的编写Unity Shader的一种说明性语言。它使用了一些嵌套在花括号内部的语义(syntax)来描述一个Unity Shader的结构,这些结构包含了许多渲染所需要的数据。
一个Unity Shader的基础结构:
Shader "ShaderName"{
Properties{
//属性
}
SubShader{
//显卡A使用的子着色器
}
SubShader{
//显卡B使用的子着色器
}
Fabllback "VertexLit"
}
每一个Shader都可以在第一行通过Shader语义景象命名,通过在字符串中添加斜杠可以控制Unity Shader在材质面板中出现的位置。
例如: Shader "Custom/MyShader" { }
Properties语义块定义了一系列属性(property),这些属性将会显示在材质面板中。
Properties{
Name("display name", PropertyType)=DefaultValue
Name("display name", PropertyType)=DefaultValue
}
当我们需要在Shader中访问时,只需要使用每个属性的名字(Name),通常由一个下划线开始。
显示的名称(display name)是出现在材质面板上的名字
类型(PropertyType)是每个属性的类型
默认值(DefaultValue)是未定义属性值时的初始默认值
属性类型 | 默认值的定义语法 | 例子 |
Int | number | _Int("Int",Int)=2 |
Float | number | _Float("Float",Float)=1.5 |
Range(min,max) | number | _Range("Range",Range(0.0,3.0))=2.9 |
Color | (number,number,number,number) | _Color("Color",Color)=(1,1,1,1) |
Vector | (number,number,number,number) | _Vector("Vector",Vector)=(2,3,6,1) |
2D | "defaulttexture"{} | _2D("2D",2D)=""{} |
Cube | "defaulttexture"{} | _Cube("Cube",Cube)=”white“{} |
3D | "defaulttexture"{} | _3D("3D",3D)="black"{} |
对于Int,Float,Range数字类型属性,默认值是一个单独数字;对于Color和Vector属性,默认值时一个四维向量;对于2D,Cube,3D这种纹理属性,默认值时通过一个字符串加花括号指定,其中字符串要么为空,要么时内置的纹理名称,如"white","black","grey"或者"bump"
每一个Unity Shader文件可以包含多个SubShader语义快,但最少要有一个,当Unity需要加载这个Unity Shader时,会扫描所有的SubShader,选择第一个能够在目标平台上运行的SubShader,如果都不支持就会使用Fallback语义快指定的Unity Shader。
基本结构:
SubShader{
//可选的
[Tags]
//可选的
[RenderSetup]
Pass{
}
//Other Passes
}
SubShader定义了一系列Pass以及可选的状态[RenderSetup]和标签[Tags]设置,每一个Pass定义了一次完整的渲染流程,但是如果Pass数目过多往往会造成渲染性能下降。
状态和标签也可以在Pass内声明,不同的是,SubShader中的一些标签时特定的,这些标签设置和Pass中使用的标签是不一样的。而对于状态设置来说其使用语法是相同的,
但是,在SubShader进行的设置会用于所有的Pass,在Pass里设置的只会影响当前Pass。
状态名称 | 设置指令 | 解释 |
Cull | Cull Back|Front|Off | 设置剔除模式:剔除背面/正面/关闭剔除 |
ZTest | ZTest Less Greater|LEqual|GEqual|Equal|NotEqual|Always | 设置深度测试时使用的函数 |
ZWrite | ZWrite On|Off | 开启/关闭深度写入 |
Blend | Blend SrcFactor DstFactor | 开启并设置混合模式 |
SubShader的标签(Tags)是一个键值对,都是字符串类型。
这些键值对是SubShader和渲染引擎之间的沟通桥梁,用来告诉渲染引擎怎样以及何时渲染这个对象。
标签的结构:
Tags{"TagName1"="Value1" "TagName2"="Value2"}
标签类型:
标签类型 | 说明 | 例子 |
Queue | 控制渲染顺序,指定该物体属于哪一个渲染队列,通过这个方式可以保证所有的透明物体可以在所有不透明的物体后面被渲染,也可以自定义使用的渲染队列来控制物体的渲染顺序 | Tags{"Queue"="Transparent"} |
RenderType | 对着色器进行分类,例如这是一个不透明的着色器,或是一个透明的着色器,这可以被用于着色器替换功能 | Tags{"RenderType"="Opaque"} |
DisableBatching | 一些SubShader在使用Unity的批处理功能时会出现问题,如使用了模型空间下的坐标进行顶点动画。这时可以通过该标签来直接指明是否对该SubShader使用批处理 | Tags{"DisableBatching"="True"} |
ForceNoShadowCasting | 控制使用该SubShader的物体是否会投射阴影 | Tags{"ForceNoShadowCasting"="True"} |
IgnoreProjector | 如果该标签为”True“则使用该SubShader的物体将不会受Projector影响,通常用于半透明物体 | Tags{"IgnoreProjector"="True"} |
CanUseSpriteAtlas | 当该SubShader是用于精灵(Sprite)时,将该标签设置为"False" | Tags{"CanUseSpriteAtlas"="False"} |
PreviewType | 指明材质面板将如何预览该材质。默认情况下材质显示为球形,可以通过标签设置为"Plane""SkyBox"等改变预览类型 | Tags{"PreviewType"="Plane" |
这些标签可以在SubShader中声明但不能在Pass中声明,Pass也可以定义标签,但是这些标签是不同于SubShader的标签类型
基本结构:
Pass{
[Name]
[Tags]
[RenderSetup]
//other code
}
首先可以在Pass中定义Pass的名称(Name)
如:Name "MyPassName"
通过这个名称我们可以使用ShaderLab的UsePass命令直接使用其它Unity Shader中的Pass
如: UsePass "MyShader/MYPASSNAME"
需要注意的是,由于Unity内部会把所有Pass的名称转换成大写字母表示,所有使用UsePass时PassName必须都是大写字母
其次可以对Pass设置渲染状态,SubeShader里设置的渲染状态同样适用于Pass,除了之前的状态外,还可以使用固定管线的着色器。
Pass同样可以设置标签,但不同于SubShader的标签,这些标签同样时用来告诉渲染引擎怎样渲染物体
标签类型 | 说明 | 例子 |
LightMode | 定义该Pass在Unity的渲染流水线中的角色 | Tags{"LightMode"="ForwardBase"} |
RequireOptions | 用于指定当满足某些条件时才渲染该Pass,它的值是一个由空格分隔的字符串。 | Tags{"RequireOptions"="SoftVegetation"} |
除了上面的普通Pass定义外,Unity还支持一些特殊的Pass,以便进行复用和实现更复杂的效果。
可以使用该命令来复用其它UnityShader中的Pass
该Pass负责抓取屏幕并且将结果存储在一张纹理中,以用于后续的Pass处理
紧跟在各个SubShader语义快后面,用于告诉Unity当上面所有SubShader都不可用时该使用哪一个更低级的Shader。
语义结构:
FallBack "name"
或者
Fallback Off
5.ShaderLab其他语义
除了上述语义,还有一些不常用到的语义,比如如果不满足Unity内置的属性类型,想要自定义材质面板的编辑界面,就可以使用CustomEditor语义来扩展编辑器界面,还可以使用Category语音对Unity Shader中的命令进行分组。
Unity Shader最重要的任务是指定各种着色器所需要的代码。这些着色器代码可以写在SubShader语义块中(表面着色器的做法),也可以写在Pass语义块中(顶点着色器/片元着色器和固定函数着色器的做法),但不管哪一种,真正意义上的代码都是需要写在SubShader语义块中的。
表面着色器是Unity自己创造的一种着色器代码类型,它需要的代码量很少,Unity背后做了许多工作,但是渲染的代价比较大。
当给Unity提供一个表面着色器时,它在背后仍旧把它转换成对应的顶点/片元着色器。可以理解为,表面着色器是Unity对顶点/片元着色器的更高一层的抽象。它的价值在于Unity为我们处理了很多光照细节。
一个简单的表面着色器代码示例如下:
Shader "Custom/Simple Surface Shader"{
SubShader{
Tags{"RenderType"="Opaque"}
CGPROGRAM
#pragma surface surf Lambert
struct Input{
float4 color:COLOR;
};
void surf(Input IN, inout SurfaceOutput o){
o.Albedo=1;
};
ENDCG
}
Fallback "Diffuse"
}
表面着色器被定义在SubShader语义块(而非Pass语义块)中CGPROGRAM和ENDCG之间。表面着色器不需要开发者关心使用了多少个Pass、每个Pass如何渲染等问题,Unity在背后已经做好了这些事情。
CGPROGRAM和ENDCG之间的代码是使用Cg/HLSL编写的,我们需要把Cg/HLSL语言嵌套在ShaderLab语言中。值得注意的是,这里的Cg/HLSL是Unity经过封装后提供的,它与标准的Cg/HLSL语法几乎一致但还是由细微不同的。
在Unity中可以使用Cg/HLSL语言来编写顶点/片元着色器,它们更加复杂但灵活性也更高。
一个非常简单的顶点/片元着色器代码示例如下:
Shader "Custom/Simple VertexFragment Shader"{
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert(float4 v:POSITION): SV_POSITION {
return mul (UNITY_MATRIX_MVP,v);
}
fixed4 frag():SV_Target{
return fixed4(1.0,1.0,1.0,1.0);
}
ENDCG
}
}
}
和表面着色器类似,顶点/片元着色器的代码也需要被定义在CGPROGRAM和ENDCG之间,不同的是顶点/片元着色器的代码写在了Pass语义块内。原因是我们需要自己定义每一个Pass需要使用的Shader代码,更重要的是我们可以控制渲染的实现细节。
表面着色器和顶点/片元着色器都使用了可编程管线,对于较旧的设备,它们不支持可编程管线着色器,因此就需要使用固定函数着色器完成渲染。这些着色器往往只能够完成一些非常简单的效果。
一个非常简单的固定函数着色器代码示例如下:
Shader "Tutorial/Basic"{
Properties{
_Color("Main Color",Color)=(1,0.5,0.5,1)
}
SubShader{
Pass{
Material{
Diffuse[_Color]
}
Lighting On
}
}
}
固定函数着色器的代码被定义在Pass语义块中,这些代码相当于Pass中的一些渲染设置。对于固定函数着色器来说,我们需要完全使用ShaderLab的语法(即使用ShaderLab的渲染设置命令)来编写,而非使用Cg/HLSL。
以下仅为建议:
1)除非需要特定的需求需要使用固定函数着色器(如较旧的设备运行),否则选择可编程管线的着色器。
2)如果想和光源打交道,可以选择表面着色器,但是需要小心它在移动平台性能的表现。
3)如果和光源打交道的数目非常小(比如只有一个平行光),那么选择顶点/片元着色器会更好
4)如果你有很多自定义的渲染效果,那么选择顶点/片元着色器