纹理,就是通过一系列表示颜色的数据,调用对应的GL函数,把绘制出来的几何图形,填充纹理数据所携带的颜色。
比如绘制了两个三角形组成的矩形,没有使用纹理的时候
是使用常量颜色填充的。
当使用纹理的时候
如何实现,可以通过《OPENGL ES 3.0 编程指南》第九章的学习和样例代码
的编译运行,已经有一定了解。
但纹理是查看过代码,对如何映射到绘制的图形,纹理坐标与顶点的关系,可能还是一头雾水。
比如,纹理数据保存了9个颜色值(原案例是4个颜色,这里加深理解进行了修改)
byte[] pixels =
{
0, (byte) 0xff, 0, // Green
0, 0, (byte) 0xff, // Blue
(byte) 0xff, 0, 0, // Red
(byte) 0xff, 0, 0, // Red
0, (byte) 0xff, 0, // Green
(byte) 0xff, (byte) 0xff, 0,// Yellow
0, 0, (byte) 0xff, // Blue
(byte) 0xff, (byte) 0xff, 0,// Yellow
(byte) 0xff, 0, 0 // Red
};
绿色,蓝色,红色,红色,绿色,黄色,蓝色,黄色,红色
为什么通过顶点和片段着色器进行,最终结果是这样的
而不是
的呢。
这就要研究一下OPENGL纹理的存储方式和纹理坐标与顶点映射的关系了
这里只涉及最简单的情况,不涉及压缩纹理。
纹理坐标,是指一个坐标系,通过坐标系,可以查询出纹理的值
以简单2维纹理来说,(3维纹理也是2维纹理进行的扩展)
2维纹理坐标,左下角为原点,右向为+s轴,向上为+t轴
在坐标的s t坐标的0 - 1范围内保存纹理。
比如通过
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGB, 3, 3, 0, GLES30.GL_RGB, GLES30.GL_UNSIGNED_BYTE, pixelBuffer);
加载了一个3x3的纹理,纹理数据就是上面的9个颜色值。
则这时候纹理在纹理坐标中的表示为
纹理坐标0,0处的颜色数据为 绿色
纹理坐标0,1处的颜色数据为 红色
纹理坐标1,0处的颜色数据为 蓝色
纹理坐标1,1处的颜色数据为 红色
因为已经通过顶点绘制了一个矩形,若要把这些颜色填充给这个矩形,就需要通过设置纹理坐标与每个顶点的对应关系了。
private final float[] mVerticesData =
{
-0.5f, 0.5f, 0.0f, // Position 0
0.0f, 0.0f, // TexCoord 0
-0.5f, -0.5f, 0.0f, // Position 1
0.0f, 1.0f, // TexCoord 1
0.5f, -0.5f, 0.0f, // Position 2
1.0f, 1.0f, // TexCoord 2
0.5f, 0.5f, 0.0f, // Position 3
1.0f, 0.0f // TexCoord 3
};
编号为0 的顶点 是左上角的,编号为1 的顶点,是左下角的,编号为 2 的顶点,是右下角的,编号为 3 的顶点,是右上角的。
图中,坐标系为OPENGL顶点坐标系。
程序里,
将索引为0的顶点绑定的纹理坐标 0,0
也即左上角的顶点,使用0,0纹理坐标对应的颜色值
接下来,
将索引为1的顶点绑定的纹理坐标 0,1
也即左下角的顶点,使用0,1纹理坐标对应的颜色值
接下来,
将索引为2的顶点绑定的纹理坐标 1,1
也即右下角的顶点,使用1,1纹理坐标对应的颜色值
接下来,
将索引为3的顶点绑定的纹理坐标 1,0
也即右上角的顶点,使用1,0纹理坐标对应的颜色值
到这里,各顶点的纹理坐标都已经设置好了,中间的纹理颜色值会根据插值自动映射。
完整的映射如下图
这样,通过逐顶点指定纹理坐标的方式,整张纹理就可以映射到绘制的图形上了。
当然,纹理保存在纹理坐标的0-1的区间内,
为顶点指定纹理坐标,可以指定区间以外的值,比如指定(0,2)这样的纹理坐标
如下
private final float[] mVerticesData =
{
-0.5f, 0.5f, 0.0f, // Position 0
0.0f, 0.0f, // TexCoord 0
-0.5f, -0.5f, 0.0f, // Position 1
0.0f, 2.0f, // TexCoord 1
0.5f, -0.5f, 0.0f, // Position 2
2.0f, 2.0f, // TexCoord 2
0.5f, 0.5f, 0.0f, // Position 3
2.0f, 0.0f // TexCoord 3
};
这里,每个顶点指定的纹理坐标范围,扩大到了2*2的区域。这时候纹理应用的效果如下
可以看到,因为纹理坐标范围s,t指定的坐标值扩大了一倍,完整纹理映射了1/4的区域就已经完成了映射,超出纹理坐标的区域将纹理进行了重复,这个行为可以通过
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_REPEAT);
来设置超出纹理范围时的行为,当前设置的是从重复,纹理坐标会再从起始开始映射。
又如设置s方向的超出纹理坐标范围时,使用纹理坐标边界数据来处理,则代码为
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D,GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
综上,对OPENGL的纹理处理方式进行了初步的解读,希望能帮助大家理解如何加载纹理。
纹理应用很广泛,比如使用OPENGL 播放视频,是将视频帧数据作为纹理数据来设置到OPENGL绘制的形状上,如果不了解纹理坐标的工作方式,可能会出现视频影像倒置等BUG。
希望感兴趣的同学进行交流。真心推荐这个开源demo
是经典教程书籍《OPENGL ES 3.0 编程指南》配套的代码。自己下载下来编译运行一下,对这本书的理解会起到关键性的作用。
上述的项目包含整个OpenGL ES 3.0编程指南的例子,本次介绍的这个例子我也独立出来了一个demo工程放到了gtihub上,可以在AS上编译。