Android 平台 OpenGL 环境搭建的两种方法及示例


在 Android 平台有两种方法可以构建 OpenGL 环境:通过系统自带的 GLSurfaceView 组件和通过 EGL 。GLSurfaceView 内部自带了 OpenGL 的渲染线程,通过 EGL 需要自己创建渲染线程。

Android 平台通过 OpenGL 渲染图像的本质是通过一系列的 GPU 指令将绘制的图形或者获得的图像帧(本地/网络视频解码或摄像头采集)渲染到设备屏幕上的过程,这个屏幕在系统中被抽象为 Surface ,而图形和图像则被抽象为 Texture (纹理),纹理通过顶点和片段着色器映射到 Surface 上。顶点着色器可以简单理解为确定每个点在屏幕上的坐标,而片段着色器可以简单理解为确定每个点的颜色,即一个计算位置,一个计算颜色。OpenGL 的编程模式为线性编程,即上一段程序的输出是下一段程序的输入。

以绘制三角形为例:

GLSurfaceView

在 Android 的 OpenGL 环境中,用纹理 ID 来标识纹理 ,它实际上指向的是 GPU 中分配的一块内存区域,用于存储纹理数据, SurfaceTexture 将视频帧数据存储在这个内存区域中。

初始设置

// 创建一个OpenGL ES 2.0的上下文
setEGLContextClientVersion(2);
// 仅在绘制数据发生变化时才进行绘制
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
// 设置渲染器
setRenderer(...);

创建 Renderer

public class MyGLRenderer implements GLSurfaceView.Renderer {
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // 设置清除屏幕所用的颜色
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        // 清除屏幕
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        // 设置视口
        GLES20.glViewport(0, 0, width, height);
    }
}

编写着色器

private final String vertexShaderCode =
    "attribute vec4 vPosition;" +
    "void main() {" +
    "  gl_Position = vPosition;" +
    "}";

private final String fragmentShaderCode =
    "precision mediump float;" +
    "uniform vec4 vColor;" +
    "void main() {" +
    "  gl_FragColor = vColor;" +
    "}";

编译着色器

public static int loadShader(int type, String shaderCode){
    // 创建一个着色器类型(GLES20.GL_VERTEX_SHADER 或 GLES20.GL_FRAGMENT_SHADER)
    int shader = GLES20.glCreateShader(type);

    // 将源码添加到着色器并编译它
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
}

链接着色器

int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

int mProgram = GLES20.glCreateProgram();         // 创建一个空的OpenGL程序
GLES20.glAttachShader(mProgram, vertexShader);   // 将顶点着色器添加到程序中
GLES20.glAttachShader(mProgram, fragmentShader); // 将片段着色器添加到程序中
GLES20.glLinkProgram(mProgram);                  // 创建可执行的OpenGL程序

定义图形数据

static float triangleCoords[] = {   // 按逆时针方向顺序
    0.0f,  0.622008459f, 0.0f,      // top
   -0.5f, -0.311004243f, 0.0f,      // bottom left
    0.5f, -0.311004243f, 0.0f       // bottom right
};

private final FloatBuffer vertexBuffer;

// 设置颜色
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

public Triangle() {
    // 初始化顶点字节缓冲区
    ByteBuffer bb = ByteBuffer.allocateDirect(
        // (每个浮点数占用4个字节)
        triangleCoords.length * 4);
    bb.order(ByteOrder.nativeOrder());
    vertexBuffer = bb.asFloatBuffer();
    vertexBuffer.put(triangleCoords);
    vertexBuffer.position(0);
}

绘制图形

public void draw() {
    // 将程序添加到OpenGL环境
    GLES20.glUseProgram(mProgram);

    // 获取顶点着色器的位置
    int positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // 启用顶点着色器的句柄
    GLES20.glEnableVertexAttribArray(positionHandle);

    // 准备三角形的坐标数据
    GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
                                 GLES20.GL_FLOAT, false,
                                 vertexStride, vertexBuffer);

    // 获取片段着色器的颜色句柄
    int colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // 设置绘制三角形的颜色
    GLES20.glUniform4fv(colorHandle, 1, color, 0);

    // 绘制三角形
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // 禁用顶点数组
    GLES20.glDisableVertexAttribArray(positionHandle);
}

EGL

EGL (Embedded-System Graphics Library) 是一个接口层,连接了 OpenGL ES 等图形库和显示设备(Surface),GLSurfaceView 就是对 EGL 的封装。它有几个关键概念:

EGLDisplay:表示与本地窗口系统的连接。

EGLConfig:描述绘图表面的格式和类型的配置,包括颜色深度、缓冲区类型等。

EGLContext:表示OpenGL ES等图形库的渲染上下文。

EGLSurface:表示一个可以渲染的表面,例如窗口表面、Pixmap表面或Pbuffer表面。

它们的关系可以简单理解为 EGLSurface 和 EGLContext 通过 EGLDisplay 连接。

创建 EGL 显示和初始化

EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
int[] version = new int[2];
EGL14.eglInitialize(eglDisplay, version, 0, version, 1);

配置 EGL

int[] configAttribs = {
    EGL14.EGL_RED_SIZE, 8,
    EGL14.EGL_GREEN_SIZE, 8,
    EGL14.EGL_BLUE_SIZE, 8,
    EGL14.EGL_ALPHA_SIZE, 8,
    EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
    EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
EGL14.eglChooseConfig(eglDisplay, configAttribs, 0, configs, 0, configs.length, numConfigs, 0);
EGLConfig eglConfig = configs[0];

创建 EGL 上下文

int[] contextAttribs = {
    EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
    EGL14.EGL_NONE
};
EGLContext eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT, contextAttribs, 0);

创建 EGL Surface

Surface surface = ...; // 获取或创建一个 Android Surface
int[] surfaceAttribs = {
    EGL14.EGL_NONE
};
// 创建一个 EGLSurface,关联到一个 Android Surface。
EGLSurface eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface, surfaceAttribs, 0);

编写着色器

编译和链接着色器

定义图形数据

均同上

连接 EGL 上下文和显示设备

// 绑定 EGL 上下文和 Surface
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);

// 获取属性位置
int positionHandle = GLES20.glGetAttribLocation(program, "vPosition");
int colorHandle = GLES20.glGetUniformLocation(program, "vColor");

// 启用顶点数组
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer);

// 设置颜色
GLES20.glUniform4fv(colorHandle, 1, color, 0);

// 清屏并绘制三角形
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);

// 交换显示缓冲区。GLSurfaceView 自动处理了缓冲区的交换。在渲染循环的每一帧结束时,它会调用 EGL 函数来交换前后缓冲区。这些操作是通过 GLSurfaceView
// 的内部类 GLThread 来完成的。
EGL14.eglSwapBuffers(eglDisplay, eglSurface);

// 禁用顶点数组
GLES20.glDisableVertexAttribArray(positionHandle);

释放 EGL 和 OpenGL 资源

GLES20.glDeleteProgram(program);
GLES20.glDeleteShader(vertexShader);
GLES20.glDeleteShader(fragmentShader);
EGL14.eglDestroySurface(eglDisplay, eglSurface);
EGL14.eglDestroyContext(eglDisplay, eglContext);
EGL14.eglTerminate(eglDisplay);

使用 GLSurfaceView 和 EGL 渲染本地视频帧的 Demo 地址:

https://github.com/cmder/Demos/tree/main/Mp4Player

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值