openGL学习(基本窗口)
学习路线
学习 OpenGL 需要掌握一系列基础知识和技能,这些内容涵盖了计算机图形学的基本概念、编程语言、数学知识以及 OpenGL 的具体 API 使用。以下是学习 OpenGL 所需的主要知识点:
1. 计算机图形学基础
-
图形学概念:了解图形学的基本概念,如像素、分辨率、颜色模型(RGB、RGBA)、光栅化、光栅图形等。
-
图形管线:理解图形渲染管线的工作原理,包括顶点处理、光栅化、片段处理等阶段。
-
图形对象:熟悉常见的图形对象,如点、线、多边形、纹理、光照等。
2. 数学基础
-
线性代数:
-
向量:向量的基本运算(加法、减法、点积、叉积)。
-
矩阵:矩阵的基本运算(乘法、逆矩阵、转置)。
-
变换:平移、旋转、缩放等变换的矩阵表示。
-
-
几何知识:
-
坐标系:理解世界坐标系、物体坐标系、视图坐标系、屏幕坐标系等。
-
投影:正交投影和透视投影的概念及实现。
-
-
光栅化算法:
-
直线绘制算法:如 Bresenham 算法。
-
多边形填充算法:如扫描线填充算法。
-
3. 编程语言
-
C/C++:OpenGL 主要使用 C/C++ 进行编程,因此需要熟练掌握 C/C++ 的基本语法和编程技巧。
-
GLSL(OpenGL Shading Language):用于编写着色器程序,包括顶点着色器、片段着色器等。
4. OpenGL API
-
OpenGL 基础:
-
初始化:创建窗口、初始化 OpenGL 上下文(可以使用 GLFW 等库)。
-
状态管理:了解 OpenGL 的状态机模型,如何设置和查询状态。
-
缓冲区对象:如顶点缓冲区对象(VBO)、顶点数组对象(VAO)、帧缓冲区对象(FBO)等。
-
-
着色器编程:
-
着色器类型:顶点着色器、片段着色器、几何着色器等。
-
着色器程序:编写、编译、链接着色器程序。
-
变量传递:如何将数据从 CPU 传递到 GPU(如 uniform 变量、attribute 变量)。
-
-
渲染技术:
-
基本渲染:绘制点、线、三角形等基本图形。
-
纹理映射:加载和应用纹理,理解纹理坐标、纹理过滤、纹理环绕等概念。
-
光照:理解光照模型(如 Phong 照明模型),实现平行光、点光源、环境光等。
-
阴影:实现阴影映射等高级光照效果。
-
-
高级技术:
-
曲面细分:使用曲面细分着色器生成更平滑的曲面。
-
计算着色器:用于通用计算任务。
-
多视口渲染:在多个视口同时渲染不同的场景。
-
后处理效果:如模糊、HDR、抗锯齿等。
-
5. 辅助工具和库
-
GLFW:用于创建窗口和处理输入。
-
GLAD:用于加载 OpenGL 函数指针。
-
GLM(OpenGL Mathematics):用于处理数学运算,如向量和矩阵运算。
-
FreeGLUT:用于创建窗口和处理输入(与 GLFW 类似)。
-
Assimp:用于加载 3D 模型文件。
-
SOIL:用于加载纹理文件。
6. 调试和优化
-
调试工具:使用调试工具(如 RenderDoc、gDEBugger)来调试 OpenGL 程序。
-
性能优化:了解常见的性能瓶颈(如 CPU/GPU 瓶颈、内存带宽瓶颈)和优化方法。
7. 实践项目
-
小型项目:从简单的项目开始,如绘制一个旋转的立方体。
-
复杂项目:逐步实现更复杂的场景,如加载 3D 模型、实现简单的游戏引擎等。
学习资源推荐
-
书籍:
-
《OpenGL SuperBible》:适合初学者和进阶学习者。
-
《OpenGL Programming Guide》(红宝书):经典教材。
-
-
在线教程:
-
LearnOpenGL:非常详细的 OpenGL 教程,适合初学者。
-
OpenGL Tutorial:包含丰富的示例和代码。
-
-
视频教程:
-
YouTube 上有许多 OpenGL 教程,如 The Cherno 的 OpenGL 系列。
-
初试OpenGL
1.首先需要编译glfw-3.4的动态库,和glad库,使用vs2021进行程序编写。
#include <iostream>
//#include "thrirdParty/include/GLFW/glfw3.h"
#include "glad/glad.h" //需要先引用glad的头文件。
#include "GLFW/glfw3.h"using namespace std;
/*
* 创建glfw的窗体系统
*/
int main(int argc,char**argv)
{cout << "===================================" << endl;//1. 初始化GLFW基本环境glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);//启用核心模式(非立即渲染模式)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 2. 创建窗体对象GLFWwindow* win = glfwCreateWindow(800, 600, "OpenGlStudy", NULL, NULL);//设置窗体对象为Opengl的绘制舞台glfwMakeContextCurrent(win);// // 3. 执行窗体循环// while (!glfwWindowShouldClose(win)){//接受并且分发窗体消息//检查消息队列是否有需要处理的鼠标,键盘等信息//如果有的话就将消息批量处理,清空队列。glfwPollEvents();}// 4. 退出程序前做相关清理glfwTerminate();cout << "===================================" << endl;return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.12)project(OpenGL_Lecture)set(CMAKE_CXX_STANDARD 11)include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/thrirdParty/include
)
link_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/thrirdParty/lib)
add_executable(openglStudy "main.cpp" "glad.c")target_link_libraries(openglStudy glfw3.lib)
事件响应
#include <iostream>
//#include "thrirdParty/include/GLFW/glfw3.h"
#include "glad/glad.h" //需要先引用glad的头文件。
#include "GLFW/glfw3.h"using namespace std;
/*
* 创建glfw的窗体系统
* 加入窗体变化的事件回调
*/void frameBufferSizeCallback(GLFWwindow* win, int width, int height)
{std::cout << "窗体的最新大小为:" << width << "高度为:" << height << std::endl;}void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){// key 字母按键码 // scancode 物理按键码 // action:0抬起1按下2长按// mods:是否有shift(1)或ctrl(2) cout << "key = " << key << " scancode = " << scancode << " action = " << action << " mods = " << mods << endl;if (key == GLFW_KEY_W){//按下了w}else if (action == GLFW_PRESS){//按下了}else if (action == GLFW_RELEASE){//抬起}}int main(int argc,char**argv)
{cout << "===================================" << endl;//1. 初始化GLFW基本环境glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);//启用核心模式(非立即渲染模式)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 2. 创建窗体对象GLFWwindow* win = glfwCreateWindow(800, 600, "OpenGlStudy", NULL, NULL);//设置窗体对象为Opengl的绘制舞台glfwMakeContextCurrent(win);//设置监听帧缓冲窗口大小回调函数。glfwSetFramebufferSizeCallback(win, frameBufferSizeCallback);glfwSetKeyCallback(win, keyCallback);// // 3. 执行窗体循环// while (!glfwWindowShouldClose(win)){//接受并且分发窗体消息//检查消息队列是否有需要处理的鼠标,键盘等信息//如果有的话就将消息批量处理,清空队列。glfwPollEvents();}// 4. 退出程序前做相关清理glfwTerminate();cout << "===================================" << endl;return 0;
}
函数加载
Opengl运行环境是一个巨大的状态机,每一个函数都会改变状态机的状态或者触发其执行某个行为。
OpenGL采用双缓冲机制 ,利用两个缓冲区进行交替展示。
#include <iostream>
//#include "thrirdParty/include/GLFW/glfw3.h"
#include "glad/glad.h" //需要先引用glad的头文件。
#include "GLFW/glfw3.h"using namespace std;
/*
* 创建glfw的窗体系统
* 加入窗体变化的事件回调
*/void frameBufferSizeCallback(GLFWwindow* win, int width, int height)
{std::cout << "窗体的最新大小为:" << width << "高度为:" << height << std::endl;//更新窗体的大小glViewport(0, 0, width, height);}void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){// key 字母按键码 // scancode 物理按键码 // action:0抬起1按下2长按// mods:是否有shift(1)或ctrl(2) cout << "key = " << key << " scancode = " << scancode << " action = " << action << " mods = " << mods << endl;if (key == GLFW_KEY_W){//按下了w}else if (action == GLFW_PRESS){//按下了}else if (action == GLFW_RELEASE){//抬起}}int main(int argc,char**argv)
{cout << "===================================" << endl;//1. 初始化GLFW基本环境glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);//启用核心模式(非立即渲染模式)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 2. 创建窗体对象GLFWwindow* win = glfwCreateWindow(800, 600, "OpenGlStudy", NULL, NULL);//设置窗体对象为Opengl的绘制舞台glfwMakeContextCurrent(win);//设置监听帧缓冲窗口大小回调函数。glfwSetFramebufferSizeCallback(win, frameBufferSizeCallback);glfwSetKeyCallback(win, keyCallback);// 使用glad加载所有当前版本的OpenGL函数,加载后才能使用if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) ) {cout << "Failed to initialize glad " << endl;}//设置OpenGL视口以及清理颜色glViewport(0,0,800,600);glClearColor(0.2f,0.3f,0.3f,1.0f);// 3. 执行窗体循环// while (!glfwWindowShouldClose(win)){//接受并且分发窗体消息glfwPollEvents();glClear(GL_COLOR_BUFFER_BIT);//渲染操作// //切换双缓冲glfwSwapBuffers(win);}// 4. 退出程序前做相关清理glfwTerminate();cout << "===================================" << endl;return 0;
}
OpenGL函数错误处理
main.cpp
#include <iostream>
//#include "thrirdParty/include/GLFW/glfw3.h"
#include "glad/glad.h" //需要先引用glad的头文件。
#include "GLFW/glfw3.h"
#include <assert.h>
#include "wrapper/checkerror.h"using namespace std;/*
* 创建glfw的窗体系统
* 加入窗体变化的事件回调
*/void frameBufferSizeCallback(GLFWwindow* win, int width, int height)
{std::cout << "窗体的最新大小为:" << width << "高度为:" << height << std::endl;//更新窗体的大小glViewport(0, 0, width, height);}void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){// key 字母按键码 // scancode 物理按键码 // action:0抬起1按下2长按// mods:是否有shift(1)或ctrl(2) cout << "key = " << key << " scancode = " << scancode << " action = " << action << " mods = " << mods << endl;if (key == GLFW_KEY_W){//按下了w}else if (action == GLFW_PRESS){//按下了}else if (action == GLFW_RELEASE){//抬起}}int main(int argc,char**argv)
{cout << "===================================" << endl;//1. 初始化GLFW基本环境glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);//启用核心模式(非立即渲染模式)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 2. 创建窗体对象GLFWwindow* win = glfwCreateWindow(800, 600, "OpenGlStudy", NULL, NULL);//设置窗体对象为Opengl的绘制舞台glfwMakeContextCurrent(win);//设置监听帧缓冲窗口大小回调函数。glfwSetFramebufferSizeCallback(win, frameBufferSizeCallback);glfwSetKeyCallback(win, keyCallback);// 使用glad加载所有当前版本的OpenGL函数,加载后才能使用if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) ) {cout << "Failed to initialize glad " << endl;}//设置OpenGL视口以及清理颜色glViewport(0,0,800,600);glClearColor(0.2f,0.3f,0.3f,1.0f);// 3. 执行窗体循环// while (!glfwWindowShouldClose(win)){//接受并且分发窗体消息glfwPollEvents();GL_CALL( glClear(GL_COLOR_BUFFER_BIT) );//glClear(-1);//设置错误颜色不会崩溃,会出现黑色//checkError();//GL_CALL(glClear(-1) ); //渲染操作// //切换双缓冲glfwSwapBuffers(win);}// 4. 退出程序前做相关清理glfwTerminate();cout << "===================================" << endl;return 0;
}
wrapper/checkerror.cpp
头文件checkerror.h就是void checkError();
#include "checkerror.h"
#include "glad/glad.h"
#include <string>
#include <iostream>
#include <assert.h>using namespace std;void checkError()
{GLenum errornum = glGetError();//cout << "errornum = " << errornum << endl; //错误码1281 GL_INVALID_VALUEstd::string errorstr = "";if (errornum != GL_NO_ERROR){cout << "GL有错误" << endl;switch (errornum){case GL_INVALID_ENUM:errorstr = "无效的枚举 GL_INVALID_ENUM"; break;case GL_INVALID_VALUE:errorstr = "无效的值 GL_INVALID_VALUE"; break;case GL_INVALID_OPERATION:errorstr = "无效的操作 GL_INVALID_OPERATION"; break;case GL_OUT_OF_MEMORY:errorstr = "无效的内存 GL_OUT_OF_MEMORY"; break;default:errorstr = "UNKNOWN";break;}cout << "errorstr = " << errorstr << endl;assert(false); //断言根据bool决定是否停止程序。}
}
./CMakeLists.txt
cmake_minimum_required(VERSION 3.12)project(OpenGL_Lecture)set(CMAKE_CXX_STANDARD 11)include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/thrirdParty/include
)
link_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/thrirdParty/lib)add_subdirectory(wrapper)#在项目夹加入全局的预编译宏
add_definitions(-DCHECK_ERROR)add_executable(openglStudy "main.cpp" "glad.c" )target_link_libraries(openglStudy glfw3.lib wrapper)
wrapper/CMakeLists.txt
#递归将本文件夹下的所有cpp放到WRAPPER中file(GLOB_RECURSE WRAPPER ./ *.cpp)#将所有cpp文件链接到这个wrapper lib库中。 默认生成静态库,动态库编译不过去
add_library(wrapper ${WRAPPER})
Application封装
application.cpp
#include "application.h"
#include "application.h"
#include "application.h"#include "glad/glad.h" //需要先引用glad的头文件。 用于加载 OpenGL 函数指针
#include "GLFW/glfw3.h" // 用于创建窗口和处理输入。 Application* Application::m_app = nullptr;
Application* Application::getInst()
{if (m_app == nullptr){m_app = new Application();}return m_app;
}int Application::init(const int width, const int height)
{cout << "Application::init" << endl;m_Width = width;m_Height = height;//1. 初始化GLFW基本环境glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);//启用核心模式(非立即渲染模式)glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 2. 创建窗体对象m_Window = glfwCreateWindow(m_Width, m_Height, "OpenGlStudy", NULL, NULL);if (m_Window == NULL){return -1;}//设置窗体对象为Opengl的绘制舞台glfwMakeContextCurrent(m_Window);// 使用glad加载所有当前版本的OpenGL函数,加载后才能使用if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){cout << "Failed to initialize glad " << endl;return -1;}//GLFW监听必须要使用static进行声明。//窗口大小改变的事件glfwSetFramebufferSizeCallback(m_Window, frameBufferSizeCallback);//键盘的事件glfwSetKeyCallback(m_Window, keyCallback);//this就是全局Application对象glfwSetWindowUserPointer(m_Window, this);//将this暂时存在窗体中。return 0;
}int Application::update()
{if (glfwWindowShouldClose(m_Window)){return -1;}//接受并且分发窗体消息glfwPollEvents();//切换双缓冲glfwSwapBuffers(m_Window);return 0;
}int Application::destroy()
{cout << "Application::destroy" << endl;// 4. 退出程序前做相关清理glfwTerminate();return 0;
}Application::Application()
{}void Application::frameBufferSizeCallback(GLFWwindow* win, int width, int height)
{//因为是静态函数,所以不能调用全局唯一实例。// 静态成员函数属于类本身,而不是类的某个具体对象。//它不依赖于任何对象实例,因此不能访问非静态成员变量//(因为非静态成员变量需要对象实例来存储其值)。cout << "frameBufferSizeCallback " << endl;//m_resizeCallBack();// //面试题:如何在静态成员函数中调用非静态函数?//if(Application::getInst()->m_resizeCallBack != nullptr)// Application::getInst()->m_resizeCallBack(width,height);Application* self = (Application*) glfwGetWindowUserPointer(win);if(self->m_resizeCallBack != nullptr)self->m_resizeCallBack(width, height);}void Application::keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{cout << "keyCallback " << endl;Application* self = (Application*)glfwGetWindowUserPointer(window);if (self->m_keyCallBack != nullptr)self->m_keyCallBack(window, key,scancode,action,mods);}Application::~Application()
{}
application.h
#pragma once#include <iostream>#define app Application::getInst()class GLFWwindow; //提前声明class,避免头文件重复定义的问题.using resizeCallBack = void(*)(int width,int height);
using keyCallBack = void(*)(GLFWwindow* window,int key, int scancode, int action, int mods);using namespace std;//单例类实现class Application
{
public:~Application();static Application* getInst();void test() { cout << "test" << endl; };uint32_t getWidth() const { return m_Width; }; //const含义:不改变类中的成员uint32_t getHeight() const { return m_Height; };int init(const int width = 800, const int height = 500);int update();int destroy(); void setResizeCallBack(resizeCallBack call) { m_resizeCallBack = call; };void setkeyCallBack(keyCallBack call) { m_keyCallBack = call; };private:Application();static Application* m_app;uint32_t m_Width{ 0 }; //列表初始化uint32_t m_Height{ 0 };GLFWwindow* m_Window{ nullptr };resizeCallBack m_resizeCallBack{nullptr};keyCallBack m_keyCallBack{ nullptr };private:static void frameBufferSizeCallback(GLFWwindow*win,int width,int height);static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);};
main.cpp
#include <iostream>
//#include "thrirdParty/include/GLFW/glfw3.h"
#include "glad/glad.h" //需要先引用glad的头文件。 用于加载 OpenGL 函数指针
#include "GLFW/glfw3.h" // 用于创建窗口和处理输入。
#include <assert.h>
#include "wrapper/checkerror.h"
#include "application/application.h"using namespace std;/*
* 创建glfw的窗体系统
* 加入窗体变化的事件回调
*/void onResize(int width,int height)
{GL_CALL(glViewport(0, 0, width, height));cout << "onResize " << endl;
}void frameBufferSizeCallback(GLFWwindow* win, int width, int height)
{std::cout << "窗体的最新大小为:" << width << "高度为:" << height << std::endl;//更新窗体的大小glViewport(0, 0, width, height);}void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){// key 字母按键码 // scancode 物理按键码 // action:0抬起1按下2长按// mods:是否有shift(1)或ctrl(2) cout << "key = " << key << " scancode = " << scancode << " action = " << action << " mods = " << mods << endl;if (key == GLFW_KEY_W){//按下了w}else if (action == GLFW_PRESS){//按下了}else if (action == GLFW_RELEASE){//抬起}}int main(int argc,char**argv)
{cout << "===================================" << endl;if (app->init(800,600) == -1){return -1;}//设置监听帧缓冲窗口大小回调函数。//glfwSetFramebufferSizeCallback(win, frameBufferSizeCallback);//glfwSetKeyCallback(win, keyCallback);app->setResizeCallBack(onResize);app->setkeyCallBack(keyCallback);//设置OpenGL视口以及清理颜色glViewport(0,0,800,600);glClearColor(0.2f,0.3f,0.3f,1.0f);// 3. 执行窗体循环// while (app->update() == 0 ){GL_CALL( glClear(GL_COLOR_BUFFER_BIT) );//渲染操作}// 4. 退出程序前做相关清理app->destroy();cout << "===================================" << endl;return 0;
}