Android中的OpenGL使用初探

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

查看系统支持的OpenGL版本

    /**
     * 获取支持的OpenGL版本,前16位代表主要版本,后16位代表次要版本,如0x30002,表示OpenGL 3.2
     */
    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()中的清屏操作,基本就完整了。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值