[OpenGL]从零开始写一个Android平台下的全景视频播放器——1.4 用OpenGL ES 2.0显示一张图片(下)

时间:2023-01-31 23:02:07

Github项目地址

为了方便没有准备好*的同学,我把项目在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则是放大的
    关于放大和缩小时的可用方式,参见下图:
    [OpenGL]从零开始写一个Android平台下的全景视频播放器——1.4 用OpenGL ES 2.0显示一张图片(下)

  • GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,bitmap,0);
    然后将纹理加载到OpenGL中,并且及时回收bitmap

  • GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);用于接触和纹理的绑定,等使用时再绑定

关于最近邻过滤(GL_NEAREST),线性过滤(GL_LINEAR)等,感兴趣的可以去查找相关资料

OpenGL纹理坐标系

又是一个新的坐标系,直接放一张图片来说明吧
[OpenGL]从零开始写一个Android平台下的全景视频播放器——1.4 用OpenGL ES 2.0显示一张图片(下)

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);
}

运行一下,应该能显示出图片了(好累。。)
[OpenGL]从零开始写一个Android平台下的全景视频播放器——1.4 用OpenGL ES 2.0显示一张图片(下)
咦,好像有点变形了嘛。。
因为在这里我用的图片是3:4的,但是显示的区域却是1:1,所以图片被压扁了,我们更改一下纹理坐标,只显示图片的上部分区域:

private final float[] textureVertexData = {
0.5f,0.375f,
1f,0f,
0f,0f,
0f,0.75f,
1f,0.75f
};

[OpenGL]从零开始写一个Android平台下的全景视频播放器——1.4 用OpenGL ES 2.0显示一张图片(下)
嗯,正常多了。

看到这里,大家是不是对于OpenGL ES有了一定了解呢?
在下一章,我们要学习如何用OpenGL ES播放一个平面视频,从图片向视频迈进~