OpenGL ES - 渲染摄像头

OpenGL : Open Graphics Library

OpenGL ES : 针对嵌入式系统设计的OpenGL的子集。

在Android中使用OpenGL ES 2.0需要在AndroidManifest.xml添加:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

GLSurfaceView :

android.opengl提供的渲染界面,是SurfaceView的子类,封装了一些使用OpenGL ES需要的配置。
管理Surface和EGL。
(EGL是OpenGL和设备屏幕之间做桥接的类)
允许自定义渲染器Renderer.
让渲染器在独立线程里运作,和UI主线程分离。
支持按需渲染和连续渲染。

OpenGL绘制流程

由三角形组成各种图形。


OpenGL绘制流程

OpenGL坐标系

OpenGL坐标系

坐标映射

坐标映射

Shader着色器

着色器是运行在GPU上的小程序。
顶点着色器(vertex shader)
如何处理顶点、法线等数据的小程序
片元着色器(fragment shader)
如何处理光、阴影、遮挡、环境等对物体表面的影响。

OpenGL函数命名格式

函数库:gl
根命令:Color
参数个数:3
参数类型:f
glColor3f(...)

Open GL语言 Open GL Shading Language GLSL

数据类型

float / vec2 / vec4 / sampler2D

修饰符

  • attribute : 属性变量,只能用于顶点着色器中。一般用该变量表示一些顶点数据,如:顶点坐标、纹理坐标、颜色等。
  • uniforms : 一致变量,在着色器执行期间值不变的。与const常量不同的是,该值在编译时是不确定的,是运行时由着色器外部初始化的。
  • varying : 易变变量,从顶点着色器传递给片元着色器的数据变量。

内建函数

texture2D(采样器,坐标) : 采样指定位置的纹理

内建变量

gl_Position vec4类型,表示顶点着色器中顶点位置
gl_FragColor vec4类型,表示片元着色器中的颜色

精度控制

precision lowp 低精度
precision mediump 中精度
precision highp 高精度

顶点着色器 Vertex Shader

//把顶点坐标给这个变量,确定要画画的形状
attribute vec4 vPosition;

//接收纹理坐标,接收采样器采样图片的坐标
attribute vec4 vCoord;

//变换矩阵,需要将原本的vCoord(01,11,00,10)与矩阵相乘,才能得到surfacetexture的正确采样坐标
uniform mat4 vMatrix;

//传递给片元着色器,像素点
varying vec2 aCoord;

void main() {
    //内置变量gl_position
    //把顶点数据赋值给这个变量,opengl就知道要画什么形状了
    gl_Position = vPosition;

    //和设备相关
    aCoord = vCoord.xy;
}

片元着色器

//预览相机的着色器,顶点着色器不变,需要修改片元着色器
//不再用sampler2D采样,需要用samplerExternalOES纹理采样器
//需要在头部增加使用扩展纹理的声明#extension GL_OES_EGL_image_external : require
#extension GL_OES_EGL_image_external : require

//设置float采用的数据精度
precision mediump float;

//采样点坐标
varying vec2 aCoord;

//采样器
uniform samplerExternalOES vTexture;


void main() {
    //gl_FragColor 变量接收像素值
    //texture2D() : 采样器,采集aCoord的像素
    gl_FragColor = texture2D(vTexture,aCoord);
}

OpenGL实例:渲染Camera数据

项目代码放在GitHub上,地址为:
https://github.com/Hujunjob/AdvanceAndroid/tree/master/opengldemo

1、自定义GLSurfaceView
用于设置EGL版本号、设置自定义的渲染器、设置渲染模式

class MyGLSurface(context: Context?, attrs: AttributeSet? = null) : GLSurfaceView(context, attrs) {
    init {
        //1、设置EGL版本
        setEGLContextClientVersion(2)

        //2、设置渲染器
        setRenderer(MyGLRenderer(this))

        //3、设置渲染模式
        renderMode = RENDERMODE_WHEN_DIRTY
    }
}

2、自定义渲染器

/**
 * 核心类,自定义渲染器
 */
class MyGLRenderer(var myGLSurface: MyGLSurface) : GLSurfaceView.Renderer,
    SurfaceTexture.OnFrameAvailableListener {
    private lateinit var mTextureIds: IntArray
    private lateinit var screenFilter: ScreenFilter
    var cameraEngine: ICameraEngine? = null
    var mSurfaceTexture:SurfaceTexture? = null

    var mtx = FloatArray(16)

    override fun onDrawFrame(gl: GL10?) {
        //在这里画画
        //1、清空画布,设置画布颜色,这里设置为红色
        GLES20.glClearColor(255f,0f,0f,0f)
        //设置清理模式
        //GL_COLOR_BUFFER_BIT 颜色缓存区
        //GL_DEPTH_BUFFER_BIT 深度缓冲区
        //GL_STENCIL_BUFFER_BIT 模型缓冲区
        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT)

        //2、输出摄像头数据
        //更新纹理
        mSurfaceTexture?.updateTexImage()
        //获取旋转矩阵
        mSurfaceTexture?.getTransformMatrix(mtx)

        screenFilter.onDrawFrame(mTextureIds[0],mtx)

    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        cameraEngine?.openCamera(true)

        //设置gl窗口
//        GLES20.glViewport(0,0,width,height)

        screenFilter.onReady(width,height)
    }

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        cameraEngine =
            CameraFactory.createCameraEngine(CameraFactory.CameraType.CAMERA1, myGLSurface.context)

        //准备画布
        mTextureIds = IntArray(1)
        //通过OpenGL创建纹理id
        GLES20.glGenTextures(mTextureIds.size,mTextureIds,0)
        //通过纹理id创建画布
        mSurfaceTexture = SurfaceTexture(mTextureIds[0])
        mSurfaceTexture?.setOnFrameAvailableListener(this)
        cameraEngine?.addSurfaceView(mSurfaceTexture!!)

        screenFilter = ScreenFilter(myGLSurface.context)


    }

    //画布有可用数据时回调
    override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {
        //请求渲染
        myGLSurface.requestRender()
    }

}

3、绘制类

class ScreenFilter(var mContext: Context) {

    private var mVertexBuffer: FloatBuffer
    private var mTextureBuffer: FloatBuffer
    private var vTexture: Int
    private var vMatrix: Int
    private var vCoord: Int
    private var vPosition: Int
    private var mWidth = 0
    private var mHeight = 0
    var mProgramId = 0

    fun onReady(width: Int, height: Int) {
        mWidth = width
        mHeight = height
    }

    fun onDrawFrame(textureId: Int, mtx: FloatArray) {
        //1、设置视窗的宽高
        GLES20.glViewport(0,0,mWidth,mHeight)

        //2、使用着色器程序
        GLES20.glUseProgram(mProgramId)

        //3、渲染传值
        // 先传递顶点
        mVertexBuffer.clear()

        // C function void glVertexAttribPointer ( GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLint offset )
//        fun glVertexAttribPointer(
//            indx: Int,
//            size: Int,
//            type: Int,
//            normalized: Boolean,
//            stride: Int,
//            ptr: Buffer?
//        ): Unit
        //参数分别为:顶点坐标的索引,每个值的长度,值类型,是否归一化,步进(每次取完size后跳过多少个值取下一次值),数据
        GLES20.glVertexAttribPointer(vPosition,2,GLES20.GL_FLOAT,false,0,mVertexBuffer)

        //传递值后需要激活
        GLES20.glEnableVertexAttribArray(vPosition)


        //传递纹理
        mTextureBuffer.clear()
        GLES20.glVertexAttribPointer(vCoord,2,GLES20.GL_FLOAT,false,0,mTextureBuffer)
        GLES20.glEnableVertexAttribArray(vCoord)

        //4、变换矩阵
        GLES20.glUniformMatrix4fv(vMatrix,1,false,mtx,0)


        //片元着色器
        //首先激活图层
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
        //绑定纹理
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,textureId)
        //然后再传递片元着色器参数
        GLES20.glUniform1f(vTexture,0f)

        //通知OpenGL绘制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4)
    }


    init {
        //顶点着色器
        val vertext = TextResourceReader.read(mContext, R.raw.camera_vertex)
        //片元着色器
        val fragment = TextResourceReader.read(mContext, R.raw.camera_fragment)

        //一、配置顶点着色器
        //1、创建着色器
        val vShaderId = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER)

        //2、绑定代码到着色器上面,把着色器里的代码加载的着色器里
        GLES20.glShaderSource(vShaderId, vertext)

        //3、编译着色器代码
        GLES20.glCompileShader(vShaderId)

        var status = IntArray(1)
        //4、主动获取编译是否成功状态
        GLES20.glGetShaderiv(vShaderId, GLES20.GL_COMPILE_STATUS, status, 0)

        if (status[0] != GLES20.GL_TRUE) {
            throw IllegalStateException("配置顶点着色器失败")
        }


        //一、配置片元着色器
        //1、创建着色器
        val fShaderId = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER)

        //2、绑定代码到着色器上面,把着色器里的代码加载的着色器里
        GLES20.glShaderSource(fShaderId, fragment)

        //3、编译着色器代码
        GLES20.glCompileShader(fShaderId)

        //4、主动获取编译是否成功状态
        GLES20.glGetShaderiv(fShaderId, GLES20.GL_COMPILE_STATUS, status, 0)

        if (status[0] != GLES20.GL_TRUE) {
            throw IllegalStateException("配置片元着色器失败")
        }


        //三、着色器程序
        //1. 创建新的OpenGL程序
        mProgramId = GLES20.glCreateProgram()

        //2. 将着色器附加到程序,两个着色器都附加
        GLES20.glAttachShader(mProgramId, vShaderId)
        GLES20.glAttachShader(mProgramId, fShaderId)

        //3. 链接程序
        GLES20.glLinkProgram(mProgramId)

        //4. 获取链接状态
        GLES20.glGetShaderiv(fShaderId, GLES20.GL_LINK_STATUS, status, 0)
        if (status[0] != GLES20.GL_TRUE) {
            throw IllegalStateException("链接片元着色器失败")
        }
        GLES20.glGetShaderiv(vShaderId, GLES20.GL_LINK_STATUS, status, 0)
        if (status[0] != GLES20.GL_TRUE) {
            throw IllegalStateException("链接顶点着色器失败")
        }

        //四、释放、删除着色器
        //链接完成后,着色器都放到了OpenGL程序Program里,则着色器可以删除了
        GLES20.glDeleteShader(vShaderId)
        GLES20.glDeleteShader(fShaderId)


        //五、获取着色器程序变量的索引,通过索引来赋值
        //1. 顶点各个变量的索引
        vPosition = GLES20.glGetAttribLocation(mProgramId, "vPosition")
        vCoord = GLES20.glGetAttribLocation(mProgramId, "vCoord")
        vMatrix = GLES20.glGetUniformLocation(mProgramId, "vMatrix")

        //2、片元各个变量的索引
        vTexture = GLES20.glGetUniformLocation(mProgramId, "vTexture")

        //六、赋值
        //1、顶点坐标赋值
        mVertexBuffer = ByteBuffer
            .allocateDirect(4 * 4 * 2)     // 屏幕一共4个点,每个点有x/y两个坐标,坐标是Float类型,4个字节,则为4*4*2
            .order(ByteOrder.nativeOrder())     //设置使用硬件本地字节序列,保证数据排序
            .asFloatBuffer()                    //创建FloatBuffer
        mVertexBuffer.clear()

        //采用OpenGL的坐标系
        //屏幕边缘4个点
        //不能采用顺时针或逆时针依次获取4个点,因为这样无法无法组成闭合的矩形
        // 例如顺时针四个点分别为 a b c d,则输入进去的点为a b d c
        val v = floatArrayOf(
            -1f, 1f,
            -1f, -1f,
            1f, 1f,
            1f, -1f
        )
        mVertexBuffer.put(v)


        //2、纹理坐标赋值
        mTextureBuffer = ByteBuffer
            .allocateDirect(4 * 4 * 2)     // 屏幕一共4个点,每个点有x/y两个坐标,坐标是Float类型,4个字节,则为4*4*2
            .order(ByteOrder.nativeOrder())     //设置使用硬件本地字节序列,保证数据排序
            .asFloatBuffer()                    //创建FloatBuffer
        mTextureBuffer.clear()

        //纹理的坐标系采用Android系统坐标系
        //屏幕边缘4个点
        //需要跟顶点坐标的顺序一一对应,且需要是OpenGL坐标系和Android屏幕坐标系对应
//        val t = floatArrayOf(
//            0f, 0f,
//            0f, 1f,
//            1f, 0f,
//            1f, 1f
//        )

        //后摄顺时针旋转90度
//        val t = floatArrayOf(
//            0f, 1f,
//            1f, 1f,
//            0f, 0f,
//            1f, 0f
//        )

        //前摄逆时针旋转90度
//        val t = floatArrayOf(
//            1f, 0f,
//            0f, 0f,
//            1f, 1f,
//            0f, 1f
//        )
        //前摄逆时针旋转90度后再左右镜像
        val t = floatArrayOf(
            1f, 1f,
            0f, 1f,
            1f, 0f,
            0f, 0f
        )
        mTextureBuffer.put(t)

    }
}

4、配套工具类
将raw文件里的文件读取为String输出。

class TextResourceReader {
    companion object {
        fun read(context: Context, resourceId: Int):String {
            val sb = StringBuilder()
            val inputStream = context.resources.openRawResource(resourceId)
            val inputStreamReader = InputStreamReader(inputStream)
            val bufferReader = BufferedReader(inputStreamReader)
            var s = bufferReader.readLine()
            while (s!=null){
                sb.append(s)
                sb.append("\n")
                s = bufferReader.readLine()
            }
            return sb.toString()
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353