作者:位元組流動
來源:
https://blog.csdn.net/Kennethdroid/article/details/103449935OpenGLES 相機抖音濾鏡
最近幾篇文章主要是利用 OpenGL 實作相機預覽的一些常見的濾鏡,上一篇主要介紹了 LUT 濾鏡的原理及簡單實作方法,而本文主要介紹抖音短視訊 App 裡面一些常見濾鏡的實作,這裡隻做抛磚引玉,玩濾鏡主要靠想象力去實作一些酷炫的效果。
分色偏移
分色偏移濾鏡原理:基于原紋理坐标進行偏移,分别采樣後再按照 RGBA 通道進行合成,組成一個新的顔色。
//分色偏移
#version 100
precision highp float;
varying vec2 v_texcoord;
uniform lowp sampler2D s_textureY;
uniform lowp sampler2D s_textureU;
uniform lowp sampler2D s_textureV;
uniform float u_offset;
vec4 YuvToRgb(vec2 uv) {
float y, u, v, r, g, b;
y = texture2D(s_textureY, uv).r;
u = texture2D(s_textureU, uv).r;
v = texture2D(s_textureV, uv).r;
u = u - 0.5;
v = v - 0.5;
r = y + 1.403 * v;
g = y - 0.344 * u - 0.714 * v;
b = y + 1.770 * u;
return vec4(r, g, b, 1.0);
}
void main()
{
vec4 originColor = YuvToRgb(v_texcoord);
//右下方偏移
vec4 offsetColor0 = YuvToRgb(vec2(v_texcoord.x + u_offset, v_texcoord.y + u_offset));
//左上方偏移
vec4 offsetColor1 = YuvToRgb(vec2(v_texcoord.x - u_offset, v_texcoord.y - u_offset));
//混合成一個顔色輸出
gl_FragColor = vec4(originColor.r, offsetColor1.g, offsetColor0.b, originColor.a);
}
靈魂出竅
靈魂出竅濾鏡的原理:根據偏移量 offset,進行 scale 變換紋理坐标,分别進行采樣後,再按照混合系數進行權重混合。
//靈魂出竅
#version 100
precision highp float;
varying vec2 v_texcoord;
uniform lowp sampler2D s_textureY;
uniform lowp sampler2D s_textureU;
uniform lowp sampler2D s_textureV;
uniform float u_offset;
uniform vec2 texSize;
vec4 YuvToRgb(vec2 uv) {
float y, u, v, r, g, b;
y = texture2D(s_textureY, uv).r;
u = texture2D(s_textureU, uv).r;
v = texture2D(s_textureV, uv).r;
u = u - 0.5;
v = v - 0.5;
r = y + 1.403 * v;
g = y - 0.344 * u - 0.714 * v;
b = y + 1.770 * u;
return vec4(r, g, b, 1.0);
}
const float MAX_ALPHA = 0.5;
const float MAX_SCALE = 0.8;
void main()
{
//根據偏移量計算混合系數 alpha
float alpha = MAX_ALPHA * (1.0 - u_offset);
//根據偏移量計算混合系數 scale
float scale = 1.0 + u_offset * MAX_SCALE;
//縮放操作
float scale_x = 0.5 + (v_texcoord.x - 0.5) / scale;
float scale_y = 0.5 + (v_texcoord.y - 0.5) / scale;
vec2 scaleCoord = vec2(scale_x, scale_y);
vec4 maskColor = YuvToRgb(scaleCoord);
vec4 originColor = YuvToRgb(v_texcoord);
//權重混合
gl_FragColor = originColor * (1.0 - alpha) + maskColor * alpha;
}
旋轉的圓
旋轉的圓:對某一半徑内的所有像素,按照偏移量轉換成的角度進行旋轉,半徑之外的像素正常渲染。
//旋轉的圓
#version 100
precision highp float;
varying vec2 v_texcoord;
uniform lowp sampler2D s_textureY;
uniform lowp sampler2D s_textureU;
uniform lowp sampler2D s_textureV;
uniform float u_offset;
uniform vec2 texSize;
vec4 YuvToRgb(vec2 uv) {
float y, u, v, r, g, b;
y = texture2D(s_textureY, uv).r;
u = texture2D(s_textureU, uv).r;
v = texture2D(s_textureV, uv).r;
u = u - 0.5;
v = v - 0.5;
r = y + 1.403 * v;
g = y - 0.344 * u - 0.714 * v;
b = y + 1.770 * u;
return vec4(r, g, b, 1.0);
}
const float PI = 3.141592653;
void main()
{
//紋理坐标轉為圖檔坐标
vec2 imgTex = v_texcoord * texSize;
float r = 0.3 * texSize.x; //設定半徑為圖檔寬度的 0.3 倍
//取圓心為中心點
if(distance(imgTex, vec2(texSize.x / 2.0, texSize.y / 2.0)) < r)
{
vec2 tranTex = v_texcoord - 0.5;
vec2 imgTranTex = tranTex * texSize;
float len = length(imgTranTex);
float angle = 0.0;
angle = acos(imgTranTex.x / len);
if(tranTex.y < 0.0)
{
angle *= -1.0;
}
angle -= u_offset;
imgTranTex.x = len * cos(angle);
imgTranTex.y = len * sin(angle);
vec2 newTexCoors = imgTranTex / texSize + 0.5;
gl_FragColor = YuvToRgb(newTexCoors);
}
else
{
gl_FragColor = YuvToRgb(v_texcoord);
}
}
畫中畫
畫中畫:将原紋理采樣到螢幕中間的一塊區域中,而螢幕之外區域的紋理坐标進行縮放之後再進行采樣。
//畫中畫
#version 100
precision highp float;
varying vec2 v_texcoord;
uniform lowp sampler2D s_textureY;
uniform lowp sampler2D s_textureU;
uniform lowp sampler2D s_textureV;
vec4 YuvToRgb(vec2 uv) {
float y, u, v, r, g, b;
y = texture2D(s_textureY, uv).r;
u = texture2D(s_textureU, uv).r;
v = texture2D(s_textureV, uv).r;
u = u - 0.5;
v = v - 0.5;
r = y + 1.403 * v;
g = y - 0.344 * u - 0.714 * v;
b = y + 1.770 * u;
return vec4(r, g, b, 1.0);
}
vec2 scale(vec2 uv, float level)
{
vec2 center = vec2(0.5, 0.5);
vec2 newTexCoord = uv.xy;
newTexCoord -= center;
newTexCoord = newTexCoord / level;
newTexCoord += center;
return newTexCoord;
}
const float OFFSET_LEVEL = 0.15;
const float SCALE_LEVEL = 4.0;
void main()
{
if(OFFSET_LEVEL < v_texcoord.x && v_texcoord.x < (1.0 - OFFSET_LEVEL)
&& OFFSET_LEVEL < v_texcoord.y && v_texcoord.y < (1.0 - OFFSET_LEVEL))
{
//将原圖下采樣到指定區域中
vec2 newTexCoord = v_texcoord;
newTexCoord -= OFFSET_LEVEL;
newTexCoord = newTexCoord / (1.0 - 2.0 * OFFSET_LEVEL);
gl_FragColor = YuvToRgb(newTexCoord);
}
else
{
//原紋理坐标縮放之後再進行采樣
gl_FragColor = YuvToRgb(scale(v_texcoord, SCALE_LEVEL));
}
}
聯系與交流
技術交流/擷取源碼可以添加我的微信:Byte-Flow
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。
