欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenGL ES 3.0 学习 —— 绘制一个三角形

程序员文章站 2022-07-04 09:08:58
...

上篇 我们使用 OpenGL ES 3.0 给 GLSurfaceView 绘制了一个背景色,现在是时候更加一步了,我们来画一个三角形。

首先,我们来了解一下什么是图形管线(graphics pipeline)。GPU 把绘图的过程分成了几步,每一步执行一些特定的任务。使用 OpenGL ES,可以让我们指定其中的某些步骤,从而获得极大的灵活性:
1. 首先是顶点着色器(vertex shader)。他用于接收 CPU 的数据,然后输出图元(primitives)的顶点。在 OpenGL 的世界里,我们只能绘制三角形、直线和点。这三个形状就是所谓的图元。
2. 顶点着色器输出顶点数据后,经由形状装配、光栅化等,得到一个个的小片段(可以理解为一个个像素点)。
3. 接下来,这些小片段会交由片段着色器(fragment shader)处理。片段着色器的任务是确定像素点的颜色。

在这些步骤里,我们关心的只是顶点着色器和片段着色器。他们由程序员通过 OpenGL ES SL 语言(一种类 C 语言)编写。使用 OpenGL ES 3 的时候,如果没有同时指定顶点着色器和片段着色器,我们将无法绘制任何东西。

有了这两个基本概念后,我们就可以开始动手写程序。完整的代码可以在 Github 上找到。

首先,我们来编写顶点着色器:

#version 300 es
layout (location = 0) in vec2 vPosition;
void main() {
   gl_Position = vec4(vPosition, 0.0f, 1.0f);
}

编写一个着色器程序,我们首先要声明 GLSL 语言的版本,这里是 300 es 指 OpenGL ES SL 3.0 版本。第二行的in 表示这是一个(顶点)输入变量,也叫顶点属性;location = 0 把这个属性的位置设为 0,在下面我们还将遇到它。数据的输入在下面我们也会介绍。

和 C 语言一样,main 函数是程序的入口。

vec4 的 GLSL 内置的数据类型,表示长度为 4 的向量。类似的,还有 vec2, vec3。顶点坐标由 4 维的向量表示,分别是 xyzw。因为我们只是在平面上画三角形,所以只在程序里设置了 xyz 坐标为 0;w 坐标成为其次坐标,在对物体进行平移的时候非常有用,这里我们暂时忽略,将它设置为 1。

gl_Position 是 GLSL 预定于的变量,用于输出顶点的坐标(我们只要赋值给它就可以了)。

接下来是片段着色器:

#version 300 es
precision mediump float;
out vec4 fragColor;
void main() {
    fragColor = vec4(0.64f, 0.77f, 0.22f, 1.0f);
}

precision mediump float 指定默认精度为 mediump,表示中等精度。类似的还有 lowp, highp,分别是低、高精度。对于片段着色器,float 必须指定精度;而顶点着色器如果没有指定精度,默认为 highp


由于详细地写太耗时间,我又没有把握写得比别人好。所以,从这里开始,不再详细描述所有过程,而是假定读者具有相关的背景知识。文章的目的是复习,而不是学习。

out vec4 fragColor 用于输出该片段的颜色。由于这里只需要一个颜色值,所以不需要指定 location

下面,我们编译着色器:

GLuint createShader(const char *sourceCode, GLenum shaderType) {
    auto shader = glCreateShader(shaderType);
    if (!shader) {
        checkGlError("glShaderSource");
        return 0;
    }
    glShaderSource(shader, 1, &sourceCode, nullptr);

    auto compiled = GL_FALSE;
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    if (!compiled) {
        LOGE(kTag, "fail to compile shader");
        GLint infoLen = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
        if (infoLen > 0) {
            GLchar *info = new GLchar[infoLen];
            glGetShaderInfoLog(shader, infoLen, nullptr, info);
            LOGE(kTag, "error msg = %s, source = %s", info, sourceCode);
            delete[] info;
            glDeleteShader(shader);
            shader = 0;
        }
    }
    return shader;
}

类似于 C 用于,编译后,还需要链接步奏,才是一个完整的程序:

GLuint createProgram(const char *vertexShaderSource, const char *fragmentShaderSource) {
    auto vertexShader = createShader(vertexShaderSource, GL_VERTEX_SHADER);
    if (!vertexShader) return 0;
    auto fragmentShader = createShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
    if (!fragmentShader) return 0;

    auto program = glCreateProgram();
    if (!program) {
        checkGlError("glCreateProgram");
        return 0;
    }

    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);
    auto compiled = GL_FALSE;
    glGetProgramiv(program, GL_LINK_STATUS, &compiled);
    if (!compiled) {
        LOGE(kTag, "fail to link program");
        GLint infoLen = 0;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
        if (infoLen > 0) {
            GLchar *info = new GLchar[infoLen];
            glGetProgramInfoLog(program, infoLen, nullptr, info);
            LOGE(kTag, "error msg = %s", info);
            delete[] info;
            glDeleteProgram(program);
            program = 0;
        }
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    return program;
}

这里值得注意的是,链接完成后,shader 对象就可以删除了。

下面我们开始画三角形。首先是顶点的数据,一共三个点:

const GLfloat kVertex[] = {
        -0.5f, -0.5f,   // bottom left
         0.5f, -0.5f,   // bottom right
         0.0f,  0.5f,   // top
};

接下来我们配置顶点着色器的输入:

    glGenVertexArrays(1, &mVAO);
    glGenBuffers(0, &mVBO);

    glBindVertexArray(mVAO);

    glBindBuffer(GL_ARRAY_BUFFER, mVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(kVertex), kVertex, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr);
    glEnableVertexAttribArray(0);

注意后两行的第一个参数 0。这里的 0 就对应上文我们提到的 location = 0glVertexAttribPointer 的第2个参数表示一共顶点的数据量,由于我们只指定了 x, y,所以这里是 2

最后,我们绘制三角形:

glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(mProgram);
glBindVertexArray(mVAO);
glDrawArrays(GL_TRIANGLES, 0, 3);

出来的效果是这样的:
OpenGL ES 3.0 学习 —— 绘制一个三角形