0%

OpenGL

知识树

graph LR
    A[OpenGL] --> B(基础)
    A --> C(渲染管线)
    A --> D(核心概念)
    A --> E(常用功能)
    A --> F(工具与扩展)

    B --> B1[跨平台图形API]
    B --> B2[固定功能 vs 可编程管线]

    C --> C1[顶点数据输入]
    C --> C2[顶点着色器]
    C --> C3[图元组装]
    C --> C4[光栅化]
    C --> C5[片段着色器]
    C --> C6[测试与混合]
    C --> C7[输出]

    C1 --> C2 --> C3 --> C4 --> C5 --> C6 --> C7

    D --> D1[缓冲区]
    D --> D2[着色器]
    D --> D3[纹理]
    D --> D4[变换]
    D --> D5[状态机]

    D1 --> D1a[VBO]
    D1 --> D1b[VAO]
    D1 --> D1c[EBO]
    D1 --> D1d[FBO]

    D2 --> D2a[顶点着色器]
    D2 --> D2b[片段着色器]
    D2 --> D2c[几何着色器]

    D3 --> D3a[2D纹理]
    D3 --> D3b[3D纹理]
    D3 --> D3c[立方体贴图]

    D4 --> D4a[模型变换]
    D4 --> D4b[视图变换]
    D4 --> D4c[投影变换]

    E --> E1[绘制调用]
    E --> E2[清屏与缓冲区]
    E --> E3[混合与深度]

    E1 --> E1a[glDrawArrays]
    E1 --> E1b[glDrawElements]

    F --> F1[GLFW/GLUT]
    F --> F2[GLEW]
    F --> F3[调试]

详解

1. OpenGL基础

1.1 定义与特点

  • 功能作用: OpenGL(Open Graphics Library)是一个跨平台的图形编程接口,用于创建高性能的2D和3D图形应用程序。它通过与GPU交互,提供硬件加速的渲染能力,开发者可以直接操作底层图形硬件。
  • 使用场景:
    • 游戏开发(如Unity、Unreal Engine底层依赖OpenGL)。
    • CAD软件(如AutoCAD的3D建模)。
    • 虚拟现实(VR)和增强现实(AR)应用。
    • 科学可视化(如分子结构渲染)。
  • 底层原理: OpenGL通过驱动程序与GPU通信,开发者调用API函数(如glDrawArrays),这些命令被翻译为GPU可执行的指令。它是一个状态机,函数调用会修改全局状态(如启用深度测试、绑定缓冲区),状态会影响后续操作。
  • 示例代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #include <GLFW/glfw3.h>
    int main() {
    // 初始化GLFW
    glfwInit();
    // 创建窗口
    GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Window", NULL, NULL);
    if (!window) {
    glfwTerminate();
    return -1;
    }
    // 设置当前上下文
    glfwMakeContextCurrent(window);
    // 主循环
    while (!glfwWindowShouldClose(window)) {
    // 清空颜色缓冲区
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    // 交换缓冲区
    glfwSwapBuffers(window);
    // 处理事件
    glfwPollEvents();
    }
    // 清理并退出
    glfwTerminate();
    return 0;
    }
  • 注意事项:
    • 需要配合窗口管理库(如GLFW或GLUT)创建上下文,单独的OpenGL无法直接显示图形。
    • 确保显卡驱动支持目标OpenGL版本(如4.6),否则可能需要降级版本。
    • 检查返回值(如glfwCreateWindow)以避免初始化失败。

1.2 固定功能管线 vs 可编程管线

  • 功能作用:
    • 固定功能管线: 老版本(如1.x-2.x)提供预定义的功能(如光照、纹理映射),开发者只需调用函数,无需编写着色器。
    • 可编程管线: 现代版本(如3.x-4.x)允许开发者通过GLSL(OpenGL Shading Language)编写着色器,全面控制渲染过程。
  • 使用场景:
    • 固定功能管线: 适用于简单应用(如早期教学示例、小型2D游戏)。
    • 可编程管线: 适用于现代复杂图形应用(如实时光影效果、物理渲染)。
  • 底层原理:
    • 固定功能管线依赖GPU内置的算法(如Phong光照模型),开发者无法修改其实现。
    • 可编程管线将渲染任务交给开发者编写的GLSL程序,这些程序在GPU上并行执行,提供更高灵活性。
  • 示例代码 (固定功能管线绘制三角形):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include <GL/glut.h>
    void display() {
    glClear(GL_COLOR_BUFFER_BIT);
    glBegin(GL_TRIANGLES); // 开始绘制三角形
    glColor3f(1.0f, 0.0f, 0.0f); // 红色
    glVertex3f(0.0f, 1.0f, 0.0f);
    glColor3f(0.0f, 1.0f, 0.0f); // 绿色
    glVertex3f(-1.0f, -1.0f, 0.0f);
    glColor3f(0.0f, 0.0f, 1.0f); // 蓝色
    glVertex3f(1.0f, -1.0f, 0.0f);
    glEnd(); // 结束绘制
    glFlush();
    }
    int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutCreateWindow("Fixed Pipeline Triangle");
    glutDisplayFunc(display);
    glutMainLoop();
    return 0;
    }
  • 示例代码 (可编程管线基础框架):
    1
    // 见后续缓冲区和着色器部分
  • 注意事项:
    • 固定功能管线在现代OpenGL(3.0+)中已被废弃,建议学习可编程管线。
    • 可编程管线需要手动管理着色器和缓冲区,入门门槛较高。

2. 渲染管线(Rendering Pipeline)

2.1 顶点数据输入 (Vertex Data)

  • 功能作用: 定义几何体的顶点属性(如位置、颜色、法线、纹理坐标),作为渲染的起点。
  • 使用场景: 绘制任何图形对象(如三角形、立方体、复杂网格模型)。
  • 底层原理: 顶点数据最初存储在CPU内存,通过缓冲区对象(VBO)传输到GPU显存,供后续管线阶段使用。
  • 示例代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <GLFW/glfw3.h>
    #include <GL/glew.h>
    float vertices[] = {
    // 位置 // 颜色
    -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下
    0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 右下
    0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 顶部
    };
    unsigned int VBO;
    void setup() {
    glGenBuffers(1, &VBO); // 生成缓冲区对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定到GL_ARRAY_BUFFER目标
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 传输数据
    }
  • 注意事项:
    • 数据格式(如位置+颜色)需与着色器输入匹配。
    • GL_STATIC_DRAW表示数据不频繁修改,GL_DYNAMIC_DRAW适用于动态更新。
    • 绑定后需解绑(glBindBuffer(GL_ARRAY_BUFFER, 0))以避免误操作。

2.2 顶点着色器 (Vertex Shader)

  • 功能作用: 处理每个顶点,执行位置变换、光照计算、传递属性给后续阶段。
  • 使用场景: 实现模型动画(如骨骼动画)、视角变换(如摄像机移动)。
  • 底层原理: GPU并行执行GLSL代码,每个顶点独立处理,输出变换后的顶点数据(如gl_Position)。
  • 示例代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #version 330 core
    layout(location = 0) in vec3 aPos; // 顶点位置
    layout(location = 1) in vec3 aColor; // 顶点颜色
    out vec3 vertexColor; // 传递给片段着色器
    uniform mat4 MVP; // 模型-视图-投影矩阵
    void main() {
    gl_Position = MVP * vec4(aPos, 1.0); // 变换位置
    vertexColor = aColor; // 传递颜色
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    unsigned int shaderProgram;
    const char* vertexShaderSource = /* GLSL代码 */;
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glLinkProgram(shaderProgram);
    glUseProgram(shaderProgram);
  • 注意事项:
    • gl_Position是内置输出变量,必须赋值,表示裁剪空间坐标。
    • 使用uniform传递外部数据(如矩阵)。
    • 检查编译和链接状态(glGetShaderivglGetProgramiv)。

2.3 图元组装 (Primitive Assembly)

  • 功能作用: 将顶点连接成图元(如点、线、三角形),为光栅化准备数据。
  • 使用场景: 定义绘制方式(如三角形网格、线框视图)。
  • 底层原理: GPU根据绘制模式(GL_TRIANGLESGL_LINES)和顶点顺序组装图元,确定正面/背面。
  • 示例代码:
    1
    2
    glBindVertexArray(VAO); // 假设VAO已设置
    glDrawArrays(GL_TRIANGLES, 0, 3); // 绘制一个三角形
  • 注意事项:
    • 顶点顺序影响面剔除(默认逆时针为正面)。
    • 图元类型需与顶点数据匹配(如3的倍数顶点用于GL_TRIANGLES)。

2.4 光栅化 (Rasterization)

  • 功能作用: 将图元转换为片段(像素候选),为片段着色器提供输入。
  • 使用场景: 任何可见图形的生成。
  • 底层原理: GPU插值顶点属性(如颜色、纹理坐标),将图元分解为离散的片段,每个片段对应屏幕上的潜在像素。
  • 示例代码: 无需显式调用,由硬件自动完成。
  • 注意事项:
    • 不可直接控制,依赖硬件实现。
    • 视口设置(glViewport)影响光栅化范围。

2.5 片段着色器 (Fragment Shader)

  • 功能作用: 为每个片段计算最终颜色,可处理纹理、光照等效果。
  • 使用场景: 实现复杂材质(如金属反射)、阴影、后处理。
  • 底层原理: GPU并行执行GLSL代码,处理每个片段,输出颜色值到帧缓冲区。
  • 示例代码:
    1
    2
    3
    4
    5
    6
    7
    #version 330 core
    in vec3 vertexColor; // 从顶点着色器接收
    out vec4 FragColor; // 输出颜色
    uniform vec3 lightColor;
    void main() {
    FragColor = vec4(vertexColor * lightColor, 1.0); // 简单光照
    }
    1
    2
    3
    4
    5
    6
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    const char* fragmentShaderSource = /* GLSL代码 */;
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
  • 注意事项:
    • 输出变量需声明为out,通常命名为FragColor
    • 避免过多计算,片段着色器开销大。

2.6 测试与混合 (Testing and Blending)

  • 功能作用: 执行深度测试、模板测试和颜色混合,决定片段是否写入帧缓冲区。
  • 使用场景:
    • 深度测试: 遮挡剔除(如绘制不透明物体)。
    • 模板测试: 掩模效果(如轮廓线)。
    • 混合: 透明物体(如玻璃)。
  • 底层原理: GPU根据状态(如深度缓冲区值)丢弃或混合片段,混合公式由glBlendFunc定义。
  • 示例代码:
    1
    2
    3
    4
    5
    // 启用深度测试
    glEnable(GL_DEPTH_TEST);
    // 启用混合
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // 标准透明混合
  • 注意事项:
    • 混合时需先绘制不透明物体,再绘制透明物体(按深度排序)。
    • 深度缓冲区需清空(glClear(GL_DEPTH_BUFFER_BIT))。

2.7 输出 (Output)

  • 功能作用: 将渲染结果写入帧缓冲区,最终显示到屏幕。
  • 使用场景: 每帧渲染的最后步骤。
  • 底层原理: GPU将颜色缓冲区数据传输到显示设备,双缓冲机制(前后缓冲)避免闪烁。
  • 示例代码:
    1
    2
    glBindFramebuffer(GL_FRAMEBUFFER, 0); // 默认帧缓冲区
    glfwSwapBuffers(window); // 交换前后缓冲
  • 注意事项:
    • 双缓冲需显式交换(glfwSwapBuffers)。
    • 确保帧缓冲区完整。

3. 核心概念

3.1 缓冲区

3.1.1 VBO (Vertex Buffer Object)

  • 功能作用: 在GPU存储顶点数据(如位置、颜色),提高渲染效率。
  • 使用场景: 绘制复杂模型(如3D角色)。
  • 底层原理: 数据从CPU复制到GPU显存,避免每次绘制都传输。
  • 示例代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    float vertices[] = {
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f,
    0.0f, 0.5f, 0.0f
    };
    unsigned int VBO;
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
  • 注意事项:
    • 绑定后解绑以避免误操作。
    • 数据大小单位为字节。

3.1.2 VAO (Vertex Array Object)

  • 功能作用: 封装顶点数据的状态(如VBO绑定、属性指针),简化绘制调用。
  • 使用场景: 多对象渲染(如场景中多个模型)。
  • 底层原理: VAO记录顶点属性的布局和绑定状态,减少重复配置。
  • 示例代码:
    1
    2
    3
    4
    5
    6
    7
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glBindVertexArray(0); // 解绑
  • 注意事项:
    • 绘制时只需绑定VAO。
    • 属性索引(如0)需与着色器layout(location)一致。

3.1.3 EBO (Element Buffer Object)

  • 功能作用: 存储顶点索引,优化重复顶点的绘制。
  • 使用场景: 网格模型(如立方体)。
  • 底层原理: 使用索引引用VBO中的顶点,减少数据冗余。
  • 示例代码:
    1
    2
    3
    4
    5
    6
    7
    unsigned int indices[] = {0, 1, 2};
    unsigned int EBO;
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
  • 注意事项:
    • 索引类型(如GL_UNSIGNED_INT)需匹配数据。
    • EBO绑定到VAO中。

3.1.4 FBO (Framebuffer Object)

  • 功能作用: 实现离屏渲染,将结果存储到纹理而非屏幕。
  • 使用场景: 后处理(如模糊、HDR)。
  • 底层原理: FBO重定向渲染目标,包含颜色、深度等附件。
  • 示例代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    unsigned int FBO;
    glGenFramebuffers(1, &FBO);
    glBindFramebuffer(GL_FRAMEBUFFER, FBO);
    unsigned int texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
    printf("Framebuffer incomplete!\n");
    }
  • 注意事项:
    • 检查FBO完整性。
    • 解绑时恢复默认帧缓冲(glBindFramebuffer(GL_FRAMEBUFFER, 0))。

3.2 着色器

3.2.1 顶点着色器

  • 功能作用: 处理顶点变换和属性传递。
  • 使用场景: 动画、摄像机变换。
  • 底层原理: GPU并行执行。
  • 示例代码: 同2.2。
  • 注意事项: 确保输出gl_Position

3.2.2 片段着色器

  • 功能作用: 计算片段颜色。
  • 使用场景: 材质效果。
  • 底层原理: GPU并行执行。
  • 示例代码: 同2.5。
  • 注意事项: 输出变量需定义。

3.2.3 几何着色器 (Geometry Shader)

  • 功能作用: 在图元级别操作,可生成或修改图元。
  • 使用场景: 粒子系统、轮廓扩展。
  • 底层原理: 在顶点和片段阶段之间运行,处理整个图元。
  • 示例代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #version 330 core
    layout(triangles) in;
    layout(triangle_strip, max_vertices = 3) out;
    void main() {
    for (int i = 0; i < 3; i++) {
    gl_Position = gl_in[i].gl_Position;
    EmitVertex();
    }
    EndPrimitive();
    }
  • 注意事项:
    • 可选阶段,性能开销较大。

3.3 纹理

3.3.1 2D纹理

  • 功能作用: 为表面添加图像细节。
  • 使用场景: 贴图(如地形)。
  • 底层原理: 纹理数据存储在GPU,片段着色器采样。
  • 示例代码:
    1
    2
    3
    4
    5
    6
    unsigned int texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
  • 注意事项:
    • 设置过滤和环绕模式。

3.3.2 3D纹理

  • 功能作用: 存储三维纹理数据。
  • 使用场景: 体视化(如医学成像)。
  • 示例代码:
    1
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, width, height, depth, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
  • 注意事项:
    • 数据量大,需优化。

3.3.3 立方体贴图

  • 功能作用: 实现环境映射。
  • 使用场景: 天空盒、反射。
  • 示例代码:
    1
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
  • 注意事项:
    • 需要6张图。

3.4 变换

3.4.1 模型变换

  • 功能作用: 移动、旋转、缩放物体。
  • 使用场景: 物体动画。
  • 示例代码:
    1
    glm::mat4 model = glm::rotate(glm::mat4(1.0f), glm::radians(45.0f), glm::vec3(0.0f, 0.0f, 1.0f));
  • 注意事项: 使用GLM库。

3.4.2 视图变换

  • 功能作用: 定义摄像机视角。
  • 使用场景: 场景漫游。
  • 示例代码:
    1
    glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));

3.4.3 投影变换

  • 功能作用: 将3D场景投影到2D屏幕。
  • 使用场景: 透视/正交投影。
  • 示例代码:
    1
    glm::mat4 projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);

3.5 状态机

  • 功能作用: 管理OpenGL的全局状态。
  • 使用场景: 所有操作。
  • 示例代码:
    1
    2
    glEnable(GL_DEPTH_TEST);
    glDisable(GL_BLEND);
  • 注意事项: 状态需手动重置。

4. 常用功能

4.1 绘制调用

4.1.1 glDrawArrays

  • 功能作用: 顺序绘制顶点。
  • 使用场景: 简单几何体。
  • 示例代码:
    1
    2
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);
  • 注意事项: 不使用索引。

4.1.2 glDrawElements

  • 功能作用: 使用索引绘制。
  • 使用场景: 复杂网格。
  • 示例代码:
    1
    2
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);

4.2 清屏与缓冲区操作

  • 功能作用: 清空缓冲区,准备新帧。
  • 使用场景: 每帧开始。
  • 示例代码:
    1
    2
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  • 注意事项: 指定缓冲区类型。

4.3 混合与深度

  • 功能作用: 控制渲染效果。
  • 使用场景: 透明、遮挡。
  • 示例代码:
    1
    2
    3
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

5. 工具与扩展

5.1 GLFW/GLUT

  • 功能作用: 管理窗口和输入。
  • 使用场景: 创建OpenGL上下文。
  • 示例代码: 同1.1。

5.2 GLEW

  • 功能作用: 加载OpenGL扩展函数。
  • 使用场景: 现代OpenGL。
  • 示例代码:
    1
    glewInit();

5.3 调试

  • 功能作用: 检查错误。
  • 使用场景: 开发调试。
  • 示例代码:
    1
    2
    GLenum error = glGetError();
    if (error != GL_NO_ERROR) printf("Error: %d\n", error);

问题研究

Android 使用 OpenGL

方式1:使用GLSurfaceView

GLSurfaceView 是 Android 提供的封装类,内置了 EGL 管理和渲染线程,适合快速实现 OpenGL 渲染。

流程概述

  1. 创建并配置 GLSurfaceView
  2. 实现 GLSurfaceView.Renderer 接口,定义渲染逻辑。
  3. 编写顶点和片段着色器,加载顶点数据。
  4. 在 Activity 中管理生命周期。
  5. 新增:使用矩阵变换实现旋转,添加纹理映射渲染图片。

关键代码片段

步骤1:创建GLSurfaceView
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MainActivity extends Activity {
private GLSurfaceView glSurfaceView; // GLSurfaceView 对象,用于显示 OpenGL 渲染内容

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 创建 GLSurfaceView 实例
glSurfaceView = new GLSurfaceView(this);
// 设置 OpenGL ES 版本,这里使用 2.0
glSurfaceView.setEGLContextClientVersion(2);
// 设置渲染器,MyRenderer 包含具体的渲染逻辑
glSurfaceView.setRenderer(new MyRenderer(this));
// 将 GLSurfaceView 设置为 Activity 的内容视图
setContentView(glSurfaceView);
}

@Override
protected void onPause() {
super.onPause();
// 暂停 GLSurfaceView 的渲染线程,避免后台运行浪费资源
glSurfaceView.onPause();
}

@Override
protected void onResume() {
super.onResume();
// 恢复 GLSurfaceView 的渲染线程,继续渲染
glSurfaceView.onResume();
}
}
步骤2:实现Renderer(含矩阵变换和纹理映射)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyRenderer implements GLSurfaceView.Renderer {
private int program; // OpenGL 程序对象,链接顶点和片段着色器
private FloatBuffer vertexBuffer; // 存储顶点数据的缓冲区
private FloatBuffer textureBuffer; // 存储纹理坐标的缓冲区
private int textureId; // 纹理 ID
private final Context context; // 用于加载图片资源
private float[] mvpMatrix = new float[16]; // 模型-视图-投影矩阵
private float angle = 0.0f; // 旋转角度

// 定义矩形顶点坐标 (x, y, z),用于纹理映射
private final float[] rectCoords = {
-0.5f, 0.5f, 0.0f, // 左上
-0.5f, -0.5f, 0.0f, // 左下
0.5f, -0.5f, 0.0f, // 右下
0.5f, 0.5f, 0.0f // 右上
};

// 定义纹理坐标 (s, t)
private final float[] textureCoords = {
0.0f, 0.0f, // 左上
0.0f, 1.0f, // 左下
1.0f, 1.0f, // 右下
1.0f, 0.0f // 右上
};

public MyRenderer(Context context) {
this.context = context;
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 当渲染表面创建时调用,通常用于初始化 OpenGL 环境
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // 设置清屏颜色为黑色 (R, G, B, A)
initShaders(); // 初始化着色器程序
initBuffers(); // 初始化顶点和纹理缓冲区
initTexture(); // 初始化纹理
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 当表面大小改变时调用,例如屏幕旋转
GLES20.glViewport(0, 0, width, height); // 设置视口大小,匹配屏幕分辨率
// 设置正交投影矩阵
float aspectRatio = (float) width / height;
Matrix.setIdentityM(mvpMatrix, 0);
Matrix.orthoM(mvpMatrix, 0, -aspectRatio, aspectRatio, -1.0f, 1.0f, -1.0f, 1.0f);
}

@Override
public void onDrawFrame(GL10 gl) {
// 每帧渲染时调用,绘制图形内容
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); // 清除颜色缓冲区,使用预设的黑色填充
drawTexturedRect(); // 绘制带纹理的矩形

// 更新旋转角度,实现动画效果
angle += 1.0f;
if (angle >= 360.0f) angle = 0.0f;
}

private void initShaders() {
// 定义顶点着色器代码,包含矩阵变换
String vertexShaderCode =
"uniform mat4 uMVPMatrix;\n" + // 模型-视图-投影矩阵
"attribute vec4 aPosition;\n" + // 定义输入的顶点位置属性
"attribute vec2 aTexCoord;\n" + // 定义输入的纹理坐标属性
"varying vec2 vTexCoord;\n" + // 传递给片段着色器的纹理坐标
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" + // 应用矩阵变换
" vTexCoord = aTexCoord;\n" + // 传递纹理坐标
"}";

// 定义片段着色器代码,支持纹理映射
String fragmentShaderCode =
"precision mediump float;\n" + // 设置浮点精度为中等
"varying vec2 vTexCoord;\n" + // 从顶点着色器接收纹理坐标
"uniform sampler2D uTexture;\n" + // 纹理采样器
"void main() {\n" +
" gl_FragColor = texture2D(uTexture, vTexCoord);\n" + // 从纹理采样颜色
"}";

// 编译顶点着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
// 编译片段着色器
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
// 创建 OpenGL 程序对象
program = GLES20.glCreateProgram();
// 将顶点着色器附加到程序
GLES20.glAttachShader(program, vertexShader);
// 将片段着色器附加到程序
GLES20.glAttachShader(program, fragmentShader);
// 链接程序,生成可执行的着色器程序
GLES20.glLinkProgram(program);
}

private int loadShader(int type, String shaderCode) {
// 创建指定类型的着色器对象(顶点或片段)
int shader = GLES20.glCreateShader(type);
// 将着色器源代码上传到 GPU
GLES20.glShaderSource(shader, shaderCode);
// 编译着色器代码
GLES20.glCompileShader(shader);
return shader; // 返回编译后的着色器 ID
}

private void initBuffers() {
// 为顶点数据分配直接字节缓冲区,每个 float 占 4 字节
ByteBuffer bb = ByteBuffer.allocateDirect(rectCoords.length * 4);
// 设置字节顺序为本地硬件的顺序
bb.order(ByteOrder.nativeOrder());
// 将字节缓冲区转换为浮点缓冲区
vertexBuffer = bb.asFloatBuffer();
// 将矩形顶点数据写入缓冲区
vertexBuffer.put(rectCoords);
// 将缓冲区位置重置为 0,准备读取
vertexBuffer.position(0);

// 为纹理坐标分配缓冲区
ByteBuffer tb = ByteBuffer.allocateDirect(textureCoords.length * 4);
tb.order(ByteOrder.nativeOrder());
textureBuffer = tb.asFloatBuffer();
textureBuffer.put(textureCoords);
textureBuffer.position(0);
}

private void initTexture() {
// 创建纹理对象
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
textureId = textures[0];

// 绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
// 设置纹理参数
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

// 加载图片(假设在 res/drawable 中有一张图片 texture.png)
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), android.R.drawable.star_big_on);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); // 将 Bitmap 上传到纹理
bitmap.recycle(); // 释放 Bitmap 资源
}

private void drawTexturedRect() {
// 使用已编译和链接的着色器程序
GLES20.glUseProgram(program);

// 获取矩阵、顶点和纹理坐标的句柄
int mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
int positionHandle = GLES20.glGetAttribLocation(program, "aPosition");
int texCoordHandle = GLES20.glGetAttribLocation(program, "aTexCoord");
int textureHandle = GLES20.glGetUniformLocation(program, "uTexture");

// 启用顶点属性数组
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glEnableVertexAttribArray(texCoordHandle);

// 设置顶点数据
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
// 设置纹理坐标数据
GLES20.glVertexAttribPointer(texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);

// 应用旋转矩阵
float[] rotationMatrix = new float[16];
Matrix.setIdentityM(rotationMatrix, 0);
Matrix.rotateM(rotationMatrix, 0, angle, 0.0f, 0.0f, 1.0f); // 绕 Z 轴旋转
float[] finalMatrix = new float[16];
Matrix.multiplyMM(finalMatrix, 0, mvpMatrix, 0, rotationMatrix, 0); // 组合投影和旋转矩阵
GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, finalMatrix, 0);

// 绑定纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
GLES20.glUniform1i(textureHandle, 0);

// 绘制矩形(使用三角形条带)
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 4);

// 禁用顶点属性数组,清理状态
GLES20.glDisableVertexAttribArray(positionHandle);
GLES20.glDisableVertexAttribArray(texCoordHandle);
}
}
步骤3:AndroidManifest配置
1
2
<!-- 声明应用需要 OpenGL ES 2.0 支持 -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

方式2:使用SurfaceView + 自己构建EGL环境

这种方式需要手动管理 EGL 和渲染线程,提供更高的灵活性,适合复杂场景。

流程概述

  1. 创建 SurfaceView 并监听表面变化。
  2. 初始化 EGL(显示、上下文、表面)。
  3. 在独立线程中运行渲染循环。
  4. 编写着色器并绘制图形。
  5. 清理资源。
  6. 新增:使用矩阵变换实现旋转,添加纹理映射渲染图片。

关键代码片段

步骤1:创建SurfaceView和渲染线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class MainActivity extends Activity {
private EGLSurfaceView eglSurfaceView; // 自定义 SurfaceView,用于手动管理 EGL

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 创建自定义 SurfaceView
eglSurfaceView = new EGLSurfaceView(this);
// 设置为 Activity 的内容视图
setContentView(eglSurfaceView);
}
}

class EGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private RenderThread renderThread; // 渲染线程对象

public EGLSurfaceView(Context context) {
super(context);
// 为 SurfaceHolder 添加回调,监听表面生命周期
getHolder().addCallback(this);
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
// 表面创建时,启动渲染线程
renderThread = new RenderThread(holder, getContext());
renderThread.start();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 表面大小改变时,更新渲染线程的宽高
renderThread.setSize(width, height);
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 表面销毁时,停止渲染线程
renderThread.stopRendering();
}
}
步骤2:构建EGL环境和渲染线程(含矩阵变换和纹理映射)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.view.SurfaceHolder;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

class RenderThread extends Thread {
private SurfaceHolder surfaceHolder; // SurfaceView 的表面持有者
private EGLDisplay eglDisplay; // EGL 显示对象
private EGLContext eglContext; // EGL 上下文对象
private EGLSurface eglSurface; // EGL 表面对象
private int width, height; // 渲染表面宽高
private volatile boolean running = true; // 控制渲染循环的标志
private int program; // OpenGL 程序对象
private FloatBuffer vertexBuffer; // 顶点数据缓冲区
private FloatBuffer textureBuffer; // 纹理坐标缓冲区
private int textureId; // 纹理 ID
private final Context context; // 用于加载图片资源
private float[] mvpMatrix = new float[16]; // 模型-视图-投影矩阵
private float angle = 0.0f; // 旋转角度

// 定义矩形顶点坐标 (x, y, z)
private final float[] rectCoords = {
-0.5f, 0.5f, 0.0f, // 左上
-0.5f, -0.5f, 0.0f, // 左下
0.5f, -0.5f, 0.0f, // 右下
0.5f, 0.5f, 0.0f // 右上
};

// 定义纹理坐标 (s, t)
private final float[] textureCoords = {
0.0f, 0.0f, // 左上
0.0f, 1.0f, // 左下
1.0f, 1.0f, // 右下
1.0f, 0.0f // 右上
};

public RenderThread(SurfaceHolder holder, Context context) {
this.surfaceHolder = holder; // 初始化时传入 SurfaceHolder
this.context = context; // 初始化时传入 Context
}

public void setSize(int width, int height) {
// 设置渲染表面的宽高
this.width = width;
this.height = height;
// 设置正交投影矩阵
float aspectRatio = (float) width / height;
Matrix.setIdentityM(mvpMatrix, 0);
Matrix.orthoM(mvpMatrix, 0, -aspectRatio, aspectRatio, -1.0f, 1.0f, -1.0f, 1.0f);
}

public void stopRendering() {
// 停止渲染循环
running = false;
}

@Override
public void run() {
// 线程主循环
initEGL(); // 初始化 EGL 环境
initGL(); // 初始化 OpenGL 环境
while (running) {
drawFrame(); // 绘制一帧
EGL14.eglSwapBuffers(eglDisplay, eglSurface); // 交换缓冲区,显示渲染结果
// 更新旋转角度
angle += 1.0f;
if (angle >= 360.0f) angle = 0.0f;
}
cleanup(); // 清理资源
}

private void initEGL() {
// 获取默认显示设备(通常是屏幕)
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
// 初始化 EGL,返回主次版本号
int[] version = new int[2];
EGL14.eglInitialize(eglDisplay, version, 0, version, 1);

// 配置 EGL 属性,支持 OpenGL ES 2.0 和 RGB8 颜色
int[] configAttribs = {
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, // 支持 OpenGL ES 2.0
EGL14.EGL_RED_SIZE, 8, // 红色分量 8 位
EGL14.EGL_GREEN_SIZE, 8, // 绿色分量 8 位
EGL14.EGL_BLUE_SIZE, 8, // 蓝色分量 8 位
EGL14.EGL_NONE // 属性列表结束
};
EGLConfig[] configs = new EGLConfig[1]; // 存储选择的配置
int[] numConfigs = new int[1]; // 存储配置数量
// 选择符合条件的 EGL 配置
EGL14.eglChooseConfig(eglDisplay, configAttribs, 0, configs, 0, 1, numConfigs, 0);

// 创建 EGL 上下文,指定 OpenGL ES 2.0
int[] contextAttribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE };
eglContext = EGL14.eglCreateContext(eglDisplay, configs[0], EGL14.EGL_NO_CONTEXT, contextAttribs, 0);

// 创建与 SurfaceHolder 关联的窗口表面
int[] surfaceAttribs = { EGL14.EGL_NONE };
eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, configs[0], surfaceHolder, surfaceAttribs, 0);
// 将 EGL 上下文和表面绑定到当前线程
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
}

private void initGL() {
// 设置 OpenGL 清屏颜色为黑色
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
initShaders(); // 初始化着色器
initBuffers(); // 初始化顶点缓冲区
initTexture(); // 初始化纹理
}

private void drawFrame() {
// 设置视口大小
GLES20.glViewport(0, 0, width, height);
// 清除颜色缓冲区
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
drawTexturedRect(); // 绘制带纹理的矩形
}

private void initShaders() {
// 定义顶点着色器代码,包含矩阵变换
String vertexShaderCode =
"uniform mat4 uMVPMatrix;\n" + // 模型-视图-投影矩阵
"attribute vec4 aPosition;\n" + // 输入的顶点位置属性
"attribute vec2 aTexCoord;\n" + // 输入的纹理坐标属性
"varying vec2 vTexCoord;\n" + // 传递给片段着色器的纹理坐标
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" + // 应用矩阵变换
" vTexCoord = aTexCoord;\n" + // 传递纹理坐标
"}";

// 定义片段着色器代码,支持纹理映射
String fragmentShaderCode =
"precision mediump float;\n" + // 设置中等精度
"varying vec2 vTexCoord;\n" + // 从顶点着色器接收纹理坐标
"uniform sampler2D uTexture;\n" + // 纹理采样器
"void main() {\n" +
" gl_FragColor = texture2D(uTexture, vTexCoord);\n" + // 从纹理采样颜色
"}";

// 编译顶点和片段着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
// 创建并链接 OpenGL 程序
program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, fragmentShader);
GLES20.glLinkProgram(program);
}

private int loadShader(int type, String shaderCode) {
// 创建并编译着色器
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}

private void initBuffers() {
// 初始化顶点缓冲区
ByteBuffer bb = ByteBuffer.allocateDirect(rectCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(rectCoords);
vertexBuffer.position(0);

// 初始化纹理坐标缓冲区
ByteBuffer tb = ByteBuffer.allocateDirect(textureCoords.length * 4);
tb.order(ByteOrder.nativeOrder());
textureBuffer = tb.asFloatBuffer();
textureBuffer.put(textureCoords);
textureBuffer.position(0);
}

private void initTexture() {
// 创建纹理对象
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
textureId = textures[0];

// 绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
// 设置纹理参数
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

// 加载图片(假设在 res/drawable 中有一张图片 texture.png)
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), android.R.drawable.star_big_on);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); // 将 Bitmap 上传到纹理
bitmap.recycle(); // 释放 Bitmap 资源
}

private void drawTexturedRect() {
// 使用着色器程序并绘制带纹理的矩形
GLES20.glUseProgram(program);

// 获取矩阵、顶点和纹理坐标的句柄
int mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
int positionHandle = GLES20.glGetAttribLocation(program, "aPosition");
int texCoordHandle = GLES20.glGetAttribLocation(program, "aTexCoord");
int textureHandle = GLES20.glGetUniformLocation(program, "uTexture");

// 启用顶点属性数组
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glEnableVertexAttribArray(texCoordHandle);

// 设置顶点数据
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
// 设置纹理坐标数据
GLES20.glVertexAttribPointer(texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);

// 应用旋转矩阵
float[] rotationMatrix = new float[16];
Matrix.setIdentityM(rotationMatrix, 0);
Matrix.rotateM(rotationMatrix, 0, angle, 0.0f, 0.0f, 1.0f); // 绕 Z 轴旋转
float[] finalMatrix = new float[16];
Matrix.multiplyMM(finalMatrix, 0, mvpMatrix, 0, rotationMatrix, 0); // 组合投影和旋转矩阵
GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, finalMatrix, 0);

// 绑定纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
GLES20.glUniform1i(textureHandle, 0);

// 绘制矩形(使用三角形条带)
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 4);

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

private void cleanup() {
// 清理 EGL 资源
EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroySurface(eglDisplay, eglSurface); // 销毁表面
EGL14.eglDestroyContext(eglDisplay, eglContext); // 销毁上下文
EGL14.eglTerminate(eglDisplay); // 终止 EGL 显示
}
}
步骤3:AndroidManifest配置
1
2
<!-- 声明需要 OpenGL ES 2.0 支持 -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

两种方式对比

特性 GLSurfaceView SurfaceView + EGL
易用性 高,封装完善 低,手动管理
灵活性 较低,受限于封装 高,可自定义EGL和线程
代码量 较少 较多
适用场景 简单2D/3D渲染 复杂渲染或多上下文需求

新增功能说明

矩阵变换

  • 实现:在顶点着色器中添加 uniform mat4 uMVPMatrix,用于接收模型-视图-投影矩阵。
  • 旋转效果
    • 使用 Matrix.rotateM 实现绕 Z 轴的旋转,角度 angle 在每帧递增。
    • onSurfaceChanged 中初始化正交投影矩阵,结合旋转矩阵生成最终变换矩阵。
  • 应用:通过 glUniformMatrix4fv 传递矩阵到着色器,顶点位置经过变换后渲染。

纹理映射

  • 顶点和纹理坐标
    • 将三角形改为矩形,使用 GL_TRIANGLE_FAN 绘制。
    • 添加纹理坐标缓冲区,映射到矩形的四个顶点。
  • 纹理加载
    • initTexture 中加载一张图片(这里使用系统资源 star_big_on 作为示例)。
    • 使用 GLUtils.texImage2D 将 Bitmap 上传到 OpenGL 纹理。
  • 着色器支持
    • 顶点着色器传递纹理坐标给片段着色器。
    • 片段着色器使用 sampler2D 从纹理采样颜色。
  • 渲染
    • drawTexturedRect 中绑定纹理并绘制。

运行效果

  • 两种方式都会渲染一个旋转的矩形,矩形上贴有一张纹理图片(例如星星)。
  • GLSurfaceView 使用内置渲染线程,SurfaceView + EGL 使用自定义线程。

Android 使用 CameraX 采集 GLSurfaceView 渲染

流程概述

  1. 配置 CameraX:使用 CameraX 绑定摄像头预览,输出到 SurfaceTexture
  2. 设置 GLSurfaceView:创建并配置 GLSurfaceView,使用 OpenGL ES 渲染纹理。
  3. 处理纹理:将 CameraX 提供的 SurfaceTexture 数据转换为 OpenGL 纹理。
  4. 添加滤镜:在片段着色器中实现灰度滤镜。
  5. 调整方向:根据设备方向动态调整纹理坐标。
  6. 渲染到屏幕:在 GLSurfaceView 的 Renderer 中绘制纹理。
  7. 管理生命周期:确保 CameraX 和 GLSurfaceView 与 Activity 生命周期同步。

前置条件

  • build.gradle 中添加 CameraX 依赖:
1
2
3
implementation "androidx.camera:camera-core:1.3.2"
implementation "androidx.camera:camera-camera2:1.3.2"
implementation "androidx.camera:camera-lifecycle:1.3.2"
  • AndroidManifest.xml 中添加权限:
1
2
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

关键代码片段

步骤1:Activity 配置 CameraX 和 GLSurfaceView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutionException;

public class MainActivity extends AppCompatActivity {
private GLSurfaceView glSurfaceView; // GLSurfaceView 用于显示 OpenGL 渲染结果
private CameraRenderer cameraRenderer; // 自定义 Renderer,处理摄像头纹理渲染
private static final int REQUEST_CAMERA_PERMISSION = 100; // 权限请求码

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// 初始化 GLSurfaceView
glSurfaceView = new GLSurfaceView(this);
glSurfaceView.setEGLContextClientVersion(2); // 设置 OpenGL ES 2.0
cameraRenderer = new CameraRenderer(this); // 创建渲染器实例
glSurfaceView.setRenderer(cameraRenderer); // 设置渲染器
setContentView(glSurfaceView); // 设置为 Activity 的视图

// 检查并请求摄像头权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
} else {
startCamera(); // 已授权,直接启动摄像头
}
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CAMERA_PERMISSION && grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startCamera(); // 权限通过后启动摄像头
}
}

private void startCamera() {
// 获取 CameraX 的 ProcessCameraProvider 实例
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();

// 配置预览用例
Preview preview = new Preview.Builder().build();
// 将预览输出绑定到 SurfaceTexture,由 Renderer 提供
preview.setSurfaceProvider(cameraRenderer.getSurfaceProvider());

// 选择后置摄像头
CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;

// 绑定摄像头生命周期
cameraProvider.unbindAll(); // 先解绑所有用例
Camera camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview); // 绑定预览到 Activity 生命周期
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, ContextCompat.getMainExecutor(this)); // 在主线程执行
}

@Override
protected void onPause() {
super.onPause();
glSurfaceView.onPause(); // 暂停 GLSurfaceView 渲染
}

@Override
protected void onResume() {
super.onResume();
glSurfaceView.onResume(); // 恢复 GLSurfaceView 渲染
}
}

步骤2:实现 CameraRenderer(含滤镜和方向调整)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.view.Display;
import android.view.WindowManager;
import androidx.camera.core.Preview;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class CameraRenderer implements GLSurfaceView.Renderer {
private final Context context;
private SurfaceTexture surfaceTexture; // 从 CameraX 接收图像数据的 SurfaceTexture
private int textureId; // OpenGL 纹理 ID
private int program; // OpenGL 着色器程序
private FloatBuffer vertexBuffer; // 顶点坐标缓冲区
private FloatBuffer textureBuffer; // 纹理坐标缓冲区
private boolean enableGrayscale = true; // 是否启用灰度滤镜,默认为 true
private int rotation = 0; // 设备旋转角度 (0, 90, 180, 270)

// 全屏矩形的顶点坐标 (x, y, z)
private final float[] vertices = {
-1.0f, 1.0f, 0.0f, // 左上
-1.0f, -1.0f, 0.0f, // 左下
1.0f, -1.0f, 0.0f, // 右下
1.0f, 1.0f, 0.0f // 右上
};

// 基础纹理坐标 (s, t),后续根据方向调整
private final float[] baseTextureCoords = {
0.0f, 0.0f, // 左上
0.0f, 1.0f, // 左下
1.0f, 1.0f, // 右下
1.0f, 0.0f // 右上
};

public CameraRenderer(Context context) {
this.context = context;
// 初始化顶点缓冲区
ByteBuffer vb = ByteBuffer.allocateDirect(vertices.length * 4);
vb.order(ByteOrder.nativeOrder());
vertexBuffer = vb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

// 初始化纹理坐标缓冲区
ByteBuffer tb = ByteBuffer.allocateDirect(baseTextureCoords.length * 4);
tb.order(ByteOrder.nativeOrder());
textureBuffer = tb.asFloatBuffer();
updateTextureCoords(); // 根据初始方向设置纹理坐标
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 设置清屏颜色为黑色
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 初始化着色器程序
initShaders();
// 创建并绑定外部纹理 (OES 纹理,用于摄像头数据)
textureId = createTexture();
// 将 SurfaceTexture 与 OpenGL 纹理绑定
surfaceTexture = new SurfaceTexture(textureId);
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 设置视口大小
GLES20.glViewport(0, 0, width, height);
// 更新设备旋转角度
updateRotation();
updateTextureCoords(); // 根据旋转调整纹理坐标
}

@Override
public void onDrawFrame(GL10 gl) {
// 清除颜色缓冲区
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 更新 SurfaceTexture 中的摄像头数据
surfaceTexture.updateTexImage();
// 使用着色器程序
GLES20.glUseProgram(program);

// 获取顶点位置和纹理坐标的句柄
int positionHandle = GLES20.glGetAttribLocation(program, "aPosition");
int textureCoordHandle = GLES20.glGetAttribLocation(program, "aTextureCoord");
int textureHandle = GLES20.glGetUniformLocation(program, "uTexture");
int grayscaleHandle = GLES20.glGetUniformLocation(program, "uGrayscale"); // 灰度开关句柄

// 启用顶点属性
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glEnableVertexAttribArray(textureCoordHandle);

// 设置顶点数据
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
// 设置纹理坐标数据
GLES20.glVertexAttribPointer(textureCoordHandle, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);

// 绑定纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
GLES20.glUniform1i(textureHandle, 0);
// 设置灰度滤镜开关
GLES20.glUniform1i(grayscaleHandle, enableGrayscale ? 1 : 0);

// 绘制矩形(两个三角形组成)
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 4);

// 禁用顶点属性
GLES20.glDisableVertexAttribArray(positionHandle);
GLES20.glDisableVertexAttribArray(textureCoordHandle);
}

private int createTexture() {
// 创建外部 OES 纹理
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]);
// 设置纹理参数
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
return textures[0];
}

private void initShaders() {
// 顶点着色器代码
String vertexShaderCode =
"attribute vec4 aPosition;\n" + // 顶点位置属性
"attribute vec2 aTextureCoord;\n" + // 纹理坐标属性
"varying vec2 vTextureCoord;\n" + // 传递给片段着色器的纹理坐标
"void main() {\n" +
" gl_Position = aPosition;\n" + // 设置顶点位置
" vTextureCoord = aTextureCoord;\n" + // 传递纹理坐标
"}";

// 片段着色器代码,支持外部 OES 纹理和灰度滤镜
String fragmentShaderCode =
"#extension GL_OES_EGL_image_external : require\n" + // 启用 OES 纹理扩展
"precision mediump float;\n" + // 设置中等精度
"varying vec2 vTextureCoord;\n" + // 从顶点着色器接收纹理坐标
"uniform samplerExternalOES uTexture;\n" + // 外部纹理采样器
"uniform int uGrayscale;\n" + // 灰度滤镜开关 (0: 关闭, 1: 开启)
"void main() {\n" +
" vec4 color = texture2D(uTexture, vTextureCoord);\n" + // 从纹理采样颜色
" if (uGrayscale == 1) {\n" +
" float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n" + // 计算灰度值
" gl_FragColor = vec4(gray, gray, gray, color.a);\n" + // 应用灰度滤镜
" } else {\n" +
" gl_FragColor = color;\n" + // 保持原始颜色
" }\n" +
"}";

// 编译顶点和片段着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

// 创建并链接程序
program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, fragmentShader);
GLES20.glLinkProgram(program);
}

private int loadShader(int type, String shaderCode) {
// 创建并编译着色器
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}

public Preview.SurfaceProvider getSurfaceProvider() {
// 提供 SurfaceTexture 给 CameraX
return request -> {
android.util.Size resolution = request.getResolution();
surfaceTexture.setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
request.provideSurface(new android.view.Surface(surfaceTexture),
ContextCompat.getMainExecutor(context), result -> {});
};
}

private void updateRotation() {
// 获取设备当前旋转角度
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
rotation = display.getRotation() * 90; // 转换为 0, 90, 180, 270
}

private void updateTextureCoords() {
// 根据设备旋转角度调整纹理坐标
float[] adjustedCoords = new float[baseTextureCoords.length];
for (int i = 0; i < 4; i++) {
float s = baseTextureCoords[i * 2];
float t = baseTextureCoords[i * 2 + 1];
switch (rotation) {
case 0: // 正常方向
adjustedCoords[i * 2] = s;
adjustedCoords[i * 2 + 1] = t;
break;
case 90: // 顺时针旋转 90 度
adjustedCoords[i * 2] = 1.0f - t;
adjustedCoords[i * 2 + 1] = s;
break;
case 180: // 旋转 180 度
adjustedCoords[i * 2] = 1.0f - s;
adjustedCoords[i * 2 + 1] = 1.0f - t;
break;
case 270: // 顺时针旋转 270 度
adjustedCoords[i * 2] = t;
adjustedCoords[i * 2 + 1] = 1.0f - s;
break;
}
}
// 更新纹理缓冲区
textureBuffer.clear();
textureBuffer.put(adjustedCoords);
textureBuffer.position(0);
}

// 可选:外部调用以切换滤镜状态
public void toggleGrayscale(boolean enable) {
this.enableGrayscale = enable;
}
}

代码详细说明

MainActivity

  • GLSurfaceView 初始化:创建 GLSurfaceView 并绑定自定义 CameraRenderer
  • CameraX 配置
    • 使用 ProcessCameraProvider 获取摄像头实例。
    • 配置 Preview 用例,将输出绑定到 CameraRenderer 提供的 SurfaceTexture
    • 通过 CameraSelector 选择后置摄像头。
    • 将摄像头绑定到 Activity 的生命周期。
  • 权限管理:动态请求摄像头权限。
  • 生命周期管理:同步 GLSurfaceView 的暂停和恢复。

CameraRenderer

  • 顶点和纹理坐标
    • 定义一个全屏矩形(由两个三角形组成),使用 GL_TRIANGLE_FAN 绘制。
    • 纹理坐标通过 updateTextureCoords() 根据设备旋转动态调整。
  • SurfaceTexture
    • onSurfaceCreated 中创建并绑定到 OpenGL 的 OES 纹理。
    • 通过 updateTexImage() 更新每一帧的摄像头数据。
  • 着色器
    • 顶点着色器:传递位置和纹理坐标。
    • 片段着色器:支持灰度滤镜,通过 uGrayscale 控制是否应用。灰度计算使用标准公式:gray = 0.299R + 0.587G + 0.114B
  • 渲染流程
    • onDrawFrame 中更新纹理、绑定数据并绘制矩形。
    • 通过 Uniform 变量 uGrayscale 控制滤镜开关。
  • 方向调整
    • updateRotation() 获取设备旋转角度。
    • updateTextureCoords() 根据旋转角度调整纹理坐标,确保图像方向正确。
  • 滤镜控制
    • 添加 toggleGrayscale() 方法,允许外部切换滤镜状态(默认启用灰度)。

运行效果

  • 启动应用后,GLSurfaceView 显示后置摄像头的实时预览图像。
  • 方向调整:图像会根据设备旋转自动调整为正确方向。
  • 滤镜效果:默认显示灰度图像,可通过调用 cameraRenderer.toggleGrayscale(false) 切换为彩色。

注意事项

  1. 性能:确保 SurfaceTexture 的分辨率与摄像头输出匹配,避免性能问题。
  2. 方向调整:纹理坐标调整基于后置摄像头,针对前置摄像头可能需要镜像处理。
  3. 滤镜扩展:灰度滤镜只是示例,可修改片段着色器实现其他效果(如亮度、对比度调整)。
  4. 错误检查:实际开发中可添加 OpenGL 错误检查(如 GLES20.glGetError())。

扩展建议

  • 更多滤镜:可添加 Uniform 变量控制亮度、对比度等,或实现边缘检测等复杂效果。
  • 动态切换:通过 UI 按钮调用 toggleGrayscale(),实现实时切换。
  • 前置摄像头支持:调整纹理坐标以支持前置摄像头的镜像效果。