一、OpenGL ES 初探:从零开始认识这个图形引擎
说实话,我第一次接触 OpenGL ES 的时候,心里是有点发怵的。那时候我刚从 Java 界面开发转到图形方向,满脑子都是「这玩意儿到底怎么在手机上画个三角形?」。后来踩了不少坑,才慢慢摸清楚它的脾气。
OpenGL ES,全称是 OpenGL for Embedded Systems。说白了,它就是 OpenGL 的「轻量版」,专门为手机、平板这类嵌入式设备设计的。你想想看,桌面显卡动不动几百瓦功耗,手机电池才几千毫安时,能一样吗?
我个人的理解是:OpenGL ES 是一套图形渲染的「标准接口」。它不关心你用的是高通还是联发科,也不管你是 Mali 还是 Adreno。它只规定——你要画一个三角形,就得按这个流程来。至于底层怎么实现,那是芯片厂商的事。
1.1 渲染管线:图形从数据到像素的流水线
渲染管线,这个词听起来很唬人。其实就是一个「流水线作业」——你把顶点数据扔进去,经过一道道工序,最后输出一张图片。
我在项目中遇到过最典型的坑:以为只要把顶点坐标传进去,GPU 就能自动画出好看的图形。结果屏幕上啥也没有。为什么?因为管线里的每一步,你都得自己写代码控制。
来,我画个图帮你理解:
这张图我建议你多看两眼。整个渲染管线,说白了就是两个「可编程阶段」——顶点着色器和片段着色器,中间夹着几个「固定功能」阶段。你写的 GLSL 代码,就是在这两个阶段里运行的。
核心要点:OpenGL ES 的渲染管线是「状态机」驱动的。你设置好各种状态(比如是否开启深度测试),然后提交顶点数据,管线就会自动跑完。但如果你忘了设置某个状态——嗯,我曾经花了一整天找 bug,最后发现是忘了 glEnable(GL_DEPTH_TEST)。
1.2 Android 中的 GLSurfaceView:你的 OpenGL 画板
在 Android 上做 OpenGL 开发,GLSurfaceView 是你绕不开的起点。它封装了 EGL(OpenGL 和窗口系统之间的桥梁)的创建、渲染线程的管理、以及生命周期处理。
我个人习惯把 GLSurfaceView 理解成一个「带 OpenGL 上下文的 SurfaceView」。它帮你做了三件重要的事:
- 管理 EGL 上下文——你不用自己写 EGL 初始化代码
- 提供独立的渲染线程——不会阻塞 UI 线程
- 处理生命周期——Activity 暂停时自动暂停渲染
来看一个最基础的实现:
public class MyGLSurfaceView extends GLSurfaceView {
public MyGLSurfaceView(Context context) {
super(context);
// 设置 OpenGL ES 版本,我一般用 3.0
setEGLContextClientVersion(3);
// 设置渲染器
setRenderer(new MyRenderer());
// 设置渲染模式:连续渲染还是按需渲染
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
}
这里有个细节:setEGLContextClientVersion(3) 告诉系统你要用 OpenGL ES 3.0。如果你用 2.0,就传 2。但注意——不是所有设备都支持 3.0。我在项目里遇到过老机型不支持的情况,所以建议你在运行时检测一下。
渲染器(Renderer)是核心,它有三个回调方法:
| 方法 | 调用时机 | 我踩过的坑 |
|---|---|---|
| onSurfaceCreated | Surface 创建时(只调用一次) | 别在这里做耗时操作,会阻塞渲染线程 |
| onSurfaceChanged | Surface 大小变化时 | 记得更新视口(glViewport),否则画面会拉伸 |
| onDrawFrame | 每帧渲染时调用 | 保持 60fps,别做复杂计算 |
写一个最简单的渲染器:
public class MyRenderer implements GLSurfaceView.Renderer {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 设置清屏颜色——我习惯用淡蓝色
GLES30.glClearColor(0.8f, 0.9f, 1.0f, 1.0f);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 设置视口大小
GLES30.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
// 清屏
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
}
}
小技巧:如果你只是想在屏幕上显示一个纯色背景,上面这段代码就够了。但如果你发现屏幕还是黑的——检查一下是不是忘了调用 setRenderer()。我曾经犯过这个低级错误,排查了半小时。
1.3 渲染模式的选择:连续渲染 vs 按需渲染
GLSurfaceView 有两种渲染模式:
- RENDERMODE_CONTINUOUSLY——持续渲染,每 16ms 调用一次 onDrawFrame。适合游戏、动画等需要实时更新的场景。
- RENDERMODE_WHEN_DIRTY——按需渲染,只有调用
requestRender()时才重绘。适合静态场景或触摸事件触发更新的情况。
我建议:如果你的应用不是游戏,尽量用 RENDERMODE_WHEN_DIRTY。为什么?省电。持续渲染会让 GPU 一直工作,手机发烫、掉电快。我在做一个电子书阅读器的时候,就因为用了持续渲染,被测试同事吐槽「看一页书手机能当暖手宝」。
注意:GLSurfaceView 的渲染线程和 UI 线程是分开的。不要在 onDrawFrame 里直接操作 UI 组件,会引发线程安全问题。如果需要更新 UI,用 Handler 或者 runOnUiThread 切回主线程。
1.4 生命周期管理:别让渲染线程泄漏
Activity 销毁时,GLSurfaceView 的渲染线程可能还在跑。如果你不处理,轻则内存泄漏,重则崩溃。
标准做法是在 Activity 的 onPause 和 onResume 中调用 GLSurfaceView 对应的方法:
@Override
protected void onPause() {
super.onPause();
myGLSurfaceView.onPause(); // 暂停渲染线程
}
@Override
protected void onResume() {
super.onResume();
myGLSurfaceView.onResume(); // 恢复渲染线程
}
嗯,这里要注意:如果你在 onSurfaceCreated 中加载了纹理或创建了缓冲区,Activity 重建时这些资源会丢失。我一般会在 onSurfaceCreated 中重新加载所有资源,或者在 onPause 中手动释放。
1.5 本章小结
到这,你应该对 OpenGL ES 有了个初步印象。说白了,它就是一套图形渲染的「标准作业流程」——你准备好数据,设置好状态,然后让 GPU 去跑。GLSurfaceView 则是 Android 帮你封装好的「画板」,你只需要关心渲染器里的三个回调方法。
下一章我们会真正动手,在屏幕上画出一个三角形。那是 OpenGL 的「Hello World」,也是你踏入图形学大门的第一步。
公众号:蓝海资料掘金营,微信deep3321