Unity Shader入门精要 第三章

  1. 1. 3. Unity Shader基础
  2. 2. 3.1.1 材质和Unity Shader
  3. 3. 3.1.2 Unity中的材质
  4. 4. 3.1.3 Unity中的Shader
  5. 5. 3.2 Unity Shader的基础:ShaderLab
  6. 6. 3.3 Unity Shader的结构
  7. 7. 3.3.1 给我们的Shader起个名字
  8. 8. 3.3.2 材质和Unity Shader的桥梁:Properties
  9. 9. 3.3.3 重量级成员:SubShader
  10. 10. 3.3.4 留一条后路:Fallback
  11. 11. 3.3.5 ShaderLab还有其他的语义吗
  12. 12. 3.4 Unity Shader的形式
  13. 13. 3.4.1 Unity的宠儿:表面着色器
  14. 14. 3.4.2 最聪明的孩子:顶点/片元着色器
  15. 15. 3.4.3 被抛弃的角落:固定函数着色器
  16. 16. 3.4.4 选择哪种Unity Shader形式
  17. 17. 3.5 本书使用的Unity Shader形式
  18. 18. 3.6 答疑解惑
  19. 19. 3.6.1 Unity Shader != 真正的Shader
  20. 20. 3.6.2 Unity Shader和CG/HLSL之间的关系

3. Unity Shader基础


3.1.1 材质和Unity Shader

  • 在Unity中,需要配合使用材质Material和Unity Shader才能达到需要的效果
  • 流程:
    1.创建一个材质
    2.创建一个Unity Shader,并把它赋给上一步创建的材质
    3.把材质赋给要渲染的对象
    4.在材质面板中调整Unity Shader的属性,以得到满意的效果

3.1.2 Unity中的材质

  • Unity中的材质需要结合一个GameObject的Mesh或者Particle System组件来工作,它决定了我们的游戏对象看起来是什么样子的

3.1.3 Unity中的Shader

  • 为了和前面通用的Shader语义进行区分,这里把Unity中的Shader文件统称为Unity Shader
  • Unity Shader和我们之前提及的渲染管线的Shader有很大不同
  • Unity中4种Shader模板:
    1.Standard Surface Shader:产生一个包含标准光照模型的表面着色器模板
    2.Unlit Shader:产生一个不包含光照的基本的顶点/片元着色器
    3.Image Effect Shader:实现各种屏幕后处理效果
    4.Compute Shader:产生一个特殊的Shader文件,旨在利用GPU的并行性来进行一些与常规渲染流水线无关的计算(此书不讨论)
  • Unity会显示Shader的相关信息:
    · 是否是一个表面着色器
    · 是否是一个固定函数着色器——Fixed Function Shader
    · 还有一些标签设置:是否会投射阴影、使用的渲染队列、LOD值
  • Show generated code
    · 打开一个新的文件,在该文件里面将显示Unity在背后为该表面着色器生成的顶点/片元着色器
    · 方便对生成的代码进行研究,需要复制到新的Unity Shader中才可保存
  • Compile and show code
    · 可以让开发者检查该Unity Shader针对不同图像编程接口编译成的Shader代码

3.2 Unity Shader的基础:ShaderLab

  • 在Unity Shader的帮助下,开发者只需要使用ShaderLab来编写Unity Shader文件就可以完成所有工作
  • 1
    2
    3
    4
    5
    6
    7
    Shader "ShaderName"
    {
    Properties{//属性}
    SubShader{//显卡A使用的子着色器}
    SubShader{//显卡B使用的子着色器}
    Fallback "VertexLit"
    }

3.3 Unity Shader的结构

3.3.1 给我们的Shader起个名字

  • 每一个Unity Shader文件的第一行都需要通过Shader语义来指定该Unity Shader的名字
  • 这个名字由一个字符串来定义,例如”MyShader”
  • 当为材质选择使用的Unity Shader时,这些名称就会出现在材质面板的下拉列表里
  • 通过在字符串中添加斜杠(”/“),可以控制Unity Shader在材质面板中出现的位置

3.3.2 材质和Unity Shader的桥梁:Properties

  • Properties语义块中包含了一系列属性(property),这些属性将会出现在材质面板中
  • 1
    2
    3
    4
    5
    6
    Properties
    {
    Name ("display name",PropertyType) = DefaultValue
    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,5.0)) = 3.0
  • Color —— (number,number,number,number) —— _Color (“Color”,Color) = (1,1,1,1)
  • Vector —— (number,number,number,number) —— _Vector (“Vecter”,Vector) = (2,3,6,1)
  • 2D —— “defaulttexture” {} —— _2D (“2D”,2D) = “” {}
  • Cube —— “defaulttexture” {} —— _Cube (“Cube”,Cube) = “white” {}
  • 3D —— “defaulttexture” {} —— _3D (“3D”,3D) = “black” {}

3.3.3 重量级成员:SubShader

  • 每一个Unity Shader文件可以包含多个SubShader语义块,但最少要有一个

  • 当Unity需要加载这个Unity Shader时,Unity会扫描所有的SubShader语义块,然后选择第一个能够在目标平台上运行的SubShader

  • 如果都不支持的话,Unity就会使用Fallback语义指定的Unity Shader

  • Unity提供这种语义的原因在于,不同的显卡具有不同的能力

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    SubShader
    {
    //可选的
    [Tags]
    //可选的
    [RenderSetup]
    Pass{}
    //Other Passes
    }

    · SubShader中定义了一系列Pass以及可选的状态([PenderSetup])和标签([Tag])设置
    · 每个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)是一个键值对(Key/Value Pair),它的键和值都是字符串类型

  • SubShader的标签类型:
    · Queue —— 控制渲染顺序,指定该物体属于哪一个渲染队列,通过这种方式可以保证所有的透明物体可以在所有不透明物体后面被渲染(详见第8章),我们也可以自定义使用的渲染队列来控制物体的渲染顺序

    1
    Tags{"Queue" = "Transparent"}

    · RenderType —— 对着色器进行分类,例如这是一个不透明的着色器,或是一个透明的着色器等。这可以被用于着色器替换(Shader Replacement)功能

    1
    Tags{"RenderType" = "Opaque"}

    · DisableBatching —— 一些SubShader在使用Unity的批处理功能时会出现问题,例如使用了模型空间下的坐标进行顶点动画(详见11.3节)。这时可以通过该标签来直接指明是否对该SubShader使用批处理

    1
    Tags{"DisableBatching" = "True"}

    · ForceNoShadowCasting —— 控制使用该SubShader的物体是否会投射阴影(详见8.4节)

    1
    Tags{"ForceNoShadowCasting" = "True"}

    · IgnoreProjector —— 如果该标签值为”True”,那么使用该SubShader的物体将不会受Projector的影响,通常用于半透明物体

    1
    Tags{"IgnoreProjector" = "True"}

    · CanUseSpriteAtlas —— 当该SubShader是用于精灵(sprites)时,将该标签设为”False”

    1
    Tags{"CanUseSpriteAtlas" = "False"}

    · PreviewType —— 指明材质面板将如何预览该材质。默认情况下,材质将显示为一个球形,我们可以通过把该标签的值设为”Plane””SkyBox”来改变预览类型

    1
    Tags{"PreviewType" = "Plane"}
  • Pass的标签类型
    · LightMode —— 定义该Pass在Unity的渲染流水线中的角色

    1
    Tags{"LightMode" = "ForwardBase"}

    · RequireOptions —— 用于指定当满足某些条件时才渲染该Pass,它的值是一个由空格分隔的字符串。目前,Unity支持的选项有:SoftVegetation。在后面的版本中,可能会增加更多的选项

    1
    Tags{"RequireOptions" = "SoftVegetation"}
  • 特殊的Pass
    · UsePass:如我们之前提到的一样,可以使用该命令来复用其他Unity Shader中的Pass
    · GrabPass:该Pass负责抓取屏幕并将结果存储在一张纹理中,以用于后续的Pass处理(详见10.2.2节)

3.3.4 留一条后路:Fallback

  • 紧跟在各个SubShader语义块后面的,可以是一个Fallback指令。它用于告诉Unity,”如果上面所有的SubShader在这块显卡上都不能运行,那么就使用这个最低级的Shader吧!”
  • 1
    2
    3
    Fallback "name"
    // 或者
    Fallback Off
    · 我们可以通过一个字符串来告诉Unity这个”最低级的Unity Shader”是谁
    · 也可以任性地关闭Fallback功能
  • Fallback还会影响阴影的投射

3.3.5 ShaderLab还有其他的语义吗

  • 就可以使用CustomEditor语义来扩展编辑界面
  • 还可以使用Category语义来对Unity Shader中的命令进行分组
  • 这些命令很少用到,本书不进行深讲

3.4 Unity Shader的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Shader "MyShader"
{
Properties
{
// 所需的各种属性
}
SubShader
{
// 真正意义上的Shader代码会出现在这里
// 表面着色器(Surface Shader)或者
// 顶点/片元着色器(Vertex/Fragment Shader)或者
// 固定函数着色器(Fixed Function Shader)
}
SubShader
{
// 和上个SubShader类似
}
}

3.4.1 Unity的宠儿:表面着色器

  • 表面着色器(Surface Shader)是Unity自己创造的一种着色器代码类型
  • 背后仍旧把它转换成对应的顶点/片元着色器
  • Unity为我们处理了很多光照细节,使得我们不需要再操心这些
  • 表面着色器被定义在SubShader语义块(而非Pass语义块)中的CGPROGRAM和ENDCG之间。原因是,表面着色器不需要开发者关心使用多少个Pass、每个Pass如何渲染等问题,Unity会在背后为我们做好这些事情
  • CGPROGRAM和ENDCG之间的代码是使用Cg/HLSL编写的,也就是说,我们需要把Cg/HLSL语言嵌套在ShaderLab语言中

3.4.2 最聪明的孩子:顶点/片元着色器

  • 在Unity中我们可以使用Cg/HLSL语言来编写顶点/片元着色器(Vertex/Fragment Shader)
  • 它们更加复杂,但灵活性也很高
  • 和表面着色器类似,顶点/片元着色器的代码也需要定义在CGPROGRAM和ENDCG之间,但不同的是,顶点/片元着色器是写在Pass语义块内,而非SubShader内的
  • 原因是,我们需要自己定义每个Pass需要使用的Shader代码

3.4.3 被抛弃的角落:固定函数着色器

  • 而对于一些较旧的设备,它们不支持可编程管线着色器,因此,这时候我们就需要使用固定函数着色器(Fixed Function Shader)来完成渲染
  • 可以看出,固定函数着色器的代码被定义在Pass语义中,这些代码相当于pass中的一些渲染设置
  • 对于固定函数着色器来说,我们需要完全使用ShaderLab的语法(即使用ShaderLab的渲染设置命令)来编写,而非使用Cg/HLSL
  • 由于现在绝大多数GPU都支持可编程的渲染管线,这种固定管线的编程方式已经逐渐被抛弃
  • 实际上,在Unity5.2中,所有固定函数着色器都会在背后被Unity编译成对应的顶点/片元着色器,因此真正意义上的固定函数着色器已经不存在了

3.4.4 选择哪种Unity Shader形式

  • 除非你有非常明确的需求必须要使用固定函数着色器,例如需要在非常旧的设备上运行你的游戏(这些设备非常少见),否则请使用可编程管线的着色器,即表面着色器或顶点/片元着色器
  • 如果你想和各种光源打交道,你可能更喜欢使用表面着色器,但需要小心它在移动平台的性能表现
  • 如果你需要使用的光照数目非常少,例如只有一个平行光,那么使用顶点/片元着色器是一个更好的选择
  • 最重要的是,如果你有很多自定义的渲染效果,那么请选择顶点/片元着色器

3.5 本书使用的Unity Shader形式

  • 本书的目的不仅在于教给读者如何使用Unity Shader,更重要的是想要让读者掌握渲染背后的原理,仅仅了解高层抽象虽然可能会暂时使工作简化,但从长久来看”知其然而不知其所以然”所带来的影响更加深远
  • 因此,在本书接下来的内容中,将着重使用顶点/片元着色器来进行Unity Shader的编写

3.6 答疑解惑

3.6.1 Unity Shader != 真正的Shader

  • Unity Shader并不等同于第2章中所讲的Shader,尽管Unity Shader翻译过来就是Unity着色器。在Unity里,Unity Shader实际上指的就是一个ShaderLab文件——硬盘上以shader作为文件后缀的一种文件
  • 在传统的Shader中,我们仅可以编写特定类型的Shader,例如顶点着色器、片元着色器等。而在Unity Shader中,我们可以在同一个文件里同时包含需要的顶点着色器和片元着色器代码
  • 在传统的Shader中,我们无法设置一些渲染设置,例如是否开启混合、深度测试等,这些是开发者在另外的代码中自行设置的。而在Unity Shader中,我们通过一行特定的指令就可以完成这些设置
  • 在传统的Shader中,我们需要编写冗长的代码来设置着色器的输入和输出,要小心地处理这些输入输出的位置和对应关系等。而在Unity Shader中,我们只需要在特定语句块中声明一些属性,就可以依靠材质来方便地改变这些属性。而且对于模型自带的数据(如顶点位置、纹理坐标、法线等)Unity Shader也提供了直接访问的方法,不需要开发者自行编码来传给着色器
  • 由于Unity Shader的高度封装性,我们可以编写的Shader类型和语法都被限制了。对于一些类型的Shader,例如曲面细分着色器(Tessellation Shader)、几何着色器(Geometry Shader)等,Unity的支持就相对差一些

3.6.2 Unity Shader和CG/HLSL之间的关系

  • 正如之前所讲,Unity Shader是用ShaderLab语言编写的,但对于表面着色器和顶点/片元着色器,我们可以在ShaderLab内部嵌套Cg/HLSL语言来编写这些着色器代码
  • 这些Cg/HLSL代码是嵌套在CGPROGRAM和ENDCG之间的,如下图所示:
  • 从本质上来说Unity中只存在顶点/片元着色器