四、OpenGL 简单开始 --- 顶点着色器(shader)

上一篇:三、OpenGL 简单开始 --- 简单三角形绘制(vertex buffer & shader pre)

1. 什么是着色器?

着色器(Shader)是用来实现 图像渲染 的,用来替代固定渲染管线的可编辑程序。 其中如 Vertex Shader(顶点着色器)主要负责顶点的几何关系等的运算,Pixel Shader(像素着色器)主要负责片元颜色等的计算。简单讲就是:一个程序,负责告诉 GPU 我的这些数据怎么用,如如何进行计算位置、光照、阴影等渲染工作。

2. 两个着色器的工作

两个渲染管线中的基本着色器(还有一些扩展的,如曲面细分着色器、几何着色器):

  • Vertex Shader(顶点着色器)主要负责顶点的几何关系等的运算。
  • Pixel Shader(像素着色器)主要负责片元颜色等的计算。

2.1 两者关系

顶点着色器 处理后的数据为 顶点数据(带有其多个属性:位置、法线、颜色......),交给 光栅化 找出顶点围成三角形内部的 像素(或者说 片元),然后把这些像素信息交给 像素着色器,其负责给这些像素进行着色计算

2.2 两者区别

  • 顶点着色器每个顶点进行计算,如我们提供了三个点,则调用三次来计算顶点的属性;
  • 像素着色器每个像素(片元) 计算,三个点围成的三角形内可能有很多个像素(成百上千),每个都要调用,这里也可以看出花销很大。

3. 代码参考

GL 的 API 参考地址
主要学习:

  • 与着色器绑定的程序对象
    • glCreateProgramglAttachShaderglLinkProgramglValidateProgramglDeleteShaderglUseProgramglDeleteProgram
  • 着色器相关
    • glCreateShaderglShaderSourceglCompileShader
  • 着色器错误处理
    • glGetShaderivglGetShaderInfoLog
  • 画出三角形
    • glDrawArrays

3.1 生成着色器函数部分

/* ------------------------- 着色器 ------------------------- (static 限定在文件)*/

/* ~~~~~~ 编译着色器 ~~~~~~ */
// 返回值:返回一个编号值用于绑定
// 参数值:type 是着色器类型,string 接一下源代码
static unsigned int CompileShader(unsigned int type, const string& source)
{
    // 1. Creates a shader object (指定着色器类型:vertex shader)
    unsigned int id = glCreateShader(type);
    // 不可变的字符数组指针
    const char* src = source.c_str();
    // 2. 着色器源代码替换
    glShaderSource(
        id,     // 指定着色器
        1,      // 指定字符串数组和长度数组中元素的个数。
        &src,   // 指定指向字符串的指针数组,字符串中包含要加载到着色器中的源代码。
        // 指定字符串长度的数组
        // 如果 length 为 NULL,则每个字符串都被假定为空值结束。
        // 如果 length 值不是 NULL,它将指向一个数组,其中包含字符串中每个相应元素的字符串长度。
        nullptr 
    );
    // 3. 编译 正式生成 新 Shader
    glCompileShader(id);

    // 4. 错误处理
    int result;
    glGetShaderiv(id, GL_COMPILE_STATUS, &result); // 获取错误状态
    if (result == GL_FALSE) 
    {
        int length;
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length); // 获取错误信息长度
        char* message = (char*)malloc(length * sizeof(char));  // 申请同大小堆空间,存放错误信息
        glGetShaderInfoLog(id, length, &length, message); // 根据长度,空间,程序提取出 错误信息
        // 输出
        cout << "编译"<< 
            (type == GL_VERTEX_SHADER ? "顶点" : "片元") 
            << "着色器失败!" << endl;
        cout << message << endl;
        return 0;
    }

    // 返回着色器编号
    return id;
}
/* ~~~~~~ 编译着色器 ~~~~~~ */


/* ~~~~~~ 创建着色器 ~~~~~~ */
// 返回值:返回一个编号值用于绑定
// 参数值:这里用 string 接一下源代码,也可用 二进制文件
static int CreateShader(const string& vertextShader, const string& fragmentShader)
{
    // 1. Creates a program object (程序对象是一个可以附加着色器对象的对象)
    unsigned int program = glCreateProgram();
    // 2. 编译着色器 (指定着色器类型,源代码)
    unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertextShader);
    unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

    // 3. 附加着色器对象
    glAttachShader(program, vs);
    glAttachShader(program, fs);
    // 4. 链接程序对象(被用于创建在之前附加的着色器上运行的可执行文件)
    glLinkProgram(program);
    // 5. 检查程序中包含的可执行文件是否能在当前 OpenGL 状态下执行。
    glValidateProgram(program);

    // 6. 绑定生成后,删除之前临时的文件 (不用 detach 原因:留下的源代码可执行文件占用空间少,而且经常用)
    glDeleteShader(vs);
    glDeleteShader(fs);

    // 返回程序对象
    return program;
}
/* ~~~~~~ 创建着色器 ~~~~~~ */

/* ------------------------- 着色器 ------------------------- */

3.2 着色器使用部分

int main(void)
{
    ..................
    ..................
    /* ------------------------- Draw A Triangle ------------------------- */
    // !!!!!!!!!!! 数据  !!!!!!!!!!!
    float posintions[6] = {
        -0.5f, -0.5f,
        0.0f, 0.5f,
        0.5f, -0.5f
    };
    // !!!!!!!!!!!数据 !!!!!!!!!!!


    // !!!!!!!!!!! 缓冲区 !!!!!!!!!!!
    ..................
    // !!!!!!!!!!! 缓冲区 !!!!!!!!!!!


    // !!!!!!!!!!! 顶点属性布局  !!!!!!!!!!!
    ..................
    // !!!!!!!!!!! 顶点属性布局  !!!!!!!!!!!


    // !!!!!!!!!!! 着色器  !!!!!!!!!!!
    // 源代码
    string vertexShader =
        "#version 330 core\n"
        "\n"
        "layout(location = 0) in vec4 position;\n"  // 顶点属性索引 0, 存放在四维数组位置处(跟齐次坐标有关)
        "\n"
        "void main()\n"
        "{\n"
        "   gl_Position = position;\n"
        "}\n";
    string fragmentShader =
        "#version 330 core\n"
        "\n"
        "layout(location = 0) out vec4 color;\n"
        "\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);
    // !!!!!!!!!!! 着色器  !!!!!!!!!!!

    /* ------------------------- Draw A Triangle ------------------------- */

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

        // 画一下
        glDrawArrays(GL_TRIANGLES, 0, 3);
        ..................
    }
    // 清除着色器程序对象
    glDeleteProgram(shader);

    glfwTerminate();
    return 0;
}

结果如下:

image

片元着色器的源代码内故意忘写“;”, 测试错误处理结果:
image

4. 包装(文件形式读取源代码)

4.1 创建新文件夹与新文件。

image

4.2 文件内书写之前的着色器源代码 (修改了一下颜色:蓝色);用 #shader vertex 标识具体属于哪个着色器(顶点 or 像素)。

#shader vertex
#version 330 core

layout(location = 0) in vec4 position;

void main()
{
   gl_Position = position;
};

#shader fragment
#version 330 core

layout(location = 0) out vec4 color;

void main()
{
   color = vec4(0.2, 0.3, 0.8, 1);
};

4.3 代码修改

4.3.1 新建存放两个着色器字符串源码的结构体

// 程序源码(同时返回多个参数的载体)
struct ShaderProgramSource
{
    string VertexSource;
    string FragmentSource;
};

4.3.2 新增函数文件内提取源码

记得包含头文件 #include <fstream>#include <string>#include <sstream>

// 提取源码(多个字符串返回)
static ShaderProgramSource ParseShader(const string& filePath)
{
    // 着色器枚举类
    enum class ShaderType
    {
        NONE = -1, VERTEX = 0, FRAGMENT = 1
    };
    // 默认类型定义
    ShaderType type = ShaderType::NONE;

    // 文件流输入流
    ifstream stream(filePath);
    // 字符串输入输出流 (字符串数组 大小为 2)
    stringstream ss[2];

    // 按行获取文件内容
    string line;
    while (getline(stream, line))
    {
        // 获取着色器类型
        if (line.find("#shader") != string::npos)
        {
            if (line.find("vertex") != string::npos)
                type = ShaderType::VERTEX;
            else if (line.find("fragment") != string::npos)
                type = ShaderType::FRAGMENT;
        }
        else
        {
            // 写入字符串数组中
            ss[int(type)] << line << '\n';
        }
    }

    return { ss[0].str(), ss[1].str() };
}
/* ~~~~~~ 提取着色器源代码 ~~~~~~ */

4.3.3 主函数体修改获取源代码方式

......
// !!!!!!!!!!! 着色器  !!!!!!!!!!!
// 源代码
ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");
// 创建
unsigned int shader = CreateShader(source.VertexSource, source.FragmentSource);
// 使用
glUseProgram(shader);
// !!!!!!!!!!! 着色器  !!!!!!!!!!!
......

4.4 结果图

image

下一篇:五、OpenGL 简单开始 --- 索引缓冲区

posted @ 2024-05-09 15:23  bok_tech  阅读(576)  评论(0)    收藏  举报