前言
前面提到过,在OpenGL ES 世界里面是没有正方形和圆形的,只有点、线、三角形。三角形是OpenGL ES提供的最复杂的土元单位。序偶一我们要绘制填充正方形和圆形就需要利用三角形来充实。
正方形
正方形的构建比较简单,可以用两个三角形组成。当然,你也可以用很多三角形去合成一个正方形,只要你乐意。如下图所示,我们可以按照123组成的三角形和134组成的三角形,两个拼成一个正方形。
可以设置正方形的坐标数组为:
private float triangleCoors[] = {
-0.5f, 0.5f, 0.0f, // top left 0
-0.5f, -0.5f, 0.0f, // bottom left 1
0.5f, -0.5f, 0.0f, // bottom right 2
0.5f, 0.5f, 0.0f // top right 3
};
颜色
private float color[] = {
1.0f , 1.0f , 1.0f , 1.0f
};
根据上图1绘制三角形坐标的顺序
private short index[] = {
0,1,2,0,2,3
};
顶点着色器
private final String vertextShaderCode =
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"void main() {" +
" gl_Position = vMatrix * vPosition;" +
"}";
vPosition:接收顶点坐标
vMatrix:变换矩阵,作用上篇文章已经说过
片元着色器
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
vColor:接收颜色
onSurfaceCreated 方法
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
//将背景设置为灰色
GLES20.glClearColor(0.0f,0.0f,0.0f,1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//申请底层空间 顶点坐标
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(triangleCoors.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
//将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
vertexBuffer = byteBuffer.asFloatBuffer();
//将三角形坐标传入FloatBuffer
vertexBuffer.put(triangleCoors);
vertexBuffer.position(0);
//申请底层空间 坐标顺序
ByteBuffer indexBuffer = ByteBuffer.allocateDirect(index.length * 2);
indexBuffer.order(ByteOrder.nativeOrder());
indexShortBuffer = indexBuffer.asShortBuffer();
indexShortBuffer.put(index);
indexShortBuffer.position(0);
//创建顶点着色器程序
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertextShaderCode);
//创建片元着色器程序
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
if (vertexShader == 0 || fragmentShader == 0)
{
return;
}
//创建一个空的OpenGL ES程序
program = GLES20.glCreateProgram();
//将顶点着色器加入程序
GLES20.glAttachShader(program, vertexShader);
//将片元着色器加入程序
GLES20.glAttachShader(program, fragmentShader);
//连接到着色器程序中
GLES20.glLinkProgram(program);
//使用程序
GLES20.glUseProgram(program);
}
onSurfaceChanged方法
@Override
public void onSurfaceChanged(GL10 gl, int width, int height)
{
GLES20.glViewport(0,0,width,height);
float ratio = (float) width/height;
//设置透视矩阵
Matrix.frustumM(mProjectMatrix,0,-ratio,ratio,-1,1,3,7);
//设置相机位置
Matrix.setLookAtM(mViewMatrix,0,0,0,7.0f,0,0,0,0,1.0f,0);
//计算变换矩阵
Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
}
onDrawFrame 方法
@Override
public void onDrawFrame(GL10 gl)
{
if(program == 0)
return;
//获取变换矩阵vMatrix成员句柄
int vMatrix = GLES20.glGetUniformLocation(program,"vMatrix");
//设置vMatrix的值
GLES20.glUniformMatrix4fv(vMatrix,1,false,mMVPMatrix,0);
//获取顶点着色器的vPosition成员句柄
int vPosition = GLES20.glGetAttribLocation(program, "vPosition");
//启用vPosition句柄
GLES20.glEnableVertexAttribArray(vPosition);
//传的坐标数据
GLES20.glVertexAttribPointer(vPosition,3,GLES20.GL_FLOAT,false,3*4, vertexBuffer);
//获取顶点着色器的vColor成员句柄
int aColor = GLES20.glGetUniformLocation(program, "vColor");
//设置绘制三角形的颜色
GLES20.glUniform4fv(aColor, 1, color, 0);
//绘制三角形
GLES20.glDrawElements(GLES20.GL_TRIANGLES,index.length,GLES20.GL_UNSIGNED_SHORT,indexShortBuffer);
//禁止顶点数组的句柄
GLES20.glDisableVertexAttribArray(vPosition);
}
圆形
圆形的构建,相对复杂一点,我们可以把圆形看成一个正多边形,边越多,圆越平滑。如下图所示,分别为正六边形、正八边形、正十六边形和正一百边形。
以六边形为例,由012、023,034、045、056、061六个三角形,更多变形同样如此。
利用简单的数学知识,即可得到,以多边形中心建立直角坐标系,得到n变形的顶点坐标为L:
private float[] createPositions()
{
ArrayList data = new ArrayList<>();
data.add(1.0f); //设置圆心坐标
data.add(1.0f);
data.add(0.0f);
float angDegSpan = 360f / n;
for (float i = 0; i < 360 + angDegSpan; i += angDegSpan)
{
data.add((float) (radius * Math.sin(i * Math.PI / 180f)));
data.add((float) (radius * Math.cos(i * Math.PI / 180f)));
data.add(0.0f);
}
float[] f = new float[data.size()];
for (int i = 0; i < f.length; i++)
{
f[i] = data.get(i);
}
return f;
}
createPositions方法得到顶点坐标数组,剩下的工作就和三角形的绘制基本相同,唯一不同的地方是需要修改:
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 1);
为:
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, shapePos.length/3);
public static native void glDrawArrays(
int mode, //表示绘制方式
int first, //表示偏移量
int count //顶点个数
);
绘制方式有:
GL_POINTS //将传入的顶点坐标作为单独的点绘制
GL_LINES //将传入的顶点坐标作为单独线条绘制,ABCDEF六个顶点,绘制AB、CD、EF三条线
GL_LINE_LOOP // 将传入的顶点作为闭合折线绘制 ,ABCD四个顶点,绘制AB、BC、CD、DA四条线
GL_LINE_STRIP //将传入的顶点作为折线绘制,ABCD四个顶点,绘制AB、BC、CD三条线
GL_TRIANGLES //将传入的顶点作为单独的三角形绘制,ABCDEF六个顶点,绘制ABC、DEF两个三角形
GL_TRIANGLE_STRIP //将传入的顶点作为三角条带绘制,ABCDEF六个顶点,绘制ABC、BCD、CDE、DEF四个三角形
GL_TRIANGLE_FAN //将传入的顶作为扇面绘制,ABCDEF六个顶点,绘制ABC、ACD、ADE、AEF四个三角形
颜色
private float color[] = {
1.0f , 1.0f , 1.0f , 1.0f
};
顶点着色器
private final String vertextShaderCode =
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"void main() {" +
" gl_Position = vMatrix * vPosition;" +
"}";
vPosition:接收顶点坐标
vMatrix:变换矩阵,作用上篇文章已经说过
片元着色器
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
vColor:接收颜色
onSurfaceCreated 方法
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
//将背景设置为灰色
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//圆顶点坐标
shapePos = createPositions();
//申请底层空间
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(shapePos.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
//将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
vertexBuffer = byteBuffer.asFloatBuffer();
//将三角形坐标传入FloatBuffer
vertexBuffer.put(shapePos);
vertexBuffer.position(0);
//申请底层空间
ByteBuffer colorBuffer = ByteBuffer.allocateDirect(color.length * 4);
colorBuffer.order(ByteOrder.nativeOrder());
colorFloatBuffer = colorBuffer.asFloatBuffer();
colorFloatBuffer.put(color);
colorFloatBuffer.position(0);
//创建顶点着色器程序
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertextShaderCode);
//创建片元着色器程序
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
if (vertexShader == 0 || fragmentShader == 0)
{
return;
}
//创建一个空的OpenGL ES程序
program = GLES20.glCreateProgram();
//将顶点着色器加入程序
GLES20.glAttachShader(program, vertexShader);
//将片元着色器加入程序
GLES20.glAttachShader(program, fragmentShader);
//连接到着色器程序中
GLES20.glLinkProgram(program);
//使用程序
GLES20.glUseProgram(program);
}
onSurfaceChanged 方法
@Override
public void onSurfaceChanged(GL10 gl, int width, int height)
{
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
//设置透视矩阵
Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
//设置相机位置
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7.0f, 0, 0, 0, 0, 1.0f, 0);
//计算变换矩阵
Matrix.multiplyMM(mMVPMatrix, 0, mProjectMatrix, 0, mViewMatrix, 0);
}
onDrawFrame 方法
@Override
public void onDrawFrame(GL10 gl)
{
if (program == 0)
return;
//获取变换矩阵vMatrix成员句柄
int vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
//设置vMatrix的值
GLES20.glUniformMatrix4fv(vMatrix, 1, false, mMVPMatrix, 0);
//获取顶点着色器的vPosition成员句柄
int vPosition = GLES20.glGetAttribLocation(program, "vPosition");
//启用vPosition句柄
GLES20.glEnableVertexAttribArray(vPosition);
//传的坐标数据
GLES20.glVertexAttribPointer(vPosition, 3, GLES20.GL_FLOAT, false, 3 * 4, vertexBuffer);
//获取顶点着色器的vColor成员句柄
int aColor = GLES20.glGetUniformLocation(program, "vColor");
//设置绘制三角形的颜色
GLES20.glUniform4fv(aColor, 1, color, 0);
//绘制三角形
// GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,4);
// GLES20.glDrawElements(GLES20.GL_TRIANGLES,index.length,GLES20.GL_UNSIGNED_SHORT,indexShortBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 1, shapePos.length / 3);
//禁止顶点数组的句柄
GLES20.glDisableVertexAttribArray(vPosition);
}
绘制总结
GL_TRIANGLE_STRIP
由上面我们可以知道,GL_TRIANGLE_STRIP的方式绘制连续的三角形,比直接用GL_TRIANGLES的方式少好几个顶点,效率会高很多。另外,GL_TRIANGLE_STRIP并不只能绘制连续的三角形构造的物体,我们只需要将不需要重复绘制的点重复两次即可。比如,传入ABCDEEFFGH坐标,就会得到ABC、BCD、CDE以及FGH四个三角形。
GL_TRIANGLE_FAN
扇面绘制是以第一个为零点进行绘制,通常我们绘制圆形,圆锥的锥面都会使用到,值得注意的是,最后一个点的左边应当与第二个重合,在计算的时候,起始角度为0度,重点角度包含360度。
顶点法和索引法
GLES20.glDrawArrays,也就是顶点法,是根据传入的顶点顺序进行绘制的。
GLES20.glDrawElements,称之为索引法,是根据索引序列,在顶点序列中找到对应的顶点,并根据绘制的方式,组成相应的图元进行绘制。
顶点法拥有的绘制方式,所引发也都有。相对于顶点发在复杂图形的绘制中无法避免大量顶点重复的情况,所引发可以相对顶点发减少很多重复顶点占用的空间。