世界坐标系到观察坐标系的变换步骤_实时渲染中的坐标系变换(4):投影变换-2...

525c66f4ef340871eef8244d99cf85e5.png

上一篇 介绍了3D图形学中透视相机的成像原理,和 视锥(View Frustum) / FOV / Zoom / 宽高比(Aspect Ratio) 等概念。这一篇可以开始写投影矩阵到底是怎么计算的了。


先来推导 CameraSpace 到 ClipSpace 的变换矩阵。其实这个变换矩阵,就是大家常说的"投影矩阵/Projection Matrix"。

"Clip Space"是什么意思?"Clip"是裁剪的意思,"Clip Space"顾名思义,就是裁剪空间。回到"视锥"的概念,"Clip"的第一层意思,就是"裁剪":所有3D场景中的物体,只有在处于视锥中间时,才会参与渲染。超出视锥的部分,统统不参与裁剪。"视锥"的具体几何形状如何确定?参考下图,除了 FovH 和 FovV 两个夹角外,还需要确定棱锥顶面和底面的位置,这两个面被称为"裁剪平面",离视锥顶点(也就是相机所在位置)的距离,通常被记作

b29c7d546ba0de85b36ed3dd87794799.png
视锥;裁剪平面 near/far plane

之前讲到了"视锥"是为了构造计算机图形学的成像原理而创造的。既然是成像,那一定有"成像平面"。按当前图形学的规范,成像平面可以理解为,是放在near plane也就是近裁剪平面这个位置的。当然,理论上,成像平面改成与near plane平行的任意一个截面都可以。之所以选择 near plane,一方面是为了约定俗成,一方面是为了数学上的便利性(后面会讲到)。

这个成像平面上记录的,就是最终的一帧渲染结果。而之前也讲到过,成像平面上的每个像素,最终选取什么颜色,是由相机到这个像素的连线,与视锥中的3D场景的交点来决定的(这个说法也是简化而不准确的,后面讲投影变换的具体计算以后会再讲)。——因此这个"成像/投影"的过程,实际上是将3D场景压缩成一张2D图像的过程。

视锥是以相机为顶点来构造的,而相机所处的位置,又是 camera space 的坐标系原点。见下图:

dc3aa4a1cacc84f662b76738841ab1a7.png
Camera Space & World Space

看到这里你可能已经发现了,这个camera space跟 上一篇 的不一样啊?Well,上一篇为了简化问题,在做推导的时候,把camera space的坐标系按照 world space 的方式来构建:camera space x轴正方向为相机朝向,z轴正方向为相机的“头顶”朝向 (也就是常说的"up vector")。——但是实际上的 camera space并不是这样。camera 比较特殊,除camera 之外,其他的摆放到3D场景中的物体基本上都是按照上述方式来生成自己的local坐标系。

Camera Space的特殊之处在哪里?就在于,一定会把 camera 朝向的方向作为 z轴(正方向、或者负方向)。而之所以一定会对 camera space 的 z轴做这个约定俗称的限制,是为了方便计算“Z值/depth值”。


什么是"Z值/depth值"?这是个图形学的基本原理。当3D场景中有多个物体时,从视锥的角度看过去,可能会有多个物体,映射到同一个像素上。这时候如何保证正确地选择该选用哪个物体的颜色来作为这个像素的颜色呢? 直观的方法就是:选择离相机近的。

实际上图形管线里面也是这样做的。在上一篇讲过,视锥的"成像平面",与视锥的近裁剪平面(near plane)重合。在这个"成像平面"上,除了记录颜色之外,还要记录"该像素所对应的3D物体到near plane的距离"。这样,在遍历3D场景中的物体,完成渲染时,如果有物体被投射到某个像素上,但其"离near plane的距离"不是最近的,那么就跳过不渲染。

这张记录"3D物体到near plane的距离"的"成像贴图",叫做深度缓冲/depth buffer。因为现在约定俗成的相机 camera space 都是视线沿着z轴,所以又俗称为 "Z buffer",对应地,一个像素的深度值,也俗称为 "Z值"。

这里需要注意的一个细节是:Z值是”物体到 near plane平面的距离“,而不是”物体到 camera 的距离“。这两者之间有细微的区别。具体会在推导投影矩阵的时候做进一步说明。


下面以Unity为例来推导投影矩阵。

一个3D场景,其中的各种模型的3D坐标可能千奇百怪。但是在通过视锥、渲染到"成像平面"上以后,坐标都应该变成相同的2D图像坐标。此外,还需要根据3D坐标,计算出刚才讲到的Z值。因此,投影矩阵做的主要是这样一件坐标系转换的事情。

Unity中的投影变换是怎么样的呢?可以用如下代码进行测试,相机的transform等设置如下所示:

ade7906a49ba3f9dafdf56d9af61c801.png
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GetProjectionMatrix : MonoBehaviour
{

    // Start is called before the first frame update
    void Start()
    {
        Camera t = GetComponent<Camera>();
        Debug.Log(t.projectionMatrix);
    }
}

b74117123d5f109d1f32bba7632d667f.png
Unity投影矩阵(透视投影/FOV为60度/near plane为0.3/far plane为1000;显示分辨率为1024x768)

上面这个矩阵的数值是怎么来的?Well,先直接给公式:

归一化齐次坐标以后的结果是:

带入到前面的测试例子中进行计算,可以发现,Unity的camera上的 FOV 设置,其实设置的是 FovV。

另外,从上面的公式可以看出,投影以后的屏幕坐标的x/y分量,都跟3D空间camera space的z坐标有关。举例来说,x分量为

。camera space 3D空间中,相同的x,z越大,投影变换以后的x分量越靠近0。"近大远小"的透视效果,就是这么算出来的。

对视锥里面的所有 3d 坐标来说,上面这个变换矩阵可以起到什么样的作用呢?可以取几个视锥内部的顶点来测试一下。顺便说一下,Unity这个投影矩对应的 camera space,z轴是朝内的:

d071700a987d9c069aa193d58579bb78.png
Unity Camera Space

按照控制变量法,取如下这些测试点:

  • z轴与near plane交点
    ,投影变换后的坐标为
  • z轴与far plane交点
    ,投影变换后的坐标为
  • x轴平行于near plane的点
    ,投影变换后的坐标为
  • x轴平行于near plane的点
    ,投影变换后的坐标为
  • y轴平行于near plane的点
    ,投影变换后的坐标为
  • y轴平行于near plane的点
    ,投影变换后的坐标为

从上面这些测试可以进一步得出(推导过程略),Unity的投影矩阵,是把视锥内的所有3D坐标,转换到 [-1,1] 范围之内,即:

,其中的 x/y 坐标,会经过进一步的计算,变换成 screen space 的屏幕坐标;而 z值,则会经过一些变换,成为存放在 zbuffer深度缓冲 里面的 "Z值"。

参照投影变换对视锥内3D坐标的影响,可以发现:视锥内,平行于near plane 或者 far plane的任意一个截面,其上面的所有点,经过投影变换以后的z坐标(深度值),都是相同的。因此这个"z value",记录的并不是3D空间中的点到 camera 的距离,而是这些点到 near plane 的垂直距离。参见下图:

1f3feb42e279e8bfeb93a6cf1dad3e16.png
上图中,蓝色虚线为正确的Z值计算方式,绿色虚线为错误的Z值计算方式

从 clip space 到 screen space,是一个比较直观的 2D 变换,这里就不展开了。

以Unity为例,因为在 clip space 中,x/y 的取值范围都被做成了 [-1, 1],所以在转换到 screen space 时,只要把取值范围 [-1, 1] 变换到 [0,1] 即可。


前面推导的投影变换,叫做透视投影变换(perspective projection)。在3D渲染里,除了透视投影变换(perspective projection),还有正交投影变换(orthographic projection)。两者的区别是:

  • 透视投影变换,有"近大远小"的透视效果。3D空间中的两条平行线,在投影变换以后会相交于某个"灭点"。
  • 正交投影变换,没有"近大远小"的透视效果。3D空间中的两条平行线,在投影变换以后,仍旧是平行的。

以Unity为例来说明正交变换。

正交变换与透视变换的一个明显区别是:视锥变了,不再是一个棱锥,而是一个长方体。因此,在衡量"成像平面"的大小时,不再需要 FovH/FovV 的概念(实际上这两个夹角也不存在了),而是可以直接使用 SizeH / SizeV。如下图:

00a22fc498b05d973420aca50852b12d.png
Unity 正交相机视锥

相应地,上一篇 中讲到的 ZoomH/ZoomV 概念,也不再由 FovH/FovV 计算得到,而是由 SizeH / SizeV 计算得到,约定俗成的公式是:

Unity中的正交投影变换的变换矩阵为:

通过上面的公式,可以计算得出,Unity正交变换的投影矩阵,同样是把视锥内的所有3D坐标的x/y/z值转换到[-1,1]范围。

另外需要注意的一点是:Unity camera为正交相机时,其参数面板上可以设置的"size"值,其实是这里的

。举例来说,下图这种设置时,其正交投影矩阵如下:

aaa752f034bf484dd090f6467a5375dc.png

80d540e0ca3d015a62d156ed0e848a50.png

暂时先写到这里。UE4的camera space和投影变换,跟Unity还不一样,之后再补。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值