1、资源:首先你需要一张纹理图片,在OpenGL ES中的纹理图片的尺寸是有要求的,它的高度和宽度必须为2的n次方,比如32x32、256x512等。图片不等于纹理,有了图片,还要经过加工才能当做纹理使用。
public int initTexture(GL10 gl,int textureId)//textureId是资源图片的ID
{
int[] textures = new int[1]; //定义纹理数组,用来存放纹理ID
gl.glGenTextures(1, textures, 0); //生成纹理ID,放入textures,此时已经可以通过ID获取该纹理,但是纹理内容还是null
int currTextureId=textures[0]; //当前纹理ID
gl.glBindTexture(GL10.GL_TEXTURE_2D, currTextureId); //根据纹理ID绑定该纹理,也就是下面的操作将是针对该纹理的
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_NEAREST);//下面4行是在真正加工纹理前的参数设定
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_LINEAR); //东西较多,留在下面纹理高级部分再详解
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,GL10.GL_REPEAT);
InputStream is = this.getResources().openRawResource(textureId);//下面是通过流生成Bitmap,不解释
Bitmap bitmapTmp;
try
{
bitmapTmp = BitmapFactory.decodeStream(is);
}
finally
{
try
{
is.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmapTmp, 0);//真正把纹理图片按照前面的设定加工成可以使用的纹理
bitmapTmp.recycle();//图片回收
return currTextureId;//返回纹理ID
}
2、纹理坐标:这是贴图的精髓也是难点。纹理是披在模型表面的花衣(有‘皮之不存,毛将焉附’的感觉),在OpenGL ES中,模型最终是由许许多多的三角形拼成的,而每· 个三角形都是由三个顶点定义的。既然纹理是依赖于模型的,可想而知,纹理坐标也是对应于模型的顶点坐标的。顶点坐标是针对3D坐标系而言的,纹理坐标是这样规定的:左上(0,0),右上(1,0),左下(0,1),右下(1,1),中间(0.5,0.5)其他依次类推。在模型定义完或者模型定义中,为每个顶点指定对应的纹理坐标:是这步的重心。
三角形纹理坐标定义为例:
//顶点坐标数据的初始化================begin============================
final int UNIT_SIZE=30000;
vCount=3;//顶点的数量
int vertices[]=new int[]//顶点坐标数据数组
{
2*UNIT_SIZE,0,0,
-2*UNIT_SIZE,0,0,
0,4*UNIT_SIZE,0
};
//创建顶点坐标数据缓冲
//vertices.length*4是因为一个整数四个字节
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
vbb.order(ByteOrder.nativeOrder());//设置字节顺序
mVertexBuffer = vbb.asIntBuffer();//转换为int型缓冲
mVertexBuffer.put(vertices);//向缓冲区中放入顶点坐标数据
mVertexBuffer.position(0);//设置缓冲区起始位置
//特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer
//转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题
//顶点坐标数据的初始化================end============================
//顶点纹理数据的初始化================begin============================
float textureCoors[]=new float[]//顶点纹理S、T坐标值数组
{
0,1,
1,1,
0.5f,0
};
//创建顶点纹理数据缓冲
//textureCoors.length×4是因为一个float型整数四个字节
ByteBuffer cbb = ByteBuffer.allocateDirect(textureCoors.length*4);
cbb.order(ByteOrder.nativeOrder());//设置字节顺序
mTextureBuffer = cbb.asFloatBuffer();//转换为int型缓冲
mTextureBuffer.put(textureCoors);//向缓冲区中放入顶点着色数据
mTextureBuffer.position(0);//设置缓冲区起始位置
//特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer
//转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题
//顶点纹理数据的初始化================end============================
这些顶点是直接给出来的,更多的模型的顶点是计算出来的(比如圆是将经纬度微分之后计算出每个顶点的),这里不讲建模。
3、纹理调用:使用也是比较简单的,还是屁颠的跟着顶点坐标就行。
//顶点坐标==========begin=============================================
//允许使用顶点数组
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
//为画笔指定顶点坐标数据
gl.glVertexPointer
(
3, //每个顶点的坐标数量为3 xyz
GL10.GL_FIXED, //顶点坐标值的类型为 GL_FIXED
0, //连续顶点坐标数据之间的间隔
mVertexBuffer //顶点坐标数据
);
//顶点坐标==========end===============================================
//纹理===========begin================================================
//开启纹理
gl.glEnable(GL10.GL_TEXTURE_2D);
//允许使用纹理数组
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
//为画笔指定纹理uv坐标数据
gl.glTexCoordPointer
(
2, //每个顶点两个纹理坐标数据 S、T
GL10.GL_FLOAT, //数据类型
0, //连续纹理坐标数据之间的间隔
mTextureBuffer //纹理坐标数据
);
//为画笔绑定指定名称ID纹理
gl.glBindTexture(GL10.GL_TEXTURE_2D,textureId);
//纹理===========end==================================================
//绘制图形
gl.glDrawArrays
(
GL10.GL_TRIANGLES,
0,
vCount
);
gl.glDisable(GL10.GL_TEXTURE_2D);//关闭纹理
1、纹理拉伸
你的纹理尺寸不可能总是和你的目标面积一样大小,有时需要把纹理贴到一张大目标上,有时需要贴到小目标上。这是有三种选择:
(1)将纹理拉伸,这样会使纹理模糊,效果不好
(2)将目标切分成和纹理大小一样的小块,,每块贴一张纹理,这样要存储的冗余顶点太多,消耗太大
(3)纹理在不被拉伸或者压缩的情况下,重复地贴到目标上。
举个例子,如果你要给大海贴图,你能准备一张和大海一样大小的纹理给它贴上去么,显然不合适,也没必要。找一张水波图,重复的贴上去就ok了。
要实现上面的目标要做两件事:
(1)上面提到过在生成纹理之前有些属性参数需要先定义好。其中有两个参数指定时要注意:
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,GL10.GL_REPEAT);
最后一个参数指定为GL10.GL_REPEAT,这样纹理就允许重复,如果设置成GL_CLAMP_TO_EDGE那就是边缘拉伸了(也就是说你贴上去的纹理会贴到目标的左上角,但是空白处会不断复制你的纹理的边缘去填充,没图,想象不出来就别想了)
(2)纹理坐标的范围不再是0-1,而是0-n,n就是你希望重复的次数。
2、纹理过滤
要理解什么是纹理过滤,首先需要弄明白一个道理或者说走出一个误区:纹理贴图就是把一张二维图片画到三维空间上去么?这只是一个笼统肤浅的认识。所谓纹理映射是指不断的从纹理图像中按照某种方式(过滤方式)取出像素然后绘制到相应的目标映射区域中。这样纹理过滤的概念就清晰了,它就是从纹理图像中选取像素的方法。
(1)最近点采样
这是一种最简单的粗狂的过滤方式。原理就是计算出目标映射区域中像素对应的纹理坐标,然后根据纹理坐标从纹理图像中选取相应像素进行映射。很显然如果映射目标和纹理图像的尺寸是一样的,也就是像素是一一对应的,那这必然是最省事最好的过滤方式了,然而这种情况太少了,在三维世界中近大远小是不断变化的,一旦映射目标和纹理本身尺寸相差较大,这种方式的效果是最差的。
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_NEAREST);
如上在图像绘制时小于贴图的原始尺寸时采用的方式。
(2)线性过滤
这种方式比最近点采样方式稍微复杂点,它不是简单的从纹理坐标中选取一个像素点来作为映射像素,而是以该像素为中心像素选取周围2x2的像素区域,然后把这区域中的像素做加权平均计算出一个平均像素点作为映射像素。这种做法的效果也很明显,提高了承担“映射目标与纹理像素尺寸差距大”的风险,同时它能让贴图更加平滑。
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_LINEAR);
如上在图像绘制时大于贴图的原始尺寸时采用的方式。
(3)mipmap 多重细节层
mipmap是一种更复杂更智能的过滤方式,它会生成一系列的纹理,用来适配镜头与物体距离的差异,在渲染效率和真实感上都有很好的表现。缺点是他是以消耗内存为代价的。
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10_GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR_MIPMAP_NEAREST);
大目标MAG的设置类似。
设置完后还需要有个生成mipmap系列纹理的命令:
((GL11)gl).glTexParameterf(GL10.GL_TEXTURE_2D,GL11.GL_GENERATE_MIPMAP,GL10_GL_TRUE);
纹理部分到此结束!