三 视图变换, 投影变换, 正交投影, 透视投影

一 视图/相机 变换 View/Camera transformation

什么是视图变换

在现实生活中是如何拍照的?

  1. 找一个地方, 将拍摄的内容(人或物)安排位置 -> 模型变换(model transformation)
  2. 找一个角度, 将相机按照角度摆好 -> 视图变换 (view transformation)
  3. 拍照 -> 投影变换 (projection transformation)

如何执行视图变换

定义相机
  1. 定义位置 e
  2. 看的方向 g
  3. 向上方向 t
观察点

为了能够观察物体的运动, 做以下约定:

  1. 相机永远放在原点 (0,0,0)
  2. 相机永远看向 -Z 轴方向
  3. 相机向上方向为 Y 轴方向

将相机从任意一点移动到原点:

  1. 将 e 移动到原点

    1, 0, 0, -xe

    1, 1, 0, -ye

    1, 0, 1, -ze

    1, 0, 0, 1

  2. 旋转 g 到 -Z 方向

  3. 旋转 t 到 Y 方向

  4. (g × t)的结果向量 旋转到 X 方向

对于2-4点不容易求旋转矩阵

由于 X轴, Y轴, Z轴的坐标简单, 那么可以尝试反过来, 从结果变换回原始:

复制代码
X(1,0,0) -> (g × t)
Y(0,1,0) -> t
Z(0,0,1) -> -g

那么先求旋转矩阵的逆:

复制代码
R_-1 = [x_(g×t), x_t, x_-g,  0]
       [y_(g×t), y_t, y_-g,  0]  
       [z_(g×t), z_t, z_-g,  0]
       [      0,   0,    0,  1]

根据旋转矩阵的性质: 旋转矩阵的逆 = 旋转矩阵的转置

转置得到旋转矩阵:

复制代码
R = [x_(g×t), y_(g×t), z_(g×t),  0]
    [    x_t,     y_t,     z_t,  0]  
    [   x_-g,    y_-g,    z_-g,  0]
    [      0,       0,       0,  1]

二 投影变换 Projection transformation

两种投影: 正交投影, 透视投影

正交投影并不会给人们带来"近大远小"的感觉, 但是透视投影会有这种现象.

1. 正交投影 Orthographic projection

假设摄像机距离物体无限远, 那么近处的平面与远处的平面会逐渐趋近于完全一样的大小.

简单的理解
  1. 把相机移动到原点
  2. 相机看向 -Z 方向
  3. 相机向上的方向为 Y
  4. 把 Z 坐标丢掉, 即可得平面矩形
  5. 将结果矩形平移缩放到 [-1, 1]^2 平面上, 方便后续的计算

问题: 如果把 Z 丢掉, 如何区分物体的前后关系呢?

定义
  1. 定义空间中的一个立方体

    包括x轴上的左右 [l, r], y轴上的下上 [b, t], z轴上的远近 [f, n]

  2. 将立方体的中心移动到原点, 将 x,y,z 个方向分别拉伸到 [-1, 1]^3

    也就是将立方体映射到一个正则(规范, 标准)立方体: canonical cube [-1, 1]^3

矩阵表示
  1. 平移到原点

    立方体的中心: (r+l)/2, (t+b)/2, (n+f)/2

    1, 0, 0, -(r+l)/2

    0, 1, 0, -(t+b)/2

    0, 0, 1, -(n+f)/2

    0, 0, 0, 1

  2. 缩放到 [-1, 1]^3

    立方体原本覆盖的范围: r-l, t-b, n-f (因为摄像机面向的是-Z, 所以 近>远)

    缩放后的范围: 1-(-1) = 2

    那么应该缩放的因子: 2 / (r-l), 2 / (t-b), 2 / (n-f)

    2/(r-l), 0, 0, 0

    0, 2/(t-b), 0, 0

    0, 0, 2/(n-f), 0

    0, 0, 0, 1

  3. 先平移, 后缩放

    M_ortho =

    2/(r-l), 0, 0, 0\] \[1, 0, 0, -(r+l)/2

    0, 2/(t-b), 0, 0\] \[0, 1, 0, -(t+b)/2

    0, 0, 2/(n-f), 0\] \[0, 0, 1, -(n+f)/2

    0, 0, 0, 1\] \[0, 0, 0, 1

2. 透视投影 Perspective projection

投影原理

把摄像机放在某一个位置上, 将摄像机当做一个点, 从摄像机出发把空间连成一个四棱锥, 其中从某一个深度到另一个深度之间的这块区域称作 "视椎体" Frustum, 把这块区域中的所有物体都显示出来, 显示到近处的平面上.

在远平面上的4个点, 将这些点在同一平面上向内挤压, 挤压到与近平面相同的高度上, 那么从近平面到远平面整体就会形成一个长方体, 有了长方体, 那么再做一次正交投影就可以得到最终的平面.

也就是把透视投影拆分为两部分操作:

复制代码
1. 挤压长方体 (从透视到正交)
2. 正交投影

性质:

复制代码
1. 在挤压的过程中, 近平面上的所有点n永远不会变
2. 远平面上的点f由于只是在平面上向内挤压, 所以z值不会变
3. 远平面上的中心点, 在挤压之后没有任何变化
矩阵表示, 推理
  1. 挤压, 从投影到正交

    可以先解决高度y, 也就是远平面上任意一点的 y 如何被挤压到 y'

    假设远平面上的点为P(x, y, z), 近平面上对应的点为M(x', y', z')

    那么点P在挤压后的高度y应该与点M的高度y'相同

    远平面的点P距离摄像机的距离为z, 近平面的点M距离摄像机的距离为n

    在相似三角形中, y' 与 y 的关系是什么呢?

    底边上 n 与 z 的比值就等于高度 y' 与 y 的比值, 那么:

    n / z = y' / y

    =>

    y' = (n / z) * y = ny / z

    同理, 求 x':

    x' = (n / z) * x = nx / z

  2. 改写为矩阵

    x\] =\> \[ nx/z

    y\] \[ ny/z

    z\] \[ ?

    1\] \[ 1

由于在齐次坐标中, 点的各个坐标乘以同一个值依旧是同一个点, 那么同时乘以z得到:

复制代码
[ nx/z ]  × z =  [ nx ]
[ ny/z ]         [ ny ]
[   ?  ]         [  ? ]
[   1  ]         [  z ]
  1. 根据向量反推变换矩阵

    目前已经知道向量的大部分的值,

    那么根据矩阵乘法规则来反推左边需要相乘的那个变换矩阵.

    x\] = \[?, ?, ?, ?\] \[nx

    y\] \[?, ?, ?, ?\] \[ny

    z\] \[?, ?, ?, ?\] \[ ?

    1\] \[?, ?, ?, ?\] \[ z

    =>

    计算 nx

    x*? + y*? + z*? + ? = nx

    xn + y0 + z*0 + 0 = nx

    =>

    得到第一行

    x\] = \[n, 0, 0, 0\] \[nx

    y\] \[?, ?, ?, ?\] \[ny

    z\] \[?, ?, ?, ?\] \[ ?

    1\] \[?, ?, ?, ?\] \[ z

    =>

    同理得到其余3行

    x\] = \[n, 0, 0, 0\] \[nx

    y\] \[0, n, 0, 0\] \[ny

    z\] \[?, ?, ?, ?\] \[ ?

    1\] \[0, 0, 1, 0\] \[ z

那么接下来的任务就是求出第三行未知的值

同理, 我们先求右侧结果中的第三个未知值, 再反推左侧的矩阵.

根据投影原理中的性质:

  1. 在近平面上, 任意一点始终不变.

    近平面上点的z值是n

    x\] =\> \[x

    y\] \[y

    z\] \[n

    1\] \[1

    =>

    变换后点不变, 也就是映射回自己

    x

    y

    n

    1

    =>

    在齐次坐标中, 点的坐标乘以一个值还是原来的点

    x\] \* n = \[ nx

    y\] \[ ny

    n\] \[ n\^2

    1\] \[ n

    =>

    回过头来再看我们最终需要求的变换矩阵中的第三行的未知值

    矩阵中未知的第三行乘以向量的结果是 n^2

    ?, ?, ?, ?\] \[x\] = n\^2 \[y

    n

    1

    现在拿到结果了, 那么反推左侧矩阵中第三行的未知值

    由于结果中的第三个值是 n^2, 根据矩阵乘法规则, 肯定与 x, y 的计算无关

    所以左侧矩阵第三行的前两个值一定是 0

    0, 0, ?, ?\] \[x\] = n\^2 \[y

    n

    1

    =>

    构建一个方程, 设第一个未知值为A, 第二个未知值为B

    0, 0, A, B\] \[x\] = n\^2 \[y

    n

    1

    =>

    0x + 0y + A*n + B = n^2

    =>

    方程1

    A*n + B = n^2

  2. 在远平面上, 中心点在变换后依旧不变

    远平面上点的z值是f, 它正对着摄像机, 所以 x, y 都是 0

    x\] =\> \[0

    y\] \[0

    z\] \[f

    1\] \[1

    =>

    变换后点不变, 也就是映射回自己

    0

    0

    f

    1

    =>

    在齐次坐标中, 点的坐标乘以一个值还是原来的点

    0\] \* f = \[ 0

    0\] \[ 0

    f\] \[ f\^2

    1\] \[ f

    =>

    与近平面点同理

    构建一个方程, 设第一个未知值为A, 第二个未知值为B

    0, 0, A, B\] \[0\] = f\^2 \[0

    f

    1

    =>

    00 + 00 + A*f + B = f^2

    =>

    方程2

    A*f + B = f^2

  3. 解方程

    An + B = n^2
    A
    f + B = f^2

    =>

    A = n + f

    B = -nf

那么到此为止, 所求透视矩阵中的所有未知值就都计算出来了.

复制代码
[n, 0, 0, 0]
[0, n, 0, 0]
[?, ?, ?, ?]
[0, 0, 1, 0]

=>

M_persp->ortho = 
    [n, 0,   0,   0]
    [0, n,   0,   0]
    [0, 0, n+f, -nf]
    [0, 0,   1,   0]
  1. 求最终的投影矩阵

最终矩阵用挤压矩形的矩阵再乘以正交矩阵即可.

自右向左应用:

复制代码
M_persp = M_ortho M_persp->ortho

=

[2/(r-l),       0,        0, 0] [1, 0, 0, -(r+l)/2] [n, 0,   0,   0]
[      0, 2/(t-b),        0, 0] [0, 1, 0, -(t+b)/2] [0, n,   0,   0]
[      0,       0,  2/(n-f), 0] [0, 0, 1, -(n+f)/2] [0, 0, n+f, -nf]
[      0,       0,        0, 1] [0, 0, 0,        1] [0, 0,   1,   0]
相关推荐
superman超哥2 小时前
仓颉Result类型的错误处理模式深度解析
c语言·开发语言·c++·python·仓颉
八月的雨季 最後的冰吻2 小时前
FFmepg-- 38-ffplay源码-缓冲区 audio_buf调试
c++·ffmpeg·音视频
会思考的猴子2 小时前
UE5 C++ 笔记 GameplayAbilitySystem人物角色
c++·笔记·ue5
ht巷子2 小时前
Qt:信号与槽
开发语言·c++·qt
千里马-horse2 小时前
Checker Tool
c++·node.js·napi
北辰水墨2 小时前
【算法篇】单调栈的学习
c++·笔记·学习·算法·单调栈
惆怅客1232 小时前
在 vscode 中断点调试 ROS2 C++ 的办法
c++·vscode·调试·ros 2
眠りたいです2 小时前
Docker:镜像的运行实体-Docker Container
java·运维·c++·docker·容器·eureka
ComputerInBook2 小时前
C++ 标准提供的 thread (线程)之 join() 函数示例(windows平台)
c++·线程·join函数