创建顶点着色器和片段着色器
顶点着色器代码示例:
attribute vec4 a_Position;
void main()
{
gl_Position = a_Position;
}
gl_Position为当前顶点的最终位置
片段着色器代码示例:
precision mediump float;
uniform ver4 u_Color;
void main()
{
gl_FragColor = u_Color;
}
gl_FragColor为当前片段最终输出的颜色
编译着色器
因为有顶点着色器和片段着色器,所以需要编译两次
- 创建着色器对象,type值为GL_VERTEX_SHADER和GL_FRAGMENT_SHADER,分别代表顶点着色器和片段着色器类型
final int shaderObjectId = glCreateShader(type);
- 加载着色器代码,shaderCode是前面写的顶点着色器和片段着色器代码,分别将其转换成String类型即可
glShaderSource(shaderObjectId, shaderCode);
- 编译着色器
glCompileShader(shaderObjectId);
现在顶点着色器和片段着色器都编译好了,我们之后只需要通过shaderObjectId来调用对应的着色器对象即可使用。
将着色器连接到OpenGL ES的program中
前面创建好了顶点着色器对象和片段着色器对象,使用时两者需要一起工作,OpenGL ES就是通过program来将两者链接到一起
链接program的过程和编译着色器的过程很类似,如下:
- 创建program对象
final int programObjectId = glCreateProgram();
- 附上着色器,vertexShaderId和fragmentShaderId就是前面编译生成的着色器对象的ID
glAttachShader(programObjectId, vertexShaderId);
glAttachShader(programObjectId, fragmentShaderId);
- 链接program
glLinkProgram(programObjectId);
- 告诉OpenGL ES使用此program程序
glUseProgram(programObjectId);
到这里OpenGL ES绘制任何东西到屏幕上都是使用此program程序,这时你问了那我们是怎么设置顶点坐标和片段颜色的呢?
设置顶点坐标和片段颜色
我们在顶点着色器和片段着色器中分别定义了一下两个变量a_Position和u_Color,顶点着色器设置当前顶点的最终坐标位置gl_Position = a_Position;片段着色器设置当前片段的最终颜色gl_FragColor = u_Color;当OpenGL ES把两个着色器链接成一个程序时,会给所有的变量都分配一个位置,我们需要获取这些位置来设置顶点坐标和片段颜色。
- 首先获取顶点着色器的a_Position位置
我们在Renderer类中定义两个实例变量,一个是所需获取的着色器中的变量名,一个是代表此变量在program中的位置
private static final String A_POSITION = "a_Position";
private int aPositionLocation;
然后在Renderer的onSurfaceCreated()方法中获取位置
aPositionLocation = glGetAttribLocation(program, A_POSITION);
- 获取片段着色器的u_Color位置
同样在Renderer类中定义两个实例变量
private static final String U_COLOR = "u_Color";
private int uColorLocation;
然后在Renderer的onSurfaceCreated()方法中获取位置
uColorLocation = glGetUniformLocation(program, U_COLOR);
现在我们获取到了所需属性的位置,通过这个位置我们就可以设置OpenGL ES的顶点坐标和片段颜色了
- 定义顶点数据缓冲区,我们先画个三角形
这里是每两个数据代表一个顶点的(x,y)坐标,屏幕坐标系如下,屏幕中间为原点,(-1, -1)是屏幕的左下角,(1, -1)是右下角,(0, 1)在中上位置
float[] triangleVertices = {
-1.0f, -1.0f,
1.0f, -1.0f,
0.0f, 1.0f
};
因为OpenGL ES作为本地系统库直接运行中硬件上的,所以我们的顶点数据也希望不能被垃圾回收,因此我们使用jni方式,但是为了方便我们还可以使用另一种方式NIO,
private final FloatBuffer vertexData;
vertexData = ByteBuffer
.allocateDirect(triangleVertices.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexData.put(triangleVertices);
现在顶点数组就会从虚拟机拷贝到本地内存中,不会受到垃圾回收,当然当进程结束时这块内存会被释放掉。
关联顶点数据和顶点属性
现在顶点数据和OpenGL ES中的顶点位置都有了,我们还需要将两者关联起来,也就是让OpenGL ES找到顶点坐标的最终位置。
我们在Renderer的onSurfaceCreated方法中加入以下代码:
//让vertexData内部的指针指向数据的开头
vertexData.position(0);处,否则可能有bug
//aPositionLocation代表对应OpenGL ES中的属性位置,2代表这个属性每次读取几个数据,GL_FLOAT代表数据类型,false表示我们暂时忽略此参数,0表示跨度,这里就连着读取数据,vertexData就是告诉OpenGL ES去哪读数据
glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 0, vertexData);
注意这个2,它是表示每次从vertexData读两个数据表示一个顶点,但是前面a_Position的类型是vec4,也就说每个顶点需要四个分量,而我们只传了2个数据,那么剩下的两个分量会使用默认值(0,1),所以最后顶点的坐标是(-1,-1,0,1),(1,-1,0,1),(0,1,0,1),对于0我觉得比较好理解,就是Z轴为0,但是最后的是1怎么说呢,它是用来做透视除法的,以后再细说,现在知道默认为1就行
现在OpenGL ES的program程序已经和顶点数据关联起来了,现在还需要使能顶点数据,也就是让OpenGL ES开始更新顶点数据
在glVertexAttribPointer后面加入下述代码
glEnableVertexAttribArray(aPositionLocation);
设置片段着色器输出的颜色,也就是三角形的颜色
后面四个参数代表RGBA,此例为红色
在onDrawFrame()中加入下述代码
glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 0.0f);
最后绘制出来三角形
在onDrawFrame()尾部加入下述代码
//GL_TRIANGLES代表绘制三角形,GL_POINTS代表绘制点,GL_LINES代表绘制直线,0表示从顶点数组的起始位置读顶点,3表示读3个顶点,因为一个三角形有三个顶点
glDrawArrays(GL_TRIANGLES, 0, 3);
现在运行起来屏幕上就会显示红色三角形了