OpenGL Chan视频学习-9 Index Buffers inOpenGL

news2025/6/3 1:37:07

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方法一

1.1.1.1代码
//准备数据
float position[] = {
    0.5f, 0.5f,
    -0.5f, -0.5f,
    0.5f, -0.5f,

    -0.5f,0.5f,
    -0.5f,-0.5f,
    0.5f,-0.5f

};

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

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

1.1.1.3解释

在float数组里面直接添加组成绘制方形的两个三角形所需的六个顶点。

1.1.1.4缺点

顶点冗余

1.1.2方法二——索引缓冲

1.1.1.1代码
//准备数据
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;
//功能:生成缓冲区对象,并将数据写入缓冲区
glGenBuffers(1, &buffer);
//功能:将缓冲区对象绑定到目标
glBindBuffer(GL_ARRAY_BUFFER, buffer);
//功能:将数据写入缓冲区
glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW);

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



//索引缓冲区对象
unsigned int ibo;
//功能:生成缓冲区对象,并将数据写入缓冲区
glGenBuffers(1, &ibo);
//功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区
//参数:1.GL_ELEMENT_ARRAY_BUFFER:指定目标为索引缓冲区对象
//2.ibo:索引缓冲区对象ID
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
//功能:将数据写入缓冲区
//参数:1.GL_ELEMENT_ARRAY_BUFFER:指定目标为索引缓冲区对象
//2.6*sizeof(unsigned int):索引数据大小
//3.indices:索引数据指针
//4.GL_STATIC_DRAW:指定数据不会改变
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW);

while绘制时改动

//glDrawArrays(GL_TRIANGLES, 0, 6);
//功能:绘制三角形
//参数:1.GL_TRIANGLES:绘制三角形
//2.6:顶点数量
//3.GL_UNSIGNED_INT:索引数据类型
//4.nullptr:索引数据指针
//因为索引缓冲区已经绑定到GL_ELEMENT_ARRAY_BUFFER目标,所以这里不需要再传入索引数据指针
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
1.1.1.2运行结果

如果不用unsigned,如:

glDrawElements(GL_TRIANGLES, 6, GL_INT, nullptr);

会绘制错误

1.1.1.3解释

利用索引缓冲。避免方法一的重复撰写,减少内存占用(在实际应用中顶点还包含了颜色,法线等信息)。顶点数从6->4。

QS:position的坐标点怎么和indices联系起来的

1. 顶点数据 (position 数组)

float position[] = {
    -0.5f, -0.5f,  // 顶点 0
    0.5f, -0.5f,   // 顶点 1
    0.5f, 0.5f,    // 顶点 2
    -0.5f, 0.5f    // 顶点 3
};
  • position数组存储了四个顶点的坐标。
  • 每个顶点由两个浮点数表示,分别是x和y坐标。
  • 顶点的顺序是:左下、右下、右上、左上。

2. 索引数据 (indices 数组)

unsigned int indices[] = {
    0, 1, 2,  // 三角形 1: 左下 -> 右下 -> 右上
    2, 3, 0   // 三角形 2: 右上 -> 左上 -> 左下
};
  • indices数组存储了绘制两个三角形所需的顶点索引。
  • 索引值对应position数组中顶点的顺序。
  • 例如:
    • 第一个三角形的顶点索引是 0, 1, 2,这意味着使用 position 数组中的第0个、第1个和第2个顶点来绘制一个三角形。
    • 第二个三角形的顶点索引是 2, 3, 0,这意味着使用 position 数组中的第2个、第3个和第0个顶点来绘制另一个三角形。

3. 顶点缓冲区对象 (buffer)

unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW);
  • 生成一个顶点缓冲区对象(VBO),用于存储顶点数据。
  • 将顶点数据绑定到GL_ARRAY_BUFFER,这是一个用于存储顶点属性(如位置、颜色等)的缓冲区目标。
  • 使用glBufferData将顶点数据传输到GPU的缓冲区中。

4. 索引缓冲区对象 (ibo)

unsigned int ibo;
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW);
  • 生成一个索引缓冲区对象(IBO),用于存储索引数据。
  • 将索引数据绑定到GL_ELEMENT_ARRAY_BUFFER,这是一个用于存储顶点索引的缓冲区目标。
  • 使用glBufferData将索引数据传输到GPU的缓冲区中。

如何联系起来

  • 顶点缓冲区对象 (buffer) 存储了顶点的位置数据。
  • 索引缓冲区对象 (ibo) 存储了顶点的索引数据,这些索引数据告诉OpenGL如何使用顶点缓冲区中的顶点来绘制几何图形。

main函数中,使用glDrawElements函数来绘制几何图形:

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
  • GL_TRIANGLES:指定绘制的图形类型为三角形。
  • 6:指定索引的数量(即indices数组的长度)。
  • GL_UNSIGNED_INT:指定索引数据的类型为无符号整数。
  • nullptr:指定索引数据在缓冲区中的偏移量为0。因为索引缓冲区已经绑定到GL_ELEMENT_ARRAY_BUFFER,OpenGL会自动从该缓冲区中读取索引数据。

详细联系过程

  1. 顶点数据绑定

    • position数组中存储了四个顶点的坐标。
    • 使用glVertexAttribPointer配置顶点属性指针:
      glEnableVertexAttribArray(0);
      glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);
      
    • 这里,0表示顶点属性的索引,2表示每个顶点有两个分量(x和y坐标),GL_FLOAT表示数据类型为浮点数。
  2. 索引数据绑定

    • indices数组中存储了绘制两个三角形所需的顶点索引。
    • 使用glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)将索引数据绑定到GL_ELEMENT_ARRAY_BUFFER
  3. 绘制过程

    • 当调用glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr)时,OpenGL会按照indices数组中的索引顺序从position数组中获取顶点数据。
    • 例如:
      • 第一个三角形的索引是 0, 1, 2,OpenGL会获取position数组中的第0个、第1个和第2个顶点来绘制第一个三角形。
      • 第二个三角形的索引是 2, 3, 0,OpenGL会获取position数组中的第2个、第3个和第0个顶点来绘制第二个三角形。

总结

  • 顶点缓冲区对象 (buffer) 和 索引缓冲区对象 (ibo) 分别存储顶点数据和索引数据。
  • glDrawElements 函数使用索引数据来决定如何从顶点缓冲区中提取顶点数据来绘制几何图形。
  • 这种方法可以减少顶点数据的冗余存储,提高内存效率。

二、完整代码

2.1完全注释代码

#include <GL/glew.h>
#include <GLFW/glfw3.h>

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

//功能:定义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;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    glGenBuffers(1, &buffer);
    //功能:将缓冲区对象绑定到目标
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    //功能:将数据写入缓冲区
    glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW);

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

    //索引缓冲区对象
    unsigned int ibo;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    glGenBuffers(1, &ibo);
    //功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区
    //参数:1.GL_ELEMENT_ARRAY_BUFFER:指定目标为索引缓冲区对象
    //2.ibo:索引缓冲区对象ID
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    //功能:将数据写入缓冲区
    //参数:1.GL_ELEMENT_ARRAY_BUFFER:指定目标为索引缓冲区对象
    //2.6*sizeof(unsigned int):索引数据大小
    //3.indices:索引数据指针
    //4.GL_STATIC_DRAW:指定数据不会改变
    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);
    //使用着色器程序
    glUseProgram(shader);

    //渲染循环,直到窗口被关闭
    while (!glfwWindowShouldClose(window))
    {
        //清除颜色缓冲区
        glClear(GL_COLOR_BUFFER_BIT);

        
        //glDrawArrays(GL_TRIANGLES, 0, 6);
        //功能:绘制三角形
        //参数:1.GL_TRIANGLES:绘制三角形
        //2.6:顶点数量
        //3.GL_UNSIGNED_INT:索引数据类型
        //4.nullptr:索引数据指针
        //因为索引缓冲区已经绑定到GL_ELEMENT_ARRAY_BUFFER目标,所以这里不需要再传入索引数据指针
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);

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

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

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

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

2.2简洁注释代码

#include <GL/glew.h>
#include <GLFW/glfw3.h>

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

//功能:定义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;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    glGenBuffers(1, &buffer);
    //功能:将缓冲区对象绑定到目标
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    //功能:将数据写入缓冲区
    glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW);

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

    //索引缓冲区对象
    unsigned int ibo;
    //功能:生成缓冲区对象,并将数据写入缓冲区
    glGenBuffers(1, &ibo);
    //功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区
    //参数:1.GL_ELEMENT_ARRAY_BUFFER:指定目标为索引缓冲区对象
    //2.ibo:索引缓冲区对象ID
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    //功能:将数据写入缓冲区
    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);
    //使用着色器程序
    glUseProgram(shader);

    //渲染循环,直到窗口被关闭
    while (!glfwWindowShouldClose(window))
    {
        //清除颜色缓冲区
        glClear(GL_COLOR_BUFFER_BIT);

        
        //glDrawArrays(GL_TRIANGLES, 0, 6);
        //功能:绘制三角形
        //参数:1.GL_TRIANGLES:绘制三角形
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);

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

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

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

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

2.3运行结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2392461.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx安全防护与HTTPS部署实战

目录 前言一. 核心安全配置1. 隐藏版本号2. 限制危险请求方法3. 请求限制&#xff08;CC攻击防御&#xff09;&#xff08;1&#xff09;使用nginx的limit_req模块限制请求速率&#xff08;2&#xff09;压力测试验证 4. 防盗链 二. 高级防护1. 动态黑名单&#xff08;1&#x…

JAVA重症监护系统源码 ICU重症监护系统源码 智慧医院重症监护系统源码

智慧医院重症监护系统源码 ICU重症监护系统源码 开发语言&#xff1a;JavaVUE ICU护理记录&#xff1a;实现病人数据的自动采集&#xff0c;实时记录监护过程数据。支持主流厂家的监护仪、呼吸机等床旁数字化设备的数据采集。对接检验检查系统&#xff0c;实现自动化录入。喜…

python:机器学习(KNN算法)

本文目录&#xff1a; 一、K-近邻算法思想二、KNN的应用方式&#xff08; 一&#xff09;分类流程&#xff08;二&#xff09;回归流程 三、API介绍&#xff08;一&#xff09;分类预测操作&#xff08;二&#xff09;回归预测操作 四、距离度量方法&#xff08;一&#xff09;…

【笔记】2025 年 Windows 系统下 abu 量化交易库部署与适配指南

#工作记录 前言 在量化交易的学习探索中&#xff0c;偶然接触到 2017 年开源的 abu 量化交易库&#xff0c;其代码结构和思路对新手理解量化回测、指标分析等基础逻辑有一定参考价值。然而&#xff0c;当尝试在 2025 年的开发环境中部署这个久未更新的项目时&#xff0c;遇到…

小程序 - 视图与逻辑

个人简介 👨‍💻‍个人主页: 魔术师 📖学习方向: 主攻前端方向,正逐渐往全栈发展 🚴个人状态: 研发工程师,现效力于政务服务网事业 🇨🇳人生格言: “心有多大,舞台就有多大。” 📚推荐学习: 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js实战 🍒T…

ChatGPT Plus/Pro 订阅教程(支持支付宝)

订阅 ChatGPT Plus GPT-4 最简单&#xff0c;成功率最高的方案 1. 登录 chat.openai.com 依次点击 Login &#xff0c;输入邮箱和密码 2. 点击升级 Upgrade 登录自己的 OpenAI 帐户后&#xff0c;点击左下角的 Upgrade to Plus&#xff0c;在弹窗中选择 Upgrade plan。 如果…

[蓝帽杯 2022 初赛]网站取证_2

一、找到与数据库有关系的PHP文件 打开内容如下&#xff0c;发现数据库密码是函数my_encrypt()返回的结果。 二、在文件夹encrypt中找到encrypt.php,内容如下&#xff0c;其中mcrypt已不再使用&#xff0c;所以使用php>7版本可能没有执行结果&#xff0c;需要换成较低版本…

安装 Node.js 和配置 cnpm 镜像源

一、安装 Node.js 方式一&#xff1a;官网下载&#xff08;适合所有系统&#xff09; 访问 Node.js 官网 推荐选择 LTS&#xff08;长期支持&#xff09;版本&#xff0c;点击下载安装包。 根据系统提示一步步完成安装。 方式二&#xff1a;通过包管理器安装&#xff08;建…

MacOS内存管理-删除冗余系统数据System Data

文章目录 一、问题复现二、解决思路三、解决流程四、附录 一、问题复现 以题主的的 Mac 为例&#xff0c;我们可以看到System Data所占数据高达77.08GB&#xff0c;远远超出系统所占内存 二、解决思路 占据大量空间的是分散在系统中各个位置Cache数据&#xff1b; 其中容量最…

行为型:中介者模式

目录 1、核心思想 2、实现方式 2.1 模式结构 2.2 实现案例 3、优缺点分析 4、适用场景 5、注意事项 1、核心思想 目的&#xff1a;通过引入一个中介对象来封装一组对象之间的交互&#xff0c;解决对象间过度耦合、频繁交互的问题。不管是对象引用维护还是消息的转发&am…

光谱相机在生态修复监测中的应用

光谱相机通过多维光谱数据采集与智能分析技术&#xff0c;在生态修复监测中构建起‌“感知-评估-验证”‌的全周期管理体系&#xff0c;其核心应用方向如下&#xff1a; 一、土壤修复效能量化评估 ‌重金属污染动态监测‌ 通过短波红外&#xff08;1000-2500nm&#xff09;波…

吉林大学操作系统上机实验五(磁盘引臂调度算法(scan算法)实现)

本次实验无参考&#xff0c;从头开始实现。 一.实验内容 模拟实现任意一个磁盘引臂调度算法&#xff0c;对磁盘进行移臂操作列出基于该种算法的磁道访问序列&#xff0c;计算平均寻道长度。 二.实验设计 假设磁盘只有一个盘面&#xff0c;并且磁盘是可移动头磁盘。磁盘是可…

ESP8266+STM32 AT驱动程序,心知天气API 记录时间: 2025年5月26日13:24:11

接线为 串口2 接入ESP8266 esp8266.c #include "stm32f10x.h"//8266预处理文件 #include "esp8266.h"//硬件驱动 #include "delay.h" #include "usart.h"//用得到的库 #include <string.h> #include <stdio.h> #include …

⭐️⭐️⭐️ 模拟题及答案 ⭐️⭐️⭐️ 大模型Clouder认证:RAG应用构建及优化

考试注意事项: 一、单选题(21题) 检索增强生成(RAG)的核心技术结合了什么? A. 图像识别与自然语言处理 B. 信息检索与文本生成 C. 语音识别与知识图谱 D. 数据挖掘与机器学习 RAG技术中,“建立索引”步骤不包括以下哪项操作? A. 将文档解析为纯文本 B. 文本片段分割(…

kali系统的安装及配置

1 kali下载 Kali 下载地址&#xff1a;Get Kali | Kali Linux &#xff08;https://www.kali.org/get-kali&#xff09; 下载 kali-linux-2024.4-installer-amd64.iso (http://cdimage.kali.org/kali-2024.4/) 2. 具体安装步骤&#xff1a; 2.1 进入官方地址&#xff0c;点击…

Redis的大Key问题如何解决?

大家好&#xff0c;我是锋哥。今天分享关于【Redis的大Key问题如何解决&#xff1f;】面试题。希望对大家有帮助&#xff1b; Redis的大Key问题如何解决&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Redis中的“大Key”问题是指某个键的值占用了过多…

影楼精修-AI追色算法解析

注意&#xff1a;本文样例图片为了避免侵权&#xff0c;均使用AIGC生成&#xff1b; AI追色是像素蛋糕软件中比较受欢迎的一个功能点&#xff0c;本文将针对AI追色来解析一下大概的技术原理。 功能分析 AI追色实际上可以理解为颜色迁移的一种变体或者叫做升级版&#xff0c;…

node入门:安装和npm使用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、安装npm命令nvm 前言 因为学习vue接触的&#xff0c;一直以为node是和vue绑定的&#xff0c;还以为vue跑起来必须要node&#xff0c;后续发现并不是。 看…

java虚拟机2

一、垃圾回收机制&#xff08;GC&#xff09; 1. 回收区域&#xff1a;GC主要回收堆内存区域。堆用于存放new出来的对象 。程序计数器、元数据区和栈一般不是GC回收的重点区域。 2. 回收单位&#xff1a;GC以对象为单位回收内存&#xff0c;而非字节。按对象维度回收更简便&am…

Pydantic 学习与使用

Pydantic 学习与使用 在 Fastapi 的 Web 开发中的数据验证通常都是在使用 Pydantic 来进行数据的校验&#xff0c;本文将对 Pydantic 的使用方法做记录与学习。 **简介&#xff1a;**Pydantic 是一个在 Python 中用于数据验证和解析的第三方库&#xff0c;它现在是 Python 使…