【OpenGL ES 3.x】绘制点线面

938 阅读6分钟

我正在参加「掘金·启航计划

使用GLSurfaceView

GLSurfaceView继承自SurfaceView同样也是在View基础上具有创建独立Surface能力。另外GLSurfaceView又在SurfaceView上实现了使用OpenGL的能力,可通过它创造出OpenGL环境在Surface进行绘制。因此学习使用GLSurfaceView也是学习OpenGL ES重要环节之一。

如下是GLSurfaceView最基础使用方法:

  1. 继承GLSurfaceView创建PointGLRenderer
  2. PointGLRenderer构造方法中配置EGL相关初始化
  3. SceneRenderer继承Renderer主要实现三个回调方法:Surface创建回调、Surface窗口大小回调、渲染绘制刷新回调。
  4. 实际中最重要部分就是Point对象实现,它主要负责顶点着色器、片元着色器以及GL程序实现和运行。
public class PointGLRenderer extends GLSurfaceView {

    SceneRenderer mRenderer;//自定义渲染器的引用
    Point point;

    public PointGLRenderer(Context context) {
        super(context);
        this.setEGLContextClientVersion(3);
        mRenderer = new SceneRenderer();
        this.setRenderer(mRenderer);
        this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

    private class SceneRenderer implements Renderer {

        public void onDrawFrame(GL10 gl) {
            //清除深度缓冲与颜色缓冲
            GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
            point.drawSelf();
        }


        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            //设置屏幕背景色RGBA
            GLES30.glClearColor(1, 1, 1, 1.0f); // 白色
            //创建对象
            point = new Point(PointGLRenderer.this);
        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            GLES30.glViewport(0, 0, width, height);
        }
    }
}

GLSurfaceView的简单使用就是这样,只需要知道其内部已经帮助开发者封装实现GL环境。(当然开发者也可以自行实现EGL环境使开发过程更可控,但绝大多数情况下GLSurfaceView已满足开发需求)

点线面

OpenGL中绘制是支持线三角形,其中三角形可构成

首先大致先了解OpenGL环境下坐标系情况,原点位置(0,0)在中心,左上角坐标为(-1,1),右下角坐标为(-1,-1),如图所展示只是2维空间上xy坐标,另外z坐标暂时还未涉及,等涉及到3维空间投影模式再做介绍。

坐标系坐标系上绘制
坐标系.png坐标系2.png

点绘制

绘制点信息需要配置点坐标信息。例如下面代码中点坐标配置vertices[],其中第一个坐标信息是(-0.9f, 0.9f,0),对应的坐标系是(x,y,z)。由于坐标系x轴从左到右是[-1,1],y轴从上到下是[1,-1],z轴暂时先不考虑。因此第一个点坐标信息所在位置大致是在左上角。另外colors[]设定了每个点的颜色

    public void initVertexData()//初始化顶点数据的方法
    {
        // 点坐标
        float vertices[] = new float[]{//顶点坐标数组
                -0.9f, 0.9f , 0, //1
                0.9f, 0.9f  , 0,
                -0.9f, -0.9f, 0,
                0.9f, -0.9f , 0,

        };
        mVertexBuffer = BufferUtil.creatFloatBuffer(vertices);
        // 点颜色
        float colors[] = new float[]{//顶点颜色数组
                1, 0, 0, 0,//   // 红
                0, 1, 0, 0,     // 绿
                0, 0, 1, 0,     // 蓝
                1, 0, 1, 0,     // 紫

        };

        mColorBuffer = BufferUtil.creatFloatBuffer(colors);
    }

顶点着色器代码部分,输入值aPositionaColor分别代表顶点位置和顶点颜色

#version 300 es
layout (location = 0) in vec3 aPosition;  //顶点位置
layout (location = 1) in vec4 aColor;    //顶点颜色
out  vec4 vColor;  //用于传递给片元着色器的变量

void main()
{
   gl_Position = vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置
   vColor = aColor;//将接收的颜色传递给片元着色器
   gl_PointSize=10.0;//点大小
}

对应的在Java代码层获取到aPositionaColor的引用,在加载脚本信息后获取maPositionHandlemaColorHandle表示为aPositionaColor的引用。

//初始化着色器的方法
    public void initShader(GLSurfaceView mv) {
        //加载顶点着色器的脚本内容
        mVertexShader = ShaderUtil.loadFromAssetsFile("samplexs/samplex1/vertex_samplex_1.vsh", mv.getResources());
        //加载片元着色器的脚本内容
        mFragmentShader = ShaderUtil.loadFromAssetsFile("samplexs/samplex1/frag_samplex_1.fsh", mv.getResources());
        //基于顶点着色器与片元着色器创建程序
        mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
        //获取程序中顶点位置属性引用
        maPositionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition");
        //获取程序中顶点颜色属性引用
        maColorHandle = GLES30.glGetAttribLocation(mProgram, "aColor");
    }

取到引用之后分别将顶点位置数据和颜色数据送入到管线中并在GL环境下开启引用可用,最后使用绘制点方法绘制点信息。

 //将顶点位置数据送入渲染管线
        GLES30.glVertexAttribPointer(
                maPositionHandle, 3,
                GLES30.GL_FLOAT, false,
                3 * 4, mVertexBuffer);
        //将顶点颜色数据送入渲染管线
        GLES30.glVertexAttribPointer(
                maColorHandle, 4,
                GLES30.GL_FLOAT, false,
                4 * 4, mColorBuffer);


        //启用顶点位置数据数组
        GLES30.glEnableVertexAttribArray(maPositionHandle);
        //启用顶点颜色数据数组
        GLES30.glEnableVertexAttribArray(maColorHandle);
        // 五个点 1
        GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 4);
  1. mVertexBuffer是顶点坐标,因为每个顶点由xyz三个坐标组成因此size为3,又因为数据类型是FLOAT,每个数据是4位因此stride为3 * 4;
  2. maColorHandle是颜色数据,同理因为每个色值由RGBA四个组成,因此size为4,又因为数据类型是FLOAT因此stride为4 * 4。因为·
  3. GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 4)坐标点数组是4个同时起始下标为0,因此是从0开始绘制一共绘制4个点,绘制形式是POINTS

点.jpeg

线绘制

绘制线的方法和绘制点在步骤上基本一样,不同点在于glDrawArrays的绘制模式不同,且绘制线有多种形式。 在此之前已知点绘制是xyz三个坐标确定,点颜色绘制是有rgba确定,线的绘制也是如此(因为由点组成线)。在绘制线之前修改vertices[]colors[]数据源,绘制线需要增加更多的坐标信息。

  public void initVertexData()//初始化顶点数据的方法
    {
        float vertices[] = new float[]{//顶点坐标数组

                -0.8f, 0.4f, 0, //2
                -0.7f, 0.4f, 0,
                -0.8f, 0.3f, 0,
                -0.7f, 0.3f, 0,


                -0.6f, 0.4f, 0,//3
                -0.5f, 0.4f, 0,
                -0.6f, 0.3f, 0,
                -0.5f, 0.3f, 0,

                -0.4f, 0.4f, 0,//4
                -0.3f, 0.4f, 0,
                -0.4f, 0.3f, 0,
                -0.3f, 0.3f, 0,

        };
        mVertexBuffer = BufferUtil.creatFloatBuffer(vertices);

        float colors[] = new float[]{//顶点颜色数组

                1, 0, 0, 0,//2 // 红
                0, 1, 0, 0, // 绿
                0, 0, 1, 0, // 蓝
                1, 0, 1, 0, // 紫

                1, 0, 0, 0,//3 // 红
                0, 1, 0, 0, // 绿
                0, 0, 1, 0, // 蓝
                1, 0, 1, 0, // 紫

                1, 0, 0, 0,//4// 红
                0, 1, 0, 0, // 绿
                0, 0, 1, 0, // 蓝
                1, 0, 1, 0, // 紫

        };

        mColorBuffer = BufferUtil.creatFloatBuffer(colors);
    }

线的绘制提供了三种模式:GLES30.GL_LINESGLES30.GL_LINE_STRIPGLES30.GL_LINE_LOOP

  1. GLES30.GL_LINES会根据顶点顺序两两一组为线段进行绘制。
  2. GLES30.GL_LINE_STRIP会根据顶点顺序依次连接组成线段进行绘制。
  3. GLES30.GL_LINE_LOOP和GLES30.GL_LINE_STRIP绘制形式一样,区别在于最后一个顶点和第一个顶点会相连形成线段环。
        // 四个点 2
        GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 4);
        // 两条线
        GLES30.glDrawArrays(GLES30.GL_LINES, 0, 4);

        // 四个点 3
        GLES30.glDrawArrays(GLES30.GL_POINTS, 4, 4);
        // 两条线
        GLES30.glDrawArrays(GLES30.GL_LINE_STRIP, 4, 4);

        // 四个点 4
        GLES30.glDrawArrays(GLES30.GL_POINTS, 8, 4);
        // 两条线
        GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 8, 4);

PS: GLES30.glLineWidth(5) 可以设置线段宽度

线.jpeg

面绘制

由于在OepnGL环境下只有绘制三角形的方法,因此绘制面其实就是绘制三角形。此外和线绘制一样,三角形绘制也有多种模式可选:GLES30.GL_TRIANGLESGLES30.GL_TRIANGLE_STRIPGLES30.GL_TRIANGLE_FAN

  1. GLES30.GL_TRIANGLES会按照顶点顺序每3个组成三角形进行绘制。因此在绘制一个矩形时会有两对顶点是重合关系,存在顶点冗余。
  2. GLES30.GL_TRIANGLE_STRIP会按照顶点顺序依次组成三角形。例如N个顶点则绘制N-2个三角形。此方法会节省顶点空间。
  3. GLES30.GL_TRIANGLE_FAN按照第一个顶点作为中心点,其他顶点作为边缘点绘制出扇形三角形。

矩形面设定vertices[]配置,GLES30.GL_TRIANGLES绘制方式因为顶点重合所以需要6个顶点绘制一个矩形;GLES30.GL_TRIANGLE_STRIP绘制方式优化了顶点数量,可以用4个顶点绘制一个矩形;GLES30.GL_TRIANGLE_FAN不同于以上两种顶点坐标需要以顺时针或者逆时针顺序组成矩形。具体代码如下所示:

public void initVertexData()//初始化顶点数据的方法
    {
        // 五个点坐标
        float vertices[] = new float[]{//顶点坐标数组

                -0.8f, 0.4f, 0, //2
                -0.7f, 0.4f, 0,
                -0.8f, 0.3f, 0,
                -0.7f, 0.4f, 0,
                -0.8f, 0.3f, 0,
                -0.7f, 0.3f, 0,


                -0.6f, 0.4f, 0,//3
                -0.5f, 0.4f, 0,
                -0.6f, 0.3f, 0,
                -0.5f, 0.3f, 0,


                -0.4f, 0.4f, 0,//4
                -0.4f, 0.3f, 0,
                -0.3f, 0.3f, 0,
                -0.3f, 0.4f, 0,

        };
        mVertexBuffer = BufferUtil.creatFloatBuffer(vertices);
    }

面.jpeg