1. 先说思路:macOS 上跑 learnOpenGL 到底在忙活什么
1.1 三个组件各管哪一摊
很多人第一次翻开 learnOpenGL 教程,看到 GLFW、GLAD、CMake 这一堆词直接蒙了。实际上在 macOS 上搭环境,本质上就是解决三件事:谁负责创建窗口、谁负责加载 OpenGL 函数、谁负责把代码编译链接起来。
OpenGL 本身是图形接口规范,真正实现它的是显卡驱动。macOS 系统自带 OpenGL 框架,不需要额外下载驱动。也就是说,OpenGL 的运行环境系统已经给你了,你缺的是两个东西:一个是负责窗口创建和接收鼠标键盘事件的 GLFW,另一个是负责把 OpenGL 的 API 从驱动里“挖”出来给你用的 GLAD。
我习惯把这套流程比作去餐厅吃饭:OpenGL 驱动是后厨,能做菜但不直接和你打交道;GLFW 是前台服务员,负责给你安排座位(创建窗口)和传话(处理输入事件);GLAD 则是一份完整的菜单,把后厨的各种菜品(OpenGL 函数)翻译成你能看懂的语言。没有 GLAD,你在代码里调用glGenVertexArrays就会直接报undefined symbol,因为那些函数在编译时根本不在你的程序里。
理解了这个分工,再去看 CMake 和编译链接的报错,你就知道问题出在哪个环节了。窗口打不开找 GLFW,函数找不到找 GLAD,编译不过找 CMake 和头文件路径,运行图形不对找 OpenGL 版本和着色器。
1.2 为什么版本要卡在 OpenGL 3.3 / 4.1
macOS 上的 OpenGL 支持是有上限的。苹果官方文档和相关资料里写明,macOS 提供的是最高 OpenGL 4.1 核心版本,之后就停止更新了,官方主推的是 Metal。你要在这台 Macbook Pro 上跑 learnOpenGL,就必须接受这个现实:所有教程里的底层特性,只要不依赖 OpenGL 4.1 之后的新功能,都能跑。
learnOpenGL 教程的绝大多数示例只用到 3.3 核心特性,所以环境配置策略就很明确了:窗口上下文按 3.3 来建,GLAD 生成时也选 3.3,着色器都写#version 330 core。这样和教程完全对齐,拿过来就能用。
有朋友会问,那我直接选 4.1 行不行?也可以。macOS 支持 4.1,GLAD 生成 4.1 的绑定后,大部分 3.3 的代码照样能跑,因为 4.1 是后向兼容的。但你要注意,如果教程代码里出现某些只在 3.3 中定义的枚举或行为,你在 4.1 上下文里不一定能遇到相同结果。我建议新手走教程原配的 3.3,等基础扎实了再考虑高版本问题。
1.3 芯片架构不同,路径天差地别
写这篇笔记时,我特意把 Intel 和 Apple Silicon 分开说,因为环境搭建里最容易卡死的路径问题,根源就是芯片架构。
Intel 版 Macbook Pro 上,Homebrew 默认安装位置是/usr/local,然后include和lib都在这下面,编译时系统找库相对顺利。但 Apple Silicon 机型上,Homebrew 装在了/opt/homebrew,很多 CMake 工程默认不会主动去/opt/homebrew下找头文件和库。于是你就看到明明brew install glfw成功了,CMake 就是报找不到GLFW/glfw3.h。
这个区别也影响你后面写的 CMakeLists。如果你用 Apple Silicon,构建时要么通过CMAKE_PREFIX_PATH把 Homebrew 前缀传给 CMake,要么在 CMakeLists 里手动指定查找路径。两种方式我后面都会演示,先有个印象就行。
2. 开工前的基础环境准备
2.1 Xcode Command Line Tools:先让编译器就位
macOS 上写 C/C++ 项目,不一定非要装整个 Xcode,但你必须要装 Xcode Command Line Tools。它提供clang编译器、make、git、ld链接器等基础工具链。没有它,你连最简单的g++ main.cpp都跑不了。
安装方式很简单,打开终端,输入:
xcode-select --install系统会弹出一个图形化的安装提示,等它下载完就行。下载速度取决于你的网络状况,一般几分钟到十几分钟不等。装完可以验证一下:
clang --version如果你看到版本号信息,说明工具链没问题。这里提醒一句,很多时候网上教程让你装完整 Xcode,其实完全没必要,Command Line Tools 体积小很多,命令行开发足够用了。
2.2 用 Homebrew 安装 CMake 和 GLFW
Homebrew 在 macOS 开发里基本是标配,安装命令这里不多啰嗦,直接执行官网那行命令就行。装完之后,先确认一下 Homebrew 自己所在的目录:
brew --prefixIntel 机器会输出/usr/local,Apple Silicon 机器会输出/opt/homebrew。记下这个路径,后面 CMake 能不能找到 GLFW 就靠它了。
接下来装两个东西:
brew install cmake glfwCMake 是构建工具,GLFW 是窗口库。Homebrew 会把 GLFW 的头文件放到$(brew --prefix)/include/GLFW/glfw3.h,把动态库放到$(brew --prefix)/lib/libglfw.3.dylib。你可以验证一下:
ls $(brew --prefix)/lib | grep glfw如果看到libglfw开头的文件,说明 GLFW 安装成功。
这里插一句,有人会问为什么不直接从 GLFW 官网下载预编译二进制?也可以,但在 macOS 上维护起来麻烦,版本升级、架构切换都要手动替换。Homebrew 帮你统一管理,项目出问题后清理也方便,所以推荐直接 brew 安装。
2.3 GLAD 在线生成绑定:最容易翻车的一步
GLAD 的作用刚才说了,负责加载 OpenGL 函数。它没有一个固定下载的版本,需要你到 GLAD 的在线生成页去定制一份,选错了后面例子全部白搭。
生成时这几个选项要特别留意:
- 接口语言选 C/C++;
- API 规格选 gl;
- API 版本选 3.3;
- 配置文件选 Core;
- 勾选生成一个加载器,如果生成器里没有这个选项,也要保证生成的源码里有
gladLoadGLLoader相关的加载函数。
生成的压缩包解压后,结构大概是这样的:
glad/ ├── include │ ├── glad │ │ └── gl.h │ └── KHR │ └── khrplatform.h └── src └── glad.c注意,KHR/khrplatform.h是 GLAD 头文件依赖的公共平台头,必须和glad/gl.h放在一起,否则编译时会出现找不到khrplatform.h的报错。我建议把整个include和src目录拷进你的工程,不要只拷单个文件。
生成完的文件里,所有 OpenGL 函数指针都会通过加载逻辑在运行时解析出来。你在主程序里写的glCreateShader、glUseProgram这些函数,背后其实是glad_glCreateShader这类指针。GLAD 生成的glad.c初始化了这些指针,所以它必须参与编译链接,不能漏掉。
3. CMake 工程搭建与第一个窗口
3.1 目录结构长这样
为了少踩坑,我建议你的第一个工程目录就按下面这个结构放。等以后学完基础,再按个人习惯重组不迟。
learnopengl_hello/ ├── CMakeLists.txt ├── main.cpp ├── include │ ├── glad │ │ └── gl.h │ └── KHR │ └── khrplatform.h └── src └── glad.cmain.cpp是入口文件,include里放 GLAD 生成的头文件,src/glad.c也按 GLAD 生成的目录放。GLFW 的头文件不需要拷进来,它会通过 Homebrew 路径找到。
3.2 CMakeLists.txt 逐行拆解
我直接给一份能用的 CMakeLists,然后解释每段是什么意思。
cmake_minimum_required(VERSION 3.16) project(learnopengl_hello) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(learning_gl main.cpp src/glad.c) target_include_directories(learning_gl PRIVATE include) find_package(OpenGL REQUIRED) find_package(glfw3 3.3 REQUIRED) target_link_libraries(learning_gl PRIVATE OpenGL::GL glfw)前两行是声明工程名字和最低 CMake 版本,没太多可讲的。第三行设置 C++ 标准为 17,这是用现代 C++ 写 shader 和工程代码时候的常见选择,其实这个例子用 C++11 也够,但直接上 17 以后方便。
add_executable把main.cpp和glad.c一起编到可执行文件里。注意glad.c是 C 文件,CMake 会按 C 规则编译它,不需要额外处理。
target_include_directories指定头文件搜索路径,这里把当前目录下的include加进去。这样代码里写#include <glad/gl.h>就能找到了。
find_package(OpenGL REQUIRED)是找系统自带 OpenGL。在 macOS 上,CMake 会找到OpenGL.framework,并且为目标生成一个OpenGL::GL的导入库。
find_package(glfw3 3.3 REQUIRED)是找 Homebrew 安装的 GLFW。如果你在构建时没指定好 Homebrew 前缀,这一步会报错。解决方法是在执行 CMake 时带上参数,后面我会写。如果你更习惯用 pkg-config,也可以改成:
find_package(PkgConfig REQUIRED) pkg_check_modules(GLFW REQUIRED IMPORTED_TARGET glfw3) target_link_libraries(learning_gl PRIVATE OpenGL::GL PkgConfig::GLFW)但 pkg-config 工具在部分新装系统上不一定自带,所以我还是推荐直接find_package(glfw3),配合CMAKE_PREFIX_PATH使用,最省心。
3.3 编译、运行、验证结果
先写一个最简单的窗口程序main.cpp,它能验证整条链路是否通畅,不涉及 shader 和三角形绘制:
#include <glad/gl.h> #include <GLFW/glfw3.h> #include <iostream> int main() { if (!glfwInit()) { std::cerr << "Failed to initialize GLFW" << std::endl; return -1; } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr); if (!window) { std::cerr << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(glfwGetProcAddress))) { std::cerr << "Failed to initialize GLAD" << std::endl; return -1; } while (!glfwWindowShouldClose(window)) { glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); return 0; }生成 CMake 时,Intel 和 Apple Silicon 的命令略有不同。Apple Silicon 机器上,一定要手动告诉 CMake 去哪里找 GLFW:
mkdir build cd build cmake .. -DCMAKE_PREFIX_PATH=$(brew --prefix) make ./learning_glIntel 机器上如果一切正常,不带CMAKE_PREFIX_PATH也能过。但我建议统一都带上,省得后面因为路径问题折腾。
运行后你应该看到一个 800x600 的窗口,背景是墨绿色,可以正常关闭。终端里没有任何报错,说明 GLFW 创建了 OpenGL 3.3 核心上下文,GLAD 也成功加载了函数。到这里,环境搭建就算完成了 80%,剩下的就是跟着教程写 shader 和画三角形了。
4. 常见问题与排查技巧实录
4.1 链接器找不到 GLFW,或者一堆 undefined symbols
如果你在make阶段看到这种报错:
ld: library not found for -lglfw说明链接器没找到 GLFW 库。大概率是find_package(glfw3)没成功,或者 CMake 找到了错误架构的库。Apple Silicon 上最典型的原因就是没设CMAKE_PREFIX_PATH,导致 CMake 压根没去/opt/homebrew下找。
处理办法很简单,执行 CMake 前先确认:
brew info glfw看输出的安装路径是不是/opt/homebrew/Cellar/glfw/x.x.x。是的话,就在 CMake 时带上-DCMAKE_PREFIX_PATH=/opt/homebrew。如果你用的是 Intel,路径相应的换成/usr/local。
如果你看到的是Undefined symbols for architecture arm64,但窗口代码能编译,那问题多半出在链接 OpenGL framework 上。检查 CMakeLists 里有没有find_package(OpenGL REQUIRED)并且target_link_libraries里有没有OpenGL::GL。漏掉的话,glClear、glClearColor这些都是未定义符号。
4.2 OpenGL 已弃用警告刷屏
把工程跑起来以后,编译时经常出现一大串:
warning: 'glClear' is deprecated: first deprecated in macOS 10.14这不是错误,不影响运行,就是苹果在提醒你别再依赖老旧的 OpenGL。问题是警告太多会干扰你看真正的报错信息。我一般会在 CMakeLists 里加一行编译定义,让警告闭嘴:
target_compile_definitions(learning_gl PRIVATE GL_SILENCE_DEPRECATION=1)在main.cpp顶部也可以写#define GL_SILENCE_DEPRECATION,放在所有 OpenGL 相关头文件之前。注意必须先定义再 include,不然没效果。这个小技巧能让终端清爽很多,排查问题时省心。
4.3 窗口创建失败:OpenGL 3.2+ Core Profile 没开
有些朋友在glfwCreateWindow一步直接拿到空指针,窗口黑屏或者直接崩溃。绝大多数情况是你忘了在创建窗口前设置 GLFW 窗口提示。
macOS 的 OpenGL 老版本默认上下文是 legacy profile,而 learnOpenGL 用的是 core context。如果没有这两行:
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);GLFW 很可能无法创建你想要的 3.3 核心上下文,返回空指针。特别是GLFW_OPENGL_FORWARD_COMPAT这一行,在 macOS 上基本是必须的。很多新人在 Windows 上看别人教程,Windows 上不写 forward compat 也能跑,到了 macOS 就原形毕露。
还有一种情况是你请求的版本过高。比如把GLFW_CONTEXT_VERSION_MINOR设成 5,macOS 支持不到,自然失败。按 3.3 或 4.1 来设就不会有问题。
4.4 GLAD 版本选错导致函数不可用
GLAD 报的错很隐蔽,常见的是编译通过、窗口也能开,但一调用某个 OpenGL 函数就崩溃,或者返回GL_INVALID_ENUM。
这种大多是 GLAD 生成版本选低了。比如说你生成了 OpenGL 3.0 的绑定,然后在程序里用了glVertexAttribPointer这些 3.3 才标准化的函数,GLAD 里没有正确指针,运行时自然出错。
我在实际项目中遇到过最诡异的一次:着色器编译正常,但画面全黑,排查很久才发现 GLAD 生成时选了兼容模式而不是核心模式,导致大量 core profile 函数没有正确加载。所以生成 GLAD 时核心模式这一个选项必须勾死。如果项目已经生成过 GLAD,重新生成后记得把旧include和src替换干净,别让新旧 GLAD 头文件混在一起。
4.5 列出通用的自查清单
环境出问题时,我一般按下表顺序逐项检查,能省去大量盲目摸索。
| 检查项 | 确认方式 | 常见原因 |
|---|---|---|
| 编译器工具链 | clang --version | Xcode Command Line Tools 未装 |
| Homebrew 路径 | brew --prefix | Apple Silicon 是/opt/homebrew |
| GLFW 库文件 | ls $(brew --prefix)/lib|grep glfw | 装错架构或没装成功 |
| CMake 前缀 | 构建参数-DCMAKE_PREFIX_PATH | 忘了指到 Homebrew 前缀 |
| OpenGL 链接 | CMakeLists 中的OpenGL::GL | 漏掉 framework |
| GLAD 配置 | 生成选项版本 3.3 + Core | 选了兼容模式或版本过低 |
| 窗口提示 | GLFW_OPENGL_CORE_PROFILE | macOS 必须开 core 和 forward compat |
每次排错,先定位问题层次:编译失败看 CMake 和头文件,链接失败看库路径,运行崩溃看 GLAD 和上下文版本。这样一步一步来,比盯着终端瞎试强得多。
最后说一点我自己的体会。当年第一次在 Macbook Pro 上搭这个环境,最花时间的不是写 CMake,而是意识到 macOS 的 OpenGL 已经“停更”这个现实。只要接受它最高只支持 4.1,并且在建上下文和生成 GLAD 时都按 3.3 来,这套组合其实非常稳定。后面再学阴影、纹理、模型加载,只要基础环境没坏,换机器搬工程也基本是几分钟的事。现在我把这台 M 系芯片 Macbook 的环境配置留档成笔记,再换新机器时直接照做,省下的时间都够多跑几个学习示例了。