本文为学习UnityShader时所记录的笔记,供学习产出和日后复习使用。
学习资料为冯乐乐(问就是我女神)的《UnityShader入门精要》
呜呜呜今天看书看这节的时候已经感觉人开始略微劝退了,不过还是充满激情的!
3.1 UnityShader概述
在unity中,Shader需要和材质配合使用。
unity中提供了四种Shader模板:
Standard Surface Shader:包含了标准光照模型的表面着色器模板
Unlit Shader:不包含标准光照的基本顶点/片元着色器(教程中大部分使用)
Image Effect Shader:实现各种屏幕后处理效果
Compute Shader:产生特殊Shader文件来利用GPU并行性进行与渲染无关的计算(一般用不到)
3.2 UnityShaderLab
UnityShaderLab是专门用来为UnityShader服务的语言,是高级层的渲染抽象层。
一个UnityShader基础结构如下:
Shader "ShaderName"
{
Properties
{//属性}
SubShader
{//子着色器A}
SubShader
{//子着色器B}
Fallback “VertexLit”
}
unity会根据目标平台将以上的UnityShaderLab代码编译成真正的代码和Shader文件。
3.3 UnityShader的结构
起个名字
每个Unity Shader 第一行都需要通过Shader语义来指定该Unity Shader的名字。
Shader "MyShader"
或者是这样
Shader "Custom/MyShader"
在字符串中添加斜杠可以控制Shader在材质面板出现的位置
Properties
Propertie是材质和UnityShader的桥梁,他包含了一系列属性,这些语义会出现在材质面板中。
Properties语义块定义一般如下:
Properties
{
Name ("display name",PropertyType)=DefaultValue
}
声明这些属性是为了在材质面板中能方便的调整。
若需要在Shader中访问这些属性,则需要使用每个属性的名字(Name)一般由下划线开始。
显示的名称(display name)是出现在材质面板上面的名字。
我们还需要为每个属性指定他的类型(PropertyType)并指定一个默认值
下列代码展示了所有的属性:
Shader "Custom/MyShad类型er"
{
Properties
{
//number and slider
_MainTex("Texture", 2D) = "white" {}
_Int("Int",Int) = 2
_Float("Float",Float) = 1.5
_Range("Range",Range(0.0,5.0)) = 3.0
//color and vector
_Color("Color",Color) = (1,1,1,1)
_Vector("Vector",Vector) = (2,3,6,1)
//texture
_2D("2D",2D) = ""{ }
_3D("3D",3D) = "Black"{ }
_Cube("Cube",Cube) = "White"{}
}
FallBack "Diffuse"
对于Int Float Range这些数字类型的属性,其默认值是一个单独的数字。
对于Color和Vector这类属性,默认值是圆括号包围的四维向量。
对于2D,Cube,3D这类属性,默认值是一个字符串跟着一个花括号。
字符串要么是空的要么是内置的纹理名称(white,black,yellow)
SubShader
每一个Unity Shader文件可以包含多个SubShader语义块,但最少得有一个。
Unity会扫描所有语义块,选择第一个能在目标平台运行的语义块。若都不支持则会使用Fallback语义指定的Unity Shader。
原因是这样可以供不同性能的显卡使用不同复杂程度的语义块。
SubShader语义块包含的定义通常如下:
SubShader
{
//可选的标签设置
[Tags]
//可选的状态设置
[RenderSetup]
Pass{
}
//其他pass
}
SubShader中提供了一系列Pass和可选的状态设置(tag)和标签设置(RenderSetup)。
每个Pass定义了一套完整的渲染流程,但如果Pass过多就好造成渲染性能的下降。应尽量使用尽可能少数目的Pass。
Tags和RenderSetup可在SubShader中定义,若在SubShader中定义则会用于所有Pass;也可以在Pass中单独定义。
其中Tags有些特定设置,在SubShader和Pass是不一样的。而RenderSetup的语法则完全相同。
状态设置(RenderSetup):
ShaderLab提供了一系列渲染状态的设置指令,用于设置显卡的各种状态。
例如:
Cull Back | Front |Off (剔除背面/正面/关闭)
ZTest 巴拉巴拉(设置深度测试使用的函数)
ZWrite On | Off(开启关闭深度写入)
Blend 巴拉巴拉(开启并设置混合模式)
在SubShader中设置以上渲染状态时,将会应用到所有的Pass。
若不想这样也可以在Pass中单独进行以上设置。
标签设置(Tags):
Tags是一个键值对,值和键都是字符串类型
Tags{"TagName1"="Value1" "TagName2"="Value2"}
标签有以下(具体的键对应的值上网查吧太懒了555):
Queue(控制渲染顺序,指定该物体属于哪一个渲染队列)
RenderType(对着色器进行分类)
DisableBatching(是否禁用批处理)
ForceNoShadowCasting(控制该物体是否会投射阴影)
IgnoreProjector(是否受Projector影响,通常用于半透明物体)
CanUseSpriteAtlas(当SubShader用于精灵时将这个设为false)
PreviewType(指明材质面板该如何预览该材质,Plane,SkyBox)
上面的标签只能在SubShader中声明,而不能在Pass内。
Pass中声明的标签不同于SubShader的标签类型。
Pass语义块:
Pass语义块定义如下
Pass{
[Name]
[Tags]
[RenderType]
//Other Type
首先可以定义名字
例如Name "MyPassName"
这样
通过这个名字,可以使用UsePass命令来直接使用其他Unity Shader的Pass。
例如:
UsePass "MyShader/MYPASSNAME"
这样能提高代码的复用性,注意Unity内部会把所有Pass的名字转换为大写,因此使用UsePass命令时必须使用大写的名字。
Pass可以单独设置状态,这个在上面提到过了,用的是和在SubShader里面一样的状态类型。
但Pass内的标签就不同与SubShader的标签了,Pass内使用的是:
LightMode(定义该Pass在渲染流水线的角色)
RequireOptions(用于指定满足某些条件时才渲染该pass)
Fallback
Fallback是我们的后路,紧跟在SubShader语义块之后。
意味着所有的SubShader都不能在这张显卡上运行的情况下,使用Fallback指定的最低级的Shader。
Fallback "Name"
//或者
Fallback Off
Fallback还会影响阴影的投射,所以正确的设置Fallback是很重要的。
3.4 UnityShader的形式
我们可以用表面着色器(Surface Shader),顶点/片元着色器(Vertex/Fragment Shader),固定函数着色器(Fixed Function Shader)的形式来编写UnityShader。
其中固定函数着色器很少使用,所以略掉。
表面着色器(Surface Shader)
表面着色器是Unity自己创造的一种着色器代码类型,渲染的代价比较大。
本质上和顶点/片元着色器相同,实际上unity也在背后转换成顶点/片元着色器。
可以理解为表面着色器是顶点/片元着色器更高一层的抽象,并为我们处理了很多光照细节。
SubShader{
Tags{"RenderType"="Opaque"}
CGPROGRAM
#pragma surface surf Lambert
struct Input{
float 4 color : COLOR;
};
void surf (Input IN,inout SurfaceOutput o){
o.Albedo=1;
}
ENDCG
}
表面着色器被定义在SubShader语义块(非pass内),不需要关心使用了多少个Pass,Unity会在背后为我们处理好一切。
CGPROGRAM和ENDCG之间的代码使用Cg/HLSL编写,相当于将Cg/HLSL嵌套在ShaderLab中,语法和标准Cg/HLSL几乎一致。
顶点/片元着色器(Vertex/Fragment Shader)
以下代码来源于Unity新建的Unlit Shader模板。
现在可以不用看懂,只关注语义框架。
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
顶点/片元着色器代码也需要定义在CGPROGRAM和ENDCG之间
但顶点/片元着色器需要写在Pass内,而非SubShader内
因为与表面着色器不同,此处我们需要自己单独定义每个pass的Shader代码。
虽然这样需要编写更多代码,但灵活性也更高,我们可以由此控制渲染细节。
这里CGPROGRAM和ENDCG之间的代码也使用Cg/HLSL编写。
如何选择
- 除非在非常旧的设备(iPhone3)上运行,否则一般使用可编程管线的着色器而不是固定管线着色器。
- 若希望和各类光源打交道,不妨试试表面着色器(小心移动平台的性能表现)
- 若所需光源数目少(例如只有一个平行光),则使用顶点/片元着色器是更好的选择。
- 若需要很多自定义的渲染效果,则也请使用顶点/片元着色器。
昨天中间跑去约会又去酒吧爽喝,又睡了个大觉,拖下来十几个小时才完成这些,不过这两天还是蛮开心的!2021.8.29 16:24