BabylonJS后處理系列: Streak效果

前些日子偶然看見群友分享的車展場景的視頻,車輛的尾燈亮起的時(shí)候,屏幕有水平臟鏡的效果,想起之前在Keijiro大佬的Git上面看到過這個(gè)效果(https://github.com/keijiro/Kino),原理也十分簡單,遂拿來和大家一起學(xué)習(xí)分享。

https://github.com/keijiro/Kino

發(fā)文前測試用PG:https://playground.babylonjs.com/#0S31R1#7


這個(gè)場景我使用一個(gè)簡單的Shader實(shí)現(xiàn)了一個(gè)按uv.y分段隨機(jī)高亮的效果,這個(gè)Shader使用round函數(shù)沿Y軸對模型進(jìn)行分層,然后使用非常常用的noise函數(shù)隨機(jī)顏色和亮度。由于我們還沒有使用后處理開啟HDR紋理模式,顏色會(huì)顯得比較蒼白(https://playground.babylonjs.com/#APCGJ1#1

    float rand(vec2 n) {
        return fract(cos(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
    }

    float noise(vec2 n) {
        const vec2 d = vec2(0.0, 1.0);
        vec2 b = floor(n);
        vec2 f = smoothstep(vec2(0.0), vec2(1.0), fract(n));
        return mix(mix(rand(b), rand(b + d.yx), f.x), mix(rand(b + d.xy), rand(b + d.yy), f.x), f.y);
    }

    void main(void)
    {
        float c = vUV.x;
        float y = round(vUV.y * 30.)/30.;
        float scale = 100.;
        float cy = noise(vec2(vUV.y*scale,vUV.y*scale));
        float cr = noise(vec2(vUV.y*scale+scale,vUV.y*scale+scale));
        float cg = noise(vec2(vUV.y*scale+scale*2.,vUV.y*scale+scale*2.));
        float cb = noise(vec2(vUV.y*scale+scale*3.,vUV.y*scale+scale*3.));
        vec3 randC = vec3(cr,cg,cb);
        gl_FragColor = vec4(vec3(pow(c * cy,8.)*100.) * randC, 1.);
    }
//

想要制作Streak后處理,我們有兩件事需要做:提取水平臟鏡的發(fā)光主體和水平拉絲。

1.第一件事我們有兩種方式實(shí)現(xiàn),第一種規(guī)則是高亮發(fā)光,類似于Babylon DefaultRenderPipeline的Bloom效果,我們在HDR模式渲染場景,使用一個(gè)Threshold去截取高亮部分,讓高亮部分作為發(fā)光的源頭,第二種方法是使用Mask把希望發(fā)光的部分標(biāo)記,然后讓標(biāo)記的部分作為發(fā)光的源頭。這兩種方法有不同的應(yīng)用場景,如果希望水平臟鏡是全局的效果,可以使用第一種方法,如果只希望部分物體臟鏡且不希望它們過亮影響場景的觀感,選擇第二種(如汽車的尾燈),本文為了方便使用第一種方法提取發(fā)光部分。我們使用第一個(gè)EffectWrapper來提取高亮部分,由于水平臟鏡是一種非??鋸埖哪:Ч?,對紋理的精度要求不是非常的高,為了降低計(jì)算量,我們配置的RTT(RenderTargetTexture)的高度只有原窗口的一半,同時(shí)采樣的時(shí)候做了一部分額外的豎直方向的平滑,我們拿從平滑得到的顏色取RGB三值中的最大值和Threshold做比較,得到選取的高亮部分。


Streak_Down_0.png
    precision highp float;
    varying vec2 vUV;
    
    uniform sampler2D textureSampler;
    uniform vec2 u_Resolution;
    //x:threshold y:stretch, z:intensity
    uniform vec4 streakParams;

    void main(void)
    {
        //豎直方向的平滑
        vec2 ss = vUV - vec2(0.,0.5/u_Resolution.y);
        vec2 ss1 = vUV + vec2(0.,0.5/u_Resolution.y);
        vec3 c0 = texture2D(textureSampler,ss).rgb;
        vec3 c1 = texture2D(textureSampler,ss1).rgb;
        vec3 c = (c0 + c1) / 2.;
        //選取RGB中最大值去和threshold作比較
        float br = max(c.r, max(c.g, c.b));
        float _Threshold = streakParams.x;
        c *= max(0., br - _Threshold) ;
        gl_FragColor = vec4(c, 1.);
        // gl_FragColor = vec4(vec3(max(0., br-1. )),1.);
    }

第二件事是水平拉絲,這個(gè)過程比較繁瑣。首先我們可以看一個(gè)Demo,這個(gè)demo主要是將圖像強(qiáng)制縮小到寬度較小的紋理中再強(qiáng)制拉伸到原來的尺寸中,按↑↓鍵可以調(diào)整縮小的比例(https://playground.babylonjs.com/#UHAXWA#1)。當(dāng)按↑到一定程度就出現(xiàn)了水平拉絲的圖像,但是這種方法很明顯存在兩個(gè)問題:拉絲寬度不夠以及沒辦法細(xì)節(jié)控制。

簡單拉絲圖

為了解決問題并實(shí)現(xiàn)更好的效果,我們可以對上述過程進(jìn)行優(yōu)化,首先我們舍棄一次性壓縮,選擇以2為單位分多次對原圖進(jìn)行水平壓縮,在每一次壓縮時(shí),對上一級(jí)的紋理進(jìn)行一步水平方向的模糊,模糊的效果可以沿著縮小的次數(shù)指數(shù)傳遞到最后一張圖,使得后面的低級(jí)圖有更強(qiáng)的模糊效果。

    precision highp float;
    varying vec2 vUV;
    
    uniform sampler2D textureSampler;
    uniform vec2 u_Resolution;

    void main(void)
    {
        vec2 uv = vUV;
        float dx = 1./u_Resolution.x;

        float u0 = uv.x - dx * 5.;
        float u1 = uv.x - dx * 3.;
        float u2 = uv.x - dx * 1.;
        float u3 = uv.x + dx * 1.;
        float u4 = uv.x + dx * 3.;
        float u5 = uv.x + dx * 5.;

        vec3 c0 = texture2D(textureSampler, vec2(u0, uv.y)).rgb;
        vec3 c1 = texture2D(textureSampler, vec2(u1, uv.y)).rgb;
        vec3 c2 = texture2D(textureSampler, vec2(u2, uv.y)).rgb;
        vec3 c3 = texture2D(textureSampler, vec2(u3, uv.y)).rgb;
        vec3 c4 = texture2D(textureSampler, vec2(u4, uv.y)).rgb;
        vec3 c5 = texture2D(textureSampler, vec2(u5, uv.y)).rgb;

        vec3 c = (c0 + c1 * 2. + c2 * 3. + c3 * 3. + c4 * 2. + c5) / 6.;
        gl_FragColor = vec4(c, 1.);
    }

在一步的計(jì)算下,我們可以得到從短到長的一系列拉絲圖(如拼接圖1),明顯這些圖已有水平拉絲的雛形,一般我們希望靠近發(fā)光點(diǎn)的地方亮,然后朝著發(fā)光點(diǎn)的兩側(cè)漸弱,我們可以從右向左依次混合,亮度的權(quán)重從右向左依次增加,就可以產(chǎn)生中間亮兩邊弱的圖像(如拼接圖2)。


拼接圖1

拼接圖2

這一步我們使用了一個(gè)新的Pass對紋理進(jìn)行混合(如下文),我們在這個(gè)Pass里面添加了兩個(gè)控制參數(shù):_Stretch 和_StretchIntensity ,其中_Stretch是右邊低級(jí)圖疊加的權(quán)重,數(shù)值越小衰減越快,_StretchIntensity 是右邊低級(jí)圖的亮度。

    precision highp float;
    varying vec2 vUV;
    
    uniform sampler2D textureSampler;
    uniform sampler2D _HighTexture;
    uniform vec2 u_Resolution;
    //x:threshold y:stretch, z:intensity
    uniform vec4 streakParams;

    void main(void)
    {
        vec2 uv = vUV;
        float dx = 1./u_Resolution.x *1.5;

        float u0 = uv.x;
        float u11 = uv.x - dx;
        float u12 = uv.x + dx;
        float u21 = uv.x - dx * 2.;
        float u22 = uv.x + dx * 2.;

        vec3 c0 = texture2D(textureSampler, vec2(u0, uv.y)).rgb;
        vec3 c11 = texture2D(textureSampler, vec2(u11, uv.y)).rgb;
        vec3 c12 = texture2D(textureSampler, vec2(u12, uv.y)).rgb;
        vec3 c21 = texture2D(textureSampler, vec2(u21, uv.y)).rgb;
        vec3 c22 = texture2D(textureSampler, vec2(u22, uv.y)).rgb;
        vec3 c3 = texture2D(_HighTexture,  uv).rgb;
        float _Stretch = streakParams.y;
        float _StretchIntensity = streakParams.z;
        gl_FragColor = vec4(mix(c3, (c0+ (c11+c12)/2.+(c21+c22)/4.)*_StretchIntensity, _Stretch), 1.);
    }

上面幾步完成了拉絲的效果,接下來要將獲取的拉絲圖和原圖進(jìn)行疊加,這一步我們對拉斯圖進(jìn)行進(jìn)一步的水平模糊處理,添加了一個(gè)參數(shù)_Intensity控制水平拉絲光的強(qiáng)度。

    precision highp float;
    varying vec2 vUV;
    
    uniform sampler2D textureSampler;
    uniform sampler2D streakTex;

    uniform vec2 u_Resolution;
    //x:threshold y:stretch, z:intensity
    uniform vec4 streakParams;
    uniform vec3 _Color;

    void main(void)
    {
        vec2 uv = vUV;
        float dx = 1./u_Resolution.x * 1.5;

        float u0 = uv.x - dx;
        float u1 = uv.x;
        float u2 = uv.x + dx;

        vec3 c0 = texture2D(streakTex,vec2(u0, uv.y)).rgb;
        vec3 c1 = texture2D(streakTex,vec2(u1, uv.y)).rgb;
        vec3 c2 = texture2D(streakTex,vec2(u2, uv.y)).rgb;

        vec3 c3 = texture2D(textureSampler,vUV).rgb;

        float _Intensity = streakParams.w;
        vec3 cf = (c0 / 4. + c1 / 2. + c2 / 4.) * _Color * _Intensity * 5.;
        
        gl_FragColor = vec4(cf + c3, 1.);

    }

最終大功告成,完成了水平拉絲的后處理效果,可以訪問PG:https://playground.babylonjs.com/#0S31R1#8體驗(yàn)后處理的效果。如圖,選中pipeline節(jié)點(diǎn),通過自定義字段修改后處理的四個(gè)參數(shù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容