
简介这份 PDF 是 GAMES101 计算机图形学课程作业 0 的说明文档面向刚接触课程、需要搭建统一开发环境的学生重点解决虚拟机安装、虚拟硬盘挂载、Guest Additions 增强以及作业框架导入编辑等入门问题。资源包仅含 1 个 PDF 文件大小约 874KB篇幅集中、便于随查随用适合作为开课前的环境配置清单。内容覆盖 VirtualBox 在 Windows、macOS、Linux 下的安装指引Ubuntu 虚拟硬盘的下载与配置系统密码、拖放功能设置以及 VSCode 查看编辑和命令行编译运行流程同时简述 main.cpp 样例、头文件与函数体结构并整理编译错误、链接错误、段错误等常见问题。已有 435 人学习可帮助读者少走环境配置弯路快速进入图形学编程练习。1. 决定这套环境成败的不是那道旋转题是 .vdi 和 6.1.4Games101 作业0 的题目一句话就能读完把点 (2,1) 绕原点逆时针转 45 度再平移 (1,2)用齐次坐标算出结果。真让人卡住的从来是它前半段——装 VirtualBox、挂上一个 Ubuntu 18.04.2 的虚拟硬盘、装 Guest Additions、把作业框架从主机拖进去、用 cmake 编译出可执行文件。这套方案的好处是所有人跑在同一个镜像里环境绝对统一坏处是它对版本相当挑VirtualBox 主程序版本、Guest Additions 版本、虚拟机的内核头文件版本三者错一个要么拖不进文件要么编译到一半直接翻车。我见过不少人题算得明明是对的结果 ./Transformation 压根没跑起来。下面按我自己的装机顺序走一遍从装什么版本讲到坏了怎么救。2. 先把版本账算清VirtualBox 6.1.4、Ubuntu 18.04.2 与 Eigen 的搭配装这套东西之前最好先把「谁配谁」写在纸上。这门课给的虚拟硬盘是一个完整预装好的 Ubuntu 18.04.2 (64bit) 系统里面自带 Visual Studio Code、Sublime、g、cmake 和 Eigen也就是说编译器、构建工具、线性代数库都不需要自己装你要装的只有「壳」——虚拟机软件和增强功能。这个认知很关键很多人一上来就 apt 装一堆东西反而把镜像里原本配好的版本顶掉了。2.1 三个平台各下哪个包别跨版本乱配课程文档点名的是 Oracle VM VirtualBox 6.1.4构建号 136177。三个平台对应的安装包不一样但都是同一个版本号这一点别搞混主机系统安装包形态安装时需要留意的点WindowsVirtualBox-6.1.4-136177-Win.exe需要管理员权限安装过程会短暂重置虚拟网卡网络会断几秒macOSVirtualBox-6.1.4-136177-OSX.dmg装完要去「安全性与隐私」放行 Oracle 的系统扩展否则虚拟机起不来Linux官网 Linux Downloads 页按发行版选包装完必须重建内核模块vboxdrv内核升级后还得再跑一次这里有个常见的取舍很多人手上有更新的 7.x 版本觉得「新版肯定更好」直接装了。我的经验是这个镜像就按 6.1.4 来因为增强功能的安装包是跟着主程序版本走的主程序换了代老镜像里那套编译环境不一定认。版本这种事属于玄学范畴能对齐就别折腾。2.2 虚拟硬盘只认解压后的 .vdi 文件下载下来的是个压缩包里面装的才是真正的虚拟硬盘文件。文件名里带空格和括号命令行操作时必须加引号不然 shell 会把括号当语法解析直接报错。# Ubuntu / Debian 主机上装解压工具Windows 用 7-ZipmacOS 用 Keka 或 unar sudo apt install p7zip-full unrar-free # 解压文件名带空格和括号整段用双引号包住 7z x GAMES101_Ubuntu 18.04.2(64bit).rar # 解压完先看一眼体积正常是几个 GB 级别 ls -lh GAMES101_Ubuntu 18.04.2 (64bit).vdi7z x里的x表示带完整路径解压-o可以指定输出目录不写就解到当前目录。ls -lh里的h是 human-readable让你一眼看出是 3.2G 还是 320M。体积明显偏小就是没解压完这种半截文件挂上去VirtualBox 只会给你一个VERR_VD_IMAGE_READ_ONLY之类的黑匣子报错很难往解压失败上想。我一般会顺手算一下校验值虽然文档没给但至少确认文件不是 0 字节。2.3 开工前的三项硬检查第一项是虚拟化开关。进 BIOS/UEFI 把 Intel VT-x 或 AMD-V 打开没开的话 VirtualBox 启动 64 位系统会直接弹「VT-x is disabled」。第二项是磁盘空间虚拟硬盘本身占几个 GB第一次开机后系统会写日志、生成交换文件还要留快照余地我一般预留 20GB 以上。第三项是主机内存虚拟机分 2GB主机最好 8GB 起步4GB 的机器跑起来会卡到你想砸键盘。提示别急着给虚拟机分配 4GB 甚至更多内存。这个镜像是 32/64 位混合装的环境2GB 是文档建议值给多了容易和主机抢内存反而更慢。3. 装 VirtualBox 挂上 .vdi新建、开机、登录三步环境账算完就动手。这一章的目标很明确让那个 .vdi 在 VirtualBox 里变成一个能点亮、能登录的 Ubuntu 桌面。3.1 Windows 与 macOS向导里两个必须改的选项Windows 上双击 exe 一路下一步就行但有两处别用默认值。一是安装路径别放在中文目录下VirtualBox 对非 ASCII 路径的处理一直不太稳扩展包加载失败是常见后果。二是安装过程中会弹出「网络接口」警告并断开网络这是它在重装虚拟网卡驱动点「是」继续装完网络会自己恢复。macOS 上装完 dmg 之后第一次启动虚拟机会提示「系统扩展被阻止」。去「系统偏好设置 → 安全性与隐私 → 通用」点左下角解锁允许 Oracle 的扩展加载然后重启一次。跳过这一步的表现是VirtualBox 界面能开但新建虚拟机后点启动直接报内核驱动加载失败。这个坑我踩过排查了半天以为是镜像坏了。3.2 Linux 主机包管理器装完还要重建内核模块Linux 桌面用户直接走包管理器更省事但装完不等于能用vboxdrv这个内核模块得手动重建一次sudo apt update # 主程序 扩展包USB 2.0/3.0 支持靠它装增强功能也会用到 sudo apt install virtualbox virtualbox-ext-pack # 关键编译内核模块必须有当前内核的头文件和编译工具 sudo apt install build-essential linux-headers-$(uname -r) # 重建 vboxdrv 模块装完、升级内核后都要跑 sudo /sbin/vboxconfig$(uname -r)会展开成当前运行的内核版本号比如5.4.0-150-generic头文件包名必须和它一字不差否则vboxconfig会报「Kernel headers not found」。vboxconfig这一步实际上是在编译vboxdrv、vboxnetflt、vboxnetadp三个模块编译日志在/var/log/vbox-install.log失败时先看这个文件而不是反复重装。3.3 新建虚拟机四个参数填对打开 VirtualBox点「新建」然后按下面这张表填。这里的每一步都不是随便填的填错了要么开不了机要么开机后卡成幻灯片。步骤参数填什么说明1名称任意如 GAMES101只影响显示名不影响运行2类型Linux选错类型会导致鼠标、分辨率异常3版本Ubuntu (64-bit)镜像是 64 位系统选 32 位无法启动4内存大小2048 MB文档建议值给 1GB 桌面会非常卡5虚拟硬盘使用已有的虚拟硬盘文件点文件夹图标 → 添加 → 选中解压出的 .vdi第 5 步是这套方案的核心。不要选「现在创建虚拟硬盘」那会新建一个空盘开机直接进安装程序等于白折腾。正确的动作是在虚拟硬盘页面选第三项「使用已有的虚拟硬盘文件」点右侧文件夹图标在弹出的窗口里点「添加」定位到.vdi文件选中后点「选择」再点「创建」。虚拟机建好后选中它点右侧上方的「启动」。第一次开机会有几秒黑屏属于正常解压内存页的过程。3.4 第一次登录密码与「先别升级系统」登录界面的用户名在镜像里已经配好直接输密码Ilovegraphics注意大小写I 是大写。进去之后你会看到一个完整的 Ubuntu 桌面左侧有 VS Code 和 Sublime 的图标。进来第一件事我的习惯是先断网或者先别点系统更新。这个镜像是 18.04.2源里那些包已经很老了一次apt upgrade有可能把 g 或内核顶到新版本而新内核和已经装好的 Guest Additions 版本不匹配增强功能会静默失效——表现就是拖放突然不能用了但没有任何报错。血泪经验镜像里给什么就用什么别手痒。4. Guest Additions、拖放、CMake把框架从主机搬进虚拟机并编译能开机只是第一步。不装增强功能虚拟机的分辨率锁死在 800x600文件拖不进去剪贴板不通写代码会非常难受装了增强功能但没配好拖放作业框架就只能靠 U 盘中转效率低还容易传错版本。这一章按「装增强功能 → 把文件递进去 → 配置编译链 → 跑出可执行文件」的顺序推一遍。4.1 图形界面装增强功能先补编译依赖图形界面的路径是虚拟机窗口上方菜单「设备」→「安装增强功能」。点了之后 VirtualBox 会把一个 ISO 挂到虚拟机的光驱里Ubuntu 桌面一般会自动弹出光盘窗口双击里面的VBoxLinuxAdditions.run选「在终端中运行」输密码等它跑完然后重启。但这一步失败率不低因为增强功能里有一部分是内核模块必须在虚拟机里现场编译而镜像里不一定装了编译工具和当前内核的头文件。稳妥的做法是先开终端补齐依赖再去点菜单# 编译增强功能模块所需的最小依赖集 sudo apt install build-essential linux-headers-$(uname -r) dkms # 确认当前内核版本后面看日志要用 uname -rbuild-essential提供 gcc/g/makelinux-headers-$(uname -r)是内核模块编译必需的dkms让模块在内核升级后自动重建。这三个包缺任何一个安装脚本都会在编译阶段失败并回滚界面上只是一闪而过看不出问题。4.2 图形界面失败时的命令行兜底四条命令逐条拆如果点了菜单没反应或者装完重启发现分辨率还是老样子就走命令行。文档给的四条命令是这套流程的保底方案我把它拆开讲清楚每条在干什么# 1. 建挂载点。-p 是短横线加小写 p不是排版里的长破折号 sudo mkdir -p /media/cdrom # 2. 把光驱设备挂到挂载点上-t auto 让系统自己识别文件系统类型 sudo mount -t auto /dev/cdrom /media/cdrom/ # 3. 进到挂载目录确认 VBoxLinuxAdditions.run 在里面 cd /media/cdrom/ ls # 4. 用 root 权限执行安装脚本 sudo sh VBoxLinuxAdditions.run第一条命令里的-p是最容易抄错的地方。文档排版时短横线被渲染成了长破折号–直接复制粘贴会变成mkdir –pshell 报mkdir: invalid option -- 新手很容易在这里卡住以为是自己环境有问题。-p的作用是「父目录不存在就一起建」不加的话/media存在但/media/cdrom不存在时会报错。第二条mount -t auto中auto表示让内核自己探测 ISO9660 文件系统/dev/cdrom是光驱设备的软链接实际指向/dev/sr0。如果提示「找不到介质」说明你在点菜单之前就把虚拟机的光驱弹出了重新点一次「设备 → 安装增强功能」再挂。第四条用sh而不是./是因为镜像里的脚本可能没有可执行权限sh会显式调用解释器绕过这个限制。安装过程会输出一大段编译日志最后如果看到You must restart your guest system就说明成功了重启生效。如果中途报错日志在/var/log/vboxadd-setup.log这是唯一的排查依据别凭感觉重装。4.3 拖放与共享文件夹大文件夹别用拖放增强功能装好、重启之后去「设备 → 拖放」选「双向」剪贴板也顺手设成「双向」。这样从主机直接往虚拟机窗口里拖文件就能落地。但拖放对大文件夹很不友好网络断一下或者拖到一半松手复制会中断且没有断点续传得到一个残缺的目录。作业框架虽然不大里面有一堆头文件和源码我一般改用共享文件夹稳得多# 先在 VirtualBox 菜单设备 - 共享文件夹 - 添加 # 路径选主机上的框架目录名称填 share勾选「自动挂载」 # 回到虚拟机把自己加进 vboxsf 组否则 /media/sf_share 无权访问 sudo usermod -aG vboxsf $USER # 组权限要重新登录才生效直接重启最省事 sudo rebootusermod -aG里的-a是 append不加的话会把用户从其他附加组里踢出去。加完必须重新登录否则groups命令里看不到 vboxsf访问/media/sf_share会一直提示权限不足这个现象特别像「共享文件夹没挂上」很多人会反复删了重建。4.4 Eigen 头文件与 CMakeLists编译链的两处关键配置框架进了虚拟机用 VS Code 打开File → Open Folder选中框架目录。注意是「打开文件夹」而不是「打开文件」CMake 和编译器需要在文件夹级别工作。Eigen 在镜像里是系统级安装的头文件位于/usr/include/eigen3/。所以引入语句必须写成带路径的形式// 注意是 eigen3/Eigen/Core不是 Eigen/Core #include eigen3/Eigen/Core #include eigen3/Eigen/Dense // 用矩阵分解、求解时才需要 #include iostream #include cmath如果只写#include Eigen/Core会报fatal error: Eigen/Core: No such file or directory。原因是系统把头文件装在了一个带版本号的子目录里编译器默认只搜/usr/include不会自己往下钻一层。两种解法一是按上面那样写全路径二是在 CMakeLists 里加include_directories(/usr/include/eigen3)然后代码里就能写短路径。我倾向第一种改动只在一行不用碰构建脚本。对应的 CMakeLists.txt 最小可用版本长这样cmake_minimum_required(VERSION 3.10) # 工程名随意但下面 add_executable 的目标名决定可执行文件名 project(Transformation) # 指定 C 标准Eigen 的部分模板特性需要 C11 以上 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Eigen 是纯头文件库不需要 find_package 和 target_link_libraries add_executable(Transformation main.cpp)add_executable(第一个参数 第二个参数 ...)的第一个参数就是生成的可执行文件名运行的时候敲./Transformation就是它第二个参数往后是所有参与编译的源文件后面作业加文件时记得往这里补漏了就会出现「函数找不到定义」的链接错误。Eigen 全部实现都在头文件里所以既不用find_package也不用链接.so这一点和 OpenCV 那类库完全不同配置起来省事很多。4.5 从 cmake .. 到 ./Transformation一次完整跑通编译流程文档写得很清楚我用带注释的方式再走一遍每一条都可以直接抄# 在 main.cpp 所在目录下执行 mkdir build # 建独立构建目录别在源码目录里直接 cmake cd build # 进构建目录 cmake .. # .. 表示去上一级找 CMakeLists.txt. 表示当前目录别写错 make # 编译报错会直接打在终端里 ./Transformation # 运行名字来自 CMakeLists.txt 里的 add_executablecmake ..里的两个点是最容易写错的地方。写成cmake .会去 build 目录里找 CMakeLists.txt找不到就报The source directory does not appear to contain CMakeLists.txt。mkdir build这一步也不是可有可无的仪式感CMake 会在构建目录里生成一堆CMakeCache.txt、CMakeFiles/如果直接在源码目录里构建清理起来很麻烦而且CMakeCache.txt会记住上一次的路径配置换目录后容易出各种诡异错误。make成功时会输出类似[100%] Built target Transformation。如果这一步报错重点看第一条 error 而不是最后一条编译器后面的报错往往是第一条引发的连锁反应。5. 排查手册五条踩坑记录按现象、原因、解决写这套环境最大的问题是失败时没有明显提示要么静默失效要么报一个和真实原因八竿子打不着的错。下面五条是我和身边人踩过的按「现象 → 原因 → 解决」记下来。5.1 现象点「安装增强功能」没反应或者装完重启分辨率还是 800x600现象菜单点了光驱也挂上了双击脚本一闪就没了重启之后窗口还是不能自适应大小拖放用不了剪贴板也不通。原因增强功能安装脚本在编译内核模块时失败了而它默认不把错误顶到前台。最常见的是缺linux-headers-$(uname -r)其次是镜像的 gcc 版本和内核头文件版本对不上。安装脚本失败后会回滚已复制的文件所以你在界面上完全看不出装过。解决先补依赖再重装然后看日志确认。sudo apt install build-essential linux-headers-$(uname -r) dkms sudo sh /media/cdrom/VBoxLinuxAdditions.run # 失败的话日志是唯一线索 tail -n 50 /var/log/vboxadd-setup.log日志里出现Kernel headers not found就是头文件没装对版本出现gcc: command not found就是build-essential没装上。重装前先sudo /media/cdrom/VBoxLinuxAdditions.run uninstall清一次残留直接覆盖安装有时会卡在「已有版本」的判断上。装完必须完整重启不是注销。5.2 现象拖放勾了「双向」文件拖进去还是没反应现象「设备 → 拖放」明明选了双向从主机拖一个文件夹进来鼠标变了形状松手之后什么都没有。原因三种可能。一是增强功能没装成功拖放的实现依赖它功能项在界面上能选但底层没有服务响应二是拖放对大目录不稳定文件多了会静默中断三是复制到了需要权限的位置比如/usr或/etc。解决先确认增强功能状态再看内核模块是否加载# 有输出说明模块已加载没输出就是增强功能没装上 lsmod | grep vboxguest # 换个有写权限的目标目录再试一次比如桌面或家目录 cd ~/Desktop pwdlsmod列出已加载的内核模块vboxguest和vboxsf是增强功能的核心模块。如果这里为空回到 5.1 处理。如果模块在、功能还是不正常就别跟拖放较劲了改用 4.3 节的共享文件夹稳定得多。5.3 现象编译报 fatal error: eigen3/Eigen/Core: No such file or directory现象代码里明明写了#include eigen3/Eigen/Coremake还是说找不到文件。原因镜像里根本没装 Eigen或者装在了非标准路径。少数情况是有人为了「干净」重装过系统把预装环境清掉了。解决先找文件在哪找不到再装# 全盘找 Core 头文件的真实位置 find /usr -name Core -path *Eigen* 2/dev/null # 找不到就装Ubuntu 的官方包名是 libeigen3-dev sudo apt install libeigen3-dev # 装完确认版本目录 ls /usr/include/eigen3/Eigen/ | headfind里的2/dev/null是把权限不足的报错丢掉避免输出被刷屏。装完libeigen3-dev后头文件会落在/usr/include/eigen3/包含路径按 4.4 节写就行。如果装在别的位置就在 CMakeLists 里加include_directories(实际路径)不要靠复制文件到/usr/include这种野路子后面换机器会全部失效。5.4 现象undefined reference toxxx现象编译阶段没有任何报错链接阶段突然冒出一堆undefined reference to指向自己写的函数。原因函数声明了、也调用了但定义所在的源文件没参与编译。在这个作业里通常是往框架里新增了.cpp文件却忘了写进CMakeLists.txt的add_executable。解决把新文件加进构建目标然后删掉构建目录重来一次# 编辑 CMakeLists.txt把所有源文件都列上 # add_executable(Transformation main.cpp transform.cpp) # CMake 缓存会记住文件列表改完必须重新生成 cd build rm -rf * cmake .. makerm -rf *在 build 目录里执行是安全的它只清构建产物。很多人改完 CMakeLists 直接make发现还是老错误就是因为缓存没刷新。另一种情况是把 Eigen 相关的模板函数写在了.cpp里却没有正确实例化这类报错信息会带一长串模板参数特征很明显把实现挪回头文件即可。5.5 现象程序能编过一运行就 Segmentation fault 或者输出乱码现象make显示Built target./Transformation一敲下去直接Segmentation fault (core dumped)或者打印出一堆不符合预期的数字。原因段错误基本是数组越界或者访问了未初始化的指针输出不对则多是两个坑——三角函数用了角度制而不是弧度制或者 Eigen 的赋值时行列数填错。这个作业里最常见的低级错误是std::sin(45)C 的三角函数只吃弧度45 度得写成45.0/180.0*std::acos(-1)。解决先定位崩在哪一行再逐项核对数值。# 用 gdb 跑崩了会停在出错行 gdb ./Transformation # 进去后输入 run崩溃后输入 bt 看调用栈 # 不加调试器的话编译时带 -g -fsanitizeaddress 也能直接指出越界位置bt是 backtrace会打出调用栈一眼能看出是哪个函数越界。至于数值问题用std::cout把中间结果打出来旋转矩阵乘完先看一眼是不是 (0.707, 2.121)对不上就是角度制搞错了。Eigen 的是按行填充的3x3 矩阵写了 9 个数但顺序按列来理解结果会完全错位这个错误编译器不会提醒。6. 验证跑通三组输出对得上才算环境真的配好装完不算完得能证明它真的对。我一般用三层验证从环境到代码逐层收敛任何一层对不上就说明前面某步有问题。第一层验证增强功能。拖动窗口边缘改变大小桌面分辨率跟着变、不出现黑边说明 Guest Additions 生效了lsmod | grep vboxguest有输出说明模块加载正常。这一层不过后面文件传输会很痛苦。第二层验证样例程序。框架里的main.cpp本身就是一份环境自检代码跑出来的五个数字应该和下面这张表对得上输出内容预期值对不上说明什么a1基础输出流有问题a/b0.5浮点除法写成了整除sqrt(a)1数学头文件没引对acos(-1)3.14159圆周率常量正常sin(30.0/180.0*acos(-1))0.5三角函数用的弧度制最后一行是关键。如果你把30.0/180.0*acos(-1)换成sin(30)输出会是 -0.988看着像个正常数字其实完全错了。这种错不会报错、不会崩溃只会让你在后面的作业里一直怀疑自己的数学。第三层验证这道题本身。点 P(2,1) 先逆时针转 45 度旋转后的坐标是 (0.7071, 2.1213)再平移 (1,2) 得到(1.7071, 4.1213)。用齐次坐标写就是三行代码的事但顺序不能反// 齐次坐标二维点补一维 1才能用 3x3 矩阵同时表达旋转和平移 Eigen::Vector3f p(2.0f, 1.0f, 1.0f); float theta 45.0f / 180.0f * std::acos(-1.0f); // 弧度制 Eigen::Matrix3f rot; rot std::cos(theta), -std::sin(theta), 0.0f, // Eigen 的 按行填 std::sin(theta), std::cos(theta), 0.0f, 0.0f, 0.0f, 1.0f; Eigen::Matrix3f trans; trans 1.0f, 0.0f, 1.0f, // 平移量写在第三列 0.0f, 1.0f, 2.0f, 0.0f, 0.0f, 1.0f; // 矩阵从右往左作用先旋转再平移 Eigen::Vector3f result trans * rot * p; std::cout result std::endl;trans * rot * p这个顺序是整道题的考点。写成rot * trans * p就变成了「先平移再旋转」结果会差得很远而且程序照样能跑、照样有输出。齐次坐标的第三维那个 1 也别省省了就只能做 3x3 的旋转平移没地方放。三层都对上这套环境才算真的装好了。从那以后我每次配新机器都会先跑一遍样例程序看那五个数再动笔写作业代码——环境问题混在业务代码里排查成本要高十倍。希望这套流程能帮你少走点弯路。本文还有配套的精品资源点击获取