图形学作业0:虚拟机环境、Eigen与齐次坐标变换全链路 简介Games101-作业0pdf 面向计算机图形学初学者与 GAMES101 课程学习者聚焦作业 0 的虚拟机环境搭建与使用帮助读者解决课程环境配置不统一、上手门槛较高的问题。压缩包内共 1 个 PDF 文件大小约 874KB内容围绕 Oracle VM VirtualBox 安装、虚拟硬盘文件下载与挂载、Guest Additions 增强功能配置以及作业框架在主机与虚拟机之间的传输和编辑展开。文档还说明了 Ubuntu 系统密码、拖放功能开启方式并简要介绍 Visual Studio Code、Sublime 等开发工具附带 C 头文件、函数体示例及编译错误、链接错误、段错误等常见问题的排查提示同时提及 Eigen 线性代数库。已有 435 人学习适合需要按统一环境完成作业 0、快速熟悉虚拟机操作与 C 作业框架的读者参考。1. 图形学作业0 的第一道门槛不是代码是虚拟机环境图形学作业0 的 PDF 一到手很多人先翻到最后看题目觉得不就是把点转一下再平移代码能有多难。真正卡住的反而是虚拟机虚拟硬盘镜像挂不上、增强功能装完拖放没反应、终端里编译器找不到头文件。这份作业0 文档的价值就在这儿它把统一环境、C 编译链、线性代数库和齐次坐标变换串成一条可以照着跑的线而不是扔一个题目让你自己猜环境。适合刚接触图形学、准备做后续作业的人也适合想拆作业框架的熟手。先把虚拟机跑通再理解矩阵左乘顺序最后用构建工具把程序编出来这三步走完作业0 才算真正落地。环境配置有时候很玄学但这份文档把容易翻车的点基本都标出来了。2. 环境链路拆解从虚拟硬盘镜像到能编译的终端2.1 安装虚拟机软件与挂载虚拟硬盘课程给的是虚拟硬盘镜像不是完整安装盘。这个选择很实际图形学作业最怕每个人机器上的编译器版本、库路径、系统依赖不一样最后代码在你那儿能跑、在别人那儿报一堆链接错误。用统一镜像把环境锁住省掉大量“我这边明明是好的”的扯皮。我一般会先确认主机 BIOS 里 CPU 虚拟化已经打开再装虚拟机软件不然新建完虚拟机启动时直接黑屏连报错都懒得给。安装完虚拟机软件后新建虚拟机的步骤不复杂但有几个参数别乱改。类型选 Linux版本选 64 位内存给 2GB 左右课程文档里也是这个建议。内存给小了编译大一点的文件会卡给大了主机本身吃不消。关键一步是选择“使用已有的虚拟硬盘文件”把课程提供的镜像挂上去而不是新建一块空硬盘。镜像挂错启动后看到的是安装界面不是课程环境很多人到这一步才发现自己选成了“现在创建虚拟硬盘”。启动虚拟机后系统会要求输入密码课程文档里给了默认密码照着输就行。进去之后先别急着改代码打开终端敲pwd、ls确认当前用户目录和作业框架目录能对上。常见做法是把作业框架放在用户目录下路径里不要带中文和空格后面构建工具最怕这种路径。虚拟硬盘镜像本身建议保留一份原始备份改坏了直接换回来比在系统里修半天快。提示虚拟机内存、CPU 核心数、显存这些参数按主机配置给不要照搬别人的数值。编译卡顿先看内存拖放失败先看增强功能不要一上来就重装系统。2.2 增强功能与共享拖放为什么装了还是拖不进去增强功能装不装直接影响使用体验。不装的时候虚拟机窗口分辨率可能固定得很小鼠标进出要按切换键主机和虚拟机之间拖文件也没反应。课程文档里给了菜单安装和终端安装两条路菜单安装失败是很常见的情况别慌终端里手动挂载光盘再执行脚本就行。下面这段命令是终端安装的通用流程脚本名以ls列出来的实际文件为准。sudo mkdir -p /media/cdrom sudo mount -t auto /dev/cdrom /media/cdrom cd /media/cdrom ls # 找到增强功能安装脚本后执行脚本名以 ls 结果为准 sudo sh ./增强功能安装脚本.runsudo用来拿临时管理员权限mkdir -p会一次性建好目录已经存在也不报错。mount -t auto让系统自己识别光盘文件系统/dev/cdrom是光驱设备/media/cdrom是挂载点。执行完安装脚本后重启虚拟机分辨率自适应和鼠标无缝切换一般就正常了。如果重启后还是拖不进去先看虚拟机设置里的拖放选项是不是“双向”再看增强功能是不是真的装完了最后才怀疑镜像本身。拖放失败还有一层原因有些桌面环境对拖放支持不完整尤其是从主机往虚拟机里拖文件夹。我的血泪经验是大文件别硬拖用共享文件夹或者压缩包传。共享文件夹在虚拟机设置里指定主机目录虚拟机里挂载后当普通目录用比拖放稳。作业框架这种带多个源文件、头文件、构建脚本的目录压缩成包再传解压后路径更干净构建工具也不容易因为文件权限问题翻车。2.3 代码编辑器与终端的分工课程镜像里一般会带代码编辑器打开方式是在文件管理器里右键作业框架目录选择用编辑器打开或者在编辑器里选“打开文件夹”。注意是打开文件夹不是单独打开main.cpp。只打开单文件时编辑器的工作目录可能不对头文件跳转和构建命令都会出问题。打开文件夹后左侧能看到main.cpp、CMakeLists.txt这些文件终端里再cd到对应目录编辑和编译各干各的互不干扰。我不建议在编辑器里点“运行”按钮跑这种作业。课程要求用命令行构建终端里能看到完整的编译输出报错行号、链接信息、警告都在排查起来比编辑器底部弹一行红字清楚。常见做法是左边编辑器改代码右边终端执行mkdir build、cd build、cmake ..、make出错就回到编辑器改改完再make。构建目录独立出来源文件目录不会被中间文件污染删起来也方便。还有个小细节终端当前目录一定要在build里执行cmake ..两个点表示上一级目录。很多人写成cmake .结果在build目录里找不到CMakeLists.txt报错信息看起来像构建工具坏了其实只是路径不对。编译成功后执行./Transformation如果提示找不到命令先ls看可执行文件在不在当前目录再看CMakeLists.txt里生成的可执行文件名是不是这个。3. 作业框架里的 C 与线性代数库main.cpp 逐段拆3.1 头文件与输出先确认编译链能跑作业框架的main.cpp一开始不会直接上矩阵而是先用几行输出确认环境没问题。头文件一般包含cmath和iostream前者提供三角函数、平方根这些数学函数后者提供std::cout输出。线性代数库的头文件单独引入课程环境里通常是eigen3/Eigen/Core这个路径和安装位置有关换环境时最容易在这里报“找不到头文件”。#include cmath #include iostream #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.0f) std::endl; std::cout std::sin(30.0f / 180.0f * std::acos(-1.0f)) std::endl; return 0; }这段代码的输出分别是1、0.5、1、圆周率、30 度的正弦值。std::acos(-1.0f)用来拿圆周率比手写3.14159稳。三角函数用的是弧度制所以30.0f / 180.0f * std::acos(-1.0f)先把角度转成弧度再传给std::sin。如果直接写std::sin(30.0f)输出不是 0.5这种结果不对不是编译器的问题是单位没换。float后面带f是告诉编译器这是单精度浮点字面量避免和double混用时的隐式转换警告。std::endl除了换行还会刷新输出缓冲调试时看输出更及时。编译时如果报cmath找不到先看编译器是不是装全了报线性代数库头文件找不到先看 include 路径不要急着改代码逻辑。3.2 向量与矩阵线性代数库的初始化顺序和弧度制线性代数库的向量定义很直观三维浮点向量直接给三个分量。输出、加法、数乘都能直接用运算符和数学写法接近。矩阵初始化要注意是按行填充的写的时候一行一行排别按列填否则矩阵转置了还看不出来最后变换结果怎么都对不上。Eigen::Vector3f v(1.0f, 2.0f, 3.0f); Eigen::Vector3f w(1.0f, 0.0f, 0.0f); std::cout v w std::endl; std::cout v * 3.0f std::endl; Eigen::Matrix3f i; i 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f; Eigen::Matrix3f j; 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; std::cout i * 2.0f std::endl; std::cout i * j std::endl; std::cout i * v std::endl;Eigen::Vector3f是三分量单精度向量Eigen::Matrix3f是 3×3 单精度矩阵。矩阵和向量相乘时维度要匹配i * v是 3×3 乘 3×1结果是 3×1 向量。矩阵乘法不满足交换律i * j和j * i结果不一样写变换矩阵时顺序错了结果就偏了。数乘和加法按元素来输出时线性代数库会排成矩阵形状刚接触时容易把输出当成代码格式错乱其实是正常的。点积的写法在官方文档里有常见的是v.dot(w)也可以写成v.transpose() * w。作业0 主要用矩阵乘向量点积先放一边。这里的关键不是背 API而是确认矩阵初始化顺序、向量维度、乘法方向这三件事。后面写旋转矩阵和平移矩阵全靠这三件事撑着。3.3 齐次坐标变换旋转和平移的矩阵写法作业0 的题目是给定点 P(2,1)绕原点逆时针旋转 45 度再平移 (1,2)用齐次坐标计算变换后的坐标。二维点写成三维齐次坐标就是(2,1,1)旋转矩阵放在左上角 2×2 区域平移量放在第三列前两行最后一行保持0,0,1。先旋转再平移矩阵乘法顺序是T * R * p不是R * T * p顺序反了结果完全不同。#include cmath #include iostream #include eigen3/Eigen/Core int main() { float theta 45.0f / 180.0f * std::acos(-1.0f); float c std::cos(theta); float s std::sin(theta); // 齐次坐标下的点 P(2,1) Eigen::Vector3f p(2.0f, 1.0f, 1.0f); // 逆时针旋转 45 度 Eigen::Matrix3f R; R c, -s, 0.0f, s, c, 0.0f, 0.0f, 0.0f, 1.0f; // 再平移 (1,2) Eigen::Matrix3f T; T 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 2.0f, 0.0f, 0.0f, 1.0f; Eigen::Vector3f result T * R * p; std::cout result std::endl; return 0; }theta把 45 度转成弧度c和s分别是余弦和正弦。旋转矩阵第一行c, -s, 0第二行s, c, 0第三行0,0,1这是逆时针旋转的标准形式。平移矩阵第三列前两个数是1和2对应 x 和 y 方向的平移量。T * R * p从右往左算先对点做旋转再做平移符合题目“先旋转再平移”的描述。手算一遍能对上旋转后 x 约0.7071y 约2.1213再平移得到 x 约1.7071y 约4.1213。如果输出是(2.1213, 3.7071)之类的结果大概率是把平移矩阵放在旋转矩阵左边了或者旋转方向写反了。齐次坐标最后一位保持1不要归一化掉二维点变换用不上透视除法。4. 构建链路mkdir build 到 ./Transformation 的每一步4.1 CMakeLists.txt 最小结构作业0 要求用构建工具编译不是手敲g main.cpp。手敲编译在单文件时能跑但后面作业文件一多、依赖一加命令会越来越长换台机器就编不过。CMakeLists.txt把编译器、C 标准、头文件路径、可执行文件名都写进去换环境时只改路径不改正条命令。cmake_minimum_required(VERSION 3.10) project(Transformation) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 课程镜像里通常已经装好线性代数库先找包 find_package(Eigen3 REQUIRED) include_directories(${EIGEN3_INCLUDE_DIR}) add_executable(Transformation main.cpp)cmake_minimum_required限制最低版本写太低可能用不上新特性写太高课程环境可能不认。project给工程起名和可执行文件名可以不同。CMAKE_CXX_STANDARD设成 14 是常见做法作业0 用不到太新的语法但后面作业可能会用。find_package(Eigen3 REQUIRED)找线性代数库REQUIRED表示找不到就直接报错不要在配置阶段偷偷放过。include_directories把头文件目录加进编译搜索路径add_executable指定源文件和生成的可执行文件名。如果find_package报找不到先看课程镜像里库装没装再看是不是需要手动指定路径。常见做法是加一行include_directories(/usr/include/eigen3)但路径因环境而异不要照抄到别的机器上。CMakeLists.txt里的可执行文件名要和终端里执行的名字一致改完名字记得重新cmake ..不然make出来的还是旧名字。4.2 构建目录与编译命令构建目录独立出来是习惯问题也是排查问题的基础。源文件目录里放main.cpp和CMakeLists.txtbuild目录放中间文件和可执行文件编译失败时直接删build重来不会误删源码。下面这组命令在main.cpp所在目录下执行顺序不要乱。mkdir build cd build cmake .. make ./Transformationmkdir build建目录已经存在会报错可以忽略或者用mkdir -p build。cd build进构建目录。cmake ..读取上一级目录的CMakeLists.txt生成 Makefile。两个点表示上一级写成一个点会在build里找配置文件找不到就报错。make按 Makefile 编译错误信息会直接打在终端里。最后./Transformation运行当前目录下的可执行文件前面的./表示当前目录不加的话系统会去 PATH 里找找不到就提示命令不存在。编译报错先看第一条不要从最后一条往回看。链接错误通常在编译错误之后根因可能只是某个源文件没加进add_executable。改完CMakeLists.txt要重新执行cmake ..只make有时不会重新生成构建规则。想彻底重来就删掉build目录再走一遍比在中间文件里猜快。4.3 可执行文件名与头文件路径可执行文件名由add_executable第一个参数决定课程文档里写的是Transformation终端里执行./Transformation。如果你改成别的名字执行命令也要跟着改。这个细节看起来小实际排查时经常有人make成功但运行报“No such file”最后发现是名字没对上。头文件路径是另一个高频问题。#include eigen3/Eigen/Core这种写法依赖 include 搜索路径里有/usr/include因为实际文件在/usr/include/eigen3/Eigen/Core。如果编译器报找不到eigen3/Eigen/Core先确认库装没装再看CMakeLists.txt里有没有把/usr/include/eigen3加进去。有些环境里头文件路径是/usr/local/include/eigen3照抄别人的配置不一定能用。注意不要用绝对路径把源码目录写死在CMakeLists.txt里换台机器就编不过。路径尽量用相对路径或者find_package拿到的变量构建目录和源码目录分开后面加文件、换编译器都省事。5. 常见问题排查虚拟机拖放、编译链接与运行崩溃5.1 现象拖放文件没反应现象是从主机往虚拟机里拖作业框架鼠标变成禁止符号或者拖进去没反应。原因通常是增强功能没装完、拖放模式没设成双向或者桌面环境对文件夹拖放支持不好。解决顺序是先看虚拟机设置里的拖放选项改成双向再重启虚拟机还不行就在终端里重新挂载光盘装一遍增强功能。文件夹拖不进去时压缩成 zip 再拖或者用共享文件夹挂载主机目录。不要反复拖同一个文件夹先换单文件测试能拖单文件说明增强功能是好的问题在文件夹层级或权限。5.2 现象cmake 找不到线性代数库现象是执行cmake ..时报找不到线性代数库或者find_package那行直接失败。原因是课程镜像里库没装、头文件路径没配或者CMakeLists.txt里包名写错。解决是先确认库文件在不在终端里ls /usr/include/eigen3看一眼在的话把include_directories指到实际路径不在就按课程文档装依赖。不要为了绕过报错把find_package删掉删掉后编译阶段还是会报头文件找不到只是错误换了个地方。5.3 现象undefined reference to xxx现象是编译通过链接阶段报undefined reference to某个函数。原因是源文件没加进add_executable、库没链接或者函数声明了但没实现。解决先看报错里的函数名属于哪个文件确认那个文件在add_executable列表里。只有头文件没有源文件时检查是不是把声明当实现了。课程作业0 文件少这种错误多半是CMakeLists.txt里漏了源文件或者改完文件名没重新cmake ..。5.4 现象Segmentation Fault 或 Math Error现象是程序运行直接段错误或者输出Math Error。段错误常见原因是数组越界、指针没初始化、栈空间开太大Math Error常见原因是除数为 0。作业0 代码量小先检查向量和矩阵维度有没有对错Matrix3f乘Vector3f没问题但拿Vector2f去乘 3×3 矩阵就会出问题。再用std::cout把中间结果打出来二分定位是哪一行崩的。不要一看到段错误就重装环境先看代码。5.5 现象变换结果和手算对不上现象是程序能跑输出坐标和手算不一致。原因通常是矩阵乘法顺序反了、旋转方向写反、角度没转弧度或者矩阵初始化按列填了。解决先把T * R * p和R * T * p各跑一遍看哪个和题目描述一致再检查theta是不是弧度最后把R和T单独输出确认第三列平移量在正确位置。齐次坐标最后一位应该是1如果输出最后一位不是 1检查矩阵最后一行有没有写错。6. 二次开发把作业0 的变换链路改成可复用的小工具作业0 的main.cpp把角度、平移量、点坐标都写死了跑通一次就结束。想拿它做二次开发第一步是把变换抽成函数参数化角度和平移向量后面做批量点变换或者换旋转中心时不用改一大片。我一般会保留一个transformPoint输入原始点、角度、平移量返回变换后的点主函数只负责准备数据和打印结果。Eigen::Vector3f transformPoint(const Eigen::Vector3f p, float degree, const Eigen::Vector2f offset) { float theta degree / 180.0f * std::acos(-1.0f); float c std::cos(theta); float s std::sin(theta); Eigen::Matrix3f R; R c, -s, 0.0f, s, c, 0.0f, 0.0f, 0.0f, 1.0f; Eigen::Matrix3f T; T 1.0f, 0.0f, offset.x(), 0.0f, 1.0f, offset.y(), 0.0f, 0.0f, 1.0f; return T * R * p; }改造后验证方法很简单拿(0,1,1)逆时针转 90 度应该得到(-1,0,1)再平移(2,3)得到(1,3,1)。再用(2,1,1)转 45 度平移(1,2)和作业0 的结果对一遍。表格里这几组用例跑通函数基本就稳了。改造点目的验证方式角度参数化换旋转角度不改矩阵90 度、45 度各跑一次平移向量参数化换平移量不改矩阵零平移和(2,3)对比返回齐次坐标方便链式变换检查最后一位是否为 1批量点循环验证函数复用多个点输出顺序正确从那以后我每次改变换代码都先用单位矩阵和零平移跑一遍确认链路没接反再加旋转和平移。这个习惯救过我好几次比出错后找后悔药强。如果你还没拿到这份作业0 的 PDF先把虚拟机环境跑通再按上面的函数改一版资源里的虚拟硬盘和命令就是省掉环境折腾的那一步。希望帮到你。本文还有配套的精品资源点击获取