当前位置: 首页 > news >正文

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

OpenGL ES 相机基础滤镜

上文中我们通过 ImageReader 获取到 Camera2 预览的 YUV 数据,然后利用 OpenGLES 渲染实现相机预览,这一节将利用 GLSL (OpenGL 着色器语言)基于不同的着色器实现多种基础滤镜。

内建函数函数说明
float distance (genType p0, genType p1)计算向量 p0 ,p1 之间的距离
float length (genType x)返回向量 x 的长度
genType floor (genType x)返回小于等于 x 的最大整数值
genType ceil (genType x)返回大于等于 x 的最小整数值
genType mod (genType x, float y)返回 x – y * floor (x / y) ,即求模计算 %
float dot (genType x, genType y)向量 x ,y 之间的点积
vec3 cross (vec3 x, vec3 y)向量 x ,y 之间的叉积
genType normalize (genType x)标准化向量,返回一个方向和 x 相同但长度为 1 的向量

GLSL 一些使用频率比较高的内建函数

动态网格

动态网格

动态网格滤镜主要是将纹理划分为多个网格,然后根据一个偏移量动态改变网格线的宽度。mod 和 floor 为 GLSL 的内建函数,分别表示取模和取整。需要注意的是,计算之前需要将纹理坐标系转换为图片坐标系,保证网格没有被拉伸。

//dynimic mesh 动态网格着色器
#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);
}
void main()
{
    vec2 imgTexCoord = v_texcoord * texSize;//将纹理坐标系转换为图片坐标系
    float sideLength = texSize.y / 6.0;//网格的边长
    float maxOffset = 0.15 * sideLength;//设置网格线宽度的最大值
    float x = mod(imgTexCoord.x, floor(sideLength));
    float y = mod(imgTexCoord.y, floor(sideLength));

    float offset = u_offset * maxOffset;

    if(offset <= x
    && x <= sideLength - offset
    && offset <= y
    && y <= sideLength - offset)
    {
        gl_FragColor = YuvToRgb(v_texcoord);
    }
    else
    {
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}

分屏

四分屏

分屏滤镜的原理是在多个指定区域内对整个纹理进行下采样(缩小),从而实现整个图像在多个区域内多次显示。

//分屏(四分屏)
#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);
}
void main()
{
    vec2 newTexCoord = v_texcoord;
    if(newTexCoord.x < 0.5)
    {
        newTexCoord.x = newTexCoord.x * 2.0;
    }
    else
    {
        newTexCoord.x = (newTexCoord.x - 0.5) * 2.0;
    }

    if(newTexCoord.y < 0.5)
    {
        newTexCoord.y = newTexCoord.y * 2.0;
    }
    else
    {
        newTexCoord.y = (newTexCoord.y - 0.5) * 2.0;
    }

    gl_FragColor = YuvToRgb(newTexCoord);
}

缩放的圆

缩放的圆

缩放的圆效果实现主要依赖偏移量来动态改变圆半径的大小,在半径区域内对纹理采样显示图像,在半径区域外返回一个固定颜色(如白色)。distance 也是 GLSL 的内建函数,用于计算两点之间的距离。另外需要注意是,在计算之前首先要将纹理坐标系转换为图片坐标系,否则绘制的将会是一个椭圆形图像(图像宽高不同的情况下),想一想为什么会这样?

//scale circle 缩放的圆
#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);
}
void main()
{
    vec2 imgTex = v_texcoord * texSize;//将纹理坐标系转换为图片坐标系
    float r = (u_offset + 0.208 ) * texSize.x;
    if(distance(imgTex, vec2(texSize.x / 2.0, texSize.y / 2.0)) < r)
    {
        gl_FragColor = YuvToRgb(v_texcoord);
    }
    else
    {
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}

在计算之前首先要将纹理坐标系转换为图片坐标系,其原因在于纹理纵横坐标的取值范围均为 [0, 1] ,从数值上看纹理的纵横方向长度相同,但是在 OpenGL 采样时,图像的宽高比往往不是 1 ,这就导致了数值相同的纵横坐标,对应不同的采样权重,出现了预期绘制圆形而实际上却绘制出椭圆的情况。

实现代码路径见阅读原文。


-- END --


技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

推荐阅读:

音视频面试基础题

OpenGL ES 学习资源分享

一文读懂 YUV 的采样与格式

OpenGL 之 GPUImage 源码分析

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

相关文章:

  • 有必要去研究Handler和Binder么?
  • 音视频交流群又来啦~~~
  • Android 手机如何拍摄RAW图
  • 华为手机刷微博体验更好?技术角度的分析和思考
  • 播放视频时如何调整音频的音量
  • 视频码控:CBR、VBR和ABR
  • OpenGL ES 相机 LUT 滤镜
  • Android 11 正式发布 | 开发者们的舞台已就绪
  • 刚刚,鸿蒙 OS 2.0 发布!HarmonyOS 将正式开源!
  • 如何给 FFmpeg 添加自定义 Codec 编码器
  • FFmpeg从入门到精通——进阶篇,SEI那些事儿
  • iOS音频采集技术解读:如何实现男女变声的音效?
  • MediaCodec 、x264、faac 实现音视频编码并通过 rtmp 协议实现推流
  • 从《黑神话:悟空》的爆火,浅谈当前游戏从业者面临的机遇与挑战
  • 面试官: 说一下你做过哪些性能优化?
  • AWS实战 - 利用IAM对S3做访问控制
  • bearychat的java client
  • canvas 高仿 Apple Watch 表盘
  • CSS相对定位
  • JavaScript 一些 DOM 的知识点
  • Median of Two Sorted Arrays
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 代理模式
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 前言-如何学习区块链
  • 入手阿里云新服务器的部署NODE
  • UI设计初学者应该如何入门?
  • ​secrets --- 生成管理密码的安全随机数​
  • # Swust 12th acm 邀请赛# [ E ] 01 String [题解]
  • #图像处理
  • #在线报价接单​再坚持一下 明天是真的周六.出现货 实单来谈
  • $$$$GB2312-80区位编码表$$$$
  • $.type 怎么精确判断对象类型的 --(源码学习2)
  • (4)Elastix图像配准:3D图像
  • (MATLAB)第五章-矩阵运算
  • (Oracle)SQL优化技巧(一):分页查询
  • (二)linux使用docker容器运行mysql
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (附源码)springboot学生选课系统 毕业设计 612555
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (十八)用JAVA编写MP3解码器——迷你播放器
  • (十三)Maven插件解析运行机制
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (译)2019年前端性能优化清单 — 下篇
  • (转)Sublime Text3配置Lua运行环境
  • .net framework 4.0中如何 输出 form 的name属性。
  • .Net FrameWork总结
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • .NET/C# 使用反射注册事件
  • .NET开源快速、强大、免费的电子表格组件
  • @取消转义
  • [20150707]外部表与rowid.txt
  • [Android Pro] android 混淆文件project.properties和proguard-project.txt