【技术美术】渲染管线空间变换

【技术美术】渲染管线空间变换

管线空间

渲染管线中共经历以下几个空间:

  1. 物体空间(模型空间):模型文件的坐标系,以每个模型的自身为准。
  2. 世界空间:场景中的坐标系,所有物体都需要放在该空间。
  3. 相机空间(观察空间):以相机视角为准的坐标系。
  4. 裁剪空间(DNC 标准设备坐标系):图形接口绘图所用的坐标系。

其中“物体空间”、“世界空间”、“相机空间”是引擎开发者自己定义的空间,这些空间通常都是由 TRS 矩阵表示的,其矩阵参数跟随物体的变换状态而改变,具体见 TRS 矩阵章节。

“裁剪空间”则是由图形接口确定的一个固定范围的方形空间。在不同的图形接口中,其空间参数略有不同:

图形接口 X 轴 Y 轴 Z 轴
Direct3D 向右 (-1,1) 向上 (-1,1) 向前(0,1)
OpenGL 向右 (-1,1) 向上 (-1,1) 向后(-1,1)
Vulkan 向右 (-1,1) 向下 (-1,1) 向前(0,1)

其中:

  • 坐标系原点都是屏幕中心。
  • 空间范围都是映射到屏幕。
  • 深度判断都是 z 越大越深。
  • 部分参数实际可用在接口中自定义,故上述只是一种事实标准。

空间变换

从一个空间变换到下一个空间,都是由矩阵实现的,具体实现方式如下:

  • 物体到世界:就是物体空间矩阵本身。
  • 世界到相机:就是相机的物体空间矩阵的逆矩阵。
  • 相机到裁剪(投影变换):分正交和透视两种变换方案,具体见投影变换章节。

TRS 矩阵

TRS 矩阵是最常用的变换矩阵,其本质就是平移、旋转、缩放矩阵的复合,其复合顺序为:

本文 TRS 矩阵以行业主流的左手坐标系为准,则:

平移矩阵(Translate)

[100x010y001z0001]

旋转矩阵(Rotate)

X=[1000cos(x)sin(x)0sin(x)cos(x)]

Y=[cos(y)0sin(y)010sin(y)0cos(y)]

Z=[cos(z)sin(z)0sin(z)cos(z)0001]

若旋转矩阵要同时表示 3 轴旋转,就是通过对 x、y、z 旋转矩阵的复合得出的,如 Unity 中的复合顺序为:

YXZ

推导复合顺序过程是这样的(四元数的 Euler 函数中也有说明):

  1. 在 Unity 中放一个方块。
  2. 对其先后进行 x、y 旋转并逆序尝试。
  3. 发现 y 旋转永远都是以世界 y 轴,而不是自身 y 轴。
  4. 说明 x 旋转无法干扰 y 旋转,反着说即 x 旋转已经被应用到顶点。
  5. 故矩阵复合一定是先 Y 后 X,Z 同理推出。

=[cos(y)0sin(y)010sin(y)0cos(y)][1000cos(x)sin(x)0sin(x)cos(x)][cos(z)sin(z)0sin(z)cos(z)0001]=[cos(y)0sin(y)010sin(y)0cos(y)][cos(z)sin(z)0cos(x)sin(z)cos(x)cos(z)sin(x)sin(x)sin(z)sin(x)cos(z)cos(x)]=[cos(y)cos(z)+sin(y)sin(x)sin(z)cos(y)sin(z)+sin(y)sin(x)cos(z)sin(y)cos(x)cos(x)sin(z)cos(x)cos(z)sin(x)sin(y)cos(x)+cos(y)sin(x)sin(z)sin(y)sin(z)+cos(y)sin(x)cos(z)cos(y)cos(x)]

缩放矩阵(Scale)

[x000y000z]

旋转矩阵求逆

观察旋转矩阵中各分量旋转矩阵,可以发现:

以 z 旋转矩阵为例(下方Cn表示矩阵中的列向量):

[cos(z)sin(z)0sin(z)cos(z)0001]

  • C1C2=cos(z)sin(z)+sin(z)cos(z)+00=0
  • C1C3=cos(z)0+sin(z)0+01=0
  • C2C3=sin(z)0+cos(z)0+01=0
  • |C1|=cos2(z)+sin2(z)+02=1
  • |C2|=(sin(z))2+cos2(z)+02=1
  • |C3|=02+02+12=1

以此方式可以推断出各分量旋转矩阵都是正交矩阵,而正交矩阵相乘还是正交矩阵,故最终的复合旋转矩阵也是正交矩阵。

根据正交矩阵的性质,其逆矩阵就非常好求了:

Rotation1=RotationT

TRS 矩阵的拆解

从 TRS 矩阵中还原 T,R,S 的信息。

还原 T

还原 T 最简单,因为 R,S 都是 3x3 矩阵,第四列就是 T 的信息。

t=[a14,a24,a34]A=TRS

还原 S

将 4x4 矩阵强制转为 3x3 矩阵后,余下的就是 RS 矩阵。利用 R 是正交矩阵的特性(在“旋转矩阵求逆”章节中得出),将 RS 乘上 RS 的转置矩阵,可以得到:

(RS)TRS=STRTRS=STR1RS=STS=S2

这样就可以非常容易的求出 S 的信息了:

s=[a11,a22,a33]A=(RS)TRS,RS=TRS33

还原 R

https://blog.csdn.net/weixin_39675633/article/details/103434557

有了 S 后可以很容易的获取到 R 矩阵:

R=RSS1

接着观察 R 矩阵的公式,可以发现利用 a23 可以很轻松的求出 cos(x) 的值,而其他项中有很多用该值组成的多项式:

[cos(y)cos(z)+sin(y)sin(x)sin(z)cos(y)sin(z)+sin(y)sin(x)cos(z)sin(y)cos(x)cos(x)sin(z)cos(x)cos(z)sin(x)sin(y)cos(x)+cos(y)sin(x)sin(z)sin(y)sin(z)+cos(y)sin(x)cos(z)cos(y)cos(x)]

但由于 cos(x) 可能等于 0,且正常的反三角函数无法完全逆推原角度,故此 cos(x) 不一定准确。所以需要对这些注意事项特殊处理,不能简单粗暴的直接用 a23 推导所有角度:

  1. |sin(x)|1

    • z=atan2(a21,a22)
    • y=atan2(a13,a33)

    有了 y 和 z 的角度后我们便可以通过带入求解真正的 cos(x),但依然要注意值为 0 的问题,好在 cos 和 sin 同时只可能有一个为 0,故只需要简单的判断即可:

    1. a210cos(x)=a21/sin(z)
    2. a21=0cos(x)=a22/cos(z)

    有了未损失原角度信息的 cos(x) 后,x 就很好求出了:

    • x=atan2(a23,cos(x))
  2. |sin(x)|=1 时,此时镜头朝正上或正下。

    由于 cos(x) 的值为 0,因此无法使用第 1 点的计算方法了,但因为 sin(x)=±1sin(x)=a23),所以其他的项代数式变的可用。

    1. sin(x)>0 时,此时镜头朝下,z 旋转轴与 y 旋转轴反向。

      • a11=cos(y)cos(z)+sin(y)sin(z)=cos(yz)
      • a12=sin(y)cos(z)cos(y)sin(z)=sin(yz)

      于是 yz=atan2(a12,a11)

    2. sin(x)<1 时,此时镜头朝上,z 旋转轴与 y 旋转轴同向。

      • a11=cos(y)cos(z)sin(y)sin(z)=cos(y+z)
      • a12=(sin(y)cos(z)+cos(y)sin(z))=sin(y+z)

      于是 y+z=atan2(a12,a11)

    可以发现无论怎么求,只能同时求到 y 和 z,因为此时发生了万向锁,y 和 z 的旋转效果变的相关了。由于相关,导致 y 和 z 可以有多种取值组合,因此这种情况下无法求解原角度了。但为了返回结果,我们可以直接随便选一种组合,例如假定 z 始终为 0。于是该情况的 R 信息也可以确定了:

    1. sin(x)>0

      • z=0(z 旋转全部转移到 y 上)
      • y=atan2(a12,a11)
      • x=π/2(镜头朝正下方)
    2. sin(x)<0

      • z=0(z 旋转全部转移到 y 上)
      • y=atan2(a12,a11)
      • x=π/2(镜头朝正下方)

可以看出部分情况下旋转是无法被真正逆推回来的,这也解释了为什么镜头旋转到世界正上方或正下方时无法继续沿镜头上下旋转的原因。因此旋转物体时要保留原始的旋转值,不能完全依靠矩阵。

投影变换

本文剪辑空间以 Direct3D 为准,因为这和 TRS 矩阵的主流一样都是左手坐标系,所以更方便变换。

正交投影

投影过程就是简单的范围缩放偏移,没有近大远小的效果,难以看出深度关系,常用于 2D 游戏。

若视锥体参数如下:

  • l:视锥体左平面
  • r:视锥体右平面
  • b:视锥体下平面
  • t:视锥体上平面
  • n:视锥体近平面
  • f:视锥体远平面

则投影矩阵为:

ortho(r,l,t,b,n,f)=[2rl00r+lrl02tb0t+btb001fnnfn0001]

特别的,当视锥体的左右、上下平面坐标对称时:

ortho(r,t,n,f)=[1r00001t00001fnnfn0001]

推导过程

https://zhuanlan.zhihu.com/p/474879818

正交投影中从相机空间转换到裁剪空间,是一个方形空间到另一个方形空间的转换,所以只需分别考虑三个轴的各自变化就行,若根据直线实现映射,可构成以下公式:

Xn=k1Xe+b1Yn=k2Ye+b2Zn=k3Ze+b3

其中 n 下标表示转换后的剪辑空间坐标,e 表示原本的视图空间坐标。

当有以下参数:

  • l:视锥体左平面
  • r:视锥体右平面
  • b:视锥体下平面
  • t:视锥体上平面
  • n:视锥体近平面
  • f:视锥体远平面

映射效果如下(要注意图片使用的是 OpenGL 剪辑空间, Direct3D 的 Z 的计算与其不同):

img

带入参数计算后即可推出:

k1=1(1)rlb1=Xnk1Xe=11(1)rlr=rlrl2rrl=r+lrl

k2=1(1)tbb2=Ynk2Ye=11(1)tbt=tbtb2ttb=t+btb

k3=10fnb3=Znk3Ze=110fnf=fnfnffn=nfn

再将这种映射方法套用到矩阵即可得到

ortho=[1(1)rl00r+lrl01(1)tb0t+btb0010fnnfn0001]

透视投影

与人眼一样,随深度不同,具有近大远小的效果,容易看出物体距离远近,常用于 3D 游戏。

若视锥体参数如下:

  • n:近平面
  • f:远平面
  • v:视野上下角度
  • r:视野宽高比

Perspective=Ortho(tan(v2)n/r,tan(v2)n,n,f)[n0000n0000n+fnf0010]

推导过程
  1. 问题分析

    透视投影的空间是一个四棱台,将其变换到剪辑空间的步骤可以拆成两步:

    1. 将四棱台缩放为长方体,实现近大远小。
    2. 将长方体缩放到剪辑空间,等同于正交投影。

    由于正交投影已推导,故现在只需要求出将四棱台缩放为长方体的方法就行。

  2. 公式推导

    https://www.zhyingkun.com/perspective/perspective/

    img

    由于缩放为长方体,故对于处在视锥线上的点,缩放后应与在近平面的 x,y 坐标一致。以上图为例,应有:

    f(y)=ys

    根据相似三角形原理,可得:

    ysd=yzys=ydz

    由于 d 就是近平面距离,若近平面距离为 n,则:

    f(y)=ynzys=f(y)f(x)=xnz(x、y缩放一致)

  3. 矩阵推导

    现在要将该公式反应在矩阵变换上:

    • 对于 n,这是一个定值,直接利用缩放矩阵的原理就可以实现。
    • 对于 z,这是一个变量,肯定无法直接写在矩阵中,但可以借助其次坐标 w 归一化的特性,将向量的 w (位置在 m43)设为 z 即可。

    于是便可得出初步矩阵:

    [n0000n00????0010]

    其中 z 的系数都被标记为?,因为 z 也会受 w 归一的影响,而我们实际需要 z 保持不变,故需要对这些能对 z 产生作用的系数进行推导,以确保最终计算出的向量归一化前的 z 分量为z2

    由于前两个系数(m31,m32)是与 x,y 相乘,我们不需要所以始终为 0,又因为输入向量的 w 分量默认为 1,此时当剩余的两个系数(m33,m34)分别为 A,B 时,可将问题写成以下公式:

    Az+B=z2

    带入 n(近平面 z),f (远平面 z)两个已知实例:

    An+B=n2Af+B=f2

    利用消元法可得出:

    A(nf)=n2f2A=(n+f)(nf)nfA=n+f

    (n+f)n+B=n2B=n2(n2+fn)B=nf

    于是最终矩阵可得出为:

    [n0000n0000n+fnf0010]

线性深度逆推

利用透视投影计算出的新深度是非线性的。因为利用 (n+f)znf 抵消除 z 得出的新 z,只能保证远近平面一致,而其他值域则会受到非线性变化(具体变化如下图)。因此当希望得到远近平面间的线性深度时,必须要进行进一步处理。

投影函数

首先归纳出各种投影对 z 的影响函数:

  • 正交

    z=znfn

  • 逆正交

    z=z(fn)+n

  • 透视

    z=(n+f)znfz

  • 逆透视

    z=(n+f)znfzz=(n+f)zznfzz(n+f)zz=nfzz(1n+fz)=nfzz=nfz(n+f)z=nfn+fz

投影过程

正常的投影过程如下:

z[n,f]线=(z[n,f]线)z[0,1]线=(z[n,f]线)

由此可以逆推出 z[0,1]线 的求解过程:

z[n,f]线=(z[0,1]线)z[n,f]线=(z[n,f]线)z[0,1]线=(z[n,f]线)

z[0,1]线=(((z[0,1]线)))z=(nfn+f(z(fn)+n))nfnz=(nffz(fn)))nfnz=nfn(ffz(fn))1)z=nfn(f(fz(fn))fz(fn)))z=nfn(z(fn)fz(fn)))z=nzfz(fn))

posted @   BDFFZI  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示