OpenGL Chan视频学习-11 Uniforms in OpenGL

bilibili视频链接:

【最好的OpenGL教程之一】https://www.bilibili.com/video/BV1MJ411u7Bc?p=5\&vd_source=44b77bde056381262ee55e448b9b1973

函数网站:

docs.gl

说明:

1.之后就不再单独整理网站具体函数了,网站直接翻译会更直观也会有更多注意点。直接通过csdn索引查找反而会慢。

2.代码区域会单独注释功能参数返回值和相关注意事项。

3.课程学习从4-本节,如果有些函数没有注释可以看专栏里面的前面发表的文章,一般有解释。

4.如果觉得代码注释白色字体不太直观可以直接copy到相应软件看

5.有两种版本的可供查看:注释全面的和注释简洁版的,可以在索引里面找到相关代码查看。

6.希望能帮到你。

7.有错误请跟我说明一下,可能整理的时候没有检查好。

一、知识点整理

1.1获取数据方式

1.1.1分类

  • 属性
  • Uniforms

1.1.2区别

以下是两种从CPU到GPU获取数据的方式(属性、uniform)之间的联系和区别的表格形式总结:

(来自文心一言)

特性 属性(Attribute) Uniform
定义 用于向顶点着色器传递每个顶点的独立数据,如位置、颜色、纹理坐标等。 用于向所有着色器阶段(顶点、片段等)传递全局一致的数据,如变换矩阵、光照参数等。
作用范围 仅作用于顶点着色器,每个顶点有独立的一份数据。 可作用于顶点着色器和片段着色器,所有顶点或片段共享同一份数据。
数据更新频率 通常在每次绘制调用(Draw Call)时更新,每个顶点可以有不同的值。 在渲染一帧或多次绘制调用期间保持不变,适合频繁使用但较少变化的数据。
性能影响 数据量随顶点数量增加而增加,频繁更新大量顶点属性可能影响性能。 数据量较小,更新频率低,性能开销相对较小。
使用场景 传递需要逐顶点变化的数据,如模型顶点坐标、顶点颜色、法线等。 传递需要全局共享的数据,如模型视图投影矩阵、光照参数、材质属性等。
API示例(OpenGL) 使用glVertexAttribPointer绑定顶点属性缓冲区,通过glVertexAttrib系列函数启用或禁用属性。 使用glGetUniformLocation获取uniform变量的位置,通过glUniform系列函数设置uniform变量的值。
缓冲区类型 通常存储在顶点缓冲区对象(VBO)中。 通常直接存储在着色器程序中,或通过uniform缓冲区对象(UBO)或着色器存储缓冲区对象(SSBO)管理。
灵活性 灵活性高,适合处理每个顶点不同的数据。 灵活性较低,但适合处理全局一致的数据,且可以通过UBO/SSBO提高组织效率。
内存占用 内存占用随顶点数量线性增长。 内存占用固定,与顶点数量无关。
适用数据类型 适合存储每个顶点独立的数据,如向量、标量等。 适合存储全局一致的矩阵、向量、标量等数据。

联系

  1. 数据传递:两者都是从CPU向GPU传递数据的方式,用于着色器程序的运行。
  2. 着色器访问:两者都可以在着色器中被访问,但作用范围和生命周期不同。
  3. 优化目标:两者都旨在提高渲染性能,通过合理使用可以减少CPU-GPU之间的数据传输开销。

区别

  1. 作用范围:属性是逐顶点的,uniform是全局的。
  2. 数据更新频率:属性可能频繁更新,uniform通常较少更新。
  3. 性能开销:属性可能因数据量大而影响性能,uniform性能开销较小。
  4. 使用场景:属性适合逐顶点数据,uniform适合全局共享数据。

通过合理选择属性或uniform,可以优化渲染性能,减少不必要的CPU-GPU数据传输。

1.2Uniform

1.2.1是什么

从CPU获取数据的方式,希望能在CPU端口定义数据。在这里,从C++到我们的着色器,可以当成

也有可能通过顶点缓冲区从CPU获取数据到GPU。

1.2.2调用时机

每一次绘制时调用。在调用glDrawElements或glDrawArray或任何用来绘图的东西之前设置。

1.2.3注意点

  • 在绘制之前就已经设置好了,每次绘制时设置差异很大

1.2.4应用

1.2.4.1代码+步骤

进入着色器文件Basic.shader

复制代码
#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;
        
//分配矩形的每个像素的每个片段的输出颜色为该统一值
uniform vec4 u_Color;

void main()
{
    color = vec4 u_Color;
};

从Application.cpp中设置uniform

在着色器绑定之后调用glUseProgram

区别在我们实际发送的数据和我们有多少组件

cpp 复制代码
 //功能:获取着色器程序中 "u_Color" 的 uniform 变量的位置
 //参数:1.shader: 着色器程序对象
 //参数:2.name: 变量名
 //返回; 如果 uniform 变量不存在,则返回 -1;存在,则返回它的位置,即它的索引。
 GLCall(int location = glGetUniformLocation(shader, "u_Color"));
 //着色器程序(vertex 或 fragment)中声明了某个 uniform 变量(例如 "u_Color"),
 // 但是在实际的 GLSL 代码中没有使用这个变量,编译器可能会优化掉这个变量,
 // 从而导致 glGetUniformLocation 返回 -1。
 //检查返回值是否为 -1,避免在后续对一个不存在的 uniform 变量操作。
 ASSERT(location != -1);
 //功能:设置颜色
 // 参数:1.location: 着色器程序中 uniform 变量的位置
 // 参数:2.v0: 第一个分量
 // 参数:3.v1: 第二个分量
 // 参数:4.v2: 第三个分量
 // 参数:5.v3: 第四个分量
 // 作用:设置 uniform 变量的值,这里设置了 u_Color 的值为红色(0.2, 0.3, 0.8, 1.0)
 // GL_FLOAT_VEC4 表示该 uniform 变量是一个四维向量,每个分量的类型为 float。
 // 注意:这里的颜色值是通过 glGetUniformLocation 函数获取的,而不是直接写死的。
 // 这样做的好处是,当着色器程序中的 uniform 变量的名称发生变化时,
 // 我们只需要修改这里的名称,而不需要修改渲染代码。
 GLCall(glUniform4f(location, 0.8f, 0.3f, 0.8f, 1.0f));
1.2.4.2运行结果

1.3变色动画

1.3.1代码+步骤(int main函数修改)

修改刷新频率

cpp 复制代码
//设置刷新频率
//参数:1表示程序会等待一个垂直重绘周期之后再进行缓冲区交换。大概可以理解为每秒60帧。
// 如果显示器的刷新率不是 60 Hz,而是其他值(例如 75 Hz 或 120 Hz),
// glfwSwapInterval(1); 会根据显示器的实际刷新率来调整程序的帧率。
// glfwSwapInterval 函数通常只接受 0、1 或 -1 作为参数,分别代表无同步、垂直同步以及可自适应同步。
glfwSwapInterval(1);

设置r值使其能动态调整颜色

设置increment实现阶段等间隔变化

cpp 复制代码
    float r = 0.0f;
    float increment = 0.05f;

通过在while循环里直接设置颜色和颜色浮点值来改变

cpp 复制代码
 GLCall(glUniform4f(location, r, 0.3f, 0.8f, 1.0f))
 //功能:绘制三角形
 GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));

 //更新颜色
 //实现颜色的循环变化
 if(r > 1.0f)
     increment = -0.05f;
 else if(r < 0.0f)
     increment = 0.05f;

 r += increment;

1.3.2运行效果

蓝色粉色渐变循环

二、完整代码

2.1 将shader数据从c++传入

2.1.1 完全注释代码

Basic.shader

cpp 复制代码
#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;
        
//分配矩形的每个像素的每个片段的输出颜色为该统一值
uniform vec4 u_Color;

void main()
{
    color = u_Color;
};

Application.cpp

cpp 复制代码
#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include<iostream>
#include<fstream>
#include<string>
#include<sstream>

//功能:定义宏,用于在调试过程中进行条件断言,检测OpenGL错误
#define ASSERT(x) if(!(x)) __debugbreak();
//功能:定义了一个宏 GLCall(x),用于调试 OpenGL 应用程序时检查和处理 OpenGL 错误。
#define GLCall(x) GLClearError();\
    x;\
    ASSERT(GLLogError(#x,__FILE__,__LINE__));

static void GLClearError()
{
    //功能:检测OpenGL错误
    while (glGetError() != GL_NO_ERROR);
}

//参数:1.function: 发生错误的函数名
static bool GLLogError(const char* function, const char* file, int line)
{
    //功能:检测OpenGL错误,输出错误信息
    //while循环,直到glGetError()返回GL_NO_ERROR,表示没有错误发生。
    while (GLenum error = glGetError())
    {
        std::cout << "[OpenGL Error] (" << error << "): " << function << 
            " " << file << ":" << line << std::endl;
        return false;
    }
    return true;
}


//功能:定义ShaderProgramSource结构体,用于存储着色器代码
struct ShaderProgramSource
{
    std::string VertexSource;
    std::string FragmentSource;
};


//功能:解析着色器代码文件。
static ShaderProgramSource ParseShader(const std::string& filepath)
{
    //功能:打开文件流
    std::ifstream stream(filepath);

    //定义着色器类型
    enum  class ShaderType
    {
        NONE=-1,VERTEX=0,FRAGMENT=1
    };

    //该变量用于存储着色器代码
    std::string line;
    //该变量用于存储着色器类型
    std::stringstream ss[2];
    //该变量是当前着色器类型
    ShaderType type = ShaderType::NONE;
    //功能:读取文件中的每一行内容,直到文件结束
    while (getline(stream, line))
    {
        //如果当前行包含#shader,则说明接下来是着色器代码
        if (line.find("#shader") != std::string::npos)
        {
            //如果当前行包含vertex,则说明接下来是顶点着色器代码
            if (line.find("vertex") != std::string::npos)
            {
                type = ShaderType::VERTEX;
            }
            else if (line.find("fragment") != std::string::npos)
            {
                type = ShaderType::FRAGMENT;
            }
        }
        else
        {
            //否则,将当前行添加到对应着色器代码的stringstream中
            ss[(int)type] << line << '\n';
        }
    }
    //返回ShaderProgramSource结构体
    return { ss[0].str(), ss[1].str() };
}


//功能:编译着色器代码
static unsigned int CompilesShader(unsigned int type, const std::string& source)
{
    //功能:创建着色器对象
    unsigned int id = glCreateShader(type);
    //功能:设置着色器源代码.
    const char* src = source.c_str();
    //功能:替换着色器对象中的源代码。将该id的指定着色器的源代码设置为src指针指向的字符串
    glShaderSource(id, 1, &src, nullptr);
    //功能:编译着色器对象的源代码
    glCompileShader(id);

    //设置返回着色器的对象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 shader!" << (type == GL_VERTEX_SHADER? "Vertex" : "Fragment") << "shader!" << std::endl;
        std::cout << message << std::endl;
        //删除着色器对象
        glDeleteShader(id);
        return 0;
    }

    //TODO:错误处理ing
    return id;
}


//功能:创建着色器程序
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
    //创建程序对象
    unsigned int program = glCreateProgram();
    //编译顶点着色器对象
    unsigned int vs = CompilesShader(GL_VERTEX_SHADER, vertexShader);
    //编译片段着色器对象
    unsigned int fs = CompilesShader(GL_FRAGMENT_SHADER, fragmentShader);

    //功能:将编译好的着色器对象附加到程序对象中
    glAttachShader(program, vs);
    glAttachShader(program, fs);

    //功能:链接程序对象
    glLinkProgram(program);

    //功能:验证着色器程序对象是否可以在当前OpenGL状态中执行。检查着色器程序的完整性和可执行性。
    glValidateProgram(program);

    //删除着色器对象,因为它们已经被链接到程序对象中
    glDeleteShader(vs);
    glDeleteShader(fs);

    //返回着色器程序
    return program;
}


int main(void)
{
    GLFWwindow* window;

    //初始化glfw
    if (!glfwInit())
        return -1;

    //创建一个窗口模式的窗口并设置OpenGL上下文
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)//如果窗口创建失败,则终止程序
    {
        glfwTerminate();//释放glfw资源
        return -1;
    }

    //设置当前窗口的上下文,之后所有的OpenGL调用都会在这个上下文中进行
    glfwMakeContextCurrent(window);

    //初始化GLEW
    if (glewInit() != GLEW_OK)
        std::cout << "Error!" << std::endl;

    //打印OpenGL版本信息
    std::cout << glGetString(GL_VERSION) << std::endl;

    //准备数据
    float position[] = {
        -0.5f, -0.5f,
        0.5f, -0.5f,
        0.5f,0.5f,
        -0.5f,0.5f,
    };

    //定义顶点索引缓存,用于标定顶点顺序
    unsigned int indices[] = {
        0,1,2,
        2,3,0
    };

    
    //定义缓冲区对象
    unsigned int buffer;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    GLCall(glGenBuffers(1, &buffer));
    //功能:将缓冲区对象绑定到目标
    GLCall(glBindBuffer(GL_ARRAY_BUFFER, buffer));
    //功能:将数据写入缓冲区
    GLCall(glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW));

    //功能:配置顶点属性指针
    GLCall(glEnableVertexAttribArray(0));
    //功能:指定顶点属性数组的索引、大小、数据类型、是否归一化、偏移量、数据指针
    GLCall(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0));
    

    //索引缓冲区对象
    unsigned int ibo;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    GLCall(glGenBuffers(1, &ibo));
    //功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区
    GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo));
    //功能:将数据写入缓冲区
    GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW));


    //解析着色器代码文件
    ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");
    std::string vertexShader = source.VertexSource;
    std::string fragmentShader = source.FragmentSource;
    //创建着色器程序
    unsigned int shader = CreateShader(vertexShader, fragmentShader);
    //使用着色器程序
    GLCall(glUseProgram(shader));

    //功能:获取着色器程序中 "u_Color" 的 uniform 变量的位置
    //参数:1.shader: 着色器程序对象
    //参数:2.name: 变量名
    //返回; 如果 uniform 变量不存在,则返回 -1;存在,则返回它的位置,即它的索引。
    GLCall(int location = glGetUniformLocation(shader, "u_Color"));
    //着色器程序(vertex 或 fragment)中声明了某个 uniform 变量(例如 "u_Color"),
    // 但是在实际的 GLSL 代码中没有使用这个变量,编译器可能会优化掉这个变量,
    // 从而导致 glGetUniformLocation 返回 -1。
    //检查返回值是否为 -1,避免在后续对一个不存在的 uniform 变量操作。
    ASSERT(location != -1);
    //功能:设置颜色
    // 参数:1.location: 着色器程序中 uniform 变量的位置
    // 参数:2.v0: 第一个分量
    // 参数:3.v1: 第二个分量
    // 参数:4.v2: 第三个分量
    // 参数:5.v3: 第四个分量
    // 作用:设置 uniform 变量的值,这里设置了 u_Color 的值为红色(0.2, 0.3, 0.8, 1.0)
    // GL_FLOAT_VEC4 表示该 uniform 变量是一个四维向量,每个分量的类型为 float。
    // 注意:这里的颜色值是通过 glGetUniformLocation 函数获取的,而不是直接写死的。
    // 这样做的好处是,当着色器程序中的 uniform 变量的名称发生变化时,
    // 我们只需要修改这里的名称,而不需要修改渲染代码。
    GLCall(glUniform4f(location, 0.8f, 0.3f, 0.8f, 1.0f));




    //渲染循环,直到窗口被关闭
    while (!glfwWindowShouldClose(window))
    {
        //清除颜色缓冲区
        GLCall(glClear(GL_COLOR_BUFFER_BIT));
        
        //功能:绘制三角形
        GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));

        //刷新缓冲区并交换窗口
        GLCall(glfwSwapBuffers(window));

        //处理窗口事件,如键盘输入、鼠标移动等
        GLCall(glfwPollEvents());
    }

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

    //释放 GLFW 库占用的所有资源。
    glfwTerminate();
    return 0;
}

2.1.2简洁注释代码

shader同上,略

Application.cpp

cpp 复制代码
#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include<iostream>
#include<fstream>
#include<string>
#include<sstream>

//功能:定义宏,用于在调试过程中进行条件断言,检测OpenGL错误
#define ASSERT(x) if(!(x)) __debugbreak();
//功能:定义了一个宏 GLCall(x),用于调试 OpenGL 应用程序时检查和处理 OpenGL 错误。
#define GLCall(x) GLClearError();\
    x;\
    ASSERT(GLLogError(#x,__FILE__,__LINE__));

static void GLClearError()
{
    //功能:检测OpenGL错误
    while (glGetError() != GL_NO_ERROR);
}

//参数:1.function: 发生错误的函数名
static bool GLLogError(const char* function, const char* file, int line)
{
    //功能:检测OpenGL错误,输出错误信息
    //while循环,直到glGetError()返回GL_NO_ERROR,表示没有错误发生。
    while (GLenum error = glGetError())
    {
        std::cout << "[OpenGL Error] (" << error << "): " << function << 
            " " << file << ":" << line << std::endl;
        return false;
    }
    return true;
}


//功能:定义ShaderProgramSource结构体,用于存储着色器代码
struct ShaderProgramSource
{
    std::string VertexSource;
    std::string FragmentSource;
};


//功能:解析着色器代码文件。
static ShaderProgramSource ParseShader(const std::string& filepath)
{
    //功能:打开文件流
    std::ifstream stream(filepath);

    //定义着色器类型
    enum  class ShaderType
    {
        NONE=-1,VERTEX=0,FRAGMENT=1
    };

    //该变量用于存储着色器代码
    std::string line;
    //该变量用于存储着色器类型
    std::stringstream ss[2];
    //该变量是当前着色器类型
    ShaderType type = ShaderType::NONE;
    //功能:读取文件中的每一行内容,直到文件结束
    while (getline(stream, line))
    {
        //如果当前行包含#shader,则说明接下来是着色器代码
        if (line.find("#shader") != std::string::npos)
        {
            //如果当前行包含vertex,则说明接下来是顶点着色器代码
            if (line.find("vertex") != std::string::npos)
            {
                type = ShaderType::VERTEX;
            }
            else if (line.find("fragment") != std::string::npos)
            {
                type = ShaderType::FRAGMENT;
            }
        }
        else
        {
            //否则,将当前行添加到对应着色器代码的stringstream中
            ss[(int)type] << line << '\n';
        }
    }
    //返回ShaderProgramSource结构体
    return { ss[0].str(), ss[1].str() };
}


//功能:编译着色器代码
static unsigned int CompilesShader(unsigned int type, const std::string& source)
{
    //功能:创建着色器对象
    unsigned int id = glCreateShader(type);
    //功能:设置着色器源代码.
    const char* src = source.c_str();
    //功能:替换着色器对象中的源代码。将该id的指定着色器的源代码设置为src指针指向的字符串
    glShaderSource(id, 1, &src, nullptr);
    //功能:编译着色器对象的源代码
    glCompileShader(id);

    //设置返回着色器的对象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 shader!" << (type == GL_VERTEX_SHADER? "Vertex" : "Fragment") << "shader!" << std::endl;
        std::cout << message << std::endl;
        //删除着色器对象
        glDeleteShader(id);
        return 0;
    }

    //TODO:错误处理ing
    return id;
}


//功能:创建着色器程序
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
    //创建程序对象
    unsigned int program = glCreateProgram();
    //编译顶点着色器对象
    unsigned int vs = CompilesShader(GL_VERTEX_SHADER, vertexShader);
    //编译片段着色器对象
    unsigned int fs = CompilesShader(GL_FRAGMENT_SHADER, fragmentShader);

    //功能:将编译好的着色器对象附加到程序对象中
    glAttachShader(program, vs);
    glAttachShader(program, fs);

    //功能:链接程序对象
    glLinkProgram(program);

    //功能:验证着色器程序对象是否可以在当前OpenGL状态中执行。检查着色器程序的完整性和可执行性。
    glValidateProgram(program);

    //删除着色器对象,因为它们已经被链接到程序对象中
    glDeleteShader(vs);
    glDeleteShader(fs);

    //返回着色器程序
    return program;
}


int main(void)
{
    GLFWwindow* window;

    //初始化glfw
    if (!glfwInit())
        return -1;

    //创建一个窗口模式的窗口并设置OpenGL上下文
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)//如果窗口创建失败,则终止程序
    {
        glfwTerminate();//释放glfw资源
        return -1;
    }

    //设置当前窗口的上下文,之后所有的OpenGL调用都会在这个上下文中进行
    glfwMakeContextCurrent(window);

    //初始化GLEW
    if (glewInit() != GLEW_OK)
        std::cout << "Error!" << std::endl;

    //打印OpenGL版本信息
    std::cout << glGetString(GL_VERSION) << std::endl;

    //准备数据
    float position[] = {
        -0.5f, -0.5f,
        0.5f, -0.5f,
        0.5f,0.5f,
        -0.5f,0.5f,
    };

    //定义顶点索引缓存,用于标定顶点顺序
    unsigned int indices[] = {
        0,1,2,
        2,3,0
    };

    
    //定义缓冲区对象
    unsigned int buffer;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    GLCall(glGenBuffers(1, &buffer));
    //功能:将缓冲区对象绑定到目标
    GLCall(glBindBuffer(GL_ARRAY_BUFFER, buffer));
    //功能:将数据写入缓冲区
    GLCall(glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW));

    //功能:配置顶点属性指针
    GLCall(glEnableVertexAttribArray(0));
    //功能:指定顶点属性数组的索引、大小、数据类型、是否归一化、偏移量、数据指针
    GLCall(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0));
    

    //索引缓冲区对象
    unsigned int ibo;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    GLCall(glGenBuffers(1, &ibo));
    //功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区
    GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo));
    //功能:将数据写入缓冲区
    GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW));


    //解析着色器代码文件
    ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");
    std::string vertexShader = source.VertexSource;
    std::string fragmentShader = source.FragmentSource;
    //创建着色器程序
    unsigned int shader = CreateShader(vertexShader, fragmentShader);
    //使用着色器程序
    GLCall(glUseProgram(shader));

    //功能:获取着色器程序中 "u_Color" 的 uniform 变量的位置
    GLCall(int location = glGetUniformLocation(shader, "u_Color"));
    //检查返回值是否为 -1,避免在后续对一个不存在的 uniform 变量操作。
    ASSERT(location != -1);
    //功能:设置颜色
    GLCall(glUniform4f(location, 0.8f, 0.3f, 0.8f, 1.0f));




    //渲染循环,直到窗口被关闭
    while (!glfwWindowShouldClose(window))
    {
        //清除颜色缓冲区
        GLCall(glClear(GL_COLOR_BUFFER_BIT));
        
        //功能:绘制三角形
        GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));

        //刷新缓冲区并交换窗口
        GLCall(glfwSwapBuffers(window));

        //处理窗口事件,如键盘输入、鼠标移动等
        GLCall(glfwPollEvents());
    }

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

    //释放 GLFW 库占用的所有资源。
    glfwTerminate();
    return 0;
}

2.1.3运行结果

2.12动画效果

2.1.1 完全注释代码

cpp 复制代码
#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include<iostream>
#include<fstream>
#include<string>
#include<sstream>

//功能:定义宏,用于在调试过程中进行条件断言,检测OpenGL错误
#define ASSERT(x) if(!(x)) __debugbreak();
//功能:定义了一个宏 GLCall(x),用于调试 OpenGL 应用程序时检查和处理 OpenGL 错误。
#define GLCall(x) GLClearError();\
    x;\
    ASSERT(GLLogError(#x,__FILE__,__LINE__));

static void GLClearError()
{
    //功能:检测OpenGL错误
    while (glGetError() != GL_NO_ERROR);
}

//参数:1.function: 发生错误的函数名
static bool GLLogError(const char* function, const char* file, int line)
{
    //功能:检测OpenGL错误,输出错误信息
    //while循环,直到glGetError()返回GL_NO_ERROR,表示没有错误发生。
    while (GLenum error = glGetError())
    {
        std::cout << "[OpenGL Error] (" << error << "): " << function << 
            " " << file << ":" << line << std::endl;
        return false;
    }
    return true;
}


//功能:定义ShaderProgramSource结构体,用于存储着色器代码
struct ShaderProgramSource
{
    std::string VertexSource;
    std::string FragmentSource;
};


//功能:解析着色器代码文件。
static ShaderProgramSource ParseShader(const std::string& filepath)
{
    //功能:打开文件流
    std::ifstream stream(filepath);

    //定义着色器类型
    enum  class ShaderType
    {
        NONE=-1,VERTEX=0,FRAGMENT=1
    };

    //该变量用于存储着色器代码
    std::string line;
    //该变量用于存储着色器类型
    std::stringstream ss[2];
    //该变量是当前着色器类型
    ShaderType type = ShaderType::NONE;
    //功能:读取文件中的每一行内容,直到文件结束
    while (getline(stream, line))
    {
        //如果当前行包含#shader,则说明接下来是着色器代码
        if (line.find("#shader") != std::string::npos)
        {
            //如果当前行包含vertex,则说明接下来是顶点着色器代码
            if (line.find("vertex") != std::string::npos)
            {
                type = ShaderType::VERTEX;
            }
            else if (line.find("fragment") != std::string::npos)
            {
                type = ShaderType::FRAGMENT;
            }
        }
        else
        {
            //否则,将当前行添加到对应着色器代码的stringstream中
            ss[(int)type] << line << '\n';
        }
    }
    //返回ShaderProgramSource结构体
    return { ss[0].str(), ss[1].str() };
}


//功能:编译着色器代码
static unsigned int CompilesShader(unsigned int type, const std::string& source)
{
    //功能:创建着色器对象
    unsigned int id = glCreateShader(type);
    //功能:设置着色器源代码.
    const char* src = source.c_str();
    //功能:替换着色器对象中的源代码。将该id的指定着色器的源代码设置为src指针指向的字符串
    glShaderSource(id, 1, &src, nullptr);
    //功能:编译着色器对象的源代码
    glCompileShader(id);

    //设置返回着色器的对象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 shader!" << (type == GL_VERTEX_SHADER? "Vertex" : "Fragment") << "shader!" << std::endl;
        std::cout << message << std::endl;
        //删除着色器对象
        glDeleteShader(id);
        return 0;
    }

    //TODO:错误处理ing
    return id;
}


//功能:创建着色器程序
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
    //创建程序对象
    unsigned int program = glCreateProgram();
    //编译顶点着色器对象
    unsigned int vs = CompilesShader(GL_VERTEX_SHADER, vertexShader);
    //编译片段着色器对象
    unsigned int fs = CompilesShader(GL_FRAGMENT_SHADER, fragmentShader);

    //功能:将编译好的着色器对象附加到程序对象中
    glAttachShader(program, vs);
    glAttachShader(program, fs);

    //功能:链接程序对象
    glLinkProgram(program);

    //功能:验证着色器程序对象是否可以在当前OpenGL状态中执行。检查着色器程序的完整性和可执行性。
    glValidateProgram(program);

    //删除着色器对象,因为它们已经被链接到程序对象中
    glDeleteShader(vs);
    glDeleteShader(fs);

    //返回着色器程序
    return program;
}


int main(void)
{
    GLFWwindow* window;

    //初始化glfw
    if (!glfwInit())
        return -1;

    //创建一个窗口模式的窗口并设置OpenGL上下文
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)//如果窗口创建失败,则终止程序
    {
        glfwTerminate();//释放glfw资源
        return -1;
    }

    //设置当前窗口的上下文,之后所有的OpenGL调用都会在这个上下文中进行
    glfwMakeContextCurrent(window);

    //设置刷新频率
    //参数:1表示程序会等待一个垂直重绘周期之后再进行缓冲区交换。大概可以理解为每秒60帧。
    // 如果显示器的刷新率不是 60 Hz,而是其他值(例如 75 Hz 或 120 Hz),
    // glfwSwapInterval(1); 会根据显示器的实际刷新率来调整程序的帧率。
    // glfwSwapInterval 函数通常只接受 0、1 或 -1 作为参数,分别代表无同步、垂直同步以及可自适应同步。
    glfwSwapInterval(1);

    //初始化GLEW
    if (glewInit() != GLEW_OK)
        std::cout << "Error!" << std::endl;

    //打印OpenGL版本信息
    std::cout << glGetString(GL_VERSION) << std::endl;

    //准备数据
    float position[] = {
        -0.5f, -0.5f,
        0.5f, -0.5f,
        0.5f,0.5f,
        -0.5f,0.5f,
    };

    //定义顶点索引缓存,用于标定顶点顺序
    unsigned int indices[] = {
        0,1,2,
        2,3,0
    };

    
    //定义缓冲区对象
    unsigned int buffer;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    GLCall(glGenBuffers(1, &buffer));
    //功能:将缓冲区对象绑定到目标
    GLCall(glBindBuffer(GL_ARRAY_BUFFER, buffer));
    //功能:将数据写入缓冲区
    GLCall(glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW));

    //功能:配置顶点属性指针
    GLCall(glEnableVertexAttribArray(0));
    //功能:指定顶点属性数组的索引、大小、数据类型、是否归一化、偏移量、数据指针
    GLCall(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0));
    

    //索引缓冲区对象
    unsigned int ibo;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    GLCall(glGenBuffers(1, &ibo));
    //功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区
    GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo));
    //功能:将数据写入缓冲区
    GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW));


    //解析着色器代码文件
    ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");
    std::string vertexShader = source.VertexSource;
    std::string fragmentShader = source.FragmentSource;
    //创建着色器程序
    unsigned int shader = CreateShader(vertexShader, fragmentShader);
    //使用着色器程序
    GLCall(glUseProgram(shader));

    //功能:获取着色器程序中 "u_Color" 的 uniform 变量的位置
    GLCall(int location = glGetUniformLocation(shader, "u_Color"));
    //检查返回值是否为 -1,避免在后续对一个不存在的 uniform 变量操作。
    ASSERT(location != -1);
    //功能:设置颜色
    GLCall(glUniform4f(location, 0.8f, 0.3f, 0.8f, 1.0f));

    float r = 0.0f;
    float increment = 0.05f;


    //渲染循环,直到窗口被关闭
    while (!glfwWindowShouldClose(window))
    {
        //清除颜色缓冲区
        GLCall(glClear(GL_COLOR_BUFFER_BIT));
        
        GLCall(glUniform4f(location, r, 0.3f, 0.8f, 1.0f))
        //功能:绘制三角形
        GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));

        //更新颜色
        //实现颜色的循环变化
        if(r > 1.0f)
            increment = -0.05f;
        else if(r < 0.0f)
            increment = 0.05f;

        r += increment;



        //刷新缓冲区并交换窗口
        GLCall(glfwSwapBuffers(window));

        //处理窗口事件,如键盘输入、鼠标移动等
        GLCall(glfwPollEvents());
    }

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

    //释放 GLFW 库占用的所有资源。
    glfwTerminate();
    return 0;
}

2.1.2简洁注释代码

cpp 复制代码
#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include<iostream>
#include<fstream>
#include<string>
#include<sstream>

//功能:定义宏,用于在调试过程中进行条件断言,检测OpenGL错误
#define ASSERT(x) if(!(x)) __debugbreak();
//功能:定义了一个宏 GLCall(x),用于调试 OpenGL 应用程序时检查和处理 OpenGL 错误。
#define GLCall(x) GLClearError();\
    x;\
    ASSERT(GLLogError(#x,__FILE__,__LINE__));

static void GLClearError()
{
    //功能:检测OpenGL错误
    while (glGetError() != GL_NO_ERROR);
}

//参数:1.function: 发生错误的函数名
static bool GLLogError(const char* function, const char* file, int line)
{
    //功能:检测OpenGL错误,输出错误信息
    //while循环,直到glGetError()返回GL_NO_ERROR,表示没有错误发生。
    while (GLenum error = glGetError())
    {
        std::cout << "[OpenGL Error] (" << error << "): " << function << 
            " " << file << ":" << line << std::endl;
        return false;
    }
    return true;
}


//功能:定义ShaderProgramSource结构体,用于存储着色器代码
struct ShaderProgramSource
{
    std::string VertexSource;
    std::string FragmentSource;
};


//功能:解析着色器代码文件。
static ShaderProgramSource ParseShader(const std::string& filepath)
{
    //功能:打开文件流
    std::ifstream stream(filepath);

    //定义着色器类型
    enum  class ShaderType
    {
        NONE=-1,VERTEX=0,FRAGMENT=1
    };

    //该变量用于存储着色器代码
    std::string line;
    //该变量用于存储着色器类型
    std::stringstream ss[2];
    //该变量是当前着色器类型
    ShaderType type = ShaderType::NONE;
    //功能:读取文件中的每一行内容,直到文件结束
    while (getline(stream, line))
    {
        //如果当前行包含#shader,则说明接下来是着色器代码
        if (line.find("#shader") != std::string::npos)
        {
            //如果当前行包含vertex,则说明接下来是顶点着色器代码
            if (line.find("vertex") != std::string::npos)
            {
                type = ShaderType::VERTEX;
            }
            else if (line.find("fragment") != std::string::npos)
            {
                type = ShaderType::FRAGMENT;
            }
        }
        else
        {
            //否则,将当前行添加到对应着色器代码的stringstream中
            ss[(int)type] << line << '\n';
        }
    }
    //返回ShaderProgramSource结构体
    return { ss[0].str(), ss[1].str() };
}


//功能:编译着色器代码
static unsigned int CompilesShader(unsigned int type, const std::string& source)
{
    //功能:创建着色器对象
    unsigned int id = glCreateShader(type);
    //功能:设置着色器源代码.
    const char* src = source.c_str();
    //功能:替换着色器对象中的源代码。将该id的指定着色器的源代码设置为src指针指向的字符串
    glShaderSource(id, 1, &src, nullptr);
    //功能:编译着色器对象的源代码
    glCompileShader(id);

    //设置返回着色器的对象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 shader!" << (type == GL_VERTEX_SHADER? "Vertex" : "Fragment") << "shader!" << std::endl;
        std::cout << message << std::endl;
        //删除着色器对象
        glDeleteShader(id);
        return 0;
    }

    //TODO:错误处理ing
    return id;
}


//功能:创建着色器程序
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
    //创建程序对象
    unsigned int program = glCreateProgram();
    //编译顶点着色器对象
    unsigned int vs = CompilesShader(GL_VERTEX_SHADER, vertexShader);
    //编译片段着色器对象
    unsigned int fs = CompilesShader(GL_FRAGMENT_SHADER, fragmentShader);

    //功能:将编译好的着色器对象附加到程序对象中
    glAttachShader(program, vs);
    glAttachShader(program, fs);

    //功能:链接程序对象
    glLinkProgram(program);

    //功能:验证着色器程序对象是否可以在当前OpenGL状态中执行。检查着色器程序的完整性和可执行性。
    glValidateProgram(program);

    //删除着色器对象,因为它们已经被链接到程序对象中
    glDeleteShader(vs);
    glDeleteShader(fs);

    //返回着色器程序
    return program;
}


int main(void)
{
    GLFWwindow* window;

    //初始化glfw
    if (!glfwInit())
        return -1;

    //创建一个窗口模式的窗口并设置OpenGL上下文
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)//如果窗口创建失败,则终止程序
    {
        glfwTerminate();//释放glfw资源
        return -1;
    }

    //设置当前窗口的上下文,之后所有的OpenGL调用都会在这个上下文中进行
    glfwMakeContextCurrent(window);

    //设置刷新频率
    glfwSwapInterval(1);

    //初始化GLEW
    if (glewInit() != GLEW_OK)
        std::cout << "Error!" << std::endl;

    //打印OpenGL版本信息
    std::cout << glGetString(GL_VERSION) << std::endl;

    //准备数据
    float position[] = {
        -0.5f, -0.5f,
        0.5f, -0.5f,
        0.5f,0.5f,
        -0.5f,0.5f,
    };

    //定义顶点索引缓存,用于标定顶点顺序
    unsigned int indices[] = {
        0,1,2,
        2,3,0
    };

    
    //定义缓冲区对象
    unsigned int buffer;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    GLCall(glGenBuffers(1, &buffer));
    //功能:将缓冲区对象绑定到目标
    GLCall(glBindBuffer(GL_ARRAY_BUFFER, buffer));
    //功能:将数据写入缓冲区
    GLCall(glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW));

    //功能:配置顶点属性指针
    GLCall(glEnableVertexAttribArray(0));
    //功能:指定顶点属性数组的索引、大小、数据类型、是否归一化、偏移量、数据指针
    GLCall(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0));
    

    //索引缓冲区对象
    unsigned int ibo;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    GLCall(glGenBuffers(1, &ibo));
    //功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区
    GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo));
    //功能:将数据写入缓冲区
    GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW));


    //解析着色器代码文件
    ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");
    std::string vertexShader = source.VertexSource;
    std::string fragmentShader = source.FragmentSource;
    //创建着色器程序
    unsigned int shader = CreateShader(vertexShader, fragmentShader);
    //使用着色器程序
    GLCall(glUseProgram(shader));

    //功能:获取着色器程序中 "u_Color" 的 uniform 变量的位置
    GLCall(int location = glGetUniformLocation(shader, "u_Color"));
    //检查返回值是否为 -1,避免在后续对一个不存在的 uniform 变量操作。
    ASSERT(location != -1);
    //功能:设置颜色
    GLCall(glUniform4f(location, 0.8f, 0.3f, 0.8f, 1.0f));

    float r = 0.0f;
    float increment = 0.05f;


    //渲染循环,直到窗口被关闭
    while (!glfwWindowShouldClose(window))
    {
        //清除颜色缓冲区
        GLCall(glClear(GL_COLOR_BUFFER_BIT));
        
        GLCall(glUniform4f(location, r, 0.3f, 0.8f, 1.0f))
        //功能:绘制三角形
        GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));

        //更新颜色
        if(r > 1.0f)
            increment = -0.05f;
        else if(r < 0.0f)
            increment = 0.05f;

        r += increment;


        //刷新缓冲区并交换窗口
        GLCall(glfwSwapBuffers(window));

        //处理窗口事件,如键盘输入、鼠标移动等
        GLCall(glfwPollEvents());
    }

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

    //释放 GLFW 库占用的所有资源。
    glfwTerminate();
    return 0;
}

2.1.3运行结果

蓝色粉色渐变循环

相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习