Android 使用 OpenGL ES 绘制图片

关于 OpenGL ES 的介绍,请先看上篇:Android 使用 OpenGL ES 绘制三角形

1. 纹理介绍

使用 OpenGL ES 绘制简单的几何形状还不够,OpenGL 更多地是用来显示而纹理图像,比如本地图片、相机画面。简单说,纹理(texture)就是一个图像或照片,它们可以被加载进 OpenGL 中。

OpenGL 中的纹理可以用来表示图像、照片等,每个二维的纹理都由许多小的纹理元素组成,它们是小块的数据,类似片段和像素。要使用纹理,最常用的方式是直接从一个图像文件加载数据。

纹理不会被直接绘制,它们要被绑定到纹理单元,然后把这些纹理单元传递给着色器。纹理映射的基本思想就是:首先为图元中的每个顶点指定恰当的纹理坐标,然后通过纹理坐标在纹理图中可以确定选中的纹理区域,最后将选中纹理区域中的内容根据纹理坐标映射到指定的图元上。

纹理的坐标系和顶点着色器的坐标系是不一样的。纹理坐标用浮点数来表示,范围 [0, 1],左上角坐标为 (0.0, 0.0),右上角坐标为 (1.0, 0.0)。注意:要将纹理坐标对应到正确的顶点上,才能使纹理正确地显示。

2. 使用纹理显示图片

定义顶点和纹理坐标,两者的顺序必须一一对应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static final int COORDS_PER_VERTEX = 2;
private static final int COORDS_PER_TEXTURE = 2;
private static final float[] VERTEX = {
1, 1, // top right
-1, 1, // top left
1, -1, // bottom right
-1, -1,// bottom left
};
private static final float[] TEXTURE = {
1, 0, // top right
0, 0, // top left
1, 1, // bottom right
0, 1, // bottom left
};

定义着色器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;" +
"attribute vec4 aPosition;" +
"attribute vec2 aTexCoord;" +
"varying vec2 vTexCoord;" +
"void main() {" +
" gl_Position = uMVPMatrix * aPosition;" +
" vTexCoord = aTexCoord;" +
"}";
private static final String FRAGMENT_SHADER =
"precision mediump float;" +
"uniform sampler2D uTextureUnit;" +
"varying vec2 vTexCoord;" +
"void main() {" +
" gl_FragColor = texture2D(uTextureUnit, vTexCoord);" +
"}";

加载图片到 OpenGL 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 加载图片并且保存在 OpenGL 纹理系统中
Bitmap bitmap = BitmapFactory.decodeFile(mImagePath);
mBitmapWidth = bitmap.getWidth();
mBitmapHeight = bitmap.getHeight();
int[] texture = new int[1];
// 生成纹理
GLES20.glGenTextures(1, texture, 0);
// 绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
// 激活纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 设置缩小过滤为三线性过滤
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,读入 Bitmap 定义的位图数据,并把它复制到当前绑定的纹理对象
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
// 生成 MIP 贴图
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
// 把选定的纹理单元传给片段着色器
GLES20.glUniform1i(texUnitHandle, 0);

计算变换矩阵,采用 CenterInside 或者 CenterCrop 的方式显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static float[] changeMvpMatrixInside(float viewWidth, float viewHeight, float textureWidth, float textureHeight) {
float scale = viewWidth * textureHeight / viewHeight / textureWidth;
float[] mvp = new float[16];
Matrix.setIdentityM(mvp, 0);
Matrix.scaleM(mvp, 0, scale > 1 ? (1F / scale) : 1F, scale > 1 ? 1F : scale, 1F);
return mvp;
}

public static float[] changeMvpMatrixCrop(float viewWidth, float viewHeight, float textureWidth, float textureHeight) {
float scale = viewWidth * textureHeight / viewHeight / textureWidth;
float[] mvp = new float[16];
Matrix.setIdentityM(mvp, 0);
Matrix.scaleM(mvp, 0, scale > 1 ? 1F : (1F / scale), scale > 1 ? scale : 1F, 1F);
return mvp;
}

显示图片。

1
2
3
4
5
6
7
8
9
10
11
12
GLES20.glUseProgram(mProgram);
GLES20.glUniformMatrix4fv(mMvpMatrixHandle, 1, false, mMvpMatrix, 0);
// 传入顶点坐标
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
// 传入纹理坐标
GLES20.glEnableVertexAttribArray(mTexCoordHandle);
GLES20.glVertexAttribPointer(mTexCoordHandle, COORDS_PER_TEXTURE, GLES20.GL_FLOAT, false, 0, mTextureBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX.length / COORDS_PER_VERTEX);
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisableVertexAttribArray(mTexCoordHandle);
GLES20.glUseProgram(0);

源码在 GitHub 上。

参考资料:

本文标题:Android 使用 OpenGL ES 绘制图片

文章作者:落英坠露

发布时间:2019年05月02日 - 20:05

最后更新:2019年05月02日 - 20:05

原始链接:https://isuperqiang.cn/2019/05/02/Android-使用-OpenGL-ES-绘制图片/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

赞赏是一种行为艺术