法向量的坐标系变换
在本文中
1. 矩阵是行优先存储
2. 向量是列向量
3. 向量 = 矩阵 * 向量。其中,乘法是行列式的乘法规则
知识回顾
回顾一个知识
- 三维向量\(\vec{n} = (x,y,z)\)的齐次坐标为\((x,y,z,0)\),其中第四维为0
- 三维点\(p=(x,y,z)\)的齐次坐标为\((x,y,z,1)\),其中第四维为1
引言
法向量的坐标系变换,与顶点位置的坐标系变换,有所不同。
举个例子,法向量(1,0,0)、顶点坐标(0,0,0),做一个(0,2,0)的平移变换
按照我们的直观感受
- \((0,0,0) + (0,2,0)\),因此顶点坐标就变成(0,2,0)
- 而法向量,\((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,还有其他情况。那么,应该如何对法向量做坐标系变换呢?
方法一:通用公式
公式
直接给出一个通用公式,它能够计算所有情况下的坐标系变换。
记
- 旧坐标系下的顶点坐标为\(v_{old}\)
- 新坐标系下的顶点坐标为\(v_{new}\)
- 旧坐标系下的法向量为\(n_{old}\)
- 新坐标系下的法向量为\(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\)
- 那么对顶点做变换的话,就要乘上\(M\)
- 为了等式依旧成立(事实上,坐标系变换后等式依旧成立),法向量就要乘上\(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}
\]
案例
仅平移
- 法向量\(\vec{n_{old}} = (0,0,1)\)
- 变换矩阵:只有平移部分,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)\)
仅旋转
- 法向量\(\vec{n_{old}} = (0,0,1)\)
- 变换矩阵:只有旋转的部分,沿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\)。因此,只有旋转时,可以直接乘以原先的变换矩阵
仅缩放
- 法向量\(\vec{n_{old}} = (0,0.6,0.8)\)
- 变换矩阵:缩放因子\((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}
\]
方法二:向量的齐次坐标乘以变换矩阵
回顾
- 在“前言”当中,我们谈到,如果变换矩阵只有平移的话,用向量的齐次坐标乘上矩阵,也能得到正确答案
- 在“仅旋转”当中,我们也发现,如果变换矩阵只有旋转的话,那向量的齐次坐标乘上矩阵也行
那么,这里就引出一个问题,是否变换矩阵只有缩放的时候,用向量齐次坐标乘上矩阵也行?接下来,就讨论“引言”中,“没有这么简单”的原因
案例一:XYZ缩放因子不同
示例
- 法向量\(\vec{n_{old}} = (0,0.6,0.8)\)
- 变换矩阵:缩放因子\((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缩放因子相同
示例
- 法向量\(\vec{n_{old}} = (0,0.6,0.8)\)
- 变换矩阵:缩放因子\((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缩放因子相同时,也可以用方法二。
结论
法向量的坐标系变换,与顶点的坐标系变换有所不同
- 方法一:通用公式。其中,\(\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}
\]
- 方法二:齐次坐标乘上变换矩阵
\[
\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缩放因子不同) |
√ |
× |
参考文章
- OpenGL Normal Vector Transformation (songho.ca)
相关工具
- 网页矩阵计算器 - Reshish