简介:这份PDF是GAMES101课程“作业0:虚拟机的使用”的配套资料,面向选修计算机图形学与混合现实研讨会的学生,旨在解决作业前虚拟机环境配置过程繁琐、步骤易错的问题。资源为单个PDF文档,压缩包大小约874KB,内容完整覆盖从零配置虚拟机的全流程:包括VirtualBox在Windows、macOS、Linux下的安装方式,虚拟硬盘文件的下载解压与挂载,新建虚拟机时类型版本、内存大小等参数设置,Ubuntu系统的登录密码说明,以及Guest Additions增强功能的两种安装方法(图形界面与命令行)。同时,资源还介绍了作业框架如何从主机拖入虚拟机、开启双向拖放功能,并推荐使用Visual Studio Code进行查看和编辑,在命令行中编译运行程序;此外附有C++头文件、函数体、常见编译错误与Eigen库使用注意事项,方便初学者自查排错。目前已有433人学习下载,适合刚接触Linux虚拟机或想在统一环境下完成图形学作业的同学,按文档逐步操作即可规避环境配置的常见陷阱,节省大量时间。
1. Games101 作业0:一份 PDF 背后是整套图形学环境的第一道安检
很多人第一次接触 Games101,是从作业0 开始的,也几乎在同时意识到:图形学不是「写个算法跑张图」那么简单,而是要先和编译器、线性代数库、矩阵变换打交道。作业0 的要求很朴素——用 C++ 和 Eigen 把点 (2,1) 绕原点逆时针旋转 45 度,再平移向量 (1,2),把最终坐标输出到控制台,并将代码与运行结果整理成一份 PDF 提交。它考的不是图形学算法,而是你有没有把环境备齐、能不能读懂矩阵代码、愿不愿意认真交付一份可读的作业文档。这份作业适合零基础入门者、自学者,以及拿到课件却卡在第一步的开发者。别小看它:后续作业的编译流程、Eigen 调用和 PDF 交付习惯,全在这里埋好了伏笔。
2. 作业0 在考什么:Eigen 选型与旋转矩阵背后的数学准备
2.1 Eigen 为什么是图形学入门的事实标准
Eigen 是一个 header-only 的 C++ 模板线性代数库,核心能力是矩阵、向量、四元数和几何变换。图形学入门课程普遍选它,而不是让每个人手写矩阵类、或者引入 OpenCV 的 Mat,原因非常实际。
第一,header-only 意味着不需要链接动态库。你下载解压后,只要在编译命令里用 -I 指定头文件路径,代码里写#include <Eigen/Dense>就能用。对于第一周还没搞定构建工具的同学,这是最大的减负。第二,Eigen 的模板表达式让代码写出来和数学公式几乎一一对应,矩阵乘法、转置、求逆都是一行调用,后续作业里做模型视图投影变换、重心坐标插值,用的都是同一套 API。第三,它的运算经过专门优化,作业0 里感觉不到,但后面光栅化时要变换成百上千个三角形顶点,不会因为线性代数库成为瓶颈。
对比一下其他方案就更清楚:手写一个 Matrix3f 类,你要自己实现乘法、求逆、转置,作业0 勉强能忍,作业2 开始就会失控;OpenCV 的 Mat 功能更杂,还带着图像数据结构和额外依赖,放在纯图形学练习里明显过重。Eigen 恰好卡在「只有线性代数、没有多余负担」的位置上,这也是它在科研代码和图形学作业里长期是标配的原因。
版本上我的建议是选近两三年的稳定版,别用十年前的远古版本。老版本对 C++11 的支持不完整,某些模板报错会让新手直接懵掉。解压后的目录通常是eigen-3.4.0/,里面有一个Eigen/子目录,注意是大写 E,编译时 -I 指到eigen-3.4.0这一层就对了。
2.2 先旋转再平移:齐次坐标与变换顺序是唯一的失分点
作业0 的数学只有两步,但每一步都有讲究。第一步是绕原点逆时针旋转 45 度,二维旋转矩阵写作:
R = [cosθ -sinθ] [sinθ cosθ]
逆时针方向对应正角度,所以 θ = 45°,转成弧度是 π/4。第二步是平移 (1,2),问题来了:平移不是线性变换,写不进 2x2 矩阵。必须把点升到齐次坐标再写 3x3 矩阵:点 (x,y) 变成 (x,y,1),旋转矩阵 R 扩成 3x3 的块对角形式,平移矩阵 T 的第三列放 (1,2,1)。于是先旋转再平移的组合变换写作 T·R·P,矩阵乘法从右往左读:P 先被 R 作用,再被 T 作用。
这个顺序是作业0 唯一真正的失分点。如果写成 R·T·P,等价于先平移后旋转,结果完全不同。手工算一遍就能记住:P(2,1) 旋转 45 度后约等于 (0.7071, 2.1213),再平移 (1,2),最终是 (1.7071, 4.1213)。如果顺序反了,点先到 (3,3),再旋转就变成约 (0, 4.2426),两个结果差得很远,肉眼可辨。
我一般会建议把手工计算写进 PDF,或者至少写在自己的笔记里。原因很直接:程序输出 1.70711 和 4.12132 时,你能立刻判断它符合预期;输出 (0, 4.24264) 时,你也知道是顺序写反了。这种「先手算、再对程序」的验证习惯,比记住任何矩阵公式都值钱。
2.3 作业0 的验收逻辑:代码、运行结果、PDF 三件套
作业0 的交付物是一份 PDF,但判分的人实际上在 PDF 里找三样东西:代码是否真的用了 Eigen、是否真的完成了旋转和平移两步变换、输出结果是否正确且截图清晰可读。
这意味着不能只贴一个终端截图就交差。常见做法是:PDF 开头写一行说明「用 Eigen 实现了旋转 45 度与平移 (1,2)」,然后贴完整 main.cpp,再贴编译命令和运行截图,最后写一行最终坐标。一页足够,多了反而散。
有些同学会把 .cpp 文件直接改名成 .pdf 交上去,或者把代码截图扔进聊天窗口再转存,这些都会被判无效。作业0 的 PDF 本身也是一次小小的工程交付练习:格式、清晰度、可读性,都在模拟后续工作中「让别人能快速看懂你的结果」这件事。
3. 把作业0 跑通:环境安装、最小 C++ 工程与结果核对
3.1 编译环境准备:三个平台的快速清单
先准备编译器。下面这张表是我在不同平台上常用的方案,原则是哪个平台最省事用哪个:
| 平台 | 编译器 | 最小安装方式 | 验证命令 |
|---|---|---|---|
| Linux | g++ | 安装 build-essential 工具集 | g++ --version |
| macOS | clang++ | 安装 Command Line Tools | clang++ --version |
| Windows | MinGW-w64 的 g++ | 安装 MinGW-w64 并加入 PATH | g++ --version |
编译器核心要求只有一个:支持 C++11。作业0 代码量很小,不需要 CMake 也能编译,所以 g++ 或 clang++ 足够。Windows 上如果你已经装了 MSVC 也可以用,但需要额外处理M_PI宏定义,后面避坑章节会讲。
然后是 Eigen。去官网下载稳定版压缩包,解压后把整个目录放在一个固定位置,比如项目外层的libs/下。我习惯的项目布局是这样:
hw0/ main.cpp libs/ eigen-3.4.0/ Eigen/注意 -I 参数要指到能命中Eigen/Dense的那一层。代码里写的是#include <Eigen/Dense>,编译器会拿Eigen/Dense去 -I 指定的目录下找。如果 -I 指到libs/eigen-3.4.0/,命中libs/eigen-3.4.0/Eigen/Dense,正确。真正会把人卡死的是 -I 指到了Eigen文件夹本身,那编译器要找的路径就变成Eigen/Eigen/Dense,必然报错。
3.2 核心代码实现:完整代码与每个参数的含义
下面这份 main.cpp 是作业0 最直白的写法。手写矩阵而不是直接调 Eigen 的旋转接口,为的是把每一步都摊开,方便和手算结果对照:
// main.cpp #include <iostream> #include <cmath> #include <Eigen/Dense> int main() { // 齐次坐标下的点 P(2, 1, 1),第三维为 1 表示这是一个点而非方向向量 Eigen::Vector3f P(2.0f, 1.0f, 1.0f); // 角度转弧度:cos/sin 使用弧度制,45 度 = π/4 float theta = 45.0f * M_PI / 180.0f; float c = std::cos(theta); float s = std::sin(theta); // 绕原点逆时针旋转 45 度的齐次变换矩阵 Eigen::Matrix3f R; R << c, -s, 0.0f, s, c, 0.0f, 0.0f, 0.0f, 1.0f; // 平移矩阵:x 方向平移 1,y 方向平移 2 Eigen::Matrix3f T; T << 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 2.0f, 0.0f, 0.0f, 1.0f; // 先旋转再平移:矩阵乘法从右往左读,P 先被 R 作用,再被 T 作用 Eigen::Vector3f result = T * R * P; std::cout << "旋转 45 度并平移 (1,2) 后的坐标:" << std::endl; std::cout << "x = " << result.x() << ", y = " << result.y() << std::endl; return 0; }代码逻辑分四段:定义点、算角度、构造两个矩阵、组合变换输出。几个参数值得单独说明:
| 符号 | 类型 | 取值 | 含义 |
|---|---|---|---|
| theta | float | π/4 | 旋转角度,必须用弧度 |
| R | Matrix3f | 3x3 旋转块 | 绕原点逆时针旋转 |
| T | Matrix3f | 对角线为 1,第三列为 (1,2,1) | 平移变换 |
| result | Vector3f | T·R·P | 变换后的齐次坐标 |
一个常见疑问:为什么不用 Vector2f 而用 Vector3f?因为只有齐次坐标才能把平移表达成矩阵乘法。只用 2x2 矩阵的话,旋转可以写,平移却要在矩阵乘法之后再单独做一次向量加法,组合变换就失去了「一个矩阵表达全部变换」的优雅性。作业0 规模小看不出差别,但后续做视图变换、投影变换时,你会感谢当时用了齐次坐标。
想写更短也可以直接用 Eigen 的几何模块:
#include <Eigen/Geometry> Eigen::Matrix3f R = Eigen::AngleAxisf(45.0f * M_PI / 180.0f, Eigen::Vector3f::UnitZ()).toRotationMatrix();这行代码等价于手写的 R 矩阵。我建议作业0 两种都写一遍:先手写验证理解,再用 AngleAxisf 交叉验证结果一致。这不只是作业技巧,也是后面所有变换作业的排错手段。
3.3 编译运行与结果核对:怎么确认自己算对了
编译命令很简单,关键是别漏-std=c++11和-I:
# Linux / macOS g++ -std=c++11 main.cpp -I ../libs -o hw0 ./hw0 # Windows (MinGW-w64) g++ -std=c++11 main.cpp -I ../libs -o hw0.exe hw0.exe运行后应该看到:
旋转 45 度并平移 (1,2) 后的坐标: x = 1.70711, y = 4.12132如果-std=c++11不写,老编译器会默认按 C++98 解析模板代码,Eigen 的模板表达式会吐出一大段看不懂的报错,第一次遇到的人很容易误以为代码写错了。实际把标准切到 C++11 就安静了。
习惯用 CMake 的话,最小配置是这样:
# CMakeLists.txt cmake_minimum_required(VERSION 3.10) project(hw0) set(CMAKE_CXX_STANDARD 11) add_executable(hw0 main.cpp) target_include_directories(hw0 PRIVATE ../libs)输出核对分两个层次。第一层是对数:x 约 1.70711,y 约 4.12132,和手算一致即可。第二层是自检:把角度改成 0、平移改成 (0,0),输出应该还是 (2,1);把角度改成 90 度,旋转后应该是 (-1,2),再叠加平移得到 (0,4)。这种「改参数看输出是否符合直觉」的验证,能帮你把错误定位到具体某一步,而不是盯着一个孤立数字发呆。
4. 作业0 避坑指南:从编译报错到 PDF 乱码的五个现场
作业0 翻车高发区集中在环境、矩阵、文档三块。下面五个场景是最典型的,按「现象 → 原因 → 解决」逐个拆。
4.1 现象:编译报fatal error: Eigen/Dense: No such file or directory
原因九成是 include 路径指错了层。比如 -I 指到了libs/eigen-3.4.0/Eigen,但代码写的是<Eigen/Dense>,编译器实际去找libs/eigen-3.4.0/Eigen/Eigen/Dense,自然找不到。另一种情况是目录名带版本号,路径写成了libs/eigen/Eigen/Dense,而实际根本没有libs/eigen这一层。
解决:把 -I 指到包含Eigen文件夹的那一层,也就是libs/eigen-3.4.0。用ls或资源管理器确认Eigen目录的真实位置,再改命令。实在不行,打开Eigen/Dense文件看一眼它 include 了哪些子头文件,能帮你理解 Eigen 的头文件组织方式,比瞎试路径高效得多。
4.2 现象:编译通过,输出却是 (0, 4.24264) 或别的奇怪数值
原因有两个高频来源。一个是变换顺序写反了,写成了R * T * P,变成先平移后旋转。另一个是角度单位搞错,直接把 45 传进了 cos 和 sin,而它们按弧度计算,45 弧度约等于绕了 7 圈多,输出自然是天书。
解决:先旋转后平移必须写T * R * P;角度先乘 π/180 转弧度。这里有个 Windows 专属坑:MSVC 下M_PI不一定可用,需要在 include 之前写#define _USE_MATH_DEFINES,或者干脆自己定义const float PI = 3.1415926f;。我一般选后者,跨平台零争议,不依赖编译器宏。
4.3 现象:控制台输出中文乱码,PDF 里中文变成方块
原因是源码文件编码和控制台代码页不一致。Windows 控制台默认 GBK,源文件存成 UTF-8 时中文输出就花掉;反过来源文件是 GBK 而编辑器按 UTF-8 解析,代码里的中文注释也会乱。PDF 里中文变方块,通常是字体没有嵌入,换台设备打开就缺字。
解决:作业0 最省事的方案是程序输出全部用英文,截图也干净。PDF 正文尽量用简短中文或英文,中文标题用 Word 另存为 PDF 时勾选「嵌入字体」,用 LaTeX 就选带中文字体的模板。提交前我会换一台设备或用一个在线 PDF 预览器打开检查一遍,专查乱码和缺字。
4.4 现象:交上去的是 .cpp、.zip 或者一张模糊的照片,被判无效
原因是没认真读提交要求,或者图省事把代码复制进聊天窗口再截图,分辨率被压得一塌糊涂。
解决:提交前确认扩展名是.pdf,不是改名后的假 PDF。截图用系统自带截图工具,只圈代码和输出窗口,别把桌面、浏览器、聊天窗口截进去。一页 PDF 内包含代码、编译命令、运行截图和坐标结果。清晰度不够就裁剪重截,不要用手机拍屏幕交差。
4.5 现象:代码能跑、输出正确,但心里没底——缺少人工复核
原因是程序替你算完了,但你并不知道这个结果为什么对,被问一句「这个数怎么来的」就答不上来。作业0 的核心价值恰恰在这里:它逼你完成一次从手算到程序输出的闭环。
解决:先拿纸笔算出 (1.70711, 4.12132),再跑程序对照;把手工计算的旋转矩阵、平移矩阵写进 PDF 的附录或备注。这个习惯能直接迁移到后续作业:任何变换代码跑完,先问自己一句「这个输出符合预期吗,为什么」,答不上来就回去查矩阵,而不是接着往下写。
5. 把作业0 变成后续作业的模板:验证技巧与最小化环境的进阶习惯
作业0 跑通之后,不要急着删除工程目录,它值得被改造成一个可复用的最小模板。我建议做完三件事。
第一,写一个交叉验证脚本。用 Eigen 的 AngleAxisf 构造旋转矩阵,和你手写的 R 矩阵做差,输出为零矩阵则实现无误:
#include <Eigen/Geometry> #include <iostream> #include <cmath> int main() { float theta = 45.0f * 3.1415926f / 180.0f; Eigen::Matrix3f R_hand; R_hand << std::cos(theta), -std::sin(theta), 0, std::sin(theta), std::cos(theta), 0, 0, 0, 1; Eigen::Matrix3f R_api = Eigen::AngleAxisf(theta, Eigen::Vector3f::UnitZ()).toRotationMatrix(); std::cout << "差值矩阵(应接近零):\n" << (R_hand - R_api) << std::endl; return 0; }差值矩阵每一项在 1e-7 量级就说明实现正确。这个「用官方接口验证手写实现」的技巧,后面写透视投影矩阵、视图矩阵时同样适用,能省下大量查错时间。
第二,把编译命令沉淀成一个脚本或最小 CMakeLists.txt。后续作业会新增多个源文件,到时候再补构建配置也行,但更省事的是现在就把环境固化下来。我习惯在工程根目录放一个build.sh,内容是固定的三条命令,换作业只改源文件名。
第三,学会读 Eigen 的模板报错。Eigen 的报错动辄几百行,新手看到就慌;其实关键信息在第一个 error 和最末尾的required from here附近。遇到看不懂的报错,先确认三件事:C++11 是否开启、include 路径是否正确、矩阵维度是否匹配。作业阶段的报错九成逃不出这三类。
我后来养成的习惯是:任何图形学作业开工前,先花十分钟确认编译环境可复现,再写第一行业务代码。作业0 教会我的不是旋转矩阵本身,而是「环境问题先查宏定义和路径,再怀疑代码逻辑」这个排错顺序。那次在 M_PI 上浪费了一下午之后,我就再也没把环境细节当玄学对待过。希望你也能把这份作业当成第一个可复用的工程模板,而不仅仅是一次提交。希望帮到你。
本文还有配套的精品资源,点击获取