提示:各种渲染效果的切换需要手动在片段着色器中切换FragColor的赋值函数
目录
简介
本OpenGL程序旨在通过OpenGL从底层出发,准许现实世界的真实物理表现,通过OpenGL实现一个基于物理的初级渲染管线。我们可以在OpenGL中自由的创建出我们想要的Model,然后也可以自由的为其添加材质贴图,最后我们通过着色器将我们的模型与材质数据输入按照一定的物理法则进行处理,进而实现在计算机中模拟出各种各样的画面表现。
效果图
玻璃折射
金属反射
标准材质
阴影渲染
一、开发流程
1.环境配置
为了更好的编写OpenGL,我们使用的许多功能完善的OpenGL的库,GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户创建OpenGL上下文、定义窗口参数以及处理用户输入,对我们来说这就够了。
我们在VS的NuGet包中安装各种我们所需要的库,这样比传统的计算机环境配置更加便捷
配置GLAD,GLAD是一个开源的库,它能解决我们上面提到的那个繁琐的问题。GLAD的配置与大多数的开源库有些许的不同,GLAD使用了一个在线服务。在这里我们能够告诉GLAD需要定义的OpenGL版本,并且根据这个版本加载所有相关的OpenGL函数。
GLAD现在应该提供给你了一个zip压缩文件,包含两个头文件目录,和一个glad.c文件。将两个头文件目录(glad和KHR)复制到你的Include文件夹中(或者增加一个额外的项目指向这些目录),并添加glad.c文件到你的工程中。
配置 Stb_image,为了将外部的纹理材质加载到OpenGL中来,我们使用了较为新颖的stb,为了能正常使用stb,我们将其作为头文件导入到项目中来,使用一下的的代码将其导入到cpp中
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
其余的OpenGL库的导入较为简单,我们只需要在NuGet包管理中将其导入即可,不需要额外的单独配置。
2.框架搭建
首先是创建一个可运行的空程序,我们第一步就是创建一个简单的窗口,然后整个程序进行初始化使得这个Opengl可以正常的运行。这里包含了我们项目最终所需要的所有的头文件以及包。
#include <glad/glad.h>
#include <GLFW/glfw3.h>#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>#include "Shader.h"
#include "camera.h"
#include "model.h"#include <iostream>
初始化程序并搭建窗口
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
return 0;
}
首先,我们在main函数中调用glfwInit函数来初始化GLFW,然后我们可以使用glfwWindowHint函数来配置GLFW。glfwWindowHint函数就是表明了我们所使用的的OpenGL的版本。
随后我们生成一个窗口对象,这个对象会被其他函数调用到。同时我们也会对这个生成的窗口进行检测来判断窗口是否成功的创建。
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
我们也要检测Glad这个库是否被成功的导入到项目中来,所以我们需要检测一下。
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
在我们开始渲染图形前,我们还要告诉OpenGL的渲染窗口尺寸的大小,即视口的大小。
glViewport(0, 0, 800, 600);
glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)。
我们实际上也可以将视口的维度设置为比GLFW的维度小,这样子之后所有的OpenGL渲染将会在一个更小的窗口中显示,这样子的话我们也可以将一些其它元素显示在OpenGL视口之外。
然而,当用户改变窗口的大小的时候,视口也应该被调整。我们可以对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用。这个回调函数的原型如下:
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
这个帧缓冲大小函数需要一个GLFWwindow作为它的第一个参数,以及两个整数表示窗口的新维度。每当窗口改变大小,GLFW会调用这个函数并填充相应的参数供你处理。
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
我们还需要注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数:
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
我们最后要设置渲染循环,我们在进行退出之前OpenGL每时每刻的在实时更新他的渲染结果。当我们退出时,此时OpenGL停止渲染。我们可以通过一个简单的循环来实现这样的效果。
while(!glfwWindowShouldClose(window))
{
glfwSwapBuffers(window);
glfwPollEvents();
}
- glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true然后渲染循环便结束了,之后为我们就可以关闭应用程序了。
- glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
- glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
最后,我们要在渲染结束后,清除之前所有的资源
glfwTerminate();
return 0;
最终的项目框架:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
// 设置好生成的窗口的大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
int main()
{
// glfw: GLFW的初始化
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
// 常见一个窗口
// --------------------
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);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// 检查Glad是否正确加载
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 渲染的循环
// -----------
while (!glfwWindowShouldClose(window))
{
// 键鼠输入
// -----
processInput(window);
// 渲染
// ------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// glfw: 我们需要交换缓存,这里使用了双缓存技术
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
// glfw: 清除所有的资源
// ------------------------------------------------------------------
glfwTerminate();
return 0;
}
// 键盘输入函数
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
// glfw: 实时将视口的长宽设置为窗口的长宽
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
二、基础功能
1.图形绘制
在OpenGL中,图形的绘制首先要获取图形的数据,这包括顶点的位置,法相,UV等等,有了这些的顶点数据我们就能知道图形最基本的显示效果。下一步就是如何创建顶点信息并将其储存在OpenGL的缓存中,并告诉OpenGL如何使用这些顶点数据。
- 顶点数组对象:Vertex Array Object,VAO
- 顶点缓冲对象:Vertex Buffer Object,VBO
- 元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO
在OpenGL中ÿ