美文网首页
OpenGL(1) —— OpenGL 如何取顶点数据以及画一个

OpenGL(1) —— OpenGL 如何取顶点数据以及画一个

作者: 你可记得叫安可 | 来源:发表于2020-01-24 15:31 被阅读0次

    获取顶点数据

    假设我们有三角形的三个点的数据,每个顶点由 (x, y, z) 三个基向量表示,每个纹理由 (u, v) 两个基向量表示。

    private float[] mTriangleVerticeData = {
                -1.0f, -0.5f, 0.0f, -0.5f, 0.0f,
                1.0f, -0.5f, 0.0f, 1.5f, -0.0f,
                0.0f, 1.1180339f, 0.0f, 0.5f, 1.6183399f
        };
    

    OpenGL 中处理数据没有像面向语言一样那么结构化的存取,只能使用数组的方式进行存取(开发者需要自己知道数组中每个数据表示什么意思)。

    private static final int FLOAT_SIZE_BYTES = 4; // 一个 float 数据占4字节
    private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; // 每5个元素表示一个顶点
    private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; // 第一个顶点坐标数据的偏移量
    private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; // 第一个顶点 U,V 数据的偏移量
    
    private FloatBuffer mTriangleVertices; // 用于将 jvm 堆栈中的属性,存储到计算机的直接内存中
    mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
                    .order(ByteOrder.nativeOrder()).asFloatBuffer(); // 分配直接内存空间
    mTriangleVertices.put(mTriangleVerticesData); // 将堆栈中的顶点数据存到直接内存中
    mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); // 将读数据指针指向索引 0 处,准备开始读数据
    glVertexAttribPointer(aPositionHandle, 3, GL_FLOAT, false,
                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); // 从 0 开始取 3 个字节数据,再间隔 20 字节,再取 3 个,直到数组尾部。这是取块状数据的关键
    GlUtil.checkGlError("glVertexAttribPointer aPositionHandle");
    

    上面的程序块首先将 JVM 中的数据数组 mTriangleVerticeData 转移到直接内存 mTriangleVertices 中,然后再从直接内存中按块读出数据,拷贝到 GPU 存储空间的 aPositionHandle 地址。

    画一个点

    我们先来看看绘制的 Shader 程序:

    1. 顶点 Shader 程序:写了一个点的位置和大小。其中 gl_Position 表示一个存储 4 维向量数组的地址,但是其实我们在下面的应用中,点坐标只有两个值 (x, y)

    为什么只有两个值的坐标却要用 4 维向量来存储?
    点坐标其实是 (x, y, z, 1.0f),表示向量时为 (x, y, z, 0.0f),不论是点表示还是向量表示,最终都会与一个 4x4 的矩阵相乘进行变换。因此必须是一个 4 维向量。

    我们在 onDrawFrame() 绘画时也要注意取值使用 GL_POINTS,这样就会两个 float 一组地取。gl_PointSize 单位为像素。

    attribute vec4 a_Position;
    
    void main()
    {
        gl_Position = a_Position;
        gl_PointSize = 30.0;
    }
    
    1. 片元 Shader 程序:表明顶点图形中应该填充什么颜色。这里的 gl_FragColor 接受一个四维向量,它们依次表示 R, G, B, A。我们在下面 onDrawFrame() 调用绘制时,会将颜色向量传到该地址。
    precision mediump float;
    
    uniform vec4 u_Color;
    
    void main() {
        gl_FragColor = u_Color;
    }
    

    假设我们需要画的点为 (0, 0)

    float[] pointVertex = {
            0f, 0f
    };
    

    按照上面的步骤,我们首先将 JVM 数据拷贝到直接内存中:

    private FloatBuffer vertexData;
    ...
    vertexData = ByteBuffer.allocateDirect(pointVertex.length * BYTES_PER_FLOAT)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer();
    vertexData.put(pointVertex);
    

    然后在 onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) 方法中通过 glGetUniformLocationglGetAttribLocation 来获取点坐标存储在 GPU 中的位置值,并将直接内存中的点坐标数据拷贝到该地址指针中。最后调用 glEnableVertexAttribArray(aPositionLocation) 来使能 aPositionLocation

    通过 glGetUniformLocationglGetAttribLocation 获取到的位置值一般是 0, 1... 这样增加的,不过 aColorLocation 究竟是 0 还是 1,是不可知的。也可以在 glLinkProgram 之前调用 glBindAttribLocation(program, 0, "a_Position") 来指定 aPosotionLocation 值就是 0。
    如果顶点着色器中使用了 attribute 属性的变量(在本例中是 a_Position),那么在 GPU 执行着色器代码之前,一定要通过 glEnableVertexAttribArray() 使能这个变量,否则在 GPU执行着色器代码时(通过调用 glDraw**()),是无法访问到该 attribute 属性的。

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        // 前面是 shader 程序编译和初始化
        ...
        aColorLocation = glGetUniformLocation(program, U_COLOR); // 获取颜色信息在 GPU 存储空间的地址
        aPositionLocation = glGetAttribLocation(program, A_POSITION); // 获取点在 GPU 存储空间的地址
        vertexData.position(0);
        Timber.d("enable vertex attribute");
        glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT, false, 0, vertexData); // 拷贝点坐标到 GPU 存储空间
        glEnableVertexAttribArray(aPositionLocation);
    }
    

    然后在 onDrawFrame(GL10) 时,指明每一帧的绘制的数据颜色和位置。

    @Override
    public void onDrawFrame(GL10 gl10) {
        glClear(GL_COLOR_BUFFER_BIT);
        glUniform4f(aColorLocation, 0.0f, 1.0f, 0.5f, 1.0f);
        glDrawArrays(GL_POINTS, 0, 1);
    }
    

    其中 glUniform4f 中指明的颜色是 R, G, B, A 排列。上面的 (0.0f, 1.0f, 0.5f, 1.0f) 表示绿色,每个元素的取值范围是 0.0f ~ 1.0f 之间,表示 0~255 的归一化数据。

    画一条线

    1. 线的连个点坐标:
    float[] lineVertex = {
            -0.5f, 0.5f,
            0.5f, -0.5f
    };
    
    1. 绘制时使用 GL_LINES
    glDrawArrays(GL_LINES, 0, 2);
    

    画三角形

    1. 三角形的 3 个顶点,注意是逆时针的 3 个点
    float[] triangleVertex = {
            -1f, 1f,
            -1f, -1f,
            1f, -1f
    };
    

    OpenGL 中所有形状的定义都最好按照逆时针来定义,这是因为 OpenGL 中所有形状都有正面和反面,逆时针定义的面是正面,顺时针定义的面是反面。当你开启 face culling 时,OpenGL 将不会画反面,而只会画正面。因此逆时针定义形状是约定俗成。https://developer.android.com/guide/topics/graphics/opengl.html#faces-winding

    1. 绘制时使用 GL_TRIANGLES
    glDrawArrays(GL_TRIANGLES, 0, 3);
    

    https://github.com/fightyz/OpenGLPlayground
    checkout 到 a650a852fe91d2ad89ca9deab22306979e8a2c7e

    相关文章

      网友评论

          本文标题:OpenGL(1) —— OpenGL 如何取顶点数据以及画一个

          本文链接:https://www.haomeiwen.com/subject/talhzctx.html