18、OpenGL ES纹理渲染:纹理概念、纹理坐标、加载纹理、纹理映射到SurfaceView
纹理渲染,说白了就是把一张图片贴到3D模型上。你想想看,如果所有物体都是纯色的,那画面得多单调?纹理就是给图形世界穿上衣服的过程。
我个人习惯把纹理理解成「GPU能读懂的图片」。它不像我们平时看到的JPG或PNG,而是经过特殊处理后的像素数据。嗯,这里要注意,纹理在GPU里是以二维数组的形式存在的,每个元素就是一个像素的颜色值。
纹理概念:从图片到纹理的转变
纹理本质上就是一张图像数据。但为什么我们不直接叫它「图片」呢?因为纹理在图形管线中有特殊的用途和限制。
我在项目中遇到过一个问题:加载了一张高清照片,结果渲染出来是花的。后来才发现,纹理的宽高必须是2的幂次方,比如256、512、1024。虽然现在的OpenGL ES 3.0已经放宽了这个限制,但为了兼容性,我建议你还是尽量遵守这个规则。
- 纹素:纹理中的最小单位,相当于像素
- 纹理层级:Mipmap链,从大到小的一组纹理
- 纹理格式:RGB、RGBA、Luminance等
- 纹理环绕方式:重复、镜像、截取等
纹理坐标:贴图的位置信息
纹理坐标,也叫UV坐标。它决定了纹理的哪一部分被贴到模型的哪个位置。
纹理坐标的范围是0.0到1.0。为什么不是像素坐标?因为这样设计可以跟纹理的分辨率解耦。你换一张更高清的图片,坐标不用改,效果自动变好。
| 坐标轴 | 范围 | 说明 |
|---|---|---|
| S (U) | 0.0 - 1.0 | 水平方向,从左到右 |
| T (V) | 0.0 - 1.0 | 垂直方向,从下到上 |
注意,纹理坐标的Y轴方向跟屏幕坐标是相反的。屏幕坐标Y轴向下,纹理坐标Y轴向上。我曾经因为这个搞反了,结果贴图上下颠倒,排查了半天才发现是坐标写反了。
加载纹理:把图片数据送入GPU
加载纹理,说白了就是把Bitmap数据传给OpenGL ES。这个过程有几个关键步骤:
- 生成纹理对象:
glGenTextures() - 绑定纹理:
glBindTexture() - 设置纹理参数:环绕方式、过滤方式
- 上传纹理数据:
glTexImage2D() - 生成Mipmap:
glGenerateMipmap()
// 加载纹理的核心代码
int[] textureIds = new int[1];
GLES20.glGenTextures(1, textureIds, 0);
int textureId = textureIds[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
// 设置纹理参数
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
// 上传纹理数据
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
纹理映射:把纹理贴到SurfaceView上
纹理映射,就是把纹理坐标跟顶点坐标对应起来。每个顶点都有一个纹理坐标,GPU会根据这些坐标从纹理中采样颜色。
举个例子,一个矩形有4个顶点,对应的纹理坐标就是矩形的四个角:
// 顶点坐标
float[] vertices = {
-0.5f, 0.5f, // 左上
-0.5f, -0.5f, // 左下
0.5f, -0.5f, // 右下
0.5f, 0.5f // 右上
};
// 纹理坐标
float[] texCoords = {
0.0f, 0.0f, // 左上
0.0f, 1.0f, // 左下
1.0f, 1.0f, // 右下
1.0f, 0.0f // 右上
};
在片段着色器里,我们通过纹理采样器来获取颜色:
// 片段着色器
precision mediump float;
uniform sampler2D uTexture;
varying vec2 vTexCoord;
void main() {
gl_FragColor = texture2D(uTexture, vTexCoord);
}
纹理渲染的完整流程
把纹理渲染到SurfaceView上,整个流程是这样的:
- 创建SurfaceView,获取SurfaceHolder
- 创建EGL环境,绑定到Surface
- 编译着色器程序
- 加载纹理数据
- 设置顶点和纹理坐标
- 绘制纹理
- 交换缓冲区
嗯,这里要注意,纹理渲染是在单独的渲染线程中完成的,不能在主线程操作OpenGL ES。
纹理渲染是视频播放、图像处理的基础。掌握了纹理的概念和映射方式,你就能在SurfaceView上展示各种视觉效果了。
- 使用纹理图集,减少纹理切换
- 合理设置纹理过滤方式,平衡质量和性能
- 避免频繁更新纹理数据
- 使用VBO减少CPU-GPU数据传输
好了,纹理渲染的核心内容就这些。记住,纹理就是GPU能读懂的图片,纹理坐标就是贴图的位置,加载纹理就是把数据送入GPU,纹理映射就是把纹理贴到模型上。这四个概念搞清楚了,纹理渲染你就掌握了。