猫都能学会的Unity3DShader ⼊门指南(⼀)
猫都能学会的Unity3D Shader
⼊门指南(⼀)⾃⼰使⽤Unity3D 也有⼀段时间了,但是很多时候是流于表⾯,更多地是把这个引擎简单地⽤作脚本控制,⽽对更深⼊⼀些的层次⼏乎没有了解。虽然说Unity 引擎设计的初衷就是创建简单的不需要开发者操⼼的谁都能⽤的3D 引擎,但是只是肤浅的使⽤,可能是⽆法达到随⼼所欲的境地的,因此,这种状况必须改变!从哪⾥开始呢,貌似有句话叫做会写Shader 的都是⾼⼿,于是,想⼤概看看从Shader 开始能不能使⾃⼰到达的层次能再深⼊⼀些吧,再于是,有了这个系列(希望我能坚持写完它,虽然应该会拖个半年左右)。
Unity3D 的所有渲染⼯作都离不开着⾊器(Shader ),如果你和我⼀样最近开始对Shader 编程⽐较感兴趣的话,可能你和我有着同样的困惑:如何开始?Unity3D 提供了⼀些Shader 的⼿册和⽂档(⽐如这⾥,这⾥和这⾥),但是⼀来内容⽐较分散,⼆来学习阶梯稍微陡峭了些。这对于像我这样之前完全没有接触过有关内容的新⼈来说是相当不友好的。国内外虽然也有⼀些Shader 的介绍和⼼得,但是也同样存在内容分散的问题,很多教程前⼀章就只介绍了基本概念,接下来马上就搬出⼀个超复杂的例⼦,对于很多基本的⽤法并没有解释。也许对于Shader 熟练使⽤的开发者来说是没有问题,但是我相信像我这样的⼊门者也并不在少数。在多⽅寻觅⽆果后,我觉得有必要写⼀份教程,来以⼀个⼊门者的⾓度介绍⼀些Shader 开发的基本步骤。其实与其说是教程,倒不如说是⼀份⾃我总结,希望能够帮到有需要的⼈。
所以,本“教程”的对象是
总的来说是新接触Shader 开发的⼈:也许你知道什么是Shader ,也会使⽤别⼈的Shader ,但是仅限于知道⼀些基本的内建Shader 名字,从来没有打开它们查看其源码。
想要更多了解Shader 和有需求要进⾏Shader 开发的开发者,但是之前并没有Shader 开发的经验。当然,因为我本⾝在Shader 开发⽅⾯也是⼀个不折不扣的⼤菜鸟,本⽂很多内容也只是在⾃⼰的理解加上⼀些可能不太靠谱的求证和总结。本⽂中的⽰例应该会有更好的⽅式来实现,因此您是⾼⼿并且恰巧
路过的话,如果有好的⽅式来实现某些内容,恳请您不吝留下评论,我会对本⽂进⾏不断更新和维护。Shader 和Material
如果是进⾏3D 游戏开发的话,想必您对着两个词不会陌⽣。Shader (着⾊器)实际上就是⼀⼩段程序,它负责将输⼊的Mesh (⽹格)以指定的⽅式和输⼊的贴图或者颜⾊等组合作⽤,然后输出。绘图单元可以依据这个输出来将图像绘制到屏幕上。输⼊的贴图或者颜⾊等,加上对应的Shader ,以及对Shader 的特定的参数设置,将这些内容(Shader 及输⼊参数)打包存储在⼀起,得到的就是⼀个Material (材质)。之后,我们便可以将材质赋予合适的renderer (渲染器)来进⾏渲染(输出)了。
Unity Shader 教程
动机
⼀些基本概念
所以说Shader 并没有什么特别神奇的,它只是⼀段规定好输⼊(颜⾊,贴图等)和输出(渲染器能够读懂的点和颜⾊的对应关系)的程序。⽽Shader 开发者要做的就是根据输⼊,进⾏计算变换,产⽣输出⽽已。Shader ⼤体上可以分为两类,简单来说
表⾯着⾊器(Surface Shader ) - 为你做了⼤部分的⼯作,只需要简单的技巧即可实现很多不错的效果。
类⽐卡⽚机,上⼿以后不太需要很多努⼒就能拍出不错的效果。
⽚段着⾊器(Fragment Shader ) - 可以做的事情更多,但是也⽐较难写。使⽤⽚段着⾊器的主要⽬的是可以在⽐较低的层级上进⾏更复杂(或者针对⽬标设备更⾼效)的开发。
因为是⼊门⽂章,所以之后的介绍将主要集中在表⾯着⾊器上。
Shader 程序的基本结构
因为着⾊器代码可以说专⽤性⾮常强,因此⼈为地规定了它的基本结构。⼀个普通的着⾊器的结构应该是这样的:
⾸先是⼀些属性定义,⽤来指定这段代码将有哪些输⼊。接下来是⼀个或者多个的⼦着⾊器,在实际运⾏中,哪⼀个⼦着⾊器被使⽤是由运⾏的平台所决定的。⼦着⾊器是代码的主体,每⼀个⼦着⾊器中包含⼀个或者多个的Pass 。在计算着⾊时,平台先选择最优先可以使⽤的着⾊器,然后依次运⾏其中的Pass ,然后得到输出的结果。最后指定⼀个回滚,⽤来处理所有Subshader 都不能运⾏的情况(⽐如⽬标设备实在太⽼,所有Subshader 中都有其不⽀持的特性)。
需要提前说明的是,在实际进⾏表⾯着⾊器的开发时,我们将直接在Subshader 这个层次上写代码,系统将把我们的代码编译成若⼲个合适的Pass 。废话到此为⽌,下⾯让我们真正实际进⼊Shader 的世界吧。⼀段Shader 程序的结构
Hello Shader
百⾏⽂档不如⼀个实例,下⾯给出⼀段简单的Shader 代码,然后根据代码来验证下上⾯说到的结构和阐述⼀
些基本的Shader 语法。因为本⽂是针对Unity3D 来写Shader 的,所以也使⽤Unity3D 来演⽰吧。⾸先,
新建⼀个Shader ,可以在Project ⾯板中到,Create ,选择Shader ,然后将其命名为Diffuse Texture :
随便⽤个⽂本编辑器打开刚才新建的Shader :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26Shader  "Custom /Diffuse  Texture " {  Properties  {      _MainTex  ("Base  (RGB )", 2D ) = "white " {}  }  SubShader  {      Tags  { "RenderType "="Opaque " }      LOD  200            CGPROGRAM      #pragma  surface  surf  Lambert      sampler2D  _MainTex ;      struct  Input  {          float2 uv_MainTex ;      };      void  surf  (Input  IN , inout  SurfaceOutput  o ) {          half4 c  = tex2D  (_MainTex , IN .uv_MainTex );          o .Albedo  = c .rgb ;          o .Alpha  = c .a ;      }      ENDCG  }  FallBack  "Diffuse "}
如果您之前没怎么看过Shader 代码的话,估计细节上会看不太懂。但是有了上⾯基本结构的介绍,您应该可以识别出这个Shader 的构成,⽐如⼀个Properties 部分,⼀个SubShader ,以及⼀个FallBack 。另外,第⼀⾏
在Unity3D 中新建⼀个Shader
只是这个Shader 的声明并为其指定了⼀个名字,⽐如我们的实例Shader ,你可以在材质⾯板选择Shad
er 时在对应的位置到这个Shader 。
接下来我们讲逐句讲解这个Shader ,以期明了每⼀个语句的意义。
属性
在Properties{}中定义着⾊器属性,在这⾥定义的属性将被作为输⼊提供给所有的⼦着⾊器。每⼀条属性的定义的语法是这样的:一个介于0至5之间的字符串是什么
_Name("Display Name", type) = defaultValue[{options}]
_Name - 属性的名字,简单说就是变量名,在之后整个Shader 代码中将使⽤这个名字来获取该属性的内容
Display Name - 这个字符串将显⽰在Unity 的材质编辑器中作为Shader 的使⽤者可读的内容
type - 这个属性的类型,可能的type 所表⽰的内容有以下⼏种:
Color - ⼀种颜⾊,由RGBA (红绿蓝和透明度)四个量来定义;
2D - ⼀张2的阶数⼤⼩(256,512之类)的贴图。这张贴图将在采样后被转为对应基于模型UV 的每个像素的颜⾊,最终被显⽰出来;
Rect - ⼀个⾮2阶数⼤⼩的贴图;
Cube - 即Cube map texture (⽴⽅体纹理),简单说就是6张有联系的2D 贴图的组合,主要⽤来做反射效果(⽐如天空盒和动态反射),也会被转换为对应点的采样;
Range(min, max) - ⼀个介于最⼩值和最⼤值之间的浮点数,⼀般⽤来当作调整Shader 某些特性的参数(⽐如透明度渲染的截⽌值可以是从0⾄1的值等);
Float - 任意⼀个浮点数;
Vector - ⼀个四维数;
defaultValue 定义了这个属性的默认值,通过输⼊⼀个符合格式的默认值来指定对应属性的初始值(某些效果可能需要某些特定的参数值来达到需要的效果,虽然这些值可以在之后在进⾏调整,但是如果默认就指定为想要的值的话就省去了⼀个个调整的时间,⽅便很多)。
Color - 以0~1定义的rgba 颜⾊,⽐如(1,1,1,1);
2D/Rect/Cube - 对于贴图来说,默认值可以为⼀个代表默认tint 颜⾊的字符串,可以是空字符串或者”white”,”black”,”gray”,”bump”中的⼀个
Float ,Range - 某个指定的浮点数
Vector - ⼀个4维数,写为 (x,y,z,w)
另外还有⼀个{option},它只对2D ,Rect 或者Cube 贴图有关,在写输⼊时我们最少要在贴图之后写⼀对什么都不含的空⽩的{},当我们需要打开特定选项时可以把其写在这对花括号内。如果需要同时打开多个选项,可以使⽤空⽩分隔。可能的选择有ObjectLinear, EyeLinear, SphereMap, CubeReflect, CubeNormal 中的⼀个,这些都是OpenGL 中TexGen 的模式,具体的留到后⾯有机会再说。
在Unity3D 中到刚才新建的Shader
所以,⼀组属性的申明看起来也许会是这个样⼦的
1 2 3 4//Define a color with a default value of semi-transparent blue _MainColor("Main Color",Color)=(0,0,1,0.5)
//Define a texture with a default of white
_Texture("Texture",2D)="white"{}
现在看懂上⾯那段Shader(以及其他所有Shader)的Properties部分应该不会有任何问题了。接下来就是SubShader部分了。
Tags
表⾯着⾊器可以被若⼲的标签(tags)所修饰,⽽硬件将通过判定这些标签来决定什么时候调⽤该着⾊器。⽐如我们的例⼦中SubShader的第⼀句
Tags { "RenderType"="Opaque" }
告诉了系统应该在渲染⾮透明物体时调⽤我们。Unity定义了⼀些列这样的渲染过程,与RenderType是Opaque相对应的显⽽易见的是"RenderType" = "Transparent",表⽰渲染含有透明效果的物体时调⽤。在这⾥Tags其实暗⽰了你的Shader输出的是什么,如果输出中都是⾮透明物体,那写在Opaque⾥;如果想渲染透明或者半透明的像素,那应该写在Transparent中。
另外⽐较有⽤的标签还有"IgnoreProjector"="True"(不被Projectors影响),"ForceNoShadowCasting"="True"(从不产⽣阴影)以及"Queue"="xxx"(指定渲染顺序队列)。这⾥想要着重说⼀下的是Queue这个标签,如果你使⽤Unity做过⼀些透明和不透明物体的混合的话,很可能已经遇到过不透明物体⽆法呈现在透明物体之后的情况。这种情况很可能是由于Shader的渲染顺序不正确导致的。Queue指定了物体的渲染顺序,预定义的Queue有:
Background - 最早被调⽤的渲染,⽤来渲染天空盒或者背景
Geometry - 这是默认值,⽤来渲染⾮透明物体(普通情况下,场景中的绝⼤多数物体应该是⾮透明的)
AlphaTest - ⽤来渲染经过Alpha Test的像素,单独为AlphaTest设定⼀个Queue是出于对效率的考虑Transparent - 以从后往前的顺序渲染透明物体
Overlay - ⽤来渲染叠加的效果,是渲染的最后阶段(⽐如镜头光晕等特效)
这些预定义的值本质上是⼀组定义整数,Background = 1000,Geometry = 2000, AlphaTest = 2450,Transparent = 3000,最后Overlay = 4000。在我们实际设置Queue值时,不仅能使⽤上⾯的⼏个预定义值,我们也可以指定⾃⼰的Queue值,写成类似这样:"Queue"="Transparent+100",表⽰⼀个在Transparent之后100的Queue上进⾏调⽤。通过调整Queue值,我们可以确保某些物体⼀定在另⼀些物体之前或者之后渲染,这个技巧有时候很有⽤处。
LOD
LOD很简单,它是Level of Detail的缩写,在这⾥例⼦⾥我们指定了其为200(其实这是Unity的内建Diffuse着⾊器的设定值)。这个数值决定了我们能⽤什么样的Shader。在Unity的Quality Settings中我们可以设定允许的最⼤LOD,当设定的LOD⼩于SubShader所指定的LOD时,这个SubShader将不可⽤。Unity内建Shader 定义了⼀组LOD的数值,我们在实现⾃⼰的Shader的时候可以将其作为参考来设定⾃⼰的LOD数值,这样在之后调整根据设备图形性能来调整画质时可以进⾏⽐较精确的控制。
VertexLit及其系列 = 100
Decal, Reflective VertexLit = 150
Diffuse = 200
Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
Bumped, Specular = 300
Bumped Specular = 400
Parallax = 500
Parallax Specular = 600

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。