本节我们继续来看一下《OPENGL ES 3.0编程指南 原书第2版(中文版)》书中第6章的内容,PDF下载地址:
OPENGL ES 3.0编程指南 原书第2版(中文版)
,代码下载地址:
Opengl ES Source Code
。本书中第3、4、5章讲解的是Opengl ES着色器语言的语法知识,没有实例,不过这些语法也是我们掌握Opengl ES的硬功底,万丈高楼平地起,只有基础扎实,才能往上实现出更炫丽的效果。
第6章一共两个实例,分别对应代码中Example63Renderer、Example66Renderer两个类,先来看一下实现效果。
效果很简单,就是两个三角形。我们先来看一下Example63Renderer类,该小节主要讲的是通过Opengl ES 3.0的新语法指定顶点属性,然后通过API赋值,其他方法就不看了,主要是onDrawFrame方法,源码如下:
public void onDrawFrame(GL10 glUnused) {
// Set the viewport
GLES30.glViewport(0, 0, mWidth, mHeight);
// Clear the color buffer
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
// Use the program object
GLES30.glUseProgram(mProgramObject);
// Set the vertex color to red
GLES30.glVertexAttrib4f(0, 1.0f, 0.0f, 0.0f, 1.0f);
// Load the vertex position
// mVertices.position(0);
GLES30.glVertexAttribPointer(1, 3, GLES30.GL_FLOAT,
false,
0, mVertices);
GLES30.glEnableVertexAttribArray(1);
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3);
GLES30.glDisableVertexAttribArray(1);
}
该方法中主要有两句:GLES30.glVertexAttrib4f(0, 1.0f, 0.0f, 0.0f, 1.0f)、GLES30.glVertexAttribPointer(1, 3, GLES30.GL_FLOAT, false, 0, mVertices),这两句代码用来给着色器中的顶点数据赋值,第一个参数就是要赋值的目标对象的ID,为什么分别是0和1呢?上一节我们已经讲过,可以通过调用GLES30.glGetAttribLocation来获取顶点属性的ID值,这就是3.0的新语法,我们来看一下app\src\main\assets\chapter63目录下的顶点着色器,源码如下:
#version 300 es
layout(location = 0) in vec4 a_color;
layout(location = 1) in vec4 a_position;
out vec4 v_color;
void main()
{
v_color = a_color;
gl_Position = a_position;
}
第二行、第三行是通过location来定义a_color、a_position两个属性的,所以我们在Application中才可以通过0、1来给顶点属性赋值,其他的代码都基本相同,我们就不分析了。
继续看一下Example66Renderer,源码如下:
public class Example66Renderer implements GLSurfaceView.Renderer {
private static final String TAG = Example66Renderer.class.getSimpleName();
private Context mContext;
///
// Constructor
//
public Example66Renderer(Context context) {
mContext = context;
mVertices = ByteBuffer.allocateDirect(mVerticesData.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVertices.put(mVerticesData).position(0);
mColors = ByteBuffer.allocateDirect(mColorData.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mColors.put(mColorData).position(0);
mIndices = ByteBuffer.allocateDirect(mIndicesData.length * 2)
.order(ByteOrder.nativeOrder()).asShortBuffer();
mIndices.put(mIndicesData).position(0);
}
///
// Initialize the shader and program object
//
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
String vShaderStr = ESShader.readShader(mContext, "chapter66/vertexShader.vert");
String fShaderStr = ESShader.readShader(mContext, "chapter66/fragmentShader.frag");
// Load the shaders and get a linked program object
mProgramObject = ESShader.loadProgram(vShaderStr, fShaderStr);
mVBOIds[0] = 0;
mVBOIds[1] = 0;
mVBOIds[2] = 0;
GLES30.glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
}
// /
// Draw a triangle using the shader pair created in onSurfaceCreated()
//
public void onDrawFrame(GL10 glUnused) {
// Set the viewport
GLES30.glViewport(0, 0, mWidth, mHeight);
// Clear the color buffer
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
// Use the program object
GLES30.glUseProgram(mProgramObject);
drawPrimitiveWithVBOs();
}
private void drawPrimitiveWithVBOs() {
int numVertices = 3;
int numIndices = 3;
// mVBOIds[0] - used to store vertex position
// mVBOIds[1] - used to store vertex color
// mVBOIds[2] - used to store element indices
if (mVBOIds[0] == 0 && mVBOIds[1] == 0 && mVBOIds[2] == 0) {
// Only allocate on the first draw
GLES30.glGenBuffers(3, mVBOIds, 0);
Log.e(TAG, "0: " + mVBOIds[0] + ", 1: " + mVBOIds[1] + ", 2: " + mVBOIds[2]);
mVertices.position(0);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBOIds[0]);
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vtxStrides[0] * numVertices,
mVertices, GLES30.GL_STATIC_DRAW);
mColors.position(0);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBOIds[1]);
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vtxStrides[1] * 3,
mColors, GLES30.GL_STATIC_DRAW);
mIndices.position(0);
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mVBOIds[2]);
GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER, 2 * numIndices,
mIndices, GLES30.GL_STATIC_DRAW);
}
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBOIds[0]);
GLES30.glEnableVertexAttribArray(VERTEX_POS_INDX);
GLES30.glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,
GLES30.GL_FLOAT, false, vtxStrides[0], 0);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBOIds[1]);
GLES30.glEnableVertexAttribArray(VERTEX_COLOR_INDX);
GLES30.glVertexAttribPointer(VERTEX_COLOR_INDX, VERTEX_COLOR_SIZE,
GLES30.GL_FLOAT, false, vtxStrides[1], 0);
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mVBOIds[2]);
GLES30.glDrawElements(GLES30.GL_TRIANGLES, numIndices,
GLES30.GL_UNSIGNED_SHORT, 0);
GLES30.glDisableVertexAttribArray(VERTEX_POS_INDX);
GLES30.glDisableVertexAttribArray(VERTEX_COLOR_INDX);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0);
}
///
// Handle surface changes
//
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
mWidth = width;
mHeight = height;
}
// Handle to a program object
private int mProgramObject;
// Additional member variables
private int mWidth;
private int mHeight;
private FloatBuffer mVertices;
private FloatBuffer mColors;
private ShortBuffer mIndices;
// VertexBufferObject Ids
private int[] mVBOIds = new int[3];
// 3 vertices, with (x,y,z) ,(r, g, b, a) per-vertex
private final float[] mVerticesData =
{
0.0f, 0.5f, 0.0f, // v0
-0.5f, -0.5f, 0.0f, // v1
0.5f, -0.5f, 0.0f // v2
};
private final short[] mIndicesData =
{
0, 1, 2
};
private final float[] mColorData =
{
1.0f, 0.0f, 0.0f, 1.0f, // c0
0.0f, 1.0f, 0.0f, 1.0f, // c1
0.0f, 0.0f, 1.0f, 1.0f // c2
};
final int VERTEX_POS_SIZE = 3; // x, y and z
final int VERTEX_COLOR_SIZE = 4; // r, g, b, and a
final int VERTEX_POS_INDX = 0;
final int VERTEX_COLOR_INDX = 1;
private int vtxStrides[] =
{
VERTEX_POS_SIZE * 4,
VERTEX_COLOR_SIZE * 4
};
}
先来看成员变量:mProgramObject表示编译链接完成的program id;mWidth、mHeight表示视窗口的宽度和高度;mVertices存储顶点位置数据;mColors存储顶点颜色数据;mIndices存储顶点的索引数据;mVBOIds存储顶点缓冲区分配到的ID值,因为我们要使用顶点缓冲区、颜色缓冲区、索引缓冲区三个,所以它的size为3;mVerticesData存储的三角形三个顶点的位置数据,v0(0.0f, 0.5f, 0.0f)位于Y轴正方向上0.5距离处,也就是最上面那个点,v1(-0.5f, -0.5f, 0.0f)位于X、Y轴都是负方向0.5距离处,就是数学上所说的第三象限,也就是左下角那个点,v2(0.5f, -0.5f, 0.0f)位于X轴正方向0.5距离、Y轴负方向0.5距离处,也就是第四象限,右下角那个点;mIndicesData存储索引数据,一共只有三个点,所以就只有0、1、2了,索引值我们在
Opengl ES系列学习–增加地形
一节已经详细讲解过了,如果还有疑问,请回头先搞清楚;mColorData存储了三个顶点的颜色值,分别是红、绿、蓝三元色,透明度全部为1,而所有中间的片段都是经过插值计算出来的,所以大家就会看到一个渐变过度的彩色三角形了,我们在
Opengl ES系列学习–序
一节中利用插值画了一个很简单的线条,也是用的这样的原理;VERTEX_POS_SIZE表示描述顶点位置需要3个size,分别对应X、Y、Z;VERTEX_COLOR_SIZE表示描述顶点颜色需要4个size,分别对应R、G、B、A;VERTEX_POS_INDX表示着色器中定义的顶点位置属性的ID值为0,我们后面看到顶点着色器的代码就可以看到了,不过总是感觉这样的方式不友好,代码可读性差,个人建议大家不要使用这样的方式;VERTEX_COLOR_INDX表示着色器中定义的顶点颜色属性的ID值为1,和上一个位置属性相同,都是在着色器程序中通过location关键字来指定的;vtxStrides定义了位置属性、颜色属性的跨距,这个我们应该已经非常熟悉了。
6-6小节主要讲解的就是如何使用顶点缓冲区,代码实现和
Opengl ES系列学习–增加地形
中的顶点缓冲区基本是一样的,主要就是在drawPrimitiveWithVBOs方法当中了,根据if (mVBOIds[0] == 0 && mVBOIds[1] == 0 && mVBOIds[2] == 0)判断条件,如果之前没有分配过,那就执行一次分配,分配时调用GLES30.glGenBuffers(3, mVBOIds, 0),分配成功的三个缓冲区ID会存储在mVBOIds数组当中,接下来调用GLES30.glBindBuffer、GLES30.glBufferData系统API给缓冲区填充数据就可以了,最后不要忘了调用GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)、GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)分别解除绑定。
6-6小节的两个着色器程序和6-3完全相同,我们就不分析了,相信大家都能理解着色器的代码原理。
回头来总结一下,本节主要就是介绍如何使用缓冲区的概念来实现一个效果,缓冲区能很好的提高我们程序的执行效率,也是Opengl ES的基本操作,以后我们要实现什么效果的话,也要基于缓冲区来实现,而不要再使用顶点数组了,那样效率太低,而且浪费内存。使用的流程都是非常规范的API调用操作,也没有什么难度,大家直接照着复制就可以了。
好了,让我们继续开车!!