C++预备知识
打印有两种方式
std是一个命名空间(namespace),‘::’是作用域运算符,cout是std空间中的一个函数名
- 方式一:每次使用时对cout说明:
std::cout << "Input two numbers:";
- 方式二:在主函数前说明一下,后面就可以直接使用cout:
using namespace std;
cout << "Input two numbers:";
函数定义
c++编译时是“顺序编译”,在main函数后面定义的函数会出现找不到标识符的错误,所以要么在main函数之前先定义一下,编写函数的实际内容时就可以放在main之后了。
void processInput(GLFWwindow* window);
int main() {
}
void processInput(GLFWwindow* window) {
}
要么直接把详细定义整个移到main函数之前。
void processInput(GLFWwindow* window) {
}
int main() {
}
创建窗口
GLFW头文件
#include <glad/glad.h>
#include <GLFW/glfw3.h>
main函数
我们将主版本号(Major)和次版本号(Minor)都设为3。我们同样明确告诉GLFW我们使用的是核心模式(Core-profile)。
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本号(Major)
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本号(Minor)都设为3
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //明确告诉GLFW我们使用的是核心模式(Core-profile) GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
return 0;
}
创建一个窗口对象
窗口的宽和高作为它的前两个参数。第三个参数表示这个窗口的名称(标题),创建完窗口我们就可以通知GLFW将我们窗口的上下文设置为当前线程的主上下文了。
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
GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLAD。我们给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数。GLFW给我们的是glfwGetProcAddress,它根据我们编译的系统定义了正确的函数。
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
视口
我们开始渲染之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小,即视口(Viewport),这样OpenGL才只能知道怎样根据窗口大小显示数据和坐标。我们可以通过调用glViewport函数来设置窗口的维度(Dimension):
函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)
glViewport(0, 0, 800, 600);
用户改变窗口的大小的时候,视口也应该被调整。我们可以对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用。
这个帧缓冲大小函数需要一个GLFWwindow作为它的第一个参数,以及两个整数表示窗口的新维度。每当窗口改变大小,GLFW会调用这个函数并填充相应的参数供你处理
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
我们还需要在main函数中注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数:
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
渲染循环(Render Loop)
我们需要在程序中添加一个while循环,我们可以把它称之为渲染循环(Render Loop),它能在我们让GLFW退出前一直保持运行。
while(!glfwWindowShouldClose(window))
{
glfwSwapBuffers(window);
glfwPollEvents();
}
- glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话该函数返回true然后渲染循环便结束了,之后就为我们关闭应用程序了。
- glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
- glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
释放资源
渲染循环结束后我们需要正确释放/删除之前的分配的所有资源。我们可以在main函数的最后调用glfwTerminate函数来完成
glfwTerminate();
return 0;
输入
我们检查用户是否按下了返回键(Esc)(如果没有按下,glfwGetKey将会返回GLFW_RELEASE。如果用户的确按下了返回键,我们将通过glfwSetwindowShouldClose使用把WindowShouldClose属性设置为 true的方法关闭GLFW。
void processInput(GLFWwindow *window)
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
在渲染循环(render loop)的每一个迭代中调用processInput:
while (!glfwWindowShouldClose(window))
{
processInput(window);
glfwSwapBuffers(window);
glfwPollEvents();
}
渲染
我们把所有的渲染(Rendering)操作放到渲染循环中,因为我们想让这些渲染指令在每次渲染循环迭代的时候都能被执行
// 渲染循环
while(!glfwWindowShouldClose(window))
{
// 输入
processInput(window);
// 渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 检查并调用事件,交换缓冲
glfwPollEvents();
glfwSwapBuffers(window);
}
glClearColor来设置清空屏幕所用的颜色。当调用glClear函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色。
调用glClear函数来清空屏幕的颜色缓冲,它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲。
最后效果
完整代码:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void franmebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本号(Major)
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本号(Minor)都设为3
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //明确告诉GLFW我们使用的是核心模式(Core-profile)
//宽和高作为它的前两个参数,第三个参数表示这个窗口的名称
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create glfw window" << std::endl;
glfwTerminate();
return -1;
}
//通知GLFW将我们窗口的上下文设置为当前线程的主上下文
glfwMakeContextCurrent(window);
//注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数
glfwSetFramebufferSizeCallback(window, franmebuffer_size_callback);
//调用任何OpenGL的函数之前我们需要初始化GLAD
//给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
//OpenGL渲染窗口的尺寸大小,即视口(Viewport)
//两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度
glViewport(0, 0, 80, 600);
//渲染循环(Render Loop),它能在我们让GLFW退出前一直保持运行
while (!glfwWindowShouldClose(window)) {
//输入关闭函数
processInput(window);
//渲染指令
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
//函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数
glfwPollEvents();
}
//正确释放/删除之前的分配的所有资源
glfwTerminate();
return 0;
}
//当用户改变窗口的大小的时候,视口也应该被调整。我们可以对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用
void franmebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
//按下了返回键(Esc),程序将会关闭
void processInput(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
双缓冲(Double Buffer)
应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。
这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。