ShaderOne的体积雾

ShaderOne的体积雾

ShaderOne 是Unity商店的一个shader资源,作者期望把所有的渲染特性集合到一个shader上去,所以起个名字叫 ShaderOne

本人其实比较反对这种 All In One 的shader,因为变种实在太多,优化起来比较蛋疼,不过作为一个学习资料还是挺好的。

今天我们来看一下他的 体积雾 的实现,先上截图:

screenshot1.gif

核心代码

说到体积雾,大家的第一个印象就是费。

没错了,指望 ShaderOne体积雾 能在手机上跑得飞起是有点强人所难的,因为计算涉及到 射线步进3d纹理采样

不过如果射线数设置的不那么高,高端手机还是可以一战的,文章的最后会给出一个测试。

在介绍 体积雾 之前,我们先看一下 ShaderOne 提供的全部雾效类型,应用雾效的入口代码如下:

inline void FogApply ( inout SOLightingData i_sold, in v2f i )
{

#ifdef SO_GD_BASE_PASS
    #ifdef SO_GD_FOG_SOLID
        FogApplySolid ( i_sold, i );
    #endif

    #ifdef SO_GD_FOG_VOLUMETRIC
        FogApplyVolumetric ( i_sold, i );
    #endif

    #ifdef SO_GD_FOG_VOLUMETRIC_3D
        FogApplyVolumetric3D ( i_sold, i );
    #endif

#endif
}

可以看到 ShaderOne 提供了3种雾:

  • FogApplySolid:线性雾
  • FogApplyVolumetric:线性雾 + 指数高度雾
  • FogApplyVolumetric3D: 线性雾 + 3d指数高度雾

FogApplySolid

FogApplySolid 就是最简单的 线性雾,提供的参数就三个:颜色、开始距离、结束距离。

screenshot2.png
screenshot3.png

代码简单到不想废话:

inline void FogApplySolid ( inout SOLightingData i_sold, in v2f i )
{
#ifdef SO_GD_BASE_PASS
#ifdef SO_SF_FOG_ON
#ifdef SO_GD_FOG_SOLID
    half    dist            = distance ( _WorldSpaceCameraPos.xyz , i.positionWorld );
    half    distFade        = 1.0 - clamp ( ( _FogEndDistance - dist ) / _FogSolidDistance, 0.0, 1.0 );
    i_sold.finalRGBA.rgb    = lerp ( i_sold.finalRGBA.rgb, _FogColorFade, distFade );
#endif
#endif
#endif
}

FogApplyVolumetric

FogApplyVolumetric指数高度雾 再混合之前的 线性雾

screenshot4.png
screenshot5.png

这里除了之前的线性雾参数之外,又加了高度雾参数。可以看到雾有高低和远近的过渡,并且有2种雾色。

关于指数高度雾,这里的计算公式如下:

fogAmount = 1.0 - exp ( -dist * ( fogHeightIntensity * _FogDensity ) );

这里的公式和Unity的 Exponential 雾类似,只是多了一个 fogHeightIntensity 这个高度因子。

fogHeightIntensity 的计算公式类似线性雾,不过是算的是高度:

fogHeightIntensity = ( 1.0 - min ( ( length (  i.positionWorld.y - _FogHeight ) / _FogHeightSize ), 1.0 ) );

最后雾效和场景颜色的混合计算,是先算指数高度雾,后算线性雾,代码如下:

i_sold.finalRGBA.rgb = lerp ( i_sold.finalRGBA.rgb, fogColor, fogAmount );
i_sold.finalRGBA.rgb = lerp ( i_sold.finalRGBA.rgb, _FogColorFade, distFade );

代码还是比较清楚的,市面上大部分手游的雾效和这个类似,只是计算公式可能有一点差别。

FogApplyVolumetric3D

最后来看一下 FogApplyVolumetric3D,也就是 体积雾

FogApplyVolumetric3DFogApplyVolumetric 的设置差不多,多了一些体积雾相关的参数,还有一个全局的射线数量需要设置:

screenshot6.png
screenshot7.png
screenshot8.png

两者的计算流程也非常相似,指数雾部分的公式如下:

fogAmount = 1.0 - exp ( -dist * fogAmount );

不过这里 fogAmount 的计算和 FogApplyVolumetric 相比就比较复杂了:为了平滑的勾勒出体积,这里需要依靠 射线步进,主要代码如下:

fogAmount       = 0;

for ( int i = 0; i < SO_GD_FOG_RAYCOUNT; i++ )
{
    rayPos      += viewDir * stepSize;

    fogValue    = tex3D ( _FogTexture3D, ( rayPos * _FogScale ) + fogUVOffset ).a;

    #ifdef SO_GD_FOG_ROUGHNESS_ON
        fogYLength = length ( rayPos.y - ( _FogHeight + ( ( fogValue - 0.5 ) * _FogVerticalRoughness ) ) );
    #else
        fogYLength  = length ( rayPos.y - _FogHeight );
    #endif

    heightIntensity  = 1.0 - min ( ( fogYLength / _FogHeightSize ), 1.0 );

    fogAmount   += fogValue * heightIntensity;
}

fogAmount = ( fogAmount / SO_GD_FOG_RAYCOUNT ) * _FogDensity;

上述代码的大致计算流程如下:

  • 从摄像机向场景发射 SO_GD_FOG_RAYCOUNT 条射线,这里的 SO_GD_FOG_RAYCOUNT 即前面的全局射线数,这个值越高效果越好,当然也越费。

  • 每一条射线从摄像机点沿着 viewDir 方向逐步向前探测 stepSize 段距离,并且从一个预定的3d纹理 _FogTexture3D 去采样雾的密度。

  • 最后把这么多根射线的采样结果加权平均,得出 fogAmount

关于_FogTexture3D

可以看到,雾的体积由 _FogTexture3D 这张3d纹理决定。

_FogTexture3D 是一个3维的噪声图,如果我们想要自己调整噪音参数,可以借由作者提供的一个工具:Fog Texture Designer

screenshot9.png

这个工具依赖另一个Unity的插件:FastNoise,具体细节可以参考插件源码,这个不是本文的重点。

关于stepSize的计算

前文提到 射线步进 的步长是 stepSizestepSize 的计算方式如下:

dist            = length ( _WorldSpaceCameraPos.xyz - i.positionWorld ) - _FogOffset;
distFade        = 1.0 - clamp ( ( _FogEndDistance - dist ) / _FogSolidDistance, 0.0, 1.0 );
stepSize        = dist / (float)SO_GD_FOG_RAYCOUNT;

这里比较简单,把dist按照射线数等分即可。

关于SO_GD_FOG_RAYCOUNT

SO_GD_FOG_RAYCOUNT 控制射线的数量,这个参数对体积雾的质量影响较大,下图是只发3条射线的计算结果:

screenshot10.png

可以看到之前的平滑过渡出现了断裂。

关于Fog Roughness

Fog Roughness 通过给高度增加扰动来增加雾的高低起伏,计算代码如下:

#ifdef SO_GD_FOG_ROUGHNESS_ON
    fogYLength = length ( rayPos.y - ( _FogHeight + ( ( fogValue - 0.5 ) * _FogVerticalRoughness ) ) );
#else
    fogYLength = length ( rayPos.y - _FogHeight );
#endif

heightIntensity  = 1.0 - min ( ( fogYLength / _FogHeightSize ), 1.0 );

下图是 Fog Roughness 设置为0和设置为16的效果对比:

screenshot11.png

移动设备的帧率

体积雾就介绍到这里了。

最后,看看哥的 小米MIX2 是否可以一战。

24条射线,Roughness不开很高的情况下,稳稳60帧,效果也能接受,如下图:

screenshot12.jpg

个人主页

本文的个人主页链接:https://baddogzz.github.io/2020/01/06/Volumetric-Fog/

好了,拜拜。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352