资讯动态

图形学作业0实战:VirtualBox+Ubuntu+Eigen构建旋转平移环境

发布时间:2026/10/17 19:12:35 来源:尧图企业网站定制
简介《Games101-作业0pdf》是计算机图形学课程 GAMES101闫令琪主讲作业零的官方任务书面向选修该课的学生旨在帮助其通过虚拟机完成统一开发环境的配置并提交第一份作业。包内仅含1个PDF文档压缩包大小874KB文件精简但信息完整覆盖从 VirtualBox 安装、虚拟硬盘下载、Ubuntu 系统配置到 Guest Additions 增强功能安装、作业框架传输与编辑的全流程要点同时附有 C 常见错误与 Eigen 库使用注意事项。借助这份指南读者可在自身操作系统上快速搭建可复现的图形学实验环境避免因环境不一致导致的编译与运行问题文档还明确了截止时间、提交平台及独立完成要求适合正在学习 GAMES101 或需要上手虚拟机作业流程的初学者参考。该资源目前已有433人学习使用对入门计算机图形学作业具有直接帮助。1. 图形学作业0开课第一关居然是装虚拟机很多初次接触这台图形学课程的人第一眼看到“作业0”会以为是一道热身算法题结果打开文档才发现任务本身只是一个点绕原点旋转再平移的矩阵计算真正的门槛全在环境上。这门课要求用虚拟机里的 Ubuntu 系统完成编译和运行而不是在自己电脑上折腾依赖目的是让所有人在同一套环境里跑代码避免“我机器上能跑”这类玄学问题。作业0的价值不在于算那一个点而在于把 VirtualBox、Ubuntu 镜像、Guest Additions、VSCode、Eigen 和 CMake 这一整条链路走通。适合刚开始接触图形学方向、想从零搭一套可复现实验环境的开发者也适合被各种库依赖折磨过、想省心的人。2. 虚拟机选型与整条环境链为什么非要用 Ubuntu 18.04 镜像2.1 统一环境的思路镜像比手动配置靠谱得多这份作业框架的核心思路是课程方直接提供一个已经装好编译器、编辑器、Eigen 库和依赖的虚拟硬盘文件学生只需要把它挂载到虚拟机里就能直接开工。常见做法是自己在本地装双系统或者用 Docker 拉镜像但这两种方式对学生来说都有门槛双系统伤硬盘分区Docker 又需要理解容器挂载和图形界面转发。用虚拟硬盘文件的好处在于环境是“固态”的出了问题删掉重来不影响宿主机。我在自己拆这个资源的时候第一反应是去确认这个 vdi 镜像的系统版本。文档里写的是 Ubuntu 18.04.2 64 位内核版本对应的编译器是 GCC 7.5。这个版本选得比较保守因为 Eigen 3.3 和 CMake 3.10 在 18.04 的 apt 源里都是现成的不需要手动编译安装。如果你拿到手的镜像版本比这个新比如 Ubuntu 20.04 或 22.04那 Eigen 的路径和 CMake 的最低版本要求可能会略有差异不过后面讲的配置步骤通用。2.2 VirtualBox 安装与虚拟机参数参考表宿主机操作系统不同安装 VirtualBox 的方式也不同。Windows 和 macOS 直接下载对应安装包Linux 用户用发行版的包管理器安装。以下是我建议的参数配置参考了原文档的推荐值也补上了我自己实跑之后觉得合理的选项。配置项推荐值说明操作系统类型Linux / Ubuntu 64-bit必须选 64 位否则无法加载 vdi内存大小2048 MB原文档推荐 2GB低于此值编译时容易卡顿处理器核心2 核单核编译 Eigen 头文件会比较慢显存128 MB默认值偏低调高能缓解图形界面卡顿硬盘控制器SATA / IDE 均可导入 vdi 时使用“使用已有的虚拟硬盘文件”拖放双向没有这个设置文件拖不进虚拟机创建虚拟机的过程很直接打开 VirtualBox点“新建”按照表格里的参数设置然后在硬盘这一步选择“使用已有的虚拟硬盘文件”指向解压出来的.vdi文件。这里有一个容易忽略的点如果 VirtualBox 提示 vdi 无法访问多半是文件路径里含有中文或空格把 vdi 放到纯英文路径下再导入即可。2.3 启动系统后的第一件事安装 Guest Additions进入 Ubuntu 系统后建议先做两件事第一确认系统能正常上网因为后面可能要更新 apt 源第二马上安装 Guest Additions。这个增强包提供三个关键能力自适应分辨率、拖放文件传输、剪贴板共享。没有装好它你会在“怎么把作业代码拷进虚拟机”这一步浪费大量时间。图形界面安装方式是点设备菜单里的“安装增强功能”但这种方式经常遇到失败原因多是光驱挂载异常或依赖未装全。我一般会直接切到命令行用兜底方式装sudo mkdir -p /media/cdrom sudo mount -t auto /dev/cdrom /media/cdrom cd /media/cdrom sudo sh VBoxLinuxAdditions.run第一行创建挂载目录第二行把虚拟光驱里的增强功能镜像挂载进来第三行进入挂载点第四行执行安装脚本。装完重启分辨率应该会自动适配窗口大小拖放功能也会生效。如果执行 VBoxLinuxAdditions.run 时报缺少编译内核模块的错需要先执行sudo apt-get install linux-headers-$(uname -r) build-essential再重试。2.4 作业框架传输拖放虽方便建议多做一步备份开启双向拖放后直接把主机上下载好的作业框架拖进虚拟机桌面即可。这个操作对小型目录没问题但里面文件较多时容易拖到一半中断。我一般会用 VirtualBox 的“共享文件夹”功能做传输比拖放可控得多。设置共享文件夹时注意勾选“自动挂载”然后新建一个挂载点手动 mount。sudo mkdir -p /home/student/hostshare sudo mount -t vboxsf shared /home/student/hostshare其中shared是你在 VirtualBox 设置里定义的共享文件夹名称。挂载成功后把作业框架拷贝到 home 目录下再解压使用不要直接在共享文件夹里编译vboxsf 文件系统在大量小文件读写时会明显偏慢。这一条不算踩坑但能让人舒服不少。3. C 编译细节与 Eigen 最小实践先把例程编译跑通3.1 从 main.cpp 入手头文件、函数体与输出格式作业框架里的 main.cpp 是一个完整的可运行程序里面包含 C 头文件引入、main 函数定义以及 Eigen 向量矩阵的使用样例。先把这段程序跑通就已经完成了作业的一半。以下是最精简的启动版本#include iostream #include cmath #include eigen3/Eigen/Core int main() { float a 1.0f, b 2.0f; std::cout a std::endl; std::cout a / b std::endl; std::cout std::sqrt(a) std::endl; std::cout std::acos(-1) std::endl; std::cout std::sin(30.0f / 180.0f * std::acos(-1)) std::endl; return 0; }这段代码输出五行的含义分别是a 的值、a 除以 b 的结果、a 的平方根、arccos(-1) 也就是圆周率 π、以及 sin(30°) 的值。注意最后一行三角函数参数是 30 度换算成弧度后的结果C 的数学库只认弧度不认角度这是新手最容易忽略的地方。输出1 0.5 1 3.14159 0.5五个值说明环境基本正常。3.2 Eigen 的向量与矩阵先亲自动手改一遍Eigen 是这套作业里唯一的第三方库它的头文件路径是eigen3/Eigen/Core注意前面带着eigen3/前缀。很多人在自己的机器上编译 Eigen 程序时习惯写Eigen/Core那是因为通过pkg-config配置了额外的 include 路径在这台虚拟机的默认环境里必须带上eigen3/前缀。Eigen 的向量和矩阵用法可以分成四类操作定义、输出、加减数乘、矩阵乘法。下面这段代码把框架里提到的样例整理成一个连续的小实验#include iostream #include eigen3/Eigen/Core int main() { // 三维浮点向量定义 Eigen::Vector3f v(1.0f, 2.0f, 3.0f); Eigen::Vector3f w(1.0f, 0.0f, 0.0f); std::cout v w (v w).transpose() std::endl; std::cout v * 3 (v * 3.0f).transpose() std::endl; std::cout dot(v,w) v.dot(w) std::endl; // 三阶浮点矩阵定义按行语义填写 Eigen::Matrix3f i, j; i 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f; j 2.0f, 3.0f, 1.0f, 4.0f, 6.0f, 5.0f, 9.0f, 7.0f, 8.0f; std::cout i * j std::endl i * j std::endl; std::cout i * v (i * v).transpose() std::endl; return 0; }这里有几个值得说明的点。Vector3f和Matrix3f的f后缀表示使用float类型作业里的坐标数值都很小float 精度完全够用。矩阵用操作符初始化时数据是逐行填入的但它底层存储是列优先你不需要关心存储顺序Eigen 会处理好。dot是向量的点积矩阵乘法直接用*没有特殊语法。我自己第一次用 Eigen 时踩过坑以为矩阵乘法要写i.multiply(j)实际上重载运算符已经实现了。3.3 常见编译错误速查报错信息里藏着答案C 首次作业的报错基本集中在五类。Compile Error 是语法或类型错误报错信息里会直接指出文件与行号undefined reference 是链接错误说明某个符号没有找到实现这时候去看 CMakeLists.txt 里有没有包含对应模块Segmentation Fault 通常是数组越界或非法内存访问Bus Error 和段错误成因相似多半出现在内存对齐出问题时Math Error 一般是除数为零检查分母变量有没有被赋零值。遇到报错时先读最上面的内容不要盯着最后一行看编译器真正的原因往往藏在第一行。4. 齐次坐标实现旋转平移从数学推导到可编译代码4.1 先手算一遍旋转 45 度加平移 (1, 2)题目要求把点 P(2, 1) 绕原点逆时针旋转 45 度再平移 (1, 2)用齐次坐标完成计算。图形学里的标准做法是把二维点扩展成三维齐次坐标点记作 (x, y, 1)旋转矩阵和平移矩阵都升维到 3x3然后按“先旋转再平移”的顺序做矩阵乘法。逆时针旋转 45 度对应的旋转矩阵是cos45° -sin45° 0 sin45° cos45° 0 0 0 1平移 (1, 2) 的齐次变换矩阵是1 0 1 0 1 2 0 0 1最终的变换矩阵是平移矩阵乘以旋转矩阵即 M T * R。因为旋转是相对于原点做的平移又发生在旋转之后所以不能简单地把旋转结果和平移分量分开算。先算旋转P R * P (2 * cos45° - 1 * sin45°, 2 * sin45° 1 * cos45°)。cos45° 和 sin45° 都是 √2/2 ≈ 0.70710678所以旋转后坐标为 (0.70710678, 2.12132034)。再整体平移结果就是 (1.70710678, 4.12132034)。这个值会作为我们验证代码输出是否正确的基准为什么强调这个因为很多同学写完代码发现输出的是x1.70711, y4.12132但不敢确定对不对。4.2 用 Eigen 4x4 矩阵实现完整可编译的 main.cpp虽然二维齐次坐标用 3x3 矩阵足够但框架里 Eigen 示例演示的是Matrix3f作业题目又要求“用齐次坐标”我建议直接用 4x4 矩阵Matrix4f把二维点写成 (x, y, 0, 1)这样可以统一处理旋转和平移也为以后的三维变换作业铺路。下面是可以直接编译运行的完整实现#include iostream #include cmath #include eigen3/Eigen/Core int main() { using namespace Eigen; // 角度转弧度 float theta 45.0f / 180.0f * std::acos(-1.0f); // 平移矩阵 T最后一列为平移分量 Matrix4f T Matrix4f::Identity(); T(0, 3) 1.0f; T(1, 3) 2.0f; // 旋转矩阵 R左上角 2x2 为旋转部分 Matrix4f R Matrix4f::Identity(); R(0, 0) std::cos(theta); R(0, 1) -std::sin(theta); R(1, 0) std::sin(theta); R(1, 1) std::cos(theta); // 齐次坐标表示点 P(2,1) Vector4f P(2.0f, 1.0f, 0.0f, 1.0f); // 先旋转再平移 Vector4f rotated R * P; Vector4f result T * rotated; std::cout After rotation: rotated.transpose() std::endl; std::cout After transform: result.transpose() std::endl; return 0; }预期输出为After rotation: 0.707107 2.12132 0 1和After transform: 1.70711 4.12132 0 1。代码逻辑分三步定义旋转矩阵和平移矩阵将点表示为齐次向量按变换顺序做乘法。注意T * rotated与rotated * T的结果完全不同矩阵乘法不满足交换律。transpose()是为了让输出变成单行否则 Eigen 默认按列输出也会可读但单行更直观。4.3 CMake 编译从命令行到构建脚本作业要求用 CMake 构建这也是图形学课程后续作业的标准方式。在 main.cpp 所在目录下创建 CMakeLists.txt内容如下cmake_minimum_required(VERSION 3.10) project(Transformation) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Eigen3 REQUIRED) add_executable(Transformation main.cpp) target_link_libraries(Transformation Eigen3::Eigen)然后打开终端依次执行mkdir build cd build cmake .. make ./Transformationcmake ..中的..指向上一级目录也就是 CMakeLists.txt 所在位置。如果你在 build 目录里写了cmake .它会认为当前目录才是项目根目录找不到 CMakeLists.txt 就会报错。find_package(Eigen3 REQUIRED)会从系统路径里寻找 Eigen3 的配置文件Ubuntu 18.04 的镜像里预装了 Eigen所以这一步不会失败。如果是在自己的机器上复现需要先sudo apt install libeigen3-dev。5. 环境配置避坑录五条能救命的排查记录5.1 虚拟机启动黑屏光标闪烁不进系统现象新建虚拟机并挂载 vdi 后点击启动屏幕黑屏且光标闪烁等几分钟依然无反应。原因宿主机没有开启 CPU 虚拟化或者内存分配过小。解决进入宿主机 BIOS找到 Intel VT-x 或 AMD-V 选项并开启同时确认虚拟机内存不低于 2048 MB。排查时可以打开 VirtualBox 的设置界面看“系统 - 处理器”里是否勾选了“启用 PAE/NX”有些老版本需要手动开。5.2 设备菜单里的“安装增强功能”灰色不可点现象进入 Ubuntu 桌面后发现设备菜单下“安装增强功能”是灰色状态无法点击。原因虚拟光驱没有加载增强功能的镜像文件通常发生在 vdi 导入时没有保留原始光驱控制器配置。解决在虚拟机关机状态下进入设置 - 存储找到光驱设备点击光盘图标选择“选择一个虚拟光盘”手动定位到 VirtualBox 安装目录下的VBoxGuestAdditions.iso再启动系统安装。5.3 拖放功能打开双向了文件还是拖不进去现象设置里已经将拖放改为双向但从宿主机拖文件到虚拟机时鼠标指针出现禁止符号。原因Guest Additions 没有真正安装成功或者安装后没有重启系统。解决重新执行命令行安装结束后reboot再检查是否生效。另外不要拖拽包含中文文件名的目录vboxsf 对中文支持并不稳定。这个问题如果换了三个方法还不行直接改用共享文件夹不要死磕。5.4 编译时报 Eigen 头文件找不到现象执行make时出现fatal error: eigen3/Eigen/Core: No such file or directory。原因CMakeLists.txt 中没有调用find_package(Eigen3)或者头文件包含路径写成了Eigen/Core。解决在 CMakeLists.txt 里添加find_package(Eigen3 REQUIRED)和target_link_libraries(Transformation Eigen3::Eigen)并且代码里必须写eigen3/Eigen/Core。如果两台机器之间迁移代码建议保留这个前缀写法。5.5 输出结果与手算不一致小数点后偏差大现象程序输出的结果是1.70712和4.12131与手算结果最后一位有偏差。原因浮点运算的精度损耗以及输出流的默认精度只显示 6 位有效数字。解决用std::cout std::fixed std::setprecision(6)控制输出格式或者在代码里用std::abs(result.x() - 1.70710678f) 1e-6做自动断言比肉眼对比靠谱得多。6. 验证技巧与编译流固化把作业0的检查变成脚本手算值和代码输出对上了作业 0 的核心任务就算完成但我建议多走两步把验证过程脚本化顺便试一下逆变换验算法。具体思路是用 Eigen 求变换矩阵的逆然后把结果点再做一次逆变换如果最终回到了 (2, 1)说明正向矩阵没有写错。#include iostream #include cmath #include eigen3/Eigen/Core #include eigen3/Eigen/Geometry int main() { using namespace Eigen; float theta 45.0f / 180.0f * std::acos(-1.0f); Matrix4f T Matrix4f::Identity(); T(0, 3) 1.0f; T(1, 3) 2.0f; Matrix4f R Matrix4f::Identity(); R(0, 0) std::cos(theta); R(0, 1) -std::sin(theta); R(1, 0) std::sin(theta); R(1, 1) std::cos(theta); Matrix4f M T * R; Vector4f P(2.0f, 1.0f, 0.0f, 1.0f); Vector4f result M * P; Matrix4f M_inv M.inverse(); Vector4f back M_inv * result; float err (back - P).norm(); std::cout round-trip error err std::endl; std::cout back to origin back.transpose() std::endl; return err 1e-5f ? 0 : 1; }这段代码的第二行引入了eigen3/Eigen/Geometry里面包含transform相关的扩展功能inverse()函数本身在 Core 模块就有但加上这个头文件不会有副作用。逻辑是求 M 的逆矩阵将变换后的结果反向乘回去再计算与原点的欧氏距离。误差在 1e-5 以内说明正向变换和矩阵组合都写对了程序退出码 0 也可以被脚本捕获。验证完正确性我最后做的一件事是把编译和运行固化成一个脚本避免每次打开终端敲五条命令也防止自己手滑敲错目录名#!/usr/bin/env bash set -e rm -rf build mkdir build cd build cmake .. -DCMAKE_BUILD_TYPEDebug make ./Transformation把这段内容保存为run.sh执行chmod x run.sh之后每次改完代码只需要./run.sh一条命令。set -e的作用是让脚本在任何一步失败时立即退出这样错误能被第一时间看到而不是跳过编译失败继续运行旧版本的程序。-DCMAKE_BUILD_TYPEDebug会带上调试信息配合 gdb 排查越界问题时能直接看到源码行号。作业0这门课不评分但它的隐藏价值是逼你把工具链在开课前彻底跑通。从那以后我每次接手新环境都强制走一遍这个流程装虚拟机、跑最小 Eigen 程序、验证矩阵逆变换、固化编译脚本整个过程不到十分钟却能把八成环境问题挡在正式作业之前。希望这次拆解帮到你也让你后面的每一次“作业1”都少一点环境折腾。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑