Android OpenGL ES2.0从放弃到入门(三)—— 绘制正方形、圆形和立方体

上一篇文章中,我们通过去写一个三角形,了解了OpenGL ES大概怎么用,这篇文章我们会去尝试构建更多的图形。由于之前的代码,我们已经构建好了部分基础类,所以我们就按照图形一个一个去讲解。

正方形

由于我们知道,OpenGL ES相比于OpenGL,舍弃了很多基本图形,只保留了三角形的绘制,那我们如何去画一个正方形呢?其实很简单,把一个正方形拆解成两个三角形就可以了

接下来我们仿照上一篇写三角形的代码,来创建我们的正方形类Square

public class Square extends Shape{

    static float triangleCoords[] = {
            -0.5f, 0.5f, 0.0f, // top left
            -0.5f, -0.5f, 0.0f, // bottom left
            0.5f, -0.5f, 0.0f, // bottom right
            0.5f, 0.5f, 0.0f  // top right
    };

    static short index[] = {
            0, 1, 2, 0, 2, 3
    };

    //顶点个数
    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    //顶点之间的偏移量
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 每个顶点四个字节

    private int mMatrixHandler;

    //设置颜色,依次为红绿蓝和透明通道
    float color[] = {1.0f, 0.6f, 0.3f, 1.0f};

    @Override
    public void init() {
        ByteBuffer bb = ByteBuffer.allocateDirect(
                triangleCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(triangleCoords);
        vertexBuffer.position(0);

        ByteBuffer cc = ByteBuffer.allocateDirect(index.length * 2);
        cc.order(ByteOrder.nativeOrder());
        indexBuffer = cc.asShortBuffer();
        indexBuffer.put(index);
        indexBuffer.position(0);

        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        //创建一个空的OpenGLES程序
        mProgram = GLES20.glCreateProgram();
        //将顶点着色器加入到程序
        GLES20.glAttachShader(mProgram, vertexShader);
        //将片元着色器加入到程序中
        GLES20.glAttachShader(mProgram, fragmentShader);
        //连接到着色器程序
        GLES20.glLinkProgram(mProgram);
    }

    @Override
    public void onDraw(float[] mMVPMatrix) {
        //将程序加入到OpenGLES2.0环境
        GLES20.glUseProgram(mProgram);
        //获取变换矩阵vMatrix成员句柄
        mMatrixHandler = GLES20.glGetUniformLocation(mProgram, "vMatrix");
        //指定vMatrix的值
        GLES20.glUniformMatrix4fv(mMatrixHandler, 1, false, mMVPMatrix, 0);
        //获取顶点着色器的vPosition成员句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        //启用三角形顶点的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //准备三角形的坐标数据
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);
        //获取片元着色器的vColor成员的句柄
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
        //设置绘制三角形的颜色
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);
        //绘制三角形
//        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);
        //索引法绘制正方形
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, index.length, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
        //禁止顶点数组的句柄
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }

    private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
                    "uniform mat4 vMatrix;" +
                    "void main() {" +
                    "  gl_Position = vMatrix*vPosition;" +
                    "}";

    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "uniform vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";
}

发现比三角形好像有点不一样,我们一个一个来看。
首先在代码中多了个index[ ]。这个是用来存放顶点索引的。我们把一个正方形分成了两个三角形,按照上一篇我们的绘制方法,肯定有顶点既属于这个三角形,又属于那个三角形。为了能够较少这种顶点重复写法,于是就有了索引。

顶点法和索引法

我们之前绘制三角形所用的方法是GLES20.glDrawArrays,这是顶点法,根据我们传入的点去一个一个的绘制。而我们在正方形绘制时,使用的是GLES20.glDrawElements方法,称之为索引法,根据我们传入的索引序列,去顶点序列中寻找对应的顶点,然后去绘制图形。索引法的优势就是相比顶点法减少很多重复顶点占用空间。

绘制结果如下:

圆形

圆形的构建就稍微复杂点了,我们可以把圆和正多边形放在一起去考虑,如果正多边形的边无限的去增加,那不就是圆了吗,盗用别人的图说明下:

既然是圆,我们就需要知道圆上的X和Y的坐标,这就需要我们用到三角函数。在我们代码中有Math.sinMath.cos

Math.sin(x) x 的正玄值。返回值在 -1.0 到 1.0 之间;X 都是指的“弧度”而非“角度”,弧度的计算公式为: 2PI/360角度

同理Math.cos也是一样的,那这就好办了,我们写个方法,来创建圆形的X和Y的坐标,由于我们现阶段只做平面的图形,暂不考虑Z。

	private float[] createPositions() {
        ArrayList<Float> data = new ArrayList<>();
        //设置圆心坐标
        data.add(0.0f);
        data.add(0.0f);
        data.add(height);
        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(height);
        }
        float[] f = new float[data.size()];
        for (int i = 0; i < f.length; i++) {
            f[i] = data.get(i);
        }
        return f;
    }

其中height为Z轴的数值,可能觉得奇怪了,不是和Z无关吗,此处是为了后续为立体图形拓展的时候使用,先留个伏笔。变量n为切割分数,此处我们让n = 360,这样画出来就像个圆了。
其他的方法和正方形的绘制都差不多,就是在具体绘制的时候,调用的方法需要注意,如下:

GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, shapePos.length / 3);

我们发现绘制方法又用回了顶点法,但三角形引用顶点法时,参数GLES20.GL_TRIANGLE_FAN不一样。这里我们解读下绘制方法的三个参数。
GLES20.glDrawArrays的第一个参数表示绘制方式,第二个参数表示偏移量,第三个参数表示顶点个数。接下来我们来看看都有哪些绘制方式

  1. int GL_POINTS //将传入的顶点坐标作为单独的点绘制
  2. int GL_LINES //将传入的坐标作为单独线条绘制,ABCDEFG六个顶点,绘制AB、CD、EF三条线
  3. int GL_LINE_STRIP //将传入的顶点作为折线绘制,ABCD四个顶点,绘制AB、BC、CD三条线
  4. int GL_LINE_LOOP //将传入的顶点作为闭合折线绘制,ABCD四个顶点,绘制AB、BC、CD、DA四条线。
  5. int GL_TRIANGLES //将传入的顶点作为单独的三角形绘制,ABCDEF绘制ABC,DEF两个三角形
  6. int GL_TRIANGLE_FAN //将传入的顶点作为扇面绘制,ABCDEF绘制ABC、ACD、ADE、AEF四个三角形
  7. int GL_TRIANGLE_STRIP //将传入的顶点作为三角条带绘制,ABCDEF绘制ABC,BCD,CDE,DEF四个三角形

这里可以看到,在我们构建圆形的时候,采用的是将顶点作为扇面绘制。绘制出来的效果是:
在这里插入图片描述

立方体

之前写的都是些平面图形,现在我们可以去操纵Z的值去绘制一些立体的几何图形了。同样,我们先去构建顶点坐标和坐标索引

final float cubePositions[] = {
            -1.0f, 1.0f, 1.0f,    //正面左上0
            -1.0f, -1.0f, 1.0f,   //正面左下1
            1.0f, -1.0f, 1.0f,    //正面右下2
            1.0f, 1.0f, 1.0f,     //正面右上3
            -1.0f, 1.0f, -1.0f,    //反面左上4
            -1.0f, -1.0f, -1.0f,   //反面左下5
            1.0f, -1.0f, -1.0f,    //反面右下6
            1.0f, 1.0f, -1.0f,     //反面右上7
    };
    final short index[] = {
            6, 7, 4, 6, 4, 5,    //后面
            6, 3, 7, 6, 2, 3,    //右面
            6, 5, 1, 6, 1, 2,    //下面
            0, 3, 2, 0, 2, 1,    //正面
            0, 1, 5, 0, 5, 4,    //左面
            0, 7, 3, 0, 4, 7,    //上面
    };

同时这次我们换点不一样的东西,我们让我们的顶点,每一个顶点都去拥有不一样的颜色。想实现我们的需求,就要先定义一个保存顶点颜色的数组:

float color[] = {
            1f, 0f, 0f, 1f,
            0f, 1f, 0f, 1f,
            0f, 0f, 1f, 1f,
            0f, 1f, 0f, 1f,
            1f, 0f, 0f, 1f,
            0f, 1f, 0f, 1f,
            0f, 0f, 1f, 1f,
            0.5f, 0.5f, 0.5f, 1f,
    };

颜色是我随机写的,估计成品可能会有点辣眼睛。既然我们让每个顶点都拥有自己的颜色,那么我们原来的顶点着色器和片元着色器的代码可能就不满足了,那我们修改一下:

private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
                    "uniform mat4 vMatrix;" +
                    "varying  vec4 vColor;" +
                    "attribute vec4 aColor;" +
                    "void main() {" +
                    "  gl_Position = vMatrix*vPosition;" +
                    "  vColor=aColor;" +
                    "}";

    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "varying vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";

可以看到,我们在顶点着色器代码中,多了varying vec4 vColor参数,此参数的目的就是将我们根据顶点获取的颜色传递给片元着色器去上色。既然我们着色器的代码发生了变动,那么我们在绘制具体图形的地方代码也应该进行相应的更改:

	@Override
    public void onDraw(float[] mMVPMatrix) {
        //将程序加入到OpenGLES2.0环境
        GLES20.glUseProgram(mProgram);
        //获取变换矩阵vMatrix成员句柄
        mMatrixHandler = GLES20.glGetUniformLocation(mProgram, "vMatrix");
        //指定vMatrix的值
        GLES20.glUniformMatrix4fv(mMatrixHandler, 1, false, mMVPMatrix, 0);
        //获取顶点着色器的vPosition成员句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        //启用三角形顶点的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //准备三角形的坐标数据
        GLES20.glVertexAttribPointer(mPositionHandle, 3,
                GLES20.GL_FLOAT, false,
                0, vertexBuffer);
        //获取片元着色器的vColor成员的句柄
        mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
        //设置绘制三角形的颜色
//        GLES20.glUniform4fv(mColorHandle, 2, color, 0);
        GLES20.glEnableVertexAttribArray(mColorHandle);
        GLES20.glVertexAttribPointer(mColorHandle, 4,
                GLES20.GL_FLOAT, false,
                0, colorBuffer);
        //索引法绘制正方体
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, index.length, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
        //禁止顶点数组的句柄
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }

还有一个需要注意的地方,在我们开启绘制的时候,需要开启深度缓冲GLES20.glEnable(GLES20.GL_DEPTH_TEST),同时在绘制前也要对应的去清除深度缓冲GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT)。那么何为深度缓冲

在计算机图形学中,深度缓冲是在三维图形中处理图像深度坐标的过程,这个过程通常在硬件中完成,它也可以在软件中完成,它是可见性问题的一个解决方法。可见性问题是确定渲染场景中哪部分可见、哪部分不可见的问题。画家算法是另外一种常用的方法,尽管效率较低,但是也可以处理透明场景元素。深度缓冲也称为Z缓冲。

通俗点的理解,我们的计算机在画图形的时候是有一定的步骤的,当画完近处的物体,去画远端的物体。在没有开启深度缓冲的情况下,就会对近处的物体产生一定的遮挡,虽然我们设置了不同的Z值。但是这种效果肯定不是我们所希望的。我们开启了深度缓冲,我们在缓存中就会保存对应像素的深度值(可以理解为Z值),这样在绘制的时候,就不会有相互遮挡的感觉了。

来跑一下看看具体效果:
在这里插入图片描述
只能说,确实有点辣眼睛。

之所以把立方体和平面图形放在一起,是因为立方体的绘制方法和正方形大同小异,代码层面更容易理解。在下一篇博客中,我们就要画点不一样的立体图形了。
Android OpenGL ES2.0从放弃到入门(四)—— 绘制圆柱体和球体

源码地址

关于OpenGL代码全部在一个项目中,托管在Github上——OpenGL4Android

欢迎转载,转载请保留文章出处。有梦想的咸鱼9527的博客https://blog.csdn.net/weixin_39560693/article/details/95513995

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值