Android OpenGL ES开发(五)正方形和圆

前言

前面提到过,在OpenGL ES 世界里面是没有正方形和圆形的,只有点、线、三角形。三角形是OpenGL ES提供的最复杂的土元单位。序偶一我们要绘制填充正方形和圆形就需要利用三角形来充实。

正方形

正方形的构建比较简单,可以用两个三角形组成。当然,你也可以用很多三角形去合成一个正方形,只要你乐意。如下图所示,我们可以按照123组成的三角形和134组成的三角形,两个拼成一个正方形。


1.jpg

可以设置正方形的坐标数组为:

    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);
    }
3.jpg

圆形

圆形的构建,相对复杂一点,我们可以把圆形看成一个正多边形,边越多,圆越平滑。如下图所示,分别为正六边形、正八边形、正十六边形和正一百边形。


2.jpg

以六边形为例,由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);
    }
4.jpg

绘制总结

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,称之为索引法,是根据索引序列,在顶点序列中找到对应的顶点,并根据绘制的方式,组成相应的图元进行绘制。

顶点法拥有的绘制方式,所引发也都有。相对于顶点发在复杂图形的绘制中无法避免大量顶点重复的情况,所引发可以相对顶点发减少很多重复顶点占用的空间。

你可能感兴趣的:(Android OpenGL ES开发(五)正方形和圆)