经过阅读本文,你将取得以下收成:
1.如何写图画几许改换shader
2.如何给视频增加几许改换动画

上篇回忆

上一篇博文一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之改换滤镜(理论根底篇)能够说给咱们上了一节数学课,线性代数和三角函数都复习了不少,不知咱们看得是不是很过瘾(单调)呢?可是改换关于图形烘托来说实在太重要了,不仅是做滤镜的时分需求,以后要做3维烘托将3维物体投影到二维平面的时分更是重中之重,所以这也是我上一篇博文那么啰嗦地解析数学细节的原因。现在理论根底打得差不多了,咱们打开另一扇门,进入实战吧。究竟纸上得来终觉浅,绝知此事要躬行。究竟葵花宝典送到你手,可是你不练还是不会呀。

一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之变换滤镜(实践篇)

温馨提示:假如前面几篇博文都理解清楚了,那么今日的博文关于各位来说应该只是简单动动脑皮子的事情。

在OpenGL中做几许改换

在上一篇博文中一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之改换滤镜(理论根底篇),最终得到2维图画的组合仿射改换矩阵

一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之变换滤镜(实践篇)

[10xt01yt001]\begin{bmatrix}1 & 0 & x_t \\ 0 & 1 & y_t \\ 0 & 0 & 1 \end{bmatrix} [cos⁡−sin⁡0sin⁡cos0001]\begin{bmatrix}\cos\phi & -\sin\phi & 0\\ \sin\phi & cos\phi & 0 \\ 0 & 0 & 1 \end{bmatrix} [a000b0001]\begin{bmatrix}a & 0 & 0\\ 0 & b & 0 \\ 0 & 0 & 1 \end{bmatrix} 三个改换矩阵的相乘成果。这儿x_t、y_t别离为图画某个点在x和y轴方向的平移距离,\phi为图画某个点以原点为中心的逆时针旋转角度,a和b为图画某个点以原点为中心的在x和y轴方向上的缩放系数。

所以假如图画上的任何一个点对应的向量为[xaya1]\begin{bmatrix}x_a \\y_a \\1 \end{bmatrix} ,经过改换后的点为[x′y′1]\begin{bmatrix}x’ \\y’ \\1\end{bmatrix},则它们之间能够经过下面的矩阵乘法式子来表明:

一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之变换滤镜(实践篇)

已然改换是作用于图画上的点的,那么应该怎样作用于图画上的点才干完结对图画的几许改换呢?比方此刻屏幕上的一个三角形的极点是在a,b,c三个点上,如何对它往x,y方向别离平移xtx_tyty_t呢?

如何运用改换矩阵做改换

假如你已经阅读过本专栏之前的文章,想必你已经有答案了,那便是对极点进行移动,所以只需将三角形的极点都别离平移xtx_tyty_t即可。

那么如何改动极点呢,咱们知道极点是由传进去的极点数组确定的,所以首要,咱们能够在客户端程序(不清楚客户端程序的能够看下一看就懂的OpenGL ES教程——再谈OpenGL工作机制)先对极点数组进行处理,核算出经过几许改换的极点数组,然后再传入OpenGL中。

这样做能够是能够,可是弊端也是明显的:

  1. 需求对每个极点进行处理,假如极点数量很多,代码写起来或许很不方便。
  2. 假如是烘托几许改换的视频,相当于每一帧都要去做改换的核算,由于客户端程序是在cpu运转的,核算后的极点数组又要经过总线传输到gpu中的,所以开支会很大。

已然这个计划有硬伤,那么肯定有另一种更好的计划了,那便是在shader里边处理改换核算

这个计划看起来就靠谱了不少:

  1. 一个是极点数组只需传入一次就行了,所以cpu省了很多核算担负,总线开支也省了十分多。
  2. 另一个是改换的所有核算都放在shader即gpu中,核算速度更快。
  3. 还有一个是由于极点上色器本身便是针对每个极点的,所以咱们都不需求针对极点一个个去向理了,处理代码针对一个极点写即可。

那么如何将改换矩阵传到shader里边呢?

将改换矩阵传到shader

假如看过前面的章节,应该知道shader有个表明矩阵的数据类型,名曰mat,在一看就懂的OpenGL ES教程——烘托宫崎骏动漫重拾幼年便是用了mat来处理yuv转rgba的(还没看过这篇文章?还不赶忙去看~),同理,这儿的改换矩阵也是用mat来处理,由于OpenGL是处理3D的,所以坐标系是3维的,加上齐次坐标(不清楚齐次坐标的请看一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之改换滤镜(理论根底篇)中“齐次坐标”章节。),所以改换矩阵需求的是一个4*4的矩阵,即mat4。

那么,之前进行纹路烘托的极点上色器就能够写成如下代码:


        #version 300 es
        //输入的极点坐标,会在程序指定将数据输入到该字段
        //,假如传入的向量是不行4维的,主动将前三个重量设置为0.0,最终一个重量设置为1.0
        layout (location = 0) 
        in vec4 aPosition;
        //输入的纹路坐标,会在程序指定将数据输入到该字段
        layout (location = 1) 
        in vec2 aTextCoord;
        out vec2 vTextCoord;//输出的纹路坐标;
        uniform mat4 uMatrix;//改换矩阵
        void main() {
            //这儿其实是将上下翻转过来(由于安卓图片会主动上下翻转,所以转回来)
            vTextCoord = vec2(aTextCoord.x, 1.0 - aTextCoord.y);
            //几许改换最要害的代码在这儿,用改换矩阵乘上极点坐标。留意,这儿要用左乘
            gl_Position = uMatrix * aPosition;
        };

其实便是比本来多出一个mat4的uMatrix变量作为改换矩阵而已,在最终输出极点坐标给OpenGL的时分,用该矩阵左乘本来传入的极点坐标即可,所以此刻输出的极点坐标即为改换之后的极点坐标

首要关于3维的改换来说,改换矩阵的推导和之前2维是几乎相同的,所以3维的改换矩阵的通用形式如下:

一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之变换滤镜(实践篇)

其间axa_x表明缩放、旋转、错切相关的改换,xtx_tyty_tztz_t表明平移改换。

平移改换

所以假如咱们需求对图片x,y轴别离平移1个单位,则改换矩阵如下:

[1001010100100001]\begin{bmatrix}1 & 0 & 0 & 1 \\ 0 & 1 & 0 & 1 \\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1\end{bmatrix}

那么怎样从客户端程序传到shader中呢?由于改换矩阵用uniform类型界说,所以用uniform的方式传即可,而在OpenGL中,有一个办法叫glUniformMatrix4fv能够传入4*4的矩阵,在客户端程序能够经过数组来传入。则代码能够如下这样写:

//用一个16个元素的数组表明4*4矩阵,留意这儿为列主序
float arr[16] = 
{
 1.0, 0.0, 0.0 ,0.0, //第一列   
 0.0, 1.0, 0.0 ,0.0, //第二列   
 0.0, 0.0, 1.0 ,0.0, //第三列     
 1.0, 1.0, 0.0 ,1.0 //第四列
 };
//获取uMatrix在shader中的方位引用
GLint uScaleMatrixLocation = glGetUniformLocation(program, "uMatrix");
//修正对应的shader变量uMatrix
glUniformMatrix4fv(uScaleMatrixLocation, 1, GL_FALSE, arr);

glUniformMatrix4fv()第一个参数便是熟悉的方位引用参数,第二个参数告诉需求传多少个矩阵,三个参数表明是否转置(OpenGL默许选用列主序,咱们就选用列主序,所以设为GL_FALSE即可),最终一个参数便是传入的数组,这儿OpenGL内部会将其依照矩阵来读取

这儿要留意的便是,glUniformMatrix4fv办法传入的数组是列主序的,即每4个元素为一列。

运转下,看下作用:

能够看到,有点小荷才露尖尖角的味道,视频只露出了本来的左下角刚好四分之一了。留意这儿平移1可不是平移1个像素,由于坐标是归一化的了,所以这儿平移是一个单位:

一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之变换滤镜(实践篇)

所以本来视频的中心点被平移到屏幕右上角方位。

缩放改换

假如要缩放,比方x、y轴方向缩小为0.5倍,则矩阵的数组改为:

float arr[16] =
{
 0.5, 0.0, 0.0 ,0.0, //第一列   
 0.0, 0.5, 0.0 ,0.0, //第二列   
 0.0, 0.0, 1.0 ,0.0, //第三列     
 0.0, 0.0, 0.0 ,1.0 //第四列
 };

运转下,看下作用:

视频公然沿着中心点缩小为0.5倍。

旋转改换

假如要旋转,比方逆时针旋转45度,那么代入之前得到的改换矩阵公式,数组能够写成如下:


float arr[16] = {
static_cast<float>(cos(45 * M_PI / 180))
, static_cast<float>(sin(45 * M_PI / 180))
, 0.0, 0.0, 
-static_cast<float>(sin(45 * M_PI / 180))
, static_cast<float>(cos(45 * M_PI / 180))
, 0.0, 0.0
, 0.0, 0.0, 1.0, 0.0
, 0.0, 0.0,0.0, 1.0};

其间“45 * M_PI / 180”表明对应的弧度,运转作用:

能够看到整个视频被转起来了(让人情不自禁把脖子扭斜,多看看这样的视频也是有利颈椎健康的)

组合改换

假如要组合改换呢,将上面3个数组归纳起来即可,以下是先缩小到0.5倍,然后逆时针旋转45度,最终x,y方向别离平移0.5个单位的改换矩阵对应的组合改换矩阵

[10xt01yt001]\begin{bmatrix}1 & 0 & x_t \\ 0 & 1 & y_t \\ 0 & 0 & 1 \end{bmatrix} [cos⁡−sin⁡0sin⁡cos0001]\begin{bmatrix}\cos\phi & -\sin\phi & 0\\ \sin\phi & cos\phi & 0 \\ 0 & 0 & 1 \end{bmatrix} [a000b0001]\begin{bmatrix}a & 0 & 0\\ 0 & b & 0 \\ 0 & 0 & 1 \end{bmatrix} = [acos⁡−bsin⁡xtasin⁡bcos⁡yt001]\begin{bmatrix}a\cos\phi & -b\sin\phi & x_t \\ a\sin\phi & b\cos\phi & y_t \\ 0 & 0 & 1 \end{bmatrix}

代入xt=0.5,yt=0.5,a=0.5,b=0.5,=45度x_t = 0.5,y_t = 0.5,a=0.5,b = 0.5,\phi=45度

//这儿要先核算出弧度
float theta = 45 * M_PI / 180;
float arr[16] = {
                  0.5f*cos(theta), -0.5f*sin(theta), 0.0, 0.0
                , 0.5f*sin(theta), 0.5f*cos(theta), 0.0, 0.0
                , 0.0, 0.0, 1.0, 0.0
                , 0.5, 0.5,0.0, 1.0 
                };

运转作用:

good,契合预期~

一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之变换滤镜(实践篇)

作用是出来了,可是你肯定会觉得这样手动核算出数组太费事。没关系,已经有人帮咱们做好了,咱们的膂力将得到很好的解放。

glm库

OpenGL没有内建矩阵运算办法,所以一般是运用第三方库glm。OpenGL Mathematics (GLM)是基于OpenGL上色言语(GLSL)标准的图形软件的C++数学库。详细介绍在这儿OpenGL Mathematics (GLM),更详细的官方网址是glm Github。

从Github下载到源码之后,解压之后把整个glm文件夹复制到你的项目所在文件夹下:

一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之变换滤镜(实践篇)

然后再CMakeList文件中增加glm的路径:

一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之变换滤镜(实践篇)

再对应源文件引进glm的头文件和名称空间

一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之变换滤镜(实践篇)

然后就能够在项目中运用glm了,glm的api也是望文生义。

对矩阵的处理操作,比方缩放操作:

        //先创立一个单位矩阵
        mat4 scaleMatrix = glm::mat4(1.0f);
        //缩放系数
        float scale = 0.5;
        //vec3(scale)的3个重量别离乘以scaleMatrix的前三行,第四行齐次坐标不变
        //即3个重量别离是x、y、z的缩放系数
        mat4 resultMatrix = glm::scale(scaleMatrix, vec3(scale));
        //修正shader对应数值。glm::value_ptr(scaleMatrix)获取scaleMatrix的指针
        glUniformMatrix4fv(uScaleMatrixLocation, 1, GL_FALSE, glm::value_ptr(resultMatrix));

运转下看作用:

假如要完结上面说的先缩小到0.5倍,然后逆时针旋转 \phi度,最终x,y方向别离平移0.5个单位,则代码如下:

    //x,y轴方向别离平移0.5
    scaleMatrix = glm::translate(scaleMatrix,vec3(0.5));
    //沿着(0,0,0)点逆时针旋转45度
    scaleMatrix = glm::rotate(scaleMatrix, glm::radians(45.0f),vec3(0.0f, 0.0f, 1.0f));
    //缩小到0.5倍
    scaleMatrix = glm::scale(scaleMatrix,vec3(0.5));
    //glm::value_ptr(scaleMatrix)获取scaleMatrix的指针
    glUniformMatrix4fv(uScaleMatrixLocation, 1, GL_FALSE, glm::value_ptr(scaleMatrix));

这儿需求留意的是,这儿代码是先平移,后旋转,然后缩放,实践履行的改换是反过来的,用数学表达式如下:

[100.5010.5001]\begin{bmatrix}1 & 0 & 0.5 \\ 0 & 1 & 0.5 \\ 0 & 0 & 1 \end{bmatrix} [cos⁡−sin⁡0sin⁡cos0001]\begin{bmatrix}\cos\phi & -\sin\phi & 0\\ \sin\phi & cos\phi & 0 \\ 0 & 0 & 1 \end{bmatrix} [0.50000.50001]\begin{bmatrix}0.5 & 0 & 0\\ 0 & 0.5 & 0 \\ 0 & 0 & 1 \end{bmatrix} [xy1]\begin{bmatrix}x \\y \\1 \end{bmatrix}

能够看出,依照调用顺序是矩阵从左到右相乘摆放的,由于矩阵相乘满意结合律,所以能够看做最右边的矩阵最早一步右乘要做改换的点向量,然后下一个改换矩阵再右乘上一个改换的成果

运转下看作用:

和传入手动创立的数组相同的作用。

滤镜升级打怪

铂金

铂金开始进入新的高潮,由于引进了改换动画形式

缩放动画

是不是有点抖音内味了?

(由于上传文件巨细限制,这儿的gif帧数太低,导致看起来有点鬼畜的感觉,理解万岁。。)

细心一看,主要的动画逻辑是:先以均匀的速度扩大,然后扩大到一定程度后加速度缩小

要害代码在Java_com_example_openglstudydemo_YuvPlayer_loadYuvWithFilterEffect办法中, 在视频每帧的循环中处理:

        ...
        //每一轮缩放周期为总帧数的十分之一
        int scaleDuration = frameCount / 10;
        ...
        //这儿取第i帧对应的缩放系数
        float scale = getTransformMatrix(scaleDuration, i);
        //vec3(scale)的3个重量别离乘以scaleMatrix的前三行,第四行齐次坐标不变
        mat4 resultMatrix = glm::scale(scaleMatrix, vec3(scale));
        glUniformMatrix4fv(uScaleMatrixLocation, 1, GL_FALSE, glm::value_ptr(resultMatrix));
float getTransformMatrix(int scaleDuration, int frame) {
    int remainder = frame % scaleDuration;
    LOGD("ScaleFilter onDraw remainder:%d", remainder);
    float ratio;
    //扩大的时分是线性改换,即扩大系数和时刻成正比。算出每个周期的帧数占一个周期的份额
    if (remainder < scaleDuration / 2) {
        ratio = remainder * 1.0F / scaleDuration;
    } else {
        //缩小速度加速度增快
        ratio = static_cast<float>(pow(remainder * 1.0F / scaleDuration, 2));
    }
    //MAX_DIFF_SCALE是最大缩放倍数,值为1.5F
    float scale = MAX_DIFF_SCALE * ratio;
    //不要缩到比原图小
    if (scale < 1) {
        scale = 1;
    }
    LOGD("scale:%f", scale);
    return scale;
}

这儿做动画肯定要获取一个一个动画周期的时刻以及当时帧在一个动画周期中占的时刻比。由于帧率是固定的,所以这儿详细的时刻比能够经过帧数比来算

详细逻辑便是每一轮缩放周期帧数为总帧数的十分之一,然后依据当时是第几帧,算出其对缩放周期帧数的余数,即算出当时帧在当时缩放周期的时刻占比。

这儿在一个周期内,前一半时刻作为线性的速度扩大,后一半时刻进行加速度缩小。详细看代码注释,相信咱们一定能够理解通透。

改换和其他滤镜的组合

一看就懂的OpenGL ES教程——仿抖音滤镜的奇技淫巧之变换滤镜(实践篇)

这个只需把两者结合起来就行了。

极点上色器:


        #version 300 es
        //输入的极点坐标,会在程序指定将数据输入到该字段
        //,假如传入的向量是不行4维的,主动将前三个重量设置为0.0,最终一个重量设置为1.0
        layout (location = 0) 
        in vec4 aPosition;
        //输入的纹路坐标,会在程序指定将数据输入到该字段
        layout (location = 1) 
        in vec2 aTextCoord;
        out vec2 vTextCoord;//输出的纹路坐标;
        uniform mat4 uMatrix;//改换矩阵
        void main() {
            //这儿其实是将上下翻转过来(由于安卓图片会主动上下翻转,所以转回来)
            vTextCoord = vec2(aTextCoord.x, 1.0 - aTextCoord.y);
            //几许改换最要害的代码在这儿,用改换矩阵乘上极点坐标。留意,这儿要用左乘
            gl_Position = uMatrix * aPosition;
        };

片段上色器:

#version 300 es
precision mediump float;
//纹路坐标
in vec2 vTextCoord;
//输入的yuv三个纹路
uniform sampler2D yTexture;//采样器
uniform sampler2D uTexture;//采样器
uniform sampler2D vTexture;//采样器
out vec4 FragColor;
void main() {
    //采样到的yuv向量数据
    vec3 yuv;
    //yuv转化得到的rgb向量数据
    vec3 rgb;
    vec2 uv = vTextCoord.xy;
    if (uv.x <= 0.5) {
        //当x小于0.5的时分,采样2倍x坐标的纹素色彩
        uv.x = uv.x * 2.0;
    }else{
        //当x大于0.5的时分,采样2倍x坐标减0.5的纹素色彩
        uv.x = (uv.x - 0.5) * 2.0;
    }
     if (uv.y <= 0.5) {
           //当y小于0.5的时分,采样2倍y坐标的纹素色彩  
           uv.y = uv.y * 2.0;
     }else{
           //当y大于0.5的时分,采样2倍y坐标减0.5的纹素色彩
           uv.y = (uv.y - 0.5) * 2.0;
     }
    //别离取yuv各个重量的采样纹路
    yuv.x = texture(yTexture, uv).r;
    yuv.y = texture(uTexture, uv).g - 0.5;
    yuv.z = texture(vTexture, uv).b - 0.5;
    rgb = mat3(
            1.0, 1.0, 1.0,
            0.0, -0.183, 1.816,
            1.540, -0.459, 0.0
    ) * yuv;
    FragColor = vec4(rgb, 1.0);
 };

假如看不懂这段片段上色器代码,能够看下一看就懂的OpenGL ES教程——仿抖音滤镜的各种奇技淫巧之根底滤镜的详细解释。

总结

今日在上一次详细解析几许改换原理的根底上进行了实战,从手动核算出数组进行单一的几许改换到组合改换,再到运用glm库进行改换,再到实现改换动画以及将改换动画和之前的滤镜进行结合,滤镜的国际也变得越来越有趣了,接下来,就将进入更丰富炫酷的滤镜了,让咱们大饱眼福,咱们请拭目而待~

项目代码

opengl-es-study-demo 不断更新中,欢迎各位来star~

参阅:

GAMES101-现代核算机图形学入门-闫令琪

改换

Fundamentals of Computer Graphics, Fourth Edition

原创不易,假如觉得本文对自己有帮助,别忘了顺手点赞和重视,这也是我创造的最大动力~

系列文章目录

体系化学习系列博文,请看音视频系统学习总目录

实践项目: 介绍一个自己刚出炉的安卓音视频播放录制开源项目 欢迎各位来star~

相关专栏:

C/C++根底与进阶之路

音视频理论根底系列专栏

音视频开发实战系列专栏

一看就懂的OpenGL es教程