前面几篇博客,我们将了Android中利用OpenGL ES 2.0绘制各种形体。但是我们看到的基于OpenGL开发的应用和游戏,可不仅仅是那些规则形体和一些简单的色彩构成,而是各种不规则的形体构成了现实世界或者卡通世界的人和事物,他们都是外面穿着漂亮“衣服”的。本篇博客就是来讲解这些“衣服”的基础的。这些衣服就是纹理贴图。
什么是纹理贴图
一般说来,纹理是表示物体表面的一幅或几幅二维图形,也称纹理贴图(texture)。当把纹理按照特定的方式映射到物体表面上的时候,能使物体看上去更加真实。当前流行的图形系统中,纹理绘制已经成为一种必不可少的渲染方法。在理解纹理映射时,可以将纹理看做应用在物体表面的像素颜色。在真实世界中,纹理表示一个对象的颜色、图案以及触觉特征。纹理只表示对象表面的彩色图案,它不能改变对象的几何形式。更进一步的说,它只是一种高强度的计算行为。——百度百科
比如我们在利用OpenGL做游戏的时候,加载了一个人物模型进来了,这个人物模型上是没有色彩的。我们需要给它绘上需要的色彩才行。但是这些色彩从哪里来呢?我们不可能像之前处理球体那样,根据顶点取生成需要的色彩,那样对于我们给这个人物模型绘色的工作量实在太大了。这个时候我们就需要用到纹理贴图的技术了——把一个纹理(对于2D贴图,可以简单的理解为图片),按照所期望的方式显示在诸多三角形组成的物体的表面。
纹理映射原理
启用纹理映射后,如果想把一幅纹理映射到相应的几何图元,就必须告诉GPU如何进行纹理映射,也就是为图元的顶点指定恰当的纹理坐标。纹理坐标用浮点数来表示,范围一般从0.0到1.0,左上角坐标为(0.0,0.0),右上角坐标为(1.0,0.0),左下角坐标为(0.0,1.0),右下角坐标为(1.0,1.0),如下图所示:
左图为纹理图和纹理坐标,右图为顶点图和顶点坐标。
将纹理映射到右边的两个三角形上(也就是一个矩形),需要将纹理坐标指定到正确的顶点上,才能使纹理正确的显示,否则显示出来的纹理会无法显示,或者出现旋转、翻转、错位等情况。
将右图顶点按照V2V1V4V3传入,以三角形条带方式绘制,则纹理坐标应按照V2V1V4V3传入。如果按照V3V4V1V2传入,会得到一个旋转了180度的纹理。如果按照V4V3V2V1传入,则会得到一个左右翻转的纹理。
显示图片
根据纹理映射原理,结合之前绘制正方形的经验,我们可以根据以下步骤利用OpenGL ES显示一张图片:
第一步,修改着色器
首先,我们需要修改我们的着色器,将顶点着色器修改为:
attribute vec4 vPosition;
attribute vec2 vCoordinate;
uniform mat4 vMatrix;
varying vec2 aCoordinate;
void main(){
gl_Position=vMatrix*vPosition;
aCoordinate=vCoordinate;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
可以看到,顶点着色器中增加了一个vec2变量,并将这个变量传递给了片元着色器,这个变量就是纹理坐标。接着我们修改片元着色器为:
precision mediump float;
uniform sampler2D vTexture;
varying vec2 aCoordinate;
void main(){
gl_FragColor=texture2D(vTexture,aCoordinate);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
片元着色器中,增加了一个sampler2D的变量,sampler2D我们在前一篇博客GLSL语言基础中提到过,是GLSL的变量类型之一的取样器。texture2D也有提到,它是GLSL的内置函数,用于2D纹理取样,根据纹理取样器和纹理坐标,可以得到当前纹理取样得到的像素颜色。
第二步,设置顶点坐标和纹理坐标
根据纹理映射原理中的介绍,我们将顶点坐标设置为:
private final float[] sPos={
-1.0f,1.0f, //左上角
-1.0f,-1.0f, //左下角
1.0f,1.0f, //右上角
1.0f,-1.0f //右下角
};
- 1
- 2
- 3
- 4
- 5
- 6
相应的,对照顶点坐标,我们可以设置纹理坐标为:
private final float[] sCoord={
0.0f,0.0f,
0.0f,1.0f,
1.0f,0.0f,
1.0f,1.0f,
};
- 1
- 2
- 3
- 4
- 5
- 6
第三步,计算变换矩阵
按照上步设置顶点坐标和纹理坐标,大多数情况下我们得到的一定是一张拉升或者压缩的图片。为了让图片完整的显示,且不被拉伸和压缩,我们需要向绘制等腰直角三角形一样,计算一个合适的变换矩阵,传入顶点着色器,代码如下:
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0,0,width,height);;
float ratio = height/width;
int w=mBitmap.getWidth();
int h=mBitmap.getHeight();
float sWH=w/(float)h;
float sWidthHeight=width/(float)height;
uXY=sWidthHeight;
//设置正交投影参数
if(width>height){
if(sWH>sWidthHeight){
Matrix.orthoM(mProjectMatrix, 0, -sWidthHeight*sWH,sWidthHeight*sWH, -1,1, 3, 5);
}else{
Matrix.orthoM(mProjectMatrix, 0, -sWidthHeight/sWH,sWidthHeight/sWH, -1,1, 3, 5);
}
}else{
if(sWH>sWidthHeight){
Matrix.orthoM(mProjectMatrix, 0, -1, 1, -1/sWidthHeight*sWH, 1/sWidthHeight*sWH,3, 5);
}else{
Matrix.orthoM(mProjectMatrix, 0, -1, 1, -sWH/sWidthHeight, sWH/sWidthHeight,3, 5);
}
}
// Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
//设置相机位置
//eyex,eyey,eyez 代表相机在坐标轴的位置,centerX,centerY,centerZ代表物体的中心坐标,upX,upY,upZ代表相机往哪边看
//设置相机位置
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 5.0f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
//计算变换矩阵
Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
}
mMVPMatrix即为我们所需要的变换矩阵。
第四步,显示图片
然后我们需要做的,就和之前绘制正方形一样容易了。和之前不同的是,在绘制之前,我们还需要将纹理和纹理坐标传入着色器:
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT);
//告知OpenGL所要使用的Program
GLES20.glUseProgram(mProgramId);
//允许指向顶点数据
GLES20.glEnableVertexAttribArray(mPositionId);
//传入顶点数据
GLES20.glVertexAttribPointer(mPositionId, 3,
GLES20.GL_FLOAT, false,
3*4, vertexBuffer);
//启用指向纹理数据的句柄
GLES20.glEnableVertexAttribArray(glHCoordinate);
// GLES20.glUniform1i(glVTexture, 0);
createTexture();
//传入纹理坐标
GLES20.glVertexAttribPointer(glHCoordinate,2,GLES20.GL_FLOAT,false,2*4,textureBuffer);
//指定vMatrix的值
GLES20.glUniformMatrix4fv(mMatrixHandler,1,false,mMVPMatrix,0);
//索引法绘制正方形
GLES20.glDrawElements(GLES20.GL_TRIANGLES,index.length, GLES20.GL_UNSIGNED_SHORT,indexBuffer);
//禁用指向三角形的顶点数据
GLES20.glDisableVertexAttribArray(mPositionId);
}
这样我们就可以显示出我们需要显示的图片,并且保证它完整的居中显示而且不会变形了,如下图: