【浅墨Unity3D Shader编程】之三 光之城堡篇:子着色器、通道与标签的写法 & 纹理混合_u3dshader 材质通道-程序员宅基地

技术标签: unity  shader  unity3d  



本系列文章由@浅墨_毛星云 出品,转载请注明出处。  

文章链接: http://blog.csdn.net/poem_qianmo/article/details/41175585

作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442

邮箱: [email protected]


 

 

本文介绍了Unity中子着色器、通道和标签相关的详细概念与写法,以及纹理的设置方法,基本的纹理混合写法,写了5个Shader作为本文Shader讲解的实战内容,最后创建了一个梦幻的光之城堡场景进行了Shader的测试。依旧是国际惯例,先上本文配套程序的截图。


光之城堡:


 

 

山坡上远眺:

 


通向森林的路:



古墓:


 

雾气氤氲的森林:

 

 

来一张上帝视角:



OK,图先就上这么多。文章末尾有更多的运行截图,并提供了原工程的下载。可运行的exe下载在这里:

 

【游戏场景可运行的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的示例如下:


[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "Transparent QueueExample"  
  2. {  
  3.     SubShader  
  4. {  
  5. //写上Tags标签  
  6.        Tags { "Queue" = "Transparent" }  
  7.               //开始一个通道  
  8.        Pass  
  9. {  
  10.            // 写Shader实体内容  
  11.        }  
  12.     }  
  13. }  


 




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 可以捕获物体所在位置的屏幕的内容并写入到一个纹理中,通常在靠后的通道中使用,这个纹理能被用于后续的通道中完成一些高级图像特效。

 

 

一个示例如下:

 

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "GrabPassInvert"  
  2. {  
  3. SubShader  
  4. {  
  5.       //在所有不透明几何体之后绘制  
  6.        Tags { "Queue" = "Transparent" }  
  7.    
  8.       //捕获对象后的屏幕到_GrabTexture中  
  9.        GrabPass { }  
  10.    
  11.        //用前面捕获的纹理渲染对象,并反相它的颜色  
  12.        Pass  
  13. <span style="white-space:pre">  </span>{  
  14.            SetTexture [_GrabTexture] { combine one-texture }  
  15.        }  
  16.     }  
  17. }  


 

 

 

 



三、纹理(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 (src2src3

使用源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.上述的公式都均能通过关键字 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颜色:

 

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "浅墨Shader编程/Volume3/7.Alpha纹理混合"  
  2. {  
  3.     //-------------------------------【属性】-----------------------------------------  
  4.     Properties   
  5.     {  
  6.         _MainTex ("基础纹理(RGB)", 2D) = "white" {}  
  7.         _BlendTex ("混合纹理(RGBA) ", 2D) = "white" {}  
  8.     }  
  9.   
  10.     //--------------------------------【子着色器】--------------------------------  
  11.     SubShader   
  12.     {  
  13.         Pass   
  14.         {  
  15.             // 【1】应用主纹理  
  16.             SetTexture [_MainTex] { combine texture }  
  17.             // 【2】使用相乘操作来进行Alpha纹理混合  
  18.             SetTexture [_BlendTex] {combine texture * previous}  
  19.         }  
  20.     }  
  21. }  


进行混合的两张纹理如下:

  


 

此Shader编译后赋给材质的效果如下:

 



 

 

 

2.纹理的Alpha通道与自发光相混合

 

这个着色器使用_MainTex的Alpha来描述什么地方应用光照。它通过分两个阶段应用纹理来实现;第一个阶段,纹理的Alpha值被用来在顶点颜色和纯白色之间混合。第二阶段,乘入纹理的RGB通道:

 

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "浅墨Shader编程/Volume3/8.纹理的Alpha通道与自发光相混合"  
  2. {  
  3.     //-------------------------------【属性】-----------------------------------------  
  4.     Properties  
  5.     {  
  6.         _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) =  "red" { }  
  7.     }  
  8.   
  9.     //--------------------------------【子着色器】----------------------------------  
  10.     SubShader   
  11.     {  
  12.         Pass   
  13.         {  
  14.             //【1】设置白色的顶点光照  
  15.             Material   
  16.             {  
  17.                 Diffuse (1,1,1,1)  
  18.                 Ambient (1,1,1,1)  
  19.             }  
  20.   
  21.             //【2】开光照  
  22.             Lighting On  
  23.   
  24.             //【3】使用纹理的Alpha通道来插值混合颜色(1,1,1,1)  
  25.             SetTexture [_MainTex]   
  26.             {  
  27.                 constantColor (1,1,1,1)  
  28.                 combine constant lerp(texture) previous  
  29.             }  
  30.   
  31.             //【4】和纹理相乘  
  32.             SetTexture [_MainTex]   
  33.             {  
  34.                 combine previous * texture  
  35.             }  
  36.         }  
  37.     }  
  38. }  


此Shader编译后赋给材质的效果如下:

 

 

 

 

 

 

3. 纹理Alpha与自发光混合可调色版

 

这次我们给出一个Color属性,让自发光颜色可调:

 

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "浅墨Shader编程/Volume3/9.纹理Alpha与自发光混合可调色版"   
  2. {  
  3.     //-------------------------------【属性】---------------------------------------  
  4.     Properties   
  5.     {  
  6.         _IlluminCol ("自发光(RGB)", Color) = (1,1,1,1)  
  7.         _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) = "white" {}  
  8.     }  
  9.   
  10.     //--------------------------------【子着色器】--------------------------------  
  11.     SubShader   
  12.     {  
  13.         Pass   
  14.         {  
  15.             //【1】设置白色的顶点光照  
  16.             Material   
  17.             {  
  18.                 Diffuse (1,1,1,1)  
  19.                 Ambient (1,1,1,1)  
  20.             }  
  21.   
  22.             //【2】开启光照  
  23.             Lighting On  
  24.   
  25.             // 【3】将自发光颜色混合上纹理  
  26.             SetTexture [_MainTex]   
  27.             {  
  28.                 // 使颜色属性进入混合器  
  29.                 constantColor [_IlluminCol]  
  30.                 // 使用纹理的alpha通道混合顶点颜色  
  31.                 combine constant lerp(texture) previous  
  32.             }  
  33.   
  34.             // 【4】乘以纹理  
  35.             SetTexture [_MainTex] {combine previous * texture }  
  36.   
  37.         }  
  38.     }  
  39. }  


此Shader编译后赋给材质的效果如下,可以自由调节颜色:

 


 


 

4. 顶点光照+纹理Alpha自发光混合

 

 

我们将本文中介绍的知识点和上一篇文章中顶点光照相关的内容结合起来,主要是在Pass中添加了一句,让顶点光照可以和纹理颜色结合起来:

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. //---------------------开启独立镜面反射----------------  
  2. SeparateSpecular On  


完整的Shader代码如下:

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "浅墨Shader编程/Volume3/10.顶点光照+纹理Alpha自发光混合"   
  2. {  
  3.     //-------------------------------【属性】---------------------------------------  
  4.     Properties   
  5.     {  
  6.         _IlluminCol ("自发光色", Color) = (1,1,1,1)  
  7.         _Color ("主颜色", Color) = (1,1,1,0)  
  8.         _SpecColor ("高光颜色", Color) = (1,1,1,1)  
  9.         _Emission ("光泽颜色", Color) = (0,0,0,0)  
  10.         _Shininess ("光泽度", Range (0.01, 1)) = 0.7  
  11.         _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) = "white" { }  
  12.     }  
  13.   
  14.     //--------------------------------【子着色器】--------------------------------  
  15.     SubShader   
  16.     {  
  17.         Pass   
  18.         {  
  19.             //【1】设置顶点光照值  
  20.             Material   
  21.             {  
  22.                 Diffuse [_Color]  
  23.                 Ambient [_Color]  
  24.                 Shininess [_Shininess]  
  25.                 Specular [_SpecColor]  
  26.                 Emission [_Emission]  
  27.             }  
  28.   
  29.             //【2】开启光照  
  30.             Lighting On  
  31.   
  32.             //【3】---------------------开启独立镜面反射----------------  
  33.             SeparateSpecular On  
  34.   
  35.             // 【4】将自发光颜色混合上纹理  
  36.             SetTexture [_MainTex]   
  37.             {  
  38.                 // 使颜色属性进入混合器  
  39.                 constantColor [_IlluminCol]  
  40.                 // 使用纹理的alpha通道插值混合顶点颜色  
  41.                 combine constant lerp(texture) previous  
  42.             }  
  43.   
  44.             // 【5】乘上纹理  
  45.             SetTexture [_MainTex] {  combine previous * texture   }  
  46.   
  47.             //【6】乘以顶点纹理  
  48.              SetTexture [_MainTex]  {  Combine previous * primary DOUBLE, previous * primary}  
  49.   
  50.         }  
  51.   
  52.     }  
  53. }   

此Shader编译后赋给材质的效果如下:

 

 

 

 

 

5. 顶点光照+自发光混合+纹理混合


在刚刚介绍的第四个Shader的基础上,加上第一个Shader中讲解的纹理混合,就做成了本文最终的顶点光照+自发光混合+纹理混合Shader:

 

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "浅墨Shader编程/Volume3/11.顶点光照+自发光混合+纹理混合"   
  2. {  
  3.     //-------------------------------【属性】-----------------------------------------  
  4.     Properties   
  5.     {  
  6.         _IlluminCol ("自发光色", Color) = (0,0,0,0)  
  7.         _Color ("主颜色", Color) = (1,1,1,0)  
  8.         _SpecColor ("高光颜色", Color) = (1,1,1,1)  
  9.         _Emission ("光泽颜色", Color) = (0,0,0,0)  
  10.         _Shininess ("光泽度", Range (0.01, 1)) = 0.7  
  11.         _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) = "white" {}  
  12.     _BlendTex ("混合纹理(RGBA) ", 2D) = "white" {}  
  13.     }  
  14.   
  15.     //--------------------------------【子着色器】--------------------------------  
  16.     SubShader  
  17.     {  
  18.         //----------------通道---------------  
  19.         Pass  
  20.         {  
  21.             //【1】设置顶点光照值  
  22.             Material  
  23.             {  
  24.                 //可调节的漫反射光和环境光反射颜色  
  25.                 Diffuse [_Color]  
  26.                 Ambient [_Color]  
  27.                 //光泽度  
  28.                 Shininess [_Shininess]  
  29.                 //高光颜色  
  30.                 Specular [_SpecColor]  
  31.                 //自发光颜色  
  32.                 Emission [_Emission]  
  33.             }  
  34.   
  35.             //【2】开启光照  
  36.             Lighting On  
  37.             //【3】--------------开启独立镜面反射--------------  
  38.             SeparateSpecular On  
  39.   
  40.   
  41.             //【4】将自发光颜色混合上纹理  
  42.             SetTexture [_MainTex]   
  43.             {  
  44.                 // 使颜色属性进入混合器  
  45.                 constantColor [_IlluminCol]  
  46.                 // 使用纹理的alpha通道插值混合顶点颜色  
  47.                 combine constant lerp(texture) previous  
  48.             }  
  49.   
  50.             //【5】乘上基本纹理  
  51.             SetTexture [_MainTex] { combine previous * texture  }  
  52.   
  53.             //【6】使用差值操作混合Alpha纹理  
  54.             SetTexture [_BlendTex] { combine previous*texture }  
  55.   
  56.             //【7】乘以顶点纹理  
  57.             SetTexture [_MainTex] {Combine previous * primary DOUBLE, previous * primary }  
  58.   
  59.         }  
  60.     }  
  61. }   

此Shader编译后赋给材质的效果如下:

 


换些高光颜色玩一玩:

 

 

 正常白色高光版:



 

 




五、最终游戏场景效果演示——光之城堡

 



上一次我们处于盛大的暴风雪之中,这次的场景,不妨让我们来到梦幻又神秘的的光之城堡,领略一番不一样的味道。以大师级美工鬼斧神工的场景作品为基础,浅墨加入了音乐,并调整了场景布局,加入了更多高级特效,于是便得到了如此这次让人颇显震撼的梦幻场景。

运行游戏,我们来到繁花盛开的光之城堡:




抬头,看阳光透过树梢:

 



低头,是花草摇曳:


 

 

左侧,是通向森林的路:

 


右侧,是绵延的花海:


 

来到雾气氤氲的森林:

 


回望,一线天:

 


阳光透过树梢,满满的生机:

 


绚烂的花海:

 

 

走出森林,前方是一片幽暗之地:

 


走进去,原来是一个小型古墓:


 


在小山坡上远眺:

 

 

 

来一张上帝视角,大好河山尽收眼底:

 


依旧是一张今天所讲的Shader的全家福:­

 

 

透过洞口的可见光线偏移:

 

 

OK,美图就放这么多。游戏场景可运行的exe可以在文章开头中提供的链接下载。

 

另外,本次的工程载入后会报Mismatched serialization in thebuiltin class 'Mesh'. (Read 100788 bytes but expected 100789 bytes)系列错误,没关系,这是unity4.6之前版本的一个bug,不影响正常使用,clear掉就行了。

 


 

本篇文章的示例程序请点击此处下载:

 

【浅墨Unity3D Shader编程】之三 光之城堡篇配套Unity工程下载

 

 

 

浅墨最近事情实在是有些多,所以只好下周停更一次了。

下下个周一,我们不见不散。

本系列文章由@浅墨_毛星云 出品,转载请注明出处。  

文章链接: http://blog.csdn.net/poem_qianmo/article/details/41175585

作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442

邮箱: [email protected]


 

 

本文介绍了Unity中子着色器、通道和标签相关的详细概念与写法,以及纹理的设置方法,基本的纹理混合写法,写了5个Shader作为本文Shader讲解的实战内容,最后创建了一个梦幻的光之城堡场景进行了Shader的测试。依旧是国际惯例,先上本文配套程序的截图。


光之城堡:


 

 

山坡上远眺:

 


通向森林的路:



古墓:


 

雾气氤氲的森林:

 

 

来一张上帝视角:



OK,图先就上这么多。文章末尾有更多的运行截图,并提供了原工程的下载。可运行的exe下载在这里:

 

【游戏场景可运行的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的示例如下:


[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "Transparent QueueExample"  
  2. {  
  3.     SubShader  
  4. {  
  5. //写上Tags标签  
  6.        Tags { "Queue" = "Transparent" }  
  7.               //开始一个通道  
  8.        Pass  
  9. {  
  10.            // 写Shader实体内容  
  11.        }  
  12.     }  
  13. }  


 




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 可以捕获物体所在位置的屏幕的内容并写入到一个纹理中,通常在靠后的通道中使用,这个纹理能被用于后续的通道中完成一些高级图像特效。

 

 

一个示例如下:

 

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "GrabPassInvert"  
  2. {  
  3. SubShader  
  4. {  
  5.       //在所有不透明几何体之后绘制  
  6.        Tags { "Queue" = "Transparent" }  
  7.    
  8.       //捕获对象后的屏幕到_GrabTexture中  
  9.        GrabPass { }  
  10.    
  11.        //用前面捕获的纹理渲染对象,并反相它的颜色  
  12.        Pass  
  13. <span style="white-space:pre">  </span>{  
  14.            SetTexture [_GrabTexture] { combine one-texture }  
  15.        }  
  16.     }  
  17. }  


 

 

 

 



三、纹理(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 (src2src3

使用源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.上述的公式都均能通过关键字 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颜色:

 

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "浅墨Shader编程/Volume3/7.Alpha纹理混合"  
  2. {  
  3.     //-------------------------------【属性】-----------------------------------------  
  4.     Properties   
  5.     {  
  6.         _MainTex ("基础纹理(RGB)", 2D) = "white" {}  
  7.         _BlendTex ("混合纹理(RGBA) ", 2D) = "white" {}  
  8.     }  
  9.   
  10.     //--------------------------------【子着色器】--------------------------------  
  11.     SubShader   
  12.     {  
  13.         Pass   
  14.         {  
  15.             // 【1】应用主纹理  
  16.             SetTexture [_MainTex] { combine texture }  
  17.             // 【2】使用相乘操作来进行Alpha纹理混合  
  18.             SetTexture [_BlendTex] {combine texture * previous}  
  19.         }  
  20.     }  
  21. }  


进行混合的两张纹理如下:

  


 

此Shader编译后赋给材质的效果如下:

 



 

 

 

2.纹理的Alpha通道与自发光相混合

 

这个着色器使用_MainTex的Alpha来描述什么地方应用光照。它通过分两个阶段应用纹理来实现;第一个阶段,纹理的Alpha值被用来在顶点颜色和纯白色之间混合。第二阶段,乘入纹理的RGB通道:

 

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "浅墨Shader编程/Volume3/8.纹理的Alpha通道与自发光相混合"  
  2. {  
  3.     //-------------------------------【属性】-----------------------------------------  
  4.     Properties  
  5.     {  
  6.         _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) =  "red" { }  
  7.     }  
  8.   
  9.     //--------------------------------【子着色器】----------------------------------  
  10.     SubShader   
  11.     {  
  12.         Pass   
  13.         {  
  14.             //【1】设置白色的顶点光照  
  15.             Material   
  16.             {  
  17.                 Diffuse (1,1,1,1)  
  18.                 Ambient (1,1,1,1)  
  19.             }  
  20.   
  21.             //【2】开光照  
  22.             Lighting On  
  23.   
  24.             //【3】使用纹理的Alpha通道来插值混合颜色(1,1,1,1)  
  25.             SetTexture [_MainTex]   
  26.             {  
  27.                 constantColor (1,1,1,1)  
  28.                 combine constant lerp(texture) previous  
  29.             }  
  30.   
  31.             //【4】和纹理相乘  
  32.             SetTexture [_MainTex]   
  33.             {  
  34.                 combine previous * texture  
  35.             }  
  36.         }  
  37.     }  
  38. }  


此Shader编译后赋给材质的效果如下:

 

 

 

 

 

 

3. 纹理Alpha与自发光混合可调色版

 

这次我们给出一个Color属性,让自发光颜色可调:

 

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "浅墨Shader编程/Volume3/9.纹理Alpha与自发光混合可调色版"   
  2. {  
  3.     //-------------------------------【属性】---------------------------------------  
  4.     Properties   
  5.     {  
  6.         _IlluminCol ("自发光(RGB)", Color) = (1,1,1,1)  
  7.         _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) = "white" {}  
  8.     }  
  9.   
  10.     //--------------------------------【子着色器】--------------------------------  
  11.     SubShader   
  12.     {  
  13.         Pass   
  14.         {  
  15.             //【1】设置白色的顶点光照  
  16.             Material   
  17.             {  
  18.                 Diffuse (1,1,1,1)  
  19.                 Ambient (1,1,1,1)  
  20.             }  
  21.   
  22.             //【2】开启光照  
  23.             Lighting On  
  24.   
  25.             // 【3】将自发光颜色混合上纹理  
  26.             SetTexture [_MainTex]   
  27.             {  
  28.                 // 使颜色属性进入混合器  
  29.                 constantColor [_IlluminCol]  
  30.                 // 使用纹理的alpha通道混合顶点颜色  
  31.                 combine constant lerp(texture) previous  
  32.             }  
  33.   
  34.             // 【4】乘以纹理  
  35.             SetTexture [_MainTex] {combine previous * texture }  
  36.   
  37.         }  
  38.     }  
  39. }  


此Shader编译后赋给材质的效果如下,可以自由调节颜色:

 


 


 

4. 顶点光照+纹理Alpha自发光混合

 

 

我们将本文中介绍的知识点和上一篇文章中顶点光照相关的内容结合起来,主要是在Pass中添加了一句,让顶点光照可以和纹理颜色结合起来:

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. //---------------------开启独立镜面反射----------------  
  2. SeparateSpecular On  


完整的Shader代码如下:

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "浅墨Shader编程/Volume3/10.顶点光照+纹理Alpha自发光混合"   
  2. {  
  3.     //-------------------------------【属性】---------------------------------------  
  4.     Properties   
  5.     {  
  6.         _IlluminCol ("自发光色", Color) = (1,1,1,1)  
  7.         _Color ("主颜色", Color) = (1,1,1,0)  
  8.         _SpecColor ("高光颜色", Color) = (1,1,1,1)  
  9.         _Emission ("光泽颜色", Color) = (0,0,0,0)  
  10.         _Shininess ("光泽度", Range (0.01, 1)) = 0.7  
  11.         _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) = "white" { }  
  12.     }  
  13.   
  14.     //--------------------------------【子着色器】--------------------------------  
  15.     SubShader   
  16.     {  
  17.         Pass   
  18.         {  
  19.             //【1】设置顶点光照值  
  20.             Material   
  21.             {  
  22.                 Diffuse [_Color]  
  23.                 Ambient [_Color]  
  24.                 Shininess [_Shininess]  
  25.                 Specular [_SpecColor]  
  26.                 Emission [_Emission]  
  27.             }  
  28.   
  29.             //【2】开启光照  
  30.             Lighting On  
  31.   
  32.             //【3】---------------------开启独立镜面反射----------------  
  33.             SeparateSpecular On  
  34.   
  35.             // 【4】将自发光颜色混合上纹理  
  36.             SetTexture [_MainTex]   
  37.             {  
  38.                 // 使颜色属性进入混合器  
  39.                 constantColor [_IlluminCol]  
  40.                 // 使用纹理的alpha通道插值混合顶点颜色  
  41.                 combine constant lerp(texture) previous  
  42.             }  
  43.   
  44.             // 【5】乘上纹理  
  45.             SetTexture [_MainTex] {  combine previous * texture   }  
  46.   
  47.             //【6】乘以顶点纹理  
  48.              SetTexture [_MainTex]  {  Combine previous * primary DOUBLE, previous * primary}  
  49.   
  50.         }  
  51.   
  52.     }  
  53. }   

此Shader编译后赋给材质的效果如下:

 

 

 

 

 

5. 顶点光照+自发光混合+纹理混合


在刚刚介绍的第四个Shader的基础上,加上第一个Shader中讲解的纹理混合,就做成了本文最终的顶点光照+自发光混合+纹理混合Shader:

 

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Shader "浅墨Shader编程/Volume3/11.顶点光照+自发光混合+纹理混合"   
  2. {  
  3.     //-------------------------------【属性】-----------------------------------------  
  4.     Properties   
  5.     {  
  6.         _IlluminCol ("自发光色", Color) = (0,0,0,0)  
  7.         _Color ("主颜色", Color) = (1,1,1,0)  
  8.         _SpecColor ("高光颜色", Color) = (1,1,1,1)  
  9.         _Emission ("光泽颜色", Color) = (0,0,0,0)  
  10.         _Shininess ("光泽度", Range (0.01, 1)) = 0.7  
  11.         _MainTex ("基础纹理 (RGB)-自发光(A)", 2D) = "white" {}  
  12.     _BlendTex ("混合纹理(RGBA) ", 2D) = "white" {}  
  13.     }  
  14.   
  15.     //--------------------------------【子着色器】--------------------------------  
  16.     SubShader  
  17.     {  
  18.         //----------------通道---------------  
  19.         Pass  
  20.         {  
  21.             //【1】设置顶点光照值  
  22.             Material  
  23.             {  
  24.                 //可调节的漫反射光和环境光反射颜色  
  25.                 Diffuse [_Color]  
  26.                 Ambient [_Color]  
  27.                 //光泽度  
  28.                 Shininess [_Shininess]  
  29.                 //高光颜色  
  30.                 Specular [_SpecColor]  
  31.                 //自发光颜色  
  32.                 Emission [_Emission]  
  33.             }  
  34.   
  35.             //【2】开启光照  
  36.             Lighting On  
  37.             //【3】--------------开启独立镜面反射--------------  
  38.             SeparateSpecular On  
  39.   
  40.   
  41.             //【4】将自发光颜色混合上纹理  
  42.             SetTexture [_MainTex]   
  43.             {  
  44.                 // 使颜色属性进入混合器  
  45.                 constantColor [_IlluminCol]  
  46.                 // 使用纹理的alpha通道插值混合顶点颜色  
  47.                 combine constant lerp(texture) previous  
  48.             }  
  49.   
  50.             //【5】乘上基本纹理  
  51.             SetTexture [_MainTex] { combine previous * texture  }  
  52.   
  53.             //【6】使用差值操作混合Alpha纹理  
  54.             SetTexture [_BlendTex] { combine previous*texture }  
  55.   
  56.             //【7】乘以顶点纹理  
  57.             SetTexture [_MainTex] {Combine previous * primary DOUBLE, previous * primary }  
  58.   
  59.         }  
  60.     }  
  61. }   

此Shader编译后赋给材质的效果如下:

 


换些高光颜色玩一玩:

 

 

 正常白色高光版:



 

 




五、最终游戏场景效果演示——光之城堡

 



上一次我们处于盛大的暴风雪之中,这次的场景,不妨让我们来到梦幻又神秘的的光之城堡,领略一番不一样的味道。以大师级美工鬼斧神工的场景作品为基础,浅墨加入了音乐,并调整了场景布局,加入了更多高级特效,于是便得到了如此这次让人颇显震撼的梦幻场景。

运行游戏,我们来到繁花盛开的光之城堡:




抬头,看阳光透过树梢:

 



低头,是花草摇曳:


 

 

左侧,是通向森林的路:

 


右侧,是绵延的花海:


 

来到雾气氤氲的森林:

 


回望,一线天:

 


阳光透过树梢,满满的生机:

 


绚烂的花海:

 

 

走出森林,前方是一片幽暗之地:

 


走进去,原来是一个小型古墓:


 


在小山坡上远眺:

 

 

 

来一张上帝视角,大好河山尽收眼底:

 


依旧是一张今天所讲的Shader的全家福:­

 

 

透过洞口的可见光线偏移:

 

 

OK,美图就放这么多。游戏场景可运行的exe可以在文章开头中提供的链接下载。

 

另外,本次的工程载入后会报Mismatched serialization in thebuiltin class 'Mesh'. (Read 100788 bytes but expected 100789 bytes)系列错误,没关系,这是unity4.6之前版本的一个bug,不影响正常使用,clear掉就行了。

 


 

本篇文章的示例程序请点击此处下载:

 

【浅墨Unity3D Shader编程】之三 光之城堡篇配套Unity工程下载

 

 

 

浅墨最近事情实在是有些多,所以只好下周停更一次了。

下下个周一,我们不见不散。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/liangyuqi123456/article/details/50696252

智能推荐

hive使用适用场景_大数据入门:Hive应用场景-程序员宅基地

文章浏览阅读5.8k次。在大数据的发展当中,大数据技术生态的组件,也在不断地拓展开来,而其中的Hive组件,作为Hadoop的数据仓库工具,可以实现对Hadoop集群当中的大规模数据进行相应的数据处理。今天我们的大数据入门分享,就主要来讲讲,Hive应用场景。关于Hive,首先需要明确的一点就是,Hive并非数据库,Hive所提供的数据存储、查询和分析功能,本质上来说,并非传统数据库所提供的存储、查询、分析功能。Hive..._hive应用场景

zblog采集-织梦全自动采集插件-织梦免费采集插件_zblog 网页采集插件-程序员宅基地

文章浏览阅读496次。Zblog是由Zblog开发团队开发的一款小巧而强大的基于Asp和PHP平台的开源程序,但是插件市场上的Zblog采集插件,没有一款能打的,要么就是没有SEO文章内容处理,要么就是功能单一。很少有适合SEO站长的Zblog采集。人们都知道Zblog采集接口都是对Zblog采集不熟悉的人做的,很多人采取模拟登陆的方法进行发布文章,也有很多人直接操作数据库发布文章,然而这些都或多或少的产生各种问题,发布速度慢、文章内容未经严格过滤,导致安全性问题、不能发Tag、不能自动创建分类等。但是使用Zblog采._zblog 网页采集插件

Flink学习四:提交Flink运行job_flink定时运行job-程序员宅基地

文章浏览阅读2.4k次,点赞2次,收藏2次。restUI页面提交1.1 添加上传jar包1.2 提交任务job1.3 查看提交的任务2. 命令行提交./flink-1.9.3/bin/flink run -c com.qu.wc.StreamWordCount -p 2 FlinkTutorial-1.0-SNAPSHOT.jar3. 命令行查看正在运行的job./flink-1.9.3/bin/flink list4. 命令行查看所有job./flink-1.9.3/bin/flink list --all._flink定时运行job

STM32-LED闪烁项目总结_嵌入式stm32闪烁led实验总结-程序员宅基地

文章浏览阅读1k次,点赞2次,收藏6次。这个项目是基于STM32的LED闪烁项目,主要目的是让学习者熟悉STM32的基本操作和编程方法。在这个项目中,我们将使用STM32作为控制器,通过对GPIO口的控制实现LED灯的闪烁。这个STM32 LED闪烁的项目是一个非常简单的入门项目,但它可以帮助学习者熟悉STM32的编程方法和GPIO口的使用。在这个项目中,我们通过对GPIO口的控制实现了LED灯的闪烁。LED闪烁是STM32入门课程的基础操作之一,它旨在教学生如何使用STM32开发板控制LED灯的闪烁。_嵌入式stm32闪烁led实验总结

Debezium安装部署和将服务托管到systemctl-程序员宅基地

文章浏览阅读63次。本文介绍了安装和部署Debezium的详细步骤,并演示了如何将Debezium服务托管到systemctl以进行方便的管理。本文将详细介绍如何安装和部署Debezium,并将其服务托管到systemctl。解压缩后,将得到一个名为"debezium"的目录,其中包含Debezium的二进制文件和其他必要的资源。注意替换"ExecStart"中的"/path/to/debezium"为实际的Debezium目录路径。接下来,需要下载Debezium的压缩包,并将其解压到所需的目录。

Android 控制屏幕唤醒常亮或熄灭_android实现拿起手机亮屏-程序员宅基地

文章浏览阅读4.4k次。需求:在诗词曲文项目中,诗词整篇朗读的时候,文章没有读完会因为屏幕熄灭停止朗读。要求:在文章没有朗读完毕之前屏幕常亮,读完以后屏幕常亮关闭;1.权限配置:设置电源管理的权限。

随便推点

目标检测简介-程序员宅基地

文章浏览阅读2.3k次。目标检测简介、评估标准、经典算法_目标检测

记SQL server安装后无法连接127.0.0.1解决方法_sqlserver 127 0 01 无法连接-程序员宅基地

文章浏览阅读6.3k次,点赞4次,收藏9次。实训时需要安装SQL server2008 R所以我上网上找了一个.exe 的安装包链接:https://pan.baidu.com/s/1_FkhB8XJy3Js_rFADhdtmA提取码:ztki注:解压后1.04G安装时Microsoft需下载.NET,更新安装后会自动安装如下:点击第一个傻瓜式安装,唯一注意的是在修改路径的时候如下不可修改:到安装实例的时候就可以修改啦数据..._sqlserver 127 0 01 无法连接

js 获取对象的所有key值,用来遍历_js 遍历对象的key-程序员宅基地

文章浏览阅读7.4k次。1. Object.keys(item); 获取到了key之后就可以遍历的时候直接使用这个进行遍历所有的key跟valuevar infoItem={ name:'xiaowu', age:'18',}//的出来的keys就是[name,age]var keys=Object.keys(infoItem);2. 通常用于以下实力中 <div *ngFor="let item of keys"> <div>{{item}}.._js 遍历对象的key

粒子群算法(PSO)求解路径规划_粒子群算法路径规划-程序员宅基地

文章浏览阅读2.2w次,点赞51次,收藏310次。粒子群算法求解路径规划路径规划问题描述    给定环境信息,如果该环境内有障碍物,寻求起始点到目标点的最短路径, 并且路径不能与障碍物相交,如图 1.1.1 所示。1.2 粒子群算法求解1.2.1 求解思路    粒子群优化算法(PSO),粒子群中的每一个粒子都代表一个问题的可能解, 通过粒子个体的简单行为,群体内的信息交互实现问题求解的智能性。    在路径规划中,我们将每一条路径规划为一个粒子,每个粒子群群有 n 个粒 子,即有 n 条路径,同时,每个粒子又有 m 个染色体,即中间过渡点的_粒子群算法路径规划

量化评价:稳健的业绩评价指标_rar 海龟-程序员宅基地

文章浏览阅读353次。所谓稳健的评估指标,是指在评估的过程中数据的轻微变化并不会显著的影响一个统计指标。而不稳健的评估指标则相反,在对交易系统进行回测时,参数值的轻微变化会带来不稳健指标的大幅变化。对于不稳健的评估指标,任何对数据有影响的因素都会对测试结果产生过大的影响,这很容易导致数据过拟合。_rar 海龟

IAP在ARM Cortex-M3微控制器实现原理_value line devices connectivity line devices-程序员宅基地

文章浏览阅读607次,点赞2次,收藏7次。–基于STM32F103ZET6的UART通讯实现一、什么是IAP,为什么要IAPIAP即为In Application Programming(在应用中编程),一般情况下,以STM32F10x系列芯片为主控制器的设备在出厂时就已经使用J-Link仿真器将应用代码烧录了,如果在设备使用过程中需要进行应用代码的更换、升级等操作的话,则可能需要将设备返回原厂并拆解出来再使用J-Link重新烧录代码,这就增加了很多不必要的麻烦。站在用户的角度来说,就是能让用户自己来更换设备里边的代码程序而厂家这边只需要提供给_value line devices connectivity line devices