«

Android中的OpenGL怎么配置使用

时间:2024-8-6 09:04     作者:韩俊     分类: Android


这篇文章主要介绍“Android中的OpenGL怎么配置使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android中的OpenGL怎么配置使用”文章能帮助大家解决问题。

介绍

Android 可通过开放图形库 OpenGL ES 来支持高性能 2D 和 3D 图形,OpenGL 是一种跨平台的图形 API,用于为 3D 图形处理硬件指定标准的软件接口。OpenGL ES 是 OpenGL 规范的一种形式,适用于嵌入式设备,Android 支持多版 OpenGL ES API,各版本情况如下:

    OpenGL ES 1.0 和 1.1 - 此 API 规范受 Android 1.0 及更高版本的支持。

    OpenGL ES 2.0 - 此 API 规范受 Android 2.2(API 级别 8)及更高版本的支持。

    OpenGL ES 3.0 - 此 API 规范受 Android 4.3(API 级别 18)及更高版本的支持。

    OpenGL ES 3.1 - 此 API 规范受 Android 5.0(API 级别 21)及更高版本的支持。

在 AndroidManifest.xml 中声明 OpenGL ES 的版本

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

GLSurfaceView

GLSurfaceView
SurfaceView
OpenGL
实现,从 Android 1.5 开始加入,在
SurfaceView
的基础上添加了 EGL 的管理以及自带的渲染线程
GLThread
,其主要功能如下:

    管理一个

    Surface
    ,这个
    Surface
    是一块特殊的内存,可以组合到 Android 的
    View
    系统中,也就是可以和
    View
    一起使用。

    管理一个

    EGL
    ,这个
    EGL
    可以让
    OpenGL
    渲染到这个
    Surface
    上,
    EGL
    是 Android 与
    OpenGL
    之间的桥梁。

    支持用户自定义渲染器

    Renderer
    对象。

    使用专用线程上进行渲染。

    支持按需渲染(on-demand)和连续渲染(continuous )。

    Optionally wraps, traces, and/or error-checks the renderer's OpenGL calls.

EGL 窗口、OpenGL 表面、GL 表面含义都相同。

GLSurfaceView
常用设置如下:

EGL配置

EGLConfigChooser
的默认实现是
SimpleEGLConfigChooser
,默认情况下
GLSurfaceView
将选择深度缓冲深度至少为 16 位的
PixelFormat.RGB_888
格式的
surface
,默认的
EGLConfigChooser
实现是
SimpleEGLConfigChooser
,具体如下:

private class SimpleEGLConfigChooser extends ComponentSizeChooser {
    public SimpleEGLConfigChooser(boolean withDepthBuffer) {
        super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0);
    }
}

可以通过如下方式修改

EGLConfig
的默认行为:

// 设置默认EGLConfig的深度缓冲,true则为16位的深度缓冲
setEGLConfigChooser(boolean needDepth)
// 指定自定义的EGLConfigChooser
setEGLConfigChooser(android.opengl.GLSurfaceView.EGLConfigChooser configChooser)
// 指定各个分量的值
public void setEGLConfigChooser(int redSize, int greenSize, int blueSize,
            int alphaSize, int depthSize, int stencilSize)

渲染

通过

setRenderer
设置渲染器并启动渲染线程
GLThread
,渲染模式有两种如下:

    RENDERMODE_CONTINUOUSLY
    :适合重复渲染的场景,默认的渲染模式。

    RENDERMODE_WHEN_DIRTY
    :只有
    Surface
    被创建后渲染一次,只调用了
    requestRender
    才会继续渲染。

渲染模式可以通过

setRenderMode
来进行设置,具体如下:

// 设置渲染器
public void setRenderer(Renderer renderer)
// 设置渲染模式,仅在setRenderer之后调用生效
public void setRenderMode(int renderMode)

setDebugFlags和setGLWrapper

setDebugFlags
用于设置 Debug 标记,方便调试跟踪代码,可选值为
DEBUG_CHECK_GL_ERROR
DEBUG_LOG_GL_CALLS
setGLWrapper
可以通过自定义
GLWrapper
来委托 GL 接口来添加一些自定义行为,具体如下:

// DEBUG_CHECK_GL_ERROR:每次GL调用都会检查,如果出现glError则会抛出异常
// DEBUG_LOG_GL_CALLS:以TAG为GLSurfaceView将日志记录在verbose级别的日志中
setDebugFlags(int debugFlags)
// 用于调试跟踪代码,可自定义GLWrapper包装GL接口并返回GL接口,可在
setGLWrapper(android.opengl.GLSurfaceView.GLWrapper glWrapper)

渲染器Renderer

这部分在前面提到过,这里单独说一下,要想在 GL 表面上执行渲染操作,需要实现

Renderer
对象完成实际渲染操作,通过如下方式给
GLSurfaceView
设置渲染器对象
Renderer
以及制定渲染模式,如下:

// 给GLSurfaceView设置渲染器对象Renderer
public void setRenderer(Renderer renderer)
// 设置渲染模式,仅在setRenderer之后调用生效
public void setRenderMode(int renderMode)

设置渲染器

Renderer
的时候,同时会创建独立线程
GLThread
并开启该线程,这个线程就是独立于 UI 线程的渲染线程。

这里就涉及到两个线程 UI 线程和渲染线程,自然涉及到线程之间的通信,可以使用

volatile
synchronized
等实现线程之间的通信。

如果是在 UI 线程中调用渲染线程中的操作,可以使用

GLSurfaceView
queueEvent
方法来将该操作执行到渲染线程中,一般需要自定义
GLSurfaceView
的时候会用到,同样如果在渲染线程可以通过
runOnUiThread
来将与 UI 相关的操作执行到 UI 线程。

下面看下渲染器

Reander
的基本实现:

public class GLES20Renderer implements Renderer {
    private static final String TAG = GLES20Renderer.class.getSimpleName();
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        Log.i(TAG, "onSurfaceCreated");
        GLES20.glClearColor(0.0f, 0.0f, 1.0f, 1);
    }
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        Log.i(TAG, "onSurfaceChanged");
        GLES20.glViewport(0, 0, width, height);
    }
    public void onDrawFrame(GL10 gl) {
        Log.i(TAG, "onDrawFrame");
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    }
}

坐标映射

先来了解下 OpenGL 的世界坐标系和与之对应的 Android 上的纹理坐标系,如下图所示:

在 Android 中使用 OpenGL 就要进行相应坐标的转换,下面看下 OpenGL 坐标系在 Android 屏幕中的映射关系,如下图所示:

如上图所示,左侧是默认的 OpenGL 坐标系,右侧是 OpenGL 坐标系在 Android 屏幕上的映射,可以明显看到图中的三角形是变形了的,为了保证图像比例就需要应用 OpenGL 投影模式和相机视图来转换坐标,这就涉及到投影矩阵和视图矩阵,这部分内容会在后续的文章中介绍。

绘制三角形

通过以上内容,Android OpenGL 算是初步入门了,按照习惯来个小案例,这里使用 OpenGL 绘制一个三角形,如下

Triangle
是三角形数据封装及着色器的的使用,后续渲染直接调用
draw
方法进行渲染绘制,如下:

// Triangle
class Triangle(context: Context) {
    companion object {
        // 坐标数组中每个顶点的坐标数
        private const val COORDINATE_PER_VERTEX = 3
    }
    private var programHandle: Int = 0
    private var positionHandle: Int = 0
    private var colorHandler: Int = 0
    private var vPMatrixHandle: Int = 0
    private var vertexStride = COORDINATE_PER_VERTEX * 4
    // 三角形的三条边
    private var triangleCoordinate = floatArrayOf(     // 逆时针的顺序的三条边
        0.0f, 0.5f, 0.0f,      // top
        -0.5f, -0.5f, 0.0f,    // bottom left
        0.5f, -0.5f, 0.0f      // bottom right
    )
    // 颜色数组
    private val color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f)
    private var vertexBuffer: FloatBuffer =
        // (number of coordinate values * 4 bytes per float)
        ByteBuffer.allocateDirect(triangleCoordinate.size * 4).run {
            // ByteBuffer使用本机字节序
            this.order(ByteOrder.nativeOrder())
            // ByteBuffer to FloatBuffer
            this.asFloatBuffer().apply {
                put(triangleCoordinate)
                position(0)
            }
        }
    init {
        // read shader sourceCode
        val vertexShaderCode = GLUtil.readShaderSourceCodeFromRaw(context, R.raw.vertex_shader_triangle_default)
        val fragmentShaderCode =
            GLUtil.readShaderSourceCodeFromRaw(context, R.raw.fragment_shader_triangle)
        if (vertexShaderCode.isNullOrEmpty() || fragmentShaderCode.isNullOrEmpty()) {
            throw RuntimeException("vertexShaderCode or fragmentShaderCode is null or empty")
        }
        // compile shader
        val vertexShaderHandler = GLUtil.compileShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShaderHandler =
            GLUtil.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
        // create and link program
        programHandle = GLUtil.createAndLinkProgram(vertexShaderHandler, fragmentShaderHandler)
    }
    /**
    *  绘制方法
    */
    fun draw(mvpMatrix: FloatArray) {
        GLES20.glUseProgram(programHandle)
        // 获取attribute变量的地址索引
        // get handle to vertex shader's vPosition member
        positionHandle = GLES20.glGetAttribLocation(programHandle, "vPosition").also {
            // enable vertex attribute,默认是disable
            GLES20.glEnableVertexAttribArray(it)
            GLES20.glVertexAttribPointer(
                it, // 着色器中第一个顶点属性的位置
                COORDINATE_PER_VERTEX,
                GLES20.GL_FLOAT,
                false,
                vertexStride, // 连续的顶点属性组之间的间隔
                vertexBuffer
            )
        }
        // get handle to fragment shader's vColor member
        colorHandler = GLES20.glGetUniformLocation(programHandle, "vColor").also {
            GLES20.glUniform4fv(it, 1, color, 0)
        }
        // draw triangle
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, triangleCoordinate.size / COORDINATE_PER_VERTEX)
        GLES20.glDisableVertexAttribArray(positionHandle)
    }
}

渲染器实现如下:

// 渲染器实现
class MRenderer(private var context: Context) : GLSurfaceView.Renderer {
    private val tag = MRenderer::class.java.simpleName
    private lateinit var triangle: Triangle
    private val vPMatrix = FloatArray(16) // 模型视图投影矩阵
    private val projectionMatrix = FloatArray(16)
    private val viewMatrix = FloatArray(16)
    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        // 创建Surface时调用,在渲染开始时调用,用来创建渲染开始时需要的资源
        Log.d(tag, "onSurfaceCreated")
        triangle = Triangle(context)
    }
    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        // Surface改变大小时调用,设置视口
        Log.d(tag, "onSurfaceChanged")
        GLES20.glViewport(0, 0, width, height)
    }
    override fun onDrawFrame(gl: GL10?) {
        // 绘制当前frame,用于渲染处理具体的内容
        Log.d(tag, "onDrawFrame")
        triangle.draw(vPMatrix)
    }
}

上面都是基本的绘制操作,没啥好说的,其中着色器的使用流程会在后续文章中进行介绍,这里就不贴其他代码了,感兴趣的可以直接在文末查看源代码。

绘制效果

上面的绘制没有使用投影矩阵和相机视图来进行坐标转换,当横竖屏切换到时候会到导致变形,这个会在下篇文章中进行修正,看下上述代码绘制的效果图,如下图所示:

标签: android

热门推荐