#.纹理介绍:
可以简单想象成一张皮,可以贴在OpenGL空间中自己指定的区域之上,从而实现需要的视觉效果。
##.使用纹理的好处:
如果想让图形看起来更真实,就必须有足够多的顶点,还要设置相应的颜色属性,会产生很多性能开销。
而如果是使用在指定的位置贴上一层纹理的方式,不需要非常多顶点,只需要纹理绘制满足需要,OpenGl中顶点位置与纹理对应位置关联正确。就可以达到需要的细节显示效果。
##."需要蒙皮"位置与纹理的对应关系
纹理有自己内部的坐标系,纹理上每个点都有相应坐标。OpenGl世界空间上,所有复杂图形都是由一堆三角形拼凑成的。
所以,只要为每个三角形的三个顶点,都设定好对应纹理坐标系上哪个坐标就行了。这样,每个三角形就知道了对应纹理上哪个区域。根据这个对应关系,片元着色器在着色时,每个片元会去纹理上对应的坐标点去采样颜色,这样每个片元就知道了自己该显示什么颜色,它们合在一起就是最终显示的画面。
例如一个常见的应用场景:需要将相机拍摄的画面,经过OpenGL处理后,正确显示到手机屏幕上 & 正确输出给第三方。
相机拍出画面存储在SurfaceTexture中,直接会转换为一个Android OpenGL外部纹理,形状是个矩形。
我们回归到上面所讲的那些原理,这个场景其实不涉及到很多的顶点,其中:
我们需要的模型就是四个顶点构成的矩形,在世界坐标系上,占满z=0对应的整个平面空间;
我们使用默认的观察空间坐标系,即与世界坐标系重合,观察视角为Z轴负反向、正交投影;
我们需要考虑源纹理与输出区域可能大小不同需要裁剪,因此需要设置正确的裁剪矩阵,控制哪些部分可见;
然后,相机纹理可能需要偏转、镜像翻转等处理,在计算纹理采样坐标时,需要乘以对应的变换矩阵。
##.OpenGL ES中纹理使用要点:
1.纹理Id:
纹理的直接引用
2.纹理单元:
可以理解为是纹理的操作容器,他们的值依次为GLES20.GL_TEXTURE0、GL_TEXTURE1、GL_TEXTURE2等。
一台设备上的纹理单元的数量有限的,一般最多16个,意味着最多只能同时操作16个纹理,不同的设备纹理单元最大值有时候不同。
激活纹理单元的时候,使用glActiveTexture方法。
3.纹理目标:
一个纹理单元可绑定不同类型的纹理目标,例如GL_TEXTURE_2D、samplerExternalOES等。
要对某张纹理进行操作(例如要将某个Bitmap绘制到指定纹理上),先要把其纹理ID绑定到指定的纹理目标上,
然后才开启它作为纹理的生命周期,进行操作。一旦将一个纹理绑定到某个目标后,它的类型在它被回收前都不能改变。
4.使用流程示例:
4.1在着色器编译代码中声明一个操作指定类型纹理目标的纹理单元:
"uniform sampler2D uTexture2D;"
4.2激活某个纹理单元,例如纹理单元1
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
4.3将已激活的纹理单元的纹理操作目标,与指定纹理Id相绑定
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture2DId);
4.4将激活的纹理单元,例如纹理单元1,赋给着色器中对应的纹理单元变量
GLES20.glUniform1i(uTexture2DLoc, 1);
这样着色器中的纹理单元,就与要操作的纹理绑定上了。
##.关于Android中OpenGl纹理坐标系的一些思考和总结:
1.OpenGL中的纹理都是以左下角为原点(0,0),向右为x轴正方向,向上为y轴正方向,在Android中也是一样。
(以下考虑一个矩形贴二维纹理的场景。)
网上很多资料说Android中Bitmap生成的2D纹理坐标原点在左上角,所以映射时需要上下翻转;
而其它类型纹理的坐标原点都在右下角,所以只需要偏转,映射时不需要上下翻转;
这种说法应该有问题,它无法很好地解释部分现象,例如:
把屏幕中任务栏旋转到手机右侧,以后置摄像头拍摄得到OES纹理,这种情况下得到的纹理需要的偏转角度为0,
按照上面的说法,应该是不加上下翻转地直接映射。但是实际中这样处理,看到的画面会是上下颠倒的。
2.事实上,Android中所有类型OpenGL纹理的坐标原点(0,0)都是左下角,
之所以出现纹理画面需要上下翻转,实际上原因出在把二维图像绘制到纹理的过程中
因为Android中图片的默认坐标系原点是左上角,而所有OpenGL纹理坐标系原点都在右下角,
所以是生成的纹理中图像跟原图像相比,发生了上下颠倒,因此将纹理映射到顶点时要上下翻转,才能翻转回原图像;
在相机画面被绘制到OES纹理的过程中,同样出现了画面上下颠倒,这也就是上面举的例子中为何画面会颠倒。
手机屏幕在各种不同旋转方向下,绘制到纹理中的画面相对于人眼看到的画面可能会有不同的偏转。
可以自己写矩阵,来矫正纹理采样坐标,但直接通过API就能获取到,在相机预览偏转角度设置正确的情况下,
相机生成的纹理通过SurfaceTexture.getTransform(Matrix)就能获取正确的处理矩阵,
可以完美的把纹理画面处理成OpenGl纹理坐标系中方向正确的画面,顶点使用这个矩阵变换后的纹理采样坐标,画面会是正确的。
3.OpenGL中最终经过各层处理后得到的输出二维画面,使用的是OpenGl归一化坐标系,其坐标原点位置与坐标轴方向与OpenGl世界坐标系相同,与Android中View中的坐标系原点位置以及y轴方向都不同,但在显示时候,OpenGl会自己计算这个二维画面中每个坐标点应该显示在View坐标系中哪个位置,不需要我们来处理。
##.多层纹理绘制,两种思路:
(例如绘制直播画面水印时,一般就会用到多层纹理。)
1.开启成图像叠加绘制模式,该模式下可重复绘制多次,后面绘制的不会覆盖前面绘制的。若不开启,后面绘制的会完全覆盖掉前面的。
* //开启GL的混合模式,即图像叠加
* GLES20.glEnable(GLES20.GL_BLEND);
* GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
* ...
* 然后可以多次调用GLES20.glDrawXXX()绘制多层画面。
2.自己在着色器中写代码判断,每个区域该去那个纹理采样并且要计算好对应的采样坐标。
##.纹理初始化和使用示例:
1.初始化示例:
//生成两纹理ID,这里必须传入一个数组,会生成对应数量的纹理Id放在数组中
int[] textureIds = new int[2];
GLES20.glGenTextures(2, textureIds, 0);
GLESUtils.checkGlError("glGenTextures");
//生成指定类型纹理对象,与纹理ID绑定,并进行相关的参数设置
//samplerExternalOES格式的纹理
GLESUtils.bindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureIds[0]);
mTextureOESId = textureIds[0];
//sampler2D格式的纹理
GLESUtils.bindTexture(GLES20.GL_TEXTURE_2D, textureIds[1]);
mTexture2DId = textureIds[1];
自定义的bindTexture()方法:
/**
* 生成指定类型纹理对象,与纹理ID绑定,并进行相关的参数设置
* @param textureType 纹理类型
* @param textureId 纹理Id
*/
public static void bindTexture(int textureType, int textureId){
//绑定GL_TEXTURE_2D
GLES20.glBindTexture(textureType, textureId);
//设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
GLES20.glTexParameteri(textureType,
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
//设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
GLES20.glTexParameteri(textureType,
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
//设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。
GLES20.glTexParameteri(textureType,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
//设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。
GLES20.glTexParameteri(textureType,
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
}
2.绘制时的使用示例:
// 设置OpenGL视口的位置与大小, 最终图像会被输出到屏幕或目标纹理的视口对应的区域显示
// 例如图像最终被渲染到GlSurfaceView上,则在此处定义的是显示在GlSurfaceView的Surface上的哪个区域
// 注意该API所采用的坐标系,与OpenGl中的默认纹理坐标系方向相同,
// 即以Surface的左下角为原点(0,0),向右为x轴正方向,向上为y轴正方向.
// OpenGl世界坐标系上经过层层矩阵变换,最后被裁剪空间选中的部分会投射出一副二维画面显示在这个区域上,
// 这张输出画面会拉伸显示到这个区域上,但若设置的视口区域超出实际Surface,超出的部分不会显示。
GLES20.glViewport(0,0, outputWidth, outputHeight);
//设置背景颜色
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
//清除深度缓冲区
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
//.................其它处理........................
//以下是将纹理绑定到着色器中变量对应的纹理单元上
//激活纹理单元1
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
//将当前激活的纹理单元,即纹理单元0,与纹理mTexture2DId相绑定
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, srcTextureId);
//将纹理单元1,赋值给着色器中对应的纹理单元变量
GLES20.glUniform1i(uTexture2DLoc, 1);
//绘制图形:复杂图形式通过绘制多个三角形拼接成的。
//自定义三角形绘制顺序,在DRAW_INDEX中定义自己的绘制顺序
GLES20.glDrawElements(GLES20.GL_TRIANGLES, DRAW_INDEX.length, GLES20.GL_UNSIGNED_SHORT, mDrawIndexBuffer);
//解绑用到的纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);