目录
1. 使用OpenGL(3.3及以上)+GLFW或freeglut画一个简单的三角形。
实现效果
实现思路
根据课程提供的教程网站: https://learnopengl-cn.github.io/ 中的过程,安装cmake,构建GLFW,GLEW等环境,在VS的项目属性中添加链接,成功配置好环境后按照网站中的流程进行代码实现:
-
实例化GLFW窗口,设置窗口的维度,调整窗口大小,创建GLFWwindow窗口对象window;
const GLuint WIDTH = 800, HEIGHT = 600;
glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr); glfwMakeContextCurrent(window); glfwSetKeyCallback(window, key_callback);
-
编译顶点着色器,编译片段着色器,把多个着色器合并之后最终链接成着色器程序;
// 着色器通过着色器语言GLSL编写 // fragmentShaderSource :color = vec4(1.0f, 0.5f, 0.5f, 1.0f) // 编译顶点着色器 // 用glCreateShader创建这个着色器 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); // vertexShaderSource是着色器代码的字符串 glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader); GLint success; GLchar infoLog[512]; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; } // 编译片段着色器 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader); glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; }
-
输入顶点数据,通过顶点缓冲对象VBO管理,使用glBindBuffer绑定;链接顶点属性,创建顶点数组对象VAO,使用glBindVertexArray绑定VAO;
// 顶点输入 GLfloat vertices[] = { -0.5f, -0.5f, 0.0f, // Left 0.5f, -0.5f, 0.0f, // Right 0.0f, 0.5f, 0.0f // Top }; GLuint VBO, VAO; glGenVertexArrays(1, &VAO); // 生成顶点缓冲对象(Vertex Buffer Objects, VBO)的id,储存在VBO变量 glGenBuffers(1, &VBO); // ..:: 初始化代码 :: .. // 1. 绑定顶点数组对象 glBindVertexArray(VAO); // 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用 // 顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER glBindBuffer(GL_ARRAY_BUFFER, VBO); // glBufferData函数会把之前定义的顶点数据复制到缓冲的内存中 // 第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。 // 第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。 // 第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。 // 下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。 // 第四个参数指定了我们希望显卡如何管理给定的数据,GL_STATIC_DRAW代表不会或很少改变 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 3. 设置顶点属性指针 // glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据,应用到逐个顶点 // 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。 // 最后一个参数的类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0);
-
添加渲染循环,glDrawArrays函数使用当前激活的着色器,之前定义的顶点属性配置,和VBO的顶点数据(通过VAO间接绑定)绘制图元。
while (!glfwWindowShouldClose(window)) { glfwPollEvents(); // 清空颜色缓存后整个界面会被设为这个清空颜色(背景) glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // ..:: 绘制代码(渲染循环中) :: .. // 4. 绘制物体 glUseProgram(shaderProgram); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); glBindVertexArray(0); glfwSwapBuffers(window); }
2. 对三角形的三个顶点分别改为红绿蓝。并解释为什么会出现这样的结果。
实现效果
实现思路(在1的基础上)
以类似添加顶点指针的方式添加颜色指针,不再使用uniform来传递片段的颜色,现而使用ourColor输出变量。
GLfloat vertices[] = {
// 位置 颜色
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // 右
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左
0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f // 上
};
glVertexAttribPointer函数的前几个参数比较明了。
由于我们现在有了两个顶点属性,我们不得不重新计算步长值。为获得数据队列中下一个属性值(比如位置向量的下个x分量)我们必须向右移动6个float,其中3个是位置值,另外3个是颜色值。这使我们的步长值为6乘以float的字节数(=24字节)。
同样,这次我们必须指定一个偏移量。对于每个顶点来说,位置顶点属性在前,所以它的偏移量是0。颜色属性紧随位置数据之后,所以偏移量就是3 * sizeof(float),用字节来计算就是12字节。
// fragmentShaderSource : FragColor = vec4(ourColor, 1.0f);
// 3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 颜色指针
// 对于每个顶点来说,位置顶点属性在前,所以它的偏移量是0。颜色属性紧随位置数据之后,所以偏移量就是3 * sizeof(float),用字节来计算就是12字节。
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
解释效果(根据官网的说法)
Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
这是在片段着色器中进行的所谓片段插值(Fragment Interpolation)的结果。当渲染一个三角形时,光栅化(Rasterization)阶段通常会造成比原指定顶点更多的片段。光栅会根据每个片段在三角形形状上所处相对位置决定这些片段的位置。
基于这些位置,它会插值(Interpolate)所有片段着色器的输入变量。
比如说,我们有一个线段,上面的端点是绿色的,下面的端点是蓝色的。如果一个片段着色器在线段的70%的位置运行,它的颜色输入属性就会是一个绿色和蓝色的线性结合;更精确地说就是30%蓝 + 70%绿。
3. 给上述工作添加一个GUI,里面有一个菜单栏,使得可以选择并改变三角形的颜色。
实现效果
实现思路
根据https://github.com/ocornut/imgui,导入imgui的头文件,选择相应的版本
在原先的基础上添加imgui窗口参数的配置,添加颜色选择选项,对三角形进行与之前类似的渲染。
float vertices[] = {
0.5f, -0.5f, 0.0f, right_color.x, right_color.y, right_color.z, // bottom right
-0.5f, -0.5f, 0.0f, left_color.x, left_color.y, left_color.z, // bottom left
0.0f, 0.5f, 0.0f, top_color.x, top_color.y, top_color.z, // top
};
// 。。。
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGui::Begin("Edit color", &ImGui, ImGuiWindowFlags_MenuBar);
ImGui::ColorEdit3("top", (float*)&top_color);
ImGui::ColorEdit3("left", (float*)&left_color);
ImGui::ColorEdit3("right", (float*)&right_color);
ImGui::End();