ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

向量点乘与叉乘:从几何意义到3D图形学实战应用详解

向量点乘与叉乘:从几何意义到3D图形学实战应用详解 1. 为什么很多初学者会在点乘和叉乘这里卡住向量点乘和叉乘几乎是所有人学线性代数时第一次感到懵的地方。前几章还在学向量加减法和数乘规则和直觉都对得上加就是把两个箭头接起来乘个负数就是把箭头掉个头。结果一到点乘和叉乘突然冒出两套公式一套算出来是一个数另一套算出来是一个新向量。更让人困惑的是这两套操作都叫乘法但结果形态完全不同。我当时学到这里脑子里就一句话这俩到底在乘什么后来才慢慢明白卡住的根源在于一个问题我们习惯了数乘数得出数这件事却忘了向量本身既携带大小又携带方向当两个向量相乘时方向这个信息也必须被处理。点乘和叉乘其实是两种处理方向信息的不同方式。点乘把方向信息压成一个标量衡量两个方向有多一致叉乘则把方向信息保留下来生成一个与两者都垂直的新向量。它们不是同一个运算的两种变体而是针对不同几何问题的两个工具。这篇文章就把这两件事彻底掰开。我会从计算方式讲到几何含义从符号意义讲到实际用途最后还会分享几个我自己踩过的坑。不管你是正在备考的学生、转行学图形学的程序员还是工作中需要补线性代数基础的工程师这篇文章都能让你在读完以后不再靠死记硬背公式过日子。2. 点乘的计算与几何含义两个向量同向程度的度量2.1 代数定义对应坐标相乘再求和为什么非这样不可点乘也叫数量积、内积英文是 dot product。它的代数定义非常简单两个向量对应分量相乘然后把所有乘积加起来。比如在三维空间里a (a₁, a₂, a₃) b (b₁, b₂, b₃) a · b a₁b₁ a₂b₂ a₃b₃举个例子a (1, 2, 3)b (4, 5, 6)那么a · b 1×4 2×5 3×6 4 10 18 32这个操作本身不复杂但问题是为什么对应位置相乘再相加就能代表两个方向的关系这看起来很像随机拼凑的规则。换个角度想这件事。向量 (1, 2, 3) 可以理解为沿 x 轴走 1沿 y 轴走 2沿 z 轴走 3。而 (4, 5, 6) 是另一个走法。当我把x 方向的位移和x 方向的位移相乘y 的乘 y 的再把它们累加实际上是在做的事情是把两个向量在同一个基方向上的分量相乘然后跨所有基方向汇总。如果两个向量方向越接近它们对应的分量符号和大小就越同步乘积的累加值就越大如果方向越相反累加值就越负。所以这个看起来普通的代数操作天然编码了方向一致性信息。2.2 几何定义|a||b|cosθ和代数定义怎么等价点乘更常用的几何定义是a · b |a| · |b| · cos θ这里 |a| 和 |b| 是向量的长度模θ 是两向量之间的夹角通常取锐角或钝角范围 0 到 π。这两个定义为什么是同一回事可以这样理解把 b 投影到 a 上投影长度是 |b|cosθ再乘以 |a|得到一个有向面积。而坐标定义里a₁b₁ a₂b₂ a₃b₃ 在做了坐标旋转以后数值不变——这是内积在正交变换下的不变性。如果你不想深究证明只需要记住一个直觉夹角越小cosθ 越接近 1点乘结果越大夹角 90 度时cosθ 0点乘为 0夹角大于 90 度点乘为负。当 a 或 b 是单位向量时点乘结果就直接等于另一个向量在它方向上的投影长度。我当年理解这个等价的捷径是先看二维情形。设 a (1, 0)它是 x 轴上的单位向量任意向量 b (x, y) 和 a 的点乘等于 x而 b 在 x 轴上的投影也恰好是 x。等于说坐标定义本来就包含了投影这件事只是被坐标数字掩盖了。2.3 点乘结果的符号和值域比你想的更有信息量点乘结果不只是一个数字它本身就是一张方向关系查询表。我习惯把它拆成三种情况a · b 0夹角小于 90 度两向量大致同向。a · b 0夹角恰好 90 度两向量垂直。注意零向量和任何向量点乘都是 0这个性质在判断垂直时非常有用。a · b 0夹角大于 90 度两向量大致反向。如果两个都是单位向量点乘结果还能直接表示 cosθ 本身数值范围在 [-1, 1]。这个性质在图形学里被大量使用比如判断三角形面是正面还是背面或者计算两个方向角的余弦值。很多时候你根本不需要算出角度只需要判断符号是正还是负就能完成逻辑判断。3. 点乘的实际应用从光照计算到向量投影3.1 用点乘判断方向最简但最常用的一招判断两个向量的方向关系是点乘最朴素的应用。我最早在 3D 游戏开发里接触这个概念时需求是判断一个 NPC 是否在我面前。如果我在位置 P面朝方向 FNPC 在位置 Q那么向量 V Q - P 表示从我的位置指向 NPC 的方向。现在做点乘F · V 0 → NPC 在面前 F · V 0 → NPC 在背后 F · V ≈ 0 → NPC 正好在我侧面这个判断在渲染管线里还有个经典用途背面剔除。一个三角形面有法向量 n相机的观察方向是从面指向相机的向量 v如果 n · v 的结果小于某个阈值取决于约定就可以判定这个面是背对相机的直接跳过不渲染。每帧要处理几十万个三角形如果每一个都做反三角函数去算夹角性能肯定崩。而点乘只需要几个乘法和加法成本极低。3.2 向量投影点乘是投影的计算核心投影Projection在物理、几何、机器学习里到处都在用。问题是把一个向量 b 分解成两个部分——一部分平行于 a一部分垂直于 a。平行部分怎么求先从单位向量入手。如果 â 是 a 方向的单位向量â a / |a|那么 b 在 a 上的投影向量是proj_a(b) (b · â) · â这里 b · â 是投影的长度有符号再乘 â 就是把长度落到方向上。如果不用单位向量可以写成proj_a(b) ((b · a) / (a · a)) · a分母 a · a |a|²正好完成归一化。这组公式在最小二乘法、正交分解、信号处理里反复出现。比如做数据降维时PCA 里把样本往主成分方向上投影本质就是在反复用点乘求坐标。3.3 3D 图形学里最简单的漫反射光照模型如果你写过一点点着色器代码一定见过类似这样的 Lambert 漫反射模型diffuse max(0, N · L) * baseColor其中 N 是表面法向量L 是从表面指向光源的方向向量通常都归一化到单位长度。点乘 N · L 算出的是光源方向与法线方向的余弦值。当光线垂直照射表面时N · L 1表面最亮当光线顺着表面掠过去时N · L 趋近 0表面几乎全黑。max(0, ...) 是为了处理背光面因为背面的 cosθ 为负光照贡献应该直接归零而不是变成负亮度。这里有个非常实用的细节很多新手在参数上翻车是因为没有把 N 和 L 归一化。如果 N 或 L 不是单位向量点乘结果会混入向量的模长导致光照强度受物体大小或距离影响画面出现诡异的亮斑。正确姿势是先 normalize 再点乘拿到一个纯粹的余弦值。4. 叉乘的计算与几何含义构造一个垂直向量4.1 叉乘的代数计算三阶行列式就是记忆钩子叉乘也叫向量积、外积英文是 cross product。和点乘不同叉乘的结果是一个向量它的方向垂直于原来的两个向量模长等于由这两个向量张成的平行四边形的面积。三维叉乘的代数定义长这样a × b (a₂b₃ - a₃b₂, a₃b₁ - a₁b₃, a₁b₂ - a₂b₁)这个公式硬背很容易记混。我当时靠的是一个记忆技巧把 a 和 b 写成一个伪三阶行列式a × b | i j k | | a₁ a₂ a₃ | | b₁ b₂ b₃ |其中 i, j, k 是三个坐标轴方向的单位向量。按第一行展开就得到i(a₂b₃ - a₃b₂) - j(a₁b₃ - a₃b₁) k(a₁b₂ - a₂b₁)注意中间的 j 分量是负号这是很多人手算时最容易漏掉的地方。我建议计算完后做一个交叉验证拿结果向量和 a 做点乘再和 b 做点乘两次都应为 0。只要有一次不为 0说明算错了。用前面的例子 a (1, 2, 3)b (4, 5, 6)a × b (2×6 - 3×5, 3×4 - 1×6, 1×5 - 2×4) (12 - 15, 12 - 6, 5 - 8) (-3, 6, -3)验证一下(-3, 6, -3) · (1, 2, 3) -3 12 - 9 0(-3, 6, -3) · (4, 5, 6) -12 30 - 18 0。垂直关系成立。4.2 右手定则叉乘方向到底朝哪全看这个手势叉乘结果向量的方向由右手定则确定把右手食指指向 a中指弯向 b保持手掌面向你弯曲大拇指指的方向就是 a × b 的方向。这个定则有一个重要后果叉乘不满足交换律。a × b 和 b × a 的方向完全相反也就是说a × b -(b × a)这是新手最容易踩的坑。很多人在代码里调 API 时发现法向量方向反了第一反应是怀疑向量坐标有问题其实往往只是把叉乘参数的顺序写反了。在左手坐标系比如某些图形引擎里规则还会镜像翻转这点后面我会单独讲。还有一个容易忽略的性质两个平行向量叉乘结果为 0 向量。因为平行向量的夹角是 0 度或 180 度张成的平行四边形面积为 0。这个性质在判断线段平行、检测退化三角形时很有用。4.3 叉乘模长的几何意义平行四边形面积与三角形面积叉乘向量的模长并不是随便定义的数它恰恰等于两个向量张成的平行四边形的面积。设 θ 是两个向量的夹角那么|a × b| |a| · |b| · sin θ如果你需要求一个三角形的面积已知两条边对应的向量 a 和 b那么三角形面积就是平行四边形面积的一半S |a × b| / 2这个公式在处理三维空间中的三角形时非常有用。用海伦公式当然也行但往往要先算三条边长还要处理开方而叉乘直接给出面积向量信息量更大。在做网格简化、碰撞检测或计算流体面积时这个技巧可以省掉大量中间计算。5. 叉乘的典型应用法向量、旋转轴与面积5.1 用叉乘求平面法向量是图形学最基础的操作在三维空间里一个平面的法向量可以通过平面上两个不平行向量的叉乘得到。比如平面上有三个点 P₁, P₂, P₃取u P₂ - P₁ v P₃ - P₁ n u × vn 就是该平面的法向量。这个操作几乎是所有 3D 渲染、网格处理、碰撞检测的第一步。需要强调的是用 u × v 还是 v × u直接决定法向量指向平面哪一侧这会直接影响光照方向、背面剔除结果以及物理碰撞的反弹方向。所以当你要统一所有三角形面的法向量朝向时唯一要保证的是取点顺序一致全部逆时针或全部顺时针。在代码里这件事会变成这样示意代码Vector3 GetTriangleNormal(Vector3 p1, Vector3 p2, Vector3 p3) { Vector3 u p2 - p1; Vector3 v p3 - p1; Vector3 n Vector3.Cross(u, v); return n.normalized; }这里唯一的坑是如果 u 和 v 方向太接近叉乘结果的模长会非常小除出来的单位向量会因浮点误差而不稳定。所以实际工程里遇到退化三角形夹角接近 0 度时最好直接跳过法向量计算。5.2 判断多边形的朝向与面积如果你拿到一个三角形想知道它的三个顶点是顺时针排列还是逆时针排列在二维平面上可以看叉乘结果的符号。把顶点坐标看成二维向量z 分量为 0计算(P₂ - P₁) × (P₃ - P₁)结果只有 z 分量。若 z 大于 0则三个点是逆时针若 z 小于 0则是顺时针。这就是很多图形库判定多边形绕序的方式。在三维空间里则要看叉乘结果与某个参考方向通常是观察方向的点乘符号来决定这个三角形是正面还是背面。叉乘还可以直接计算多边形的面积。对凸多边形把它划分成从某个顶点出发的多个三角形分别用叉乘求面积再累加就能得到总面积。这个方法对凹多边形依然有效只要把面积看作有向值累加时会自动抵消凹陷部分。这个技巧在计算任意多边形面积时特别稳不需要先做三角剖分。5.3 物理里的力矩与角速度叉乘的旋转工厂在物理里叉乘最经典的场景是力矩。力矩 τ 的定义是力臂 r 与力 F 的叉乘τ r × F这个公式的几何含义是力臂越长、力越大且力的方向与力臂越垂直力矩效果越强。力矩的方向由右手定则给出它指向转轴方向。拧瓶盖时为什么在边缘处最省力因为那里的 r 最大且手施加的力方向几乎与 r 垂直sinθ 接近 1力矩最大。角速度与位置向量的关系也是叉乘速度 v ω × r角速度叉乘位置向量。这个结构在刚体旋转、陀螺仪数据解析、机器人的运动学解算里反复出现。如果你要做任何涉及旋转的物理模拟叉乘基本上是绕不开的运算。6. 点乘和叉乘的对比到底什么时候该用哪个6.1 从输入输出维度看区别很多人在做题时纠结见到两个向量不知道该点乘还是叉乘。我习惯先看我要的结果是什么做一个快速判断对比维度点乘叉乘结果类型标量一个数向量有方向结果含义方向的一致性度量方向的垂直构造依赖的角度cos θ0 度最大sin θ90 度最大交换性a · b b · a满足a × b -b × a不满足平行向量结果模长乘积0 向量垂直向量结果0模长为两向量模长乘积运算成本乘加即可乘减组合稍高这个表能解决大部分选择困难。你想要一个数来判断有多像或有多同向就点乘你想要一个垂直于两个向量的新向量或者要算面积、求转轴就叉乘。6.2 从几何问题看区别如果面对的是实际几何问题选择会更直观判断两条直线是否垂直方向向量点乘为 0。判断两条直线是否平行方向向量叉乘为 0 向量。求三个不共线点构成的平面方程先用两个边向量叉乘求法向量再代入点。求一个向量在另一个方向上的分量点乘求投影。求三角形面积叉乘模长的一半。求两个方向夹角的余弦值归一化后点乘。这里面最容易被忽略的一件事是点乘给出的是余弦叉乘给出的是正弦。如果你需要区分两个向量夹角是 30 度还是 150 度只用点乘不够因为 cos(30°) -cos(150°) 这种对称性不会帮你区分角度范围。这时需要结合叉乘的符号来判断方向这在一些涉及方向旋转的交互逻辑里尤其重要。6.3 两个乘法联动使用的经典场景点乘和叉乘经常不是二选一而是配套使用。最典型的例子是判断一个点在三角形内部。先用叉乘判断点是否在三角形三条边的同一侧e1 P2 - P1 e2 P3 - P2 e3 P1 - P3 c1 e1 × (P - P1) c2 e2 × (P - P2) c3 e3 × (P - P3)如果 c1、c2、c3 的方向全部一致点乘结果大于 0点在三角形内。这本质上就是先用叉乘获得边与点的方向关系再用点乘比较这些方向是否一致。两种运算各负责一个部分协作完成一个复杂判断。7. 我踩过的坑坐标顺序、左右手坐标系与数值误差7.1 第一坑叉乘的方向和坐标系惯用相关代码换个平台结果就反了我第一次在图形引擎里求法向量时把法线方向算反了导致模型看起来像被从内部点亮。排查了很久才发现问题的根源不是公式错了而是不同 API 和引擎使用的是不同的坐标系约定左手系或右手系同一组叉乘在不同坐标系下视觉上的正面可能完全相反。我后来养成了一个习惯在写任何涉及叉乘的代码之前先写一个简单测试。取两个已知方向的向量做叉乘用断言验证结果方向是否符合当前坐标系预期。不要靠记忆而是靠运行时验证。尤其是从数学库切换到自研引擎时一定要确认库里的 cross 函数到底用的是左手定则还是右手定则。多数数学库文档里都会明确写但很多人根本不看。7.2 第二坑浮点数误差导致点乘结果超出 [-1, 1]反余弦直接 NaN做光照或角度计算时我踩过一个很隐蔽的坑。代码里先对向量做归一化然后计算点乘再用 acos 求角度。结果在某些极端情况下出现 NaN。一开始以为是向量数据有问题后来发现是浮点误差导致点乘结果变成 1.0000002 或 -1.0000001而 acos 函数要求输入在 [-1, 1] 内超出就是 NaN。解决方法非常简单关键是你要有这个意识cos_theta max(-1.0, min(1.0, dot_result)) angle math.acos(cos_theta)这个 clamp 操作看起来无关紧要但在处理大量几何计算时是保命的一招。同理当点乘结果因为浮点误差出现微小的负值比如 -1e-7时在判断是否垂直时也可能产生误判。我的经验是判断垂直不要用dot 0而是用abs(dot) epsilon比如取 epsilon 为 1e-6。7.3 第三坑用点乘判断前后时参考点没统一结果完全相反在 3.1 节里我说过判断一个物体是否在面前需要构造从我的位置指向目标位置的向量。这个操作有个隐藏前提向量必须从自己的位置出发。我见过一些同学直接拿目标位置向量和面朝方向做点乘结果发现判断逻辑时好时坏。原因就是没有减去自身位置导致参考系混乱。正确的做法一定是先计算相对向量再用点乘toTarget targetWorldPos - myWorldPos isFront (forwardDir · toTarget) 0类似的坑在摄像机剔除、鼠标拾取、AI 索敌逻辑里普遍存在。我现在的习惯是写这类代码时先明确标出参考点和方向向量来自哪里而不是在公式里混用世界坐标和局部坐标。任何方向判断题只要参考点不统一最终结果就不可信。还有一个小经验想分享做点乘或叉乘时尽量把参与计算的向量先归一化。虽然这会多几次开方运算但换来的是数值更稳定、结果更直觉。尤其是做几何判断时归一化后的点乘结果就在 [-1, 1] 区间一眼就能读懂含义。等你对这套运算足够熟练会发现它根本不是需要背诵的公式而是一组描述空间的直觉工具用多了自然就形成肌肉记忆了。
返回列表