本文章暂不介绍GLFW以及GL_GLAD的配置方法。学习赵新政
初识openGL
#include<iostream>
#include "glad/glad.h"
#include <GLFW/glfw3.h>
//以上是配置好的glad以及glfw 需注意glad需要在glfw上面
接下来看看GLFW官网提供的 Example Code
#include <GLFW/glfw3.h>
int main(void)
{
//创建窗体对象
GLFWwindow* window;
//初始化GLFW
if (!glfwInit())
return -1;
//设置窗体的长和宽 ,以及窗体名字
window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
if (!window)
{
//如果窗体创建失败则进行清理
glfwTerminate();
return -1;
}
//设置window这个窗体为“绘制舞台”
glfwMakeContextCurrent(window);
//执行窗体循环
while (!glfwWindowShouldClose(window))
{
/* Render here */
//清理窗口
glClear(GL_COLOR_BUFFER_BIT);
//切换双缓存
glfwSwapBuffers(window);
//接受并分发窗口消息
glfwPollEvents();
}
//清理
glfwTerminate();
return 0;
}
在我们使用的时候,会有些许不同,比如初始化我们可以这样写
glfwInit();//初始化GLFW
//设置GLFW的主版本号和次版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
//启动核心模式
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//使用glad加载所有opengl函数
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD\n";
return -1;
}
在原来的基础上加上这些即可
事件响应函数
还记得上一节中窗体循环中的 glfwPollEvents();么。
它就是用来接受并分发窗体信息,检查消息队列是否有需要处理的鼠标,键盘等信息,如果有的话就将消息批量处理清空队列。
//我们需要用到的是这两个函数 需要在窗体循环前设置
//设置监听 事件响应
glfwSetFramebufferSizeCallback(window, frameBufferSizeCallBack);
glfwSetKeyCallback(window, keyCallBack);
//其中第一个参数是窗体对象,第二个参数是函数指针
让我们进入定义,看看我们需要提供什么样的回调函数
这些参数比较简单,除了窗体对象。我们还需要宽 和 高
这些有点难懂,简单了解一下。key:字母按键码,scancode:物理按键码(不重要),action:按下还是抬起,mods:是否有Shift 或者 Ctrl
接下来我们就可以自己写回调函数啦,以下仅供参考。你们可以写自己的
void frameBufferSizeCallBack(GLFWwindow* window, int width, int height)
{
std::cout << "窗体最新大小:" << width << "," << height << std::endl;
}
void keyCallBack(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_W); //是否按下w
if (action == GLFW_PRESS);// 按压行为
if (action == GLFW_RELEASE);//松开行为
if (mods == GLFW_MOD_CONTROL);//control模式
std::cout << "按下了:" << key << std::endl;
std::cout << "action:" << action << std::endl;
std::cout << "mods" << mods << std::endl;
}
效果so COOL
函数初体验
OpenGL的运行环境是一个大的状态机,每个函数都会改变状态机的状态或者使其执行某个行为
glViewport(GLint x, GLint y,GLsizei width, GLsizei height);
设置窗口中OpenGL负责渲染的区域,即视口Viewport
x,y:表示相对窗口左下角的起始位置. width,height 表示渲染区域的长度,高度
glClearColor(GLfloat red,GLfloat green, GLfloat blue, GLfloat alpha);
设置画布清理的颜色
glClear(GL_COLOR_BUFFER_BIT);
执行画布清理工作
双缓冲:DoubleBuffer
如果直接在一张画板上绘制每一帧,就会“露馅”
双缓冲技术,在每一帧绘制任务完成后,需要把“幕后”的画布放到台前,把台前的撤到“幕后”
void glfwSwapBuffers(GLFWwindow* window);
//设置opengl视口以及清理颜色
glViewport(0, 0, 400, 600);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
//窗体循环中 切换双缓存
glfwSwapBuffers(window);
错误检查与封装
如果我们不小心写错了某个参数会怎么样?比如glClear(-1)
opengl 不会因为参数错误轻易崩溃,但错误也更难查找!OpenGL提供了错误检查函数
GLenum glGetError(); 用于检查之前的调用是否发生了问题,如果有问题则返回最近一个问题的错误码
所以我们可以根据这一特性,将其封装成CheckError文件
// 这里是头文件
#pragma once
#include<iostream>
#include <glad/glad.h>
#include<string>
#include<assert.h>
#define DEBUG
//预编译宏定义
#ifdef DEBUG
#define GL_CALL(func) func;checkError();
#else
#define GL_CALL(func) func;
#endif
void checkError();
这里的define DEBUG是为了方便是否启用检查,否则在发布时我们还得一个个删除GL_CALL
// checkError.cpp
#include "checkError.h"
void checkError() {
GLenum errorCode = glGetError();
std::string error = "";
if (errorCode != GL_NO_ERROR) {
switch (errorCode)
{
case GL_INVALID_ENUM: error = "INVALID_ENUM"; break;
case GL_INVALID_VALUE: error = "INVALID_VALUE"; break;
case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break;
case GL_OUT_OF_MEMORY: error = "OUT OF MEMORY"; break;
default:
error = "UNKNOWN";
break;
}
std::cout << error << "\n";
//assert会根据传入的bool值来决定程序是否停止
assert(false);
}
}
这里列举了常见的四种错误,ENUM,VALUE,OPERATION,MEMORY. 封装好之后我们就可以给每条语句套上,例如GL_CALL(glClear(-1) );
Application类设计
OpenGL的绘制代码会很长,所以需要做必要的封装,让代码优雅整洁
所以我们需要封装一个Application类,将窗体相关代码锁在里面,并且暴露必要的接口。
思路:3D程序都是帧循环驱动,每个对象都应该先初始化(initial),之后的每一帧更新数据或处理事件(update),退出前打扫战场(destroy)。
单例类
这里简单介绍一下单例模式。
单例模式是一种设计模式,用于确保类只有一个实例,并提供一个全局访问点。在许多情况下,单例类被用来管理全局状态或提供全局访问点。
#include <iostream>
class Singleton {
public:
// 公共静态方法,用于获取单例实例
static Singleton& getInstance() {
// 使用静态局部变量确保只创建一个实例
static Singleton instance;
return instance;
}
// 防止复制构造函数
Singleton(Singleton const&) = delete;
void operator=(Singleton const&) = delete;
// 其他公共方法和成员变量可以在这里添加
void doSomething() {
std::cout << "Singleton is doing something." << std::endl;
}
private:
// 私有构造函数,防止外部实例化
Singleton() {}
};
int main() {
// 获取单例实例
Singleton& singleton = Singleton::getInstance();
// 调用单例方法
singleton.doSomething();
return 0;
}
因此我们可以模仿上面的单例类实现Application类的封装。
#pragma once
#include<iostream>
#include <GLFW/glfw3.h>
#define app Application::getInstance()
class Application
{
public:
static Application* mInstance; //一定要放在private里面吗
//可以放在getInstance内部
~Application();
static Application* getInstance();
//访问实例的静态函数
}
private:
Application();
GLFWwindow* mWindow = nullptr;
};
cpp文件
#include"checkError.h"
#include"Application.h"
Application* Application::mInstance = nullptr;
Application* Application::getInstance() {
// 如果mInstance 已经实例化了,就直接返回
//否则先new出来 再返回
if (mInstance == nullptr) {
mInstance = new Application();
}
return mInstance;
}
Application::Application() {
}
Application::~Application() {
}
最后 我们可以在源文件中#include"Application.h" 并且#define app Application::getInstance()可以用来简化语句。我们就可以像app-> xxx 来访问类了。
成员变量与成员函数设计
成员变量思路,要有描述窗体长,宽。还要存储当前窗体对象。
mWidth, mHeight. mWindow
对外接口设计 init 负责初始化当前对象的各类参数。1,设置初始信息。2、生成窗体。3、载入OpenGL函数。
update负责每一帧更新信息或者执行工作。1、更新双缓冲。2、接受并分发窗体消息
destroy负责退出前打扫战场。执行glfwTerminate 退出gl环境
在头文件中添加这些即可,并在private: 中添加mWidth, mHeight.
先写init函数 将初始化的部分封装到init函数中(前面有展示)
bool Application::init(const int& Width,const int& Height) {
mWidth = Width; mHeight = Height;
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
mWindow = glfwCreateWindow(mWidth, mHeight, "OpenGLStudy", NULL, NULL);
if (mWindow == NULL) {
return false;
}
glfwMakeContextCurrent(mWindow);
//也可以把加载glad 放进来,但是需要在Application中 #include<glad/glad.h>
return true;
}
标签:include,openGL,window,笔记,Application,GL,GLFW,窗体,ing
From: https://blog.csdn.net/qq_66236459/article/details/137180664