专栏首页字节流动NDK OpenGL ES 3.0 开发(十九):相机抖音滤镜

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));
    }
}

本文分享自微信公众号 - 字节流动(google_developer),作者:字节流动

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-12-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 我用 OpenGL ES 给小姐姐做了几个抖音滤镜

    最近几篇文章主要是利用 OpenGL 实现相机预览的一些常见的滤镜,上一篇主要介绍了 LUT 滤镜的原理及简单实现方法 。

    字节流动
  • 我用 OpenGL 实现了那些年流行的相机滤镜

    上文中我们通过 ImageReader 获取到 Camera2 预览的 YUV 数据,然后利用 OpenGLES 渲染实现相机预览,这一节将利用 GLSL (O...

    字节流动
  • 小姐姐,这是你要的瘦脸大眼效果吗?

    旧文中我们用 OpenGL 给小姐姐实现了瘦身和大长腿效果,结果小姐姐眯着眼睛、嘟着嘴说,我需要瘦身和大长腿效果吗?

    字节流动
  • 我用 OpenGL ES 给小姐姐做了几个抖音滤镜

    最近几篇文章主要是利用 OpenGL 实现相机预览的一些常见的滤镜,上一篇主要介绍了 LUT 滤镜的原理及简单实现方法 。

    字节流动
  • Shader基础技巧整理

    自从接触了shader之后我便深深得爱上了它,因为它独特的编程思考方式冲击着我这十几年的惯性认知。 在向各位大佬学习的过程中,每学到一个新的技巧,我都不禁感叹:...

    白玉无冰
  • 快速学习-Zookeeper写数据流程

    cwl_java
  • 让机器搞懂100万种隐含语义,腾讯Peacock大规模主题模型首次全揭秘

    用户1737318
  • 腾讯云开发师资培训通知 | 云开发人才培养

    为积极响应国家及教育部的政策方针,以加强新工科专业建设及师资培养,促进新时代云计算领域的人才培养,联盟联合腾讯公司于2019年10月26日在北京召开“云开发师...

    腾讯高校合作
  • Enterprise Library深入解析与灵活应用(3):倘若将Unity、PIAB、Exception Handling引入MVP模式.. .. ..

    最近在做一个Smart Client Software Factory的项目。熟悉SCSF或者CAB的都应该很清楚MVP这种设计模式。MVP是MVC的一种变体,...

    蒋金楠
  • 企业安全建设之路:端口扫描(下)

    0x00、前言 在企业安全建设过程当中,我们也不断在思考,做一个什么样的端口扫描才能企业业务需求。同时,伴随着企业私有云、混合云以及公有云业务部署环境的不断变...

    FB客服

扫码关注云+社区

领取腾讯云代金券