Android OpenGL ES 2.0 图像渲染完整实现

43 阅读5分钟

SkyImageRender

一个基于 Android NDK 和 OpenGL ES 2.0 的高性能图像渲染项目,专注于实现完整的图像渲染管线。

项目地址:github.com/zhiwei-wu/S…

项目概述

SkyImageRender 是一个 Android 原生图像渲染应用,通过 JNI 调用 C++ 代码,使用 OpenGL ES 2.0 实现高效的图像渲染。项目展示了从图像数据加载到屏幕显示的完整渲染管线流程。

技术架构

整体架构

┌─────────────────┐    JNI     ┌─────────────────┐
│   Kotlin/Java   │ ◄────────► │      C++        │
│   (UI Layer)    │            │ (Render Layer)  │
└─────────────────┘            └─────────────────┘
         │                              │
         ▼                              ▼
┌─────────────────┐            ┌─────────────────┐
│   SurfaceView   │            │   OpenGL ES     │
│   (Display)     │            │   (Graphics)    │
└─────────────────┘            └─────────────────┘

核心组件

  • MainActivity.kt: Android 主界面,负责 Surface 管理和用户交互
  • NativeRenderer.kt: JNI 接口层,连接 Java 和 C++ 代码
  • RenderContext (C++): 核心渲染上下文,管理完整的渲染管线

渲染管线详解

1. 初始化阶段 (Initialization Pipeline)

EGL 上下文初始化
void RenderContext::initEGL() {
    // 1. 获取 EGL Display
    display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    eglInitialize(display_, nullptr, nullptr);
    
    // 2. 配置 EGL 属性
    const EGLint configAttribs[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_BLUE_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_RED_SIZE, 8,
        EGL_DEPTH_SIZE, 0,
        EGL_NONE
    };
    
    // 3. 创建 Surface 和 Context
    surface_ = eglCreateWindowSurface(display_, config, window_, nullptr);
    context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, contextAttribs);
    
    // 4. 绑定上下文
    eglMakeCurrent(display_, surface_, surface_, context_);
}
Shader 程序创建
// 顶点着色器 - 处理顶点变换
const char* VERTEX_SHADER_SOURCE =
    "attribute vec4 aPosition;\n"
    "attribute vec2 aTexCoord;\n"
    "varying vec2 vTexCoord;\n"
    "uniform mat4 uTextureMatrix;\n"
    "uniform mat4 uPositionMatrix;\n"
    "void main() {\n"
    "   gl_Position = uPositionMatrix * aPosition;\n"
    "   vTexCoord = (uTextureMatrix * vec4(aTexCoord, 0.0, 1.0)).xy;\n"
    "}\n";

// 片段着色器 - 处理像素着色
const char* FRAGMENT_SHADER_SOURCE =
    "precision mediump float;\n"
    "varying vec2 vTexCoord;\n"
    "uniform sampler2D uTexture;\n"
    "void main() {\n"
    "   gl_FragColor = texture2D(uTexture, vTexCoord);\n"
    "}\n";

2. 资源管理阶段 (Resource Management)

纹理创建与管理
GLuint RenderContext::createTexture(const uint8_t *data, int width, int height) {
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    
    // 设置纹理参数
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    // 上传纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
                 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    return texture;
}
顶点缓冲对象 (VBO) 管理
// 顶点数据(位置 + 纹理坐标)
const GLfloat vertices[] = {
    // 位置          // 纹理坐标
    -1.0f,  1.0f,   0.0f, 1.0f,  // 左上
    -1.0f, -1.0f,   0.0f, 0.0f,  // 左下
     1.0f, -1.0f,   1.0f, 0.0f,  // 右下
     1.0f,  1.0f,   1.0f, 1.0f   // 右上
};

const GLushort indices[] = {0, 1, 2, 0, 2, 3}; // 索引数据

3. 渲染管线 (Rendering Pipeline)

几何变换阶段
// 1. 计算宽高比和缩放比例
float bitmapAspectRatio = bitmapWidth / bitmapHeight;
float screenAspectRatio = screenWidth / screenHeight;
float scaleRatio = bitmapAspectRatio > screenAspectRatio
    ? screenWidth / bitmapWidth 
    : screenHeight / bitmapHeight;

// 2. 构建位置变换矩阵
float sx = (bitmapWidth * scaleRatio) / screenWidth;
float sy = (bitmapHeight * scaleRatio) / screenHeight * 1.05f;

float pMatrix[16] = {
    sx, 0, 0, 0,    // 第一列
    0, sy, 0, 0,    // 第二列
    0, 0, 1, 0,     // 第三列
    0, 0, 0, 1      // 第四列
};

// 3. 纹理翻转矩阵(处理 Android 坐标系差异)
float flipYMatrix[16] = {
    1.0f,  0.0f, 0.0f, 0.0f,
    0.0f, -1.0f, 0.0f, 0.0f,
    0.0f,  0.0f, 1.0f, 0.0f,
    0.0f,  1.0f, 0.0f, 1.0f
};
渲染执行阶段
void RenderContext::renderFrame(...) {
    // 1. 设置顶点属性
    GLint posLoc = glGetAttribLocation(program_, "aPosition");
    GLint texLoc = glGetAttribLocation(program_, "aTexCoord");
    
    glEnableVertexAttribArray(posLoc);
    glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GL_FLOAT), (void*)0);
    
    glEnableVertexAttribArray(texLoc);
    glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GL_FLOAT), 
                         (void*)(2 * sizeof(GL_FLOAT)));
    
    // 2. 设置 Uniform 变量
    glUniformMatrix4fv(positionMatrix, 1, GL_FALSE, pMatrix);
    glUniformMatrix4fv(textureMatrix, 1, GL_FALSE, flipYMatrix);
    
    // 3. 清除缓冲区
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    // 4. 绑定纹理并绘制
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture_);
    glUniform1i(glGetUniformLocation(program_, "uTexture"), 0);
    
    // 5. 执行绘制调用
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
    
    // 6. 交换缓冲区
    eglSwapBuffers(display_, surface_);
}

4. 渲染管线流程图

图像数据 (RGBA)
       │
       ▼
┌─────────────┐
│ 纹理上传     │ ◄── glTexImage2D()
│ (GPU Memory)│
└─────────────┘
       │
       ▼
┌─────────────┐
│ 顶点处理     │ ◄── Vertex Shader
│ (变换矩阵)   │     • 位置变换
└─────────────┘     • 纹理坐标变换
       │
       ▼
┌─────────────┐
│ 图元装配     │ ◄── 三角形组装
│ (Primitive)  │
└─────────────┘
       │
       ▼
┌─────────────┐
│ 光栅化      │ ◄── 像素生成
│ (Rasterize) │
└─────────────┘
       │
       ▼
┌─────────────┐
│ 片段处理     │ ◄── Fragment Shader
│ (像素着色)   │     • 纹理采样
└─────────────┘     • 颜色计算
       │
       ▼
┌─────────────┐
│ 帧缓冲输出   │ ◄── eglSwapBuffers()
│ (屏幕显示)   │
└─────────────┘

关键特性

1. 高性能渲染

  • 零拷贝纹理上传: 直接从 Java ByteArray 到 GPU 内存
  • 硬件加速: 完全基于 GPU 的并行处理
  • 优化的顶点数据: 紧凑的顶点缓冲布局

2. 自适应显示

  • 宽高比保持: 自动计算缩放比例,保持图像不变形
  • 屏幕适配: 支持不同分辨率和屏幕尺寸
  • 坐标系转换: 处理 Android 和 OpenGL 坐标系差异

3. 内存管理

  • 资源生命周期: 完整的 EGL 上下文和 OpenGL 资源管理
  • 异常处理: 完善的错误检查和资源清理机制

项目结构

SkyImageRender/
├── app/
│   ├── src/main/
│   │   ├── java/imt/skyimagerender/
│   │   │   ├── MainActivity.kt           # 主界面活动
│   │   │   └── renderer/
│   │   │       └── NativeRenderer.kt     # JNI 接口
│   │   ├── cpp/
│   │   │   ├── renderer/
│   │   │   │   ├── renderer.h           # 渲染器头文件
│   │   │   │   └── renderer.cpp         # 渲染器实现
│   │   │   ├── utils/
│   │   │   │   └── logger.h             # 日志工具
│   │   │   ├── renderer_jni.cpp         # JNI 入口
│   │   │   ├── renderer_jni.h           # JNI 头文件
│   │   │   └── CMakeLists.txt           # CMake 构建配置
│   │   └── res/
│   │       ├── drawable/                # 图像资源
│   │       └── layout/                  # 布局文件
│   └── build.gradle.kts                 # 应用构建配置
├── build.gradle.kts                     # 项目构建配置
└── settings.gradle.kts                  # 项目设置

构建要求

  • Android SDK: API Level 30+
  • NDK: 支持 armeabi-v7a, arm64-v8a
  • CMake: 3.22.1+
  • Kotlin: 1.8+
  • OpenGL ES: 2.0+

编译与运行

  1. 克隆项目

    git clone https://github.com/zhiwei-wu/SkyImageRender.git
    cd SkyImageRender
    
  2. 打开项目

    • 使用 Android Studio 打开项目
    • 确保安装了 NDK 和 CMake
  3. 构建项目

    ./gradlew assembleDebug
    
  4. 运行应用

    • 连接 Android 设备或启动模拟器
    • 点击 Run 按钮或使用 ./gradlew installDebug

使用说明

  1. 启动应用: 应用启动后会自动加载并显示第一张图片
  2. 切换图片: 点击 "Change Picture" 按钮可以在两张预设图片间切换
  3. 自适应显示: 图片会自动适配屏幕尺寸,保持原始宽高比

技术亮点

1. 完整的 OpenGL ES 渲染管线

  • 从 EGL 初始化到最终屏幕输出的完整流程
  • 标准的顶点着色器和片段着色器实现
  • 高效的纹理管理和顶点缓冲管理

2. 跨平台架构设计

  • 清晰的 Java/Kotlin 与 C++ 分层
  • 标准的 JNI 接口设计
  • 可扩展的渲染器架构

3. 性能优化

  • GPU 硬件加速渲染
  • 最小化 CPU-GPU 数据传输
  • 高效的矩阵变换计算

扩展可能

  • 多纹理支持: 扩展为支持多个纹理单元
  • 后处理效果: 添加滤镜、色彩调整等后处理管线
  • 3D 渲染: 扩展为支持 3D 模型渲染
  • 视频渲染: 支持视频帧的实时渲染

许可证

本项目仅供学习和研究使用。


本项目展示了 Android 平台上使用 OpenGL ES 进行高性能图像渲染的完整实现,是学习移动端图形编程的优秀参考。