视频录制——2.加上实时美颜

要加上实时美颜,就要编写texture -> 帧缓冲区的过程。

texture -> 帧缓冲区的过程是用OpenGL绘图。
OpenGL是一台管理渲染管线的状态机。
渲染管线有一个出口,一个入口。
入口可以指向一个texture,出口可以指向帧缓冲区或一个FBO。
帧缓冲区是显存中特殊的一块,储存经渲染管线加工后得到的二维像素数据,它对应的FBO索引为0。

几个步骤:

  1. 输出 -> cameraFrameBuffer,覆盖为(0,0,0,0)
    输入 -> 摄像头纹理
    用cameraProgram绘制 -> 顶点经过Matrix调整,cameraTexture为摄像头纹理
  2. 输出 -> gaussFrameBuffer,覆盖为(0,0,0,0)
    输入 -> cameraTexture
    用gaussProgram绘制 -> 顶点位置不变、gaussTexture为磨皮后的摄像头纹理
  3. 输出 -> 帧缓冲区 ,覆盖为(0,0,0,0)
    输入 -> cameraTexture、gaussTexture、smoothTexture
    用smoothProgram绘制 -> 帧缓冲区为美白后的二维像素数据

*每次glDrawArrays前,都要先把输出的FBO覆盖为(0,0,0,0)。
*如果一个texture绑定到一个FBO,那么对FBO的写入操作会影响texture。这被用来进行render to texture。

常用的GL接口:

// 创建n个纹理,索引用textures装载返回
public static native void glGenTextures(int n, java.nio.IntBuffer textures);

// 创建n个FBO,索引用framebuffers装载返回
public static native void glGenFramebuffers(int n, java.nio.IntBuffer framebuffers);

// 把入口指向索引为texture的texture,texture的类型为target;特别地,当texture= 0时不使用纹理
public static native void glBindTexture(int target, int texture);

// 把出口指向索引为framebuffer的FBO,FBO的类型为target;特别地,当framebuffer = 0时指向帧缓冲区
public static native void glBindFramebuffer(int target, int framebuffer);

// 用bitmap填充texture
// target:texture的类型
// level:质量等级
// internalformat:图像格式
// type:数据格式
// border:边框宽度
public static void texImage2D(int target, int level, int internalformat, Bitmap bitmap, int type, int border)

// 把texture绑定到FBO
// target:texture的类型
// attachment:FBO的接入点
// textarget:texture的索引
// level:质量等级
public static native void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level);

// 设置用来覆盖出口指向的FBO或帧缓冲区的RGBA值(还没有覆盖)
public static native void glClearColor(float red, float green, float blue, float alpha);

// 用上一个接口设置的RGBA值来覆盖出口指向的FBO或帧缓冲区(覆盖了);
// mask指明覆盖哪一层,如GLES20.GL_COLOR_BUFFER_BIT覆盖颜色层
public static native void glClear(int mask);

// 设置视口位置及尺寸
public static native void glViewport(int x, int y, int width, int height);

// 开始绘制
// mode:以怎样的行为绘制
// first:起始处偏移量
// count:顶点数量
public static native void glDrawArrays(int mode, int first, int count);

*注意:所有“创建”的行为只是在OpenGL的命名空间中创建了一个名字(索引),只有在绑定后才真正为texture或FBO分配空间

关于GLSL

磨皮和美白的算法要用GLSL写Shader程序,记录一下常用的GLSL和载入方法

// attribute修饰符表示由APP传来的顶点数据,只用于vertex
attribute vec2 position;
attribute vec2 inputTextureCoordinate;

// uniform修饰符表示由APP传来的常量,可用于vertex和fragment
uniform float texelWidthOffset;
uniform float texelHeightOffset;

// varying修饰符表示由vertex初始化,供fragment使用的值
varying vec2 blurCoordinates[3];

void main() {
    // gl_Position是内建变量,表示顶点的输出位置
    gl_Position = vec4(position.xy,0,1);
    vec2 singleStepOffset = vec2(texelWidthOffset, texelHeightOffset);
    blurCoordinates[0] = inputTextureCoordinate.xy;
    blurCoordinates[1] = inputTextureCoordinate.xy + singleStepOffset * optimizedGaussianOffsets[0];
    blurCoordinates[2] = inputTextureCoordinate.xy - singleStepOffset * optimizedGaussianOffsets[1];
}
String s = (上面那一段代码);
// 创建一个Shader,返回它的索引
int shader = GLES20.glCreateShader(GL_VERTEX_SHADER);
// 把源码绑定到这个Shader
GLES20.glShaderSource(shader, s);
// 编译源码
GLES20.glCompileShader(shader);
// 查看是否编译成功
// compiled装载是否成功,GL_TRUE或GL_FALSE
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
// 如果编译失败,删除Shader
if(compiled[0] == GL_FALSE) {
    GLES20.glDeleteShader(shader);
    shader = 0;
}elase {  // 如果编译成功
    // 创建一个Program
    int program= GLES20.glCreateProgram();
    // 把shader加入Program
    GLES20.glAttachShader(program, shader);
    // 释放shader资源
    GLES20.glDeleteShader(vertexShader );
    // 链接程序
    GLES20.glLinkProgram(program);
    // 获取program的链接情况
    GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
    // 若链接失败则删除program
    if (linkStatus[0] == GL_FALSE) {
        GLES20.glDeleteProgram(program);
        program = 0;
    } else {    // 如果链接成功
        // 使用program
        GLES20.glUseProgram(program);
        // 获取变量名的id,location是shader程序中声明为attribute的变量名
        int id = GLES20.glGetAttribLocation(program, location);
        // 允许shader从VBO读取这个变量的值
        GLES20.glEnableVertexAttribArray(id);
        // 设置索引为0的VBO为传输媒介,即APP向这个VBO写入数据,shader从这个VBO读取数据
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
        // 设置向VBO写入的数据,注意只有在调用glDrawArrays时才真正写入
        // size:变量的属性的数量,比如vec2类型的变量num = 2,vec3类型的变量num = 3
        // type:变量的属性的类型
        // normalized:如果写入的不是type类型的数据,是否要归一化
        // stride:每个数据的间隔
        // arrayBuffer:写入的数据
        GLES20.glVertexAttribPointer(id, size, type, normalized, stride, arrayBuffer);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容