5.把着色器一起链接进OpenGL的程序
既然我们已经加载并编译了一个顶点着色器和一个片段着色器,下一步就是把它们绑定在一起放入一个单个的程序里。
5.1理解OpenGL的程序:
简单来说,一个OpenGL程序就是把一个顶点着色器和一个片段着色器链接在一起变成单个对象。顶点着色器和片段着色器总是一起工作的。没有片段着色器,OpenGL就不知道怎么绘制那些组成的每个点,直线和三角形片段;如果没有顶点着色器,OpenGL就不知道在哪里绘制这些片段。
虽然顶点着色器和片段着色器总是要一起工作的,但并不意味着它们必须是一对一匹配的,我们可以同时在多个程序中使用同一个着色器。
让我们打开ShaderHelper,并在类的末尾加入如下代码:
public static int linkProgram(int vertexShaderId, int fragmentShaderId) { }
下面我们将构建这个方法。
新建程序并附着上着色器,我们要做的第一件事就是调用glCreateProgram()新建程序对象,并把那个对象的ID存进programObjectId。如下:
final int programObjectId = GLES20.glCreateProgram(); if (programObjectId == 0) { if (LoggerConfig.ON) { Log.w(TAG, "Could not create new Program"); } return 0; }
和上面的代码类似就不过多的解释了,下一步就是附上着色器:
GLES20.glAttachShader(programObjectId, vertexShaderId); GLES20.glAttachShader(programObjectId, fragmentShaderId);
使用glAttachShader()方法把顶点着色器和片段着色器附加到程序对象上。
5.2链接程序
现在准备把这些着色器联合起来了,为此,将调用
glLickProgram(programObjectId): GLES20.glLinkProgram(programObjectId);
为了检查这个链接是成功还是失败,我们遵循编译着色器时所使用的步骤:
final int[] linkStatus = new int[1]; GLES20.glGetProgramiv(programObjectId, GLES20.GL_LINK_STATUS, linkStatus, 0);
最后验证链接状态并返回程序对象ID,代码如下:
if (linkStatus[0]==0) { GLES20.glDeleteProgram(programObjectId); if(LoggerConfig.ON){ Log.w(TAG,"linking of program failed"); } return 0; } return programObjectId;
给渲染类LYJRenderer加入代码,先定义成员变量:
private int program;
然后在onSurfaceCreated()结尾处加入如下代码把着色器链接起来:
program = ShaderHelper.linkProgram(vertexShader, fragmentShader);
6.最后的拼接
6.1验证OpenGL程序的对象
在开始使用OpenGL的程序之前,我们首先应该验证一下它,看看这个程序对于当前的OpenGL状态是不是有效的。根据OpenGL ES2.0的文档,它也给OpenGL提供了一种方法让我们知道为什么当前程序可能是低效率的,无法运行的,等等。
让我们在ShaderHelper加入如下代码:
public static boolean validateProgram(int programObjectId){ GLES20.glValidateProgram(programObjectId); final int[] validateStatus=new int[1]; GLES20.glGetProgramiv(programObjectId,GLES20.GL_VALIDATE_STATUS,validateStatus,0); Log.v(TAG,GLES20.glGetProgramInfoLog(programObjectId)); return validateStatus[0]!=0; }
这段代码与上面验证类似就不过多的阐述了,然后在onSurfaceView()结尾处加入如下代码:
ShaderHelper.validateProgram(program);
然后调用glUseProgram(program);
调用glUseProgram()告诉OpenGL在绘制任何东西到屏幕上的时候要使用这个定义的程序。
获取属性的位置
在使用属性之前我们要获取它们的位置。我们可以让OpenGL自动给这些属性分配位置编号,或者在着色器被链接到一起之前,可以通过调用glBindAttrribLocation()由我们自己给它们分配位置编号。我们要让OpenGL自动分配这些位置,因为它使代码容易管理。
在LYJRenderer顶部加入如下定义:
private static final String A_POSITION="a_Position"; private int aPositionLocation; private static final int POSITION_COMPONENT_COUNT = 2;
一旦着色器被链接起来了,我们就只需要加入一些代码去获取属性位置。在onSurfaceCreated() 结尾处加入如下代码:
this.aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION);
调用glGetAttribLocation()获取属性的位置。 有了这个位置,就能告诉OpenGL到哪里去找到这个属性对应的数据了。
6.2关联属性与顶点数据的数组
下一部就是要告诉OpenGL到哪里找到属性a_Position对应的数据。
this.vertexData.position(0); GLES20.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE, this.vertexData);
在我们告诉OpenGL从这个缓冲区中读取数据之前,需要确保它会从开头处开始读取数据,而不是中间或者结尾处。每个缓冲区都有一个内部指针,可以通过调用position(int)移动它,并且当OpenGL从缓冲区读取时,它会从这个位置开始读取。
下面是参数的解析:
aPositionLocation:这个是属性位置。
POSITION_COMPONENT_COUNT:这个属性有多少分量
GLES20.GL_FLOAT:这是数据的类型
false:只有使用整型数组时候,这个数据才有意义。
STRIDE:多于一个属性时候,就要告诉取下个数据要跳过多少分量。
vertexData:这个参数告诉OpenGL去哪里读取数据。
尽管我们已经把数据属性链接起来了,在开始绘制之前,我们还需要调用glEnableVertexAttribArray()使用这个属性。代码如下:
GLES20.glEnableVertexAttribArray(aPositionLocation);
同理颜色分量的代码如下:
private static final int COLOR_COMPONENT_COUNT=3; private static final String A_COLOR = "a_Color"; private int aColorLocation; private static final int STRIDE=(POSITION_COMPONENT_COUNT+COLOR_COMPONENT_COUNT)*BYTES_PER_FLOAT; this.vertexData.position(POSITION_COMPONENT_COUNT); GLES20.glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE, this.vertexData); GLES20.glEnableVertexAttribArray(aColorLocation);
7.在屏幕上绘制
在onDrawFrame() 方法的结尾添加如下代码:
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 6); GLES20.glDrawArrays(GLES20.GL_LINES, 6, 2); GLES20.glDrawArrays(GLES20.GL_POINTS, 8, 1); GLES20.glDrawArrays(GLES20.GL_POINTS, 9, 1);
第一个语句告诉OpenGL绘制三角扇形,什么是三角扇形后面讲解,第二个语句告诉OpenGL绘制直线,第三,四条语句告诉OpenGL绘制点。
这七个步骤就是开发OpenGL程序的基本流程。如图