OpenGL ES 纹理
使用前面学过的技术已经可以利用OpenGL ES构建立体图形,并通过顶点着色器和片元着色器对其进行各种变化呢和光照等效果使得三维效果更加真实,实际上我看看到很多的3D游戏漂亮多了,那是因为有各种各样的漂亮的图像带给人很多视觉盛宴,这篇文章在前面的基础上,增加物体的表面贴图,使得物体更加好看。
纹理概念
纹理用来表示图像照片或者说一系列的数据,使用纹理可以使物体用用更多的细节。OpenGL ES 2.0 中有两种贴图:二维纹理和立方体纹理。
每个二维纹理都由许多小的纹理元素组成,类似与片元和像素,使用纹理最简单的方式就是直接从一个图像加载数据。在OpenGL中规定纹理图像的左下角由
OpenGL ES 2.0不必是正方形,但是每个维度都应该是2的幂
在Android中使用的OpenGL ES的纹理坐标系跟官方的纹理坐标系统不一样,在Android中使用官方的纹理坐标系统,得到的结果是相反的,而是左上角是
二维纹理映射的原理
使用纹理就是在纹理图中进行采样,因此需要将选定的纹理坐标穿进顶点着色器,经过插值在片元着色器中从纹理图中的指定位置采样即可,纹理图的数据通过往片元插值器传递纹理单元指定的。
纹理对象和纹理加载
创建一个纹理对象,保存渲染所需的纹理数据,例如图像数据、过滤模式、包装模式。创建生成纹理对象的函数
public static native void glGenTextures(
int n, // 指定要生成的纹理对象的数量
int[] textures, // 保存纹理对象ID的数组
int offset
);
纹理对象在应用程序中不再使用时,需要删除。
public static native void glDeleteTextures(
int n, // 指定要删除的纹理数量
int[] textures, // 保存待删除的纹理ID的数组
int offset
);
纹理对象的 ID 必须是 glGenTextures 产生的,一旦生成纹理ID,就必须绑定纹理对象才能继续进行后续的操作。后续的操作将影响绑定的纹理对象。一旦纹理被绑定到一个特定的纹理目标,再删除之前就一直保持着绑定状态。
public static native void glBindTexture(
int target, // 绑定纹理对象到目标 GL_TEXTURE_2D 或 GL_TEXTURE_CUBE_MAP
int texture // 要绑定的纹理对象ID
);
激活某个纹理单元
public static native void glActiveTexture(
int texture // 要激活的纹理单元
);
对这两个函数的理解:显卡中有N个纹理单元(GL_TEXTURE0,GL_TEXTURE1,GL_TEXTURE2…),每个纹理单元中保存着很多纹理目标(targetTexture1D,targetTexture2D,targetTexture3D,targetTextureCube…),OpenGL ES 2.0貌似只支持了targetTexture2D和targetTextureCube。
纹理单元TextureUnit的定义如下
struct TextureUnit
{
GLuint targetTexture1D;
GLuint targetTexture2D;
GLuint targetTexture3D;
GLuint targetTextureCube;
...
};
glActiveTexture函数就是设置当前活动的纹理单元
TextureUnit textureUnits[GL_MAX_TEXTURE_IMAGE_UNITS]
GLuint currentTextureUnit = 0;
// ...
void glActiveTexture(GLenum textureUnit)
{
currentTextureUnit = textureUnit - GL_TEXTURE0 ;
}
glBindTexture函数就是将纹理对象ID赋值给当前活动的纹理单元的对应的目标纹理。
void glBindTexture(GLenum textureTarget, GLuint textureObject)
{
TextureUnit *texUnit = &textureUnits[currentTextureUnit];
switch(textureTarget)
{
case GL_TEXTURE_1D: texUnit->targetTexture1D = textureObject; break;
case GL_TEXTURE_2D: texUnit->targetTexture2D = textureObject; break;
case GL_TEXTURE_3D: texUnit->targetTexture3D = textureObject; break;
case GL_TEXTURE_CUBEMAP: texUnit->targetTextureCube = textureObject; break;
}
}
获取一副图片的纹理数据
public static void texImage2D(int target, // 常数GL_TEXTURE_2D
int level, // 表示多级分辨率的纹理图像的级数,若只有一种分辨率,则level设为0。
Bitmap bitmap,
int border // 边框,一般设为0
)
其他纹理选项的设置使用glTexParameterf系列函数
public static native void glTexParameterf(
int target,
int pname, // 设定的参数,可以是GL_TEXTURE_MAG_FILTER,GL_TEXTURE_MIN_FILTER,GL_TEXTURE_WRAP_S,GL_TEXTURE_WRAP_T
float param // 参数对应的值
);
应用纹理的例子
对前面的立方体的每个面应用一张图片作为纹理贴图,效果图(这个纹理图是哪个老师来着?)
Rectangle.java
public class Rectangle {
private FloatBuffer mVertexBuffer;
private int mProgram;
private int mPositionHandle;
private int muMVPMatrixHandle;
private int mColorHandle;
private int muMMatrixHandle;
private int muLightLocationHandle;
private int mTextureCoordHandle;
private int textureId;
private int muTextureHandle;
private Context mContext;
public Rectangle(Context context) {
this.mContext = context;
initVetexData();
}
public void initVetexData() {
float vertices[] = new float[] {
// 顶点 颜色 纹理坐标
//前面
0, 0, 1, 1,1,1,0, 0.5f, 0.5f,
1, 1, 1, 1,0,0,0, 1.0f, 0.0f,
-1, 1, 1, 1,0,0,0, 0.0f, 0.0f,
0, 0, 1, 1,1,1,0, 0.5f, 0.5f,
-1, 1, 1, 1,0,0,0, 0.0f, 0.0f,
-1,-1, 1, 1,0,0,0, 0.0f, 1.0f,
0, 0, 1, 1,1,1,0, 0.5f, 0.5f,
-1,-1, 1, 1,0,0,0, 0.0f, 1.0f,
1,-1, 1, 1,0,0,0, 1.0f, 1.0f,
0, 0, 1, 1,1,1,0, 0.5f, 0.5f,
1,-1, 1, 1,0,0,0, 1.0f, 1.0f,
1, 1, 1, 1,0,0,0, 1.0f, 0.0f,
//后面
0, 0,-1, 1,1,1,0, 0.5f, 0.5f,
1, 1,-1, 1,0,0,0, 1.0f, 0.0f,
1,-1,-1, 1,0,0,0, 0.0f, 0.0f,
0, 0,-1, 1,1,1,0, 0.5f, 0.5f,
1,-1,-1, 1,0,0,0, 0.0f, 0.0f,
-1,-1,-1, 1,0,0,0, 0.0f, 1.0f,
0, 0,-1, 1,1,1,0, 0.5f, 0.5f,
-1,-1,-1, 1,0,0,0, 0.0f, 1.0f,
-1, 1,-1, 1,0,0,0, 1.0f, 1.0f,
0, 0,-1, 1,1,1,0, 0.5f, 0.5f,
-1, 1,-1, 1,0,0,0, 1.0f, 1.0f,
1, 1,-1, 1,0,0,0, 1.0f, 0.0f,
//左面
-1, 0, 0, 1,1,1,0, 0.5f, 0.5f,
-1, 1, 1, 1,0,0,0, 1.0f, 0.0f,
-1, 1,-1, 1,0,0,0, 0.0f, 0.0f,
-1, 0, 0, 1,1,1,0, 0.5f, 0.5f,
-1, 1,-1, 1,0,0,0, 0.0f, 0.0f,
-1,-1,-1, 1,0,0,0, 0.0f, 1.0f,
-1, 0, 0, 1,1,1,0, 0.5f, 0.5f,
-1,-1,-1, 1,0,0,0, 0.0f, 1.0f,
-1,-1, 1, 1,0,0,0, 1.0f, 1.0f,
-1, 0, 0, 1,1,1,0, 0.5f, 0.5f,
-1,-1, 1, 1,0,0,0, 1.0f, 1.0f,
-1, 1, 1, 1,0,0,0, 1.0f, 0.0f,
//右面
1, 0, 0, 1,1,1,0, 0.5f, 0.5f,
1, 1, 1, 1,0,0,0, 1.0f, 0.0f,
1,-1, 1, 1,0,0,0, 0.0f, 0.0f,
1, 0, 0, 1,1,1,0, 0.5f, 0.5f,
1,-1, 1, 1,0,0,0, 0.0f, 0.0f,
1,-1,-1, 1,0,0,0, 0.0f, 1.0f,
1, 0, 0, 1,1,1,0, 0.5f, 0.5f,
1,-1,-1, 1,0,0,0, 0.0f, 1.0f,
1, 1,-1, 1,0,0,0, 1.0f, 1.0f,
1, 0, 0, 1,1,1,0, 0.5f, 0.5f,
1, 1,-1, 1,0,0,0, 1.0f, 1.0f,
1, 1, 1, 1,0,0,0, 1.0f, 0.0f,
//上面
0, 1, 0, 1,1,1,0, 0.5f, 0.5f,
1, 1, 1, 1,0,0,0, 1.0f, 0.0f,
1, 1,-1, 1,0,0,0, 0.0f, 0.0f,
0, 1, 0, 1,1,1,0, 0.5f, 0.5f,
1, 1,-1, 1,0,0,0, 0.0f, 0.0f,
-1, 1,-1, 1,0,0,0, 0.0f, 1.0f,
0, 1, 0, 1,1,1,0, 0.5f, 0.5f,
-1, 1,-1, 1,0,0,0, 0.0f, 1.0f,
-1, 1, 1, 1,0,0,0, 1.0f, 1.0f,
0, 1, 0, 1,1,1,0, 0.5f, 0.5f,
-1, 1, 1, 1,0,0,0, 1.0f, 1.0f,
1, 1, 1, 1,0,0,0, 1.0f, 0.0f,
//下面
0,-1, 0, 1,1,1,0, 0.5f, 0.5f,
1,-1, 1, 1,0,0,0, 1.0f, 0.0f,
-1,-1, 1, 1,0,0,0, 0.0f, 0.0f,
0,-1, 0, 1,1,1,0, 0.5f, 0.5f,
-1,-1, 1, 1,0,0,0, 0.0f, 0.0f,
-1,-1,-1, 1,0,0,0, 0.0f, 1.0f,
0,-1, 0, 1,1,1,0, 0.5f, 0.5f,
-1,-1,-1, 1,0,0,0, 0.0f, 1.0f,
1,-1,-1, 1,0,0,0, 1.0f, 1.0f,
0,-1, 0, 1,1,1,0, 0.5f, 0.5f,
1,-1,-1, 1,0,0,0, 1.0f, 1.0f,
1,-1, 1, 1,0,0,0, 1.0f, 0.0f
};
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
mVertexBuffer = vbb.asFloatBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);
int vertexShader = loaderShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loaderShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, vertexShader);
GLES20.glAttachShader(mProgram, fragmentShader);
GLES20.glLinkProgram(mProgram);
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
muMMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMMatrix");
muLightLocationHandle = GLES20.glGetUniformLocation(mProgram, "uLightLocation");
muTextureHandle = GLES20.glGetUniformLocation(mProgram, "uTexture");
initTexture();
}
// 初始化纹理
public void initTexture() {
int [] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
textureId = textures[0];
// 激活纹理单元,默认激活的就是0号纹理单元
//GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 将纹理对象ID绑定到当前活动的纹理单元0上的GL_TEXTURE_2D目标
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
// 后面对纹理的设置都是对绑定了的纹理所生效的
//缩小采样使用最近点采样
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
//缩小采样使用最近点采样
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
//纹理包裹拉伸方式在st轴采用截取拉伸方式,这些设置指的是对坐标范围超过1的部分的限制
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);
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.texture);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
// 图片已经加载到了显存,可以回收
bitmap.recycle();
}
public void draw() {
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 12*6);
}
public void setValue(float[] mvpMatrix, float[] mMatrix) {
GLES20.glUseProgram(mProgram);
mVertexBuffer.position(0);
GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, (4+3+2) * 4, mVertexBuffer);
mVertexBuffer.position(3);
GLES20.glVertexAttribPointer(mColorHandle, 4, GLES20.GL_FLOAT, false, (4+3+2) * 4, mVertexBuffer);
mVertexBuffer.position(7);
GLES20.glVertexAttribPointer(mTextureCoordHandle, 2, GLES20.GL_FLOAT, false, (4+3+2) * 4, mVertexBuffer);
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glEnableVertexAttribArray(mColorHandle);
GLES20.glEnableVertexAttribArray(mTextureCoordHandle);
GLES20.glUniform3f(muLightLocationHandle, 0, 0, 20);
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mvpMatrix, 0);
GLES20.glUniformMatrix4fv(muMMatrixHandle, 1, false, mMatrix, 0);
// 将使用的纹理单元0传递给片元着色器
GLES20.glUniform1i(muTextureHandle, 0);
}
private int loaderShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
private String vertexShaderCode = "uniform mat4 uMVPMatrix;"
+ "attribute vec2 aTextureCoord;"
+ "varying vec2 vTextureCoord;"
+ "uniform mat4 uMMatrix;"
+ "uniform vec3 uLightLocation;"
+ "attribute vec4 aColor;"
+ "varying vec4 vColor;"
+ "varying vec4 vDiffuse;"
+ "attribute vec3 aPosition;"
+ "void main(){"
+ "vec3 normalVectorOrigin = aPosition;"
+ "vec3 normalVector = normalize((uMMatrix*vec4(normalVectorOrigin,1)).xyz);"
+ "vec3 vectorLight = normalize(uLightLocation - (uMMatrix * vec4(aPosition,1)).xyz);"
+ "float factor = max(0.0, dot(normalVector, vectorLight));"
+ "vDiffuse = factor*vec4(1,1,1,1.0);"
+ "gl_Position = uMVPMatrix * vec4(aPosition,1);"
+ "vColor = aColor;"
+ "vTextureCoord = aTextureCoord;" // 将纹理坐标传到片元着色器,得到更多的插值纹理坐标
+ "}";
private String fragmentShaderCode = "precision mediump float;"
+ "uniform sampler2D uTexture;" // 这个uniform变量表示了纹理数据,从java中传过来的是所在的纹理单元编号
+ "varying vec2 vTextureCoord;"
+ "varying vec4 vColor;"
+ "varying vec4 vDiffuse;"
+ "void main(){"
+ "gl_FragColor = (vColor*vDiffuse + vColor*vec4(0.6,0.6,0.6,1))*texture2D(uTexture, vTextureCoord);" // 在纹理的基础上还考虑到光照,texture2D函数用于纹理采样
+ "}";
}
需要注意的还是传入的顶点的时候数组里面包含了顶点、颜色和纹理坐标,因此要用ByteBuffer的position方法定位。
代码下载