OpenglES2.0 for Android:再谈纹理映射
前言
上一节我们实现了一个简单的纹理映射的例子——一个简单的贴图,这节我们来做一些稍微复杂一点的例子,最后再给我们前面的立方体做一个纹理。
纹理拉伸
重复拉伸方式
这种是经常使用的一张纹理拉伸方式,常用于绘制一些重复的元素,比如我们在游戏绘制一幅方格式的地图时。使用重复拉伸方式使得纹理能够根据目标平
面的大小自动重复,这样既不会失去纹理图的效果,也可以节省内存。如下图所示:
面的大小自动重复,这样既不会失去纹理图的效果,也可以节省内存。如下图所示:
实现起来很简单,我们回到上节的项目,找到我们纹理的工具类TextureHelper.java ,修改如下 (TextureHelper.java):
package com.cumt.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.Log;
public class TextureHelper {
public static final String TAG = "TextureHelper";
public static int loadTexture(Context context,int resourceId,boolean isRepeat){
/*
* 第一步 : 创建纹理对象
*/
final int[] textureObjectId = new int[1];//用于存储返回的纹理对象ID
GLES20.glGenTextures(1,textureObjectId, 0);
if(textureObjectId[0] == 0){//若返回为0,,则创建失败
if(LoggerConfig.ON){
Log.w(TAG,"Could not generate a new Opengl texture object");
}
return 0;
}
/*
* 第二步: 加载位图数据并与纹理绑定
*/
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;//Opengl需要非压缩形式的原始数据
final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),resourceId, options);
if(bitmap == null){
if(LoggerConfig.ON){
Log.w(TAG,"ResourceId:"+resourceId+"could not be decoded");
}
GLES20.glDeleteTextures(1, textureObjectId, 0);
return 0;
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureObjectId[0]);//通过纹理ID进行绑定
if(isRepeat){
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
}
/*
* 第三步: 设置纹理过滤
*/
//设置缩小时为三线性过滤
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);
/*
* 第四步: 加载纹理到Opengl并返回ID
*/
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
return textureObjectId[0];
}
}
我们只做了两处修改,首先loadTexture新加一个布尔型参数isRepeat ,下面更加isRepeat的值来判断是否设置重复拉伸 。如果isRepeat为true则调用 :
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
设置纹理在S与T纬度都设置为重复拉伸 。
下面我们来设置绘制4*4个,我们只需将Square.java中的顶点坐标与纹理坐标的对应改为下面 所示 :
//矩形顶点坐标 与 纹理坐标
static float squareCoords[] = { //以三角形扇的形式绘制
//x y s t
-0.5f, 0.5f , 0 , 0 , // top left
0.5f, 0.5f , 4 , 0 ,// top right
0.5f, -0.5f , 4 , 4 ,// bottom right
-0.5f, -0.5f , 0 , 4}; // bottom left
此时我们的纹理在S轴范围为0 到 4 ,在T轴也是0到4 ,于是纹理在S轴方向重复四次,在T轴方向重复四次。下面使用loadTexture方法时传入true,
运行一下即可看到上面的效果。
截取拉伸方式
将上面的纹理工具类中的 if (isRepeat )条件下的代码改为 :
if(isRepeat){
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
}
其它代码不变 。我们将S轴方向与T轴方向都设置为截取拉伸 ,运行效果如下 :
能够看出在S轴方向与T轴方向纹理的边缘都被拉伸。当然也可以设置在S轴方向为重复拉伸,在T轴方向为截取拉伸。
if(isRepeat){
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
}
立方体添加纹理
前面一直在说二维图形的贴图,其实对于三维物体也是一样。关键在于找到顶点坐标与纹理坐标的关系。
接下来我们来给我们上次绘制的立方体添加纹理。
首先,将我们的上节的纹理工具类copy到立方体的工具类下 ,修改顶点着色器和片段着色器如下 :
//vertex_shader_cube.glsl
uniform mat4 u_Matrix;
attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;
varying vec2 v_TextureCoordinates;
void main()
{
gl_Position = u_Matrix * a_Position;
v_TextureCoordinates = a_TextureCoordinates;
}
//fragment_shader_cube.glsl
precision mediump float;
uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;
void main()
{
gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
}
最后修改我们的Cube类 ,添加纹理坐标,传入纹理坐标数据等 ,过程和上一节一样 ,我们直接看代码 :
package com.cumt.shape;
import static android.opengl.GLES20.GL_TEXTURE0;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.glActiveTexture;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glUniform1i;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import com.cumt.openglescubewen.R;
import com.cumt.utils.MatrixState;
import com.cumt.utils.ShaderHelper;
import com.cumt.utils.TextResourceReader;
import com.cumt.utils.TextureHelper;
import android.content.Context;
import android.opengl.GLES20;
public class Cube {
//顶点坐标
private FloatBuffer vertexBuffer;
private Context context;
//float类型的字节数
private static final int BYTES_PER_FLOAT = 4;
//共有72个顶点坐标,每个面包含12个顶点坐标
private static final int POSITION_COMPONENT_COUNT = 6*12;
// 数组中每个顶点的坐标数
private static final int COORDS_PER_VERTEX = 3;
// 颜色数组中每个颜色的值数
private static final String A_POSITION = "a_Position";
private static final String U_MATRIX = "u_Matrix";
private int uMatrixLocation;
private int aPositionLocation;
private int program;
private static final int TEXTURE_COORDIANTES_COMPONENT_COUNT = 2; //一个纹理坐标含有的元素个数
private static final int STRIDE = (COORDS_PER_VERTEX + TEXTURE_COORDIANTES_COMPONENT_COUNT)
* BYTES_PER_FLOAT;
private static final String A_TEXTURE_COORDINATES = "a_TextureCoordinates";//纹理
private static final String U_TEXTURE_UNIT = "u_TextureUnit";//纹理
private int uTextureUnitLocation;
private int aTextureCoordinates;
private int texture;
static float vertices[] = {
//X Y Z S T
//前面
0,0,1.0f, 0.5f,0.5f,
1.0f,1.0f,1.0f, 1.0f,0,
-1.0f,1.0f,1.0f, 0,0,
0,0,1.0f, 0.5f,0.5f,
-1.0f,1.0f,1.0f, 0,0,
-1.0f,-1.0f,1.0f, 0,1.0f,
0,0,1.0f, 0.5f,0.5f,
-1.0f,-1.0f,1.0f, 0,1.0f,
1.0f,-1.0f,1.0f, 1.0f,1.0f,
0,0,1.0f, 0.5f,0.5f,
1.0f,-1.0f,1.0f, 1.0f,1.0f,
1.0f,1.0f,1.0f, 1.0f,0,
//后面
0,0,-1.0f, 0.5f,0.5f,
1.0f,1.0f,-1.0f, 1.0f,0,
1.0f,-1.0f,-1.0f, 1.0f,1.0f,
0,0,-1.0f, 0.5f,0.5f,
1.0f,-1.0f,-1.0f, 1.0f,1.0f,
-1.0f,-1.0f,-1.0f, 0,1.0f,
0,0,-1.0f, 0.5f,0.5f,
-1.0f,-1.0f,-1.0f, 0,1.0f,
-1.0f,1.0f,-1.0f, 0,0,
0,0,-1.0f, 0.5f,0.5f,
-1.0f,1.0f,-1.0f, 0,0,
1.0f,1.0f,-1.0f, 1.0f,0,
//左面
-1.0f,0,0, 0.5f,0.5f,
-1.0f,1.0f,1.0f, 1.0f,0,
-1.0f,1.0f,-1.0f, 0,0,
-1.0f,0,0, 0.5f,0.5f,
-1.0f,1.0f,-1.0f, 0,0,
-1.0f,-1.0f,-1.0f, 0,1.0f,
-1.0f,0,0, 0.5f,0.5f,
-1.0f,-1.0f,-1.0f, 0,1.0f,
-1.0f,-1.0f,1.0f, 1.0f,1.0f,
-1.0f,0,0, 0.5f,0.5f,
-1.0f,-1.0f,1.0f, 1.0f,1.0f,
-1.0f,1.0f,1.0f, 1.0f,0,
//右面
1.0f,0,0, 0.5f,0.5f,
1.0f,1.0f,1.0f, 0,0,
1.0f,-1.0f,1.0f, 0,1.0f,
1.0f,0,0, 0.5f,0.5f,
1.0f,-1.0f,1.0f, 0,1.0f,
1.0f,-1.0f,-1.0f, 1.0f,1.0f,
1.0f,0,0, 0.5f,0.5f,
1.0f,-1.0f,-1.0f, 1.0f,1.0f,
1.0f,1.0f,-1.0f, 1.0f,0,
1.0f,0,0, 0.5f,0.5f,
1.0f,1.0f,-1.0f, 1.0f,0,
1.0f,1.0f,1.0f, 0,0,
//上面
0,1.0f,0, 0.5f,0.5f,
1.0f,1.0f,1.0f, 1.0f,0,
1.0f,1.0f,-1.0f, 1.0f,1.0f,
0,1.0f,0, 0.5f,0.5f,
1.0f,1.0f,-1.0f, 1.0f,1.0f,
-1.0f,1.0f,-1.0f, 0,1.0f,
0,1.0f,0, 0.5f,0.5f,
-1.0f,1.0f,-1.0f, 0,1.0f,
-1.0f,1.0f,1.0f, 0,0,
0,1.0f,0, 0.5f,0.5f,
-1.0f,1.0f,1.0f, 0,0,
1.0f,1.0f,1.0f, 1.0f,0,
//下面
0,-1.0f,0, 0.5f,0.5f,
1.0f,-1.0f,1.0f, 1.0f,0,
-1.0f,-1.0f,1.0f, 0,0,
0,-1.0f,0, 0.5f,0.5f,
-1.0f,-1.0f,1.0f, 0,0,
-1.0f,-1.0f,-1.0f, 0,1.0f,
0,-1.0f,0, 0.5f,0.5f,
-1.0f,-1.0f,-1.0f, 0,1.0f,
1.0f,-1.0f,-1.0f, 1.0f,1.0f,
0,-1.0f,0, 0.5f,0.5f,
1.0f,-1.0f,-1.0f, 1.0f,1.0f,
1.0f,-1.0f,1.0f , 1.0f,0,
};
public Cube(Context context){
this.context = context;
vertexBuffer = ByteBuffer
.allocateDirect(vertices.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
// 把坐标们加入FloatBuffer中
vertexBuffer.put(vertices);
// 设置buffer,从第一个坐标开始读
vertexBuffer.position(0);
getProgram();
aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION);
uMatrixLocation = GLES20.glGetUniformLocation(program, U_MATRIX);
aTextureCoordinates = GLES20.glGetAttribLocation(program, A_TEXTURE_COORDINATES);
uTextureUnitLocation = GLES20.glGetAttribLocation(program, U_TEXTURE_UNIT);
texture = TextureHelper.loadTexture(context, R.drawable.umei,false);
// Set the active texture unit to texture unit 0.
glActiveTexture(GL_TEXTURE0);
// Bind the texture to this unit.
glBindTexture(GL_TEXTURE_2D, texture);
// Tell the texture uniform sampler to use this texture in the shader by
// telling it to read from texture unit 0.
glUniform1i(uTextureUnitLocation, 0);
//---------传入顶点数据数据
GLES20.glVertexAttribPointer(aPositionLocation, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false, STRIDE, vertexBuffer);
GLES20.glEnableVertexAttribArray(aPositionLocation);
//设置从第二个元素开始读取,因为从第二个元素开始才是纹理坐标
vertexBuffer.position(COORDS_PER_VERTEX);
GLES20.glVertexAttribPointer(aTextureCoordinates, TEXTURE_COORDIANTES_COMPONENT_COUNT,
GLES20.GL_FLOAT, false, STRIDE, vertexBuffer);
GLES20.glEnableVertexAttribArray(aTextureCoordinates);
}
//获取program
private void getProgram(){
//获取顶点着色器文本
String vertexShaderSource = TextResourceReader
.readTextFileFromResource(context, R.raw.vertex_shader_cube);
//获取片段着色器文本
String fragmentShaderSource = TextResourceReader
.readTextFileFromResource(context, R.raw.fragment_shader_cube);
//获取program的id
program = ShaderHelper.buildProgram(vertexShaderSource, fragmentShaderSource);
GLES20.glUseProgram(program);
}
public void draw(){
GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, MatrixState.getFinalMatrix(),0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, POSITION_COMPONENT_COUNT);
}
}
运行结果如下 (屏幕方向设置为横向):
这里如上面所说,关键是找到顶点坐标与纹理坐标的对应。
我们以正面为例,按照我们的绘制方式正面我们实际上需要确定五个顶点对应的纹理坐标,这五个顶点分别是 :
-----------------------------------------------------------------------------------------------------------------------------------------------------
左上角 (-1 ,-1,1 ) 左下角 (-1,1,1 ) 右上角 (1 ,-1 , 1)
右下角 (1, 1 ,1 ) 中心点 (0 ,0 ,1)
------------------------------------------------------------------------------------------------------------------------------------------------------
对应的纹理的坐标依次为 : (0 , 1 ) (0 ,0 ) (1 ,1 ) (1, 0) (0.5 , 0.5 )要注意纹理的左上角的ST坐标并不是(0,0)而是(0,1)
否则我们绘制的纹理绘制上下颠倒的。