1. 绘制矩形
上篇中有提到,三角形是基本形状,利用三角形我们可以“拼出”其他的任何形状,例如矩形。
绘制两个三角形时,我们可以指定 6 个顶点的坐标,但实际上只有 4 个不同的点,这样有点浪费,OpenGL 支持用另一种方式完成绘制:用一个数组保存顶点数据,用另一个数组保存顶点的绘制顺序:
private static final float[] VERTEX = { // in counterclockwise order:
1, 1, 0, // top right
-1, 1, 0, // top left
-1, -1, 0, // bottom left
1, -1, 0, // bottom right
};
private static final short[] VERTEX_INDEX = { 0, 1, 2, 0, 2, 3 };
private final ShortBuffer mVertexIndexBuffer;
MyRenderer() {
mVertexBuffer = ByteBuffer.allocateDirect(VERTEX.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(VERTEX);
mVertexBuffer.position(0);
mVertexIndexBuffer = ByteBuffer.allocateDirect(VERTEX_INDEX.length * 2)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
.put(VERTEX_INDEX);
mVertexIndexBuffer.position(0);
}
// ...
在上面的代码中,VERTEX
保存了 4 个顶点的坐标,VERTEX_INDEX
保存了顶点的绘制顺序。0 -> 1 -> 2
绘制的是 右上 -> 左上 -> 左下
上半个三角形,逆时针方向,而 0 -> 2 -> 3
则绘制的是 右上 -> 左下 -> 右下
下半个三角形,也是逆时针方向,这两个三角形则“拼接”成了一个矩形。
顶点的绘制顺序重不重要?由于这里绘制的是纯颜色,看不出区别,在下面绘制图片纹理的时候,我发现,调换顺序似乎并没有影响,绘制的图片没有变化。
shader 代码、投影变换的逻辑都不需要更改(但为了让矩形能完整显示,我们把 translateM
移动的 z 值设为 -5f),我们只需要改一下绘制时调用的函数即可:
@Override
public void onDrawFrame(GL10 unused) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUniformMatrix4fv(mMatrixHandle, 1, false, mMVPMatrix, 0);
// 用 glDrawElements 来绘制,mVertexIndexBuffer 指定了顶点绘制顺序
GLES20.glDrawElements(GLES20.GL_TRIANGLES, VERTEX_INDEX.length,
GLES20.GL_UNSIGNED_SHORT, mVertexIndexBuffer);
}
绘制效果图:
2. 绘制图片纹理
在绘制了矩形的基础上,我们更进一步,不再满足于绘制纯色纹理,而是绘制图片纹理。
2.1. 加载图片
首先我们需要加载图片并且保存在 OpenGL 纹理系统中:
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// ...
int[] texNames = new int[1];
GLES20.glGenTextures(1, texNames, 0);
mTexName = texNames[0];
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),
R.drawable.p_300px);
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);
bitmap.recycle();
}
2.2. shader 代码
此时,我们的 shader 代码当然也需要进行更改了:
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
,attribute
,varying
,GLSL 并不是我关注的重点,不过这三者的区别可以看看 https://blog.csdn.net/EveryDayOneHour/article/details/122431249,讲的非常清晰易懂:
uniform
由外部程序传递给 shader,就像是C语言里面的常量,shader 只能用,不能改;attribute
是只能在 vertex shader 中使用的变量;varying
变量是 vertex 和 fragment shader 之间做数据传递用的。
2.3. 绘制
首先我们需要指定截取纹理的哪一部分绘制到图形上:
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
};
private final ShortBuffer mVertexIndexBuffer;
MyRenderer(final Context context) {
// ...
mTexVertexBuffer = ByteBuffer.allocateDirect(TEX_VERTEX.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(TEX_VERTEX);
mTexVertexBuffer.position(0);
}
接着我们需要修改初始化和绘制的代码:
@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);
}
绘制效果如下: