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
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
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
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
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
9unsigned 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
传递外部数据(如矩阵)。 - 检查编译和链接状态(
glGetShaderiv
、glGetProgramiv
)。
2.3 图元组装 (Primitive Assembly)
- 功能作用: 将顶点连接成图元(如点、线、三角形),为光栅化准备数据。
- 使用场景: 定义绘制方式(如三角形网格、线框视图)。
- 底层原理:
GPU根据绘制模式(
GL_TRIANGLES
、GL_LINES
)和顶点顺序组装图元,确定正面/背面。 - 示例代码:
1
2glBindVertexArray(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
in vec3 vertexColor; // 从顶点着色器接收
out vec4 FragColor; // 输出颜色
uniform vec3 lightColor;
void main() {
FragColor = vec4(vertexColor * lightColor, 1.0); // 简单光照
}1
2
3
4
5
6unsigned 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
2glBindFramebuffer(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
9float 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
7unsigned 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
7unsigned 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
11unsigned 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
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
6unsigned 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
2glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND); - 注意事项: 状态需手动重置。
4. 常用功能
4.1 绘制调用
4.1.1 glDrawArrays
- 功能作用: 顺序绘制顶点。
- 使用场景: 简单几何体。
- 示例代码:
1
2glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3); - 注意事项: 不使用索引。
4.1.2 glDrawElements
- 功能作用: 使用索引绘制。
- 使用场景: 复杂网格。
- 示例代码:
1
2glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
4.2 清屏与缓冲区操作
- 功能作用: 清空缓冲区,准备新帧。
- 使用场景: 每帧开始。
- 示例代码:
1
2glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - 注意事项: 指定缓冲区类型。
4.3 混合与深度
- 功能作用: 控制渲染效果。
- 使用场景: 透明、遮挡。
- 示例代码:
1
2
3glEnable(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
2GLenum error = glGetError();
if (error != GL_NO_ERROR) printf("Error: %d\n", error);
问题研究
Android 使用 OpenGL
方式1:使用GLSurfaceView
GLSurfaceView
是 Android 提供的封装类,内置了 EGL
管理和渲染线程,适合快速实现 OpenGL 渲染。
流程概述
- 创建并配置
GLSurfaceView
。 - 实现
GLSurfaceView.Renderer
接口,定义渲染逻辑。 - 编写顶点和片段着色器,加载顶点数据。
- 在 Activity 中管理生命周期。
- 新增:使用矩阵变换实现旋转,添加纹理映射渲染图片。
关键代码片段
步骤1:创建GLSurfaceView
1 | public class MainActivity extends Activity { |
步骤2:实现Renderer(含矩阵变换和纹理映射)
1 | import android.content.Context; |
步骤3:AndroidManifest配置
1 | <!-- 声明应用需要 OpenGL ES 2.0 支持 --> |
方式2:使用SurfaceView + 自己构建EGL环境
这种方式需要手动管理 EGL 和渲染线程,提供更高的灵活性,适合复杂场景。
流程概述
- 创建
SurfaceView
并监听表面变化。 - 初始化 EGL(显示、上下文、表面)。
- 在独立线程中运行渲染循环。
- 编写着色器并绘制图形。
- 清理资源。
- 新增:使用矩阵变换实现旋转,添加纹理映射渲染图片。
关键代码片段
步骤1:创建SurfaceView和渲染线程
1 | public class MainActivity extends Activity { |
步骤2:构建EGL环境和渲染线程(含矩阵变换和纹理映射)
1 | import android.content.Context; |
步骤3:AndroidManifest配置
1 | <!-- 声明需要 OpenGL ES 2.0 支持 --> |
两种方式对比
特性 | 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 渲染
流程概述
- 配置 CameraX:使用 CameraX 绑定摄像头预览,输出到
SurfaceTexture
。 - 设置 GLSurfaceView:创建并配置 GLSurfaceView,使用 OpenGL ES 渲染纹理。
- 处理纹理:将 CameraX 提供的
SurfaceTexture
数据转换为 OpenGL 纹理。 - 添加滤镜:在片段着色器中实现灰度滤镜。
- 调整方向:根据设备方向动态调整纹理坐标。
- 渲染到屏幕:在 GLSurfaceView 的 Renderer 中绘制纹理。
- 管理生命周期:确保 CameraX 和 GLSurfaceView 与 Activity 生命周期同步。
前置条件
在
build.gradle
中添加 CameraX 依赖:1
2
3implementation "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 | import android.Manifest; |
步骤2:实现 CameraRenderer(含滤镜和方向调整)
1 | import android.content.Context; |
代码详细说明
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)
切换为彩色。
注意事项
- 性能:确保
SurfaceTexture
的分辨率与摄像头输出匹配,避免性能问题。 - 方向调整:纹理坐标调整基于后置摄像头,针对前置摄像头可能需要镜像处理。
- 滤镜扩展:灰度滤镜只是示例,可修改片段着色器实现其他效果(如亮度、对比度调整)。
- 错误检查:实际开发中可添加 OpenGL 错误检查(如
GLES20.glGetError()
)。
扩展建议
- 更多滤镜:可添加 Uniform 变量控制亮度、对比度等,或实现边缘检测等复杂效果。
- 动态切换:通过 UI 按钮调用
toggleGrayscale()
,实现实时切换。 - 前置摄像头支持:调整纹理坐标以支持前置摄像头的镜像效果。