ShaderLab语法基础

ShaderLab 语法不区分大小写。真正意义上的Shader代码则是在CGPROGRAM代码块中编写的

Shader 的组织结构
通常情况下,Shader 的大致结构如下:

Shader "Name"
{
    Properties
    {
        //开放到材质面板的属性
    }

    SubShader
    {
        //顶点-片段着色器
        //或者表面着色器
        //或者固定函数着色器
    }

    SubShader
    {
        //更加精简的版本
        //为了在旧的设备上运行
    }

    ...
    Fallback"Name"
}

Shader中可以 编写多个子着色器(SubShader),但至少需要一个。

如果编写的是顶点-片段着色器(Vertex-Fragment Shader),每个着色器中还会包含一个甚至多个Pass。在运行的过程中,如果某个子着色器能够在当前GPU上运行,那么该子着色器内的所有Pass会依次执行,每个Pass输出的结果会以指定的方式与上一步的结果进行混合,最终输出。

表面着色器不会再嵌套Pass。系统在编译表面着色器的时候,会自动生成多个对应的Pass,最终编译出来的Shader本质上就是顶点-片段着色器。

Shader 的名称
Shader 程序的第一行代码用来声明该Shader的名称以及所在路径。
名称就是指该Shader在选择使用的时候所显示的名称,而路径则是指Shader在材质面板上Shader下拉列表里的保存路径。
例如:
Shader "Custom/Simple Shader"
这一行代码的意思是:这个Shader位于Custom路径里,名称为Simple Shader。

Properties
开放的属性 不同类型的变量或贴图等资源
Unity Shader 的属性主要分为三大类:数值、颜色和向量、纹理贴图,每一条属性都是按照以下语法进行定义的:
_Name("Display Name",type) = defaultValue[{options}]

  1. _Name: 属性的名字,为了方便获取,通常在名字的最前面加一个下划线,后续在整个Shader中都将使用这个名称来获取该属性。
  2. Display Name:在材质面板中显示出来的名称。
  3. type:属性的类型。
  4. defaultValue:将Shader指定给材质的时候初始化的默认值。

数值类属性
Unity Shader 的数值类属性基本都是浮点型(Float)数据,虽然Unity提供了整数(Int)数据,但是在编译的时候最终都会转化为浮点型数据。
数值类型的数据有以下两种:

{
    name("display name", Color) = (number,number.number,number)
    name("display name", Vector) = (number1,number2,number3,number4)
}

Color 是颜色类型的数据,由R、G、B、A四个分量定义,在材质面板上显示为取色器(Color Picker)。

需要注意的是:Ps处理图片一般会使用8位深度图,每个通道的亮度最大值为2的8次方=256,由于从0开始计算,因此数值范围是[0,255]。而在Shader中,每个分量的数值范围是[0,1],于是它们之间需要按照对应关系进行线性映射,当数值为0,颜色为黑色,当数值为1,中间部分以线性关系对应。

Vector 是向量类的属性,是一个四维数组,在材质面板上作显示为4个连续的数值输入框,分别为X、Y、Z、W。

颜色和向量类型属性的默认值都是由括号括住的4个浮点数组成,其中颜色属性每个分量的数值区间为[0,1],例如中度灰:(0.5,0.5,0.5,1),而向量属性没有范围限制。

纹理贴图类型

{
    name("display name",2D) = "defaulttexture"{}
    name("display name",Cube) = "defaulttexture"{}
    name("display name",3D) = "defaulttexture"{}
}
  1. 2D属性是纹理类属性中最常使用的,漫反射贴图、法线贴图等都属于2D类型。

  2. Cube全称Cube map texture(立方体纹理),是由前、后、左、右、上、下6张有联系的2D贴图拼成的立方体,主要用作反射,例如Skybox和Reflection Prob。

  3. 3D纹理只能被脚本创建,在实际使用中很少。

2D类型的属性,默认值可以可以为空字符串,也可以是内置的表示颜色的字符串:“white”(RGBA:1,1,1,1),”black”(RGBA:0,0,0,0),”gray”(RGBA:0.5,0.5,0.5,0.5),”bump”(RGBA:0.5,0.5,1,0.5)和“red”(RGBA:1,0,0,0)。其中“bump”通常用于法线体贴图的默认值。

至于非2D类型的属性(Cube,3D,2DArray),默认值为空字符串。当材质没有指定Cubemap或者3D或者2DArray纹理的时候,会默认使用(RGBA:0.5,0.5,0.5,0.5)

所有纹理贴图类的属性最后都有一对空的花括号(是Unity 5.0之前的功能)。

所有类型属性汇总

Shader "Custom/Properties"
{
    Properties
    {
        _MyFloat("Float Property", Float) = 1            //浮点类型
        _MyRange("Range Property", Range(0,1)) = 0.1     //范围类型
        _MyColor("Color Property", Color) = (1,1,1,1)    //颜色类型
        _MyVector("Vector Property",Vector) = (0,1,0,0)  //向量类型
        _MyTex("Texture Property",2D) = "white" {}       //2D贴图类型
        _MyCube("Cube Property",Cube) = ""{}             //立方体贴图类型
        _My3D("3D Property",3D) = ""{}                   //3D贴图类型
    }
    SubShader
    {
         Pass
         {
            //pass中的代码
         }
    }
    FallBack "Diffuse"
}

Properties代码在Shader中并不是必须的。如果在实际编写过程中没有开放参数的必要,完全可以在Shader中省略Properties这一部分的代码。

SubShader

在Unity中,每一个Shader都会至少包含一个SubShader。当Unity想要显示一个物体时,它就会去检测这些SubShader,然后选择第一个能够在当前显卡运行的SubShader。

通常情况下,SubShader的大致结构如下所示:

SubShader
{
    //标签
    Tags{"TagName1" = "Value1" "TagName2" = "Value2" ...}

    //渲染状态
    Cull Back
    ...

    Pass
    {
       //第一个Pass
    }
    Pass
    {
       //第二个Pass
    }
    ...
}

每个SubShader都可以设置一个或者多个标签(Tags)和渲染状态(States),然后定义至少一个Pass。在SubShader中设置的渲染状态会影响到该SubShader中所有Pass,如果想要某些状态下不影响其他Pass,可以针对某个Pass单独设置渲染状态。但是需要主要的是,部分渲染状态在Pass中并不支持。

当Unity选择了某个SubShader来渲染某个物体的时候,SubShader中每定义一个Pass都会使这个物体执行一次渲染,当物体受到灯光影响的时候,渲染次数还会增加。所以考虑这方面的影响,应该尽可能地减少Pass的数量。当然,如果某些效果无法通过单个Pass来实现,那么只能使用多个Pass,但是这种情况一定要少出现。

                          Tags(标签)
                          States(状态)
SubShader         Pass - 1
                           Pass - 2
                          …. ….

SubShader的标签
**Tags{"TagNamel" = "Value1""TagName2" = "Value2"}**
标签通过键值对的形式进行声明,并且没有使用数量的限制。如果有需要,可以使用任意数量多个标签。

1.渲染队列
在SubShader中可以用Queue(队列)标签确定物体的渲染顺序,Unity预先定义了五种渲染队列

可以使用的渲染队列:

队列名称 描述 队列号
Background 最先执行渲染,一般用来渲染天空盒(Skybox)或者背景 1000
Geometry 非透明的几何体通常使用这个队列,当没有声明渲染队列的时候,Unity会默认使用这个队列。 2000
AlphaTest Alpha测试的几何体会使用这个队列,之所以从Geometry队列单独拆分出来,是因为当所有实体都绘制完之后再绘制Alpha测试会更高效 2450
Transparent 在这个队列的几何体按由远及近的顺序进行绘制,所有进行Alpha混合的几何体都应该使用这个队列,例如玻璃材质,粒子特效等 3000
Overlay 用来叠加渲染的效果,例如镜头光晕等,放在最后渲染 4000

除了使用Unity预定义的渲染队列, 使用者也可以自己指定一个队列,例如:
Tags{"Queue" = "Geometry + 1"}

这个队列的队列号其实就是2001,表示在所有非透明几何体绘制完成之后再进行绘制。

使用自定义的渲染队列在某些情况下非常有用,例如:透明的水应该在所有不透明几何体之后,透明几何体之前被绘制,所以透明水的渲染队列一般会使用”Queue” = “Transparent -1 “‘

2.渲染类型
RenderType(渲染类型)标签可以将Shader划分为不同的类别,用于后期进行Shader替换或者产生摄像机的深度纹理。

可以设置的渲染类型

类型名称 描述
Opaque 用于普通Shader,例如:不透明、自发光、反射、地形Shader
Transparent 用于半透明Shader,例如:透明、粒子
TransparentCutout 用于透明测试Shader,例如:植物叶子
Background 用于Skybox Shader
Overlay 用于GUI纹理、Halo、Flare Shader
TreeOpaque 用于地形系统中的树干
TreeTransparentCutout 用于地形系统中的树叶
TreeBillboard 用于地形系统中的Billboarded树
Grass 用于地形系统中的草
GrassBillboard 用于地形系统中的Billboarded草

3.禁用批处理

当使用批处理(Batching)的时候,几何体会被变换到世界空间,模型空间会被丢弃。这会导致某些使用模型空间的顶点数据的Shader最终无法实现所希望的效果。而开启DisableBatching(禁用批处理)可以解决这个问题。

禁用批处理有三个数值可以使用

  1. “DisableBatching” = “True”: 总是禁用批处理。
  2. “DisableBatching” = “False”: 不禁用批处理,这是默认数值。
  3. “DisableBatching” = “LODFading”: 当LOD效果激活的时候才会禁用批处理,主要用于地形上的树。

4.禁止阴影投射

在游戏中,有很多特效类的物体并不需要对其他物体产生投影,这个时候可以使用”ForceNoShadowCasting”(禁止阴影照射)标签来达到需要实现的效果。只要将这个标签的数值设为true,那么使用这个Shader的物体就不会对其他物体产生投射阴影了。

5.忽略Projector
如果不希望物体受到Projector(投影机)的投射,可以在Shader中添加IgnoreProjector标签。
它有两个数值可以使用:”True”和”False”,分别为忽略投射机和不忽略投射机。一般半透明的Shader都会开启这个标签。

6.其他标签
除了以上标签之外,Unity还提供了很多不常用的标签,例如CanUseSpriteAtlas、PreviewType。

Pass的渲染状态
如果想某些Pass的渲染状态不影响到其他的Pass,可以在该Pass中单独设置渲染状态。
在SubShader中使用会影响到该SubShader中的所有Pass。

可以设置的渲染状态(这些渲染状态在SubShader中同样被允许使用)

渲染状态 数值 作用
Cull Cull Back | Front | Off 设置多边形的剔除方式,有背面剔除、正面剔除、不剔除,默认为Back
ZTest ZTest(Less | Greater | LEqual | GEqual | Equal | NotEqual | Always) 设置深度测试的对比方式,默认为LEqual
ZWrite ZWrite On | Off 设置是否写入深度缓存,默认为On
Blend Blend sourceBlendMode destBlendMode 设置渲染图像的混合方式
ColorMask ColorMask RGB | A | 0 | 或者R、G、B、A的任意组合 设置颜色通道的写入蒙版,默认蒙版为RGBA,当设置为0时,则无法写入任何颜色。

FallBack
FallBack在所有SubShader之后进行定义。
当所有的SubShader都不能在当前显卡上运行的时候,就会运行Fallback定义的Shader。
语法如下:
Fallback "name"

最常用于Fallback的Shader为Unity为Unity内置的Diffuse。

如果觉得某些Shader肯定可以在目标显卡上运行,没有指定Fallback的必要,可以使用Fallback Off关闭Fall back 功能,或者直接什么都不写。

Author

TsingLoo

Posted on

2021-08-18

Updated on

2022-11-08

Licensed under

Comments