本系列文章由 出品,转载请注明出处。
文章链接:
作者:毛星云(浅墨) 微博:
邮箱:
本文介绍了Unity中子着色器、通道和标签相关的具体概念与写法。以及纹理的设置方法,主要的纹理混合写法,写了5个Shader作为本文Shader解说的实战内容,最后创建了一个梦幻的光之城堡场景进行了Shader的測试。
依然是国际惯例。先上本文配套程序的截图。
光之城堡:
山坡上远眺:
通向森林的路:
古墓:
雾气氤氲的森林:
来一张上帝视角:
OK,图先就上这么多。文章末尾有很多其它的执行截图。并提供了原project的下载。可执行的exe下载在这里:
我们正式開始。
一、子着色器(SubShader)相关内容解说
话不多说,我们接着上篇文章继续讲。
Unity中的每个着色器都包括一个subshader的列表,当Unity须要显示一个网格时。它能发现使用的着色器。并提取第一个能执行在当前用户的显示卡上的子着色器。
我们知道。子着色器定义了一个渲染通道的列表,并可选是否为全部通道初始化所须要的通用状态。
子着色器的写法例如以下:
Subshader{ [Tags] [CommonState] Passdef [Passdef ...] }
也就是通过可选标签,通用状态 和 一个Pass 定义的列表构成了子着色器。
当Unity选择用于渲染的子着色器时,它为每个被定义的通道渲染一次对象(可能会很多其它。这取决于光线的交互作用)。当对象的每一次渲染都是非常费资源之时,我们便使用尽量少的通道来定义一个着色器。当然,有时在一些显示硬件上须要的效果不能通过单次通道来完毕。自然就得使用多通道的子着色器了。
另外,通道定义的类型包括a regular Pass, a Use Pass or aGrab Pass。
不论什么出如今通道定义的状态同一时候也能整个子着色器块中可见。
这将使得全部通道共享状态。
1.1 关于子着色器标签(SubShader Tags)
子着色器使用标签来告诉渲染引擎期望何时和怎样渲染对象。
其语法例如以下:
Tags { "TagName1" ="Value1" "TagName2" = "Value2" }
也就是。为标签"TagName1"指定值"Value1"。
为标签"TagName2"指定值"Value2"。我们能够设定随意多的标签。
标签是标准的键值对,也就是能够根据一个键值获得相应的一个值的。SubShader 中的标签是用来决定渲染的次序和子着色器中的其它变量的。
1.1.1 决定渲染次序——队列标签(Queue tag)
我们能够使用 Queue 标签来决定对象被渲染的次序。着色器决定它所归属的对象的渲染队列,不论什么透明渲染器能够通过这个办法保证在全部不透明对象渲染完毕后再进行渲染。
有四种提前定义(predefined)的渲染队列,在提前定义队列之间还能够定义很多其它的队列。这四种提前定义的标签例如以下:
- 后台(Background) - 这个渲染队列在全部队列之前被渲染,被用于渲染天空盒之类的对象。
- 几何体(Geometry,默认值)- 这个队列被用于大多数对象。
不透明的几何体使用这个队列。
- 透明(Transparent) - 这个渲染队列在几何体队列之后被渲染,採用由后到前的次序。
不论什么採用alpha混合的对象(也就是不正确深度缓冲产生写操作的着色器)应该在这里渲染(如玻璃,粒子效果等)
- 覆盖(Overlay) - 这个渲染队列被用于实现叠加效果。
不论什么须要最后渲染的对象应该放置在此处。(如镜头光晕等)
一个使用Tags的示比例如以下:
Shader "Transparent QueueExample"{ SubShader{//写上Tags标签 Tags {"Queue" = "Transparent" } //開始一个通道 Pass{ // 写Shader实体内容 } }}
1.1.2 自己定义中间队列
让我们来举例说明怎样在透明队列中渲染对象。普通情况下,几何体渲染队列为了达到最优的性能优化了对象的绘制次序。而其它渲染队列根据举例排序对象,从最远的对象開始渲染到近期的对象。
而对于特殊的须要。能够使用中间队列来满足。在Unity实现中每个队列都被一个整数的索引值所代表。
后台为1000。几何体为2000,透明为3000,叠加层为4000. 着色器能够自己定义一个队列,如:
Tags { "Queue" ="Geometry+1" }
由于渲染队列是从小到大来数的,这就会使对象在全部不透明的对象渲染之后但却在全部透明物体前被渲染。该渲染队列的索引值为2001。
当我们希望某些对象总是在其它某些对象前被绘制的情况下,这用起来就非常方便了。比方,在绝大多数时候,透明的水总是应该在全部不透明的物体之后并在透明对象前被渲染,这就能够通过中间队列来满足渲染需求。
1.1.3 关于忽略投影标签(IgnoreProjector tag)
后面我们会接触到。若设置IgnoreProjector(忽略投影)标签为"True"。那么使用这个着色器的对象就不会被投影机制(Projectors)所影响。这对半透明的物体来说是一个福利,由于临时没有对他们产生投影的比較合适的办法。那么直接忽略掉即可了。
二、 通道(Pass)相关内容解说
Pass通道块控制被渲染的对象的几何体。其语法定义是这种:
Pass { [Name and Tags] [RenderSetup][TextureSetup] }
基本通道命令包括一个可选的渲染设置命令的列表,和可选的被使用的纹理的列表。
2.1 通道中的名称与标签(Name and tags )
一个通道能定义它的Name 和随意数量的Tags。通过使用tags来告诉渲染引擎在什么时候该怎样渲染他们所期望的效果。语法例如以下:
Tags { "TagName1" ="Value1" "TagName2" = "Value2" }
指定TagName1 的值为Value1 ,TagName2 的值为 Value2 你能够指定非常多自己喜欢的标签,以下会具体来列举。
标签基本上是键-值对的形式。 内部的Pass标签用来控制光照管道(环境光照。顶点光照和像素光照)中pass 的任务和一些其它选项。注意以下的标签必须在pass段内部,而不是在SubShader中被识别。
2.1.1 光照模式标签(LightMode tag)
LightMode 标签定义了Shader的光照模式。具体含义以后会在讲渲染管线时讲到。以下我们先简单了解一下有哪些光照模式可选。以及他们的具体作用:
- Always: 总是渲染。
没有运用光照。
- ForwardBase:用于正向渲染,环境光、方向光和顶点光等
- ForwardAdd:用于正向渲染。用于设定附加的像素光。每个光照相应一个pass
- PrepassBase:用于延迟光照。渲染法线/镜面光。
- PrepassFinal:用于延迟光照。通过结合纹理,光照和自发光渲染终于颜色
- Vertex: 用于顶点光照渲染。当物体没有光照映射时。应用全部的顶点光照
- VertexLMRGBM:用于顶点光照渲染,当物体有光照映射的时候使用顶点光照渲染。在平台上光照映射是RGBM 编码
- VertexLM:用于顶点光照渲染,当物体有光照映射的时候使用顶点光照渲染。在平台上光照映射是double-LDR 编码(移动平台。及老式台式CPU)
- ShadowCaster: 使物体投射阴影。
- ShadowCollector: 为正向渲染对象的路径。将对象的阴影收集到屏幕空间缓冲区中。
2.1.2 条件选项标签 (RequireOptions tag )
若想要在一些外部条件得到满足时某pass才渲染。就能够通过使用RequireOptions标签,它的值是一个空格切割的字符串,眼下由Unity3d支持的选项仅仅有一个,就是渲染植被之时:
SoftVegetation: 假设在QualitySettings中开启渲染软植被(Edit->Project Settings->Quality),则该pass能够渲染
2.2 关于渲染设置 (Render Setup )
通道设定显示硬件的各种状态,比如能打开alpha混合,能使用雾,等等。
这些命令例如以下:
Material { Material Block }
定义一个使用顶点光照管线的材质,详情參考上次我们讲的Material
Lighting On | Off
开启或关闭顶点光照。开启灯光之后,顶点光照才会有作用
Cull Back | Front | Off
设置多边形剔除模式,具体内容后面的文章会解说到。
ZTest (Less | Greater | LEqual | GEqual |Equal | NotEqual | Always)
设置深度測试模式,具体内容后面的文章会解说到。
ZWrite On | Off
设置深度写模式。具体内容后面的文章会解说到。
Fog { Fog Block }
设置雾參数,具体内容后面的文章会解说到。
AlphaTest (Less | Greater | LEqual | GEqual| Equal | NotEqual | Always) CutoffValue
开启alpha測试
Blend SourceBlendMode |DestBlendMode
设置alpha混合模式
Color Color value
设置当顶点光照关闭时所使用的颜色
ColorMask RGB | A | 0 | any combination of R, G, B, A
设置颜色写遮罩。设置为0将关闭全部颜色通道的渲染
Offset OffsetFactor , OffsetUnits
设置深度偏移
SeparateSpecular On | Off
开启或关闭顶点光照相关的平行高光颜色。
ColorMaterial AmbientAndDiffuse | Emission
当计算顶点光照时使用每顶点的颜色
2.3 关于纹理设置(Texture Setup )
在完毕渲染设定后,我们能够指定一定数量的纹理和当使用 SetTexture 命令时所採用的混合模式:
SetTexture [texture property]{ [Combineoptions] }
纹理设置,用于配置固定函数多纹理管线,当自己定义fragment shaders 被使用时。这个设置也就被忽略掉了。
2.4 一些细节
2.4.1 关于每像素光照(Per-pixel Lighting )
每像素光照管线通过多次通道渲染对象来完毕。Unity渲染对象一次来获取阴影色和不论什么顶点光照。然后再在额外的并行通道中渲染出每像素光照的效果。
2.4.2 关于每顶点光照(Per-vertex Lighting)
每顶点光照是标准的Direct3D/OpenGL光照模式,通过计算每个顶点的光照来完毕。
Lighting on命令开启光照。
而我们知道。光照被材质块,颜色材质和平行高光等命令所影响。
2.5 一些高端特效的通道命令
有时候,我们会写一些特殊的通道,要多次重复利用普通的功能或是实现高端的特效。应对这些情况。Unity中就有一些高级点武器能够选用,这里简单讲一讲吧。如今先略微有个概念就好。
2.5.1 UsePass——包括已经写好的通道
UsePass 能够包括来自其它着色器的通道,来降低重复的代码。
比如。在很多像素光照着色器中,阴影色或顶点光照通道在在相应的顶点光照着色器中是同样的。
UsePass命令仅仅是包括了还有一个着色器的给定通道。比如例如以下的命令能够使用内置的高光着色器中的名叫"Base"的通道:
UsePass "Specular/BASE"
而为了让UsePass能够认识到指定的是谁。必须给希望使用的通道命名,弄个身份证。通道中的Name命令就是这个功能:
Name "MyPassName"
2.5.2 GrabPass——捕获屏幕内容到纹理中
GrabPass 能够捕获物体所在位置的屏幕的内容并写入到一个纹理中。通常在靠后的通道中使用。这个纹理能被用于兴许的通道中完毕一些高级图像特效。
一个示比例如以下:
Shader "GrabPassInvert"{SubShader{ //在全部不透明几何体之后绘制 Tags { "Queue" = "Transparent" } //捕获对象后的屏幕到_GrabTexture中 GrabPass { } //用前面捕获的纹理渲染对象,并反相它的颜色 Pass { SetTexture [_GrabTexture] { combine one-texture } } }}
三、纹理(Texturing)相关内容解说
纹理在主要的顶点光照计算完毕之后被应用,这也就是SetTexture 命令必须放置在通道的末尾的原因了。在着色器中通过SetTexture 命令来完毕。
须要注意的是,SetTexture 命令在使用了片段着色器时不会生效;由于在片段着色器下像素操作被全然描写叙述在着色器中。
材质贴图能够用来实现旧式风格的混合器效果。
我们能够在一个通道中使用多个SetTexture命令。 SetTexture全部纹理都是按代码顺序来加入的,也就是如同Photoshop中的图层操作一样。
SetTexture的语法例如以下:
SetTexture [TexturePropertyName] { TextureBlock }
解释:分配一个纹理,当中TexturePropertyName必须为一个纹理。也就是在shader最開始的Properties中的属性。在TextrueBlock中设置怎样应用纹理。即纹理块控制纹理怎样被应用。
而在纹理块中能执行3种命令:合并操作,矩阵操作、与常量颜色进行混合操作。
3.1 纹理合并命令
combine src1 * src2
将源1和源2的元素相乘。结果会比单独输出不论什么一个都要暗
combine src1 + src2
将将源1和源2的元素相加。
结果会比单独输出不论什么一个都要亮
combine src1 - src2
源1 减去 源2
combine src1 +- src2
先相加。然后减去0.5(也就是加入了一个符号)
combine src1 lerp (src2) src3
使用源2的透明度通道值在源3和源1中进行差值。注意差值是反向的:当透明度值是1是使用源1。透明度为0时使用源3
combine src1 * src2 + src3
源1和源2的透明度相乘,然后加上源3
combine src1 * src2 +- src3
源1和源2的透明度相乘,然后和源3做符号加
combine src1 * src2 - src3
源1和源2的透明度相乘,然后和源3相减
当中,全部src属性都能够是previous,constant, primary or texture当中的一个。
-
- Previous 是上一次SetTexture的结果
- Primary 是来自光照计算的颜色或是当它绑定时的顶点颜色
- Texture是在SetTexture中被定义的纹理的颜色
- Constant是被ConstantColor定义的颜色
一些小技巧:
1.上述的公式都均能通过keyword Double 或是 Quad 将终于颜色调高亮度2倍或4倍。
2.全部的src属性,除了差值參数都能被标记一个“-”负号来使终于颜色反相。
3.全部src属性能通过尾随 alpha 标签来表示仅仅取用alpha通道。
3.2 颜色常量命令
ConstantColor color
定义在combine命令中能被使用的常量颜色
3.3 纹理矩阵命令
matrix [MatrixPropertyName]
使用给定矩阵变换纹理坐标
3.4 一些细节
较老的显卡对纹理通常会使用分层的操作方案,而纹理在每一层后被应用一次颜色的改动。对每个纹理,一般来说纹理都是和上一次操作的结果混合,如图:
须要注意的是,对于“纯正”的“固定功能流水线”设备(比方说OpenGL, OpenGL ES 1.1, Wii),每个SetTexture阶段的值被限制为0到1的范围之间。而其它的设备(如Direct3D, OpenGL ES 2.0)中,这个范围就不一定是固定的。
这种情况就可能会影响SetTexture阶段,可能使产生的值高于1.0。
3.4.1 关于分离的透明度和颜色混合(Separate Alpha & Color computation)
在默认情况下。混合公式被同一时候用于计算纹理的RGB通道和透明度。
同一时候,我们也能指定针对透明度来单独计算。比方这样,将RGB操作和Alpha操作隔开:
SetTexture [_MainTex] { combine previous *texture, previous + texture }
如上所述,我们对RGB的颜色做乘然后对Alpha透明度相加
3.4.2 关于反射高光(Specular highlights)
默认情况下primary颜色是漫反射,阴影色和高光颜色(在光线计算中定义)的加和。
假设我们将通道设置中的SeparateSpecular On 写上,高光色便会在混合计算后被加入,而不是之前。
PS:Unity内置的顶点着色器就是加上SeparateSpecular On的。
3.4.3 关于显卡的硬件支持情况说明
我们上篇文章中已经讲到过,一些旧的显示卡不能支持某些纹理混合模式,且不同的卡有不同数目的SetTexture阶段可用。所以我们应该为想支持的显卡来分开写SubShader,适应各种情况 。
PS::支持像素着色器1.1版本号的显卡(即NVIDIA GeForce 3 或更高, ATI Radeon 8500 或更高, Intel 9xx)支持全部的混合器模式。而且能够拥有至少4级渲染阶段。
下表简述了硬件支持情况。
Card 显卡
Stage count
级数
Combiner modes not supported不支持的结合模式
NVIDIA GeForce 3/4Ti and up
4
In OpenGL on Windows, src1*src2-src3 is not supported
NVIDIA TNT2, GeForce 256, GeForce 2, GeForce 4MX
2
In OpenGL on Windows, src1*src2-src3 is not supported
ATI Radeon 9500 and up
4-8
8 in OpenGL, 4 in D3D9
ATI Radeon 8500-9250
4-6
6 in OpenGL, 4 in D3D9
ATI Radeon 7500
3
ATI Rage
2
src1*src2+src3 src1*src2+-src3 src1*src2-src3
四、Shader书写实战
上面讲了一堆一堆的概念和写法,预计大家一遍看下来头都大了。没关系。依然是让我们看一些演示样例Shader的写法。弄清楚上面这一堆堆的概念是怎样应用的。
主要是纹理相关内容的Shader书写
1. Alpha纹理混合
先看看怎样用本文解说的写法,写出一个简单的纹理混合Shader。
首先设置第一个混合器仅仅使用_MainTex,然后使用_BlendTex的Alpha通道来淡入_BlendTex的RGB颜色:
Shader "浅墨Shader编程/Volume3/7.Alpha纹理混合"{ //-------------------------------【属性】----------------------------------------- Properties { _MainTex ("基础纹理(RGB)", 2D) = "white" {} _BlendTex ("混合纹理(RGBA) ", 2D) = "white" {} } //--------------------------------【子着色器】-------------------------------- SubShader { Pass { // 【1】应用主纹理 SetTexture [_MainTex] { combine texture } // 【2】使用相乘操作来进行Alpha纹理混合 SetTexture [_BlendTex] {combine texture * previous} } }}进行混合的两张纹理例如以下:
此Shader编译后赋给材质的效果例如以下:
2.纹理的Alpha通道与自发光相混合
这个着色器使用_MainTex的Alpha来描写叙述什么地方应用光照。它通过分两个阶段应用纹理来实现;第一个阶段,纹理的Alpha值被用来在顶点颜色和纯白色之间混合。
第二阶段。乘入纹理的RGB通道:
Shader "浅墨Shader编程/Volume3/8.纹理的Alpha通道与自发光相混合"{ //-------------------------------【属性】----------------------------------------- Properties { _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) = "red" { } } //--------------------------------【子着色器】---------------------------------- SubShader { Pass { //【1】设置白色的顶点光照 Material { Diffuse (1,1,1,1) Ambient (1,1,1,1) } //【2】开光照 Lighting On //【3】使用纹理的Alpha通道来插值混合颜色(1,1,1,1) SetTexture [_MainTex] { constantColor (1,1,1,1) combine constant lerp(texture) previous } //【4】和纹理相乘 SetTexture [_MainTex] { combine previous * texture } } }}此Shader编译后赋给材质的效果例如以下:
3. 纹理Alpha与自发光混合可调色版
这次我们给出一个Color属性。让自发光颜色可调:
Shader "浅墨Shader编程/Volume3/9.纹理Alpha与自发光混合可调色版" { //-------------------------------【属性】--------------------------------------- Properties { _IlluminCol ("自发光(RGB)", Color) = (1,1,1,1) _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) = "white" {} } //--------------------------------【子着色器】-------------------------------- SubShader { Pass { //【1】设置白色的顶点光照 Material { Diffuse (1,1,1,1) Ambient (1,1,1,1) } //【2】开启光照 Lighting On // 【3】将自发光颜色混合上纹理 SetTexture [_MainTex] { // 使颜色属性进入混合器 constantColor [_IlluminCol] // 使用纹理的alpha通道混合顶点颜色 combine constant lerp(texture) previous } // 【4】乘以纹理 SetTexture [_MainTex] {combine previous * texture } } }}此Shader编译后赋给材质的效果例如以下,能够自由调节颜色:
4. 顶点光照+纹理Alpha自发光混合
我们将本文中介绍的知识点和上一篇文章中顶点光照相关的内容结合起来,主要是在Pass中加入了一句,让顶点光照能够和纹理颜色结合起来:
//---------------------开启独立镜面反射----------------SeparateSpecular On完整的Shader代码例如以下:
Shader "浅墨Shader编程/Volume3/10.顶点光照+纹理Alpha自发光混合" { //-------------------------------【属性】--------------------------------------- Properties { _IlluminCol ("自发光色", Color) = (1,1,1,1) _Color ("主颜色", Color) = (1,1,1,0) _SpecColor ("高光颜色", Color) = (1,1,1,1) _Emission ("光泽颜色", Color) = (0,0,0,0) _Shininess ("光泽度", Range (0.01, 1)) = 0.7 _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) = "white" { } } //--------------------------------【子着色器】-------------------------------- SubShader { Pass { //【1】设置顶点光照值 Material { Diffuse [_Color] Ambient [_Color] Shininess [_Shininess] Specular [_SpecColor] Emission [_Emission] } //【2】开启光照 Lighting On //【3】---------------------开启独立镜面反射---------------- SeparateSpecular On // 【4】将自发光颜色混合上纹理 SetTexture [_MainTex] { // 使颜色属性进入混合器 constantColor [_IlluminCol] // 使用纹理的alpha通道插值混合顶点颜色 combine constant lerp(texture) previous } // 【5】乘上纹理 SetTexture [_MainTex] { combine previous * texture } //【6】乘以顶点纹理 SetTexture [_MainTex] { Combine previous * primary DOUBLE, previous * primary} } }}此Shader编译后赋给材质的效果例如以下:
5. 顶点光照+自发光混合+纹理混合
在刚刚介绍的第四个Shader的基础上。加上第一个Shader中解说的纹理混合。就做成了本文终于的顶点光照+自发光混合+纹理混合Shader:
Shader "浅墨Shader编程/Volume3/11.顶点光照+自发光混合+纹理混合" { //-------------------------------【属性】----------------------------------------- Properties { _IlluminCol ("自发光色", Color) = (0,0,0,0) _Color ("主颜色", Color) = (1,1,1,0) _SpecColor ("高光颜色", Color) = (1,1,1,1) _Emission ("光泽颜色", Color) = (0,0,0,0) _Shininess ("光泽度", Range (0.01, 1)) = 0.7 _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) = "white" {} _BlendTex ("混合纹理(RGBA) ", 2D) = "white" {} } //--------------------------------【子着色器】-------------------------------- SubShader { //----------------通道--------------- Pass { //【1】设置顶点光照值 Material { //可调节的漫反射光和环境光反射颜色 Diffuse [_Color] Ambient [_Color] //光泽度 Shininess [_Shininess] //高光颜色 Specular [_SpecColor] //自发光颜色 Emission [_Emission] } //【2】开启光照 Lighting On //【3】--------------开启独立镜面反射-------------- SeparateSpecular On //【4】将自发光颜色混合上纹理 SetTexture [_MainTex] { // 使颜色属性进入混合器 constantColor [_IlluminCol] // 使用纹理的alpha通道插值混合顶点颜色 combine constant lerp(texture) previous } //【5】乘上基本纹理 SetTexture [_MainTex] { combine previous * texture } //【6】使用差值操作混合Alpha纹理 SetTexture [_BlendTex] { combine previous*texture } //【7】乘以顶点纹理 SetTexture [_MainTex] {Combine previous * primary DOUBLE, previous * primary } } }}此Shader编译后赋给材质的效果例如以下:
换些高光颜色玩一玩:
正常白色高光版:
五、终于游戏场景效果演示——光之城堡
上一次我们处于盛大的暴风雪之中。这次的场景。最好还是让我们来到梦幻又神奇的的光之城堡,领略一番不一样的味道。以大师级美工鬼斧神工的场景作品为基础,浅墨加入了音乐,并调整了场景布局,加入了很多其它高级特效,于是便得到了如此这次让人颇显震撼的梦幻场景。
执行游戏,我们来到繁花盛开的光之城堡:
抬头,看阳光透过树梢:
低头。是花草摇曳:
左側,是通向森林的路:
右側。是绵延的花海:
来到雾气氤氲的森林:
回望,一线天:
阳光透过树梢,满满的生机:
绚烂的花海:
走出森林。前方是一片幽暗之地:
走进去,原来是一个小型古墓:
在小山坡上远眺:
来一张上帝视角。大好河山尽收眼底:
依然是一张今天所讲的Shader的全家福:
透过洞口的可见光线偏移:
OK,美图就放这么多。游戏场景可执行的exe能够在文章开头中提供的链接下载。
另外。本次的project加载后会报Mismatched serialization in thebuiltin class 'Mesh'. (Read 100788 bytes but expected 100789 bytes)系列错误,没关系。这是unity4.6之前版本号的一个bug,不影响正常使用,clear掉即可了。
本篇文章的演示样例程序请点击此处下载:
浅墨近期事情实在是有些多,所以仅仅好下周停更一次了。
下下个周一,我们不见不散。