为了方便没有准备好*的同学,我把项目在CSDN上打包下载
今天早上突然想起来,其实矩形只要两个三角形就好了嘛。我非要用四个三角形画,肯定是中了三角形扇的毒。。
创建一个纹理
纹理的创建比较复杂,我们创建一个新的工具类,并且加入如下代码:
public class TextureHelper {
private static final String TAG="TextureHelper";
public static int loadTexture(Context context,int resourceId){
final int[] textureObjectIds=new int[1];
GLES20.glGenTextures(1,textureObjectIds,0);
if (textureObjectIds[0]==0){
Log.d(TAG,"生成纹理对象失败");
return 0;
}
BitmapFactory.Options options=new BitmapFactory.Options();
options.inScaled=false;
Bitmap bitmap=BitmapFactory.decodeResource(context.getResources(),resourceId,options);
if (bitmap==null){
Log.d(TAG,"加载位图失败");
GLES20.glDeleteTextures(1,textureObjectIds,0);
return 0;
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureObjectIds[0]);
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);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,bitmap,0);
bitmap.recycle();
//为与target相关联的纹理图像生成一组完整的mipmap
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
return textureObjectIds[0];
}
}
一句句来看吧:
- GLES20.glGenTextures(1,textureObjectIds,0);生成一个纹理,放入textureObjectIds中,同样地,如果生成成功,那么就会返回一个非零值
- 然后我们将bitmap从raw中读进来,这个就不解释了
- GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureObjectIds[0]);的作用是将我们刚生成的纹理和OpenGL的2D纹理绑定,告诉OpenGL这是一个2D的纹理(贴图)
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);
这两句话用来设置纹理过滤的方式,GL_TEXTURE_MIN_FILTER是指缩小时的过滤方式,GL_TEXTURE_MAG_FILTER则是放大的
关于放大和缩小时的可用方式,参见下图:GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,bitmap,0);
然后将纹理加载到OpenGL中,并且及时回收bitmap- GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);用于接触和纹理的绑定,等使用时再绑定
关于最近邻过滤(GL_NEAREST),线性过滤(GL_LINEAR)等,感兴趣的可以去查找相关资料
OpenGL纹理坐标系
又是一个新的坐标系,直接放一张图片来说明吧
S-T坐标系(橙色)就是OpenGL纹理坐标系,蓝色的是OpenGL屏幕坐标系,对比一下,明显的区别有:
- 原点的位置不一样
- 取值的范围不一样
- Y轴的方向也刚好和T轴相反
- 如果我们要将一张图贴到整个显示区域,即(-1,-1,0)-(1,1,0),那么对应的关系就如图中所示
更新着色器代码
vertex_shader.glsl
attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
uniform mat4 uMatrix;
void main() {
vTexCoord=aTexCoord;
gl_Position = uMatrix*aPosition;
}
首先,aTexCoord是一个二维向量,表示纹理的坐标,
varying这个变量是用来在vertex_shader和fragment_shader之间传递值用的,所以名称要相同,我们把aTexCoord赋值给vTexCoord,然后来看片元着色器的代码
fragment_shader.glsl
precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D sTexture;
void main() {
//gl_FragColor = vec4(0,0.5,0.5,1);
gl_FragColor = texture2D(sTexture,vTexCoord);
}
在片元着色器中,我们声明了一个uniform常量,类型是sampler2D,这个类型是指一个二维的纹理数据数组
使用texture2D来处理被插值的纹理坐标vTexCoord
和纹理数据sTexture
,得到的颜色值就是要显示的颜色,交给gl_FragColor
更新Renderer类
首先,利用刚才写的类来获得一个纹理ID,这句话放在onSurfaceCreated里面:
textureId=TextureHelper.loadTexture(context,R.raw.demo_pic);
然后我们加入纹理坐标数据(参见上图的对应关系)
private final float[] textureVertexData = {
0.5f,0.5f,
1f,0f,
0f,0f,
0f,1f,
1f,1f
};
并把它复制到OpenGL的本地内存中
textureVertexBuffer = ByteBuffer.allocateDirect(textureVertexData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(textureVertexData);
textureVertexBuffer.position(0);
类似的,我们要获得刚才的变量、常量引用(handle):
uTextureSamplerHandle=GLES20.glGetUniformLocation(programId,"sTexture");
aTextureCoordHandle=GLES20.glGetAttribLocation(programId,"aTexCoord");
到目前为止,类中的全局变量有:
private Context context;
private int aPositionHandle;
private int programId;
private FloatBuffer vertexBuffer;
private final float[] vertexData = {
0f,0f,0f,
1f,1f,0f,
-1f,1f,0f,
-1f,-1f,0f,
1f,-1f,0f
};
private final short[] indexData = {
0,1,2,
0,2,3,
0,3,4,
0,4,1
};
private final float[] projectionMatrix=new float[16];
private ShortBuffer indexBuffer;
private int uMatrixHandle;
private final float[] textureVertexData = {
0.5f,0.5f,
1f,0f,
0f,0f,
0f,1f,
1f,1f
};
private FloatBuffer textureVertexBuffer;
private int uTextureSamplerHandle;
private int aTextureCoordHandle;
private int textureId;
东西好像挺多的,但是应该都可以理解吧?
更新onDrawFrame
首先,把纹理坐标用类似的方法传递过去:
GLES20.glEnableVertexAttribArray(aTextureCoordHandle);
GLES20.glVertexAttribPointer(aTextureCoordHandle,2,GLES20.GL_FLOAT,false,8,textureVertexBuffer);
然后,我们启用一个纹理,并把它和刚才生成的纹理ID绑定,再把纹理数据引用传过去,因为我们启用的是GL_TEXTURE0,所以在glUniform1i中第二个参数是0(大家可以都改成1试一下,在这里应该是一样的效果):
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureId);
GLES20.glUniform1i(uTextureSamplerHandle,0);
目前onDrawFrame的代码如下:
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(programId);
GLES20.glUniformMatrix4fv(uMatrixHandle,1,false,projectionMatrix,0);
GLES20.glEnableVertexAttribArray(aPositionHandle);
GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
12, vertexBuffer);
GLES20.glEnableVertexAttribArray(aTextureCoordHandle);
GLES20.glVertexAttribPointer(aTextureCoordHandle,2,GLES20.GL_FLOAT,false,8,textureVertexBuffer);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureId);
GLES20.glUniform1i(uTextureSamplerHandle,0);
GLES20.glDrawElements(GLES20.GL_TRIANGLES,indexData.length,GLES20.GL_UNSIGNED_SHORT,indexBuffer);
}
运行一下,应该能显示出图片了(好累。。)
咦,好像有点变形了嘛。。
因为在这里我用的图片是3:4的,但是显示的区域却是1:1,所以图片被压扁了,我们更改一下纹理坐标,只显示图片的上部分区域:
private final float[] textureVertexData = {
0.5f,0.375f,
1f,0f,
0f,0f,
0f,0.75f,
1f,0.75f
};
嗯,正常多了。
看到这里,大家是不是对于OpenGL ES有了一定了解呢?
在下一章,我们要学习如何用OpenGL ES播放一个平面视频,从图片向视频迈进~