【技术美术实践部分】初识Unity Shader:基础总结!

写在前面

这篇博客是对《Unity Shader 入门精要》第3章-Unity Shader基础的学习记录和总结。从这篇开始我就再也不是“纸上谈兵”得学习理论知识啦!

激动的心颤抖的手!

如何在Unity里创建Shader?创建材质?怎么把一个shader赋给材质?shader怎么赋给场景中的游戏对象?...如果你这些问题都有疑问——还不快去学!这篇博客不会讲这些基础的东西了,只会记录我想要写下来保存的内容(见谅~


1 如何设置用Sublime打开Shader

之前在学习Unity的时候需要写C#脚本,当时用的是Visual Studio 2022写的,external tool设定的就是VS2022,结果打开Shader发现,即使添加了ShaderlabVS插件,在VS里写Shader竟然无法自动缩进!每次都要一个tab一个tab的手打(心累),于是转战之前Houdini用的sublime。

于是,我参考下面这篇教程,想要完成同时达到:用VS打开C#,用sublime打开Shader,这一效果的配置

用SublimeText当Unity Shader的编辑器 - meteoric_cry - 博客园 (cnblogs.com)

结果我只完成了在sublime里添加Unity Shader包的操作,怎么都实现不了Unity双击.shader文件自动跳到sublime打开的效果,索性直接用了最粗暴的办法:

【技术美术实践部分】初识Unity Shader:基础总结!_第1张图片

直接修改系统打开目标文件的默认程序,.shader文件选择“始终以sublime打开”.cs文件选择“始终以VS 2022打开”

这种粗暴的办法用起来也没啥不妥,但是其实用sublime写Shader也有不爽的地方——不能自动美化格式,但他可以自动缩进呀!相比VS已经够香的了~

2 我使用的Unity版本和环境

Unity我用的是Unity2021.3.8f1,但《入门精要》书是2016出版用的是Unity5.2.1,上Unity官网看的话,Unity版本3.x --> 4.x --> 5.x --> 2017.x --> ... --> 2022.x,时间线是依次推进的。

【技术美术实践部分】初识Unity Shader:基础总结!_第2张图片

系统的话我是用的Win10,Windows是基于DirectX的。

不同Unity版本内置函数的使用

既然提到了Unity版本,这里就先打个预防针,要留意一些Unity不同版本之间内置函数使用的差异性,比如我在学习后面的内容时遇到了:

o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
//把MVP矩阵替换成了后面版本出现的内置函数

o.pos = UnityObjectToClipPos(v.vertex);

shader中的UnityObjectToClipPos和UNITY_MATRIX_MVP

上文中清楚的提到了Unity5.6版本之后,Shader中的:

  • UNITY_MATRIX_MVP会被UnityObjectToClipPos替代
  • UNITY_MATRIX_VP会被UnityWorldToClipPos替代
  • UNITY_MATRIX_P会被UnityViewToClipPos替代

不过,我发现Unity还是很智能的,它会帮你转换写的低版本函数。

3 Unity Shader与Shaderlab

Unity提供了Unity Shader让我们能更加轻松地管理着色器代码以及渲染设置,由于渲染设置包含了很多相对复杂的内容,例如逐片元操作(也叫合并)中——做的很多测试工作(深度测试、模板测试)、开启/关闭混合功能等等,我们不需要复杂和冗长的代码去管理,使得上手就轻松了很多。

总之,Unity Shader意义虽然挺抽象,但它本质上就是一个文本文件,有了它,我们学习和编写着色器的过程变得更加简单。

行,那我们怎么才能去使用这个Unity Shader来实现控制呢?——Unity提供了ShaderLab这样一种说明语言,所有的Unity Shader都由它来编写。既然是说明语言,那一定有某些表说明的“标签”,在ShaderLab中这样的“标签”就是一些嵌套在花括号内部的语义(Properties、SubShader等等),这些语义定义了Unity Shader的结构。啊,还有一点,如果你学习过DirectX,一定知道它用FX效果文件(.fx)来管理渲染方式,它也包含了渲染状态的设置和HLSL代码,可以把ShaderLab和FX效果文件类比着记忆。

4  ShaderLab的语义

ShaderLab的语义定义了整个Unity Shader的结构,语义的话有Properties、SubShader、Fallback语义块,它们三个是最最常用的语义,接下来我将参考《入门精要》中3.3章对语义部分的介绍进行叙述。

4.1 Properties语义块

为什么会说Properties语义块是材质和Unity Shader的桥梁?因为它定义的内容就是一些材质属性(property),这些属性会在Unity的Inspector面板中展现出来,把材质属性在Inspector面板暴露出来,我们调整起来更加方便!

须知:即使我们不在Properties语义块中定义某些属性,只要在后续的代码段中定义变量,也能使用这些属性变量,因此Properties语义块的作用仅仅是为了让属性可视化在Inspector面板中

一个Properties语义块格式通常如下:

Properties {
    Name ("display name", propertyType) = DefaultValue
    Name ("display name", propertyType) = DefaultValue
    ...
}

其中,

Name——名字,这是我们在Shader中访问它用到的属性名字,通常以一个下划线“_”开头

display name——显示名字,这是显示在Inspector面板中的名字

propertyType——属性类型,这是我们需要指定的类型,下面会举出常见的属性

DefaultValue——默认值,我们需要给每个属性指定默认值

4.1.1 常见的属性类型

Properties语义块支持的属性类型

属性类型 默认值的定义语法
Int number
Float number
Range(min, max) number

Color

(num, num, num, num)
Vector (num, num, num, num)
2D "defaulttexture" {}
Cube "defaulttexture" {}
3D "defaulttexture" {}

其中, 2D、Cube、3D的""内要么是空的,要么是Unity内置的纹理名称(black、white、bump等),至于它们是什么?在之前的HLSL常用函数介绍中,纹理映射函数就已经说过了纹理有1D(几乎不用了)、2D、Cube和3D。如果要指定纹理属性,需要在顶点着色器中编写计算相应纹理坐标的代码。

4.1.2 演示

Shader "Unlit/3.1"
{
    Properties
    {
        //数字和slider
        _Int("Int",Int) = 2
        _Float("Float",float)=1.5
        _Range("Range",Range(0.0,5.0))=3.0
        //颜色值和向量
        _color("Color",Color)=(1,1,1,1)
        _vector("Vector",Vector)=(2,3,6,1)
        //纹理信息
        _2D ("2D", 2D) = "" {}
        _Cube ("Cube", Cube) = "white" {}
        _3D ("3D", 3D) = "black" {}
    }

    FallBack  "Diffuse"
}

【技术美术实践部分】初识Unity Shader:基础总结!_第3张图片

4.2 SubShader语义块

每个Unity Shader都会至少有一个SubShader语义块。由于不同显卡有不同的渲染能力(很好理解,高级的显卡有更多更复杂的计算能力,而低端一点仅支持部分指令),Unity加载当前Unity Shader时会扫描所有的SubShader语义块,选择一个适合当前平台运行的SubShader语义块。如果都不支持,那么就会使用Fallback语义(后续会提到)指定的Unity Shader。

一个SubShader语义块的格式通常如下:

SubShader {
    //可选的
    [Tag]

    //可选的
    [RenderSetup]

    Pass {
        ...
    }
    //other passes
}

4.2.1 渲染状态设置

还记得第3节介绍Unity Shader的时候,介绍了它可以让我们更方便地处理渲染设置吗?这就是在SubShader中实现的。ShaderLab提供了一系列渲染状态的设置指令,可以设置显卡的各种状态,是否开启混合模式、是否开启深度测等等,把冗长的代码凝炼成了RenderSetup的指令!

题外话:这里也正好验证了介绍GPU管线概述提到的——逐片元操作我们可进行配置但不是可编程的,这一特性。

还须注意,

  • 渲染状态和标签都是可选的,即,非必须项
  • SubShader中直接设置的渲染状态会应用于所有的Pass
  • Pass语义块也是可以定义标签[Tag]和渲染状态[RenderSetup]的,但和SubShader中定义的不太一样(后续会讲到)

ShaderLab常见的渲染状态设置选项

状态名称 设置指令 解释
Cull Cull Back | Front | Off 剔除模式设置:剔除背面/正面/关闭
ZTest ZTest Less Great | LEqual | GEqual | Equal | NotEqual | Always 设置深度测试使用的函数
ZWrite ZWrite On | Off 开启/关闭深度写入
Blend Blend SrcFactor DstFactor 开启并设置混合模式

4.2.2 SubShader的标签

Tags是一个键值对(Key/Value Pair),键和值都是字符串类型。目的是告诉Unity引擎,当前的SubShader希望如何以及何时渲染当前对象。

还须注意,SubShader的标签类型和接下来讲的Pass语义块的标签类型不一样,不能互通用~

SubShader的标签格式通常如下:

Tags {"TagNamel" = "Value1" "TagName2" = "Value2"}

SubShader的标签类型

标签类型 说明
Queue 控制渲染顺序,指定渲染队列,保证所有透明物体可以在所有不透明物体后面被渲染,还可以自定义渲染队列 Tags {"Queue" = "Transparent"}
RenderType 对着色器进行分类,例如定义当前着色器是透明or不透明 Tags {"RenderType" = "Opaque"}
DisableBatching 直接指明当前是否对该SubShader进行批处理 Tags {"DisableBatching" = "True"}

ForceNoShadowCasting

控制当前SubShader是否投射阴影 Tags {"ForceNoShadowCasting" = "True"}
IgnoreProjector 为True,则表示使用当前SubShader的问题不受Projection影响,通常用于半透明物体 Tags {"IgnoreProjector" = "Ture"}
CanUseSpriteAtlas 当前SubShader用于精灵sprites时,标签为False Tags {"CanUseSpriteAtlas" = "False"}
PreviewType 指明如何预览该材质,默认情况是球形,可以改成plane、skybox等 Tags {"PreviewType" = "Plane"}

定义的这些标签可以在Unity Shader的导入设置面板查看到:

【技术美术实践部分】初识Unity Shader:基础总结!_第4张图片

4.2.3 Pass语义块

Pass语义块可以说是最重要的部分,几乎整个渲染管线可编程的着色器代码都写在Pass语义块内了,它的格式通常如下:

Pass {
    [Name]
    [Tags]
    [RenderSetup]
    //other code
}
  • Name——当前Pass的名字

可以是:

Name "MyPassName"

还可以使用ShaderLab的UsePass命令——通过访问其名字,直接使用其他Unity Shader中的Pass,这样可以提高代码的复用性,但须注意用UsePass命令时必须使用大写名字,例如:

UsePass "MyShader/MYPASSNAME"
  • Tags——Pass的标签

前面说了,Pass的标签不同于SubShader的标签,但目的是一样的,都是告诉Unity如何渲染该物体,Pass的标签:

Pass的标签类型

标签类型 说明
LightMode 定义该Pass在Unity的渲染流水线中的角色 Tags {"LightMode" = "ForwardBase"}
RequireOptions 指定当满足某些条件时才渲染该Pass Tags {"RequireOptions" = "SoftVegetation"}
  • RenderSetup——设置渲染状态

前面SubShader的状态设置同样适用于Pass。

4.3 Fallback语义块

这个其实就是一个保证:万一当前的显卡实在太low了,现有的所有SubShader都无法运行,FallBack语义块就相当于一个最次备选,给他准备一个Fallback内置的最最low的Shader。

格式通常如下:

Fallback "name"
//or
Fallback Off

Fallback不仅有这一个功能,还能影响阴影的投射。渲染阴影纹理时,Unity需要在Unity Shader中寻找一个阴影投射的Pass,而Fallback刚好有内置的这样一个通用的Pass,有了它我们就不需要再单独写一个Pass了,说明正确设置Fallback是多么的重要!

5 Unity Shader的形式

5.1 表面着色器

学习渲染管线的时候似乎还没听说过这个着色器。

《入门精要》是这么描述的:它是Unity自创的一种着色器代码类型,本质上和顶点/片元着色器是一样的。总结下来有这两个特点,

  • 它需要的代码很少,Unity做了很多工作(Unity会在幕后给它转化成顶点/片元着色器),但渲染的代价较大
  • 表面着色器是被定义在SubShader里,而不是Pass里
  • 被定义在CGPROGRAMENDCG之间

5.2 顶点/片元着色器

他们是老朋友了,在Unity中是用Cg/HLSL语言编写的,灵活性相比表面着色器高很多。这里就不介绍啥代码结构啦,后面当然会再次遇到,到时候再剖析也不迟。

5.3 固定函数着色器(被抛弃

关于“固定”,其实我在PC和手机的主流图形API介绍中就提到了:“OpenGL ES 2.0——引入可编程着色器,弃用了1.x中需对固定功能”,其中的固定功能就是指的这个固定函数着色器,是OpenGL ES 1.1及之前的版本会用的,DX的话是7.0之前,现在只有一些旧设备需要使用啦,所以才会说“被抛弃”。

对于它我们不深入了解,只需要知道有过就行。

你可能感兴趣的:(技术美术百人计划学习记录,技术美术,游戏,unity)