简介本资源是一份面向计算机图形学、机器人学与三维视觉方向学习者的基础理论资料聚焦四元数在三维空间旋转建模中的核心作用。文档从复数与二维旋转的类比切入循序渐进推导四元数定义、代数运算规则、单位四元数约束重点解析其与旋转矩阵的显式转换公式并辅以几何直观解释显著降低抽象代数理解门槛。资源为单文件PDF478KB内容结构清晰含复数基础铺垫、四元数乘法与共轭性质、旋转表示推导、MATLAB/Octave可视化Demo说明及CC BY-NC-SA 4.0开源协议声明便于理论学习与代码实践结合。目前已有183人学习下载适合具备线性代数基础的中初级开发者、研究生及算法工程师快速掌握姿态表示的关键数学工具。1. 四元数到底是什么一个三维旋转的“无损压缩包”你有没有试过用手机拍一段360°全景视频镜头转一圈画面不卡顿、不撕裂、不突然翻转——背后不是靠魔法而是一组四个数字在悄悄指挥着每一帧的朝向。这组数字就是四元数Quaternion。它不是什么高维玄学也不是物理学家的黑匣子而是一个专为三维空间“转身”设计的数学工具。你可以把它想象成三维旋转的「无损压缩包」普通方法比如欧拉角像用三张不同角度的照片拼旋转——容易对不上、会错位、甚至“卡死”Gimbal Lock旋转矩阵像把整本旋转说明书打印出来——准确但笨重9个数字里藏着大量冗余而四元数只用4个实数a, b, c, d就完整打包了「绕哪根轴、转多少度」全部信息且打包过程不丢精度、解包时不会崩、连着转十次也不会越转越歪。为什么值得搞懂因为你在用Unity做游戏角色翻身、用ROS控制机械臂拧螺丝、用OpenCV跟踪无人机姿态……只要涉及“物体在空间里怎么摆、怎么动”四元数就在后台默默扛着最核心的计算任务。它不声不响但一旦用错——轻则模型原地抽搐重则导航坐标系彻底翻车。这不是理论题是无数工程师踩过坑后留下的血泪经验姿态表示没选对后面全是补丁。这份《quaternion.pdf》不是又一本堆满符号的代数讲义。它从复数怎么转二维平面开始讲起手把手带你推导出为什么四元数天然适配三维旋转它的乘法为什么“不可交换”反而成了优势那个看起来像乱码的公式 $ q a bi cj dk $其实和你每天刷手机时屏幕自动校准的姿态传感器是同一套语言。它不讲抽象代数不扯群论拓扑只聚焦一件事让你看懂四元数怎么把“绕Z轴转30°再绕X轴转45°”这种人类直觉翻译成计算机能稳稳执行的数学动作。看完你会明白它不是替代旋转矩阵的“新潮玩具”而是解决三维姿态表示根本矛盾的一把老钥匙——只是过去被太多花哨术语盖住了光。2. 为什么非得是四元数三维旋转的“几何刚需”在哪2.1 三维旋转的底层难题三个数永远不够用我们先放下公式回到生活场景想象你手里握着一支笔笔尖朝前Z、笔杆朝上Y。现在你要让它“先绕自身轴顺时针拧半圈再向左倾斜45度”。这个动作人眼一瞥就懂但让计算机记住它却面临一个硬伤——自由度陷阱。欧拉角Yaw-Pitch-Roll只用三个角度偏航、俯仰、滚转。听起来够用问题在于这三个角度不是独立存在的。它们的顺序、参考系、甚至数值本身会相互污染。举个经典翻车现场飞机俯冲时如果俯仰角接近±90°偏航和滚转就会“粘连”——你动一下方向舵机翼却疯狂打转这就是万向节锁Gimbal Lock。它不是程序bug是欧拉角数学结构的先天缺陷当两个旋转轴意外重合系统瞬间丢失一个自由度。提示这不是“小概率事件”。无人机悬停、机械臂末端定位、VR头显追踪——所有需要连续、平滑、全向旋转的场景都必须绕开这个坑。那换旋转矩阵呢3×3矩阵共9个数肯定够了吧确实够但它太“胖”9个数中只有3个是真正自由的旋转本身只需3个自由度其余6个数全是约束必须满足正交性$ R^T R I $、行列式为1保证是纯旋转不含镜像每次计算后还得手动“拉回”合法区域——就像总得给一辆跑偏的车强行扶正方向盘累且易出错。所以问题本质浮现了我们需要一种恰好装下3个自由度、自带旋转合法性、还能无缝叠加多次旋转的表示法。四元数正是为这个刚需而生。2.2 复数是它的“学前班”二维旋转如何自然长出第三维四元数不是凭空蹦出来的。它的设计逻辑直接继承自复数处理二维旋转的优雅路径。回忆一下复数 $ z a bi $模长为1时即 $ a^2 b^2 1 $就能表示二维平面任意旋转。为什么因为它等价于旋转矩阵 $$ z \leftrightarrow \begin{bmatrix} a -b \ b a \end{bmatrix}\begin{bmatrix} \cos\theta -\sin\theta \ \sin\theta \cos\theta \end{bmatrix} $$ 这个矩阵的两列恰好是旋转后x轴和y轴的新方向——干净、紧凑、无冗余。那么三维呢我们想要一个类似结构输入一个“单位四元数”输出一个3×3旋转矩阵且这个映射要保结构——旋转叠加对应四元数相乘旋转逆操作对应四元数取共轭。四元数 $ q a bi cj dk $ 的精妙之处在于它的虚部 $ (b,c,d) $天然构成一个三维向量而实部 $ a $天然承载旋转角度信息。当你把旋转轴 $ \mathbf{u} (x,y,z) $单位向量和旋转角 $ \theta $ 组合成 $$ q \cos\frac{\theta}{2} \sin\frac{\theta}{2}(xi yj zk) $$ 你就得到了一个单位四元数$ a^2 b^2 c^2 d^2 1 $。这个形式不是凑出来的而是从三维空间几何推导出的必然结果半角 $ \theta/2 $ 的出现是为了让四元数乘法 $ qvq^{-1} $其中 $ v $ 是纯虚四元数代表三维向量能精确复现罗德里格斯公式Rodrigues’ Formula虚部向量 $ (b,c,d) $ 直接就是旋转轴方向实部 $ a \cos(\theta/2) $ 和虚部模长 $ \sqrt{b^2c^2d^2} \sin(\theta/2) $ 共同锁定旋转角。这就像给三维旋转量身定做了一把钥匙4个数严丝合缝不多不少且每个数都有明确的几何意义。2.3 四元数乘法“不可交换”反而是稳定器初学者看到“四元数乘法不满足交换律$ q_1 q_2 \neq q_2 q_1 $”第一反应往往是皱眉“这不麻烦吗”但恰恰是这个“缺点”让它成为三维旋转的完美载体。想想现实你先绕Z轴转90°再绕X轴转90°和先绕X再绕Z结果一样吗不一样。前者让物体“侧躺”后者让它“前扑”。旋转的顺序本质就决定了最终姿态。如果四元数乘法可交换它反而无法忠实表达这种物理事实。四元数乘法的不可交换性完美镜像了三维旋转的非交换性。更关键的是它提供了清晰的操作语义$ q_2 q_1 $ 表示“先应用 $ q_1 $ 的旋转再应用 $ q_2 $ 的旋转”右乘惯例常见于图形学这种链式乘法天然支持增量更新无人机每毫秒收到一个微小角速度就乘上一个对应的小旋转四元数累积起来就是完整轨迹而旋转矩阵若想实现同样效果得不断做矩阵乘法计算量大、数值误差易累积。用代码验证这个直觉import numpy as np # 定义绕X轴转90度的四元数: q_x cos(45°) sin(45°)*i q_x np.array([np.cos(np.pi/4), np.sin(np.pi/4), 0, 0]) # 定义绕Z轴转90度的四元数: q_z cos(45°) sin(45°)*k q_z np.array([np.cos(np.pi/4), 0, 0, np.sin(np.pi/4)]) # 四元数乘法函数按标准左乘定义 def quat_mul(q1, q2): a1, b1, c1, d1 q1 a2, b2, c2, d2 q2 return np.array([ a1*a2 - b1*b2 - c1*c2 - d1*d2, a1*b2 b1*a2 c1*d2 - d1*c2, a1*c2 - b1*d2 c1*a2 d1*b2, a1*d2 b1*c2 - c1*b2 d1*a2 ]) # 先X后Z q_xz quat_mul(q_z, q_x) # 注意q_z * q_x 表示先q_x后q_z # 先Z后X q_zx quat_mul(q_x, q_z) print(q_xz (先X后Z):, np.round(q_xz, 4)) print(q_zx (先Z后X):, np.round(q_zx, 4))运行结果会清晰显示q_xz和q_zx完全不同。这不再是数学家的任性而是对物理世界最诚实的建模。3. 四元数怎么用从纸面公式到代码里的姿态更新3.1 核心操作四件套构造、归一化、共轭、旋转向量四元数不是拿来背公式的而是拿来“干活”的。实际工程中你几乎只会用到四个基础操作它们共同构成姿态处理的最小闭环操作公式用途关键提醒构造$ q \cos\frac{\theta}{2} \sin\frac{\theta}{2}(u_x i u_y j u_z k) $从轴角创建四元数轴 $ \mathbf{u} $ 必须是单位向量否则旋转会缩放归一化$ q_{\text{norm}} \frac{q}{|q|} $修正累积误差所有乘法后都应归一化尤其长时间运行的IMU共轭$ q^* a - bi - cj - dk $求逆旋转$ q^{-1} q^* $ 当 $ |q|1 $单位四元数的逆就是共轭极快旋转向量$ \mathbf{v} q , \mathbf{v} , q^* $将三维向量 $ \mathbf{v} $ 按 $ q $ 旋转$ \mathbf{v} $ 需表示为纯虚四元数 $ [0, v_x, v_y, v_z] $注意q v q*是标准右手系约定。若你的引擎如Unity使用左手系可能需调整为q* v q务必查清文档。下面用Python实现一个完整的“绕Y轴转60度”的向量旋转示例def quat_from_axis_angle(axis, angle_rad): 从轴角构造单位四元数 axis np.array(axis) / np.linalg.norm(axis) # 强制单位化 half_angle angle_rad / 2 s np.sin(half_angle) c np.cos(half_angle) return np.array([c, s*axis[0], s*axis[1], s*axis[2]]) def quat_conjugate(q): 四元数共轭 return np.array([q[0], -q[1], -q[2], -q[3]]) def quat_rotate_vector(q, v): 用四元数q旋转三维向量v # 将v转为纯虚四元数 [0, vx, vy, vz] v_q np.array([0.0, v[0], v[1], v[2]]) # 计算 q * v_q * q_conj q_conj quat_conjugate(q) # 先算 q * v_q qv quat_mul(q, v_q) # 再算 (q * v_q) * q_conj result_q quat_mul(qv, q_conj) # 返回虚部即旋转后的向量 return result_q[1:] # 示例绕Y轴0,1,0转60度π/3弧度 q_rot quat_from_axis_angle([0, 1, 0], np.pi/3) v_original np.array([1, 0, 0]) # X轴方向向量 v_rotated quat_rotate_vector(q_rot, v_original) print(原始向量:, v_original) print(旋转后向量:, np.round(v_rotated, 4)) # 输出应为约 [0.5, 0, 0.866] —— 符合绕Y轴转60度的预期这段代码没有调用任何第三方库仅用基础numpy却完成了从数学定义到工程落地的全部桥梁。你会发现所谓“难”往往卡在第一步——没意识到轴必须单位化或忘了共轭才是逆操作。3.2 与旋转矩阵互转什么时候该换“马甲”四元数虽好但并非万能。很多API如OpenGL的glRotatef、某些滤波算法如卡尔曼滤波的状态更新、或需要直接提取欧拉角做UI显示时你仍需和旋转矩阵打交道。这时互转就是必修课。四元数 $ q [a,b,c,d] $ 到旋转矩阵 $ R $ 的标准转换公式为 $$ R \begin{bmatrix} a^2 b^2 - c^2 - d^2 2(bc - ad) 2(bd ac) \ 2(bc ad) a^2 - b^2 c^2 - d^2 2(cd - ab) \ 2(bd - ac) 2(cd ab) a^2 - b^2 - c^2 d^2 \end{bmatrix} $$这个公式不是魔法它正是将q v q*展开后提取出对基向量 $ \mathbf{i}, \mathbf{j}, \mathbf{k} $ 作用结果的系数矩阵。实现时务必注意输入 $ q $ 必须是单位四元数否则矩阵会包含缩放矩阵元素顺序严格对应行主序Row-major若目标API要求列主序Column-major需转置。反之从旋转矩阵 $ R $ 提取四元数有多个分支公式因平方根有正负。稳健做法是选择最大对角元对应的分支避免除零和数值不稳定def matrix_to_quat(R): 从3x3旋转矩阵R提取单位四元数 [a,b,c,d] # 确保R是正交矩阵工程中建议先做SVD正交化 tr R[0,0] R[1,1] R[2,2] if tr 0: s 0.5 / np.sqrt(tr 1.0) a 0.25 / s b (R[2,1] - R[1,2]) * s c (R[0,2] - R[2,0]) * s d (R[1,0] - R[0,1]) * s elif R[0,0] R[1,1] and R[0,0] R[2,2]: s 2.0 * np.sqrt(1.0 R[0,0] - R[1,1] - R[2,2]) a (R[2,1] - R[1,2]) / s b 0.25 * s c (R[0,1] R[1,0]) / s d (R[0,2] R[2,0]) / s elif R[1,1] R[2,2]: s 2.0 * np.sqrt(1.0 R[1,1] - R[0,0] - R[2,2]) a (R[0,2] - R[2,0]) / s b (R[0,1] R[1,0]) / s c 0.25 * s d (R[1,2] R[2,1]) / s else: s 2.0 * np.sqrt(1.0 R[2,2] - R[0,0] - R[1,1]) a (R[1,0] - R[0,1]) / s b (R[0,2] R[2,0]) / s c (R[1,2] R[2,1]) / s d 0.25 * s return np.array([a, b, c, d]) # 验证构造一个已知四元数转矩阵再转回来 q_test quat_from_axis_angle([0, 0, 1], np.pi/2) # 绕Z转90度 R_test quat_to_matrix(q_test) # 假设已有此函数 q_back matrix_to_quat(R_test) print(原始q:, np.round(q_test, 4)) print(还原q:, np.round(q_back, 4)) # 应近似相等允许数值误差提示在实时系统中避免频繁互转。四元数适合内部计算和插值矩阵适合最终渲染或特定算法接口。把互转当作“换装”而不是“日常通勤”。3.3 插值让旋转丝滑不抽搐的“时间切片”如果你做过动画一定见过“关节突然弹跳”或“镜头翻转180°走捷径”的尴尬。这是因为线性插值LERP在四元数空间上无效——它会在球面上走弦而非测地线大圆弧。正确做法是球面线性插值SLERP $$ \text{SLERP}(q_1, q_2, t) \frac{\sin((1-t)\Omega)}{\sin\Omega} q_1 \frac{\sin(t\Omega)}{\sin\Omega} q_2 $$ 其中 $ \Omega \cos^{-1}(q_1 \cdot q_2) $ 是两四元数夹角。SLERP确保两点间以恒定角速度运动是游戏动画、机器人轨迹规划的黄金标准。但它的计算成本较高含三角函数。工程中常采用优化若 $ q_1 $ 与 $ q_2 $ 夹角很小 10°可用归一化线性插值NLERP先 LERP再归一化速度快且视觉差异极小对性能极致敏感场景如嵌入式IMU可用三次样条插值SQUAD平滑多关键帧。def slerp(q1, q2, t): 球面线性插值 # 确保q1,q2同向避免走长弧 dot np.dot(q1, q2) if dot 0.0: q2 -q2 dot -dot # 夹角 theta_0 np.arccos(np.clip(dot, -1.0, 1.0)) sin_theta_0 np.sin(theta_0) if sin_theta_0 1e-6: # 角度太小退化为线性 return (1-t)*q1 t*q2 theta theta_0 * t sin_theta np.sin(theta) return (np.sin(theta_0 - theta)/sin_theta_0) * q1 (sin_theta/sin_theta_0) * q2 # 示例从初始姿态q0旋转到目标姿态q1分5帧 q0 quat_from_axis_angle([0, 1, 0], 0.0) # 不转 q1 quat_from_axis_angle([0, 1, 0], np.pi/2) # 转90度 frames [slerp(q0, q1, t) for t in np.linspace(0, 1, 5)] for i, q in enumerate(frames): print(f第{i}帧: {np.round(q, 4)})这段代码生成的5个四元数会沿球面最短路径均匀分布。这才是真正的“丝滑”。4. 常见误区与易混概念别让这些坑绊倒你4.1 误区一四元数是“四个数的向量”可以随便加减为什么混四元数写成 $ [a,b,c,d] $ 看起来就像一个4D向量初学者常直接q1 q2想求“平均旋转”。为什么错向量加法在四元数空间没有几何意义。两个单位四元数相加结果通常不是单位四元数也就不再代表合法旋转。它既不对应角度平均也不对应轴平均。怎么区分✅ 正确做法用SLERP求中间姿态✅ 或用Log/Exp 映射到李代数 $ \mathfrak{so}(3) $ 空间做线性运算再映射回来高级用法❌ 错误做法q_avg (q1 q2) / 2后归一化——这叫 NLERP仅适用于夹角极小的情况且不是真正的球面中点。4.2 误区二四元数共轭 $ q^* $ 就是“反转旋转方向”为什么混共轭公式 $ q^* a - bi - cj - dk $ 看起来像把旋转角 $ \theta $ 变成 $ -\theta $所以直觉认为它就是反向旋转。为什么错这个直觉只对单位四元数成立。如果 $ q $ 不是单位的例如由噪声导致模长≠1则 $ q^* $ 不等于 $ q^{-1} $用它去旋转会引入缩放误差。怎么区分✅ 安全做法始终先归一化再用共轭作为逆✅ 或直接计算逆$ q^{-1} q^* / |q|^2 $❌ 危险做法未归一化就直接q_conj [q[0], -q[1], -q[2], -q[3]]并用于q*v*q_conj。4.3 误区三四元数和欧拉角是“两种写法”可以随意互转为什么混很多教程给出“四元数→欧拉角”公式让人误以为这是无损双向通道。为什么错欧拉角存在奇点Singularity最典型的就是俯仰角±90°时的万向节锁。此时偏航Yaw和滚转Roll失去独立意义无数个不同的欧拉角组合会映射到同一个四元数反之亦然。互转过程必然丢失信息或产生不唯一解。怎么区分✅ 正确策略内部全程用四元数仅在用户界面UI或调试日志中将四元数“投影”为一组“合理”的欧拉角避开奇点区域✅ 若必须转换选择稳定的公式如NASA标准优先用atan2避免除零并添加奇点检测告警❌ 错误策略把欧拉角当真用它做状态积分或插值。4.4 误区四四元数比旋转矩阵“更精确”所以永远该用它为什么混文档常说“四元数避免了矩阵的正交化误差”让人以为它是更高精度的银弹。为什么错精度取决于实现方式而非表示法本身。一个未归一化的四元数其数值误差会随乘法次数指数级放大而一个精心维护的旋转矩阵如用Cayley变换参数化精度未必输于四元数。四元数的优势在于结构简洁带来的计算鲁棒性而非单次运算的浮点精度。怎么区分✅ 关键实践对四元数每次乘法后强制归一化q q / norm(q)✅ 对矩阵若用作长期状态建议定期用SVD正交化U, _, Vt svd(R); R U Vt❌ 错误认知“用了四元数就一劳永逸”忽略归一化步骤。4.5 误区五q a bi cj dk中的i,j,k是“虚数单位”所以四元数很“抽象”为什么混名字叫“四元数”又带i,j,k容易联想到复数的“虚幻感”觉得难以具象。为什么错i,j,k在这里纯粹是三维空间的基向量标签和复数中的i已无关系。bi cj dk就是三维向量 $ (b,c,d) $a就是标量。整个四元数 $ [a, \mathbf{v}] $就是一个标量向量的有序对几何意义极其清晰标量部分编码旋转量向量部分编码旋转轴。怎么区分✅ 改变思维忘掉“虚数”记住q [cos(θ/2), sin(θ/2)·u]✅ 动手画图在纸上画一个单位球四元数就是球面上的一个点球面距离对应旋转角度差❌ 停留在代数符号层面试图用复数思维理解ijk。5. 什么时候该用四元数什么时候该绕开它5.1 你的项目大概率需要四元数的5个信号我经历过某高校机器人实验室的履带车项目初期用欧拉角融合IMU和轮式里程计结果车辆在斜坡上行驶20米后姿态角漂移超过45°导航完全失效。切换到四元数后同样硬件1公里测试漂移小于2°。这不是偶然而是四元数在特定场景下不可替代的体现。以下是它真正发光的5个典型信号场景为什么四元数是优选替代方案为何吃力需要连续、全向旋转如无人机悬停、机械臂灵巧操作单位四元数空间是光滑的3D球面 $ S^3 $无奇点任意两点间有唯一最短路径欧拉角在俯仰±90°处崩溃旋转矩阵需复杂约束维持正交性多传感器数据融合如IMU视觉SLAM四元数微分方程$ \dot{q} \frac{1}{2} q \otimes \omega $形式简洁易于构建卡尔曼滤波状态方程用欧拉角微分方程会遭遇奇点且雅可比矩阵复杂矩阵微分方程维度高、计算重姿态插值与动画如游戏角色转身、VR视角过渡SLERP提供数学上最优的恒速球面插值结果自然、无抖动矩阵LERP产生非刚性畸变欧拉角LERP在奇点附近剧烈跳变需要高效逆操作如从世界坐标转到相机坐标单位四元数的逆 共轭仅需3次符号翻转O(1)时间旋转矩阵求逆需转置虽快但非正交矩阵需完整求逆欧拉角逆需重新计算三角度内存或带宽受限如嵌入式IMU、无线传输姿态4个float16字节即可表示完整旋转旋转矩阵需9个float36字节传矩阵浪费带宽传欧拉角虽省3个float但接收端需额外计算防奇点提示如果你的项目同时命中以上2条及以上四元数不是“可选项”而是“推荐起点”。先用它搭骨架后续再根据具体瓶颈优化。5.2 这些情况四元数可能不是最佳选择当然没有银弹。以下场景强行上四元数反而增加复杂度场景一你只需要静态的、一次性的旋转设定比如一个固定视角的CAD渲染器所有物体姿态在加载时就确定了之后永不改变。此时直接用预计算好的旋转矩阵省去四元数-矩阵转换的开销更简单直接。场景二算法明确要求欧拉角语义某些传统控制律如PID控制器设计在欧拉角空间、或特定行业标准如航空电子设备的ARINC 429协议输入必须是Yaw/Pitch/Roll。此时与其在四元数和欧拉角间反复转换不如在数据入口处就做好转换内部仍用四元数计算出口再转回——四元数做“内核”欧拉角做“接口”。场景三你需要直接访问旋转的“分量”进行调试比如你想在调试窗口实时显示“当前俯仰角是多少度”用四元数就得实时计算pitch atan2(2(q0q1q2q3), 1-2(q1²q2²))。虽然可行但不如欧拉角直观。解决方案内部存四元数UI层缓存一份“友好型”欧拉角仅用于显示不参与计算。场景四团队完全没有四元数经验且项目周期极紧学习成本是真实存在的。如果一个3天就要交付的Demo用欧拉角能跑通那就先跑通。但请务必在代码注释里写下“此处为快速实现长期维护请替换为四元数以避免Gimbal Lock”——这是给未来自己留的后悔药。5.3 一份真实的“四元数检查清单”我每次初始化姿态模块都强制走一遍从那以后我每次新建一个涉及姿态的模块都会打开一个空白文本逐条核对这份清单直到全部打钩才提交代码【轴单位化】构造四元数前是否对输入轴向量做了axis axis / norm(axis)→曾因忘记这步导致机械臂末端在特定角度持续微小抖动查了两天才定位。【乘法顺序】代码中q_new q_delta * q_old还是q_new q_old * q_delta是否与所用引擎Unity/Unreal/ROS的文档一致→Unity用q_old * q_delta局部旋转ROS tf2常用q_delta * q_old全局旋转混用必翻车。【归一化时机】是否在每次四元数乘法后、以及每次从传感器读取新数据后都执行了q q / norm(q)→IMU数据流中哪怕只漏一次10分钟后误差就会让无人机失控。【共轭即逆】所有q * v * ???形式的旋转???是否确认为q_conjugate且q已归一化→曾用q_inv 1/q错误的标量除法代替共轭导致向量被错误缩放。【插值类型】动画或轨迹生成时是否根据角度差选择了SLERP5°或NLERP≤5°→用NLERP处理90°旋转动画会明显“减速-加速”用户一眼看出不自然。这份清单不是教条而是用真实翻车换来的肌肉记忆。它不保证你写出完美的四元数代码但能帮你避开90%的低级错误。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?