跳转至

法向量的坐标系变换

在本文中
1. 矩阵是行优先存储
2. 向量是列向量
3. 向量 = 矩阵 * 向量。其中,乘法是行列式的乘法规则

知识回顾

回顾一个知识

  1. 三维向量\(\vec{n} = (x,y,z)\)的齐次坐标为\((x,y,z,0)\),其中第四维为0
  2. 三维点\(p=(x,y,z)\)的齐次坐标为\((x,y,z,1)\),其中第四维为1

引言

法向量的坐标系变换,与顶点位置的坐标系变换,有所不同。

举个例子,法向量(1,0,0)、顶点坐标(0,0,0),做一个(0,2,0)的平移变换

按照我们的直观感受

  1. \((0,0,0) + (0,2,0)\),因此顶点坐标就变成(0,2,0)
  2. 而法向量,\((1,0,0)+(0,2,0)\),变成(1,2,0)。这当然是错的,平移之后,法向量应该还是为(1,0,0),因为向量没有位置,只有方向

那为什么会错呢?本质上,上文是将向量当成了点(即第四维是1),再去乘上平移矩阵的:

\[ n_{new} = M * n_{old} = \begin{pmatrix} 1 && 0 && 0 && 0 \\ 0 && 1 && 0 && 2 \\ 0 && 0 && 1 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} * \begin{pmatrix} 1 \\ 0 \\ 0 \\ 1 \end{pmatrix} = \begin{pmatrix} 1 \\ 2 \\ 0 \\ 1 \end{pmatrix} \]

实际上,向量的齐次坐标,的第四维其实是0,即

\[ n_{new} = M * n_{old} = \begin{pmatrix} 1 && 0 && 0 && 0 \\ 0 && 1 && 0 && 2 \\ 0 && 0 && 1 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} * \begin{pmatrix} 1 \\ 0 \\ 0 \\ 0 \end{pmatrix} = \begin{pmatrix} 1 \\ 0 \\ 0 \\ 0 \end{pmatrix} \]

此时就能得到正确的答案。但是,法向量的坐标系变换 没有这么简单 ,这里只讨论了Translation Only,还有其他情况。那么,应该如何对法向量做坐标系变换呢?

方法一:通用公式

公式

直接给出一个通用公式,它能够计算所有情况下的坐标系变换。

  1. 旧坐标系下的顶点坐标为\(v_{old}\)
  2. 新坐标系下的顶点坐标为\(v_{new}\)
  3. 旧坐标系下的法向量为\(n_{old}\)
  4. 新坐标系下的法向量为\(n_{new}\)

已知,坐标系变换矩阵为\(M\),那么有\(M * v_{old} = v_{new}\)

那么,法向量的坐标系变换,如下

\[ \begin{pmatrix} nx_{new} \\ ny_{new} \\ nz_{new} \\ nw_{new} \end{pmatrix} = (M^{-1})^{T} * \begin{pmatrix} nx_{old} \\ ny_{old} \\ nz_{old} \\ nw_{old} \end{pmatrix} \]

推导过程

1. 向量乘法转为矩阵乘法

记平面上的一点\(\vec{v} = (x,y,z,w)\)、平面法向量\(\vec{n}=(n_x, n_y, n_z, n_w)\)。因此有向量乘法:

\[ \vec{n} * \vec{v} = 0 \]
\[ (n_x, n_y, n_z, n_w) * (x, y, z, w) = 0 \]
\[ n_x*x + n_y*y + n_z*z + n_w*w = 0 \]

平面上有很多个顶点,即可归纳出矩阵相乘的形式

\[ \begin{pmatrix} n_x && n_y && n_z && n_w \end{pmatrix} * \begin{pmatrix} x \\ y \\ z \\ w \end{pmatrix} = 0 \]

即(因为,在本文中向量被视为列向量)

\[ n^{T} * v = 0 \]

2. 在等式上乘上矩阵

记坐标系变换的矩阵为\(M\)

  1. 那么对顶点做变换的话,就要乘上\(M\)
  2. 为了等式依旧成立(事实上,坐标系变换后等式依旧成立),法向量就要乘上\(M^{-1}\)。因为\(M^{-1} *M\)是单位矩阵

\[ n_{old}^T * M^{-1} * M * v_{old} = 0 \]

又因为

\[ M * v_{old} = v_{new} \]

因此,上面的式子可简化为。将此式子记为【式子1】

\[ n^T_{old} * M^{-1} * v_{new} = 0 \]

3. 联立,解算出矩阵

根据《1 向量乘法转为矩阵乘法》一节的结论,有

\[ n^{T} * v = 0 \]

那么当完成坐标系变换后,我们也有

\[ n_{new}^T * v_{new} = 0 \]

与【式子1】联立,即可获得

\[ n^T_{old} * M^{-1} = n_{new}^T \]

通过前乘法转换为后乘法,得到

\[ (M^{-1})^T * n_{old} = n_{new} \]

案例

仅平移

  1. 法向量\(\vec{n_{old}} = (0,0,1)\)
  2. 变换矩阵:只有平移部分,x轴平移2,y轴平移3,z轴平移4
\[ M = \begin{pmatrix} 1 && 0 && 0 && 2 \\ 0 && 1 && 0 && 3 \\ 0 && 0 && 1 && 4 \\ 0 && 0 && 0 && 1 \end{pmatrix} \]

计算

\[ M^{-1} = \begin{pmatrix} 1 && 0 && 0 && -2 \\ 0 && 1 && 0 && -3 \\ 0 && 0 && 1 && -4 \\ 0 && 0 && 0 && 1 \end{pmatrix} \]
\[ (M^{-1})^T = \begin{pmatrix} 1 && 0 && 0 && 0 \\ 0 && 1 && 0 && 0 \\ 0 && 0 && 1 && 0 \\ -2 && -3 && -4 && 1 \end{pmatrix} \]

根据公式

\[ n_{new} = (M^{-1})^T * n_{old} = \begin{pmatrix} 1 && 0 && 0 && 0 \\ 0 && 1 && 0 && 0 \\ 0 && 0 && 1 && 0 \\ -2 && -3 && -4 && 1 \end{pmatrix} * \begin{pmatrix} 0 \\ 0 \\ 1 \\ 0 \end{pmatrix} = \begin{pmatrix} 0 \\ 0 \\ 1 \\ -4 \end{pmatrix} \]

请注意,虽然\(nw_{new}=-4\),但它不会影响法向量的方向和长度。因此,平移后,法向量仍然为\((0,0,1)\)

仅旋转

  1. 法向量\(\vec{n_{old}} = (0,0,1)\)
  2. 变换矩阵:只有旋转的部分,沿X轴旋转30°
\[ M = \begin{pmatrix} 1 && 0 && 0 && 0 \\ 0 && 0.866 && -0.5 && 0 \\ 0 && 0.5 && 0.866 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} \]

计算

\[ M^{-1} = \begin{pmatrix} 1 && 0 && 0 && 0 \\ 0 && 0.866 && 0.5 && 0 \\ 0 && -0.5 && 0.866 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} \]
\[ (M^{-1})^T = \begin{pmatrix} 1 && 0 && 0 && 0 \\ 0 && 0.866 && -0.5 && 0 \\ 0 && 0.5 && 0.866 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} \]

根据公式

\[ n_{new} = (M^{-1})^T * n_{old} = \begin{pmatrix} 1 && 0 && 0 && 0 \\ 0 && 0.866 && -0.5 && 0 \\ 0 && 0.5 && 0.866 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} * \begin{pmatrix} 0 \\ 0 \\ 1 \\ 0 \end{pmatrix} = \begin{pmatrix} 0 \\ -0.5 \\ 0.866 \\ 0 \end{pmatrix} \]

不难发现,变换矩阵只有旋转时,\(M = (M^{-1})^T\)。因此,只有旋转时,可以直接乘以原先的变换矩阵

仅缩放

  1. 法向量\(\vec{n_{old}} = (0,0.6,0.8)\)
  2. 变换矩阵:缩放因子\((2,4,5)\)
\[ M = \begin{pmatrix} 2 && 0 && 0 && 0 \\ 0 && 4 && 0 && 0 \\ 0 && 0 && 5 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} \]

计算

\[ M^{-1} = \begin{pmatrix} 0.5 && 0 && 0 && 0 \\ 0 && 0.25 && 0 && 0 \\ 0 && 0 && 0.2 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} \]
\[ (M^{-1})^T = \begin{pmatrix} 0.5 && 0 && 0 && 0 \\ 0 && 0.25 && 0 && 0 \\ 0 && 0 && 0.2 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} \]

根据公式计算

\[ n_{new} = (M^{-1})^T * n_{old} = \begin{pmatrix} 0.5 && 0 && 0 && 0 \\ 0 && 0.25 && 0 && 0 \\ 0 && 0 && 0.2 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} * \begin{pmatrix} 0 \\ 0.6 \\ 0.8 \\ 0 \end{pmatrix} = \begin{pmatrix} 0 \\ 0.15 \\ 0.16 \\ 0 \end{pmatrix} \overset{归一化}{=} \begin{pmatrix} 0 \\ 0.683941129 \\ 0.729537204\\ 0 \end{pmatrix} \]

方法二:向量的齐次坐标乘以变换矩阵

回顾

  1. 在“前言”当中,我们谈到,如果变换矩阵只有平移的话,用向量的齐次坐标乘上矩阵,也能得到正确答案
  2. 在“仅旋转”当中,我们也发现,如果变换矩阵只有旋转的话,那向量的齐次坐标乘上矩阵也行

那么,这里就引出一个问题,是否变换矩阵只有缩放的时候,用向量齐次坐标乘上矩阵也行?接下来,就讨论“引言”中,“没有这么简单”的原因

案例一:XYZ缩放因子不同

示例

  1. 法向量\(\vec{n_{old}} = (0,0.6,0.8)\)
  2. 变换矩阵:缩放因子\((2,4,5)\),XYZ缩放因子不同

用通用公式来计算(即方法一)

\[ n_{new} = (M^{-1})^T * n_{old} = \begin{pmatrix} 0.5 && 0 && 0 && 0 \\ 0 && 0.25 && 0 && 0 \\ 0 && 0 && 0.2 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} * \begin{pmatrix} 0 \\ 0.6 \\ 0.8 \\ 0 \end{pmatrix} = \begin{pmatrix} 0 \\ 0.15 \\ 0.16 \\ 0 \end{pmatrix} \overset{归一化}{=} \begin{pmatrix} 0 \\ 0.683941129 \\ 0.729537204\\ 0 \end{pmatrix} \]

用向量齐次坐标乘上矩阵来计算(即方法二)

\[ M * n_{old} = \begin{pmatrix} 2 && 0 && 0 && 0 \\ 0 && 4 && 0 && 0 \\ 0 && 0 && 5 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} * \begin{pmatrix} 0 \\ 0.6 \\ 0.8 \\ 0 \end{pmatrix} = \begin{pmatrix} 0 \\ 2.4 \\ 4 \\ 0 \end{pmatrix} \overset{归一化}{=} \begin{pmatrix} 0 \\ 0.51449575542752651213595466566578 \\ 0.85749292571254418689325777610964 \\ 0 \end{pmatrix} \]

这里发现,两个计算结果不同。那哪个是对的呢?

  • 当然是“仅缩放”中计算的是对的,因为它用的是“推导过程”得出的公式
  • 因此,方法二不适用于XYZ缩放因子不同的场景

案例二:XYZ缩放因子相同

示例

  1. 法向量\(\vec{n_{old}} = (0,0.6,0.8)\)
  2. 变换矩阵:缩放因子\((2,2,2)\),XYZ缩放因子相同

用通用公式来计算(即方法一)

\[ n_{new} = (M^{-1})^T * n_{old} = (\begin{pmatrix} 2 && 0 && 0 && 0 \\ 0 && 2 && 0 && 0 \\ 0 && 0 && 2 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix}^{-1})^T * \begin{pmatrix} 0 \\ 0.6 \\ 0.8 \\ 0 \end{pmatrix} = \begin{pmatrix} 0.5 && 0 && 0 && 0 \\ 0 && 0.5 && 0 && 0 \\ 0 && 0 && 0.5 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} = \begin{pmatrix} 0 \\ 0.3 \\ 0.4 \\ 0 \end{pmatrix} \overset{归一化}{=} \begin{pmatrix} 0 \\ 0.6 \\ 0.8\\ 0 \end{pmatrix} \]

用向量齐次坐标乘上矩阵来计算(即方法二)

\[ M * n_{old} = \begin{pmatrix} 2 && 0 && 0 && 0 \\ 0 && 2 && 0 && 0 \\ 0 && 0 && 2 && 0 \\ 0 && 0 && 0 && 1 \end{pmatrix} * \begin{pmatrix} 0 \\ 0.6 \\ 0.8 \\ 0 \end{pmatrix} = \begin{pmatrix} 0 \\ 1.2 \\ 1.6 \\ 0 \end{pmatrix} \overset{归一化}{=} \begin{pmatrix} 0 \\ 0.6 \\ 0.8 \\ 0 \end{pmatrix} \]

可以看到,两种方法计算结果一样,而且法向量在变换前后也无变换。这很好理解,因为XYZ缩放因子相同,三角形所在平面其实没有改变,因此法向量也没有改变。

因此,XYZ缩放因子相同时,也可以用方法二。

结论

法向量的坐标系变换,与顶点的坐标系变换有所不同

  1. 方法一:通用公式。其中,\(\vec{n_{new}}=(nx_{new}, ny_{new}, nz_{new})\)\(nw_{new}\)可忽略
\[ \begin{pmatrix} nx_{new} \\ ny_{new} \\ nz_{new} \\ nw_{new} \end{pmatrix} = (M^{-1})^{T} * \begin{pmatrix} nx_{old} \\ ny_{old} \\ nz_{old} \\ 0 \end{pmatrix} \]
  1. 方法二:齐次坐标乘上变换矩阵
\[ \begin{pmatrix} nx_{new} \\ ny_{new} \\ nz_{new} \\ nw_{new} \end{pmatrix} = M * \begin{pmatrix} nx_{old} \\ ny_{old} \\ nz_{old} \\ 0 \end{pmatrix} \]

两种情况下的法向量都要做归一化

方法一:通用公式 方法二:齐次坐标直接乘以变换矩阵
仅平移
仅旋转
仅缩放(XYZ缩放因子相同)
仅缩放(XYZ缩放因子不同) ×

参考文章

  1. OpenGL Normal Vector Transformation (songho.ca)

相关工具

  1. 网页矩阵计算器 - Reshish