【Unity3D Shader编程】之十 深入理解Unity5中的Standard Shader 二 屏幕油画特效的实

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

 


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

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

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

 本文工程使用的Unity3D版本: 5.2.1 

 

概要:本文讲解了Unity中着色器编译多样化的思路,并对Standard Shader中正向基础渲染通道的源码进行了分析,以及对屏幕油画特效进行了实现。

 

众所周知,Unity官方文档对Shader进阶内容的讲解是非常匮乏的。本文中对Stardard Shader源码的一些分析,全是浅墨自己通过对Shader源码的理解,以及Google之后理解与分析而来。如有解释不妥当之处,还请各位及时指出。

 

依然是附上一组本文配套工程的运行截图之后,便开始我们的正文。本次的选用了新的场景,正如下图中所展示的。

城镇入口(with 屏幕油画特效):



城镇入口(原始图):



 图依然是贴这两张。文章末尾有更多的运行截图,并提供了源工程的下载。先放出可运行的exe下载,如下:


【可运行的本文配套exe游戏场景请点击这里下载】

 

提示:在此游戏场景中按F键可以开关屏幕特效。

着色器编译多样化算是Unity5中Shder书写的新特性,标准着色器之所以能独当一面,正是得益于这种特性,在这里先对此特性进行一个简单的说明与讲解。

 

 




一、关于着色器编译多样化


 

此部分参考自Unity5.2.1版官方文档(http://docs.unity3d.com/Manual/SL-MultipleProgramVariants.html),经翻译&理解后而成。如有解释不妥当之处,还请各位及时指出。

Unity5中使用了一种被称为着色器编译多样化(Multiple shader program variants)的新技术,常被称为“megashaders”或“uber shaders”,并通过为每种情况提供不同的预处理指令来让着色器代码多次被编译来实现。

在Unity中,这可以通过#pragmamulti_compile或者#pragma shader_feature指令来在着色器代码段中实现。这种做法对表面着色器也可行。

在运行时,相应的着色器变体是从材质的关键词中取得的(Material.EnableKeyword和 DisableKeyword),或者全局着色器关键词(Shader.EnableKeyword和 DisableKeyword)。



1.1 multi_compile的用法简析


若我们定义如下指令:

#pragma multi_compile FANCY_STUFF_OFFFANCY_STUFF_ON

也就表示定义了两个变体:FANCY_STUFF_OFF和FANCY_STUFF_ON。在运行时,其中的一个将被激活,根据材质或者全局着色器关键词(#ifdef FANCY_STUFF_OFF之类的宏命令也可以)来确定激活哪个。若两个关键词都没有启用,那么将默认使用前一个选项,也就是关闭(OFF)的选项FANCY_STUFF_OFF。

需要注意,也可以存在超过两个关键字的multi_compile编译选项,比如,如下代码将产生4种着色器的变体:

#pragma multi_compile SIMPLE_SHADINGBETTER_SHADING GOOD_SHADING BEST_SHADING

当#pragma multi_compile中存在所有名字都是下划线的一个指定段时,就表示需在没有预处理宏的情况下产生一个空的着色器变种。这种做法在着色器编写中比较常见,因为这样可以在不影响使用的情况下,避免使用两个关键词,这样就节省了一个变量个数的占用(下面会提到,Unity中关键词个数是有129个的数量限制的)。例如,下面的指令将产生两个着色器变体;第一个没有定义,第二个定义为FOO_ON:

#pragma multi_compile __ FOO_ON

这样就省去了一个本来需要定义出来的 FOO_OFF(FOO_OFF没有定义,自然也不能使用),节省了一个关键词个数的占用。

若Shader中有如上定义,则可以使用#ifdef来进行判断:

#ifdef FOO_ON//代码段1#endif

根据上面已经定义过的FOO_ON,此#ifdef判断的结果为真,代码段1部分的代码就会被执行到。反之,若#pragma multi_compile __FOO_ON一句代码没有交代出来,那么代码段1部分的代码就不会被执行。

这就是着色器编译多样化的实现方式,其实理解起来很容易,对吧。

 


1.2 shader_feature和multi_compile之间的区别

 

#pragma shader_feature 和#pragma multi_compile非常相似,唯一的区别在于采用了#pragmashader_feature语义的shader,在遇到不被使用的变体的时候,就不会将其编译到游戏中。所以,shader_feature中使得所有的设置到材质中的关键词都是有效的,而multi_compile指令将从全局代码里设置关键词。

另外,shader_feature还有一个仅仅含有一个关键字的快捷表达方式,例如:

#pragma shader_feature FANCY_STUFF


此为#pragma shader_feature _ FANCY_STUFF的一个简写形式,其扩展出了两个着色器变体,第一种变体自然为不定此FANCY_STUFF变量(那么若在稍后的Shader代码中进行#ifdef FANCY_STUFF的判断,则结果为假),第二种变体为定义此FANCY_STUFF变量(此情况下#ifdef FANCY_STUFF的判断结果为真)。



1.3 多个multi_compile连用会造成指数型增长

 

可以提供多个multi_compile流水线,然后着色器的结果可以被编译为几个流水线的排列组合,比如:

#pragma multi_compile A B C#pragma multi_compile D E

第一行中有3种选项,第二行中有两种选项,那么进行排列组合,总共就会有六种选项(A+D, B+D, C+D, A+E, B+E, C+E)。

容易想到,一般每以个multi_compile流水线,都控制着着色器中某一单一的特性。请注意,着色器总量的增长速度是非常快的。

比如,10条包含两个特性的multi_compil指令,会得到2的10次方,也就是1024种不同的着色器变体。

 


1.4 关于Unity中的关键词限制Keyword limit

 

当使用着色变量时,我们应该记住,Unity中将关键词的数量限制在了128个之内(着色变量算作关键字),且其中有一些已经被Unity内置使用了,因此,我们真正可以自定义使用关键词的数量以及是小于128个的。同时,关键词是在单个Unity项目中全局使用并计数的,所以我们要千万小心,在同一项目中存在的但没用到Shader也要考虑在内,千万不要合起来在数量上超出Unity的关键词数量限制了。

 


1.5 Unity内置的快捷multi_compile指令


如下有Unity内置的几个着色器变体的快捷多编译指令,他们大多是应对Unity中不同的光线,阴影和光照贴图类型。详情见rendering pipeline 。

  • multi_compile_fwdbase - 此指令表示,编译正向基础渲染通道(用于正向渲染中,应用环境光照、主方向光照和顶点/球面调和光照(Spherical Harmonic Lighting))所需的所有变体。这些变体用于处理不同的光照贴图类型、主要方向光源的阴影选项的开关与否。
  • multi_compile_fwdadd - 此指令表示, 编译正向附加渲染通道(用于正向渲染中;以每个光照一个通道的方式应用附加的逐像素光照)所需的所有变体。这些变体用于处理光源的类型(方向光源、聚光灯或者点光源),且这些变种都包含纹理cookie。
  • multi_compile_fwdadd_fullshadows – 此指令和上面的正向渲染附加通道基本一致,但同时为上述通道的处理赋予了光照实时阴影的能力。
  • multi_compile_fog - 此指令表示,编译出几个不同的Shader变体来处理不同类型的雾效(关闭/线性/指数/二阶指数)(off/linear/exp/exp2). 

 


1.6 使用指令跳过某些变体的编译


大多数内置的快捷指令导致了很多着色的变体。若我们熟悉他们且知道有些并非所需,可以使用#pragmaskip_variants语句跳过其中一些的编译。例如: 

#pragma multi_compile_fwdadd// 将跳过所有使用"POINT"或 "POINT_COOKIE"的变体#pragma skip_variants POINT POINT_COOKIE

OK,通过上面经过翻译&理解过后的官方文档材料,应该对Unity中的着色器编译多样化有了一个理解。说白了,着色器变体的定义和使用与宏定义很类似。

 


1.7 对知识的提炼


上面交代了这么多,看不懂没关系,我们提炼一下,看懂这段提炼,关于着色器变体的意义与使用方式,也就懂了大半了。

若我们在着色器中定义了这一句:

 

#pragma shader_feature _THIS_IS_A_SAMPLE

这句代码理解起来,也就是_THIS_IS_A_SAMPLE被我们定义过了,它是存在的,以后我们如果判断#ifdef _THIS_IS_A_SAMPLE,那就是真了。我们可以在这个判断的#ifdef…… #endif块里面实现自己需要的实现代码X,这段实现代码X,只会在你用#pragma multi_compile 或#pragmashader_feature定义了_THIS_IS_A_SAMPLE这个“宏”的时候会被执行,否则,它就不会被执行到。


实现代码X的执行与不执行,全靠你对变体的定义与否。这就是着色器编译多样化的实现方式,一个着色器+多个CG头文件的小团队(如标准着色器),可以独当一面,一个打一群,可以取代一大堆独立实现的Shader的原因所在。


 





二、Standard Shader中正向基础渲染通道源码分析

 



这一节主要用来解析Standard Shader中正向基础渲染通道的源码。

先上Standard Shader正向渲染基础通道(Shader Model 3.0版)的Shader源代码: 

 //------------------------------------【子着色器1】------------------------------------ // 此子着色器用于Shader Model 3.0 //---------------------------------------------------------------------------------------- SubShader {  //渲染类型设置:不透明  Tags { "RenderType"="Opaque" "PerformanceChecks"="False" }  //细节层次设为:300  LOD 300    //--------------------------------通道1-------------------------------  // 正向基础渲染通道(Base forward pass)  // 处理方向光,自发光,光照贴图等 ...  Pass  {   //设置通道名称   Name "FORWARD"    //于通道标签中设置光照模型为ForwardBase,正向渲染基础通道   Tags { "LightMode" = "ForwardBase" }   //混合操作:源混合乘以目标混合   Blend [_SrcBlend] [_DstBlend]   // 根据_ZWrite参数,设置深度写入模式开关与否   ZWrite [_ZWrite]   //===========开启CG着色器语言编写模块===========   CGPROGRAM   //着色器编译目标:Model 3.0   #pragma target 3.0   //编译指令:不使用GLES渲染器编译   #pragma exclude_renderers gles      // ---------编译指令:着色器编译多样化--------   #pragma shader_feature _NORMALMAP   #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON   #pragma shader_feature _EMISSION   #pragma shader_feature _METALLICGLOSSMAP    #pragma shader_feature ___ _DETAIL_MULX2   #pragma shader_feature _PARALLAXMAP      //--------着色器编译多样化快捷指令------------   //编译指令:编译正向渲染基础通道(用于正向渲染中,应用环境光照、主方向光照和顶点/球面调和光照)所需的所有变体。   //这些变体用于处理不同的光照贴图类型、主要方向光源的阴影选项的开关与否   #pragma multi_compile_fwdbase   //编译指令:编译几个不同变种来处理不同类型的雾效(关闭/线性/指数/二阶指数/)   #pragma multi_compile_fog   //编译指令:告知编译器顶点和片段着色函数的名称   #pragma vertex vertForwardBase   #pragma fragment fragForwardBase   //包含辅助CG头文件   #include "UnityStandardCore.cginc"   //===========结束CG着色器语言编写模块===========   ENDCG  } …… }


OK,一起来稍微分析一下上述代码。基本上是逐行注释,所以找几个容易疑惑的点来提一下。

 

第一处,着色器编译多样化部分,代码如下:

 

// ---------编译指令:着色器编译多样化--------#pragma shader_feature _NORMALMAP#pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON#pragma shader_feature _EMISSION#pragma shader_feature _METALLICGLOSSMAP#pragma shader_feature ___ _DETAIL_MULX2#pragma shader_feature _PARALLAXMAP


上文刚讲过着色器编译多样化的一些理解,理解起来就是这样,这边定义了很多的“宏”、 _NORMALMAP、_ALPHATEST_ON、_ALPHABLEND_ON、_EMISSION、_METALLICGLOSSMAP、_DETAIL_MULX2、_PARALLAXMAP,在顶点和片段着色器实现部分,可以用#ifdef _EMISSION类似的宏命令来对不同情况下的实现进行区别对待。

 

第二处,着色器编译多样化快捷指令部分,上文的讲解部分也有分别提到,这里代码注释已经很详细,如下:


//--------着色器编译多样化快捷指令------------//编译指令:编译正向渲染基础通道(用于正向渲染中,应用环境光照、主方向光照和顶点/球面调和光照)所需的所有变体。//这些变体用于处理不同的光照贴图类型、
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值