美文网首页
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