一 Shader的编程语言:
目前主流的有三种语言:
- 基于OpenGL的OpenGL Shading Language,简称GLSL。
- 基于DirectX的High Level Shading Language,简称HLSL。
- 还有NVIDIA公司的C for Graphic,简称Cg语言。
GLSL与HLSL分别是基于OpenGL和Direct3D的接口,两者不能混用。而Cg语言是用于图形的C语言,这其实说明了当时设计人员的一个初衷,就是让基于图形硬件的编程变得和C语言编程一样方便,自由。正如C++和 Java的语法是基于C的,Cg语言本身也是基于C语言的。如果您使用过C、C++、Java其中任意一个,那么Cg的语法也是比较容易掌握的。Cg语言极力保留了C语言的大部分语义,力图让开发人员从硬件细节中解脱出来,Cg同时拥有高级语言的好处,如代码的易重用性,可读性高等。
Cg语言是Microsoft和NVIDIA相互协作在标准硬件光照语言的语法和语义上达成了一致,所以,HLSL和Cg其实是同一种语言。
opengl 是SGI 公司的 跨平台性能非常好;
Directx:是微软开发的,显卡性能方面比较好,非跨平台
CG:微软和英伟达公司(显卡公司)联合开发,是跨平台 基于C语言。
我们知道显卡类型有:NVIDIA、ATI、Intel等等。。。
图形API有:OpenGL、DirectX、OpenglES、Vulkan、Metal等等。。。
Shader编程语言有:GLSL、HLSL、Cg等等。。。
那么在Unity中我们又应该如何做呢?在Unity中编写的Shader最终会根据不同的平台来编绎成不同的着色器语言,那么我们在Unity中应该用什么语言来书写Shader呢?
二 Unity Shader语言:
在Unity中官方的建议是用Cg/HLSL来编写,当然你也可以使用GLSL,主要是因为Cg/HLSL有更好的跨平台性,更倾向于使用Cg/HLSL来编写Shader程序。
Unity Shader严格来说并不是传统上的Shader,而是Unity自身封装后的一种便于书写的Shader,又称为ShaderLab。
2.1、 Unity Shader分类:
- Surface Shaders 表面着色器
- Vertex/Fragment Shaders 顶点/片断着色器(shader 1.0,主要是开关式, 打开和关闭功能)
- Fixed Function Shaders 固定管线着色器( shader 2.0,自己定义公式,已经被淘汰)
Surface Shader其实就是Unity对Vertex/Fragment Shader的又一层包装,以使Shader的制作方式更符合人类的思维模式,同时可以以极少的代码来完成不同的光照模型与不同平台下需要考虑的事情。
但是Surface Shader也有它的局限性,就是Vertex/Fragment Shader能实现的效果,Surface Shader不一定能实现,反过来则成立,Surface Shader能实现的Vertex/Fragment Shader则一定可以实现。
并且在Unity2018后的版本中推出了Unity官方自己的可视化Shader工具(Shader Graph)。从生成的代码上来看,也全部是用的Vertex/Fragment Shader,那是不是可以理解为在今后的可编程渲染管线中,Unity自己也抛弃了Surface Shader,而全部采用了Vertex/Fragment Shader?
2.2、 创建Unity 的Shader
创建Shader时会发现有5个选项
- Standard Surface Shader
标准表面着色器,包含了标准光照模型,是一种基于物理的着色系统(使用了Physically Based Rendering(简称PBR)技术,即基于物理的渲染技术),以模拟现实真实的方式来模拟材质与灯光之间的关系,可以很轻易的表现出各种金属反光效果,同时此种Shader的书写逻辑也更符合人类的思维模式。
- Unlit Shader
Vertex/Fragment Shader,也就是最基本的顶点片断着色器(不包含光照但包含雾效),不受光照影响的Shader,多用于特效、UI上的效果制作。
- Image Effect Shader
也是顶点片断着色器,只不过是针对屏幕后处理而定制的模版,后处理是什么呢?Bloom(也有人叫Glow/泛光/辉光等说法)、调色、景深、模糊等,这些基于最终整个屏幕画面而进行再处理的Shader就是后处理。
- Compute Shader
Compute Shader是运行在图形显卡上的一段程序,独立于常规渲染管线之外的,它可以直接将GPU作为并行处理器加以利用,从而使GPU不仅具有3D渲染能力,还具有其他的运算能力。
- Shader Variant Collection
Shader变体收集器,在上面创建的时候,你会发现Shader Variant Collection与以上四个是被隔开的,就是因为这个与它们不一样,它不是制作Shader的模版,而只是对Shader变体进行打包用的容器。
注:以上的Standard Surface Shader、Unlit Shader、Image Effect Shader仅仅只是Unity为了方便我们书写而内置的几个模版,你完全可以建一个Unlit Shader,然后将其改成Surface Shader,同样也可以将一个Standard Surface Shader改成顶点片断着色器,所以这一点一定要明白,它们只是内容格式不一样的模版本而已,我们完全可以自由修改成任意我们想要的一种着色器类型,当然我们也可以通过一些手段来定制出我们自己的模版,这在后续章节中我们再进行详细介绍
下面介绍Shader结构:
Unity中的Shader都是要通过ShaderLab的基本语法进行编写。Unity 官方推荐我们学习Shader的时候,要先了解ShaderLab语法的一些基本概念。
打开创建的shader
从整体来看的话,ShaderLab基本结构大致是这样的一个框架:
我们尝试将其名称Custom/Test1改成:
Shader "TA/MyFirstShader"
Shader "name" { [Properties] SubShaders [FallBack] [CustomEditor] }
可以拆分成以下几个大部分:
- Shader "name"
- Properties
- SubShaders
- FallBack
- CustomEditor
其中[]方括号表示的是可选,意思就是说在Shader中可以没有它,只要不需要的话不加也是没有问题的。
1、Shader "name"
如果我们把路径名称放在Hidden下面的话,比如:
Shader "Hidden /TA/MyFirstShader"
则表示在材质面板中隐藏此Shader,你将无法通过材质下拉列表中找到它。这在做一些不需暴露的Shader时很有用处,可以使Shader下拉列表更精简整洁。而Shader文件的名称,也就是我们在Project面板中的资源文件的名称,是可以与Shader内部的路径名称不一样的,这点与C#是不同的。
在定义内部路径名称时,建议统一规划下,不要Shader过多后导致很混乱,不便于美术去使用。
2、Properties
属性,在Shader中,我们在Properties中定义的变量是为了在材质面板中显示并方便我们调节,材质球面板中显示的贴图和一些参数什么的都是在此Properties中进行定义设置的,内容必须写在Properties后的{}内。当然,如果你确定不需要属性的话也可以将整个Properties以及它的{}全部删除,因为它是可选项。
3、SubShader
SubShader是专门为GPU渲染所编写的Shader的片段, 每个Shader中都可以包含多个SubShader,不可以没有,必须至少有一个,因为当前Shader的核心算法实现就是在SubShader中来实现的。在加载Shader时,Unity将遍历所有SubShader列表,并最终选择用户机器支持的第一个。
这个功能到底有什么用呢?
我们都知道不同的硬件性能是不一样的,游戏内通常把机器配置分为高中低三种,假如我们做了一个效果很好的Shader,但是呢却只能在高配机上有较好的性能表现,中低端就显的太费性能,这时怎么办呢,SubShader在这时就可以派上用场了,我们可以在这个Shader内做三个SubShader,分别对应于高中低不同的配置。为了Shader能在比较老的图形显卡中也能支持。一般比较越往下的SubShader要简化,运算指令要简单。
3.1 SubShader中也必须至少有一个Pass,Pass的意思就是渲染一次模型!
而具体要怎么渲染就需要我们在Pass中添加Cg/HLSL代码片断来实现了,这段代码片断是由CGPROGRAM开始,由ENDCG结束。也就是说我们必须要添加CGPROGRAM和ENDCG,然后在它们中间去写实现效果的代码。
Shader "TA/MyFirstShader"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
#pragma是Unity内置的编绎指令用的命令,在Pass中我们就利用此命令来声明所需要的顶点着色器与片断着色器(也叫作像素着色器)。
- #pragma vertex name
定义顶点着色器为name,通常情况下会起名为vert。
- #pragma fragmentname
定义片断着色器为name,通常情况下会起名为frag。
4、FallBack
备胎,就是备胎的意思,有的时候我们写的Shader难免在一些机器上会出现不支持的问题(最终呈现的就是显示成粉红色),而这个时候我们只要添加了FallBack,并且在后面的双引号内写上了其它Shader的有效路径名称,那么在碰到不支持的硬件时这个Shader就会后退回到最初始的状态 可以认为是默认(一般是所有硬件都支持的渲染方式)
比如: FallBack "Diffuse" 返回到漫反射的状态 。
5、CustomEditor
自定义界面,也就是说我们可以通过这个功能来自由定义材质面板的显示结果,它可以改写Properties中定义的显示方式。
举个你一定见过的例子:
Standard Shader对应的材质面板显示与众不同,很高级,这就是因为它利用了CustomEditor的功能来实现的,要做到这一点,必须要用到C#来写代码实现,这对于刚入门学Shader的同学来说不是易事,同时也由于这个功能在大多数情况下是不需要的,所以我们先抛开它,知道有它的存在就好了。
自学Shader步骤建议
- Unlit Shader,这是最基本也是最简单的模版,通过学习它了解顶点片断着色器的基本构成,以及对Shader有初步的认识。
- 利用顶点片断着色器做一些简单案例,比如一些游戏内常用的效果,与此同时深入学习Shader语法,以及用到的相关数学运算符。
- 尝试优化你的Shader,提升美术效果,减少变体数,减少运算量。
- 读一读渲染管线的书,加深对Shader理解的宽度与高度。
- 开始接触光照模型,了解Surface Shader。
- 各种光照算法研究一通。。。。顺便复习下数学。。。
- 屏幕后处理Shader
- Compute Shader
- 各种实例制作,不断强化美感、图形学以及数学。