OpenGL 3.错误处理,统一变量,顶点数组对象(VAO)

使用glGetError获取错误码

Application.cpp

//需要先引用GL/glew.h,不然会报错
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

//设置一个断言,__debugbreak是编译器本身的函数
#define ASSERT(x) if(!(x)) __debugbreak();

//x是我们要运行的函数,‘\’是换行符,#x会将x转变为一个字符串
//先清空所有的错误,然后运行函数x,检查错误
#define GLCall(x) GLClearError();\
    x;\
    ASSERT(GLLogCall(#x, __FILE__, __LINE__))

/// <summary>
/// 清空所有的错误
/// </summary>
static void GLClearError()
{
    
    
    //当代码发生错误时,会有错误标记(error flag),多个错误就有多个标记,
    //glGetError会返回其中任意一个标记并将其重置,所以要在循环中调用。这里方法体不需要写
    while (glGetError() != GL_NO_ERROR);
}

/// <summary>
/// 出现错误时中断
/// </summary>
/// <param name="function">函数名</param>
/// <param name="file">文件路径</param>
/// <param name="line">行数</param>
/// <returns></returns>
static bool GLLogCall(const char* function, const char* file, int line)
{
    
    
    while (GLenum error = glGetError())
    {
    
    
        std::cout << "[OpenGL Error] (" << error << "): " << function << " " 
            << file << ":" << line << std::endl;
        return false;
    }
    return true;
}

struct ShaderProgramSource
{
    
    
    std::string VertexSource;
    std::string FragmentSource;
};

/// <summary>
/// 读取并解析shader文件
/// </summary>
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)) 
    {
    
    
        if (line.find("#shader") != std::string::npos) 
        {
    
    
            if (line.find("vertex") != std::string::npos)
                type = ShaderType::VERTEX;
            else if (line.find("fragment") != std::string::npos)
                type = ShaderType::FRAGMENT;
        }
        else 
        {
    
    
            ss[(int)type] << line << '\n';
        }
    }

    return {
    
     ss[0].str(), ss[1].str() };
}


static unsigned int CompileShader(unsigned int type, const std::string& source) 
{
    
    
    //着色器对象的句柄(ID)
    unsigned int id = glCreateShader(type);
    //c_str()就是将C++的string转化为C的字符串数组,c_str()生成一个const char *指针,指向字符串的首地址
    const char* src = source.c_str();
    //替换着色器对象中的源代码,1表示指定的源码的数量,如果字符串是以null为结尾,就可以把nullptr赋值给length
    glShaderSource(id, 1, &src, nullptr);
    glCompileShader(id);

    //错误处理
    int result;
    //i表示整数,v表示vector,这里表示指针
    glGetShaderiv(id, GL_COMPILE_STATUS, &result);
    if (result == GL_FALSE) 
    {
    
    
        int length;
        //返回着色器信息日志中的字符数
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
        //alloca是在栈(stack)上申请空间,用完马上就释放
        char* message = (char*)alloca(length * sizeof(char));
        glGetShaderInfoLog(id, length, &length, message);
        std::cout << "Failed to compile" << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader!" << std::endl;
        std::cout << message << std::endl;

        //着色器没有编译成功,删除这个shader
        glDeleteShader(id);
        return 0;
    }

    return id;
}

/// <summary>
/// 创建着色器
/// </summary>
/// <param name="vertexShader">顶点着色器,源码作为字符串传入</param>
/// <param name="fragmentShader">片段着色器</param>
/// <returns>返回唯一标识符</returns>
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader) 
{
    
    
    unsigned int program = glCreateProgram();
    unsigned int vShader = CompileShader(GL_VERTEX_SHADER, vertexShader);
    unsigned int fShader = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

    glAttachShader(program, vShader);
    glAttachShader(program, fShader);
    glLinkProgram(program);
    //检查程序中包含的可执行文件是否可以在当前 OpenGL 状态下执行。
    glValidateProgram(program);

    //现在可以删除shader(中间文件),因为它们已经连接到一个程序中了
    glDeleteShader(vShader);
    glDeleteShader(fShader);

    return program;
}

int main(void)
{
    
    
    GLFWwindow* window;

    /* Initialize the library */
    if (!glfwInit())
        return -1;

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)
    {
    
    
        glfwTerminate();
        return -1;
    }

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    //必须在创建上下文之后,初始化glew
    GLenum err = glewInit();
    if (GLEW_OK != err)
        std::cout << err << std::endl;

    //缓冲区,两个三角形组成一个四边形,注意三角形顶点需要逆时针旋转
    float positions[] = {
    
    
        -0.5f, -0.5f, //0
         0.5f, -0.5f, //1
         0.5f,  0.5f, //2
        -0.5f,  0.5f, //3
    };

    //索引缓冲区,索引positions数组下标
    unsigned int indices[] = {
    
    
        0, 1, 2,
        2, 3, 0,
    };

    //缓冲区或者索引缓冲区类型必须是unsigned int
    unsigned int buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(float), positions, GL_STATIC_DRAW);

    //ibo指index buffer object
    unsigned int ibo;
    glGenBuffers(1, &ibo);
    //GL_ELEMENT_ARRAY_BUFFER就是索引缓冲区的插槽
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW);

    //启用顶点属性数组
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);

    ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");

    unsigned int shader = CreateShader(source.VertexSource, source.FragmentSource);
    //将对象安装为当前渲染状态的一部分
    glUseProgram(shader);

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

        //这里将GL_UNSIGNED_INT改为GL_INT
        //这里输出错误码1280,OpenGL使用16进制表示所有的错误码,所以需要转换为0x0500
        //在glew.h中搜索得到 #define GL_INVALID_ENUM 0x0500,即无效枚举
        GLCall(glDrawElements(GL_TRIANGLES, 6, GL_INT, nullptr));

        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }

    glDeleteProgram(shader);

    glfwTerminate();
    return 0;
}

统一变量

在shader中添加统一变量,在代码中获取该变量后修改。
Basic.shader

#shader vertex
#version 330 core

layout(location = 0) in vec4 position; //因为gl_Position是vec4,所有这里转换

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

//需要先引用GL/glew.h,不然会报错
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

//设置一个断言,__debugbreak是编译器本身的函数
#define ASSERT(x) if(!(x)) __debugbreak();

//x是我们要运行的函数,‘\’是换行符,#x会将x转变为一个字符串
//先清空所有的错误,然后运行函数x,检查错误
#define GLCall(x) GLClearError();\
    x;\
    ASSERT(GLLogCall(#x, __FILE__, __LINE__))

/// <summary>
/// 清空所有的错误
/// </summary>
static void GLClearError()
{
    
    
    //当代码发生错误时,会有错误标记(error flag),多个错误就有多个标记,
    //glGetError会返回其中任意一个标记并将其重置,所以要在循环中调用。这里方法体不需要写
    while (glGetError() != GL_NO_ERROR);
}

/// <summary>
/// 出现错误时中断
/// </summary>
/// <param name="function">函数名</param>
/// <param name="file">文件路径</param>
/// <param name="line">行数</param>
/// <returns></returns>
static bool GLLogCall(const char* function, const char* file, int line)
{
    
    
    while (GLenum error = glGetError())
    {
    
    
        std::cout << "[OpenGL Error] (" << error << "): " << function << " " 
            << file << ":" << line << std::endl;
        return false;
    }
    return true;
}

struct ShaderProgramSource
{
    
    
    std::string VertexSource;
    std::string FragmentSource;
};

/// <summary>
/// 读取并解析shader文件
/// </summary>
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)) 
    {
    
    
        if (line.find("#shader") != std::string::npos) 
        {
    
    
            if (line.find("vertex") != std::string::npos)
                type = ShaderType::VERTEX;
            else if (line.find("fragment") != std::string::npos)
                type = ShaderType::FRAGMENT;
        }
        else 
        {
    
    
            ss[(int)type] << line << '\n';
        }
    }

    return {
    
     ss[0].str(), ss[1].str() };
}


static unsigned int CompileShader(unsigned int type, const std::string& source) 
{
    
    
    //着色器对象的句柄(ID)
    unsigned int id = glCreateShader(type);
    //c_str()就是将C++的string转化为C的字符串数组,c_str()生成一个const char *指针,指向字符串的首地址
    const char* src = source.c_str();
    //替换着色器对象中的源代码,1表示指定的源码的数量,如果字符串是以null为结尾,就可以把nullptr赋值给length
    glShaderSource(id, 1, &src, nullptr);
    glCompileShader(id);

    //错误处理
    int result;
    //i表示整数,v表示vector,这里表示指针
    glGetShaderiv(id, GL_COMPILE_STATUS, &result);
    if (result == GL_FALSE) 
    {
    
    
        int length;
        //返回着色器信息日志中的字符数
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
        //alloca是在栈(stack)上申请空间,用完马上就释放
        char* message = (char*)alloca(length * sizeof(char));
        glGetShaderInfoLog(id, length, &length, message);
        std::cout << "Failed to compile" << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader!" << std::endl;
        std::cout << message << std::endl;

        //着色器没有编译成功,删除这个shader
        glDeleteShader(id);
        return 0;
    }

    return id;
}

/// <summary>
/// 创建着色器
/// </summary>
/// <param name="vertexShader">顶点着色器,源码作为字符串传入</param>
/// <param name="fragmentShader">片段着色器</param>
/// <returns>返回唯一标识符</returns>
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader) 
{
    
    
    unsigned int program = glCreateProgram();
    unsigned int vShader = CompileShader(GL_VERTEX_SHADER, vertexShader);
    unsigned int fShader = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

    glAttachShader(program, vShader);
    glAttachShader(program, fShader);
    glLinkProgram(program);
    //检查程序中包含的可执行文件是否可以在当前 OpenGL 状态下执行。
    glValidateProgram(program);

    //现在可以删除shader(中间文件),因为它们已经连接到一个程序中了
    glDeleteShader(vShader);
    glDeleteShader(fShader);

    return program;
}

int main(void)
{
    
    
    GLFWwindow* window;

    /* Initialize the library */
    if (!glfwInit())
        return -1;

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)
    {
    
    
        glfwTerminate();
        return -1;
    }

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    //开启垂直同步,如果它设定1就有60FPS。设置高于1的数字来降低这个速率(如果设置为2,将得到30FPS)。
    //0:关闭垂直同步 ; 1:60FPS ; 2:30FPS。
    glfwSwapInterval(1);

    //必须在创建上下文之后,初始化glew
    GLenum err = glewInit();
    if (GLEW_OK != err)
        std::cout << err << std::endl;

    //缓冲区,两个三角形组成一个四边形,注意三角形顶点需要逆时针旋转
    float positions[] = {
    
    
        -0.5f, -0.5f, //0
         0.5f, -0.5f, //1
         0.5f,  0.5f, //2
        -0.5f,  0.5f, //3
    };

    //索引缓冲区,索引positions数组下标
    unsigned int indices[] = {
    
    
        0, 1, 2,
        2, 3, 0,
    };

    //缓冲区或者索引缓冲区类型必须是unsigned int
    unsigned int buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(float), positions, GL_STATIC_DRAW);

    //ibo指index buffer object
    unsigned int ibo;
    glGenBuffers(1, &ibo);
    //GL_ELEMENT_ARRAY_BUFFER就是索引缓冲区的插槽
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW);

    //启用顶点属性数组
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);

    ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");

    unsigned int shader = CreateShader(source.VertexSource, source.FragmentSource);
    glUseProgram(shader);
    //通过变量名获取变量的位置,这个位置就是id
    GLCall(int location = glGetUniformLocation(shader, "u_Color"));
    ASSERT(location != -1);

    float r = 0.0f;
    float increment = 0.01f;

    /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window))
    {
    
    
        /* Render here */
        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.01f;
        else if(r < 0.0f)
            increment += 0.01f;

        r += increment;

        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }

    glDeleteProgram(shader);

    glfwTerminate();
    return 0;
}

顶点数组对象(Vertex Array Object)

Application.cpp

//需要先引用GL/glew.h,不然会报错
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

//设置一个断言,__debugbreak是编译器本身的函数
#define ASSERT(x) if(!(x)) __debugbreak();

//x是我们要运行的函数,‘\’是换行符,#x会将x转变为一个字符串
//先清空所有的错误,然后运行函数x,检查错误
#define GLCall(x) GLClearError();\
    x;\
    ASSERT(GLLogCall(#x, __FILE__, __LINE__))

static void GLClearError()
{
    
    
    //当代码发生错误时,会有错误标记(error flag),多个错误就有多个标记,
    //glGetError会返回其中任意一个标记并将其重置,所以要在循环中调用。这里方法体不需要写
    while (glGetError() != GL_NO_ERROR);
}

static bool GLLogCall(const char* function, const char* file, int line)
{
    
    
    while (GLenum error = glGetError())
    {
    
    
        std::cout << "[OpenGL Error] (" << error << "): " << function << " "
            << file << ":" << line << std::endl;
        return false;
    }
    return true;
}

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))
    {
    
    
        if (line.find("#shader") != std::string::npos)
        {
    
    
            if (line.find("vertex") != std::string::npos)
                type = ShaderType::VERTEX;
            else if (line.find("fragment") != std::string::npos)
                type = ShaderType::FRAGMENT;
        }
        else
        {
    
    
            ss[(int)type] << line << '\n';
        }
    }

    return {
    
     ss[0].str(), ss[1].str() };
}


static unsigned int CompileShader(unsigned int type, const std::string& source)
{
    
    
    //着色器对象的句柄(ID)
    unsigned int id = glCreateShader(type);
    //c_str()就是将C++的string转化为C的字符串数组,c_str()生成一个const char *指针,指向字符串的首地址
    const char* src = source.c_str();
    //替换着色器对象中的源代码,1表示指定的源码的数量,如果字符串是以null为结尾,就可以把nullptr赋值给length
    glShaderSource(id, 1, &src, nullptr);
    glCompileShader(id);

    //错误处理
    int result;
    //i表示整数,v表示vector,这里表示指针
    glGetShaderiv(id, GL_COMPILE_STATUS, &result);
    if (result == GL_FALSE)
    {
    
    
        int length;
        //返回着色器信息日志中的字符数
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
        //alloca是在栈(stack)上申请空间,用完马上就释放
        char* message = (char*)alloca(length * sizeof(char));
        glGetShaderInfoLog(id, length, &length, message);
        std::cout << "Failed to compile" << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader!" << std::endl;
        std::cout << message << std::endl;

        glDeleteShader(id);
        return 0;
    }

    return id;
}

static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
    
    
    unsigned int program = glCreateProgram();
    unsigned int vShader = CompileShader(GL_VERTEX_SHADER, vertexShader);
    unsigned int fShader = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

    glAttachShader(program, vShader);
    glAttachShader(program, fShader);
    glLinkProgram(program);
    //检查程序中包含的可执行文件是否可以在当前 OpenGL 状态下执行。
    glValidateProgram(program);

    //现在可以删除shader(中间文件),因为它们已经连接到一个程序中了
    glDeleteShader(vShader);
    glDeleteShader(fShader);

    return program;
}

int main(void)
{
    
    
    GLFWwindow* window;

    /* Initialize the library */
    if (!glfwInit())
        return -1;

    //opengl的主版本是3,小版本是3,即版本号3.3
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    //设置opengl的配置是兼容配置文件,兼容配置使VAO对象0成为默认对象
    //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
    //设置opengl的配置是核心配置文件,不会绑定VAO对象,需要自己创建
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)
    {
    
    
        glfwTerminate();
        return -1;
    }

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    //开启垂直同步,如果它设定1就有60FPS。设置高于1的数字来降低这个速率(如果设置为2,将得到30FPS)。
    //0:关闭垂直同步 ; 1:60FPS ; 2:30FPS。
    glfwSwapInterval(1);

    GLenum err = glewInit();
    if (GLEW_OK != err)
        std::cout << err << std::endl;

    float positions[] = {
    
    
        -0.5f, -0.5f, //0
         0.5f, -0.5f, //1
         0.5f,  0.5f, //2
        -0.5f,  0.5f, //3
    };

    unsigned int indices[] = {
    
    
        0, 1, 2,
        2, 3, 0,
    };

    //创建一个顶点数组对象(vertex array object)并绑定
    unsigned int vao;
    GLCall(glGenVertexArrays(1, &vao));
    GLCall(glBindVertexArray(vao));

    //绑定顶点缓冲区,绑定索引缓冲区,然后绘制实际对象
    unsigned int buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(float), positions, GL_STATIC_DRAW);

    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);

    glEnableVertexAttribArray(0);
    //定义属性在内存中的布局,这个顶点数组索引为0的位置将绑定到GL_ARRAY_BUFFER,
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);

    ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");

    unsigned int shader = CreateShader(source.VertexSource, source.FragmentSource);
    glUseProgram(shader);
    GLCall(int location = glGetUniformLocation(shader, "u_Color"));
    ASSERT(location != -1);

    //解绑所有东西
    glBindVertexArray(0);
    glUseProgram(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    float r = 0.0f;
    float increment = 0.01f;

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

        //原来的流程:绑定着色器,设置统一变量,绑定顶点缓冲区,启用顶点属性数组,设置顶点缓冲区布局,绑定索引缓冲区
        glUseProgram(shader);
        GLCall(glUniform4f(location, r, 0.3f, 0.8f, 1.0f));

        //glBindBuffer(GL_ARRAY_BUFFER, buffer);
        //glEnableVertexAttribArray(0);
        //glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);

        //把过程中的绑定顶点缓冲区,启用顶点属性数组,设置顶点缓冲区布局,替换为绑定顶点数组对象
        glBindVertexArray(vao);
        
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));

        if (r > 1.0f)
            increment -= 0.01f;
        else if (r < 0.0f)
            increment += 0.01f;

        r += increment;

        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }

    glDeleteProgram(shader);

    glfwTerminate();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/sinat_34014668/article/details/126842470