简单着色器编写(上)

在我的第一篇OpenGL文章中,我已经成功的画出了一个三角形,默认是白色的,那么该怎么把它换一个颜色呢?

先给出完整的代码。

#include 
#include 
#include

static unsigned int CompileShader(unsigned int type,const std::string& source)
{
    unsigned int id = glCreateShader(type);
    const char* src = source.c_str();
    glShaderSource(id, 1, &src, nullptr);
    glCompileShader(id);

    int result;
    glGetShaderiv(id, GL_COMPILE_STATUS, &result);
    if (result==GL_FALSE)
    {
        int length;
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
        char* message = (char*)alloca(length * sizeof(char));
        glGetShaderInfoLog(id, length, &length, message);
        std::cout << "Failed to compile"<<
            (type==GL_VERTEX_SHADER?"vertex":"fragment")
            << "shader!" << std::endl;
        std::cout << message << std::endl;
        glDeleteShader(id);
        return 0;
    }

    return id;
}

static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
    unsigned int program = glCreateProgram();
    unsigned int vs = CompileShader(GL_VERTEX_SHADER,vertexShader);
    unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);
    glValidateProgram(program);

    glDeleteShader(vs);
    glDeleteShader(fs);

    return program;
}

int main(void)
{
    GLFWwindow* window;

    /* Initialize the library */
    if (!glfwInit())
        return -1;

    glewInit();

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    if (glewInit() != GLEW_OK)
        std::cout << "error";

    std::cout << glGetString(GL_VERSION) << std::endl;

    float positions[6] =
    {
        -0.5f, -0.5f,
        0.0f, 0.5f,
        0.5f, -0.5f
    };

    unsigned int buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float),positions,GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    std::string vertexShader =
        "#version 330 core\n"
        "\n"
        "layout(location=0)in vec4 position;"
        "\n"
        "void main()\n"
        "{\n"
        "  gl_Position = position;\n"
        "}\n";

    std::string fragmentShader =
        "#version 330 core\n"
        "\n"
        "layout(location=0)out vec4 color;"
        "\n"
        "void main()\n"
        "{\n"
        "  color=vec4(1.0,0.0,0.0,1.0);\n"
        "}\n";
    unsigned int shader = CreateShader(vertexShader,fragmentShader);
    glUseProgram(shader);

    /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window))
    {
        /* Render here */
        glClear(GL_COLOR_BUFFER_BIT);

        //glBegin(GL_TRIANGLES);
        //glVertex2f(-0.5f, -0.5f);
        //glVertex2f(0.0f, 0.5f);
        //glVertex2f(0.5f, -0.5f);
        //glEnd();

        glDrawArrays(GL_TRIANGLES, 0,3);

        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }
    glDeleteProgram(shader);

    glfwTerminate();
    return 0;
}

不同于之前,这里我使用了新的方法来画三角形。

下面是新的三角形画法:

float positions[6] =
{
    -0.5f, -0.5f,
    0.0f, 0.5f,
    0.5f, -0.5f
};

unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float),positions,GL_STATIC_DRAW);

glEnableVertexAttribArray(0);

glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);

glBindBuffer(GL_ARRAY_BUFFER, 0);

仅靠这些还不能在屏幕上显示出一个三角形,还要继续添加OpenGL渲染的步骤,这些我们待会儿说。

我来逐步解释一下上面代码的作用;

float positions[6] =
{
    -0.5f, -0.5f,
    0.0f, 0.5f,
    0.5f, -0.5f
};

这段代码定义了一个包含三个顶点坐标的数组,每个顶点由两个浮点数表示(X 和 Y 坐标)。这些顶点坐标定义了一个位于屏幕中心的等腰三角形,顶点坐标如下:

  • 第一个顶点:(-0.5, -0.5)
  • 第二个顶点:(0.0, 0.5)
  • 第三个顶点:(0.5, -0.5)

这些坐标被用作顶点数据,可以通过OpenGL的顶点缓冲区对象(VBO)来传递给渲染管线,从而绘制这个三角形。这个代码片段是OpenGL中的基础步骤之一,用于准备顶点数据以进行渲染。

请注意,这里的坐标是以标准化设备坐标(Normalized Device Coordinates,NDC)表示的,范围从 -1 到 1。实际渲染时,这些坐标会经过变换和投影,最终映射到屏幕上的像素位置。

unsigned int buffer;

用于定义一个无符号整数变量 buffer,这个变量通常用来表示OpenGL中的缓冲区对象标识符(如顶点缓冲区对象、帧缓冲区对象等)。

 glGenBuffers(1, &buffer);

是OpenGL函数调用,用于生成缓冲区对象的标识符(ID)。让我解释一下这个函数的作用:

  • glGenBuffers:这是一个OpenGL函数,用于生成一个或多个缓冲区对象的标识符。在这里,我们生成一个缓冲区对象,所以是生成单个标识符。

  • (1, &buffer):这是函数的参数。1 表示生成一个标识符,&buffer 是用于接收生成的标识符的变量的地址。在这里,buffer 是我之前声明的无符号整数变量,用于存储生成的缓冲区对象的标识符。

通过调用这个函数,OpenGL会为我生成一个唯一的标识符,用于标识一个缓冲区对象。之后,你可以通过这个标识符来操作和管理这个缓冲区对象,比如绑定、填充数据等操作。

这个函数很长,不好记忆,那么我们把它拆开看看,

gl是OpenGL函数的前缀,这个是必须要加的,

“gen”是“generate”的缩写,表示生成,

“buffer”表示缓冲区对象,

因此,glGenBuffers 的意思可以理解为“生成缓冲区对象的标识符”。

glBindBuffer(GL_ARRAY_BUFFER, buffer);

是OpenGL中用于绑定缓冲区对象的函数调用。让我解释一下这段代码的含义:

  • glBindBuffer:这是一个OpenGL函数,用于将一个缓冲区对象绑定到OpenGL上下文中的指定缓冲区目标。

  • GL_ARRAY_BUFFER:这是一个缓冲区目标的常量,表示要绑定的缓冲区对象的类型。在这个情况下,它表示顶点缓冲区对象。

  • buffer:这是我之前生成的缓冲区对象的标识符。通过这个标识符,我可以引用特定的缓冲区对象。

通过调用 glBindBuffer(GL_ARRAY_BUFFER, buffer);,将指定的缓冲区对象绑定到OpenGL上下文中的当前顶点缓冲区目标。这意味着后续的顶点数据操作将应用于这个特定的缓冲区对象。

在绑定缓冲区之后,可以使用其他函数来填充数据、配置属性等操作。完成操作后,通常会使用 glBindBuffer(GL_ARRAY_BUFFER, 0); 解绑缓冲区,以确保后续的操作不会影响到这个缓冲区对象。

 同样,我来解释一下这个函数的命名方式,

"bind" 表示将一个对象绑定到OpenGL上下文的特定目标,

综合起来,当我们说 "bind a buffer" 时,意味着将一个缓冲区对象绑定到特定的缓冲区目标,以便后续的操作将作用于这个缓冲区对象。在操作缓冲区对象时,需要使用 bind 来确定正在操作的对象,然后使用 buffer 来描述这个对象的类型。

glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float),positions,GL_STATIC_DRAW);

是一个OpenGL函数,用于将数据上传到缓冲区对象中。让我解释一下这段代码的含义:

  • GL_ARRAY_BUFFER:这是一个缓冲区目标的常量,表示要绑定的缓冲区对象的类型。在这个情况下,它表示顶点缓冲区对象。

  • 6 * sizeof(float):这个参数表示要上传的数据的大小,以字节为单位。在这里,你上传了包含6个浮点数的数据数组,每个浮点数占用4个字节(sizeof(float))。

  • positions:这是一个指向包含要上传数据的数组的指针,即顶点的坐标数据。

  • GL_STATIC_DRAW:这是一个提示,告诉OpenGL你打算如何使用这些数据。在这个情况下,GL_STATIC_DRAW 表示这些数据将不会在绘制过程中被频繁修改,因此OpenGL可以根据需要对内部数据进行优化。

通过调用 glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float), positions, GL_STATIC_DRAW);,将顶点数据上传到绑定到 GL_ARRAY_BUFFER 目标的缓冲区对象中。这样,OpenGL就可以在渲染过程中使用这些数据来绘制图形。

需要注意的是,这只是上传数据到缓冲区对象中的第一步。接下来,可能需要在顶点着色器中设置属性指针,以告诉OpenGL如何解释这些数据,从而正确地渲染图形。

这个函数使用起来比较复杂,我来介绍一下它的函数签名,

void glBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);

这里是参数的解释:

  • target:缓冲区的目标,表示将数据上传到哪种类型的缓冲区对象。常见的值包括 GL_ARRAY_BUFFER(顶点缓冲区)、GL_ELEMENT_ARRAY_BUFFER(索引缓冲区)等。

  • size:要上传的数据大小,以字节为单位。

  • data:指向要上传的数据的指针。

  • usage:数据使用提示,告诉OpenGL如何使用这些数据,以便优化内存和性能。常见的值包括 GL_STATIC_DRAWGL_DYNAMIC_DRAW 等。

通过调用 glBufferData,你可以将数据上传到指定的缓冲区对象中,以便在渲染过程中使用。

glEnableVertexAttribArray(0);

是一个OpenGL函数,用于启用指定的顶点属性数组。让我解释一下这段代码的含义:

  • 0:这是一个顶点属性的索引,表示要启用的顶点属性数组。在这里,索引为0表示启用顶点位置属性。

通过调用 glEnableVertexAttribArray(0);,你告诉OpenGL要启用顶点位置属性数组,以便在渲染时使用。这是因为在使用顶点缓冲区对象绘制时,你可以将多个顶点属性存储在不同的顶点属性数组中,然后使用 glEnableVertexAttribArray 来启用这些属性数组。

在顶点着色器中,你需要通过指定的属性索引(在这里是0)来访问启用的顶点属性。通过启用和设置多个顶点属性,你可以在渲染过程中使用多个属性来定义顶点的属性,比如位置、法线、颜色、纹理坐标等。

这么写可能不太好理解,让我们用画画来类比,

想象你正在绘制一个彩色的风景画,你有一组颜料盒,每个盒子里装着不同颜色的颜料。你还有几个画笔,每支画笔可以绘制不同的部分。

  • 在OpenGL中,你的画布是屏幕,你的风景是3D模型。
  • 顶点属性数组就像是你的颜料盒,每个属性数组存储不同的顶点属性,比如位置、颜色、纹理坐标等。
  • 每支画笔就是你的顶点着色器,它决定了如何绘制每个顶点。
  • glEnableVertexAttribArray(0); 就相当于告诉你的画笔,你要使用颜料盒中的第一种颜色(索引0),也就是启用位置属性数组。

因此,这句话就是在告诉OpenGL的画笔(顶点着色器):“嘿,我要用位置属性这个颜料盒(属性数组)中的颜色(数据)来绘制我的风景(模型)!” 这样,OpenGL就知道在绘制过程中如何使用顶点属性数据来渲染图形了。

这个函数也比较难以理解,拆开来解释下,

  • gl:OpenGL 库的前缀,表示这个函数是属于OpenGL库的一部分。
  • Enable:启用的意思,表示你要激活或开启某个功能。
  • Vertex:顶点的意思,通常用于表示3D图形中的一个点。
  • AttribArray:属性数组,指的是一组包含顶点属性数据的数组。在OpenGL中,你可以在属性数组中存储顶点的各种属性,如位置、颜色、法线、纹理坐标等。

综合起来,glEnableVertexAttribArray 表示你要启用或开启一个顶点属性数组,以便在OpenGL渲染中使用这些属性数据。这个函数的调用告诉OpenGL,你想要激活某个顶点属性数组,从而在绘制过程中使用这些属性数据来定义模型的特征。

glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);

让我来解释一下 glVertexAttribPointer 函数的参数:

  • 0:这是顶点属性的索引,表示要设置的顶点属性数组的索引。在这里,索引0表示设置顶点位置属性数组。

  • 2:这是指定每个顶点属性的组件数量。在这里,表示每个顶点位置有两个组件,即 x 和 y 坐标。

  • GL_FLOAT:这是指定顶点属性的数据类型。在这里,表示顶点位置属性的数据类型是浮点数。

  • GL_FALSE:这是一个布尔值,用于指定是否应该将顶点属性的值标准化。在这里,表示不标准化顶点位置属性的值。

  • sizeof(float) * 2:这是指定顶点属性数组中每个顶点属性的字节数。在这里,表示每个顶点位置属性由两个浮点数(每个浮点数占4字节)组成,所以是 sizeof(float) * 2 字节。

  • 0:这是指定顶点属性数组中第一个顶点属性的偏移量。在这里,表示从数组的开头开始。

通过调用 glVertexAttribPointer 函数,你告诉OpenGL如何解释顶点属性数组中的数据。在这个例子中,它告诉OpenGL如何解释顶点位置属性的数据:每个顶点位置由两个浮点数组成,不标准化,每个顶点属性在数组中的偏移量为0。这些设置将在渲染时被顶点着色器使用,以正确地处理顶点位置属性。

把单词拆开来看,

  • gl:OpenGL 库的前缀,表示这个函数是属于OpenGL库的一部分。
  • Vertex:顶点的意思,通常用于表示3D图形中的一个点。
  • Attrib:属性的缩写,表示顶点的一个特征或属性,如位置、颜色、法线等。
  • Pointer:指针的意思,表示指向数据的指针。在这里,它指的是告诉OpenGL如何访问顶点属性数据的指针。

glBindBuffer(GL_ARRAY_BUFFER, 0);

是一个OpenGL函数调用,让我为你解释一下这个函数的作用:

  • gl:OpenGL 库的前缀,表示这个函数是属于OpenGL库的一部分。
  • Bind:绑定的意思,表示把一个对象或资源绑定到OpenGL上下文中,以便在后续的操作中使用。
  • Buffer:缓冲区的意思,表示一个用于存储数据的内存块,通常用于存储顶点数据、纹理数据等。
  • GL_ARRAY_BUFFER:这是OpenGL中的一个常量,表示绑定目标为顶点属性数组的缓冲区。
  • 0:这是一个特殊的值,表示要解绑目标缓冲区。

综合起来,glBindBuffer(GL_ARRAY_BUFFER, 0); 表示你正在将顶点属性数组的缓冲区解绑定,即将之前绑定的缓冲区与顶点属性数组分离。在OpenGL中,通过绑定和解绑缓冲区,你可以在渲染过程中使用不同的缓冲区数据来绘制不同的图形。将目标缓冲区设置为0,表示将当前绑定的缓冲区解绑定,以后的操作将不再影响该缓冲区。

所以,这句代码的作用是取消之前与顶点属性数组绑定的缓冲区,以便后续的操作不再受该缓冲区的影响。

你可能感兴趣的:(着色器,c++,OpenGL)