前些日子偶然看見群友分享的車展場景的視頻,車輛的尾燈亮起的時(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ù)。







