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纹理了。
代码分析
我们来直接看一下代码:
Pass
{
Name "Meta"
Tags {"LightMode" = "Meta"}
Cull Off CGPROGRAM
#pragma vertex vert_meta
#pragma fragment frag_meta #include "Lighting.cginc"
#include "UnityMetaPass.cginc" struct v2f
{
float4 pos:SV_POSITION;
float2 uv:TEXCOORD1;
float3 worldPos:TEXCOORD0;
}; uniform fixed4 _Color;
uniform sampler2D _MainTex;
v2f vert_meta(appdata_full v)
{
v2f o;
UNITY_INITIALIZE_OUTPUT(v2f,o);
o.pos = UnityMetaVertexPosition(v.vertex,v.texcoord1.xy,v.texcoord2.xy,unity_LightmapST,unity_DynamicLightmapST);
o.uv = v.texcoord.xy;
return o;
} fixed4 frag_meta(v2f IN):SV_Target
{
UnityMetaInput metaIN;
UNITY_INITIALIZE_OUTPUT(UnityMetaInput,metaIN);
metaIN.Albedo = tex2D(_MainTex,IN.uv).rgb * _Color.rgb;
metaIN.Emission = ;
return UnityMetaFragment(metaIN);
} ENDCG
}
上面代码中是我写的最简化后的代码。最开始的LightMode的Tag是必须写的,Unity要通过它来找到MetaPass。Unity文档中有比较完整的代码。_Color和_MainTex是在Properties里声明的贴图和调和颜色。后面会把他俩相乘作为Albedo的结果,这也正是我们在正常的光照处理里所做的。
上面的代码比较简单,有几个地方需要说明一下:
1. 首先是Unity_INITIALIZE_OUTPUT 【HLSL.cginc中定义】:
// Initialize arbitrary structure with zero values.
// Not supported on some backends (e.g. Cg-based like PS3 and particularly with nested structs).
// hlsl2glsl would almost support it, except with structs that have arrays -- so treat as not supported there either :(
#if defined(UNITY_COMPILER_HLSL) || defined(SHADER_API_PSSL) || defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE)
#define UNITY_INITIALIZE_OUTPUT(type,name) name = (type)0;
#else
#define UNITY_INITIALIZE_OUTPUT(type,name)
#endif
很简单,就是一个清零。当顶点着色器函数返回结果时候,如果要返回的结构体没有全部赋值过,那么Unity会报错,必须全部赋值。而这个宏就是用来清零的,省着手动赋0.但并不是所有的着色语言都支持。有些情况下必须手动赋值。
2.UnityMetaVertexPosition(v.vertex,v.texcoord1.xy,v.texcoord2.xy,unity_LightmapST,unity_DynamicLightmapST)[UnityMetaPass.cginc中定义]:
float4 UnityMetaVertexPosition (float4 vertex, float2 uv1, float2 uv2, float4 lightmapST, float4 dynlightmapST)
{
if (unity_MetaVertexControl.x)
{
vertex.xy = uv1 * lightmapST.xy + lightmapST.zw;
// OpenGL right now needs to actually use incoming vertex position,
// so use it in a very dummy way
vertex.z = vertex.z > ? 1.0e-4f : 0.0f;
}
if (unity_MetaVertexControl.y)
{
vertex.xy = uv2 * dynlightmapST.xy + dynlightmapST.zw;
// OpenGL right now needs to actually use incoming vertex position,
// so use it in a very dummy way
vertex.z = vertex.z > ? 1.0e-4f : 0.0f;
}
return UnityObjectToClipPos(vertex);
}
参数中的uv1,uv2分别是模型在静态光照贴图(static lightmap)和动态光照贴图(实时GI)中的uv坐标。lightmapST和dynlightmapST的值应该是经过特殊构造以用来确定其在烘焙空间中的位置。
上面代码中有两个if判断,这个unity_MetaVertexControl是个什么东西呢?
在UnityMetaPass.cginc中找到了定义如下:
CBUFFER_START(UnityMetaPass)
// x = use uv1 as raster position
// y = use uv2 as raster position
bool4 unity_MetaVertexControl; // x = return albedo
// y = return normal
bool4 unity_MetaFragmentControl;
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]:
// Tranforms position from object to homogenous space
inline float4 UnityObjectToClipPos( in float3 pos )
{
#ifdef UNITY_USE_PREMULTIPLIED_MATRICES
return mul(UNITY_MATRIX_MVP, float4(pos, 1.0));
#else
// More efficient than computing M*VP matrix product
return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
#endif
}
inline float4 UnityObjectToClipPos(float4 pos) // overload for float4; avoids "implicit truncation" warning for existing shaders
{
return UnityObjectToClipPos(pos.xyz);
}
实际上只是做了模型空间到齐次空间的转换。
3.UnityMetaInput[UnityMetaPass.cginc定义]:
struct UnityMetaInput
{
half3 Albedo;
half3 Emission;
};
很简单就是我们之前提到的两个结果值。在之前的代码里我们直接像下面这样计算了。
metaIN.Albedo = tex2D(_MainTex,IN.uv).rgb * _Color.rgb;
metaIN.Emission = ;
4:UnityMetaFragment(metaIN)[UnityMetaPass.cginc中定义]:
half4 UnityMetaFragment (UnityMetaInput IN)
{
half4 res = ;
if (unity_MetaFragmentControl.x)
{
res = half4(IN.Albedo,); // d3d9 shader compiler doesn't like NaNs and infinity.
unity_OneOverOutputBoost = saturate(unity_OneOverOutputBoost); // Apply Albedo Boost from LightmapSettings.
res.rgb = clamp(pow(res.rgb, unity_OneOverOutputBoost), , unity_MaxOutputValue);
}
if (unity_MetaFragmentControl.y)
{
half3 emission;
if (unity_UseLinearSpace)
emission = IN.Emission;
else
emission = GammaToLinearSpace (IN.Emission); res = UnityEncodeRGBM(emission, EMISSIVE_RGBM_SCALE);
}
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
Unity5中的MetaPass的更多相关文章
- 在unity5中减少Draw Calls(SetPass Calls)[转]
在unity5中减少Draw Calls(SetPass Calls) 我一直工作于unity5支持的Standard Shader(标准着色器)上,并且做了一些关于如何有效地减少draw cal ...
- Unity5中叹为观止的实时GI效果
http://www.manew.com/thread-43970-1-1.html 今天为大家分享unity与Alex Lovett共同使用unity5制作的Shrine Arch-viz Demo ...
- Unity5中新的Shader体系简析
一.Unity5中新的Shader体系简析 Unity5和之前的书写模式有了一定的改变.Unity5时代的Shader Reference官方文档也进一步地变得丰满. 主要需要了解到的是,在原来的Un ...
- 12个Unity5中优化VR 应用的技巧
本文章由cartzhang编写,转载请注明出处. 所有权利保留. 文章链接:http://blog.csdn.net/cartzhang/article/details/50176429 作者:car ...
- Unity5中WebGL平台封装的一些技巧
最近在接触unity的WebGL平台,其实这个平台作为Web Player的替代品,已经能满足大部分的开发需求,而且不需要额外的插件支持,确实方便了不少,但开发中依旧遇到了不少问题,在这里记录和共享一 ...
- Unity5中的粒子缩放(附测试源码)
本文章由cartzhang编写,转载请注明出处. 所有权利保留. 文章链接:http://blog.csdn.net/cartzhang/article/details/49363241 作者:car ...
- 在Unity5中使用C#脚本实现UI的下滑、变色、渐隐渐现效果
一.首先,我们先创建一个Text 依次选择Component→UI→Text创建一个Text,创建完成后如下: 二.创建完成后,在Project面板点击Create→C# Script,本例命名 ...
- unity5.0新功能-布料、动画系统
原作者:只待苍霞 这一章讲一下布料系统, 这次的布料系统有很大的改良.Unity4中, 需要对SkinnedMeshRenderer使用SkinnedCloth, 或者对Cloth Renderer使 ...
- Unity5 GI与PBS渲染从用法到着色代码
http://www.cnblogs.com/zhouxin/p/5168632.html 本文主要介绍Untiy5以后的GI,PBS,以及光源探头,反射探头的用法以及在着色器代码中如何发挥作用,GI ...
随机推荐
- 前端开发薪资之各地区对比(图文分析)(share)
发现最近大家都在关注有关前端开发的薪资问题,不同地方各有差异,今天我就总结一下发出来方便大家的查阅.(2014年) 前面给大家介绍了关于前端开发需要学习的东西,根据你掌握的技能程度,薪水是不一样的.d ...
- A session of Log Collect, Retrieval and Analysis using ELK Stack
Motivation 运维过程中对问题的定位往往需要跟踪和定位日志.分布式和弹性计算的引入,使得日志的定位和分析变得越发复杂. 本次实践主要的目的是考察现有成熟的日志收集.检索和分析方案:Kafka+ ...
- C#编程语言与面向对象——类与对象
由于ASP.NET技术是全面向对象的,因此,要掌握这一技术,必须具备有扎实的面向对象理论基础 使用C#编程,所有的程序代码几乎都放在类中,不存在独立于类中之外的函数,因此,类是面向对象编程的基本单元 ...
- Xcode添加代码块
1.在编辑区写好代码 @property (nonatomic, assign) <#type#> <#name#> 在Xcode中实际输入完成之后显示的是下面的样子: 2.全 ...
- Qt4编码
#if QT_VERSION < 0x050000 qDebug() << "qt5以下的版本, 从QTextCodec设置全局字符集"; QTextCodec* ...
- Java socket长连接代码实现
服务器端程序: import java.io.*; import java.net.*; import java.util.*; public class ChatServer { boolean s ...
- JSON.parse() 和 JSON.stringify()
JSON.parse()和JSON.stringify() 1.parse 用于从一个字符串中解析出json 对象.例如 var str='{"name":"cpf& ...
- 【jq】c#零基础学习之路(4)抽象类和密封
一.抽象类 1.抽象类不能被实例化 2.抽象类方法必需要实现 3.如何类中函数为抽象函数,其类也需要定义成抽象类 4.关键字 abstract ,函数重写 override. 二.密封类 1.密封类不 ...
- Android单元测试实践
为什么要写单元测试 首先要介绍为什么蘑菇街支付金融这边会采用单元测试的实践.说起来比较巧,刚开始的时候,只是我一个人会写单元测试.后来老板们知道了,觉得这是件 很有价值的事情,于是就叫我负责我们组的单 ...
- Mac10.9用brew搭建Eclipse4.4+Maven3.2.3+JDK1.8运行环境
--------------------------------------- 博文作者:迦壹 博客标题:Mac10.9用brew搭建Eclipse4.4+Maven3.2.3+JDK1.8运行环境 ...