Android OpenGL的简单使用(11):GLES20线和图形的绘制

Posted by alonealice on 2021-01-11

绘制线

线段的绘制很简单,在之前点绘制的基础上,首先需要多增加几个顶点的数据:

1
2
3
4
5
6
//顶点数组
private final float[] VERTEX = { // in counterclockwise order:
0, 0, 0,
1, 0, 0,
1, 1, 0
};

修改glVertexAttribPointer方法对应数据的数量:

1
2
GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false,
12, mBuffer);

最后在绘制时指定绘制方式为线段:

1
2
3
4
5
6
7
8
@Override
public void onDrawFrame(GL10 gl) {
// 清除屏幕
GLES20.glClear(GL10.GL_COLOR_BUFFER_BIT);

GLES20.glUniformMatrix4fv(mMatrixHandle, 1, false, mMVPMatrix, 0);
GLES20.glDrawArrays(GLES20.GL_LINE_STRIP, 0, 3);
}

GL_LINES:独立的线段,1和2连,3和4连等;

GL_LINE_STRIP:连续的线段,1和2连,2和3连等;

GL_LINE_LOOP:收尾相连,1和2连,2和3连,3和1连;

绘制矩形

利用三角形我们可以“拼出”矩形。

创建顶点索引数据:

1
2
3
4
5
6
7
8
9
10
private static final short[] VERTEX_INDEX = { 0, 1, 2, 0, 2, 3 };

private final ShortBuffer mVertexIndexBuffer;
...

mVertexIndexBuffer = ByteBuffer.allocateDirect(VERTEX_INDEX.length * 2)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
.put(VERTEX_INDEX);
mVertexIndexBuffer.position(0);

在onDrawFrame中绘制:

1
GLES20.glDrawElements(GLES20.GL_TRIANGLES, VERTEX_INDEX.length, GLES20.GL_UNSIGNED_SHORT, mVertexIndexBuffer);

glDrawElements:它使用来自启用数组的计数顺序元素,从索引开始构造几何图元序列。mode指定构造什么类型的图元以及数组元素如何构造这些图元。

mode:指定要渲染的图元类型。 接受符号常量GL_POINTSGL_LINE_STRIPGL_LINE_LOOPGL_LINESGL_TRIANGLE_STRIPGL_TRIANGLE_FANGL_TRIANGLES

count:指定要渲染的元素数。

type:指定indices中值的类型。 必须是GL_UNSIGNED_BYTEGL_UNSIGNED_SHORT

indices:指定指向存储索引的位置的指针。

绘制纹理

图片的纹理

在原先绘制矩形的基础上,先添加图片的纹理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 int[] texNames = new int[1];
GLES20.glGenTextures(1, texNames, 0);
int mTexName = texNames[0];
Bitmap bitmap = BitmapFactory.decodeResource(OpenGlTextureActivity.this.getResources(),
R.drawable.bg2);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexName);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_REPEAT);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

glGenTextures:创建纹理,产生个纹理ID存储在textures数组中。

glActiveTexture:激活纹理单元。texture必须是GL_TEXTUREi之一,其中i的范围从0到(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1)。初始值是GL_TEXTURE0

glBindTexture:将一个指定的纹理ID绑定到一个纹理目标上。

glTexParameteri:确定如何把纹理象素映射成像素。

glTexParameteri(GLenum target,GLenum pname,GLint param

target:指定之前激活了的纹理要绑定到的一个目标。必须是GL_TEXTURE_2D 或GL_TEXTURE_CUBE_MAP。

pname:指定一个单值纹理参数的符号名,pname可以是下列值之一:GL_TEXTURE_MIN_FILTER(放大过滤) GL_TEXTURE_MAG_FILTER(缩小过滤)GL_TEXTURE_WRAP_S (S方向)GL_TEXTURE_WRAP_T(T方向)。

param:指定pname的值。

params提供的缩小采样功能,可选参数如下:

GL_NEAREST

临近采样,返回与纹理像素的中心最接近(在曼哈顿距离内)的纹理元素的值。

GL_LINEAR

线性采样,返回最接近被纹理像素中心的四个纹理元素的加权平均值。

GL_NEAREST_MIPMAP_NEAREST

选择最接近匹配纹理像素大小的mipmap,并使用GL_NEAREST标准(最接近像素中心的纹理元素)来生成纹理值。

GL_LINEAR_MIPMAP_NEAREST

选择最接近匹配纹理像素大小的mipmap,并使用GL_LINEAR标准(最接近像素中心的四个纹理元素的加权平均值)来生成纹理值。

GL_NEAREST_MIPMAP_LINEAR

选择与纹理像素大小最匹配的两个mipmap,并使用GL_NEAREST标准(最接近像素中心的纹理元素)从每个mipmap生成纹理值。 最终纹理值是这两个值的加权平均值。

GL_LINEAR_MIPMAP_LINEAR

选择与纹理像素大小最匹配的两个mipmap,并使用GL_LINEAR标准(最接近像素中心的四个纹理元素的加权平均值)从每个mipmap生成纹理值。 最终纹理值是这两个值的加权平均值。

shader 代码

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 vPosition;" +
"attribute vec2 a_texCoord;" +
"varying vec2 v_texCoord;" +
"void main() {" +
" gl_Position = uMVPMatrix * vPosition;" +
" v_texCoord = a_texCoord;" +
"}";
private static final String FRAGMENT_SHADER =
"precision mediump float;" +
"varying vec2 v_texCoord;" +
"uniform sampler2D s_texture;" +
"void main() {" +
" gl_FragColor = texture2D(s_texture, v_texCoord);" +
"}";

uniform 由外部程序传递给 shader,就像是C语言里面的常量,shader 只能用,不能改;attribute 是只能在 vertex shader 中使用的变量;varying 变量是 vertex 和 fragment shader 之间做数据传递用的。

绘制

首先我们需要指定截取纹理的哪一部分绘制到图形上:

1
2
3
4
5
6
7
8
private static final float[] TEX_VERTEX = {   // in clockwise order:
1, 0, // bottom right
0, 0, // bottom left
0, 1, // top left
1, 1, // top right
};

mTexVertexBuffer = Util.getFloatBuffer(TEX_VERTEX);

修改初始化和绘制的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Override
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// ...

mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_texCoord");
mMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
mTexSamplerHandle = GLES20.glGetUniformLocation(mProgram, "s_texture");

GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false,
12, mVertexBuffer);

GLES20.glEnableVertexAttribArray(mTexCoordHandle);
GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false, 0,
mTexVertexBuffer);

// ...
}

@Override
public void onDrawFrame(GL10 unused) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

GLES20.glUniformMatrix4fv(mMatrixHandle, 1, false, mMVPMatrix, 0);
GLES20.glUniform1i(mTexSamplerHandle, 0);

// 用 glDrawElements 来绘制,mVertexIndexBuffer 指定了顶点绘制顺序
GLES20.glDrawElements(GLES20.GL_TRIANGLES, VERTEX_INDEX.length,
GLES20.GL_UNSIGNED_SHORT, mVertexIndexBuffer);
}

在 activity 销毁时,我们需要销毁 OpenGL 纹理:

1
GLES20.glDeleteTextures(1, new int[] { mTexName }, 0);

读取显存

onDrawFrame 方法执行完毕之后(实际上是 glDrawElements 执行完毕之后),我们就可以从显存中读取帧数据了。这里我们利用 glReadPixels 方法读取数据:

1
2
3
4
5
6
7
8
9
10
captureBitmap = getImage(surfaceView.getWidth(), surfaceView.getHeight());

private Bitmap getImage(int width, int height) {
ByteBuffer rgbaBuf = ByteBuffer.allocateDirect(width * height * 4);
rgbaBuf.position(0);
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE,rgbaBuf);
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(rgbaBuf);
return bmp;
}

注意:glReadPixels方法必须在onDrawFrame中,在glDrawElements之后,在onDrawFrame以外调用可能会拿到空的bitmap。