【LearnOpenGL】01 入门

根据LearnOpenGL CN学习OpenGL的笔记。本文包含简介和入门两部分内容,不过实际上学习过程中大部分笔记都写在代码注释里了,这里包含的内容较少。

简介

OpenGL本身并不是一个API,它仅仅是一个由Khronos组织制定并维护的规范(Specification)。实际的OpenGL库的开发者通常是显卡的生产商。当产生一个bug时通常可以通过升级显卡驱动来解决。

当使用新版本的OpenGL特性时,只有新一代的显卡能够支持你的应用程序。这也是为什么大多数开发者基于较低版本的OpenGL编写程序,并只提供选项启用新版本的特性。

开发者不必等待一个新的OpenGL规范面世,就可以使用这些新的渲染特性了,只需要简单地检查一下显卡是否支持此扩展。开发者可以使用这个扩展提供的一些更先进更有效的图形功能。通过ifelse语句使用

核心模式与立即渲染模式

立即渲染模式(Immediate mode,也就是固定渲染管线),这个模式下绘制图形很方便。OpenGL的大多数功能都被库隐藏起来,开发者很少有控制OpenGL如何进行计算的自由。效率太低

核心模式(Core-profile)更高的灵活性和效率

状态设置和状态使用

OpenGL自身是一个巨大的状态机(State Machine):一系列的变量描述OpenGL此刻应当如何运行。OpenGL的状态通常被称为OpenGL上下文(Context)。状态设置函数(State-changing Function),这类函数将会改变上下文。状态使用函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操作。

假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。

在OpenGL中一个对象是指一些选项的集合,它代表OpenGL状态的一个子集。比如,我们可以用一个对象来代表绘图窗口的设置,之后我们就可以设置它的大小、支持的颜色位数等等。可以把对象看做一个C风格的结构体(Struct)

结构体中的变量既可以使用C语言里的类型也可以使用OpenGL的。使用OpenGL的类型的好处是保证了在各平台中每一种类型的大小都是统一的。你也可以使用其它的定宽类型(Fixed-width Type)来实现这一点。

配环境

基本环境:

  • Win10 21H2
  • VS 2022 安装时勾“使用C++的桌面开发”

添加GLFW库。创建窗口、定义OpenGL上下文以及处理用户输入在各平台不一样,OpenGL不涉及,各平台有自己流行的包来实现。官网下载源代码(很多其它教程说预编译的二进制文件怎么怎么样会有什么问题等,因为电脑环境不同实际上可能需要自己编译)。然后在VS里文件->打开->CMake,然后等弹出CMake概述页后打开CMake设置编辑器,不用动,生成->全部生成,在glfw-3.3.8\out\build\x64-Debug\src下出现glfw3.lib文件。再在VS新建一个C++空项目,除了改个名外其他都默认就可以,在解决方案窗口里右键项目名->属性->VC++目录,在外部包含目录中添加C:\Program Files\glfw-3.3.8\include,在库目录中添加C:\Program Files\glfw-3.3.8\out\build\x64-Debug\src,然后VC++目录下面还有个“链接器”,在其输入->附加依赖项中添加glfw3.lib。这样这个库就被链接进来了。

添加OpenGL库。在上面的附加依赖项中添加opengl32.lib(用分号隔开;OpenGL库64位版本的文件名也是opengl32.lib,虽然很奇怪但确实如此)。这个库是系统自带的,所以不像上面这么麻烦。

配置GLAD。由于OpenGL函数由显卡驱动提供,因此需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用,而不能简单地直接调用。打开下载地址,gl选3.3,profile选core,Options中勾上Generate a loader,其它不用管然后点生成。点那个zip下载下来,方便起见,将include里的文件直接丢到刚才添加的include目录里。在解决方案资源管理器的源文件那里右键->添加->现有项,选择glad\src\glad.c,准备工作就完成了。

跑例程

在解决方案资源管理器的源文件那里右键->添加->新建项,新建一个cpp,内容为:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cmath>

void frameBufferSizeCallback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
#define SCR_WIDTH 800
#define SCR_HEIGHT 600
#define COLOR_BG 0.545098f, 0.227451f, 0.384314f, 1.f

/******** 顶点着色器 ********/
// 基本的顶点着色器
/*
* 第一行要说明OpenGL版本(3.3),运行于什么模式(核心模式)
* 顶点着色器从用户自己传入的GPU顶点数据中直接接收输入
* 所以要用layout描述你输入的是什么数据
* 这个着色器使用的数据只有一个坐标
* 所以描述为layout (location = 0) in vec3 pos;
* 即第零个属性输入是vec3类型的变量,名字就叫pos吧
* 要做的是将自己定义的这个坐标转换为OpenGL预定义的vec4类型的gl_Position
* 这样它就知道该画什么了
*/
const char* shader_source_vertex_basic =
"#version 330 core\n"
"layout (location = 0) in vec3 pos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(pos.x, pos.y, pos.z, 1.f);\n"
"}\0";
// 向片段着色器传递一个颜色的顶点着色器
/*
* 顶点着色器下一流程是片段着色器
* 可以向后传递一些数据,比如颜色
* 只要声明一个输出变量
* 然后在片段着色器中声明一个名字和类型都相同的输入变量即可
* 注意这里gl_Position = vec4()一句使用了另一种写法
*/
const char* shader_source_vertex_output_color =
"#version 330 core\n"
"layout (location = 0) in vec3 pos;\n"
"out vec4 out_color_vertex_shader;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(pos, 1.f);\n"
"   out_color_vertex_shader = vec4(pos.x+0.6, pos.y+0.3, pos.z+0.9, 1.f);\n"
"}\0";
// 接受带颜色值的顶点数据输入的顶点着色器
/*
* 上面两个顶点着色器只有一个位置输入
* 当然可以加其它的,颜色、纹理等,这里加个颜色吧
* 现在有两个属性了,所以位置变量在位置0,颜色在位置1
* 颜色要给片段着色器用,顶点着色器用不着
* 然而片段着色器并不从显存读数据
* 所以需要设置一个颜色输出从顶点着色器把颜色传过去
*/
const char* shader_source_vertex_with_color =
"#version 330 core\n"
"layout (location = 0) in vec3 in_pos;\n"
"layout (location = 1) in vec3 in_color;\n"
"out vec4 out_color_vertex_shader;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(in_pos, 1.0);\n"
"   out_color_vertex_shader = vec4(in_color,1.f);\n"
"}\0";

/******** 片段着色器 ********/
// 基本的片段着色器
/*
* 片段着色器需要输出一个颜色值
* 需要自己用out关键字声明这个输出变量
* 这里设置输出变量为vec4类型的out_color
*/
const char* shader_source_fragment_basic =
"#version 330 core\n"
"out vec4 out_color;\n"
"void main()\n"
"{\n"
"   out_color = vec4(0.13333f, 0.54509f, 0.13333f, 1.0f);\n"
"}\0";
// 接受顶点着色器的传入数据的着色器
/*
* 顶点着色器中声明一个变量作为输出后
* 再在片段着色器中声明一个名字相同且类型相同的输入
* 片段着色器中的输入就和顶点着色器中的输出链接了
*/
const char* shader_source_fragment_get_color_from_vertex_shader =
"#version 330 core\n"
"in vec4 out_color_vertex_shader;\n"
"out vec4 out_color;\n"
"void main()\n"
"{\n"
"   out_color = out_color_vertex_shader;\n"
"}\0";
// 接受CPU传递的数据的着色器
/*
* uniform变量是全局的,可以被着色器程序的任意着色器在任意阶段访问,可用于传递信息
* uniform变量必须在每个着色器程序对象中都是独一无二的
* 无论把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新
* 如果声明了一个uniform却在GLSL代码中没用过
* 最后编译出的版本中并不会包含它
*/
const char* shader_source_fragment_uniform_color =
"#version 330 core\n"
"uniform vec4 uniform_color;\n"
"out vec4 out_color;\n"
"void main()\n"
"{\n"
"   out_color = uniform_color;\n"
"}\0";

// 顶点坐标,每三个浮点数描述一个点
// 好像即使三角形在z轴上是错开的,画出来的前后顺序也只取决于画的顺序
float vertices_basic_draw[] = {
    0.f,0.6f,0.f,
    0.2f,0.2f,0.f,
    -0.2f,0.2f,0.f
};
float vertices_EBO_draw[] = {
    -0.6f, -0.6f, 1.f,
    -0.4f, -0.2f, 1.f,
    -0.2f, -0.6f, 1.f,
    0.f, -0.2f, 1.f,
    0.2f, -0.6f, 1.f,
    0.4f, -0.2f, 1.f,
    0.6f, -0.6f, 1.f,
};
float vertices_transport_color[] = {
    -0.2f, 0.2f, 0.0f,
    0.f, -0.2f, 0.0f,
    -0.4f, -0.2f, 0.0f
};
float vertices_uniform_color[] = {
    0.2f, 0.2f, -1.f,
    0.4f, -0.2f, -1.f,
    0.f, -0.2f, -1.f
};
float vertices_draw_color_from_vertex_shader[] = {
    -0.2f, 0.2f, 0.0f,  1.0f, 0.0f, 0.0f,
    0.2f, 0.2f, 0.0f,  0.0f, 1.0f, 0.0f,
    0.f, -0.2f, 0.0f,  0.0f, 0.0f, 1.0f
};
// 顶点索引(从0开始),每三个整数描述一个三角形
unsigned int indices[] = {
    0, 1, 2,
    1, 2, 3,
    2, 3, 4,
    3, 4, 5,
    4, 5, 6,
};

int main()
{
    /******** 配置GLFW ********/
    /*
    * 初始化GLFW
    * 主版本号为3,参数为选项名称+选项值
    * 副版本号为3,参数为选项名称+选项值
    * 配置模式为核心模式,参数为选项名称+选项值
    */
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    /******** 搞一个窗口 ********/
    // 创建窗口对象
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    // 将当前窗口的上下文设置为当前线程的主上下文
    glfwMakeContextCurrent(window);
    // 注册窗口大小变换回调函数
    /*
    * 窗口大小发生改变时将自动调用注册的函数
    * 具体改变后要干啥需自己实现,基本上就是重新设置一下窗口大小的事
    * 前两个参数是左下角点,绘制时以此点为(-1,-1)基准
    * 如果左下角点是(0,0)的话,主函数中不需要调用glViewport(),写在回调里即可
    * 否则主函数里需要显式写一次
    * 不然在回调函数调用前(窗口大小不变时)的绘制会以(0,0)为左下角点
    */
    glfwSetFramebufferSizeCallback(window, frameBufferSizeCallback);

    /******** 初始化GLAD准备好OpenGL函数 ********/
    /*
    * about glfwGetProcAddress and GLFWglproc
    * 
    * The signature of glfwGetProcAddress is:
    * GLFWglproc glfwGetProcAddress(const char *);
    * It takes a string and returns a GLFWglproc which is defined as:
    * typedef void(* GLFWglproc) (void);
    * It is a function with no parameters and no return value.
    * 
    * about GLADloadproc
    * 
    * GLADloadproc is a type alias defined as follows:
    * typedef void* (* GLADloadproc)(const char *name);
    * It takes a string and returns a void pointer.
    * (this is a type of pointer that can be safely converted to from every other type of pointer)
    * 
    * about gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)
    * 
    * In the expression (GLADloadproc)glfwGetProcAddress,
    * there is a type cast, because:
    * gladLoadGLLoader takes a GLADloadproc,
    * but we want to give it a GLFWglproc (*)(const char *).
    * 
    * 总之,我们需要运行glfwGetProcAddress()以获得OpenGL相关函数地址,
    * 方法是把这个函数传给gladLoadGLLoader()由GLAD处理,
    * 但是GLAD并未提供GLFW相关的的形式参数
    * 所以需要自己做一次强制类型转换,
    * 把glfwGetProcAddress转成GLAD的函数形式GLADloadproc
    * 之所以能转换,是因为二者都接受一个字符串,返回一个指针
    * 并且前者返回的还是虚指针,能强制转。
    */
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "初始化GLAD失败!" << std::endl;
        return -1;
    }

    // 查询顶点着色器中能声明的顶点属性上限
    int nrAttributes;
    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
    std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

    // 下一步用来检测各种编译是否成功,整型用于传回是否成功,字符串记录日志
    int success;
    char infoLog[512];

    /******** 编译顶点着色器 ********/
    // 编译shader_source_vertex_basic
    /*
    * 整个过程分为三步、三行
    * 1. 创建顶点着色器对象,将来通过整型调用它
    * 2. 将着色器源码附加到着色器对象上,参数分别为刚创建的对象,源码字符串数量,源码
    * 3. 编译它
    * 完事后可以检查是否编译成功
    */
    unsigned int shader_vertex_basic = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(shader_vertex_basic, 1, &shader_source_vertex_basic, NULL);
    glCompileShader(shader_vertex_basic);
    glGetShaderiv(shader_vertex_basic, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(shader_vertex_basic, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // 编译shader_source_vertex_output_color
    unsigned int shader_vertex_output_color = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(shader_vertex_output_color, 1, &shader_source_vertex_output_color, NULL);
    glCompileShader(shader_vertex_output_color);
    // 编译shader_source_vertex_with_color
    unsigned int shader_vertex_with_color = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(shader_vertex_with_color, 1, &shader_source_vertex_with_color, NULL);
    glCompileShader(shader_vertex_with_color);

    /******** 编译片段着色器 ********/
    // 编译shader_source_fragment_basic
    unsigned int shader_fragment_basic = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(shader_fragment_basic, 1, &shader_source_fragment_basic, NULL);
    glCompileShader(shader_fragment_basic);
    glGetShaderiv(shader_fragment_basic, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(shader_fragment_basic, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // 编译shader_source_fragment_get_color_from_vertex_shader
    unsigned int shader_fragment_get_color_from_vertex_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(shader_fragment_get_color_from_vertex_shader, 1, &shader_source_fragment_get_color_from_vertex_shader, NULL);
    glCompileShader(shader_fragment_get_color_from_vertex_shader);
    // 编译shader_source_fragment_uniform_color
    unsigned int shader_fragment_uniform_color = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(shader_fragment_uniform_color, 1, &shader_source_fragment_uniform_color, NULL);
    glCompileShader(shader_fragment_uniform_color);

    /******** 把两个着色器对象链接成为着色器程序 ********/
    // 链接shader_program_basic
    unsigned int shader_program_basic = glCreateProgram();
    glAttachShader(shader_program_basic, shader_vertex_basic);
    glAttachShader(shader_program_basic, shader_fragment_basic);
    glLinkProgram(shader_program_basic);
    glGetProgramiv(shader_program_basic, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shader_program_basic, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    // 链接shader_program_get_color_from_vertex_shader
    unsigned int shader_program_get_color_from_vertex_shader = glCreateProgram();
    glAttachShader(shader_program_get_color_from_vertex_shader, shader_vertex_output_color);
    glAttachShader(shader_program_get_color_from_vertex_shader, shader_fragment_get_color_from_vertex_shader);
    glLinkProgram(shader_program_get_color_from_vertex_shader);
    // 链接shader_program_use_uniform
    unsigned int shader_program_use_uniform = glCreateProgram();
    glAttachShader(shader_program_use_uniform, shader_vertex_basic);
    glAttachShader(shader_program_use_uniform, shader_fragment_uniform_color);
    glLinkProgram(shader_program_use_uniform);
    // 链接shader_program_vertex_with_color
    unsigned int shader_program_vertex_with_color = glCreateProgram();
    glAttachShader(shader_program_vertex_with_color, shader_vertex_with_color);
    glAttachShader(shader_program_vertex_with_color, shader_fragment_get_color_from_vertex_shader);
    glLinkProgram(shader_program_vertex_with_color);

    // 完事后着色器对象就没用了可以删了
    glDeleteShader(shader_vertex_basic);
    glDeleteShader(shader_vertex_output_color);
    glDeleteShader(shader_vertex_with_color);
    glDeleteShader(shader_fragment_basic);
    glDeleteShader(shader_fragment_get_color_from_vertex_shader);
    glDeleteShader(shader_fragment_uniform_color);

    /******** 配置VBO、VAO和EBO ********/
    // 绘制基本三角形的VBO和VAO
    /*
    * 首先glBindBuffer生成一个缓冲对象
    * 大多数OpenGL对象必须绑定到OpenGL上下文中称为“目标”的位置才能使用
    * 将一个对象绑定到一个目标意味着您希望以目标使用绑定到它的对象的方式使用该对象
    * 我们生成这个对象是为了管理顶点属性数据
    * GL_ARRAY_BUFFER目标表示将该缓冲区对象用于顶点属性数据的意图
    * 所以下一步要将生成的对象绑定到GL_ARRAY_BUFFER以告诉显卡这个缓冲对象是用来做什么的
    * 接下来把用户定义数据复制到当前绑定缓冲
    * 参数:目标缓冲类型,传输数据的字节数,数据,数据变化情况
    * 三种数据变化情况:
    * GL_STATIC_DRAW :数据不会或几乎不会改变。
    * GL_DYNAMIC_DRAW:数据会被改变很多。
    * GL_STREAM_DRAW :数据每次绘制时都会改变。
    */
    unsigned int VBO_basic;
    glGenBuffers(1, &VBO_basic);
    glBindBuffer(GL_ARRAY_BUFFER, VBO_basic);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_basic_draw), vertices_basic_draw, GL_STATIC_DRAW);
    /*
    * 生成了缓冲对象后还需要描述这里面的数据是如何组织的,也就是配置VAO
    * 一个顶点数组对象VAO会储存以下这些内容:
    * 1.glEnableVertexAttribArray和glDisableVertexAttribArray的调用;
    * 2.通过glVertexAttribPointer设置的顶点属性配置,每个对应一个VBO的处理方式;
    * 3.通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。
    * 生成比较类似VBO,绑定用的glBindVertexArray就不太一样了,只有一个参数
    * 1.当传递的参数是非0且是glGenVertexArray()返回的未绑定的名字,此时会将传递的参数名字绑定到新创建的顶点数组对象
    * ==>>这里大概同时绑定到了上下文,之后所有有关VAO的操作都是在这个VAO<<==
    * ==>>所以有操作涉及VAO时记得绑定一下当前要用的VAO<<==
    * 2.当传递的参数是已经被绑定过的名字,则此时的作用是激活顶点数组对象,后续的相关操作将作用到该顶点数组对象
    * 3.当传递的参数是0,则此时是解除先前的绑定
    * 接下来配置一下它,告诉它我们的数据是如何组织的
    * glVertexAttribPointer的参数为:
    * 1.index:第几个(从0数)属性(属性是啥需要自己记住),这里只有一个位置属性,以后可能有法线、纹理等等其他属性
    * 2.size:数据的个数,这里是xyz坐标共3个
    * 3.type:数据的类型
    * 4.normalized:归一化是否由显卡完成
    * 5.stride:一个顶点占的总的字节数
    * 6.pointer:当前指针指向的属性在顶点数据内部的偏离字节数,用于找到非第0个属性的起始位置
    * glVertexAttribPointer只是建立CPU和GPU之间的逻辑连接
    * 最后需要用glEnableVertexAttribArray允许顶点着色器读取GPU数据
    */
    unsigned int VAO_basic;
    glGenVertexArrays(1, &VAO_basic);
    glBindVertexArray(VAO_basic);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 带有EBO的VBO和VAO
    /*
    * 上面的写法是把VAO和VBO的配置分开了为了方便写注释,也能正常跑
    * 但是更规范的顺序是先处理VAO把它绑定到上下文
    * 再处理VBO和EBO
    * (顶点索引EBO(又叫索引缓冲对象IBO)其实是属于VAO的一部分
    * 但是是一个缓冲对象,生成比较像VBO,只不过绑定的地方不一样)
    * 再回过头配置VAO来描述VBO的数据(EBO就是个索引不用描述)
    */
    unsigned int VAO_draw_with_EBO, VBO_draw_with_EBO, EBO;
    glGenVertexArrays(1, &VAO_draw_with_EBO);
    glBindVertexArray(VAO_draw_with_EBO);
    glGenBuffers(1, &VBO_draw_with_EBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO_draw_with_EBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_EBO_draw), vertices_EBO_draw, GL_STATIC_DRAW);
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 其它三个VAO、VBO
    /*
    * 多个VAO、VBO(以及EBO)可以搞成数组来管理
    * 当然配置的时候还是要按上面的顺序
    * 注意第三对的属性多了颜色
    * 于是步长都变成了6,因为一个点现在由6个float描述
    * 先照常声明位置属性,再声明颜色属性
    * 这样就是告诉GPU我这些数据6个一组
    * 每组前三个是位置0,后三个是位置1,我都有用
    * 然后分别打开这两组的开关
    */
    unsigned int VBOs[3], VAOs[3];
    glGenVertexArrays(3, VAOs);
    glGenBuffers(3, VBOs);
    // 第一对VAO、VBO的设置(从顶点着色器向片段着色器传递颜色值)
    glBindVertexArray(VAOs[0]);
    glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_transport_color), vertices_transport_color, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 第二对VAO、VBO的设置(使用uniform颜色值)
    glBindVertexArray(VAOs[1]);
    glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_uniform_color), vertices_uniform_color, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 第三对VAO、VBO的设置(顶点数据带颜色输入的)
    glBindVertexArray(VAOs[2]);
    glBindBuffer(GL_ARRAY_BUFFER, VBOs[2]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_draw_color_from_vertex_shader), vertices_draw_color_from_vertex_shader, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    /******** 一些可选的代码 ********/
    /*
    * 解除绑定VBO,因为现在VBO和VAO都绑定到GL_ARRAY_BUFFER上了
    * 于是可以解绑VBO,调用VAO的时候会自动找到对应的VBO
    * 类似地可以使用glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);解绑EBO
    * 但是EBO是VAO的一部分与VAO互相绑定,不用也不能单独解绑
    * 这里解绑的话调用对应VAO的时候就没有这个EBO了
    * 同理弄完各种东西之后可以把当前VAO从上下文解绑,用的时候再绑回来
    */
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    /******* 渲染循环 ********/
    while (!glfwWindowShouldClose(window))
    {
        // 需自己实现的输入监听函数
        processInput(window);
        // 状态设置函数:设置清屏所使用的颜色
        glClearColor(COLOR_BG);
        // 状态使用函数:使用此颜色清空颜色缓冲
        glClear(GL_COLOR_BUFFER_BIT);

        /******** 画三角形! ********/
        // 基本三角形绘制
        /*
        * 先选择着色器程序
        * 再把要用的VAO绑定到上下文,同时其绑定的VBO、EBO等也就自动绑定了
        * 然后绘制
        * 没有EBO时用glDrawArrays,参数为打算画什么,第一个顶点的位置,打算画几个*每个的点数
        * 有EBO时用glDrawElements,参数为画什么,画几个*每个的点数
        */
        glUseProgram(shader_program_basic);
        glBindVertexArray(VAO_basic);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        // 切换只画线模式
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        // 带EBO的一堆三角形的绘制
        glUseProgram(shader_program_basic);
        glBindVertexArray(VAO_draw_with_EBO);
        glDrawElements(GL_TRIANGLES, 15, GL_UNSIGNED_INT, 0);
        // 改回涂色模式
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        // 顶点属性带颜色的三角形的绘制
        glUseProgram(shader_program_vertex_with_color);
        glBindVertexArray(VAOs[2]);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        // 从顶点着色器接受颜色输入的三角形的绘制
        glUseProgram(shader_program_get_color_from_vertex_shader);
        glBindVertexArray(VAOs[0]);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        // 从uniform获得颜色输入的三角形的绘制
        /*
        * 比普通的多了一行
        * 需要先用关键字查到对应变量的位置
        * 再替换对应位置的数据
        */
        glUseProgram(shader_program_use_uniform);
        glUniform4f(glGetUniformLocation(shader_program_use_uniform, "uniform_color"), 0.f, 0.f, sin(glfwGetTime()) / 2.0f + 0.5f, 1.0f);
        glBindVertexArray(VAOs[1]);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        // glfwPollEvents()检查有没有触发什么事件(比如键盘输入、鼠标移动等)
        // 如果有则更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)
        glfwPollEvents();
        // glfwSwapBuffers()交换颜色缓冲区
        // 绘制图像在后缓冲中绘制,完事后交换至前缓冲,避免扫描感
        glfwSwapBuffers(window);
    }

    // 释放资源
    glDeleteVertexArrays(1, &VAO_basic);
    glDeleteVertexArrays(1, &VAO_draw_with_EBO);
    glDeleteVertexArrays(3, VAOs);
    glDeleteBuffers(1, &VBO_basic);
    glDeleteBuffers(1, &VBO_draw_with_EBO);
    glDeleteBuffers(3, VBOs);
    glDeleteBuffers(1, &EBO);
    glDeleteProgram(shader_program_basic);
    glDeleteProgram(shader_program_vertex_with_color);
    glDeleteProgram(shader_program_get_color_from_vertex_shader);
    glDeleteProgram(shader_program_use_uniform);
    glfwTerminate();
    return 0;
}

// 窗口大小变换监听,参数为窗口、变化后的尺寸
void frameBufferSizeCallback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}
// 输入监听
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

这是应用了LearnOpenGL前几节知识写的一段程序,运行无误的话可以看到一个花里胡哨的三角形,中间层最右侧的小三角形还会变色。

运行结果图

更新

这个环境正常使用一段时间后有天突然报如下错误:

1>glfw3.lib(init.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(init.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(window.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(window.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(input.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(input.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(context.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(context.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(win32_init.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(win32_init.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(win32_monitor.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(win32_monitor.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(win32_time.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(win32_time.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(win32_thread.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(win32_thread.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(monitor.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(monitor.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(vulkan.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(vulkan.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(win32_window.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(win32_window.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(win32_joystick.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(win32_joystick.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(wgl_context.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(wgl_context.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(egl_context.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(egl_context.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样
1>glfw3.lib(osmesa_context.c.obj) : warning LNK4099: 未找到 PDB“glfw.pdb”(使用“glfw3.lib(osmesa_context.c.obj)”或在“项目路径\x64\Debug\glfw.pdb”中寻找);正在链接对象,如同没有调试信息一样

运行项目只生成白色窗口,鼠标在窗口内就转圈圈。看了下报错他似乎想找一个什么pdb的玩意找不到,翻了一下发现在C:\Program Files\glfw-3.3.8\out\build\x64-Debug\src\CMakeFiles\glfw.dir似乎有他需要的文件,于是把这个文件夹下所有文件复制一份到C:\Program Files\glfw-3.3.8\out\build\x64-Debug\src,重启,问题解决(重启前还是不行)。

配置GLM

官网下载zip包,解压后应该里面只有一个glm文件夹(这个glm文件夹里面还有一个glm文件夹,需要的是父文件夹)。打开VS,右击项目名->属性->C/C++->常规->附加包含目录,把这个文件夹加进去即可。

Leave a Reply

Your email address will not be published. Required fields are marked *