下面是一个OpenGL程序的最基本结构。
// 如果我们直接使用图形厂商实现的OpenGL库将会是一件异常艰难复杂的工作
// 现在市面上已经有很多第三方库,用于辅助OpenGL开发。这里采用glfw+glad库。
#include "glad.h" // glad库,简化库函数调用,https://www.yuque.com/tvvhealth/cs/mma5zk#1cf8ee09
#include "glfw3.h" // glfw库,辅助管理窗口、GL上下文context、用户输入、事件等。https://www.yuque.com/tvvhealth/cs/mma5zk#vVgy8
void initGLFW(); // 初始化glfw
void initGLAD(); // 初始化glad
GLFWwindow* createWindow(); // 创建窗口
void initGLState(); // 设置一些起始的OpenGL状态,https://www.yuque.com/tvvhealth/cs/mma5zk#vJqln
void cacheData(); // 缓存一部分数据到GPU显存中,如顶点数据、纹理,shader。
void processInput(); // 处理输入
void render(); // 渲染画面
void glEnd(); // 循环结束,清理工作,比如清理显存中的buffer、纹理。
void main()
{
initGLFW(); // 初始化glfw
GLFWWindow* window = createWindow(); // 创建窗口
initGLAD(); // 初始化glad
initGLState(); // 设置一些初始的OpenGL状态
cacheData(); // 预加载一些数据,如顶点数据、纹理,shader。
// 渲染循环,一个循环就绘制一帧,一帧就是一个画面
while(!glfwWindowShouldClose(window)) {
if( deltaTime >= 1/frameRate ) { // 帧率:间隔不小于1/frameRate秒才渲染一次
processInput( window ); // 处理输入:键盘、鼠标事件等。
render(); // 渲染一帧
glfwSwapBuffers( window ); // 双缓冲机制
glfwPollEvents(); // IO轮询,检查有没有触发什么事件(keys pressed/released, mouse moved etc.),
// 并调用对应的回调函数(可以通过回调方法手动设置),更新窗口状态,
}
}
glEnd(); // 循环结束,清理工作,比如一些常驻缓存
}
void initGLFW() {
glfwInit(); // 初始化glfw库
// 2、配置gflw:运行要求,至少是OpenGL 3.3以上。
glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 3 ); // 主版本号:3
glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 3 ); // 次版本号:3
glfwWindowHint( GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE ); // 使用opengl core profile核心模式,导致必须使用VAO。
......; // 其他一些参数设置
}
void initGLAD() {
if( !gladLoadGLLoader( ( GLADloadproc ) glfwGetProcAddress ) ) {
cout << "Failed to initialize GLAD" << endl;
return false;
}
return true;
}
GLFWwindow* createWindow() {
GLFWwindow *window = glfwCreateWindow(width, height, // 窗口宽高
windowName, // 窗口名称
NULL, NULL );
glfwMakeContextCurrent(window);
// glfwSetCursorPosCallback(...); // 鼠标移动回调
// glfwSetScrollCallback(...); // 鼠标滚轮回调
// glfwSetFramebufferSizeCallback(...); // 窗口大小改变回调
......
return window;
}
void initGLState() {
// glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // GL_LINE:线框模式;GL_FILL:填充模式
// glEnable(GL_DEPTH_TEST); // 开启深度测试
// glEnable(GL_BLEND); // 开启混合
// glEnable(GL_STENCIL_TEST); // 开启模板测试
}
void cacheData() {
// 构建着色器,详细见https://www.yuque.com/tvvhealth/cs/lrzlc9
// 一般就是顶点着色器、片段着色器,再高级一点用到几何着色器、细分着色器
auto shader = buildShader(...);
// 构建顶点数据,详细见https://www.yuque.com/tvvhealth/cs/nq6g1c
build_VAO_VBO_EBO(...);
// 构建纹理(图片文件数据转换成合适纹理格式上传至GPU显存中),
// 详细见https://www.yuque.com/tvvhealth/cs/d53753ed-0848-468a-a41a-62ca25b9b4fc
loadTexture(...);
}
void processInput(){
glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS; // 按下esc键
glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS; // 松开esc键
......
}
void render() {
// 绘制工作流程,总的来说就两步:
// 1、准备数据:着色器、顶点数据、纹理等等
// 2、执行绘制:glDrawArrays\glDrawElements。
// *********************************************
// ************ 一、设置GL state
// *********************************************
glClearColor( 0.1f, 0.1f, 0.1f, 0.1f );
glEnable( GL_DEPTH_TEST ); // 深度测试
......
// *********************************************
// ************ 二、设置shader,见OpenGL_shader部分
// *********************************************
glUseProgram(shaderID); // 在渲染管线上着色器使用shaderID指定的着色器。
// 后续的顶点数据将由指定的顶点着色器处理
// 生成的片段将由指定的片段着色器处理
// 设置shader中的uniform全局变量值,比如设置MVP矩阵,纹理sampler
// 下面是作用,用glsl伪代码描述:uniform int name = value,当然实际不能直接赋值。
GLint uniformLocation = glGetUniformLocation(shaderID, name);
glUniform1i(shaderID, name, value); // 还有很多的glUniform*(...)函数用于给各种类型赋值。
// *********************************************
// ************ 三、绑定纹理
// *********************************************
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture0); // texture unit 0 绑定纹理texture0
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture1); // texture unit 0 绑定纹理texture1
......;
// 通过上面的工作,就可以在片段着色器中同时使用多张纹理(多重纹理)
// *********************************************
// ************ 四、绑定VAO、VBO、EBO
// *********************************************
// 从CPU内存大批量上传顶点数据到GPU内存中,作为顶点着色器的输入。
// 详细见https://www.yuque.com/tvvhealth/cs/nq6g1c
glBindVertexArray( VAO_id ); // 一般都是采用VAO,简化设置工作
......;
// *********************************************
// ************ 五、其他一些数据,如framebuffer
// *********************************************
// 详细见:https://www.yuque.com/tvvhealth/cs/kos5bz
glBindFrameBuffer( ...... );
......;
// *********************************************
// ************ 六、执行绘制
// *********************************************
glDrawArrays(......); // 采用顶点数据绘制
glDrawElements(......); // 采用顶点索引绘制,节省顶点数
}
void glEnd() {
// 为了减少CPU和GPU之间数据传输,提升效率,我们可以将CPU内存中的数据缓存到GPU显存中。
// 这也就意味这些数据会常驻于显存中,因此我们需要及时的清除。常见的缓存数据有如下这些:
glDeleteTextures(n, &textures); // 清理纹理对象
glDeleteVertexArrays(n, &VAOs); // 清理VAO顶点数组对象
glDeleteBuffers(n, &buffers); // 清理各种缓冲对象(VBO顶点缓冲对象、EBO索引缓冲对象)
glDeleteProgram(shaderProgramIDs); // 清理着色器程序对象
glDeleteFramebuffers(n, &framebuffers); // 清理自建帧缓冲对象(RenderTexture)
glDeleteRenderbuffers(n, &renderbuffers); // 清理自建渲染缓存对象(RenderTexture)
......
}