2. OpenGL ES基础
OpenGL ES 是嵌入式与移动平台上的精简版图形 API,它把 3D 场景变成屏幕像素的过程抽象为一条可编程的图形渲染管线。理解这条管线的阶段划分、着色器职责、坐标变换链路以及纹理采样机制,是后续在 Qt 中集成 OpenGL ES 的前提。本节从整体管线出发,逐层拆解顶点着色器、图元装配、光栅化、片段着色器与逐片段操作,并给出坐标系变换与纹理映射的关键参数。
一、图形渲染管线的阶段划分
OpenGL ES 2.0 及以后版本采用可编程管线,核心阶段如下:
- 顶点着色器(Vertex Shader):每个顶点执行一次,负责把顶点从模型空间变换到裁剪空间,并计算法线、纹理坐标等可传递数据。
- 图元装配(Primitive Assembly):把顶点按绘制模式组合成点、线或三角形。
- 光栅化(Rasterization):把图元离散为片段,并插值顶点着色器输出的 varying 变量。
- 片段着色器(Fragment Shader):每个片段执行一次,计算最终颜色,可进行纹理采样、光照计算与混合准备。
- 逐片段操作(Per-Fragment Operations):包括裁剪测试、深度测试、模板测试、混合与抖动,最终写入帧缓冲。
其中顶点着色器和片段着色器是开发者必须编写的两个可编程阶段,其余阶段由 GPU 固定功能完成。下图展示了这条管线的数据流与两个着色器的位置关系。
二、顶点着色器与片段着色器的职责
顶点着色器接收顶点属性(attribute)、统一变量(uniform)和常量,输出裁剪空间坐标 gl_Position,以及可选的 varying 变量。片段着色器接收插值后的 varying,输出 gl_FragColor 或自定义输出变量。两者通过 varying 传递数据,但 varying 在光栅化阶段会被线性插值,因此适合传递纹理坐标、颜色和法线。
// 顶点着色器示例
attribute vec3 aPos;
attribute vec2 aUV;
uniform mat4 uMVP;
varying vec2 vUV;
void main() {
vUV = aUV;
gl_Position = uMVP * vec4(aPos, 1.0);
}
// 片段着色器示例
precision mediump float;
uniform sampler2D uTexture;
varying vec2 vUV;
void main() {
gl_FragColor = texture2D(uTexture, vUV);
}
三、坐标系与变换链路
顶点从模型空间到屏幕空间需要经过一系列坐标变换,这是图形学中最容易混淆的部分。完整链路为:模型空间 → 世界空间 → 观察空间 → 裁剪空间 → 归一化设备坐标(NDC)→ 窗口空间。其中模型矩阵、观察矩阵和投影矩阵通常合并为 MVP 矩阵,在顶点着色器中一次性完成。
| 变换阶段 | 输入空间 | 输出空间 | 典型矩阵 |
|---|---|---|---|
| 模型变换 | 模型空间 | 世界空间 | Model Matrix |
| 观察变换 | 世界空间 | 观察空间 | View Matrix |
| 投影变换 | 观察空间 | 裁剪空间 | Projection Matrix |
| 透视除法 | 裁剪空间 | NDC | GPU 自动完成 |
| 视口变换 | NDC | 窗口空间 | glViewport 参数 |
在 Qt 中,QMatrix4x4 提供了 perspective()、lookAt()、translate()、rotate() 等便捷方法,可以快速构建 MVP 矩阵。注意 OpenGL ES 的 NDC 范围是 [-1, 1],深度范围默认也是 [-1, 1],而 Qt 的 QMatrix4x4::perspective() 默认生成符合 OpenGL 约定的矩阵。
四、纹理映射核心概念
纹理映射是把二维图像贴到三维表面的过程。核心步骤包括:创建纹理对象、绑定纹理、设置过滤与环绕参数、上传像素数据、在片段着色器中采样。纹理坐标通常记为 (s, t) 或 (u, v),范围是 [0, 1],原点位置取决于图像加载方式。
- 过滤方式:GL_NEAREST 适合像素风,GL_LINEAR 适合平滑缩放;Mipmap 可配合 GL_LINEAR_MIPMAP_LINEAR 使用。
- 环绕方式:GL_REPEAT 平铺,GL_CLAMP_TO_EDGE 边缘拉伸,GL_MIRRORED_REPEAT 镜像重复。
- 纹理单元:通过 glActiveTexture 激活,uniform1i 指定采样器对应的单元编号。
- 像素对齐:glPixelStorei(GL_UNPACK_ALIGNMENT, 1) 可避免非 4 字节行宽导致的图像错位。
五、案例与排障复盘
假设在 Qt 中绘制一个带纹理的旋转立方体,但屏幕全黑。可按以下顺序排查:
- 检查着色器编译日志,确认 attribute 和 uniform 是否被优化掉。
- 确认 MVP 矩阵是否已正确传入,尤其是投影矩阵的远近平面是否合理。
- 检查纹理是否成功绑定,采样器单元编号是否与 uniform 一致。
- 确认深度测试是否开启,glEnable(GL_DEPTH_TEST) 是否在绘制前调用。
- 检查顶点数据是否上传到 VBO,glVertexAttribPointer 的偏移与步长是否正确。
通过这条管线视角,可以把“黑屏”问题定位到具体阶段:是顶点没进裁剪空间,还是片段颜色被深度测试丢弃,抑或纹理采样返回了全黑。掌握每个阶段的输入输出与状态开关,才能在 Qt 与 OpenGL ES 集成时快速定位问题。