Unity3D shader简介
可以肯定的说Unity3D使得很多开发者开发游戏更容易。毫无疑问,shader(着色器)编码,仍有很长的路要走。shader是一个专门运行在GPU的程序,经常被神秘包围,它最终绘制3D模型的三角形。如果你想给游戏一个特殊的显示,学习如何编写shader是必要的。Unity3D使用shader做后期处理,对2D游戏也是必不可少的。这个系列的文章将逐步介绍shader编程,并面向几乎没有任何shader知识的开发者。
简介
下图大致表示了在Unity3D渲染流程中发挥作用的3个不同实体:
3D模型本质上是,被称为顶点的3D坐标集合。他们连接在一起构成一些三角形。每个顶点包含一些其它的信息,如颜色、点指的方向(法线)、纹理映射坐标(UV数据)。
没有材质模型是不能被渲染的。材质包含一个shader和其属性值的封装。因此,不同材质可以共享相同的shader,赋予不同的数据。
shader剖析
Unity3D支持两种不同的shader:表面shader、片段和顶点shader。还有第三种类型:固定管线shader,但是如今已经过时了,将不包含在本系列文章。无论你需要的是哪种类型,shader的结构都一样:
Shader "MyShader" { Properties { // The properties of your shaders // - textures // - colours // - parameters // ... } SubShader { // The code of your shaders // - surface shader // OR // - vertex and fragment shader // OR // - fixed function shader } } |
可以包含多个SubShader,一个接一个。他们包含GPU的实际指令。Unity3D将找到与你显卡兼容的SubShader,并顺序执行他们。这对多平台编码是非常有用,因为你可以在一个文件中编写同一shader的不同版本。
属性
shader的属性在某种程度上相当于C#脚本中的public字段,他们将出现在材质的inspector面板,给你机会来调整。但不像脚本,材质是资源:编辑中游戏运行时修改材质的属性值是永久的。甚至游戏停止后,修改的属性值也是有效的。
下面的代码片段涵盖了你可以在shader中使用的所有基本类型的定义:
3~4行中的2D,表示参数是纹理。他们可以初始化为white、black、gray。你也可以使用bump表示使用法线贴图,这种情况下,它将自动初始为颜色#808080,这用来表示没有任何凸凹。Vector和Color总是有4个元素(分别为xyzw和rgba)。
下图展示了一个shader附着到一个材质上之后,在inspector面板中是如何显示的。
不幸的是,这对我们使用属性还不够。事实上,Properties块被Unity3D用来从inspector访问shader隐藏变量。这些变量仍需定义在shader中,它们包含在SubShader块中。
纹理的类型为sampler2D。向量是float4、颜色一般是half4,分别使用32和16位表示。用来编写shader的语言为Cg/HLSL,很迂腐:参数的名称必须与先前定义的匹配。然而类型不需要,例如把_MyRange声明为half而不是float不会报任何错误。一些令人困惑的是,如果你定一个向量类型的属性,关联到一个float2变量,额外的2个值将被Unity3D忽略。
渲染顺序
正如已提到的,SubShader块包含实际代码,使用Cg/HLSL(非常像C)编写。不严格地说,一个shader为图像的每个像素执行,它的性能非常关键。由于GPU的体系结构,一个shader中能够执行的指令数量有限制。可以通过分割几个部分执行,但本教程不包含这块。
一个SubShader通常看起来像这样:
第8~11行包含实际的Cg代码,通过CGPROGRAM和ENDCG指令示意。
第3行,在实际代码之前,介绍tags的概念。tags用来告诉Unity3D我们写的shader的某些属性。例如,shader渲染的顺序(Queue)和应该如何渲染(RenderType)。
当渲染三角形时,GPU通常根据它们离摄像机的距离,远的先绘制。这在渲染不透明的几何形状时够用了,但是透明物体将失败。这也是为什么Unity3D运行指定Queue标签,可以控制每个材质的渲染顺序。Queue接受正整数(越小越先绘制),预定义(mnemonic)的标签也可以使用:
l Background(1000):用于背景和天空盒
l Geometry(2000):默认标签,用于大部分不透明物体
l Transparent(3000):用于包含透明属性的材质,例如玻璃、火、粒子、水
l Overlay(4000):用于镜头耀斑,GUI元素和文本
Unity3D还允许指定相关顺序,例如Background+2,表示1002队列值。搞乱了Queue值会导致恶劣的情况,一个对象总是被绘制,即使它应该被其他模型遮挡住了。
Ztest
记住,一个包含透明属性(Transparent)的对象并不总是显示在不透明(Geometry)对象上面。默认情况下,GPU执行ZTest避免隐藏的对象被绘制。原理是,它使用了一个额外的缓冲区,其大小与屏幕渲染的相同。每个像素包含绘制对象在该像素的深度(离相机的距离)。如果我们要绘制一个像素比当前深度更大,像素就被丢弃。ZTest剪裁被其它对象遮挡住的像素,无论他们绘制到屏幕上的顺序。
表面 VS 顶点和片段
shader代码的最后一部分。在渲染之前,需要决定使用那种类型的shader。本节将给出shader效果惊鸿一瞥的样子,但不会深入解释。表面、顶点和片段shader将在本教材的下一部分覆盖。
表面shader
当材质需要根据光照模拟实际效果时,那么你就需要一个表面shader。表面shader在函数surf中隐藏光线如何被反射的计算,并允许指定“直观”的属性,如反照率、法线、反射率等。然后将这些值插入到光照模型,将输出每个像素的最终RGB值。或者,当需要非常高级的效果是,你也可以编写自己的光照模型。
一个典型的表面shader的Cg代码如下所示:
第5行指定输入的纹理,然后在第12行将指定Albedo属性。第3行指定使用Lambertian光照模型,这个是非常典型光照如何影响对象的模型。shader仅使用反照率属性通常称为漫反射(diffuse)。
顶点和片段shader
顶点和片段shader的工作贴近GPU渲染三角形的方式,并没有光照如何表现的概念。模型的几何形状首先通过一个调用函数vert,改变他的顶点。然后各个三角形通过另一个调用函数frag,这决定了最终每个像素的RGB颜色。这对2D效果非常有用,后期处理和特殊的3D效果非常付出需要使用表面shader。
下面的顶点和片段shader简单只是使用红色,没有光照:
第15~17行,将原始的3D空间顶点转换到最终的2D屏幕位置。Unity3D使用UNITY_MATRIX_MVP实现,隐藏了底层的实现数学运算。在此之后,第22行,给定每个像素为红色。只需要记住,顶点和片段shader的Cg部分需要封装到Pass块中。它跟简单的表面shader不一样。
结论
本篇文章逐渐引入了两种类型的Unity3D shader,并说明何时需要使用。接下来四篇文章,将介绍实现它们的细节。还有一个附加篇,将介绍屏幕shader,用于2D图像的后期处理。
- Part 1: Unity3D shader简介(A gentle introduction to shaders in Unity3D)
- Part 2: Unity3D表面shader(Surface shaders in Unity3D)
- Part 3: Unity3D基于物理渲染和光照模型( Physically Based Rendering and lighting models in Unity3D)
- Part 4: Unity3D顶点和片段shader(Vertex and fragment shader in Unity3D)
- Part 5: Unity3D传递数组给shader(Passing arrays to a shader: heatmaps in Unity3D)
- Part 6: Unity3D屏幕shader和效果后期处理(Screen shaders and postprocessing effects in Unity3D)