Android中的OpenGL使用初探

Posted 形上为道

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android中的OpenGL使用初探相关的知识,希望对你有一定的参考价值。

OpenGL(Open Graphics Library)是一个跨编程语言、跨平台的编程图形程序接口,主要用于图像的渲染。android提供了简化版的OpenGL接口,即OpenGL ES。

查看系统支持的OpenGL版本

    /**
     * 获取支持的OpenGL版本,前16位代表最高版本,后16位代表最低版本,如0x30002,支持OpenGL 2/3
     */
    public int getGLVersion()
        ActivityManager activityManager = getSystemService(ActivityManager.class);
        ConfigurationInfo info = activityManager.getDeviceConfigurationInfo();
        return info.reqGlEsVersion;
    

OpenGL及GLSurfaceView的初始化

Android系统提供了GLSurfaceView,在其内部做了OpenGL初始化工作,可直接使用GLSurfaceView。GLSurfaceView实际上创建了一个window,并在视图层穿了个洞,让底层的surface显示出来,与常规的View不同的是它没有动画或变形特效,因为它是window的一部分。

GLSurfaceView的初始化主要有

  • 设置EGL版本
glSurfaceView.setEGLContextClientVersion(3);
  • 设置渲染器
glSurfaceView.setRenderer(render);
  • 设置渲染模式,需要在设置渲染器之后
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

GLSurfaceView.Renderer

GLSurfaceView内部的一个接口,需要重写三个方法

  • onSurfaceCreated():surface第一次创建或画面重新resume会调用,通常用来加载着色器,以及设置清屏时的颜色:
    GLES20.glClearColor(r,g,b,a);
  • onSurfaceChanged():surface尺寸发生变化时调用,通常在这里计算坐标投影矩阵,以及设置OpenGL窗口的大小
    GLES20.glViewport(0, 0, width, height); 
  • onDrawFrame():每次绘制都会调用,主要做清屏以及绘制,如果之前设置的渲染模式是RENDERMODE_WHEN_DIRTY,则需要调用
    surfaceView.requestRender(),然后才会走这个方法
    @Override
    public void onDrawFrame(GL10 gl) 
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_STENCIL_BUFFER_BIT);//清屏
        if (surfaceTexture != null) 
            surfaceTexture.updateTexImage();
        
        shaderElement.draw();//绘制着色器元素
    

着色器(Shader)

Android中的OpenGL支持顶点着色器(VertexShader)和片元着色器(FragmentShader)。通常在res/raw中保存着色器程序(xx.glsl),建议在android studio中下载glsl的插件。

VertexShader

用来处理图形每个顶点变换,包括旋转、平移、投影等,下面例子是顶点着色器程序vertex.glsl

#version 300 es
in vec4 verTexPosition;//顶点向量,数据来自java数组,只读
in vec4 textureCoordinate;//纹理的向量
uniform mat4 textureMatrix;//与surfaceTexture相关联的纹理坐标变换矩阵,一致变量,在着色器执行期间是不变的,与片段着色器共享
uniform mat4 projectionMatrix;//坐标投影矩阵
out vec2 aCoor;//顶点着色器的输出,作为片段着色器的输入
out float weights[9];
out vec2 coordinates[9];

void main() 
    gl_Position=projectionMatrix*verTexPosition;
    aCoor=(textureMatrix*textureCoordinate).xy;

上面代码是OpenGL3.0的写法,与2.0有所区别;

gl_Position为内置变量,表示顶点的位置

FragmentShader

计算纹理的颜色并填充,片元着色器有多个,每个可以理解为一个点。

相机输出到surface采用的是外部纹理

samplerExternalOES
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
in vec2 aCoor;
out vec4 gl_FragColor;
uniform samplerExternalOES fragmentSampler;
void main() 
    gl_FragColor=texture(fragmentSampler, aCoor);//采样函数texture()
gl_FragColor为内置变量,表示输出的颜色,在OpenGL3.0中需要用out修饰声明;
其它的纹理一般是用sampler2D,结合采样函数texture()输出纹理颜色

Shader中的变量修饰符

shader中有三种变量修饰符:uniform,attribute,varying;OpenGL3.0中用in替换顶点着色器中的attibute和片元着色器中的varying,用out替换顶点着色器的varying及修饰输出的gl_FragColor。

uniform变量

外部程序传递给shader的变量,相当于常量,shader中不可修改,可用于顶点及片元着色器中;如果在vertex和fragment两者之间声明方式完全一样,则它可以在vertex和fragment共享使用;应用端通过glUniformXX()传值;一般用来表示:变换矩阵,材质,光照参数和颜色等信息。

attribute变量(3.0用 in)

只能在vertex shader中使用,在OpenGL3.0中的声明用in;一般用于表示顶点数据如:顶点坐标,法线,纹理坐标,顶点颜色等;应用端通过glVertexAttribXX()传值。

varying变量

用于vertex和fragment之间的数据传递,在二者之间的声明必须一致;在OpenGL3.0中,对应vertex中的out和fragment中的in;应用端不能使用此变量。

加载着色器程序

    private static int loadShader(int type, @RawRes int resId) 
        int shaderId = GLES20.glCreateShader(type);//创建shader
        GLES20.glShaderSource(shaderId, readCodes(resId));//加载glsl代码
        GLES20.glCompileShader(shaderId);//编译shader
        int[] status = new int[1];
        GLES20.glGetShaderiv(shaderId, GLES20.GL_COMPILE_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) 
            Log.e(TAG, "loadShader: failed, type=" + type + " message=" + GLES20.glGetShaderInfoLog(shaderId));
            GLES20.glDeleteShader(shaderId);
            return -1;
        
        return shaderId;
    

    /**
     * 加载着色器程序
     * @param vid vertex shader的资源id
     * @param fid fragment shader的资源id
     * @return OpenGL的programId,可以用来查找着色器程序的变量位置
     */
    public static int loadProgram(@RawRes int vid, @RawRes int fid) 
        int vShaderId = loadShader(GLES20.GL_VERTEX_SHADER, vid);//顶点着色器
        int fShaderId = loadShader(GLES20.GL_FRAGMENT_SHADER, fid);//片段着色器
        int program = GLES20.glCreateProgram();
        if (vShaderId != -1) 
            GLES20.glAttachShader(program, vShaderId);//指定要附加的着色器对象
        
        if (fShaderId != -1) 
            GLES20.glAttachShader(program, fShaderId);
        
        GLES20.glLinkProgram(program);
        int[] status = new int[1];
        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) 
            throw new RuntimeException("program link failed! reason=" + GLES20.glGetProgramInfoLog(program));
        
        return program;
    

从res中读取glsl的字符串,并加载glsl代码,并编译shader,这里可以封装一个帮助类。

OpenGL相关常用的java API

获取glsl中的变量位置:

int locationA=GLES20.glGetAttribLocation(programId,name);//获取attribute变量的位置,OpenGL3对应是in
int locationU=GLES20.glGetUniformLocation(programId, name1);//获取uniform变量的位置

创建buffer,用来存放要传给着色器的数组,通常包括顶点数组、纹理数组、颜色数组

    private FloatBuffer createBuffer(float[] arr) 
        FloatBuffer buffer = ByteBuffer.allocateDirect(arr.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(arr);
        buffer.position(0);
        return buffer;
    

创建纹理并设置参数

    protected int createTexture() //generate texture and set params
        int[] texture = new int[1];
        glGenTextures(texture.length, texture, 0);
        int target = forCamera ? GLES11Ext.GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D;
        glBindTexture(target, texture[0]);
        //设置纹理参数
        glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//缩小,最近邻过滤
        glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//放大,双线性过滤
        glTexParameteri(target, GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
        glTexParameteri(target, GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
        glBindTexture(target, 0);
        return texture[0];
    

指定使用的着色器

glUseProgram(programId);

设置attribute变量的值

 glEnableVertexAttribArray(location);
 glVertexAttribPointer(location, size, GL_FLOAT, false, 0, buffer);//size是每个元素的大小,比如二维坐标,就是2

设置uniform变量的值

glUniform1f(highLocation, 1f);//float 类型
glUniform1i(extSampYLocation, 3);//int 类型
glUniform4f(rectLocation, left, top, right, bottom);//float 数组
glUniformMatrix3fv(location, 1, true, matrix, 0);//矩阵传值

传递大数据量的数据,可以使用纹理的方式

glActiveTexture(GL_TEXTURE2);//之前的纹理已绑定到TEXTURE2
Buffer buffer = ByteBuffer.allocateDirect(table.length).put(table).position(0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, table.length / 3, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);

特别注意:向着色器程序传递数据,或者读取数据,只能在GL渲染线程进行,因此传值时通常是

surfaceView.queueEvent(runnable);

绘制图形:OpenGL ES 只支持点、线和三角形绘制,矩形可以用多个三角形组成

glDrawArrays(GL_TRIANGLE_STRIP, 0, count);

着色器元素的绘制,大概是这样

public void draw() 
        glUseProgram(programId);
        /*bindAttribureData(vetexLocation, vertexSize, vertexBuffer);//顶点,之前已绑定,忽略
        bindAttribureData(textureLocation, textureSize, textureBuffer);//纹理
        bindAttribureData(colorLocation, colorSize, colorBuffer);//颜色*/
        glUniformMatrix4fv(projectionMatrixLocation, 1, false, projectionMatrix, 0);//投影矩阵
        glUniformMatrix4fv(textureMatrixLocation, 1, false, textureMatrix, 0);//纹理矩阵

        glActiveTexture(GL_TEXTURE0);//选择当前活跃的纹理单元
        glBindTexture(GL_TEXTURE_2D, textureId);
        glUniform1i(samplerLocation, 0);//对应GL_TEXTURE0
        int count = vertexBuffer.capacity() / vertexSize;
        glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
        glBindTexture(GL_TEXTURE_2D, 0);//取消之前绑定的纹理
    

结合上面onDrawFrame()中的清屏操作,基本就完整了。

Android组件Activity初探

 

1.Activity是什么

Activity是Android系统中的四大组件之一,在MVC模式中属于C控制层

       M(Model 模型):Model是应用程序的主体对象。
       V(View 视图):是应用程序中负责生成用户界面的部分,使用XML作为编程语言。
       C(Controller控制层)android的控制层的重任就要落在众多的activity的肩上了,所以在这里就要建议大家不要在activity中写太多的代码,尽量能过activity交割Model业务逻辑层处理。

 

一个应用程序通常由多个Activities组成,他们通常是松耦合关系。通常,应用程序中第一个被展示的Activity被指定为"Main" Activity。每一个Activity然后可以启动另一个Activity。每一次一个Activity启动,前一个activity就停止了。当一个新Activity启动,它被推送到栈顶(压栈),取得用户焦点。Back Stack符合简单“后进先出”原则,所以,当用户完成当前Activity然后点击back按钮,它被弹出栈(并且被摧毁),然后之前的Activity恢复。

 

2.Activity的生命周期

Activity生命周期图

 

 

Activity 的生命周期中的系统回调函数。
public class Activity extends ApplicationContext {
      protected void onCreate(Bundle icicle);
      protected void onStart();
      protected void onRestart();
      protected void onResume();
      protected void onFreeze(Bundle outIcicle);
      protected void onPause();
      protected void onStop();
      protected void onDestroy();
}

 

3.Activity的状态类型

Activity有四种状态:
       活动状态(Activity):当Activity处于Stack(栈)顶时,就是手机当前的现实屏幕,这是 Activity就处于activity或者运行状态。
       运行但是失去焦点(Pause):当Activity还处于运行状态时,但是屏幕是有另外一个Activity处于文档处于焦点状态,当前的Activity就处于pause。
       停止(Stop),当Activity被另一个Activity完全覆盖的时候,就被停止了,其实就是虽然在运行,但是用户却看不见。
       结束(Destroy),当Activity处于pause或者stop时,系统可以结束 Activity,回收资源,这是Activity就是处于结束状态了。
       

4.Activity的响应时间
       当前Activity所在的线程为主线程,它的响应时间为5秒,如果在当前运行的Activity中进行耗时的操作且响应时间起过5秒,那么程序就会报ANR错误。所以,这也是不建议在Activity中写太多复杂代码的原因之一。
       当然,有些代码只能写在Activity中,不然就运行不了(它们不是生命周期方法),比如你想要获得android系统或者硬件一的些信息,就必须在Activity中写出来,如果单独写一个工具类获得不了。

以上是关于Android中的OpenGL使用初探的主要内容,如果未能解决你的问题,请参考以下文章

Android openGl开发详解-相机预览

Android中的MVP架构初探

Android 多线程编程初探

Android组件Activity初探

从精准化测试看ASM在Android中的强势插入-JaCoco初探

从精准化测试看ASM在Android中的强势插入-JaCoco初探