<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          高性能模糊算法 Dual Blur 在 2D Sprite 上的實(shí)現(xiàn)與應(yīng)用丨Cocos Creator

          共 9754字,需瀏覽 20分鐘

           ·

          2022-03-17 14:02

          引言:在游戲開發(fā)中,很多效果的實(shí)現(xiàn)都離不開圖像模糊算法的運(yùn)用。今天,一起來看看社區(qū)開發(fā)者「詠恆の承諾」是如何基于 RenderTexture 實(shí)現(xiàn)多 Pass Kawase Blur。


          屏幕后處理效果(Screen Post Processing Effects)是游戲中實(shí)現(xiàn)屏幕特效的方法,有助于提升畫面效果。圖像模糊算法在后處理渲染領(lǐng)域占據(jù)著重要地位,泛光(Bloom)、鏡頭眩光光暈(Glare Lens Flare)、景深(Depth of Field)、體積光(Volume Ray)等許多效果都用到了圖像模糊算法。所以說,后處理中所采用模糊算法的優(yōu)劣,決定了后處理管線最終的渲染品質(zhì)和消耗性能的多少。


          后處理管線中會(huì)使用到十種模糊算法總結(jié)


          前段時(shí)間,由于項(xiàng)目需要做一個(gè)背景模糊的功能,正巧之前看到了大城小胖在《如何重繪<江南百景圖>》中對(duì)比了幾種模糊算法,本著學(xué)習(xí)的態(tài)度,我決定嘗試在 Cocos Creator 2.4.x 中實(shí)現(xiàn)?Dual Blur(雙重模糊)。


          最終效果


          實(shí)現(xiàn)多 Pass


          首先要解決的問題是:如何在 v2.4.x 中實(shí)現(xiàn)多 pass?


          參考陳皮皮大佬的實(shí)現(xiàn)方案[2],基于 RenderTexture 實(shí)現(xiàn)多 Pass Kawase Blur。先將紋理渲染到 RenderTexture(下文簡(jiǎn)稱 RT)上,再對(duì)得到的 RT 做單次模糊處理并得到新的 RT,重復(fù)此操作,將最后一個(gè) RT 渲染到需要的 Sprite 中即可。


          注意:每次渲染得到的 RT 是倒置的,渲染前的紋理 Y 軸相反。


          protected?renderWithMaterial(srcRT:?cc.RenderTexture,?dstRT:?cc.RenderTexture?|?cc.Material,?material?:?cc.Material,?size?:?cc.Size)?{
          ????//?檢查參數(shù)
          ????if?(dstRT?instanceof?cc.Material)?{
          ????????material?=?dstRT;
          ????????dstRT?=?new?cc.RenderTexture();
          ????}
          ????//?創(chuàng)建臨時(shí)節(jié)點(diǎn)(用于渲染?RenderTexture)
          ????const?tempNode?=?new?cc.Node();
          ????tempNode.setParent(cc.Canvas.instance.node);
          ????const?tempSprite?=?tempNode.addComponent(cc.Sprite);
          ????tempSprite.sizeMode?=?cc.Sprite.SizeMode.RAW;
          ????tempSprite.trim?=?false;
          ????tempSprite.spriteFrame?=?new?cc.SpriteFrame(srcRT);
          ????//?獲取圖像寬高
          ????const?{?width,?height?}?=?size????{?width:?srcRT.width,?height:?srcRT.height?};
          ????//?初始化?RenderTexture
          ????//?如果截圖內(nèi)容中不包含?Mask?組件,可以不用傳遞第三個(gè)參數(shù)
          ????dstRT.initWithSize(width,?height,?cc.gfx.RB_FMT_S8);
          ????//?更新材質(zhì)
          ????if?(material?instanceof?cc.Material)?{
          ????????tempSprite.setMaterial(0,?material);
          ????}
          ????//?創(chuàng)建臨時(shí)攝像機(jī)(用于渲染臨時(shí)節(jié)點(diǎn))
          ????const?cameraNode?=?new?cc.Node();
          ????cameraNode.setParent(tempNode);
          ????const?camera?=?cameraNode.addComponent(cc.Camera);
          ????camera.clearFlags?|=?cc.Camera.ClearFlags.COLOR;
          ????camera.backgroundColor?=?cc.color(0,?0,?0,?0);
          ????//?根據(jù)屏幕適配方案,決定攝像機(jī)縮放比
          ????//?還原sizeScale,zoomRatio取屏幕與RT寬高比
          ????camera.zoomRatio?=?cc.winSize.height?/?srcRT.height;
          ????//?將臨時(shí)節(jié)點(diǎn)渲染到?RenderTexture?中
          ????camera.targetTexture?=?dstRT;
          ????camera.render(tempNode);
          ????//?銷毀臨時(shí)對(duì)象
          ????cameraNode.destroy();
          ????tempNode.destroy();
          ????//?返回?RenderTexture
          ????return?dstRT;
          }


          提示!需要留意 cc.RenderTexture.initWithSize(width, height, depthStencilFormat)?中的第3個(gè)參數(shù),之前使用時(shí)我忽略了第3個(gè)參數(shù),加上場(chǎng)景比較復(fù)雜,需要截圖的結(jié)點(diǎn)中帶有 Mask 組件,導(dǎo)致截圖丟失了 Mask 組件所在結(jié)點(diǎn)之前的所有圖片。


          查看源碼可知道,initWithSize 默認(rèn)會(huì)清除深度緩沖區(qū)、模版緩沖區(qū),depthStencilFormat 傳入 gfx.RB_FMT_D16gfx.RB_FMT_S8、gfx.RB_FMT_D24S8 時(shí),則可以保留對(duì)應(yīng)緩沖區(qū)。感謝鴉哥(渡鴉)的文章《實(shí)現(xiàn)單個(gè) Node 截圖的兩種方式》[3],代碼+注釋太香了!

          /**
          *?!#en
          *?Init?the?render?texture?with?size.
          *?!#zh
          *?初始化?render?texture?
          *?@param?{Number}?[width]?
          *?@param?{Number}?[height]
          *?@param?{Number}?[depthStencilFormat]
          *?@method?initWithSize
          */

          initWithSize?(width,?height,?depthStencilFormat)?{
          ????this.width?=?Math.floor(width?||?cc.visibleRect.width);
          ????this.height?=?Math.floor(height?||?cc.visibleRect.height);
          ????this._resetUnderlyingMipmaps();

          ????let?opts?=?{
          ????colors:?[?this._texture?],
          ????};

          ????if?(this._depthStencilBuffer)?this._depthStencilBuffer.destroy();
          ????let?depthStencilBuffer;
          ????if?(depthStencilFormat)?{
          ????depthStencilBuffer?=?new?gfx.RenderBuffer(renderer.device,?depthStencilFormat,?width,?height);
          ????if?(depthStencilFormat?===?gfx.RB_FMT_D24S8)?{
          ????opts.depthStencil?=?depthStencilBuffer;
          ????}
          ????else?if?(depthStencilFormat?===?gfx.RB_FMT_S8)?{
          ????opts.stencil?=?depthStencilBuffer;
          ????}
          ????else?if?(depthStencilFormat?===?gfx.RB_FMT_D16)?{
          ????opts.depth?=?depthStencilBuffer;
          ????}
          ????}
          ????this._depthStencilBuffer?=?depthStencilBuffer;
          ????if?(this._framebuffer)?this._framebuffer.destroy();
          ????this._framebuffer?=?new?gfx.FrameBuffer(renderer.device,?width,?height,?opts);

          ????this._packable?=?false;

          ????this.loaded?=?true;
          ????this.emit("load");
          },


          Dual Blur(雙重模糊)


          接下來只需實(shí)現(xiàn) Dual Blur 算法即可。首先簡(jiǎn)單了解一下 Dual Blur,此處引用《高品質(zhì)后處理:十種圖像模糊算法的總結(jié)與實(shí)現(xiàn)》[4]一文。


          Dual Kawase Blur,簡(jiǎn)稱 Dual Blur,是一種衍生自 Kawase Blur 的模糊算法,其由兩種不同的 Blur Kernel 構(gòu)成。相較于 Kawase Blur 在兩個(gè)大小相等的紋理之間進(jìn)行乒乓 blit 的的思路,Dual Kawase Blur 的核心思路在于 blit 過程中進(jìn)行降采樣和升采樣,即對(duì) RT 進(jìn)行了降采樣以及升采樣。



          由于靈活的升降采樣帶來了 blit RT 所需計(jì)算量的減少等原因,Dual Kawase Blur 相對(duì)而言有更好的性能。下圖是相同條件下幾種模糊算法的性能對(duì)比,可以看到,Dual Kawase Blur 在其中具有最佳的性能表現(xiàn)。



          為了帶來更好的性能表現(xiàn),可以將 uv 的偏移放在 Vert Shader 中進(jìn)行,而 Fragment Shader 中基本上僅進(jìn)行采樣即可。


          此外,為了支持合圖也能使用,這里我修改了頂點(diǎn)數(shù)據(jù)。

          //?Dual?Kawase?Blur?(雙重模糊)
          //?教程地址:https://github.com/QianMo/X-PostProcessing-Library/tree/master/Assets/X-PostProcessing/Effects/DualKawaseBlur

          CCEffect?%{
          ??techniques:
          ??-?name:?Down
          ????passes:
          ????-?name:?Down
          ??????vert:?vs:Down
          ??????frag:?fs:Down
          ??????blendState:
          ????????targets:
          ????????-?blend:?true
          ??????rasterizerState:
          ????????cullMode:?none
          ??????properties:?&prop
          ????????texture:?{?value:?white?}
          ????????resolution:?{?value:?[1920,?1080]?}
          ????????offset:?{?value:?1,?editor:?{?range:?[0,?100]?}}
          ????????alphaThreshold:?{?value:?0.5?}
          ??-?name:?Up
          ????passes:
          ????-?name:?Up
          ??????vert:?vs:Up
          ??????frag:?fs:Up
          ??????blendState:
          ????????targets:
          ????????-?blend:?true
          ??????rasterizerState:
          ????????cullMode:?none
          ??????properties:?*prop
          }%


          CCProgram?vs?%{
          ??precision?highp?float;

          ??#include?
          ??#include?

          ??in?vec3?a_position;
          ??in?vec4?a_color;
          ??out?vec4?v_color;

          ??#if?USE_TEXTURE
          ??in?vec2?a_uv0;

          ??out?vec2?v_uv0;
          ??out?vec4?v_uv1;
          ??out?vec4?v_uv2;
          ??out?vec4?v_uv3;
          ??out?vec4?v_uv4;

          ??#endif

          ??uniform?Properties?{
          ????vec2?resolution;
          ????float?offset;
          ??};

          ??vec4?Down?()?{
          ????vec4?pos?=?vec4(a_position,?1);

          ????#if?CC_USE_MODEL
          ????pos?=?cc_matViewProj?*?cc_matWorld?*?pos;
          ????#else
          ????pos?=?cc_matViewProj?*?pos;
          ????#endif

          ????#if?USE_TEXTURE
          ????vec2?uv?=?a_uv0;
          ????vec2?texelSize?=?0.5?/?resolution;
          ????v_uv0?=?uv;
          ????v_uv1.xy?=?uv?-?texelSize?*?vec2(offset);?//top?right
          ????v_uv1.zw?=?uv?+?texelSize?*?vec2(offset);?//bottom?left
          ????v_uv2.xy?=?uv?-?vec2(texelSize.x,?-texelSize.y)?*?vec2(offset);?//top?right
          ????v_uv2.zw?=?uv?+?vec2(texelSize.x,?-texelSize.y)?*?vec2(offset);?//bottom?left
          ????#endif

          ????v_color?=?a_color;

          ????return?pos;
          ??}
          ??
          ??vec4?Up?()?{
          ????vec4?pos?=?vec4(a_position,?1);

          ????#if?CC_USE_MODEL
          ????pos?=?cc_matViewProj?*?cc_matWorld?*?pos;
          ????#else
          ????pos?=?cc_matViewProj?*?pos;
          ????#endif

          ????#if?USE_TEXTURE
          ????vec2?uv?=?a_uv0;
          ????vec2?texelSize?=?0.5?/?resolution;
          ????v_uv0?=?uv;
          ????v_uv1.xy?=?uv?+?vec2(-texelSize.x?*?2.,?0)?*?offset;
          ????v_uv1.zw?=?uv?+?vec2(-texelSize.x,?texelSize.y)?*?offset;
          ????v_uv2.xy?=?uv?+?vec2(0,?texelSize.y?*?2.)?*?offset;
          ????v_uv2.zw?=?uv?+?texelSize?*?offset;
          ????v_uv3.xy?=?uv?+?vec2(texelSize.x?*?2.,?0)?*?offset;
          ????v_uv3.zw?=?uv?+?vec2(texelSize.x,?-texelSize.y)?*?offset;
          ????v_uv4.xy?=?uv?+?vec2(0,?-texelSize.y?*?2.)?*?offset;
          ????v_uv4.zw?=?uv?-?texelSize?*?offset;
          ????#endif

          ????v_color?=?a_color;

          ????return?pos;
          ??}
          }%

          CCProgram?fs?%{
          ??precision?highp?float;
          ??
          ??#include?
          ??#include?
          ??#include?

          ??in?vec4?v_color;

          ??#if?USE_TEXTURE
          ??in?vec2?v_uv0;
          ??in?vec4?v_uv1;
          ??in?vec4?v_uv2;
          ??in?vec4?v_uv3;
          ??in?vec4?v_uv4;
          ??uniform?sampler2D?texture;
          ??#endif

          ??uniform?Properties?{
          ????vec2?resolution;
          ????float?offset;
          ??};

          ??vec4?Down?()?{
          ????vec4?sum?=?vec4(1);

          ????#if?USE_TEXTURE
          ??????sum?=?texture2D(texture,?v_uv0)?*?4.;
          ??????sum?+=?texture2D(texture,?v_uv1.xy);
          ??????sum?+=?texture2D(texture,?v_uv1.zw);
          ??????sum?+=?texture2D(texture,?v_uv2.xy);
          ??????sum?+=?texture2D(texture,?v_uv2.zw);
          ??????sum?*=?0.125;
          ????#endif

          ????sum?*=?v_color;

          ????ALPHA_TEST(sum);

          ????return?CCFragOutput(sum);
          ??}

          ??vec4?Up?()?{
          ????vec4?sum?=?vec4(1);

          ????#if?USE_TEXTURE
          ??????CCTexture(texture,?v_uv1.xy,?sum);
          ??????sum?+=?texture2D(texture,?v_uv1.zw)?*?2.;
          ??????sum?+=?texture2D(texture,?v_uv2.xy);
          ??????sum?+=?texture2D(texture,?v_uv2.zw)?*?2.;
          ??????sum?+=?texture2D(texture,?v_uv3.xy);
          ??????sum?+=?texture2D(texture,?v_uv3.zw)?*?2.;
          ??????sum?+=?texture2D(texture,?v_uv4.xy);
          ??????sum?+=?texture2D(texture,?v_uv4.zw)?*?2.;
          ??????sum?*=?0.0833;
          ????#endif

          ????sum?*=?v_color;

          ????ALPHA_TEST(sum);

          ????return?CCFragOutput(sum);
          ??}

          }%


          有了 effect 后,需要?jiǎng)?chuàng)建2個(gè)材質(zhì)分別對(duì)應(yīng) techniques 中的 DownUp,示例代碼中用 materialDown、materialUp 來表示2個(gè)材質(zhì)。


          通過攝像機(jī)截圖,得到初始 RT 后(紋理倒置),對(duì)初始 RT 進(jìn)行降采樣和模糊得到新的 RT,重復(fù)若干次后,對(duì)最后的 RT 進(jìn)行相同次數(shù)的升采樣和模糊,得到最終滿足效果的 RT。當(dāng)降采樣 scale 不為1時(shí),設(shè)置 RT 尺寸時(shí)會(huì)自動(dòng)向下取整,倒置最終效果會(huì)有黑邊,iteration 次數(shù)越大越明顯,且 iteration 存在上限,實(shí)際使用時(shí)可自行取舍。

          /**
          ?*?模糊渲染
          ?*?@param?offset?模糊半徑
          ?*?@param?iteration?模糊迭代次數(shù)
          ?*?@param?scale?降采樣縮放比例
          ?*/

          blur(offset:?number,?iteration:?number,?scale:?number?=?0.5)?{
          ????//?設(shè)置源結(jié)點(diǎn)、目標(biāo)sprite
          ????const?spriteDst?=?this.spriteDst,
          ????????nodeSrc?=?this.spriteSrc.node;
          ????//?設(shè)置材質(zhì)
          ????const?material?=?this.materialDown;
          ????this.materialDown.setProperty('resolution',?cc.v2(nodeSrc.width,?nodeSrc.height));
          ????this.materialDown.setProperty('offset',?offset);
          ????this.materialUp.setProperty('resolution',?cc.v2(nodeSrc.width,?nodeSrc.height));
          ????this.materialUp.setProperty('offset',?offset);
          ????//?創(chuàng)建臨時(shí)?RenderTexture
          ????let?srcRT?=?new?cc.RenderTexture(),
          ????????lastRT?=?new?cc.RenderTexture();
          ????//?獲取初始?RenderTexture
          ????this.getRenderTexture(nodeSrc,?lastRT);
          ????//?多?Pass?處理
          ????//?注:由于 OpenGL 中的紋理是倒置的,所以雙數(shù) Pass 的出的圖像是顛倒的

          ????//?記錄升降紋理時(shí)紋理尺寸
          ????let?pyramid:?[number,?number][]?=?[],?tw:?number?=?lastRT.width,?th:?number?=?lastRT.height;
          ????//Downsample
          ????for?(let?i?=?0;?i?????????pyramid.push([tw,?th]);
          ????????[lastRT,?srcRT]?=?[srcRT,?lastRT];
          ????????//?縮小截圖尺寸,提高效率
          ????????//?縮小尺寸時(shí),RT會(huì)自動(dòng)向下取整,導(dǎo)致黑邊
          ????????tw?=?Math.max(tw?*?scale,?1),?th?=?Math.max(th?*?scale,?1);
          ????????this.renderWithMaterial(srcRT,?lastRT,?this.materialDown,?cc.size(tw,?th));
          ????}
          ????//?Upsample
          ????for?(let?i?=?iteration?-?1;?i?>=?0;?i--)?{
          ????????[lastRT,?srcRT]?=?[srcRT,?lastRT];
          ????????this.renderWithMaterial(srcRT,?lastRT,?this.materialUp,?cc.size(pyramid[i][0],?pyramid[i][1]));
          ????}
          ????//?使用經(jīng)過處理的?RenderTexture
          ????this.renderTexture?=?lastRT;
          ????spriteDst.spriteFrame?=?new?cc.SpriteFrame(this.renderTexture);
          ????//?翻轉(zhuǎn)紋理y軸
          ????spriteDst.spriteFrame.setFlipY(true);
          ????//?銷毀不用的臨時(shí)?RenderTexture
          ????srcRT.destroy();
          }




          以上是本次的分享,希望可以給大家一點(diǎn)啟發(fā)和幫助!歡迎點(diǎn)擊【閱讀原文】前往論壇專貼一起討論交流,完整代碼見代碼倉庫:

          https://github.com/RicardoZhou/CreatorStudy/tree/master/assets/Menu/Shader/DualBlur


          參考資料

          [1]如何重繪《江南百景圖》,大城小胖

          [2]基于 RenderTexture 實(shí)現(xiàn)多 Pass 的 Kawase Blur,陳皮皮

          https://forum.cocos.org/t/topic/126481

          [3]Creator丨實(shí)現(xiàn)單個(gè) Node 截圖的兩種方式,渡鴉

          [4]高品質(zhì)后處理:十種圖像模糊算法的總結(jié)與實(shí)現(xiàn),毛星云

          https://zhuanlan.zhihu.com/p/125744132

          [5]自定義頂點(diǎn)格式,GT

          https://forum.cocos.org/t/topic/95087


          往期精彩
          瀏覽 157
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  521av在线观看 | 亚洲成人h| 亚洲欧洲视频在线观看 | 中文字幕在线观看的视频 | 天天色天天日 |