目录

  • 前言
  • 一、常规操作
  • 二、使用步骤
    • 1.创建SurfaceTexture
    • 2.自定义Renderer
    • 3.坐标系
    • 4.OpenGL和Camera相结合
    • 5.实际运行效果
    • 6.分屏效果
    • 7.项目地址
  • 总结

前言

上面文章介绍了一下OpenGL基本使用,由于接触OpenGL时间不长,理解不够深入,讲得不是很清楚,接下来用这篇文章,通过一个实际的开发例子,重新介绍一下OpenGL。


提示:话不多说,正文来了。

一、常规操作

上面文章已经介绍了OpenGL如何使用,下面直接上代码了。

 glSurfaceView = findViewById(R.id.camera_glsurface_view)glSurfaceView.setEGLContextClientVersion(2)myRender = MyRender()glSurfaceView.setRenderer(myRender)glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY

简单介绍一下上面代码,首先是设置OpenGL版本号,创建了一个自定义Renderer,把Renderer设置给当前GLSurfaceView,最后一行代码强调一下,是指GLSurfaceView刷新方式,一般选择这个模式,就是有变化的主动刷新一下,如果不设置的话,每隔一段时间,自动刷新,挺消耗资源的,一般选择上面这个。

二、使用步骤

1.创建SurfaceTexture

定义一个SurfaceTexture来显示处理后的数据,并实现OnFrameAvailableListener接口回调来通知GlSurfaceview渲染新的帧数据:

 private lateinit var mSurfaceTexture: SurfaceTextureoverride fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {glSurfaceView.requestRender()}

在说创建SurfaceTexture之前,我们先回顾一下,Renderer几个方法作用:

onSurfaceCreated():系统会在创建 GLSurfaceView
时调用一次此方法。使用此方法可执行仅需发生一次的操作,例如设置 OpenGL 环境参数或初始化 OpenGL 图形对象。
onDrawFrame():系统会在每次重新绘制 GLSurfaceView
时调用此方法。请将此方法作为绘制(和重新绘制)图形对象的主要执行点。
onSurfaceChanged():系统会在GLSurfaceView 几何图形发生变化(包括
GLSurfaceView大小发生变化或设备屏幕方向发生变化)时调用此方法。例如,系统会在设备屏幕方向由纵向变为横向时调用此方法。使用此方法可响应GLSurfaceView
容器中的更改。

如上所述,创建方法写在:

 override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)mSurfaceTexture = SurfaceTexture(createOESTextureObject())...

再看createOESTextureObject如何实现的:

fun createOESTextureObject(): Int {val tex: IntArray = IntArray(1)// 生成一个纹理GLES20.glGenBuffers(1, tex, 0)// 将此纹理绑定到外部纹理上GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0])// 设置纹理过滤参数GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST.toFloat());GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR.toFloat());GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE.toFloat());GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE.toFloat());GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);return tex[0]}

主要是生成了一个纹理id,设置了一系列参数,最后返回。

2.自定义Renderer

重头戏,可以说OpenGL最重要的就是Renderer类,而绘制形状最重要的是两个着色器:

  • 顶点着色程序 - 用于渲染形状的顶点的 OpenGL ES 图形代码。
  • 片段着色程序-用于使用颜色或纹理渲染形状面的 OpenGL
    ES 代码。
    这是官方定义,其实简单来说,顶点着色器就是画形状,片段着色器就是上颜色,贴图
    再来张图片,看的更明白一些:


我们的形状都是由一个个三角形绘制而成,最后由片段着色器上色、贴图。
原理介绍完了,咋们看下代码:

 private val mPosCoordinate = floatArrayOf(-1f, -1f,-1f, 1f,1f, -1f,1f, 1f)private val mTexCoordinateBackRight = floatArrayOf(1f, 1f,0f, 1f,1f, 0f,0f, 0f)

上面定义了顶点着色器和片段着色器的坐标,最后将我们定义好的坐标传入我们的着色程序代码里面。前面文章,我们是直接写在String里面,直接引用,实际编写代码是不会这么做的,我们写好的着色程序,是放在asserts文件里面的,然后读取的:

 val vertexSource = AssetsUtils.read(instance, "camera_vertexShader.glsl")val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource!!)val fragmentSource = AssetsUtils.read(instance, "camera_fragmentShader.glsl");val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource!!)
...

再看看我们着色器代码如何写的(建议大家在AS安装一个GLSL Support插件,有惊喜)
顶点着色器:

uniform mat4 textureTransform;
attribute vec2 inputTextureCoordinate;
attribute vec4 position;//NDK坐标点
varying   vec2 textureCoordinate;//纹理坐标点变换后输出void main() {gl_Position = textureTransform * position;textureCoordinate = inputTextureCoordinate;
}

上面代码,简单说下,vec2、vec4分别代表两位float和四位float,其中gl_Position自带变量,textureTransform变换矩阵。
片段着色器:

#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES videoTex;// 图片 采样器
varying vec2 textureCoordinate;void main() {vec4 tc = texture2D(videoTex, textureCoordinate);float color = tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11; // 这里进行的颜色变换处理,传说中的黑白滤镜gl_FragColor = vec4(color,color,color,1.0);
}

gl_FragColor自带变量,上面代码实现了一个黑白滤镜。


3.坐标系

除了上面的着色器代码以外,OpenGL的坐标系尤其重要,因为我们了解它的坐标系,我们才知道形状如画绘制出来的。


我们看上面图片,主要有两种坐标,一种是绘制形状的,一种是贴图的纹理坐标,我们知道我们画图是有一个一个点连接起来的,OpenGL也是一样的,既然涉及到绘图,肯定有绘制顺序的,在OpenGL有专门的方法设置:

   GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mPosCoordinate1.size / 2)

OpenGL通常是GLES20.GL_TRIANGLE_STRIP这种绘制顺序,对应的就是左下角,右下角,左上角,右上角,敲黑板,这个是重点,因为我们的形状就是按这个顺序绘制出来的,你可以试着按逆时针顺序,连起来,你会发现我们画出来的形状不是矩形,会少一块。
同理,我们形状绘制出来了,贴图也要照着这个顺序绘出来。


4.OpenGL和Camera相结合

前面说了很多OpenGL相关的东西,最后我们的效果要结合Camera预览显示的。
我们知道Android相机目前有3种API,Camera1、Camera2、CameraX,这里图方便,就使用Camera1。

try {camera = Camera.open(0)camera.setPreviewTexture(mSurfaceTexture)camera.startPreview()camera.autoFocus(object : Camera.AutoFocusCallback {override fun onAutoFocus(success: Boolean, camera: Camera?) {if (success) {//                            camera?.cancelAutoFocus();}}})} catch (e: Exception) {e.printStackTrace()}

这里我们将OpenGL处理过的mSurfaceTexture,直接设置给camera显示。
最后我们再看下OpenGL处理的代码:

uPosHandle = GLES20.glGetAttribLocation(mProgram, "position");
aTexHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "textureTransform");mPosBuffer = convertToFloatBuffer(mPosCoordinate1)
mTexBuffer = convertToFloatBuffer(mTexCoordinateBackRight1);GLES20.glVertexAttribPointer(uPosHandle, 2, GLES20.GL_FLOAT, false, 0, mPosBuffer)
GLES20.glVertexAttribPointer(aTexHandle, 2, GLES20.GL_FLOAT, false, 0, mTexBuffer)
// 启用顶点位置的句柄
GLES20.glEnableVertexAttribArray(uPosHandle)
GLES20.glEnableVertexAttribArray(aTexHandle)

主要是把之前传入的参数,配置OpenGL ES环境中。
最后在onDrawFrame更新画面:

   GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)mSurfaceTexture.updateTexImage()GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0)GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mPosCoordinate1.size / 2)

5.实际运行效果

看下代码运行的实际效果:

可以看到画面是颠倒的,前面我们说过OpenGL坐标系,我们试着将纹理坐标旋转90度试下,对应的坐标修改如下:

 private val mTexCoordinateBackRight1 = floatArrayOf(// 旋转90度1f,0f,1f,1f,0f,0f,0f,1f
)

这个坐标怎么来的呢,可以试着将纹理坐标图片旋转90度,然后按照之前的顺序,重新填入就可以了。
然后我们再看下效果:

我们可以看到字是对称的,根据前面的经验,我们把旋转系90度后图片,两边坐标换下不就可以了:

 private val mTexCoordinateBackRight1 = floatArrayOf(// 对称翻转1f,1f,1f,0f,0f,1f,0f,0f
)

修改后看下效果:

现在看是不是正常了,其实还有一种方法,不修改坐标,直接使用矩阵的方式(顶点着色器代码里面修改),大家可以试下。

6.分屏效果

现在画面正常了,还差最后一步分屏,分屏的效果其实很简单,我们知道分屏其实就是显示两个一模一样的画面,其实主要就是显示中间那部分画面,不属于中间的那部分,我们用中间的部分重复就行。
说的可能有点绕,看代码你们就明白了,由于显示画面,我们直接修改片段着色器代码:

    vec4 tc = texture2D(videoTex, textureCoordinate);float color = tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11; // 这里进行的颜色变换处理,传说中的黑白滤镜float x = textureCoordinate.x;if(x < 0.5) {x+=0.25;}else {x-=0.25;}gl_FragColor = texture2D(videoTex, vec2(x, textureCoordinate.y));
}

代码很简单啊,就是把x<0.5和x>0.5坐标进行变换显示中间的画面,看下效果:

同理,三分屏呢:

   if (x < 1.0/3.0) {x+=1.0/3.0;} else if (x > 2.0/3.0){x-=1.0/3.0;}

替换之前x坐标代码就可以了,看下效果:

我们上面实现的是横屏的分屏啊,竖屏的分屏,照葫芦画瓢就行,这里我就不写了。


7.项目地址

参考例子地址:GitHub地址,觉得不错的给个

Android OpenGL开发学习(二)手把手教你实现抖音分屏相机相关推荐

  1. Android OpenGL ES 学习(二) -- 图形渲染管线和GLSL

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  2. 手把手教你实现抖音里面的漫画脸

    手把手教你实现抖音里面的漫画脸 抖音里面有一个漫画脸特效,上传一个自拍的头像,会自动给你生成一个漫画脸.市面上有些拍照软件提供了漫画脸的功能,不过需要付费.相信很多人都想拥有一个属于自己的漫画头像.本 ...

  3. 手把手教你提升抖音直播间人气、流量的6个技巧

    "抖音直播带货"绝对是近两年最热的"风口",几乎全民直播带货,在这样一个繁荣景象下,大家都想迎上这个风口,无数明星.名人纷纷加入"混战". ...

  4. 手把手教你,抖音去水印-有手就能学会

    前言 相信你在百度搜索可能搜到在线去水印的在线网站吧? 或者使用APP去水印,微信小程序去水印? 是的,这些的确是去水印最快的方法,不过在使用多次以后,就不能再次使用了 有些需要你花钱购买解析次数(下 ...

  5. OpenGL ES之GLSL实现仿抖音“分屏滤镜”效果

    无分屏滤镜 一.GLSL自定义着色器 Normal.vsh:顶点着色器 attribute vec4 Position; attribute vec2 TextureCoords; varying v ...

  6. Android OpenGL ES 学习(十二) - MediaCodec + OpenGL 解析H264视频+滤镜

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  7. Android OpenGL ES 学习(十) – GLSurfaceView 源码解析GL线程以及自定义 EGL

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  8. Android OpenGL ES 学习(十一) –渲染YUV视频以及视频抖音特效

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  9. Android OpenGL ES 学习(六) – 使用 VBO、VAO 和 EBO/IBO 优化程序

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

最新文章

  1. Win7/Win2008下IIS配置Asp站点启用父路径的设置方法
  2. 区跨链应用 | 区块链创业者不要再骗自己了
  3. oracle中更改列明和更改显示列长度
  4. TextView的跑马灯效果实现
  5. 十二星座用JAVA怎么,十二星座的“程序员”,都是怎么写代码的?
  6. RedHat5.2下Linux Oracle 10g ASM 安装详细实录-第二篇-ASM安装
  7. 计算机专业需要汇编语言,重点大学计算机专业系列教材·汇编语言程序设计
  8. easy bootstrap模板
  9. CSS 元素的display属性
  10. kafka跨集群同步方案
  11. 互联网产品哪个不火哪个就在红利期
  12. 了解 Promise.any() 用法
  13. PYTHON之路(九)
  14. 深入.NET 青鸟影院系统
  15. c语言 万年历实验流程图,万年历算法(万年历算法流程图)
  16. RhinoMarine v4.0.3 plugin for Rhinoceros 船艇设计分析
  17. ES Transport Client学习
  18. 台湾大学林轩田机器学习基石课程学习笔记4 -- Feasibility of Learning
  19. 开源流媒体服务器SRS环境搭建
  20. Python绘制RTKLIB的POS文件中的XYZ误差曲线及计算RMSE

热门文章

  1. linux密钥恢复密码,忘记BitLocker密码怎么办?使用恢复密钥解锁磁盘驱动器
  2. Kali Linux Vm下载与安装
  3. 松下军用计算机,松下军用笔记本电脑
  4. 物流行业分析数据集分享
  5. Python项目实战之词云图制作
  6. SQL判断两个时间段是否存在交集
  7. 线矢量及螺旋的代数计算(附python作图程序)
  8. CentOS7 安装mysql(YUM源方式)
  9. 爬虫快速入门案例———豆瓣电影Top250
  10. Ubuntu添加或删除MySQL用户与权限、远程访问