在Android中使用OpenGL ES开发第(四)节:相机预览

这里写图片描述

少年天才:“我不是天生强大,我只是天生要强”

笔者之前写了三篇Android中使用OpenGL ES入门级的文章,从OpenGL ES的相关概念出发,分析了利用OpenGL ES实现3D绘图的重要的两个步骤:定义形状和绘制形状,简单的绘制了一个三角形。
这里再简单回顾下:Android中使用OpenGL一共会涉及到四个类:

1)Activity——自不用说,Android界面展示的类;
2)GLSurfaceView——SurfaceView(API Level 1)的子类,View的孪生兄弟-SurfaceView专用于要求频繁刷新的界面中,比如相机预览界面;而GLSurfaceVIew(API Level 3)是继承于SurfaceView,且实现了SurfaceHolder.Callback2接口,专门用于OpenGL 中界面的展示;
3)GLSurfaceView.Renderer——这是一个接口,我们会自定义一个渲染器类来说实现这个接口。GLSurfaceView本身并不处理很多图像展示的相关任务,更多的实现是放在了该渲染器类中进行实现;该接口的实现会要求实现三个方法,每个方法会最终对应Camera的一种状态;
4)图像类——具体的类,具体实现看需求,比如笔者前面的文章中,该图像类就是Triangle,三角形类,关键的类,最终图像预览时呈现何种状态是在这个类中被定义的。

前期基础知识储备

SurfaceTexture了解
SurfaceTexture(API Level 11),掌握这个类是利用OpenGL实现相机预览的关键所在。等会看完代码,你就会发现,利用OpenGL画一个简单的形状和利用OpenGL实现相机预览两个看起来难度相差很大的任务实际上用到的OpenGL的代码却是十分相似的,唯一的区别就是在于相机预览时会用到SurfaceTexture进行纹理数据的处理。首先看下开发者文档中的描述:

Captures frames from an image stream as an OpenGL ES texture.
The image stream may come from either camera preview or video decode. A Surface created from a SurfaceTexture can be used as an output destination for the android.hardware.camera2, MediaCodec, MediaPlayer, and Allocation APIs. When updateTexImage() is called, the contents of the texture object specified when the SurfaceTexture was created are updated to contain the most recent image from the image stream. This may cause some frames of the stream to be skipped.

从文档中的描述,我们可以知道:SurfaceTexture的作用就是从Image Stream中捕获帧数据,用作OpenGL的纹理(纹理用于填充片元着色器,即“上色”),其中Image Stream来自相机预览或视频解码。 所以我们可以使用SurfaceTexture来和Camera进行连接(调用camera.setPreviewTexture(mSurfaceTexture)方法)从而获取相机传过来的预览图像流数据,而SurfaceTexture类获取到图像流数据之后并不会进行显示,而是对其处理后传给GLSurfaceView或者TextureView进行显示,因此SurfaceTexture和GLSurfaceView的搭配使用十分合适。

上代码,具体实现

1)自定义相机预览类,实现渲染器接口和SurfaceTexture的接口;

public class CameraGLSurfaceView extends GLSurfaceView implements Renderer, SurfaceTexture.OnFrameAvailableListener

2)写入详解预览类CameraGLSurfaceView的构造方法;

    public CameraGLSurfaceView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        // TODO Auto-generated constructor stub  
        mContext = context;  
        setEGLContextClientVersion(2);  
        setRenderer(this);  
        setRenderMode(RENDERMODE_WHEN_DIRTY);  
    }

看过笔者之前OpenGL文章的朋友理解这段代码是没有困难的,这里首先指定使用的OpenGL ES的版本为2.0,然后调用setRenderer()方法绑定适渲染器接口,最后调用setRendererMode()的方法指定模式为RENDERMODE_WHEN_DIRTY-即有新数据时进行刷新,否则等待。

3)关键-实现渲染器接口的三个方法

@Override  
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {  
        // TODO Auto-generated method stub  
        Log.i(TAG, "onSurfaceCreated...");  
        mTextureID = createTextureID();  
        mSurface = new SurfaceTexture(mTextureID);  
        mSurface.setOnFrameAvailableListener(this);  
        mDirectDrawer = new DirectDrawer(mTextureID);  
        CameraInterface.getInstance().doOpenCamera(null); //①在创建surface时打开相机 
    }  
    @Override  
    public void onSurfaceChanged(GL10 gl, int width, int height) {  
        // TODO Auto-generated method stub  
        Log.i(TAG, "onSurfaceChanged...");  
        GLES20.glViewport(0, 0, width, height);  
        if(!CameraInterface.getInstance().isPreviewing()){  
            CameraInterface.getInstance().doStartPreview(mSurface, 1.33f); //②预览
        }  
    }  
    @Override  
    public void onDrawFrame(GL10 gl) {  
        // TODO Auto-generated method stub  
        Log.i(TAG, "onDrawFrame...");  
        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);  
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);  
        mSurface.updateTexImage(); //SurfaceTexture的关键方法
        float[] mtx = new float[16];  
        mSurface.getTransformMatrix(mtx); //SurfaceTexture的关键方法
        mDirectDrawer.draw(mtx);  //③调用图形类的draw()方法
    }

分析:这三个方法里面的代码非常关键,(1)onSurfaceCreated()方法中实例化SurfaceTexture对象mSurface和图形类对象mDirectDrawer,并且在这里调用了相机的开启方法doOpenCamera(),这是使用OpenGL实现相机预览时OpenGL代码对相机的第一次控制;
(2)onSurfaceChanged()方法中调用相机的doStartPreview()方法,这个方法调用在相机界面发生变化时,会关闭相机预览然后重新打开相机预览,即doStartPreview()方法内部实现实际是连续调用了mCamera.stopPreview(); mCamera.setPreviewDisplay(mHolder); mCamera.startPreview();三个方法;
(3)onDrawFrame()方法最为关键,首先是两行OpenGL的常规设置代码(glClearColor()、glClear()),用于设置背景;接着是连续调用了SurfaceTexture的两个关键的方法:①updateTexImage(), 当updateTexImage()方法被调用时,SurfaceTexture对象所关联的OpenGLES中纹理对象的内容将被更新为相机预览传出的图像流中最新的图片;②getTransformMatrix(),该方法也是必须调用的,用以转换已经发生变换的纹理矩阵的坐标;最后是调用了图像类的draw()方法用以绘制预览的界面。

4)剩余的代码段,辅助作用

    @Override  
    public void onPause() {  
        // TODO Auto-generated method stub  
        super.onPause();  
        CameraInterface.getInstance().doStopCamera();  //④暂停预览时调用的相机方法
    }  
    private int createTextureID()  
    {  
        int[] texture = new int[1];  

        GLES20.glGenTextures(1, texture, 0);  
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);  
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,  
                GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);          
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,  
                GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);  
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,  
                GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);  
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,  
                GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);  

        return texture[0];  
    }  

    public SurfaceTexture _getSurfaceTexture(){  
        return mSurface;  
    } 

    //该方法是实现SurfaceTexture.OnFrameAvailableListener接口时实现的方法
    //用于提示新的数据流的到来
    @Override  
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {  
        // TODO Auto-generated method stub   
        this.requestRender();  
    }

以上的代码就是自定义的相机预览类的中的关键代码,接下来,我们再看看另外的一个关键类——图像类
1)自定义一个图像类 DirectDrawer

public class DirectDrawer

2)相关变量的声明:顶点着色器的声明,片元着色器的声明,缓冲区变量的声明,程式的声明,顶点坐标的声明,纹理坐标的声明

private final String vertexShaderCode =  
            "attribute vec4 vPosition;" +  
            "attribute vec2 inputTextureCoordinate;" +  
            "varying vec2 textureCoordinate;" +  //传给片元着色器的变量
            "void main()" +  
            "{"+  
                "gl_Position = vPosition;"+  
                "textureCoordinate = inputTextureCoordinate;" +  
            "}";  

    private final String fragmentShaderCode =  
            "#extension GL_OES_EGL_image_external : require\n"+  //固定指令
            "precision mediump float;" +  //固定指令
            "varying vec2 textureCoordinate;\n" +  //顶点着色器中传递过来的变量
            "uniform samplerExternalOES s_texture;\n" + //固定指定
            "void main() {" +  
            "  gl_FragColor = texture2D( s_texture, textureCoordinate );\n" +  
            "}";  

    private FloatBuffer vertexBuffer, textureVerticesBuffer;  
    private ShortBuffer drawListBuffer;  
    private final int mProgram;  
    private int mPositionHandle;  
    private int mTextureCoordHandle;  

    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; //绘制顶点的顺序  

    // number of coordinates per vertex in this array  
    private static final int COORDS_PER_VERTEX = 2;  

    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex  

    //顶点坐标-坐标值是固定的搭配,不同的顺序会出现不同的预览效果
    static float squareCoords[] = {  
       -1.0f,  1.0f,  
       -1.0f, -1.0f,  
        1.0f, -1.0f,  
        1.0f,  1.0f,  
    };  
    //纹理坐标-相机预览时对片元着色器使用的是纹理texture而不是颜色color
    static float textureVertices[] = {  
        0.0f, 1.0f,  
        1.0f, 1.0f,  
        1.0f, 0.0f,  
        0.0f, 0.0f,  
    };

2)图像类的构造方法-相机预览的图像类和画图时的图像类的构造方法极为相似

    private int texture;  

    public DirectDrawer(int texture)  
    {  
        this.texture = texture; 
        // initialize vertex byte buffer for shape coordinates  
        ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);  
        bb.order(ByteOrder.nativeOrder());  
        vertexBuffer = bb.asFloatBuffer();  
        vertexBuffer.put(squareCoords);  //第一个装载的数据是顶点坐标
        vertexBuffer.position(0);  

        // initialize byte buffer for the draw list  
        ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);  
        dlb.order(ByteOrder.nativeOrder());  
        drawListBuffer = dlb.asShortBuffer();  
        drawListBuffer.put(drawOrder);  //第二个装载的数据是绘制顶点的顺序
        drawListBuffer.position(0);  

        ByteBuffer bb2 = ByteBuffer.allocateDirect(textureVertices.length * 4);  
        bb2.order(ByteOrder.nativeOrder());  
        textureVerticesBuffer = bb2.asFloatBuffer();  
        textureVerticesBuffer.put(textureVertices);  //第三个装载的数据是纹理坐标
        textureVerticesBuffer.position(0);  

        //解析之前变量中声明的两个着色器
        int vertexShader    = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);  
        int fragmentShader  = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);  

        mProgram = GLES20.glCreateProgram();             // 创建空程式
        GLES20.glAttachShader(mProgram, vertexShader);   // 添加顶点着色器到程式中
        GLES20.glAttachShader(mProgram, fragmentShader); // 添加片元着色器到程式中
        GLES20.glLinkProgram(mProgram);                  // 链接程式
    }   
}   

3)千呼万唤始出来-绘制方法draw()

public void draw(float[] mtx)  
    {  
        GLES20.glUseProgram(mProgram);  

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);  
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture);  

        // get handle to vertex shader's vPosition member  
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");  

        // Enable a handle to the triangle vertices  
        GLES20.glEnableVertexAttribArray(mPositionHandle);  

        // Prepare the <insert shape here> coordinate data  
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);  

        //使用一次glEnableVertexAttribArray方法就要使用一次glVertexAttribPointer方法
        mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");  
        GLES20.glEnableVertexAttribArray(mTextureCoordHandle);  

        GLES20.glVertexAttribPointer(mTextureCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, textureVerticesBuffer);  

        GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);  

        // 使用了glEnableVertexAttribArray方法就必须使用glDisableVertexAttribArray方法
        GLES20.glDisableVertexAttribArray(mPositionHandle);  
        GLES20.glDisableVertexAttribArray(mTextureCoordHandle);  
    }

代码看上去有点复杂,但是结合笔者之前利用OpenGL实现画图的draw()方法的代码和整理使用OpenGL实现相机预览的draw()方法来看,就会发现这段代码看上去复杂,实际是几乎固定的代码,每个方法调用都是有规律可循。

程式相关方法
glUseProgram()
顶点着色器相关方法
glGetAttribLocation()
glEnableVertexAttribArray()
glVertexAttribPointer()
片元着色器相关方法
glGetUniformLocation()
glUniform4fv()
绘制相关方法
glDrawArrays-绘制方法
收尾相关方法
glDisableVertexAttribArray()

有读过笔者前面OpenGL相关文章的读者朋友会发现使用OpenGL画图和使用OpenGL实现相机预览时,图像类的变量声明、构造方法和绘制方法draw()都是十分的相似的,换言之,这里的使用OpenGL的代码几乎是可以看成是模板代码,而实现两种功能的不同就在于相机预览类中使用了SurfaceTexture进行相关数据传输和控制。

从南美到欧洲,飞越高山与海洋,潘帕斯雄鹰终成王者

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: OpenGL ES是一种用于开发图形应用程序的开放式图形编程接口。它被广泛用于Android系统上的游戏开发以及其他需要高性能图形渲染的应用程序。下面是关于OpenGL ESAndroid开发的一些要点: 1. OpenGL ES的版本:Android支持多个不同版本的OpenGL ES,如OpenGL ES 1.0、1.1、2.0和3.0。开发者根据自己的需求选择合适的版本。 2. 渲染管线:OpenGL ES使用可编程的渲染管线来处理图形渲染。开发者可以通过创建顶点着色器和片段着色器程序来自定义渲染过程,从而实现各种效果。 3. 缓冲对象:开发者可以使用OpenGL ES来创建和管理缓冲对象,如顶点缓冲对象(VBO)和帧缓冲对象(FBO)。这些缓冲对象用于存储图形数据,提高绘制效率。 4. 纹理映射:OpenGL ES允许开发者将纹理映射到三维对象上,以实现更加逼真的图形效果。开发者可以通过加载纹理图像并将其映射到顶点上来创建细丰富的模型。 5. 事件处理:OpenGL ES提供了一些函数来处理触摸事件、加速度计变化等输入信息。开发者可以使用这些函数来实现交互式的图形应用程序。 6. OpenGL ES的集成:在Android开发开发者可以通过GLSurfaceView类来集成OpenGL ES。GLSurfaceView是Android提供的一个用于显示OpenGL ES图形的视图类。 总结来说,OpenGL ESAndroid开发用于实现高性能图形渲染的重要工具。开发者可以利用它来创建各种各样的游戏和图形应用程序,并通过自定义着色器和纹理映射等技术来增加细和逼真度。 ### 回答2: OpenGLES是一种用于开发移动设备和嵌入式系统的图形渲染API,它主要用于在Android平台上开发游戏和图形应用程序。使用OpenGLES开发者可以利用硬件加速的图形渲染功能,提供流畅的图形效果和高性能的图形渲染。 在Android平台上进行OpenGLES开发,首先需要在应用程序引入OpenGLES库文件,并进行相关的环境设置。然后,开发者可以使用OpenGLES API提供的各种函数和方法来创建图形对象、设置渲染状态、进行变换和纹理映射等操作。同时,还可以使用OpenGLES提供的着色器语言,自定义渲染管线,实现更高级的图形效果。 在开发过程,需要注意OpenGLES使用的坐标系统是以屏幕为心的坐标系,而不是传统的以左上角为原点的坐标系。因此,在创建图形对象时,需要进行坐标转换。此外,还需要注意管理资源和内存,避免内存泄漏和资源浪费。 在实际开发,可以利用OpenGLES创建各种图形效果,如平面、立体、光照、阴影等。同时,还可以通过OpenGLES实现用户交互,如触摸屏幕,操作物体的变换等。此外,还可以使用OpenGLES与其他Android组件进行交互,如利用OpenGL ES绘制图像进行相机、视频播放等。 总之,OpenGLESAndroid开发具有重要的作用,能够实现高性能的图形渲染和丰富的图形效果,为开发者提供了强大的工具和技术支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值