下面是一个OpenGL程序的最基本结构。

    1. // 如果我们直接使用图形厂商实现的OpenGL库将会是一件异常艰难复杂的工作
    2. // 现在市面上已经有很多第三方库,用于辅助OpenGL开发。这里采用glfw+glad库。
    3. #include "glad.h" // glad库,简化库函数调用,https://www.yuque.com/tvvhealth/cs/mma5zk#1cf8ee09
    4. #include "glfw3.h" // glfw库,辅助管理窗口、GL上下文context、用户输入、事件等。https://www.yuque.com/tvvhealth/cs/mma5zk#vVgy8
    5. void initGLFW(); // 初始化glfw
    6. void initGLAD(); // 初始化glad
    7. GLFWwindow* createWindow(); // 创建窗口
    8. void initGLState(); // 设置一些起始的OpenGL状态,https://www.yuque.com/tvvhealth/cs/mma5zk#vJqln
    9. void cacheData(); // 缓存一部分数据到GPU显存中,如顶点数据、纹理,shader。
    10. void processInput(); // 处理输入
    11. void render(); // 渲染画面
    12. void glEnd(); // 循环结束,清理工作,比如清理显存中的buffer、纹理。
    13. void main()
    14. {
    15. initGLFW(); // 初始化glfw
    16. GLFWWindow* window = createWindow(); // 创建窗口
    17. initGLAD(); // 初始化glad
    18. initGLState(); // 设置一些初始的OpenGL状态
    19. cacheData(); // 预加载一些数据,如顶点数据、纹理,shader。
    20. // 渲染循环,一个循环就绘制一帧,一帧就是一个画面
    21. while(!glfwWindowShouldClose(window)) {
    22. if( deltaTime >= 1/frameRate ) { // 帧率:间隔不小于1/frameRate秒才渲染一次
    23. processInput( window ); // 处理输入:键盘、鼠标事件等。
    24. render(); // 渲染一帧
    25. glfwSwapBuffers( window ); // 双缓冲机制
    26. glfwPollEvents(); // IO轮询,检查有没有触发什么事件(keys pressed/released, mouse moved etc.),
    27. // 并调用对应的回调函数(可以通过回调方法手动设置),更新窗口状态,
    28. }
    29. }
    30. glEnd(); // 循环结束,清理工作,比如一些常驻缓存
    31. }
    32. void initGLFW() {
    33. glfwInit(); // 初始化glfw库
    34. // 2、配置gflw:运行要求,至少是OpenGL 3.3以上。
    35. glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 3 ); // 主版本号:3
    36. glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 3 ); // 次版本号:3
    37. glfwWindowHint( GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE ); // 使用opengl core profile核心模式,导致必须使用VAO。
    38. ......; // 其他一些参数设置
    39. }
    40. void initGLAD() {
    41. if( !gladLoadGLLoader( ( GLADloadproc ) glfwGetProcAddress ) ) {
    42. cout << "Failed to initialize GLAD" << endl;
    43. return false;
    44. }
    45. return true;
    46. }
    47. GLFWwindow* createWindow() {
    48. GLFWwindow *window = glfwCreateWindow(width, height, // 窗口宽高
    49. windowName, // 窗口名称
    50. NULL, NULL );
    51. glfwMakeContextCurrent(window);
    52. // glfwSetCursorPosCallback(...); // 鼠标移动回调
    53. // glfwSetScrollCallback(...); // 鼠标滚轮回调
    54. // glfwSetFramebufferSizeCallback(...); // 窗口大小改变回调
    55. ......
    56. return window;
    57. }
    58. void initGLState() {
    59. // glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // GL_LINE:线框模式;GL_FILL:填充模式
    60. // glEnable(GL_DEPTH_TEST); // 开启深度测试
    61. // glEnable(GL_BLEND); // 开启混合
    62. // glEnable(GL_STENCIL_TEST); // 开启模板测试
    63. }
    64. void cacheData() {
    65. // 构建着色器,详细见https://www.yuque.com/tvvhealth/cs/lrzlc9
    66. // 一般就是顶点着色器、片段着色器,再高级一点用到几何着色器、细分着色器
    67. auto shader = buildShader(...);
    68. // 构建顶点数据,详细见https://www.yuque.com/tvvhealth/cs/nq6g1c
    69. build_VAO_VBO_EBO(...);
    70. // 构建纹理(图片文件数据转换成合适纹理格式上传至GPU显存中),
    71. // 详细见https://www.yuque.com/tvvhealth/cs/d53753ed-0848-468a-a41a-62ca25b9b4fc
    72. loadTexture(...);
    73. }
    74. void processInput(){
    75. glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS; // 按下esc键
    76. glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS; // 松开esc键
    77. ......
    78. }
    79. void render() {
    80. // 绘制工作流程,总的来说就两步:
    81. // 1、准备数据:着色器、顶点数据、纹理等等
    82. // 2、执行绘制:glDrawArrays\glDrawElements。
    83. // *********************************************
    84. // ************ 一、设置GL state
    85. // *********************************************
    86. glClearColor( 0.1f, 0.1f, 0.1f, 0.1f );
    87. glEnable( GL_DEPTH_TEST ); // 深度测试
    88. ......
    89. // *********************************************
    90. // ************ 二、设置shader,见OpenGL_shader部分
    91. // *********************************************
    92. glUseProgram(shaderID); // 在渲染管线上着色器使用shaderID指定的着色器。
    93. // 后续的顶点数据将由指定的顶点着色器处理
    94. // 生成的片段将由指定的片段着色器处理
    95. // 设置shader中的uniform全局变量值,比如设置MVP矩阵,纹理sampler
    96. // 下面是作用,用glsl伪代码描述:uniform int name = value,当然实际不能直接赋值。
    97. GLint uniformLocation = glGetUniformLocation(shaderID, name);
    98. glUniform1i(shaderID, name, value); // 还有很多的glUniform*(...)函数用于给各种类型赋值。
    99. // *********************************************
    100. // ************ 三、绑定纹理
    101. // *********************************************
    102. glActiveTexture(GL_TEXTURE0);
    103. glBindTexture(GL_TEXTURE_2D, texture0); // texture unit 0 绑定纹理texture0
    104. glActiveTexture(GL_TEXTURE1);
    105. glBindTexture(GL_TEXTURE_2D, texture1); // texture unit 0 绑定纹理texture1
    106. ......;
    107. // 通过上面的工作,就可以在片段着色器中同时使用多张纹理(多重纹理)
    108. // *********************************************
    109. // ************ 四、绑定VAO、VBO、EBO
    110. // *********************************************
    111. // 从CPU内存大批量上传顶点数据到GPU内存中,作为顶点着色器的输入。
    112. // 详细见https://www.yuque.com/tvvhealth/cs/nq6g1c
    113. glBindVertexArray( VAO_id ); // 一般都是采用VAO,简化设置工作
    114. ......;
    115. // *********************************************
    116. // ************ 五、其他一些数据,如framebuffer
    117. // *********************************************
    118. // 详细见:https://www.yuque.com/tvvhealth/cs/kos5bz
    119. glBindFrameBuffer( ...... );
    120. ......;
    121. // *********************************************
    122. // ************ 六、执行绘制
    123. // *********************************************
    124. glDrawArrays(......); // 采用顶点数据绘制
    125. glDrawElements(......); // 采用顶点索引绘制,节省顶点数
    126. }
    127. void glEnd() {
    128. // 为了减少CPU和GPU之间数据传输,提升效率,我们可以将CPU内存中的数据缓存到GPU显存中。
    129. // 这也就意味这些数据会常驻于显存中,因此我们需要及时的清除。常见的缓存数据有如下这些:
    130. glDeleteTextures(n, &textures); // 清理纹理对象
    131. glDeleteVertexArrays(n, &VAOs); // 清理VAO顶点数组对象
    132. glDeleteBuffers(n, &buffers); // 清理各种缓冲对象(VBO顶点缓冲对象、EBO索引缓冲对象)
    133. glDeleteProgram(shaderProgramIDs); // 清理着色器程序对象
    134. glDeleteFramebuffers(n, &framebuffers); // 清理自建帧缓冲对象(RenderTexture)
    135. glDeleteRenderbuffers(n, &renderbuffers); // 清理自建渲染缓存对象(RenderTexture)
    136. ......
    137. }