前言
本文用于个人学习总结,个人能力有限文章内容仅供参考,如果文章内容有错误本人感到非常抱歉,或您对文章内容有疑问可以联系我,本文将讲解Unity ShaderLab相关内容。为了方便入门,我略去了部分理解较为复杂的内容,以帮助自己和读者更简单了解unity shader基础,在理清楚大部分内容后,建议查阅其他更专业博主的内容。
(ShaderLab - Unity 手册文中大部分内容官方文档中都有,各位可以自行查看)
一、Unity Shader和Shader的区别
ShaderLab 是一种在着色器源文件中使用的声明性语言。它使用嵌套大括号语法来描述 Shader 对象。(源自:ShaderLab - Unity 手册)
换而言之ShaderLab是Unity Shader提供给开发者用于跨平台着色器开发的一种语言体系。这里我们要知道,Unity Shader 和 Shader 是有区别的,前者就是指Unity中的着色器,后者则更广一些 指着色程序。(下文中“Unity Shader”为了方便理解和阅读均写为”Shader“)
二、Shader和材质
在unity中想要体现着色器 (Unity Shader) 的效果需要和材质 (Material) 配合使用。我们需要在unity中分别创建Shader和材质,并且赋予需要应用的对象 (网格 (Mesh) )从而达成效果。
Shader需要配合材质使用
1.在Unity中创建材质
如果需要创建一个材质,我们可以选择在“Project窗口”中右击空白处,随后点选"Create" -> "Material",或是在unity窗口顶部工具栏点选 “Assets”->"Create" -> "Material"。
(Inspector窗口中的内容在下文中细讲)
2.在Unity中创建Shader文件
与创建材质相同,可以在“Project窗口”中右击空白处,随后点选"Create" -> "Shader"->选择你需要的Shader模版①(下文提及区别),或者在unity窗口顶部工具栏点选 “Assets”->"Create" -> "Shader"->你所需要的模版。
①创建Shader时几种模版:
1.Standard Surface Shader ( 标准曲面着色 ) :
包含基于物理的标准光照模型表面着色器模板,主要用于模拟生活中常见的材质如金属、塑料、石头等。
2.Unlit Shader ( 无灯光着色器 ) :
顾名思义这个着色器去除了光照和阴影相关的内容 由基本的顶点/片元着色器组成
3.Image Effect Shader ( 图像效果着色器 ) :
多半用于实现各种屏幕后处理效果 (后处理:在最后基于整个屏幕内容进行的处理,美化,如 Bloom ( 泛光 ),Depth Of Field ( 景深 ) ,Blur(模糊)等)
4.Compute Shader ( 计算着色器 ) :
运用GPU并行计算部分和渲染管线无关的内容,一般用于大量计算内容的场景时使用。
5.Ray Tracing Shader ( 光线跟踪着色器 ) :
用于光线追踪效果的实现,计算量大。(这里不过多展开,各位可以自行了解)
我们目前主要学习的是第二种模板“Unlit Shader”。
(Inspector窗口中的内容在下文中细讲)
3.两者的配合使用
创建好材质和shader后,先在Project窗口中选择这个材质,先对Inspector窗口中的内容进行一些大致的了解:
接下来是Shader相关:
三、ShaderLab的基本介绍和结构
上文中我们所提及的所有Shader模版都是基于ShaderLab的规则编写的,这些代码和常见的编程语言不同,初见可能会看不懂,接下来我们更进一步了解下ShaderLab是什么,以及他的基础结构。
1.ShaderLab是什么
在上文中,我们已经提到ShaderLab是Unity中自定义的一种语言体系,用于方便着色器开发使用,我们可以理解成Unity Shader对Shader的一层封装,而Unity Shader又提供了ShaderLab给开发者使用。
2.ShaderLab的基本结构
这里我们分为四个部分讲解:
1、Shader命名
2、Properties(属性)
3、SubShader(子着色器)
4、Fallback (回退)
下面这段代码是一个Shader最基本的结构(具体内容下文会有介绍):
//----------------------------------------------------1.Shader的名字
Shader "着色器名字"
{
//----------------------------------------------2.属性
Properties
{
//之后会在这里声明属性,可以在材质面板上看见
}
//-----------------------------------------------3.子着色器
SubShader//子着色器——用于显卡1
{
//设置渲染标签,渲染状态
Tags { "RenderType"="Opaque" }
LOD 100
//设置渲染通道
Pass
{
//设置渲染标签,渲染状态
CGPROGRAM//开始CG语言
//CG语言,编写顶点着色器,片元着色器等
ENDCG//结束CG语言
}
}
SubShader//第二个子着色器-用于显卡2(子着色器可以有多个)
{
//同上.....
}
//----------------------------------------------4.Fallback
Fallback "备用的Shader"//上面所有SubShader都显示失败后的备用Shader
}
上文中提到的模板,都是基于这个结构的。
四、Shader的命名
这一部分非常简单,可直接在Shader后的双引号中修改,用斜杠“ \ ”代表路径,之后可以在材质面板中根据你所命名的路径找到。不过要注意,路径中不能存在中文,如果存在中文在材质面板就会找不到你创建的路径和文件。(每个文件只能有一个Shader “ ” { } )
//每个文件只能有一个Shader “ ” { } ,这部分代码定义了整个着色器
Shader "MyShader/MyUnlitShader" //在这里直接更改路径
{
//............其他内容
}
现在随便选择一个材质,在inspector窗口中的Shader下拉框中就可以找到我创建的Shader:
在这里有一点要注意的是,我们在文件内的命名和文件名是可以不一致的,但是最好保持一致,不然在之后的项目开发中会难以维护,分不清文件名和路径名。
五、Shader的属性(Properties)
我们在编写Shader时会遇到很多数值需要更改调试,这和C#代码中一样,比如人物移动速度,跳跃高度等,我们会用public关键字或者[SerializeField]特性,在Inspector窗口中显示出来,方便我们调试。在Shader中我们会把这些公开属性在Properties中定义。
主要来说属性对我们的作用就是:用于在材质面板随时编辑,作为变量给SubShader ( 子着色器 )
使用。(和C#类似 公共变量可以在Inspector窗口显示,文件中所有的方法(函数)作为变量使用)
1. Shader属性的语法
Shader属性中有很多类型:数值(大部分语言中的float,int,....),向量(类似UnityEngine.Vector3
Vector3,Vector4),颜色(本质也是向量 Vector4 ( r, g, b, a) ),纹理贴图(模型的贴图,很好理解)。
属性的语法:
_Name("Display Name", type) = defaultValue[{options}]
//这是一个2D纹理的属性
_MainTex ("Texture", 2D) = "white" {}
//属性名:"_MainTex" 在材质窗口显示:"Texture" 类型:"2D纹理" 默认值:"白色(1,1,1,1)"
_Name: 属性名字 (Shader文件中用)
Display Name:材质面板上显示的名字(不可以使用中文 会乱码)
type:属性的类型(float,int,Vector一类的)
defaultValue:数值的默认值
{}:这是规则,记住这么写就行
关于属性的命名有一套默认规则:1.以“_”下划线开头 2.首字母大写
(1) 基本数值类型属性
数值类型有三种:
大部分内容和别的语言相似,有几点需要注意:1.整形在Inspector窗口中虽然可以输入带小数点的数值,但是内容不计算会强转为整形,2.float不需要加 “ f ”的后缀,否则会报错。
//顺带一提,这部分属性不需要加“{}”,否则会报错
//1.整形(整形在Inspector窗口中可以输入小数点,但是内部计算时会去除。)
_Name("Display Name", Int) = number
//2.浮点型
_Name("Display Name", Float) = number
//3.范围浮点型(会在Inspector窗口显示滑动条)
_Name("Display Name", Range(min,max)) = number
下图是在材质面板中的效果:
此外还有一些特性值得一提(Header,Space等在UnityC#中常用的内容就不提及了):
//Toggle:可以把数值变为开关 0为false 1为true
[Toggle]
//Enum:枚举,会生成一个下拉框(和C#中public或[SerializeField]相似)
[Enum(value1,0,value2,1,value3,2)]
//KeywordEnum:与Enum用法类似但功能更多,貌似可用流程控制(目前学习过程中没有用到,等熟练后补充)
[KeywordEnum(value1,value2,value3)]
//------------------下面这两个只能用在 RangeFloat 中----------------------
//PowerSlider:可以更改滑动条增加的曲度
[PowerSlider(Value)]
//IntRange:滑动条,但是值只可能为整数
[IntRange]
效果如下:
(2) 颜色和向量类型属性
向量和颜色的表示很相近,因为他们都是4个数值,向量为(x,y,z,w),颜色为(r,g,b,a)都是由四个数值组成。在颜色的属性中,表达颜色的数值均为0到1,而不是0到255:
向量中的数值则没有限制,语法如下:
//1.颜色 (和大部分别的设置不同,Shader属性中的颜色的rgba都是0-1,对应0-255)
_Name("Display Name", Color) = (value1,value2,value3,value4)
//2.向量
_Name("Display Name", Vector) = (value1,value2,value3,value4)
下图是在材质面板中的效果:
如果代码中填写的默认颜色在材质窗口中没有显示,可以尝试更改属性名称,或者手动在材质窗口赋值(不清楚什么原因)
这部分内容也有特性的内容:
//HDR:加在颜色前可以使其在材质窗口显示HDR的标志
[HDR]_Color("MyColor", Color) = (1 , 0.25 , 0 , 0.8)
(3) 纹理贴图类型属性
这部分主要有4种类型,和别的类型不同的是,这类属性需要在结尾加上“{ }”,还有一些特殊的默认值,语法入下:
//1.2D 纹理(常用,法线贴图,光照贴图,2D图标等)
_Name("Display Name", 2D) = "DefaultTexture"{}
//2.2DArray 纹理(纹理数组,顾名思义存储了多个2D纹理文件,但是不常用)
_Name("Display Name", 2DArray) = "DefaultTexture"{}
//3.Cube map texture纹理(立方体纹理,正方体六个面的贴图,常用与天空盒和反射探针)
_Name("Display Name", Cube) = "DefaultTexture"{}
//4.3D纹理(包含3D信息,不常用,用于制作体积雾,体积云等效果)
_Name("Display Name", 3D) = "DefaultTexture"{}
材质面板的效果:
上面的代码中 “ ” 双引号里没有任何数值,代表默认的纹理为空,在材质窗口中我们可以看见确实显示None(XXX Texture),下面是一些Shader纹理的默认值:
1.空(不填写内容):默认贴图为空
2.white:默认贴图为白色 (实则就是RGBA为1,1,1,1的贴图)
3.black:默认贴图为黑色 (RGBA:0,0,0,1)
4.gray:默认贴图为灰色 (RGBA:0.5,0.5,0.5,1)
5.red:默认贴图为红色 (RGBA:1,0,0,1)
6.bump:默认贴图为法线图 (RGBA:0.5,0.5,1,1,多用于法线贴图的默认贴图)
(默认值的效果放在下面特性里一起展示)
这部分同样有一些特性供我们使用:
//NoScaleOffset:隐藏材质面板显示的纹理参数(隐藏Tiling和Offset)
[NoScaleOffset] _2DTextureNoScaleOffset("My2DTextureNoScaleOffset", 2D) = "white"{}
//Normal:提醒用户这里需要使用法线贴图
[Normal] _2DTextureNormal("My2DTextureNormal", 2D) = "red"{}
六、Shader的子着色器(SubShader)
一个Shader文件可能有多个SubShader语句块,Unity在显示一个物体时会按顺序检测这些SubShader语句块,直到第一个可以在当前显卡运行的SubShader,SubShader的作用就是为了避免在某些设备上无法显示的问题,以适应相对低性能的设备。而且SubShader决定了物体最终的渲染效果。
1.SubShader的基本构成
一个SubShader由三个主要的部分构成:
1.渲染标签(Tags):主要用于决定什么时候和如何对物体进行渲染。
2.渲染状态(Commands):用来确定渲染的剔除方式、深度测试等内容。
3.渲染通道(Pass):用于编写着色器代码的地方,之后要提到的CG语言就在这部分中编写 (需要注意的是一个子着色器中必须有一个,且可以有多个渲染通道(Pass),但是每有一个渲染通道就会让物体执行一次渲染,为了减少性能开销,我们需要尽可能的减少Pass的数量)
这里是代码的基本结构:
Shader "Unlit/NewUnlitShader"//文件名称
{
Properties//属性部分
{
_MainTex ("Texture", 2D) = "white" {}//2D纹理属性
}
//--------------------SubShader的内容---------------------
SubShader//子着色器
{
Tags { "RenderType"="Opaque" }//渲染标签(Tags)
LOD 100//渲染状态
Pass//渲染通道(Pass)
{
CGPROGRAM //CG语言开始标记
//CG语言内容
ENDCG //CG语言结束标记
}
Pass//第二个渲染通道
{
//......
}
}
//--------------------SubShader的内容---------------------
}
2.渲染标签(Tags)的语法结构
Tags语法如下,渲染标签都是由键值对的形式出现,键和值都是字符串形式,接下来我们介绍的标签可以告诉Unity我们对渲染这个物体的部分需求。我们要学习的就是Unity提供给我们的部分渲染标签,及其预先定义好的一些数值。
//标签
Tags
{
"标签名1" = "标签值1"
"标签名2" = "标签值2"
"标签名3" = "标签值3"
//....
}
需要注意的是,在本小节 ("渲染标签(Tags)的语法结构") 中介绍的内容只能在SubShader中使用,在下文中的渲染通道(Pass)中也有自己专门的标签可以使用,不要搞混。
(1)渲染队列(Queue)
渲染队列(Queue):主要用于确定物体的渲染的先后顺序。这个数值在材质面板也有显示(Render Queue),其数值越大则越后渲染。
其语法结构代码如下:
//渲染队列(Queue)
Tags{ "Queue" = "标签值" }
下面是一些Unity预先定义好的标签值:
①Background(背景) (Render Queue:1000):
#此渲染队列在任何其他队列之前进行渲染,一般用于天空盒之类的背景
Tags{ "Queue" = "Background" }
(文中"#"仅为了使代码块中的文字更加明显,不是条件编译,也没有特殊含义(csdn的代码块中注释文字颜色会变淡))
②Geometry(几何) (Render Queue:2000):
#默认的渲染队列,不透明几何体使用此队列
Tags{ "Queue" = "Geometry" }
③AlphaTest(透明测试) (Render Queue:2450):
#有透明通道的,经过alpha测试的几何体使用此队列
#当所有Geometry队列实体绘制完后再绘制AlphaTest队列,效率更高
Tags{ "Queue" = "AlphaTest" }
④Transparent(透明) (Render Queue:3000):
#任何alpha混合的东西(即不写入深度缓冲区的着色器)都应该放在这里,就是透明或半透明的物体,常用于玻璃,粒子特效等
Tags{ "Queue" = "Transparent" }
⑤.Overlay(覆盖) (Render Queue:4000):
#此渲染队列用于叠加效果。最后渲染的任何内容都应该放在此处(后处理,例如镜头光晕)
Tags{ "Queue" = "Overlay" }
标签名 | 值 | 功能 | Render Queue值 |
---|---|---|---|
Queue | Background(背景) | 背景渲染队列 | 1000 |
Geometry(几何) | 几何体渲染队列 | 2000 | |
AlphaTest(透明测试) | AlphaTest 渲染队列 | 2450 | |
Transparent(透明) | 透明渲染队列 | 3000 | |
Overlay(覆盖) | 覆盖渲染队列 | 4000 |
如果有自定义渲染队列的需求也有对应的解决方案:基于上文中提到的Unity预先定义好的标签值进行加减。
⑥自定义队列
例如我们想要得到Render Queue值为1500的渲染队列,我们可以这样表示:
#“自定义队列一般用于一些特别的渲染,例如我们需要再两个标签之间渲染一个物体,就需要用到自定义功能”
Tags{ "Queue" = "Background+500" }//Background的1000加上我们自己写的500 就是我们所需的1500
//或是
Tags{ "Queue" = "Geometry-500" } //Geometry的2000再减去500 也可以得到我们所需的1500
这里有一点要特别注意:在加减的时候不能加空格,例如"Geometry - 500"是不合法的,必须为"Geometry-500"。
(2)渲染类型(RenderType)的语法结构
渲染类型(RenderType):对着色器进行分类,不会对Shader的使用和渲染过程造成印象。主要是配合之后使用替代渲染的方法。
可以使用摄像机中的( Camera.SetReplacementShader( ) )方法,将指定的渲染类型替换成别的着色器。
语法和上文中渲染队列相似:
//渲染类型(RenderType)
Tags{ "RenderType" = "标签值" }
同样,预先定义好的标签值的内容如下:
①常用的几个:
//1.Opaque(不透明的)
#最常用的标签,常用于不透明、自发光、反射等
Tags{ "RenderType" = "Opaque" }
//2.Transparent(透明的)
#用于半透明的Shader,玻璃,粒子等
Tags{ "RenderType" = "Transparent" }
//3.TransparentCutout(透明切割)
#用于透明测试Shader,例如两个通道的植被着色器
Tags{ "RenderType" = "TransparentCutout" }
//4.Background(背景)
#常用于天空盒
Tags{ "RenderType" = "Background" }
//5.Overlay(覆盖)
#用于GUI纹理,镜头光晕等
Tags{ "RenderType" = "Overlay" }
②用于地形系统中的:
//6.TreeOpaque
#用于地形系统中的树干
Tags{ "RenderType" = "TreeOpaque" }
//7.TreeTransparentCutout
#用于地形系统中的树叶
Tags{ "RenderType" = "TreeTransparentCutout" }
//8.TreeBillboard
#用于地形系统中的Billboarded树
Tags{ "RenderType" = "TreeBillboard" }
//9.Grass
#用于地形系统中的草
Tags{ "RenderType" = "Grass" }
//10.GrassBillboard
#用于地形系统中的Billboarded草
Tags{ "RenderType" = "GrassBillboard" }
标签名 | 值 | 作用 |
RenderType | Opaque(不透明的) | 最常用的标签,常用于不透明、自发光、反射等 |
Transparent(透明的) | 用于半透明的Shader,玻璃,粒子等 | |
TransparentCutout(透明切割) | 用于透明测试Shader,例如两个通道的植被着色器 | |
Background(背景) | 常用于天空盒 | |
Overlay(覆盖) | 用于GUI纹理,镜头光晕等 | |
TreeOpaque | 用于地形系统中的树干 | |
TreeTransparentCutout | 用于地形系统中的树叶 | |
TreeBillboard | 用于地形系统中的Billboarded树 | |
Grass | 用于地形系统中的草 | |
GrassBillboard | 用于地形系统中的Billboarded草 |
(3)禁用批处理(DisableBatching)
为了解决部分情况在使用批处理模式会出现的坐标转换问题,我们可以使用标签来控制次SubShader是否禁用批处理。(默认值为false(不禁用批处理))
这对于执行对象空间操作的着色器程序十分有用。动态批处理会将所有几何体都变换为世界空间,这意味着着色器程序无法再访问对象空间。因此,依赖于对象空间的着色器程序不会正确渲染。为避免此问题,请使用此子着色器标签阻止 Unity 应用动态批处理。(官方文档)
//禁用批处理
Tags{ "DisableBatching" = "True" }
//不禁用批处理(默认值)
Tags{ "DisableBatching" = "False" }
(4) 禁用阴影投影(ForceNoShadowCasting)
标签阻止子着色器中的几何体投射(有时是接收)阴影。(确切行为取决于渲染管线和渲染路径,很好理解,禁用后使用此SubShader的物体将不再投射阴影。(默认值为false(不禁用阴影投影))
//不投射阴影
Tags{ "ForceNoShadowCasting" = "True" }
//投射阴影(默认值)
Tags{ "ForceNoShadowCasting" = "False" }
(5) 忽略投影机(IgnoreProjector)
物体是否受到投影仪(Projector)的投射。这对于排除投影器不兼容的半透明的物体多半很有用。
投影仪相关内容:Projector - Unity 手册
//忽略Projector(一般半透明Shader需要开启该标签)
Tags{ "IgnoreProjector" = "True" }
//不忽略Projector(默认值)
Tags{ "IgnoreProjector" = "False" }
(6) 是否与精灵兼容(CanUseSpriteAtlas)
简单来说就是该着色器是否可用于精灵,当值为False时,将会在Inspector窗口中提示。(默认值为True(兼容)) (Sprite Packer在 Unity 2020.1 和更新版本中已弃用,所以这部分应该不是重点)
//SubShader和精灵相兼容
Tags{ "CanUseSpriteAtlas" = "True" }
//SubShader和精灵不兼容
Tags{ "CanUseSpriteAtlas" = "False" }
(7)预览类型(PreviewType)
用于改变此SubShader在 材质窗口中预览的形状。(默认值为 Sphere 球形)
//球形(Sphere,这个是默认值)
Tags{ "PreviewType" = "Sphere" }
//平面(Plane)
Tags{ "PreviewType" = "Panel" }
//天空盒(Skybox)
Tags{ "PreviewType" = "SkyBox" }
3.渲染状态(Commands)的语法结构
也称为渲染命令,通过渲染状态来可以用于确定渲染时的剔除方式、深度测试方式、混合方式等等内容,渲染状态的语法相对简单 “渲染状态关键词+空格+状态类型” 多个状态可以用空格隔开
更多内容见:ShaderLab:命令 - Unity 手册
//渲染状态
渲染状态 状态类型
//例如
LOD 100
(1)剔除方式(Cull)
一个几何物体有多个剔除方式:背面剔除、正面剔除、不剔除三种,所谓剔除其实就是不渲染,对于玩家看不到的部分可以不渲染以减少计算量,从而增加性能。具体情况按照需求确定。(默认是背面剔除)
Cull back //背面剔除(模型的背面不渲染,此状态为默认值)
Cull front //正面剔除(模型正面不渲染)
Cull off //不剔除(模型正反都渲染)
在Unity中区别如下:
各位可以在unity中新建Quad,自己添加Shader自己尝试,效果比较明显。
(2)模板测试(Stencil)
模板缓冲区为帧缓冲区中的每个像素存储一个 8 位整数值。我们可以用这个帧缓冲中的值和给定的参数值进行比较。(这个过程叫做模板测试)如果通过,正常进入下一步渲染流程。如果不通过就直接丢弃这部分的像素。
模板测试一般用于处理一些特殊效果(可以理解为Photoshop中的蒙版遮罩),比如传送门,镜子等。语法如下:
Stencil
{
——————————————————————常用——————————————————————
Ref <ref> // 参考值(上文中提到的8位整形) 默认值为 0
//compareOperation 中模板缓冲区的当前内容与此值进行比较。
//决定readMask writeMask 进行的是读取操作还是写入操作。
ReadMask <readMask> // 读掩码 GPU 在执行模板测试时使用此值作为遮罩。
WriteMask <writeMask> // 写掩码 GPU 在写入模板缓冲区时使用此值作为遮罩。
Comp <comparisonOperation> // 比较操作 默认值为 Always 像素模板测试时执行的比较操作
Pass <passOperation> // 模板操作 默认值为 Keep 当像素通过模板测试和深度测试时,GPU 对模板缓冲区执行的操作
Fail <failOperation> // 模板操作 默认值为 Keep 没有通过模板测试时进行的操作
ZFail <zFailOperation> // 模板操作 默认值为 Keep 通过模板测试却没有通过深度测试时进行的操作
————————————————————不常用——————————————————————
CompBack <comparisonOperationBack> // 比较操作 默认值为 Always
PassBack <passOperationBack> // 模板操作 默认值为 Keep
FailBack <failOperationBack> // 模板操作 默认值为 Keep
ZFailBack <zFailOperationBack> // 模板操作 默认值为 Keep
CompFront <comparisonOperationFront> // 模板操作 默认值为 Keep
PassFront <passOperationFront> // 模板操作 默认值为 Keep
FailFront <failOperationFront> // 模板操作 默认值为 Keep
ZFailFront <zFailOperationFront> // 模板操作 默认值为 Keep
}
#上面的这些内容都是可选的,unity会根据这里你给定参数配置模板缓冲区。
#这里只介绍了几个常用的数值
其中比较操作的数值如下(默认值为Always的数值):
值 | Rendering.CompareFunction 枚举中的对应整数值 | 功能 |
---|---|---|
Never | 1 | 从不渲染像素。 |
Less | 2 | 在参考值小于模板缓冲区中的当前值时渲染像素。 |
Equal | 3 | 在参考值等于模板缓冲区中的当前值时渲染像素。 |
LEqual | 4 | 在参考值小于或等于模板缓冲区中的当前值时渲染像素。 |
Greater | 5 | 在参考值大于模板缓冲区中的当前值时渲染像素。 |
NotEqual | 6 | 在参考值与模板缓冲区中的当前值不同时渲染像素。 |
GEqual | 7 | 在参考值大于或等于模板缓冲区中的当前值时渲染像素。 |
Always | 8 | 始终渲染像素。 |
其中模板操作的数值如下(默认值为Keep的数值):
值 | Rendering.StencilOp 枚举中的对应整数值 | 功能 |
---|---|---|
Keep | 0 | 保持模板缓冲区的当前内容。 |
Zero | 1 | 将 0 写入模板缓冲区。 |
Replace | 2 | 将参考值写入缓冲区。(上文中的ref值) |
IncrSat | 3 | 递增缓冲区中的当前值。如果该值已经是 255,则保持为 255。 |
DecrSat | 4 | 递减缓冲区中的当前值。如果该值已经是 0,则保持为 0。 |
Invert | 5 | 将缓冲区中当前值的所有位求反。 |
IncrWrap | 7 | 递增缓冲区中的当前值。如果该值已经是 255,则变为 0。 |
DecrWrap | 8 | 递减缓冲区中的当前值。如果该值已经是 0,则变为 255。 |
(3)深度缓冲(ZWrite)
从这部分内容开始,会涉及到模型测试,深度测试,混合模式等内容,之前提到的高度可配置部分就是这里,如果不太了解这部分工作原理可以查看我的另外一篇文章:【Unity】Unity Shader入门学习笔记_1.5 逐片元操作扩展
主要作用是用于决定使用此SubShader的物体是否写入深度缓冲,一般在制作类似透明效果时会设置为不写入。( 默认值为 On (写入) )
ZWrite On //写入深度缓冲(默认值为这个状态)
ZWrite Off //不写入深度缓冲
(4)深度测试(ZTest)
主要用于设置深度测试的具体方式,主要目的是为了让物体以正确的顺序渲染,从而表现出遮挡和透视的效果。( 默认值为 LEqual (小于等于) )
ZTest Less //小于
ZTest Greater //大于
ZTest LEqual //小于等于(默认值为这个状态类型)
ZTest GEqual //大于等于
ZTest Equal //等于
ZTest NotEqual //不等于
#Unity会根据上面的状态类型来选择 如何和深度缓冲中的值比较,从而通过测试,写入到深度缓冲中
#假如此SubShader设置的值为LEqual,即小于等于当前深度缓冲中的值,就通过测试,写入到深度缓冲中
一般只有在想要完成特殊效果时才会更改这部分内容。 例如透明物体会更改为less等。
(5)混合方式(Blend)
用于设值图像的混合方式,例如线性减淡,正片叠底,若不设施则不混合。语法入下:
签名 | 功能 |
---|---|
Blend <state> | 禁用默认渲染目标的混合。这是默认值。 |
Blend <render target> <state> | 如上,但针对给定的渲染目标。 |
Blend <source factor> <destination factor> | 启用默认渲染目标的混合。设置 RGBA 值的混合系数。 |
Blend <render target> <source factor> <destination factor> | 如上,但针对给定的渲染目标。 |
Blend <source factor RGB> <destination factor RGB>, <source factor alpha> <destination factor alpha> | 启用默认渲染目标的混合。为 RGB 和 Alpha 值设置单独的混合系数。 |
Blend <render target> <source factor RGB> <destination factor RGB>, <source factor alpha> <destination factor alpha> | 如上,但针对给定的渲染目标。 |
其中state,render target等参数功能如下:
参数 | 值 | 功能 |
---|---|---|
render target | 整数,范围 0 到 7 | 渲染目标索引。 |
state | Off | 禁用混合。 |
factor | One | 此输入的值是 one。该值用于使用源或目标的颜色的值。 |
Zero | 此输入的值是 zero。该值用于删除源或目标值。 | |
SrcColor | GPU 将此输入的值乘以源颜色值。 | |
SrcAlpha | GPU 将此输入的值乘以源 Alpha 值。 | |
DstColor | GPU 将此输入的值乘以帧缓冲区的源颜色值。 | |
DstAlpha | GPU 将此输入的值乘以帧缓冲区的源 Alpha 值。 | |
OneMinusSrcColor | GPU 将此输入的值乘以(1 - 源颜色)。 | |
OneMinusSrcAlpha | GPU 将此输入的值乘以(1 - 源 Alpha)。 | |
OneMinusDstColor | GPU 将此输入的值乘以(1 - 目标颜色)。 | |
OneMinusDstAlpha | GPU 将此输入的值乘以(1 - 目标 Alpha)。 |
上面的内容可能有些复杂(更详细的内容可查看官方API文档),不过我们只需要记住下面这些常用的混合类型就足够应对大部分情况了:
Blend Off //不混合(默认状态为这个)
Blend One One //线性减淡
Blend SrcAlpha OneMinusSrcAlpha //正常透明混合
Blend OneMinusDstColor One //滤色
Blend DstColor Zero //正片叠底
Blend DstColor SrcColor //X光片效果
Blend One OneMinusSrcAlpha //透明度混合
(6)细节级别(LOD)
Levels of Detai,用于控制细节级别级别,在不同距离下使用不同的渲染方式处理
LOD 100
(7)颜色遮罩(ColorMask)
设置颜色通道写入遮罩,以防止 GPU 写入渲染目标中的通道。
//语法
ColorMask <channels> <render target>
//示例
ColorMask RGB 2
//render target 渲染索引目标
//channels 启用的通道 可以用R、G、B 和 A 的任意组合,无空格。例如:RB
(如果填入"0" 代表启用对 R、G、B 和 A 通道的颜色写入)
(8)抓屏(GrabPass)
GrabPass 是一个创建特殊类型通道的命令,该通道将帧缓冲区的内容抓取到纹理中。在后续通道中即可使用此纹理,从而执行基于图像的高级效果。
多半写在Pass(渲染通道)前,抓取屏幕纹理存储到变量中,然后提供给Pass进行处理,可用于制作类似滤镜的效果。语法如下:
//抓屏
GrabPass
{
"_BackgroundTexture"
}
Pass//渲染通道(如果不理解可以先看下文Pass部分)
{
//这是CG语言中的内容,不理解没关系,之后的文章中再详细介绍,
//这里先记住有这个东西可以获取屏幕纹理就好
CGPROGRAM //开始CG代码
sampler2D _BackgroundTexture;//拿到GrabPass中抓取的材料,
ENDCG //结束CG代码
}
以上这些状态不仅可以在SubShader中使用,同样也能在下文中讲到的Pass(渲染通道)中使用。不同的是在SubShader中使用会影响到之后所有的Pass,而在Pass中使用只会运用于当前的Pass。
实际的渲染状态远远不止这些:ShaderLab:命令 - Unity 手册官方文档中有很多说明。
4.渲染通道(Pass)的语法结构
通道是 Shader 对象的基本元素。它包含设置 GPU 状态的指令,以及在 GPU 上运行的着色器程序。简单的 Shader 对象可能只包含一个通道,但更复杂的着色器可以包含多个通道。您可以为 Shader 对象不同部分定义单独的通道实现不同的工作方式;例如,需要更改渲染状态、不同的着色器程序或不同的 LightMode
标签(Pass的渲染标签中有提到)的部分。
(出自:ShaderLab:定义一个通道 - Unity 手册)
简单来说就是如果需要实现在不同的时刻对同一个对象实现不同的渲染效果。就可以用到通道,但这会使物体多渲染一次,所以要尽可能减少Pass的数量,语法如下:
Pass
{
//1.通道名称
Name "ExamplePassName" // 此处是 Pass 通道名称
//2.渲染标签
Tags { "ExampleTagKey" = "ExampleTagValue" } // 此处是 ShaderLab 渲染标签
//3.渲染状态
ExampleCommandsKey ExampleCommandsValue // 此处是 ShaderLab 渲染状态
//4.CG 或 HLSL 代码
//使用HLSLPROGRAM CGPROGRAM HLSLINCLUDE CGINCLUDE添加着色器语言部分(之后再提) // 此处是 CG 或 HLSL 代码。
}
1.Pass的名称
我们对Pass命名的最主要目的是为了利用UsePass命令在其他Shader中复用此Pass代码。通道命名和UsePass命令语法如下:
//通道所在的Shader文件
Shader "Examples/ContainsNamedPass"
{
SubShader
{
Pass
{
//通道的命名
Name "ExamplePassName" //Pass名为ExamplePassName,unity自动大写后为EXAMPLEPASSNAME
}
#Unity会将Pass的名字转换为大写字母,所以在接下来的UsePass命令中需要把Pass的名字大写。
}
//UsePass的Shader文件
Shader "Examples/UsesNamedPass"
{
SubShader
{
//UsePass命令
UsePass "Examples/ContainsNamedPass/EXAMPLEPASSNAME" //前面是路径后面是文件名(要大写)
}
}
2.Pass中的渲染标签
在上文SubShader中,我们已经提到了很多的渲染标签,Pass中的渲染标签语法和SubShader中的一致,但是其标签不能在Pass中使用,这里就介绍部分专门供Pass使用的标签(这部分有些抽象,之后用到再详细解释):
(1)LightMode:
主要作用是确定Pass在哪个阶段,将着色器代码分配给什么渲染阶段。
Tags{ "LightMode" = "Always" }
其他的数值如下:
值 | 功能 |
---|---|
Always | 始终渲染;不应用任何光照。这是默认值。 |
ForwardBase | 在前向渲染中使用;应用环境光、主方向光、顶点/SH 光源和光照贴图。 |
ForwardAdd | 在前向渲染中使用;应用附加的每像素光源(每个光源有一个通道)。 |
Deferred | 在延迟渲染中使用;渲染 G 缓冲区。 |
ShadowCaster | 将对象深度渲染到阴影贴图或深度纹理中。 |
MotionVectors | 用于计算每个对象的运动矢量。 |
PrepassBase | 用于旧版延迟光照,渲染法线和镜面反射指数。 |
PrepassFinal | 用于旧版延迟光照;通过组合纹理、光照和发光来渲染最终颜色。 |
ShadowCaster | 将对象深度渲染到阴影贴图或深度纹理中。 |
Vertex | 用于旧版顶点光照渲染(当对象不进行光照贴图时);应用所有顶点光源。 |
VertexLMRGBM | 用于旧版顶点光照渲染(当对象不进行光照贴图时),以及光照贴图为 RGBM 编码的平台(PC 和游戏主机)。 |
VertexLM | 用于旧版顶点光照渲染(当对象不进行光照贴图时),以及光照贴图为双 LDR 编码的平台上(移动平台)。 |
Meta | 在常规渲染期间不使用此通道,仅用于光照贴图烘焙或实时全局光照。 |
(2)RequireOptions:
在内置渲染管线中,RequireOptions
通道标签根据项目设置启用或禁用一个通道。可以理解为设置在固定的状态下才能使用该通道。(网上的文章中 我只找到这一个状态值)
//仅当 Quality 窗口中开启了 Soft Vegetation 时才渲染此通道
Tags { "RequireOptions" = "SoftVegetation" }
(3)PassFlags:
在内置渲染管线中,使用 PassFlags
通道标签来指定 Unity 提供给通道的数据。Pass可通过一些标签来改变Pass传递数据的方式。
Tags{ "PassFlags" = "OnlyDirectional" }
仅在内置渲染管线中且渲染路径设置为 Forward,LightMode
标签值为 ForwardBase
的通道中有效。此标志的作用是仅允许主方向光和环境光/光照探针数据传递到着色器,这意味着非重要光源的数据将不会传递到顶点光源或球谐函数着色器变量,
差不多意思就限制部分光源信的传递,只收取部分的信息。
3.Pass中的渲染状态
这部分其实和上文中SubShader提到的几乎一样,需要注意的就是SubShader中使用会影响到之后所有的Pass,而在Pass中使用只会运用于当前的Pass。
4.着色器代码部分
这部分其实就是Shader主要编写的内容(顶点/片元着色器的内容),再之后的文章中会详细讲解,主要用到的就是CG语言或者是HLSL语言。
七、Fallback (回退)
我们在上文中提到了ShaderLab中有很多个子着色器(SubShader),他们会从上到下依照顺序逐个执行知道找到第一个可以正常执行的SubShader来渲染对象。
但是很有可能,你写的所以SubShader都不能在某个设备上顺利执行,这时候就要用到Fallback,他的作用就是防止这样的事情发生以至于物体无法正常渲染,可以让物体以Fallback中设置的最低级Shader将物体渲染出来,说白了就是防止物体在特别底端的设备上无法显示而准备的保底工作。语法如下:
Shader "Examples/ExampleFallback"
{
SubShader
{
//.......
}
// Fallback "ShaderName" 设置Fallback的shader,在ShaderName中写入你想要作为备用的Shader
Fallback "ExampleOtherShader"
//Fallback Off 就是不使用备用shader,代表你对你的代码非常自信,可以在几乎所有设备上运行,不需要备用Shader
Fallback Off
}
八、Shader主要所编写的内容
在unity shader中我们主要编写的形式有这三种:1.表面着色器 (可控性低) 2.顶点/片元着色器(重点) 3.固定函数着色器 (已废弃,仅作了解)
1.表面着色器
表面着色器(Surface Shader) 是unity创造的一种着色器类型,它的本质是对顶点/片元着色器的一层封装。可以用少量代码完成更多工作,而且它帮助我们处理了很多有关光照的内容,无需自己计算。但是表面着色器的性能消耗较大,可控性比较低。
在创建Shader时点选Standard Surface Shader就可以创建一个表面着色器的模板,表面着色器特殊的点是,他的代码块是直接写在SubShader中的,而不是写在Pass中。
总结一下特点:1.直接在SubShader中编写代码,不需要使用Pass 2.可以使用CG或HLSL语言编写 3.代码量少,可控性低,性能消耗高 5.适合用于光源复杂的着色器(适合用于PC和主机平台)
2.顶点\片元着色器
顶点片元着色器(Vertex and fragment shader) 可以通过在创建Shader时点选Unlit Shader来获得,它的代码是编写在Pass语句中的,上文中我们讲解用的就是顶点片元着色器。虽然代码量较多,但是编写灵活度高,可控性强,渲染效率也更加可控。
总结一下特点:1.在Pass语句中编写着色器代码 2.可以使用CG或HLSL语言编写 3.代码量较多,灵活度高,性能消耗更加可控,可以实现更多细节 4.用于光照处理较少,自定义渲染效果更多(多用于移动平台)
3.固定函数着色器
用于部分老设备,例如(DX7.0,OpenGl1.5等)不支持可编程着色器。只能实现部分简单的效果 (这部分做了解即可)
总结下特点:1.在Pass语句中编写着色器代码 2.使用ShaderLab来编写命令(于上面两个不同)
总结
本文简单的阐述了整个ShaderLab的相关语法和其中部分属性值或命令,下篇文章可能会更新于CG语言相关的内容,以补全这次在渲染通道中没有讲到的部分。非常感谢有人能看到这里,欢迎各位大佬指正错误,谢谢。