OpenGL学习教程 — 纹理
什么是纹理?
在前一节的教程中,我们使用了OpenGL完成了基本的图形绘制,这只是一个很简单基础图形,既不形象,也不逼真,为了使我们绘制的物体更加形象而生动,引入纹理这个东西,何为纹理,你可以理解为一张二维平面 贴图数据,我们使用顶点坐标绘制物体形状,顶点坐标 对应 至 纹理坐标,在光栅化阶段将纹理坐标插值到着色器中,最后在片段着色器中,使用采样器取出纹理坐标下的颜色元素,绘制到物体的片段中去,完成纹理绘制
纹理坐标
由上一段简单的阐释得知,我们需要了解纹理坐标是几何?
顶点坐标的原点是基于屏幕中心原点,X轴正方向向右,Y轴正方向向上,Z轴垂直于屏幕指向我们,而纹理坐标是以左上角为原点,范围在【0,1】,如下图:
顶点坐标如何对应纹理坐标?
即以屏幕上实际位置对应,两个坐标都应该对应屏幕上的同一个物理点,举个例子最容易懂,如上图我们把这个美女的纹理应用到屏幕上,我们使用条带Triangle方式绘制这个矩形,顶点坐标应该是:
vertex = {
-1, -1, //左下
1, -1, //右下
-1, 1, //左上
1, 1 //右上
};
而纹理坐标则应该是:
texture = {
0, 1,
1, 1,
0, 0,
1, 0
};
很简单吧!
如何将图片的数据贴到纹理上去?
纹理不仅仅用于图片数据显示,还可以把视频的一帧图像数据(也是图片)绑定,显示到图像上去,要知道如何绑定,需要掌握纹理的创建及使用过程,主要分为以下几个步骤
- glsl编程纹理信息
- 创建纹理
- 激活绑定使用纹理
glsl编程
------------vertex shader--------------
varying vec2 v_texture; //varying传递纹理坐标至片段着色器
attribute vec2 a_texture;
void main(){
v_texture = a_texture;
}
------------fragment shader--------------
precision mediump float;
varying vec2 v_texture; //传递过来的纹理坐标
uniform sampler2D u_TextureUnit; //对纹理进行取色的取色器
void main() {
gl_FragColor = texture2D(u_TextureUnit, v_texture); //取色函数
}
创建纹理
public static int[] loadTexture(Context context, int drawId){
int[] textureId = new int[1];
GLES20.glGenTextures(1, textureId, 0); //创建纹理
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), drawId, options);
//绑定纹理对象,当把纹理对象绑定到GL_TEXTURE_2D上,对它的操作都会影响到textureID上
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[1]);
//设置缩小放大过滤方式
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);
//设置图片
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
// 为当前绑定的纹理自动生成所有需要的多级渐远纹理
// 生成 MIP 贴图
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
// 解除与纹理的绑定,避免用其他的纹理方法意外地改变这个纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
return textureId;
}
创建纹理对象如上所示,重要的有两点,绑定图像数据到纹理对象和设置纹理过滤方式
绑定图像:
GLUtils.java
public static void texImage2D(int target, int level, Bitmap bitmap, int border);
target: 指定纹理单元的类型是哪一种,必须指定为 GL_TEXTURE_2D, GL_PROXY_TEXTURE_2D, GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, or GL_PROXY_TEXTURE_CUBE_MAP中的一个。二维纹理需要指定为GL_TEXTURE_2D
level: 暂时不知道,默认0,填1我测试图像显示不出来
bitmap: 图片
border: 纹理边框宽度,必须是0
除使用以上bitmap,也可以直接使用图像数据对纹理进行绑定,如下:
GLES20.glTexImage2D方法
过滤:
当我们设置纹理元素的每个单元到像素坐标上去时,不可能是一一对应,当3D物体旋转、缩放时有可能出现一对多和多对一的情况,简单来说当纹理放大时,4X4的纹理坐标元素,如何去对应一个8X8的像素坐标,怎么把纹理元素填充进去,这里就需要纹理过滤,过滤方式有4中:
- 最近采样点过滤 GL_NEAREST
- 线性纹理过滤 GL_LINEAR
- 三线性过滤 GL_LINEAR_MIPMAP_LINEAR
- 各向异性过滤
GL_NEAREST:
不采取任何处理,对每一个像素单元使用最接近他的纹理坐标单元进行采样,速度快,效果也差
GL_LINEAR:
对每一个像素单元采样最接近他的纹理坐标单元以及它附件2*2的纹理采样,使用他们的平均值,效果比第一种好
GL_LINEAR_MIPMAP_LINEAR:
三线过滤要主要要经过三次计算,使用这种过滤方式要先生成mipmap图,如果一张图片是3232大小,那么它会依次生成3232、1616、88、44、22、1*1的6张图,当我们使用像素单元时,选择最接近他的两层图进行线性过滤,最后生成线性插值到着色器中
以上是自己的一点理解,可以参考大神写得过滤介绍
激活使用纹理
int samper = GLES20.glGetAttribLocation(glProgram, "u_TextureUnit");
int textureIds = GLHelper.loadTexture(context);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0); //激活纹理单元
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds); //绑定纹理目标
GLES20.glUniform1i(samper, 0); //配置采样器对应采样哪个纹理单元
//解绑纹理目标
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
注意:GLES20.glUniform1i(samper, 0),这句为片段着色器中的采样器配置纹理单元,而纹理单元里面有已经激活的纹理,这样采样器就可以去着色了,所以如果有多个纹理,需要配置多个采样器分别对应他们的纹理单元
使用总结
每个设备纹理单元有限,使用GLES20.glActiveTexture(GLES20.GL_TEXTURE0)进行激活,纹理单元有 GLES20.GL_TEXTURE0、GLES20.GL_TEXTURE1等,每个纹理单元下有许多纹理目标GL_TEXTURE_2D、GL_CULL_FACE等,纹理目标又与我们的glGenTextures生成的纹理进行绑定,纹理则又通过texImage2D绑定图片数据
最后,献上我的纹理demo