Unity Shader-Ambient Occlusion环境光遮蔽(AO贴图,GPU AO贴图烘焙,SSAO,HBAO)

环境光遮蔽主要用来控制物体和物体相交,夹角,褶皱等位置遮挡漫反射光线的效果,提高暗部阴影效果达到一种近似自阴影的效果,提升画面的层次感,增加细节。本文主要实现几种主流的环境光遮蔽效果,AO贴图(使用预烘焙的贴图,实现离线的基于GPU的烘焙AO贴图的工具),SSAO(屏幕空间环境光遮蔽),HBAO(水平基准环境光遮蔽)
摘要由CSDN通过智能技术生成

前言

十一放假很开心,正好赶上观望了了许久的《尼尔·机械纪元》打折啦。窝在家里搞了三天三夜,终于E结局通关啦!!!真的好久没玩过这么好玩的游戏了,于是乎我的废话应该会多不少,毕竟,写blog的另一个目的就是记录玩过的好玩的游戏,2333。

最开始听说这个游戏的时候,只是被2B小姐姐的人设吸引了,毕竟小姐姐还是很漂亮的。而且游戏类型也是我喜欢的类型,看起来打击感还不错,加上最近打《地铁》和《消失的光芒》玩出3D眩晕症了,就差嗑两片晕车药再打了,正好搞一个动作型的游戏换换口味。

玩起来我才发现游戏的音乐太好听啦,最近简直每天在循环停不下来,场景也是体积光,SSAO之类的高级效果大量使用。

但是游戏通关之后,我才发现游戏的故事本身就很让人深思,加上丰富的支线剧情。我不敢说玩的游戏很多,但是的确实本人最近玩过的画面,音乐,战斗,剧情都很给力的一款游戏了。

好在三周目打完之后,加上读档几次,终于打出了E结局,还算比较完美。然而游戏竟然有26个结局,等到重温时争取都打出来。

唉,一不小心就忍不住想贴贴贴,毕竟游戏太好玩了,但是到此为止了。我不想剧透,下面才是本文的正题。

简介

《尼尔·机械纪元》中有一个关卡--复制之街。刚一进这个场景,我不由得发出一声惊叹,“我靠,这场景贴图是不是丢了?”

不过这个场景风格也是蛮不错的,同时也让我想起了一个略微进阶一些的图形技术,环境光遮蔽(AO)。整个场景看起来没有表示颜色的albedo,但是场景的阴影效果和AO效果还是存在的,这让场景的层次细节在即使没有颜色的情况下也可以展现出来,形成了一种特殊的风格。

环境光遮蔽对效果的提升有多重要,看一下顽皮狗在《Uncharted 2: HDR Lighting》的一个对比图,可以看到,左侧的车底遮挡了大部分光线,形成了阴影看起来很自然,而右侧的车感觉就像飘在上面一样,看起来比较假:

环境光遮蔽(Ambient Occlusion),最经常听到的应该是它的缩写AO。既然名字本身就带Ambient,说明其本身是对于环境光强度的一种控制,所以有必要来先了解一下环境光的计算。

光照是可以线性叠加的,一般来说最终的光照结果 = 直接光照 +  间接光照。我们计算物体的直接光照效果时,可以直接通过BRDF计算,而环境光属于间接光照,要想计算真正的环境光,需要在该点法线方向所对应的半球积分计算,在离线渲染的情况下也只能通过蒙特卡洛积分等方式近似计算,对于光线追踪的方式渲染的情况,自然可以得到比较好的效果,但是即使现在的RTX似乎也不能真正地实时跑光线追踪,所以在实时渲染领域,环境光一般使用的就是环境贴图(SkyBox,Reflection Probe),球谐光照(Spherical Harmonic Lighting),光照贴图(Light Map,需要用离线烘焙),甚至直接加一个固定的环境光值(简单粗暴,比如Unity中的UNITY_LIGHTMODEL_AMBIENT宏)。普通光源的遮挡效果也就是阴影,我们可以通过Shadow Map,模板阴影等来实现,但是对于环境光的遮挡效果,半球上的光线自然没有方法用普通的Shadow Map方式来计算了。所以研究怎样遮挡环境光的强度的就叫环境光遮蔽。

环境光遮蔽主要用来控制物体和物体相交,夹角,褶皱等位置遮挡漫反射光线的效果,简单来说就是某一点对于环境的暴露比例,如果是平面,那么没有遮蔽;如果是夹角,褶皱等那么周围的面就会遮蔽一部分环境光,就导致该点的环境光相对较弱。如果环境光没有遮蔽效果,那么不管褶皱还是平面,环境光照结果是一致的。而环境光遮蔽可以使褶皱,夹角等位置的光照效果变弱(比如一根管子,在管口的位置应该比较亮,而越向内,应该越暗),提高暗部阴影效果达到一种近似自阴影的效果,提升画面的层次感,增加细节。

在了解了环境光遮蔽的基本概念之后,本文主要实现几种主流的环境光遮蔽效果,AO贴图(使用预烘焙的贴图,实现离线的基于GPU的烘焙AO贴图的工具),SSAO(屏幕空间环境光遮蔽),HBAO(水平基准环境光遮蔽)。

AO Map-环境光遮蔽贴图

首先看一下最简单的AO贴图的使用,这也是性能最好的方式,但是这并不代表这种方法整体性能好,只是在运行时使用了预计算的结果。而AO贴图的生成是使用光线追踪的方式,反而是这几种AO方式种耗时最长但是效果相对更好的一种,毕竟只要一离线,时间什么的都是次要的。

使用美术工具烘焙AO贴图

AO贴图技术已经比较古老了,现有的各种3D软件基本都支持AO的烘焙,如3dsMax,Maya等。我今天使用的是Substance Painter,这个功能很强大的软件,而且相比于前两者,烘焙比较方便,但是据说效果没有前两者好。不过这些都不重要,毕竟怎么烘焙,那是美术同学的事情。

使用Substance Painter的烘焙选项,支持直接烘焙Mesh,烘焙面板如下:

我们用一个小狮子的模型导入Substance Painter中,然后使用低模烘焙一发AO贴图,同时工具也支持带有法线贴图的低模烘焙AO贴图,可以把法线细节的AO效果也烘焙出来。烘焙的贴图如下,左侧为直接烘焙,右侧为带有法线贴图之后烘焙的AO效果:

如果抓帧哪个游戏看到某个类似的通道,在褶皱处偏黑的,可能就是AO贴图啦。得到AO贴图之后,下面就需要看一下AO贴图的使用了。

AO贴图的使用

上面说过,光照是可以线性叠加的,全局光照 = 直接光照 + 间接光照。Unity也不例外,下面是Unity官方Shader的光照叠加部分:

 half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm)
                    + specularTerm * light.color * FresnelTerm (specColor, lh)
                    + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);

不考虑菲尼尔项的话,就是direct diffuse + direct specular + gi diffuse + gi specular,前两者通过BRDF计算,而后两者就是所谓的环境光,我们看一下Unity官方的GI Shader源代码:

inline UnityGI UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld)
{
    UnityGI o_gi;
    ResetUnityGI(o_gi);

    // Base pass with Lightmap support is responsible for handling ShadowMask / blending here for performance reason
    #if defined(HANDLE_SHADOWS_BLENDING_IN_GI)
        half bakedAtten = UnitySampleBakedOcclusion(data.lightmapUV.xy, data.worldPos);
        float zDist = dot(_WorldSpaceCameraPos - data.worldPos, UNITY_MATRIX_V[2].xyz);
        float fadeDist = UnityComputeShadowFadeDistance(data.worldPos, zDist);
        data.atten = UnityMixRealtimeAndBakedShadows(data.atten, bakedAtten, UnityComputeShadowFade(fadeDist));
    #endif

    o_gi.light = data.light;
    o_gi.light.color *= data.atten;

    #if UNITY_SHOULD_SAMPLE_SH
        o_gi.indirect.diffuse = ShadeSHPerPixel(normalWorld, data.ambient, data.worldPos);
    #endif

    #if defined(LIGHTMAP_ON)
        // Baked lightmaps
        half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy);
        half3 bakedColor = DecodeLightmap(bakedColorTex);

        #ifdef DIRLIGHTMAP_COMBINED
            fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
            o_gi.indirect.diffuse += DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld);

            #if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
                ResetUnityLight(o_gi.light);
                o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap (o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
            #endif

        #else // not directional lightmap
            o_gi.indirect.diffuse += bakedColor;

            #if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
                ResetUnityLight(o_gi.light);
                o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap(o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
            #endif

        #endif
    #endif

    #if
  • 45
    点赞
  • 152
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值