美文网首页
1.OpenGl渲染一个图片+灰度滤镜

1.OpenGl渲染一个图片+灰度滤镜

作者: liys_android | 来源:发表于2023-07-03 22:47 被阅读0次

    本章内容:主要是熟悉OpenGl使用的基本流程

    版本:GLES20

    android 音视频基础 系列----后续(调整):
    1. OpenGl--渲染一个图片+灰度滤镜
    2. OpenGl 渲染 CameraX的数据 + 滤镜

    3. MediaCodec解码---h264
    4. h264基础<一>
    5. h264基础<二>
    6. MediaCodec 硬编码---YUV数据
    7. MediaCodec 硬编码---CameraX数据 
    8. MediaCodec 硬编码---MediaProjection录屏
    9. 音频基础
    10. MediaCodec硬编码---AudioRecord音频采集
    11. MediaCodec解码---播放mp4
    12. MediaCodec编码--CameraX+麦克风编码成mp4
    13. 交叉编译基础
    14. ffmpeg交叉编译
    15. ffmpeg 视频---mediacodec硬解码
    16. ffmpeg 视频---软解码
    17. OpenGl在native层的使用
    18. OpenGl在native层渲染YUV420数据
    19. OpenGl在native层渲染NV12数据
    20. OpenGl处理常见的几种滤镜.
    21. Openssl播放音频
    22. 音视频同步
    待续..
    

    为什么要先看OpenGl?

    因为在音视频中, 渲染的时候经常涉及到纹理,涉及到数据在硬件间的传递,有了OpenGl基础, 后续对视频数据的流动才能更清晰, 例如:Camera的数据, 不经过我们应用就可以用MediaCodec编码. MediaCodec解码后你想让数据到那儿去了, Surface直接渲染、OpenGl中纹理、我们的应用, 都是可以的, 至于需要用哪种看需求.
    目前这个系列主要以音视频开发为准, 并不会太过深入OpenGL和细节

    OpenGl 是什么?

    简单说:一套往GPU写程序的API

    OpenGl主要往GPU写什么?

    1. 顶点程序:定位绘制的区域 ---> 需要了解OpenGL坐标--->世界坐标和纹理坐标
    2. 片元程序:给这块区域的每个像素点上色.

    Android中使用OpenGl的基本流程:

    以渲染一张图片+灰度滤镜为例,效果图如下:

    OpenGL图片.jpg
    1. 配置EGL环境

    我们写的代码在CPU,如果想要和GPU通讯, 就必须先创建好对应的环境,这个就叫EGL环境, EGL环境所在的线程叫OpenGl线程, 想要和GPU交互, 必须在这个线程中.
    不过Android中的Java层有个控件已经帮我们创建好了环境,这个控件是android.opengl.GLSurfaceView.
    如果要在native层使用, 那就得自己去创建EGL环境了.

    2. 把顶点和片元程序 添加 到 GPU中, 并且编译链接.

    ①. 准备好片元和顶点程序

    两个着色器都放在res/raw目录下

    顶点着色器 image_vert.glsl

    attribute vec4 vPosition;//顶点坐标
    attribute vec4 vCoordinate;  //纹理的坐标
    varying vec2 tCoordinate;  //传递给片元
    void main(){
       gl_Position = vPosition;
       tCoordinate = vCoordinate.xy;
    }
    

    片元着色器 image_frag.glsl

    precision lowp float;
    varying vec2 tCoordinate; //纹理坐标
    uniform sampler2D vTexture; //图层采样
    void main() {
       //初始颜色
    //   gl_FragColor = texture2D(vTexture, vec2(tCoordinate.x, tCoordinate.y));
    
       //灰度图
       vec4 rgba = texture2D(vTexture, vec2(tCoordinate.x, tCoordinate.y));
       float value = (rgba.r+rgba.g+rgba.b)/3.0;
       gl_FragColor = vec4(value, value, value, rgba.a);
    
    }
    

    ②. 把两个着色器的代码加到GPU, 并进行编译链接
    GLSurfaceView提供了一个接口Renderer, 只需继承这个接口在里面写代码就行了, 我做了一点简单的封装,这样可以更清晰的看清楚执行流程.
    以下代码属于初始化, 所以也写在Renderer中的onSurfaceCreated中:

            //顶点程序加到GPU中, vertexShaderCode为 image_vert.glsl的内容
            val vertexShader = GLApp.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
            //片元程序加到GPU中, fragmentShaderCode为 image_frag.glsl.glsl的内容
            val fragmentShader =  GLApp.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
            //创建一个空的OpenGLES程序
            mProgram = GLES20.glCreateProgram()
            //将顶点着色器加入到程序
            GLES20.glAttachShader(mProgram, vertexShader)
            //将片元着色器加入到程序中
            GLES20.glAttachShader(mProgram, fragmentShader)
            //连接到着色器程序
            GLES20.glLinkProgram(mProgram)
    
    3. 获取顶点和片元程序中的句柄

    以下代码属于初始化, 所以也写在Renderer中的onSurfaceCreated中

    //        --------------------------获取变量-------------------------------
            //获取变量
            positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
            vCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "vCoordinate");
            vTextureHandle = GLES20.glGetUniformLocation(mProgram, "vTexture");
    
    4. 创建纹理

    创建纹理, 并获取纹理id, 绑定纹理单元

    //      --------------------------纹理-------------------------------
            textureId = GLApp.createTexture()
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
    
    5. 通过句柄,给顶点或纹理赋值
            //赋值坐标数据
            GLApp.setCoordinate(positionHandle, vPositionBuffer!!)
            GLApp.setCoordinate(vCoordinateHandle, vCoordinateBuffer!!)
    
            //激活绑定纹理
            GLApp.activeBindTexture(vTextureHandle, textureId, 0)
    
    6. 通知GPU开始渲染
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); //绘制
    
    完整代码如下:

    GLImageRenderer.kt 如下:

    class GLImageRenderer(var bitmap: Bitmap): BaseRenderer() {
        private var vPositionBuffer: FloatBuffer? = null //顶点坐标buf
        private var vCoordinateBuffer: FloatBuffer? = null //纹理坐标buf
    
        //句柄
        private var positionHandle = 0
        private var vCoordinateHandle = 0
        private var vTextureHandle = 0
    
        //纹理id
        private var textureId = 0
    
        override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
            LogApp.d("GL onSurfaceCreated")
            GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f)
            //链接
            initProgram(
                GLApp.readRawTextFile(App.context, R.raw.image_vert),
                GLApp.readRawTextFile(App.context, R.raw.image_frag),
            )
    
    //        --------------------------获取变量-------------------------------
            //获取变量
            positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
            vCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "vCoordinate");
            vTextureHandle = GLES20.glGetUniformLocation(mProgram, "vTexture");
    
    //        --------------------------顶点转buf-----------------------------
            vPositionBuffer = GLApp.coordinate2Buf(floatArrayOf(
                -1.0f, -1.0f,
                1.0f, -1.0f,
                -1.0f, 1.0f,
                1.0f, 1.0f
            ))
            vCoordinateBuffer = GLApp.coordinate2Buf(floatArrayOf(
                0.0f, 1.0f,
                1.0f, 1.0f,
                0.0f, 0.0f,
                1.0f, 0.0f
            ))
    
    //      --------------------------纹理-------------------------------
            textureId = GLApp.createTexture()
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
            //其它数据可以用 GLES20.glTexImage2D(...)
        }
    
        override fun onDrawFrame(gl: GL10) {
            LogApp.d("onDrawFrame")
            GLES20.glClearColor(1.0f, 0f, 0f, 1.0f)
            GLES20.glUseProgram(mProgram);
    
            //赋值坐标数据
            GLApp.setCoordinate(positionHandle, vPositionBuffer!!)
            GLApp.setCoordinate(vCoordinateHandle, vCoordinateBuffer!!)
    
            //激活绑定纹理
            GLApp.activeBindTexture(vTextureHandle, textureId, 0)
    
            //绘制
            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); //绘制
            GLES20.glDisableVertexAttribArray(positionHandle);  //禁止顶点数组的句柄
        }
    }
    

    BaseRenderer.kt 如下:

    abstract class BaseRenderer : GLSurfaceView.Renderer {
    
        //总程序id
        var mProgram = 0
        var width = 0
        var height = 0
    
        /**
         * 初始化GPU程序
         * @param vertexShaderCode 顶点程序
         * @param fragmentShaderCode 片元程序
         */
        fun initProgram(vertexShaderCode:String, fragmentShaderCode:String){
            //加载,编译, 链接
            val vertexShader = GLApp.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
            val fragmentShader =  GLApp.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
            //创建一个空的OpenGLES程序
            mProgram = GLES20.glCreateProgram()
            //将顶点着色器加入到程序
            GLES20.glAttachShader(mProgram, vertexShader)
            //将片元着色器加入到程序中
            GLES20.glAttachShader(mProgram, fragmentShader)
            //连接到着色器程序
            GLES20.glLinkProgram(mProgram)
        }
    
    
        override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
            LogApp.d("GL onSurfaceChanged")
            GLES20.glViewport(0, 0, width, height); //指定宽高
            this.width = width
            this.height = height
        }
    }
    

    工具类 GLApp.kt

    object GLApp {
    
        //坐标 转 buf
        fun coordinate2Buf(coordinatePosition: FloatArray): FloatBuffer {
            val bb = ByteBuffer.allocateDirect(coordinatePosition.size * 4)
                    .order(ByteOrder.nativeOrder())
            //将坐标数据转换为FloatBuffer,用以传入OpenGL ES程序
            val coordinateBuffer = bb.asFloatBuffer()
            coordinateBuffer.put(coordinatePosition)
            coordinateBuffer.position(0)
            return coordinateBuffer
        }
    
        /**
         * 坐标赋值
         * @param coordinateHandle 坐标句柄
         * @param coordinateBuffer 坐标buf
         */
        fun setCoordinate(coordinateHandle:Int, coordinateBuffer:FloatBuffer){
            coordinateBuffer.position(0);
            GLES20.glVertexAttribPointer(coordinateHandle, 2, GLES20.GL_FLOAT, false, 0, coordinateBuffer);
            GLES20.glEnableVertexAttribArray(coordinateHandle);
        }
    
        //加载程序
        fun loadShader(type: Int, shaderCode: String): Int {
            //根据type创建顶点着色器或者片元着色器
            val shader = GLES20.glCreateShader(type)
            //将资源加入到着色器中,并编译
            GLES20.glShaderSource(shader, shaderCode)
            GLES20.glCompileShader(shader)
            return shader
        }
    
        //创建纹理
        fun createTexture():Int{
            val textures = IntArray(1)
            GLES20.glGenTextures(1, textures, 0)
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0])
    
            //纹理环绕方式
            GLES20.glTexParameterf(
                GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_S,
                GLES32.GL_CLAMP_TO_BORDER.toFloat()
            )
            GLES20.glTexParameterf(
                GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_T,
                GLES32.GL_CLAMP_TO_BORDER.toFloat()
            )
            //过滤器
            GLES20.glTexParameterf(
                GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MAG_FILTER,
                GLES20.GL_NEAREST.toFloat()
            )
            GLES20.glTexParameterf(
                GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER,
                GLES20.GL_NEAREST.toFloat()
            )
    
            return textures[0];
        }
    
    
        /**
         * 激活并绑定纹理
         * @param samplerHandle 采样器变量句柄
         * @param textureId 纹理ID
         * @param texturePosition 第几个纹理 0开始
         */
        fun activeBindTexture(samplerHandle:Int, textureId:Int, texturePosition:Int){
            GLES20.glActiveTexture(GLES20.GL_TEXTURE0+texturePosition); //激活一个纹理层(GPU内置的图层),一共32层
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); //告诉GPU 我们要使用textureId这个纹理(自己创建的)
            GLES20.glUniform1i(samplerHandle, texturePosition); //我们创建的纹理 + 采样器 + GPU图层 关联起来
        }
    
    
        //读取res/raw下的文件
        fun readRawTextFile(context: Context, rawId: Int): String {
            val `is`: InputStream = context.resources.openRawResource(rawId)
            val br = BufferedReader(InputStreamReader(`is`))
            var line = ""
            val sb = StringBuilder()
            try {
                while (br.readLine().also { line = it } != null) {
                    sb.append(line)
                    sb.append("\n")
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
            try {
                br.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
            return sb.toString()
        }
    }
    

    Activity中使用

    val bitmap = BitmapFactory.decodeResource(resources, R.drawable.test2)
    //设置OpenGl版本号
    binding.glSurfaceView.setEGLContextClientVersion(2);
    binding.glSurfaceView.setRenderer(GLImageRenderer(bitmap)) //设置一个渲染器
    binding.glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY //手动渲染 自动渲染(RENDERMODE_CONTINUOUSLY)
    
    如何取出GPU中的数据
    //取出数据
    val buf = FloatBuffer.allocate(width * height * 8)
    GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
    
    val byteBuffer = ByteBuffer.allocate(buff.capacity()*4);
    byteBuffer.asFloatBuffer().put(buff)  //数据已在buf了, 如果要转成bitmap注意宽高+数据格式
    

    相关文章

      网友评论

          本文标题:1.OpenGl渲染一个图片+灰度滤镜

          本文链接:https://www.haomeiwen.com/subject/ziqtudtx.html