12、OpenGL ES基础:管线、着色器语言、纹理映射
说实话,做音视频开发这么多年,我见过不少朋友一提到OpenGL ES就头疼。觉得它门槛高、概念抽象、代码又长。其实你想想看,OpenGL ES说白了就是一套给GPU下指令的接口。你告诉它怎么画、画什么,它帮你把像素渲染到屏幕上。今天这一章,我们就把它拆开揉碎了讲清楚。
12.1 OpenGL ES管线:数据是怎么变成画面的?
先问大家一个问题:你手机里的一张图片,或者一段视频帧,是怎么变成屏幕上那个五彩斑斓的像素点的?
答案就是——图形管线。我习惯把它想象成一条流水线。原始数据从一端进去,经过一道道工序,最后从另一端出来一张完整的画面。
核心管线阶段(简化版):
- 顶点着色器:处理每个顶点的位置、颜色。说白了就是确定“点在哪”。
- 图元装配:把顶点连成三角形、线段等基本图形。
- 光栅化:把图形拆成一个个像素碎片。
- 片段着色器:决定每个像素最终的颜色。
- 逐片元操作:深度测试、模板测试、混合,最后写入帧缓冲区。
我在项目中遇到过一个问题:视频渲染出来画面是颠倒的。排查了半天,发现是顶点坐标的Y轴方向搞反了。嗯,这种坑踩过一次就记住了。
我的小建议:刚开始学管线,别死记硬背每个阶段。你只需要记住两个关键点:顶点着色器管“形状”,片段着色器管“颜色”。其他阶段OpenGL帮你自动处理了。
12.2 着色器语言(GLSL):写给GPU的小作文
GLSL全称是OpenGL Shading Language。我把它理解为写给GPU的“小作文”。你写一段代码,告诉GPU怎么处理顶点、怎么给像素上色。
来看一个最简单的顶点着色器:
// 顶点着色器
attribute vec4 aPosition; // 输入:顶点坐标
void main() {
gl_Position = aPosition; // 直接输出坐标
}
再看对应的片段着色器:
// 片段着色器
precision mediump float; // 设置精度
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 输出红色
}
这两段代码加起来,就能在屏幕上画一个红色的三角形。是不是比想象中简单?
GLSL 核心语法速览:
| 类型 | 说明 | 示例 |
|---|---|---|
| vec2/vec3/vec4 | 2/3/4维向量 | vec4(1.0, 0.0, 0.0, 1.0) |
| mat2/mat3/mat4 | 2x2/3x3/4x4矩阵 | mat4(1.0) |
| sampler2D | 2D纹理采样器 | uniform sampler2D uTexture; |
| attribute | 顶点属性(每个顶点不同) | attribute vec4 aPosition; |
| uniform | 全局常量(所有顶点相同) | uniform mat4 uMatrix; |
| varying | 从顶点着色器传到片段着色器 | varying vec2 vTexCoord; |
我曾经犯过一个低级错误:在片段着色器里忘了写 precision mediump float;。结果在某些手机上渲染出来全是花屏。嗯,这种坑踩过一次就长记性了。
12.3 纹理映射:把图片贴到模型上
纹理映射,说白了就是把一张图片“贴”到3D模型表面。比如你做一个地球仪,总不能手动画每个国家的轮廓吧?直接把世界地图贴上去就行了。
纹理映射的核心是——纹理坐标。每个顶点对应一个纹理坐标(u, v),范围是0.0到1.0。
- (0, 0) 对应图片左下角
- (1, 1) 对应图片右上角
- (0.5, 0.5) 对应图片正中心
来看一个带纹理的顶点着色器:
attribute vec4 aPosition;
attribute vec2 aTexCoord; // 纹理坐标
varying vec2 vTexCoord; // 传给片段着色器
void main() {
gl_Position = aPosition;
vTexCoord = aTexCoord;
}
片段着色器:
precision mediump float;
uniform sampler2D uTexture; // 纹理采样器
varying vec2 vTexCoord;
void main() {
gl_FragColor = texture2D(uTexture, vTexCoord);
}
你想想看,有了纹理映射,视频播放器里的每一帧画面,本质上就是一张不断更新的纹理。我们只需要把解码后的YUV数据上传到纹理,然后渲染到一个矩形上,视频就播放出来了。
注意:纹理坐标和屏幕坐标的Y轴是相反的。如果你发现渲染出来的画面是倒的,试试把纹理坐标的Y值取反:vTexCoord.y = 1.0 - vTexCoord.y;
12.4 实战:在Android上画一个带纹理的矩形
光说不练假把式。我们来看看在Android上怎么用OpenGL ES画一个带纹理的矩形。
核心步骤就这几步:
- 创建GLSurfaceView,设置OpenGL ES版本
- 编写顶点和片段着色器
- 创建纹理,加载图片数据
- 设置顶点坐标和纹理坐标
- 在onDrawFrame中绘制
关键代码片段:
// 顶点坐标(矩形四个角)
float[] vertexData = {
-0.5f, -0.5f, // 左下
0.5f, -0.5f, // 右下
-0.5f, 0.5f, // 左上
0.5f, 0.5f // 右上
};
// 纹理坐标(对应四个角)
float[] texCoordData = {
0.0f, 0.0f, // 左下
1.0f, 0.0f, // 右下
0.0f, 1.0f, // 左上
1.0f, 1.0f // 右上
};
// 绘制时使用三角形条带
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
避坑指南:我曾经在纹理加载时忘了设置 GLES20.GL_TEXTURE_MIN_FILTER,结果纹理显示出来全是锯齿。加上 GL_LINEAR_MIPMAP_LINEAR 就好了。另外,纹理尺寸最好是2的幂次方(如256x256、512x512),否则某些老设备会出问题。
12.5 本章小结
这一章我们聊了OpenGL ES的三大基础:管线、着色器、纹理映射。管线是骨架,着色器是灵魂,纹理映射是血肉。三者结合起来,你就能在Android上渲染出任何画面了。
我个人觉得,学OpenGL ES最好的方式就是动手写代码。别怕出错,出错了才能记住。下一章我们会把这些知识用到视频播放器里,真正实现视频画面的渲染。
公众号:蓝海资料掘金营,微信deep3321