做 Android 久了,迟早会碰到这类需求:实时滤镜、相机特效、动态贴纸、复杂转场、海量图元渲染。到这一步,Canvas 和普通属性动画基本就不够用了,OpenGL ES 是绕不开的。
这篇不走“画个三角形就结束”的路线,包含两个 Demo:
- 入门但不玩具:可交互 3D 立方体(旋转、缩放、光照、深度测试)。
- 业务落地版本:相机实时滤镜链(OES 纹理 + FBO 多 Pass + 最终上屏)。
两个 Demo 放在一起看,能更直观看到 OpenGL 在 Android 项目里的落地方式,而不只是 API 用法。
一,先把 OpenGL 在 Android 里的角色说清楚
OpenGL ES 在客户端业务里,本质是一个高吞吐的像素处理管线。
常见路径是:
Camera/Bitmap/视频帧 -> 纹理 -> Shader 计算 -> FrameBuffer -> 屏幕或编码器
它可以理解成“GPU 版流水线”:CPU 负责组织数据和调度,GPU 负责并行计算像素。Android 开发里,只要进入实时图像处理场景,这套模型基本无法绕开。
二,Demo 1:可交互 3D 立方体(入门)
这个 Demo 的目标很直接:把 OpenGL 最核心的几件事一次跑通。
GLSurfaceView + Renderer的线程模型。- MVP 矩阵变换。
- Vertex/Fragment Shader 的配合。
- 深度测试和基础光照。
- 手势事件安全地投递到 GL 线程。
2.1 目录结构
1 | app/src/main/java/com/example/glcube/ |
2.2 Activity 和 GLSurfaceView
1 | class MainActivity : AppCompatActivity() { |
CubeSurfaceView 里最关键的是:触摸在主线程,渲染在 GL 线程,参数修改统一走 queueEvent。
1 | class CubeSurfaceView(context: Context) : GLSurfaceView(context) { |
2.3 Renderer:矩阵、相机、每帧绘制
1 | class CubeRenderer : GLSurfaceView.Renderer { |
2.4 Cube + Shader(带基础光照)
这里保留核心代码:顶点法线、MVP/MV 矩阵、漫反射。
1 | class Cube { |
如果这一套能够跑通,OpenGL 的基本功已经过了第一关。
三,Demo 2:业务落地版相机实时滤镜链(有难度)
下面这个是我更建议在业务中练的 Demo。因为它直接对应“拍摄页实时预览 + 滤镜 + 导出”的典型需求。
3.1 业务目标
- CameraX 预览帧进 GPU。
- 使用
samplerExternalOES采样相机纹理。 - 经过 2 个离屏 Pass(磨皮/锐化可替换)。
- 最终结果上屏,同时可复用到 MediaCodec 编码输入。
3.2 渲染链路
1 | CameraX -> SurfaceTexture(OES) -> Pass1(FBO A) -> Pass2(FBO B) -> Screen |
这条链路最关键的点:
- 第一段必须是 OES 纹理,普通
sampler2D不能直接采相机流。 - 中间处理统一落到 2D 纹理,后续 Pass 才能自由组合。
- 所有 OpenGL 调用必须在同一个 GL 线程和上下文里。
3.3 关键类设计
1 | class CameraFilterRenderer : GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener { |
3.4 OES 片元着色器(相机首帧)
1 |
|
3.5 一个可直接替换的锐化片元 Shader
1 | precision mediump float; |
3.6 这个 Demo 在业务里最常踩的坑
- 方向错乱:相机纹理坐标和屏幕坐标是两套系,通常要配合
SurfaceTexture.getTransformMatrix()。 - 画面偶发卡顿:
updateTexImage()调用节奏不对,或 CPU 侧参数频繁分配对象。 - 黑屏:FBO 没绑定完整,或者纹理尺寸和 Viewport 不一致。
- 发热明显:Pass 太多、分辨率拉满,建议在预览态降采样,导出态再开高质量。
四,线上可用性的几个建议
只给实用结论:
- 滤镜参数不要直接改全局变量,统一做“参数快照”,一帧只读一次,避免撕裂。
- Shader program、VBO、FBO 在
onSurfaceCreated/onSurfaceChanged分层创建,避免每帧分配。 - 给每个 Pass 打 GPU 时间戳或至少打帧耗时,先定位瓶颈,再谈优化。
- 对中低端机做策略降级:关闭高阶滤镜、降分辨率、降帧率,比硬顶稳定。
五,小结
第一个 Demo 解决的是“我能不能把 OpenGL 跑起来并理解基本概念”;第二个 Demo 解决的是“这套东西能不能放进业务,并在真实机型上跑稳”。
如果要在 Android 上长期做图形相关开发,后者更重要。因为线上问题通常不是 shader 会不会写,而是链路怎么设计、线程怎么管理、性能怎么兜底。