Unity5中的MetaPass

  前些天烘焙lightmap的时候发现用自己写的Shader的模型在烘焙时候不会烘焙效果不对,它不会产生对周围物体的间接光照,但是我放到了unity4.x中就是没问题的。查了一番,发现Unity5中加了一个MetaPass的东西。大家可以自己去看下。

  要想搞清楚为啥需要MetaPass,只看Unity是不行的,所以本文中还会结合着去分析分析Enlighten的工作原理。

什么是MetaPass



  加入MetaPass的原因就是因为Unity5把烘焙系统从Beast换成了Enlighten。看一下metapass的流程:

  

         (图1:metapass flow图片来源:Unity官方文档)

  从图中可以看到,enlighten需要Unity提供材质的Albdeo(反射率)和自发光(emissive)的纹理,从而用来计算间接光照。而这两个贴图都是Unity自己在GPU上渲染得到的。既然需要GPU渲染,那就需要用提供一个相应的Pass来专门让Unity用来进行这种渲染。
  这与3.x和4.x版本的Unity是不同的,在这些版本中烘焙器(姑且叫这个名字吧),从材质Shader上获取一些烘焙时使用的信息是要通过检测材质属性的名字(也就是你在Shader的Properties块里的内容)来完成的。具体可以参考响应版本文档中的Lightmapping In-Depth一节。

  那么为什么Unity自带的Shader在烘焙的时候没问题呢,是因为内置的Shader都是SurfaceShader.而SurfaceShader实际上最后都会被Unity转化成V&F Shader。在转化的这个过程中,Unity给他们添加上了metaPass(可以在#pragma 中加上nometa让响应SurfaceShader不产生metapass)。如果对Surface Shader如何转换成V&F Shader感兴趣,可以看一下《UnityShader入门精要》的第17章。

  如果你手头有5.x版本的内置Shader源代码,不妨放到Unity中一个,然后在Inspector面板中点击show generated code查看转换好的V&F代码。代码很长,metaPass一般在最后。

  前面说了,metaPass主要是Unity用来计算Albedo和emissive,然后提供给enlighten用的。

原理解释



  至于为什么需要这两个值,其实很好理解,设想一下一个物体要相对周围物体产生光照影响,无非两种情况:

  1. 作为发光体,直接将光线投射到其它物体上,对应着上面的emissive。
  2. 光线照射到该物体上然后反射(可能经过多次)到周围物体上,最后被后者反射到人眼中。而当要计算前者能反射多少,以及哪些成分的光线到后者身上就要用到Albedo

  可以看一下Enlighten官方Blog给出的Radiosity Equation公式:

    

    (图2:Radiosity Equation 图片来源:Enlighten官方Blog)

  对于全局光照的处理,目前有两种主流算法,光线追踪和辐射度算法。Enlighten所使用的即是这种。上图中的公式实际上是对RenderEquation的一种简化变形,RenderEquation是一种理想模型,也是目前所有光照处理的理论基础,详情可以自行wiki。

  上面的模型中实际上把一个像素点的受光情况(这里只考虑间接光照)分成了自发光Le和 来自其它光源的间接光照。其中Pi是材质属性,这里我们可以简单的理解成Albedo反射率,这反应了改点对应的材质对不同波段光的反射能力。那上面公式中后面的一团就不难理解了,实际上就是对从各个方向收集到的反射光的和最后乘上一个材质反射能力,从而得到最后的实际光照结果。此处只是个人理解,可以去Enlighten的Blog自己看,(网上能搜到一篇中文翻译,翻译的一般)

  好了这下子我们就应该能理解为什么在图1所示的metapass flow里enlighten需要Unity给它提供Albedo和emission纹理了。

代码分析



  我们来直接看一下代码:

 1 Pass
 2 {
 3     Name "Meta"
 4     Tags {"LightMode" = "Meta"}
 5     Cull Off
 6
 7     CGPROGRAM
 8     #pragma vertex vert_meta
 9     #pragma fragment frag_meta
10
11     #include "Lighting.cginc"
12     #include "UnityMetaPass.cginc"
13
14     struct v2f
15     {
16         float4 pos:SV_POSITION;
17         float2 uv:TEXCOORD1;
18         float3 worldPos:TEXCOORD0;
19     };
20
21     uniform fixed4 _Color;
22     uniform sampler2D _MainTex;
23     v2f vert_meta(appdata_full v)
24     {
25         v2f o;
26         UNITY_INITIALIZE_OUTPUT(v2f,o);
27         o.pos = UnityMetaVertexPosition(v.vertex,v.texcoord1.xy,v.texcoord2.xy,unity_LightmapST,unity_DynamicLightmapST);
28         o.uv = v.texcoord.xy;
29         return o;
30     }
31
32     fixed4 frag_meta(v2f IN):SV_Target
33     {
34          UnityMetaInput metaIN;
35          UNITY_INITIALIZE_OUTPUT(UnityMetaInput,metaIN);
36          metaIN.Albedo = tex2D(_MainTex,IN.uv).rgb * _Color.rgb;
37          metaIN.Emission = 0;
38          return UnityMetaFragment(metaIN);
39     }
40
41     ENDCG
42 }  

  上面代码中是我写的最简化后的代码。最开始的LightMode的Tag是必须写的,Unity要通过它来找到MetaPass。Unity文档中有比较完整的代码。_Color和_MainTex是在Properties里声明的贴图和调和颜色。后面会把他俩相乘作为Albedo的结果,这也正是我们在正常的光照处理里所做的。

  上面的代码比较简单,有几个地方需要说明一下:

  1. 首先是Unity_INITIALIZE_OUTPUT 【HLSL.cginc中定义】:

1 // Initialize arbitrary structure with zero values.
2 // Not supported on some backends (e.g. Cg-based like PS3 and particularly with nested structs).
3 // hlsl2glsl would almost support it, except with structs that have arrays -- so treat as not supported there either :(
4 #if defined(UNITY_COMPILER_HLSL) || defined(SHADER_API_PSSL) || defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE)
5 #define UNITY_INITIALIZE_OUTPUT(type,name) name = (type)0;
6 #else
7 #define UNITY_INITIALIZE_OUTPUT(type,name)
8 #endif

  很简单,就是一个清零。当顶点着色器函数返回结果时候,如果要返回的结构体没有全部赋值过,那么Unity会报错,必须全部赋值。而这个宏就是用来清零的,省着手动赋0.但并不是所有的着色语言都支持。有些情况下必须手动赋值。

  2.UnityMetaVertexPosition(v.vertex,v.texcoord1.xy,v.texcoord2.xy,unity_LightmapST,unity_DynamicLightmapST)[UnityMetaPass.cginc中定义]:

 1 float4 UnityMetaVertexPosition (float4 vertex, float2 uv1, float2 uv2, float4 lightmapST, float4 dynlightmapST)
 2 {
 3     if (unity_MetaVertexControl.x)
 4     {
 5         vertex.xy = uv1 * lightmapST.xy + lightmapST.zw;
 6         // OpenGL right now needs to actually use incoming vertex position,
 7         // so use it in a very dummy way
 8         vertex.z = vertex.z > 0 ? 1.0e-4f : 0.0f;
 9     }
10     if (unity_MetaVertexControl.y)
11     {
12         vertex.xy = uv2 * dynlightmapST.xy + dynlightmapST.zw;
13         // OpenGL right now needs to actually use incoming vertex position,
14         // so use it in a very dummy way
15         vertex.z = vertex.z > 0 ? 1.0e-4f : 0.0f;
16     }
17     return UnityObjectToClipPos(vertex);
18 }

  参数中的uv1,uv2分别是模型在静态光照贴图(static lightmap)和动态光照贴图(实时GI)中的uv坐标。lightmapST和dynlightmapST的值应该是经过特殊构造以用来确定其在烘焙空间中的位置。
上面代码中有两个if判断,这个unity_MetaVertexControl是个什么东西呢? 
  在UnityMetaPass.cginc中找到了定义如下:

1 CBUFFER_START(UnityMetaPass)
2     // x = use uv1 as raster position
3     // y = use uv2 as raster position
4     bool4 unity_MetaVertexControl;
5
6     // x = return albedo
7     // y = return normal
8     bool4 unity_MetaFragmentControl;
9 CBUFFER_END

  上面的CBUFFER_START 宏是和DX11的constant Buffer有关的,注意由于Unity对DX11的支持,导致了UnityShaderLab中有很多为了处理DX11的新添加的宏。这不是我们要讨论的核心问题,有兴趣可以自己研究下。

  从上面注释中看到unity_MetaVertexControl的xy变量指定了我们要处理的是静态光照贴图还是动态光照贴图。而这两个具体的值应该是Unity引擎自己在烘焙时候根据烘焙的配置来设置的。比如你只选择了BakedGI,那么x=true,y=false.if里的具体语句就是计算出模型顶点在lightmap空间的位置。

  至于最后的UnityObjectToClipPos(vertex)[定义在UnityCG.cginc]:

 1 // Tranforms position from object to homogenous space
 2 inline float4 UnityObjectToClipPos( in float3 pos )
 3 {
 4 #ifdef UNITY_USE_PREMULTIPLIED_MATRICES
 5     return mul(UNITY_MATRIX_MVP, float4(pos, 1.0));
 6 #else
 7     // More efficient than computing M*VP matrix product
 8     return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
 9 #endif
10 }
11 inline float4 UnityObjectToClipPos(float4 pos) // overload for float4; avoids "implicit truncation" warning for existing shaders
12 {
13     return UnityObjectToClipPos(pos.xyz);
14 }

  实际上只是做了模型空间到齐次空间的转换。

  3.UnityMetaInput[UnityMetaPass.cginc定义]:

1 struct UnityMetaInput
2 {
3     half3 Albedo;
4     half3 Emission;
5 };

  很简单就是我们之前提到的两个结果值。在之前的代码里我们直接像下面这样计算了。

1 metaIN.Albedo = tex2D(_MainTex,IN.uv).rgb * _Color.rgb;
2 metaIN.Emission = 0;

  4:UnityMetaFragment(metaIN)[UnityMetaPass.cginc中定义]:

 1 half4 UnityMetaFragment (UnityMetaInput IN)
 2 {
 3     half4 res = 0;
 4     if (unity_MetaFragmentControl.x)
 5     {
 6         res = half4(IN.Albedo,1);
 7
 8         // d3d9 shader compiler doesn‘t like NaNs and infinity.
 9         unity_OneOverOutputBoost = saturate(unity_OneOverOutputBoost);
10
11         // Apply Albedo Boost from LightmapSettings.
12         res.rgb = clamp(pow(res.rgb, unity_OneOverOutputBoost), 0, unity_MaxOutputValue);
13     }
14     if (unity_MetaFragmentControl.y)
15     {
16         half3 emission;
17         if (unity_UseLinearSpace)
18             emission = IN.Emission;
19         else
20             emission = GammaToLinearSpace (IN.Emission);
21
22         res = UnityEncodeRGBM(emission, EMISSIVE_RGBM_SCALE);
23     }
24     return re

  有点长,一点一点分析,首先unity_MetaFragmentControl和前面的MetaVertexControl一样,他的xy值用来表示fragment要返回的值是albedo,还是normal。它们是由unity来设置的。在第一个if判断里应该是对albedo的值根据用户的烘焙设置进行最后的调整,具体是对应的什么值,我也查不到。第二个if判断里先区分当前计算是不是在线性空间,如果不是,就转换到线性空间。并把结果值转换到RGBM空间(RGBM格式一般是用来存储HDR空间的lightmap的)。

  对于UnityMetaFragment这个函数我也有很多不太理解的地方,大家姑且理解一下流程吧,google也查不到什么资料,也没有源码。待日后研究吧。

补充



  另外我这篇文章还留个尾巴,上面我们讨论的都是间接光照,并没有说明烘焙时直接光照Unity是如何处理的,这个我只能个人参考了一些资料的理解:

  Enlighten实际上会把光源也作为一个物体,它也适用图2的公式,但对于光源来说实际上只有Le而后面的项是没有意义的。而Enlighten在渲染场景光照的时候实际上是一个迭代的过程,在不考虑任何模型物体自发光的情况下,第一次遍历时候实际上对由于某一点的Bi来说,它的第二项只有那些光源物体会对它造成影响,也就是只有光源的Lj是一个非0值。当第二次遍历的时候由于上一次遍历,很多表面的Lj都已经是非0值,那么Bi的第二项的计算结果就会有更多的有效项,这实际就产生了间接光照了。

  关于辐照度算法,何咏大神有一篇很好的译文,很值得一看。

  这篇文章思路有点乱,有些问题我自己也没彻底搞明白,算是抛砖引玉,由于资料不多,水平有限,希望没有误导大家,如果发现文章中有任何错误,望及时指正。

  尊重他人智慧成果,若要转载,请注明作者esfog,原文地址http://www.cnblogs.com/Esfog/p/MetaPass_In_Unity5.html

时间: 2024-10-20 16:37:55

Unity5中的MetaPass的相关文章

【浅墨Unity3D Shader编程】之九 深入理解Unity5中的Standard Shader (一)&屏幕水幕特效的实现

本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接:http://blog.csdn.net/poem_qianmo/article/details/49556461 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 本文工程使用的Unity3D版本: 5.2.1 概要:本文主要介绍了Unity5中的标准着色器,并且也涉及到了基于物理的着色.延迟渲染等高级着色技术,而在文章后半部分,也对屏幕水幕特效的实现方法进行了讲解与分析. 依然是附上

Unity5中叹为观止的实时GI效果

http://www.manew.com/thread-43970-1-1.html 今天为大家分享unity与Alex Lovett共同使用unity5制作的Shrine Arch-viz Demo,其中充分利用了Unity5的实时全局光照功能.实在是太过惊艳,随便一帧都可以直接拿来当做屏保~~~ 先奉上视频: http://static.video.qq.com/TPout.swf?vid=t017102l7by&auto=0 上面的Demo使用Unity5.2制作,没有导入任何第三方资源包

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

本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接:http://blog.csdn.net/poem_qianmo/article/details/49719247 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 本文工程使用的Unity3D版本: 5.2.1 概要:本文讲解了Unity中着色器编译多样化的思路,并对Standard Shader中正向基础渲染通道的源码进行了分析,以及对屏幕油画特效进行了实现. 众所周知,Unity官

【浅墨Unity3D Shader编程】之十一 深入理解Unity5中的Standard Shader(三)&屏幕像素化特效的实现

本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接:http://blog.csdn.net/poem_qianmo/article/details/50095705 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 本文工程使用的Unity3D版本: 5.2.1  概要:续接上文,本文进一步讲解与分析了上文未讲完的Unity5中Standard Shader正向基础渲染通道源码的片段着色实现部分,以及对屏幕像素化后期特效进行了实现. 同

Unity5中WebGL平台封装的一些技巧

最近在接触unity的WebGL平台,其实这个平台作为Web Player的替代品,已经能满足大部分的开发需求,而且不需要额外的插件支持,确实方便了不少,但开发中依旧遇到了不少问题,在这里记录和共享一下解决方法. 首先还是构建选项,在"Player Settings"里. Resolution and Presentation和老的web player没啥变化,你可以把你自制的模板放到 "\Unity\Editor\Data\PlaybackEngines\WebGLSupp

Unity5中Inspector界面上的AssetBundle值设定问题

注: 编辑器扩展方面  Unity5  AssetBundle Unity5对AssetBundle做了很大的调整,与旧版很大不同,例如,如果一个资源已经打包,如果该资源没有任何更新,那么该资源将不会被打包.打包的同时会生成该文件同名的"*.manifest"文件,该文件中记录了打包后的bundle文件的相关信息. 此外,在Inspector面板中还多出了AssetBundle的名称和文件扩展名的的选项,如图: 最近有个需求需要通过以编辑器扩展方式实现同时设置多个资源文件的AssetB

在Unity5中使用C#脚本实现UI的下滑、变色、渐隐渐现效果

一.首先,我们先创建一个Text    依次选择Component→UI→Text创建一个Text,创建完成后如下: 二.创建完成后,在Project面板点击Create→C# Script,本例命名为InAndFade 三.编写代码之前,为了确保能够调用到Text类,所以要先手动引入命名空间 using UnityEngine.UI; 四.完整代码如下 1 public class InAndFade : MonoBehaviour 2 { 3 //渐隐渐现 4 public bool Sho

(五)Unity5.0新特性------unity中编写脚本

?? 出处:http://blog.csdn.net/u010019717 author:孙广东      时间:2015.3.32 之前的Unity脚本 我们利用Mono(WinRT 在Windows Store Apps 和Windows Phone上) 使用 C# ,获得第三方库和接近本机性能的易用性.然而,有一些挑战: C# 运行时性能仍然落后于 C/c + + 最新和最好的.NET 语言和运行时功能在Unity's current version of Mono并不支持. 大约 23

Unity5 GI与PBS渲染从用法到着色代码

本文主要介绍Untiy5以后的GI,PBS,以及光源探头,反射探头的用法以及在着色器代码中如何发挥作用,GI是如何影响渲染的,主要分成三个部分,最开始说明PBS需要的材质与相应概念,二是Unity 里相应GI的操作,三是对应着色器代码的理解.如果没有特殊声明,所有操作与代码都是针对Unity5.3. PBS材质与概念 简单来说,PBS的优点不同的照明下获得一致的外观,更容易实现,更直观的参数. PBS材质概念: 1.albedo 反照率 反照率贴图定义漫反射的基本颜色,与原来的漫反射贴图相比,不