跳转至

03 Hi, Shader

如果你没有给OpenGL提供自己的着色器,一些显卡驱动实际上会为你提供一个默认的着色器。因此,上面的代码已经能绘制出一个三角形了。

着色器是一个运行在显卡上的程序,它可以在显卡上以一种非常特殊又非常强大的方式运行。

  • Shader很容易让人联想到光源,觉得和着色有关,但它只是在显卡上运行的一个程序而已,和实际的图形没有任何关系

我们可以在计算机上以文本或字符串的形式编写着色器的代码,然后通过OpenGL的API把它们发送到显卡上,它能够在显卡上编译、链接和运行。是的,它运行在GPU上,而不像C++程序运行在CPU上。

顶点着色器

  • 它会为我们试图渲染的每个顶点调用,主要目的是告诉OpenGL,这个顶点在屏幕空间的什么位置
  • 例如当前的例子,我们有3个顶点,因此会为3个顶点分别调用一次顶点着色器

片段着色器

  • 为每个需要光栅化的像素运行一次,决定每个像素的颜色
  • 例如当前的例子,片段着色器可能会被调用数万次,这取决于此三角形在屏幕上占据了多少空间

版本一:字符串形式的着色器

//编译着色器
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);   //指定id着色器的源码
        //传入nullptr,表示以src是以'\0'结束。如果只想传入source中的某一段,可以指定长度
    glCompileShader(id);                    //编译着色器

    //获取编译状态
    int result;
    glGetShaderiv(id, GL_COMPILE_STATUS, &result);
    if (result == GL_FALSE) {
        int length;
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);     // 获取日志长度

        char* msg = (char*)_malloca(length * sizeof(char)); //在栈上创建
        glGetShaderInfoLog(id, length, &length, msg);       //获取日志信息

        std::cout << "Failed to compile " << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader!" << std::endl;
        std::cout << msg << std::endl;

        glDeleteShader(id); // 删除着色器
        return 0;
    }

    return id;
}

//创建着色器
static 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); //验证是否有效

    //此时可以删除了,因为它们已经被链接到program当中了
    //和C++程序的obj中间文件一样,vs、fs就可以被删除了
    //但其实为了方便调试,这个无需删除,因为它们的开销非常小
    glDeleteShader(vs);
    glDeleteShader(fs);

    return program;
}

int main(void)
{
    //...
    std::string vertexShader =
        "#version 330 core\n"
        "\n"
        "layout(location = 0)in vec4 position;"
            //location == glVertexAttribPointer的第一个参数
            //在上面代码中,顶点着色器的位置传的是vec2,因此用vec2 position更准确。但如果用vec2,下面则需要改成gl_Position=vec4(position.xy, 0.0, 1.0);
            //但这里可以写vec4,OpenGL会自动帮我们补齐,后面gl_Position就方便了
        "\n"
        "void main()\n"
        "{\n"
        " gl_Position = position;\n"    //gl_Postion是一个vec4
        "}\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); /* 使用着色器程序 */

    while (!glfwWindowShouldClose(window)) {
        //...
    }

    glDeleteProgram(shader); //删除着色器程序
    //...
}