尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

OpenGL [ Hello Window ]

发布时间:2026/9/29 15:01:53

资讯中心
01
ARTICLE

OpenGL [ Hello Window ]

OpenGL [ Hello Window ]
上一篇我们已经完成了 OpenGL 3.3 Core Profile 的环境搭建也理解了 Context、GLFW、GLAD 和 GPU 驱动分别负责什么。接下来我们先不急着创建 VBO、VAO 和 Shader而是从一个最小问题开始如何让 OpenGL 真正拥有一个窗口并持续显示一帧清屏后的画面这一篇按照“概念讲解 → 官方定义 → 完整代码 → 运行验证 → 常见问题”的方式完成现代 OpenGL 的第一个窗口程序。程序最终会完成下面几件事情初始化 GLFW请求一个 OpenGL 3.3 Core Profile Context创建一个 960×540 的窗口让 Context 对当前线程成为 current使用 GLAD 加载 OpenGL 函数通过glClearColor和glClear清除默认 Framebuffer交换前后缓冲区让清屏颜色真正显示出来响应窗口关闭、窗口缩放和 ESC 键退出。本文的核心判断**Hello Window 的重点不是“弹出一个窗口”而是把窗口系统、OpenGL Context、默认 Framebuffer、事件处理和双缓冲连接成一个稳定的帧循环。**窗口出现只能说明窗口创建成功能够正确清屏、响应缩放、交换缓冲区并安全退出才说明这条链路真正跑通。一、Hello Window 到底要验证什么很多教程把第一个程序直接写成while (!glfwWindowShouldClose(window)) { glfwSwapBuffers(window); glfwPollEvents(); }窗口确实可能出现但这段代码没有告诉我们当前线程是否有 Context清除的是哪个 Framebuffer为什么必须交换缓冲区窗口缩放以后为什么还要更新 Viewport所以本篇先把完整执行链路画出来这条链路中每一步都有明确职责步骤负责内容官方语义glfwInit初始化 GLFW 库准备窗口、输入和平台相关功能glfwWindowHint提供窗口和 Context 创建提示只影响后续创建的窗口glfwCreateWindow创建顶层窗口和 OpenGL ContextGLFW 窗口对象同时封装二者glfwMakeContextCurrent绑定当前线程的 Context后续 OpenGL 命令针对这套状态执行gladLoadGL查询当前 Context 支持的函数地址必须在 Context current 之后调用glClear清除当前 Draw Framebuffer使用之前设置的清除值glfwSwapBuffers交换前后颜色缓冲区把已经完成的一帧交给窗口系统glfwPollEvents处理窗口系统事件让关闭、缩放、键盘等事件继续流动OpenGL 规范定义的是 Context 已经存在之后的图形 API 行为窗口和 Context 的创建由平台窗口系统或 GLFW 这类库负责。Khronos OpenGL Wiki 也把“创建 Context”和“加载函数”列为使用 OpenGL 前的两个独立阶段。官方资料Khronos OpenGL Getting StartedGLFW Context guideOpenGL 3.3 Core Profile Specification二、窗口、Context 和默认 Framebuffer1. GLFW 窗口对象不只是一个矩形GLFW 官方文档对窗口对象的定义是它同时封装了一个顶层窗口和一个 OpenGL 或 OpenGL ES Context。因此下面的GLFWwindow*不只是“窗口句柄”GLFWwindow* window glfwCreateWindow( 960, 540, OpenGL Hello Window, nullptr, nullptr);当我们调用glfwMakeContextCurrent(window);实际上做了两件事让这个窗口关联的 OpenGL Context 对当前线程成为 current让后续的 OpenGL 命令读取并修改这套 Context 的状态。GLFW 文档明确说明在调用 OpenGL 函数之前正确类型的 Context 必须对当前线程成为 current。一个线程同一时间只能有一个 current Context而一个 Context 同一时间也只能属于一个线程。因此下面的顺序不能颠倒创建窗口与 Context ↓ 让 Context 对当前线程 current ↓ 加载 OpenGL 函数 ↓ 调用 glClear、glViewport 等函数2. OpenGL 不是直接往屏幕写像素创建窗口时GLFW 通常会为它创建一个默认 Framebuffer。默认 Framebuffer 至少包含颜色缓冲区具体还可能包含深度和模板缓冲区。OpenGL 绘制或清屏时写入的是当前绑定的 Draw Framebuffer。Hello Window 阶段还没有创建自定义 Framebuffer所以我们操作的是窗口关联的默认 FramebufferglClearColor(0.10f, 0.14f, 0.22f, 1.0f); // set glClear(GL_COLOR_BUFFER_BIT); // use这里的glClear会把颜色写入当前颜色缓冲区在窗口默认 Framebuffer 的双缓冲配置下这通常就是后缓冲区。调用glfwSwapBuffers(window);以后后缓冲区和前缓冲区交换刚刚清除的颜色才会成为窗口系统准备显示的那一帧。这里的“前”和“后”不是两个 OpenGL 对象名称而是窗口默认 Framebuffer 中用于显示和绘制的缓冲区角色。交换以后下一帧通常会在新的后缓冲区中继续绘制。3.glClearColor不会自动清屏glClearColor只是设置 Context 中的清除颜色状态【set】glClearColor(0.10f, 0.14f, 0.22f, 1.0f);真正执行清除的是【use】glClear(GL_COLOR_BUFFER_BIT);这一行才是真正执行清除的命令。它告诉 OpenGL把当前绑定的 Draw Framebuffer 中由清除掩码指定的缓冲区全部重置为之前设置好的清除值。这里传入的GL_COLOR_BUFFER_BIT是一个位掩码常量表示本次只清除颜色缓冲区。需要特别注意的是glClear并不会自动调用glClearColor。如果之前没有设置过清除颜色OpenGL 会使用默认值也就是黑色(0.0, 0.0, 0.0, 0.0)。这也是很多初学者发现窗口是黑色的原因之一——他们只写了glClear却忘了先调用glClearColor设置颜色。此外glClear清除的是当前绑定的 Draw Framebuffer。在 Hello Window 阶段我们还没有创建自定义 Framebuffer所以它清除的是窗口关联的默认 Framebuffer 的后缓冲区。清除完成后还需要调用glfwSwapBuffers把后缓冲区交换到前台清除后的颜色才会真正显示在屏幕上。Khronos 参考页对glClear的描述是使用由glClearColor、glClearDepth和glClearStencil设置的值清除指定的缓冲区。清除掩码决定本次清除哪一种缓冲区掩码清除内容GL_COLOR_BUFFER_BIT颜色缓冲区GL_DEPTH_BUFFER_BIT深度缓冲区GL_STENCIL_BUFFER_BIT模板缓冲区本文还没有启用深度测试因此每一帧只需要清除颜色缓冲区。后面绘制三维物体时通常会写成glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);三、为什么请求 OpenGL 3.3 Core Profile我们在创建窗口之前设置下面三个 HintglfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);这三行的含义分别是Hint含义GLFW_CONTEXT_VERSION_MAJOR请求 Context 的主版本号GLFW_CONTEXT_VERSION_MINOR请求 Context 的次版本号GLFW_OPENGL_PROFILE请求 Core Profile 或 Compatibility Profile1. Hint 必须在创建窗口以前设置glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // 必须在 glfwCreateWindow 前设置 GLFWwindow* window glfwCreateWindow( 960, 540, OpenGL Hello Window, nullptr, nullptr);GLFW 官方把这些参数称为窗口创建提示。它们不是创建完成后可以修改的 OpenGL 状态而是告诉 GLFW 下一次创建窗口时应该尝试得到什么样的 Context。如果把 Hint 写到glfwCreateWindow后面当前窗口不会因此变成 3.3 Core Profile。2. 请求版本是最低要求不是实际版本承诺请求 OpenGL 3.3 并不意味着驱动一定返回 3.3。显卡可能创建出 4.1、4.5 或 4.6 的 Core Profile Context。程序应该查询实际返回值std::cout OpenGL version: reinterpret_castconst char*( glGetString(GL_VERSION)) \n;因此看到类似下面的输出是正常的OpenGL version: 4.1 ATI-4.7.10我们真正关心的是实际版本是否满足 3.3以及 Profile 是否为 Core。3. Core Profile 会主动排除旧式固定功能管线Core Profile 不提供glBegin、glEnd、矩阵栈等旧式固定功能接口// 现代 Core Profile 不使用这套接口 glBegin(GL_TRIANGLES); glVertex3f(0.0f, 0.5f, 0.0f); glEnd();现代 OpenGL 会使用 VAO、VBO 和 GLSL Shader 描述顶点和绘制过程。Hello Window 还没有进入绘制三角形的阶段但从这里开始就应该坚持 Core Profile 的写法避免先学一套兼容模式再重新迁移。4. macOS 的 Forward-Compatible 提示macOS 的 OpenGL 实现要求创建 Forward-Compatible Core Profile Context因此通常加入#ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); #endif这只在 macOS 分支生效不会影响 Windows 和 Linux 上的普通 3.3 Core Profile 创建。四、编写第一个 Hello Window上一篇环境搭建中已经准备好了 GLFW、GLAD 和 CMake。本文假设工程目录如下opengl-hello-window/ ├── CMakeLists.txt ├── src/ │ └── main.cpp └── third_party/ └── glad/ ├── include/ │ ├── glad/gl.h │ └── KHR/khrplatform.h └── src/ └── gl.cGLAD 头文件必须放在 GLFW 头文件之前#include glad/gl.h #include GLFW/glfw3.h这样做的原因是GLAD 生成的头文件应该负责 OpenGL 函数声明避免系统头文件或 GLFW 先引入另一套声明。1. 完整代码在src/main.cpp中写入下面的代码#include glad/gl.h #include GLFW/glfw3.h #include iostream namespace { /** * brief GLFW 全局错误回调函数 * param code 错误码 * param description 错误文本描述 * note GLFW 内部任何错误都会触发这个回调提前捕获初始化/窗口相关问题 */ void glfwErrorCallback(int code, const char* description) { std::cerr GLFW error [ code ]: description \n; } /** * brief 帧缓冲区大小变化回调 * param window 触发事件的窗口句柄 * param width framebuffer 像素宽度不等于窗口逻辑宽度高DPI屏幕会不一样 * param height framebuffer 像素高度 * note 窗口缩放时自动调用必须同步更新 OpenGL 视口 glViewport */ void framebufferSizeCallback(GLFWwindow*, int width, int height) { // glViewport告诉OpenGL渲染区域左下角原点宽高为framebuffer像素尺寸 glViewport(0, 0, width, height); } /** * brief 校验当前 OpenGL Context 版本与配置是否满足要求 * return true上下文合法false版本/配置不满足程序需要退出 * note 必须在 glfwMakeContextCurrent gladLoadGL 之后调用否则 OpenGL 函数不可用 */ bool checkOpenGLContext() { GLint major 0; GLint minor 0; GLint profileMask 0; // 获取 OpenGL Context 主版本号、次版本号 glGetIntegerv(GL_MAJOR_VERSION, major); glGetIntegerv(GL_MINOR_VERSION, minor); // 获取上下文模式标记Core / Compatibility glGetIntegerv(GL_CONTEXT_PROFILE_MASK, profileMask); // 判断版本 3.3 const bool versionOK major 3 || (major 3 minor 3); // 判断是否是 Core Profile 核心模式废弃旧固定管线API const bool coreProfile (profileMask GL_CONTEXT_CORE_PROFILE_BIT) ! 0; // 打印上下文信息 std::cout OpenGL version: reinterpret_castconst char*( glGetString(GL_VERSION)) \n; std::cout OpenGL vendor: reinterpret_castconst char*( glGetString(GL_VENDOR)) \n; std::cout OpenGL renderer: reinterpret_castconst char*( glGetString(GL_RENDERER)) \n; std::cout GLSL version: reinterpret_castconst char*( glGetString(GL_SHADING_LANGUAGE_VERSION)) \n; std::cout Context profile: (coreProfile ? Core : Compatibility/Unknown) \n; // 版本校验失败 if (!versionOK) { std::cerr OpenGL 3.3 or newer is required.\n; return false; } // 必须使用 Core Profile if (!coreProfile) { std::cerr An OpenGL Core Profile context is required.\n; return false; } return true; } } // namespace int main() { // 注册GLFW全局错误回调越早注册越好 glfwSetErrorCallback(glfwErrorCallback); // 初始化GLFW库分配全局资源 if (!glfwInit()) { std::cerr glfwInit failed.\n; return 1; } // OpenGL上下文Hint配置 // Hint配置**必须放在 glfwCreateWindow 之前**用来告诉GLFW要创建什么规格的OpenGL上下文 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // OpenGL主版本3 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // OpenGL次版本3 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用Core核心模式 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // 创建OpenGL上下文不是OpenGL ES #ifdef __APPLE__ // MacOS要求3.3 Core Profile必须开启向前兼容标记移除废弃接口 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); #endif // 创建窗口 创建OpenGL上下文 // 参数宽、高、窗口标题、全屏模式(nullptr窗口模式)、共享上下文(nullptr不共享) GLFWwindow* window glfwCreateWindow( 960, 540, OpenGL 3.3 Core - Hello Window, nullptr, nullptr); if (!window) { std::cerr glfwCreateWindow failed.\n; glfwTerminate(); // 创建失败直接释放GLFW资源 return 1; } // 将窗口的OpenGL Context绑定到当前线程**只有MakeCurrent之后OpenGL函数才可用** glfwMakeContextCurrent(window); // 使用glfwGetProcAddress加载所有OpenGL函数指针GLAD const int gladVersion gladLoadGL( reinterpret_castGLADloadfunc(glfwGetProcAddress)); if (gladVersion 0) { std::cerr gladLoadGL failed.\n; glfwDestroyWindow(window); glfwTerminate(); return 1; } // 校验OpenGL上下文版本与模式 if (!checkOpenGLContext()) { glfwDestroyWindow(window); glfwTerminate(); return 1; } // 获取当前framebuffer像素尺寸初始化视口 int framebufferWidth 0; int framebufferHeight 0; glfwGetFramebufferSize( window, framebufferWidth, framebufferHeight); // 设置初始视口指定OpenGL渲染输出区域 glViewport( 0, 0, framebufferWidth, framebufferHeight); // 注册窗口framebuffer尺寸变化回调窗口缩放自动更新视口 glfwSetFramebufferSizeCallback( window, framebufferSizeCallback); // 垂直同步 VSync1开启0关闭。开启后帧率锁定显示器刷新率防止画面撕裂 glfwSwapInterval(1); // 设置清屏颜色。属于OpenGL上下文状态设置一次即可不需要每帧调用 glClearColor(0.10f, 0.14f, 0.22f, 1.0f); // 主渲染循环 // glfwWindowShouldClose判断窗口是否收到关闭信号点X while (!glfwWindowShouldClose(window)) { // 输入检测按下ESC标记窗口需要关闭 if (glfwGetKey(window, GLFW_KEY_ESCAPE) GLFW_PRESS) { glfwSetWindowShouldClose(window, GLFW_TRUE); } // 清屏清除当前Draw Framebuffer的颜色缓冲区 glClear(GL_COLOR_BUFFER_BIT); // 双缓冲交换后缓冲区后台绘制交换为前缓冲区显示器显示 glfwSwapBuffers(window); // 轮询事件窗口缩放、鼠标、键盘、窗口关闭事件从事件队列取出并触发回调 glfwPollEvents(); } // 退出循环销毁窗口释放OpenGL上下文资源 glfwDestroyWindow(window); // 销毁GLFW全局所有资源 glfwTerminate(); return 0; }下面我们按照程序执行顺序逐步解释这段代码。五、逐段理解代码1. 设置 GLFW 错误回调void glfwErrorCallback(int code, const char* description) { std::cerr GLFW error [ code ]: description \n; } glfwSetErrorCallback(glfwErrorCallback);GLFW 的很多函数只返回成功或失败真正的原因会通过错误回调报告。把回调设置在glfwInit之前可以捕获初始化阶段的错误。例如Context 创建失败时终端可能看到GLFW error [65543]: GLX: Failed to create context这里的错误码和文字比“窗口创建失败”更有定位价值。2. 初始化 GLFWif (!glfwInit()) { std::cerr glfwInit failed.\n; return 1; }glfwInit初始化的是 GLFW 库本身包括平台窗口系统、输入设备和内部状态。它不会创建窗口也不会自动创建 OpenGL Context。因此至少要分别检查glfwInit —— GLFW 初始化成功 glfwCreateWindow —— 窗口和 Context 创建成功 gladLoadGL —— OpenGL 函数加载成功默认情况下程序里的 OpenGL 函数符号只是空壳没有真实函数地址gladLoadGL调用平台提供的获取函数地址接口这里是glfwGetProcAddress去查询当前 OpenGL Context 对应的各个 OpenGL 函数的真实地址填充到 GLAD 生成的全局函数指针里。之后我们写glClear、glViewport这些才会跳转到显卡驱动里真正的实现。⚠️ 前提硬性条件必须有当前激活的 OpenGL ContextglfwMakeContextCurrent才能查询函数地址没有上下文的时候glfwGetProcAddress查询全部返回 NULL。这也就是代码顺序为什么严格是glfwCreateWindow→glfwMakeContextCurrent→gladLoadGL3. 创建窗口和 ContextGLFWwindow* window glfwCreateWindow( 960, 540, OpenGL 3.3 Core - Hello Window, nullptr, nullptr);五个参数的含义如下参数含义960窗口宽度单位是屏幕坐标540窗口高度单位是屏幕坐标字符串窗口标题nullptr不使用全屏显示器nullptr不与其他窗口共享 Context 对象glfwCreateWindow返回空指针时不要继续调用glfwMakeContextCurrent因为此时没有可用的窗口和 Context。4. 让 Context 对当前线程成为 currentglfwMakeContextCurrent(window); const int gladVersion gladLoadGL( reinterpret_castGLADloadfunc(glfwGetProcAddress));OpenGL 的大多数对象和状态都属于 Context。GLFW 的 Context guide 明确指出在进行 OpenGL 或 OpenGL ES 调用前必须先让正确类型的 Context 对当前线程成为 current。glfwGetProcAddress查询的是当前 Context可用的函数地址。若 Context 还没有 currentGLAD 可能加载失败或后续函数指针不可用。5. 查询实际 Context 信息glGetIntegerv(GL_MAJOR_VERSION, major); glGetIntegerv(GL_MINOR_VERSION, minor); glGetIntegerv(GL_CONTEXT_PROFILE_MASK, profileMask);这里查询的是驱动实际创建出来的 Context而不是我们之前写入的 Hint。glGetString可以输出实现信息查询项说明GL_VERSIONOpenGL 版本和实现字符串GL_VENDOROpenGL 实现厂商GL_RENDERER当前使用的渲染器或 GPUGL_SHADING_LANGUAGE_VERSION当前 GLSL 版本一次成功运行可能输出OpenGL version: 4.1 ATI-4.7.10 OpenGL vendor: Apple OpenGL renderer: Apple M2 GLSL version: 4.10 Context profile: Core实际输出会随着操作系统、显卡和驱动变化。只要实际版本不低于 3.3并且 Profile 是 Core就满足本文的目标。6. 设置初始 Viewportint framebufferWidth 0; int framebufferHeight 0; glfwGetFramebufferSize( window, framebufferWidth, framebufferHeight); glViewport( 0, 0, framebufferWidth, framebufferHeight);glViewport定义的是从 Normalized Device Coordinates 到窗口像素坐标的映射区域。它使用的是 Framebuffer 的像素尺寸而不是窗口的逻辑尺寸。在普通屏幕上窗口大小和 Framebuffer 大小可能碰巧相同在 Retina 或其他高 DPI 屏幕上二者可能不同。例如窗口逻辑尺寸960 × 540 Framebuffer 像素尺寸1920 × 1080如果把960 × 540直接传给glViewport渲染结果可能只占窗口的一部分。7. 处理窗口缩放void framebufferSizeCallback(GLFWwindow*, int width, int height) { glViewport(0, 0, width, height); } glfwSetFramebufferSizeCallback( window, framebufferSizeCallback);当窗口或显示缩放变化导致 Framebuffer 尺寸改变时GLFW 会调用这个回调。回调中的width和height是新的像素尺寸因此可以直接传给glViewport。8. 启用垂直同步glfwSwapInterval(1);glfwSwapInterval设置缓冲区交换间隔。传入1通常表示等待一个垂直刷新周期可以减少画面撕裂传入0则请求关闭同步。它不会改变 OpenGL 的绘制结果只影响交换缓冲区时的等待方式。垂直同步是否严格生效还取决于平台和驱动设置。9. 清屏和交换缓冲区glClearColor(0.10f, 0.14f, 0.22f, 1.0f); while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT); glfwSwapBuffers(window); glfwPollEvents(); }这里有三个关键点glClearColor只设置清除颜色glClear才真正修改当前颜色缓冲区glfwSwapBuffers把后缓冲区提交到窗口前台。Khronos 的参考页规定glClearColor的颜色分量会被限制在[0, 1]范围内。因此glClearColor(2.0f, -1.0f, 0.5f, 1.0f);实际清除值相当于(1.0, 0.0, 0.5, 1.0)10. 处理 ESC 键和窗口事件if (glfwGetKey(window, GLFW_KEY_ESCAPE) GLFW_PRESS) { glfwSetWindowShouldClose(window, GLFW_TRUE); }glfwGetKey查询当前键盘状态glfwSetWindowShouldClose设置窗口的关闭标志。主循环通过while (!glfwWindowShouldClose(window)) {读取这个标志。当用户点击窗口关闭按钮或者按下 ESC 键时循环都会在合适的时机结束。glfwPollEvents很容易被初学者漏掉。它负责处理操作系统事件并触发回调。如果长时间不调用它窗口可能无法响应关闭、移动和缩放操作。六、构建与运行本文沿用上一篇环境搭建中的 CMake 工程。如果工程目标名称仍然是opengl_environment可以直接在项目根目录执行cmake -S . -B build -DCMAKE_BUILD_TYPEDebug cmake --build build --config Debug在 macOS 或 Linux 上运行./build/opengl_environment如果使用 Visual Studio 这类多配置生成器通常运行.\\build\\Debug\\opengl_environment.exe运行成功后会看到一个深蓝色窗口同时终端输出版本信息。点击关闭按钮或按 ESC程序应该正常退出不出现崩溃和无响应。一个正常的运行流程如下glfwInit succeeded glfwCreateWindow succeeded gladLoadGL succeeded OpenGL version: 4.x ... Context profile: Core 窗口显示清屏颜色 按 ESC 或点击关闭按钮 进程退出代码为 0如果只想快速确认程序是否进入渲染循环可以把颜色改成明显的红色glClearColor(0.8f, 0.05f, 0.05f, 1.0f);若窗口颜色随之改变说明glClearColor、glClear、默认 Framebuffer 和glfwSwapBuffers已经连通。七、常见错误和定位方法现象原因处理方式glfwInit failed平台窗口系统不可用或运行在没有显示服务器的环境检查 X11/Wayland、远程桌面、虚拟机或容器环境glfwCreateWindow failed驱动不支持请求的版本/Profile或 Hint 设置不正确查看 GLFW 错误回调确认 Hint 写在创建窗口之前gladLoadGL failedContext 没有 current或 GLAD 生成配置不匹配检查glfwMakeContextCurrent顺序确认 GLAD 至少生成到 3.3编译找不到glad/gl.hGLAD include 路径未加入目标检查target_include_directories和文件目录窗口是黑色清屏颜色太暗或没有调用glClear/glfwSwapBuffers先换成红色并逐项检查渲染循环窗口只填充一部分使用了窗口逻辑尺寸没有使用 Framebuffer 像素尺寸调用glfwGetFramebufferSize并注册回调窗口无法关闭没有调用glfwPollEvents在每帧交换缓冲区后处理事件按 ESC 没反应没有设置关闭标志或键盘查询写错窗口对象使用glfwGetKey(window, GLFW_KEY_ESCAPE)macOS 创建 Context 失败缺少 Forward-Compatible 提示在__APPLE__分支设置GLFW_OPENGL_FORWARD_COMPATCore Profile 下旧代码报错使用了固定功能管线接口改用 Shader、VAO 和 VBO不要直接切换到 Compatibility Profile最值得单独记住的是下面四个检查顺序1. glfwCreateWindow 返回非空 2. glfwMakeContextCurrent 已执行 3. gladLoadGL 返回非 0 4. glClear glfwSwapBuffers 都在循环中执行八、不要把几个相似概念混在一起1. Window size 不等于 Framebuffer size窗口是窗口系统的逻辑对象Framebuffer 是 OpenGL 写入的像素目标。高 DPI 屏幕上窗口逻辑尺寸和实际像素尺寸可能不同。int windowWidth 0; int windowHeight 0; glfwGetWindowSize(window, windowWidth, windowHeight); int framebufferWidth 0; int framebufferHeight 0; glfwGetFramebufferSize( window, framebufferWidth, framebufferHeight);设置glViewport时应该使用后者。2.glClearColor不等于glClearglClearColor(0.1f, 0.2f, 0.3f, 1.0f); // 设置状态 glClear(GL_COLOR_BUFFER_BIT); // 执行清除这两个函数的职责不同。前者是状态设置后者是实际命令。3.glfwSwapBuffers不等于glFlushglFlush是 OpenGL 命令用于要求此前的命令尽快开始执行glfwSwapBuffers是 GLFW 的窗口函数用于交换窗口关联的前后缓冲区。Hello Window 中我们需要的是后者。普通渲染循环不需要每帧调用glFinish。它会等待此前的 GPU 工作完成容易让 CPU 无意义地停下来。窗口程序只要正确清屏、交换缓冲区并处理事件即可。4. 创建 Context 和加载函数是两件事glfwCreateWindow —— 得到窗口和 Context glfwMakeContextCurrent —— 选择当前线程使用的 Context gladLoadGL —— 加载该 Context 支持的函数地址这三步都成功才可以安全调用现代 OpenGL 函数。九、一个最小的检查清单运行本文程序时可以按下面的顺序检查是否在glfwInit前设置了 GLFW 错误回调glfwInit()是否返回成功Context Version 和 Profile Hint 是否写在glfwCreateWindow之前glfwCreateWindow()是否返回非空指针是否先调用glfwMakeContextCurrent再调用gladLoadGLglGetString(GL_VERSION)是否能输出实际版本GL_CONTEXT_PROFILE_MASK是否包含GL_CONTEXT_CORE_PROFILE_BIT初始 Viewport 是否使用glfwGetFramebufferSize的结果窗口尺寸变化时是否重新设置 Viewport主循环是否同时调用glClear、glfwSwapBuffers和glfwPollEvents退出前是否调用glfwDestroyWindow和glfwTerminate。这组检查全部通过以后Hello Window 才算完成。后面的三角形绘制并不是重新搭建一套环境而是在这条已经跑通的 Context 和帧循环上继续加入 VAO、VBO、Shader 和绘制命令。十、本文小结这一篇我们完成了现代 OpenGL 3.3 Core Profile 的第一个可运行窗口程序。我们先理解了 GLFW 窗口对象同时关联顶层窗口和 OpenGL Context再按照官方要求在 Context current 以后加载 OpenGL 函数。随后通过glfwGetFramebufferSize和glViewport处理高 DPI 与窗口缩放通过glClearColor、glClear和glfwSwapBuffers完成一帧最小渲染最后用glfwPollEvents处理操作系统事件。整个 Hello Window 可以浓缩成下面这条主线初始化 GLFW → 请求 3.3 Core Profile → 创建窗口和 Context → 让 Context current → 加载 OpenGL 函数 → 设置 Viewport → 清除默认 Framebuffer → 交换前后缓冲区 → 处理窗口事件下一篇可以继续学习OpenGL [ 三角形 ]把顶点数据上传到 VBO用 VAO 描述顶点格式编译并链接 GLSL Shader最后调用glDrawArrays让第一个三角形真正出现在窗口中。FAQ1. 为什么窗口创建成功了仍然不能调用glClear因为窗口指针非空只说明 GLFW 创建窗口和 Context 的请求成功。还必须调用glfwMakeContextCurrent(window)并在此之后加载 GLAD。没有 current ContextOpenGL 函数没有确定的状态环境。2. 为什么请求 3.3输出却是 OpenGL 4.1 或 4.6GLFW 的版本 Hint 表示你需要的最低版本。驱动可以返回更高版本只要它满足 3.3 Core Profile 的要求即可。3. 为什么窗口缩放以后画面只占一部分通常是把窗口逻辑尺寸传给了glViewport。应该读取glfwGetFramebufferSize并在framebufferSizeCallback中随着 Framebuffer 尺寸变化重新设置 Viewport。4. 为什么只调用glClear窗口还是没有颜色glClear写入的是当前后缓冲区。还需要调用glfwSwapBuffers(window)把完成的后缓冲区交换到窗口前台。5. 为什么一定要调用glfwPollEvents它负责把操作系统的窗口、键盘和鼠标事件分发给 GLFW。没有事件处理窗口可能无法响应关闭、移动和缩放。6. 是否可以不使用 GLFW直接创建 OpenGL 窗口可以。Windows 可以直接使用 WGLLinux 可以使用 GLX 或 EGLmacOS 可以使用 CGL。但这些平台 API 的 Context 创建和事件处理代码差异很大。学习现代 OpenGL 时GLFW 可以把平台差异隔离起来让我们先专注于 OpenGL 本身。参考资料OpenGL 3.3 Core Profile SpecificationKhronos OpenGL Getting StartedOpenGL Reference PagesglClear Reference PageglClearColor Reference PageglViewport Reference PageGLFW Context guideGLFW Window guideGLFW Getting startedGLAD repository and usage
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。