好久没写新博客了,都是在老博客上修修补补,不能再这样划水了。今年在求职市场上兜兜转转了好久,腾讯网易进不去,那么还是老老实实做技术积累吧。。。
在CSDN上看到一篇做高度/深度雾的的博客,正好项目里也要用,所以想拿过来直接看看效果的,没想到大佬竟然放在百度云盘并且不给提取码。。。好吧,我自己来实现一遍吧= =
深度雾
顾名思义,根据深度的不同将雾的颜色不同程度的混合到场景颜色上,一般来说有三种不一样的混合公式,称之为Linear线性、Exp指数以及指数平方Exp2。Unity本身也实现了这三种雾,具体代码在UnityCG.cginc中。
#if defined(FOG_LINEAR)
// factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = (coord) * unity_FogParams.z + unity_FogParams.w
#elif defined(FOG_EXP)
// factor = exp(-density*z)
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)
#elif defined(FOG_EXP2)
// factor = exp(-(density*z)^2)
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = unity_FogParams.x * (coord); unityFogFactor = exp2(-unityFogFactor*unityFogFactor)
#else
#define UNITY_CALC_FOG_FACTOR_RAW(coord) float unityFogFactor = 0.0
#endif
自己来实现的话其实我们首先考虑的是后处理实现,毕竟要是用unity的方法处理,那么每个物体想混合雾的颜色就得去改每个物体的shader,这样实在太麻烦了,而且美术需求的定制化程度也不高,所以还是选择走后处理了。
后处理的话首先我们必须取到深度,开启深度图是必须的,然后进行采样并应用深度,关键代码如下
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
float dist = 0;
#if _DIST_TYPE_VIEWSPACE
dist = LinearEyeDepth(depth);;
#else
dist = length(i.ray * Linear01Depth(depth));
#endif
float depthFactor = 0;
#if _FUNC_TYPE_LINEAR
depthFactor = (_DepthEnd - dist) / (_DepthEnd - _DepthStart);
#elif _FUNC_TYPE_EXP
depthFactor = exp(-(_Density * dist));
#else
depthFactor = exp(-pow(_Density * dist, 2));
#endif
然后想让雾效动起来并且动的自然的话,添加噪声图和时间信息即可。
float noise = tex2D(_NoiseTex, wp.xz * _WorldPosScale + _Time.x * fixed2(_NoiseSpX, _NoiseSpY)).r;
depthFactor *= depthNoise;
这里的uv我使用的是通过屏幕像素还原出来的像素的世界坐标(当然读者在使用时也可以试试其他的uv),至于如何计算可以参考我的另一篇博客根据深度信息重建屏幕像素在世界中的坐标,核心代码就是
float3 wp = _WorldSpaceCameraPos.xyz + i.ray * Linear01Depth(depth);
这样depthFactor
再乘上雾的颜色就得到了会流动的深度雾了。
高度雾
既然有通过深度来将雾的颜色混合场景颜色的方法,那当然也有通过高度来做雾效的方法,关键是后处理时如何得到场景的高度。方法上面已经说了,核心就是这句代码
float3 wp = _WorldSpaceCameraPos.xyz + i.ray * Linear01Depth(depth);
得到世界坐标了那么y就是我们所需要的高度了。再按照上面的思路加上雾的流动效果就大功告成!关键代码如下
float noise = tex2D(_NoiseTex, wp.xz * _WorldPosScale + _Time.x * fixed2(_NoiseSpX, _NoiseSpY)).r;
float heightNoise = noise * _HeightNoiseScale;
float heightFactor = (_HeightEnd - wp.y - heightNoise) / (_HeightEnd - _HeightStart);
最后,我们希望雾不要去影响粒子特效这类半透明的物体,所以用了CommandBuffer来指定渲染的时机,代码如下
cmd = new CommandBuffer();
cmd.name = "Fog";
int fogID = Shader.PropertyToID("_FogTex");
cmd.GetTemporaryRT(fogID,-1,-1,24,FilterMode.Bilinear);
int fogID2 = Shader.PropertyToID("_Temp");
cmd.GetTemporaryRT(fogID2,-1,-1,24,FilterMode.Bilinear);
cmd.Blit(BuiltinRenderTextureType.CurrentActive,fogID2);
cmd.Blit(fogID2,fogID);
cmd.Blit(fogID,fogID2,mat);
cmd.SetGlobalTexture("_FogTex",fogID2);
cmd.Blit(fogID, BuiltinRenderTextureType.CameraTarget,blend);
cam.AddCommandBuffer(CameraEvent.BeforeForwardAlpha,cmd);
这里我本意是只想用一张RT来承载雾效的,不过试了一下在手机上会出现雾效不断叠加的效果,不是我想要的,不得已只好开了两张RT。。。
并且,初始化RT时要用cmd.Blit(BuiltinRenderTextureType.CurrentActive,fogID2);
这句,不然你会深切感受到IMR和TBDR渲染机制的不同,各位可以把这句注释掉再在iPhone上打个包试试,就明白我在说什么了。
最后,放上项目地址,大家可以慢慢玩。
参考
Unity Shader PostProcessing - 11 - 雾效/深度雾效/高度雾/深度+高度雾
大萌喵的着色器特效第三发---垂直雾(Vertical Fog)