OpenGL Chan视频学习-11 Uniforms in OpenGL

发布于:2025-05-27 ⋅ 阅读:(29) ⋅ 点赞:(0)

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

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

 //功能:获取着色器程序中 "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函数修改)

修改刷新频率

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

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

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

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

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

 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

#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

#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

#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 完全注释代码

#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简洁注释代码

#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运行结果

蓝色粉色渐变循环


 


网站公告

今日签到

点亮在社区的每一天
去签到