NDK OpenGL ES 3.0 開發(十九):相機抖音濾鏡

該原創文章首發於微信公衆號:字節流動

OpenGLES 相機抖音濾鏡

最近幾篇文章主要是利用 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));
    }
}

實現代碼路徑:
OpenGLCamera2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章