视图变换和投影变换矩阵的原理及推导,以及OpenGL,DirectX以及Unity的对应矩阵 agile Posted on Oct 2 2021 优秀博文 > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [zhuanlan.zhihu.com](https://zhuanlan.zhihu.com/p/362713511) 前言 -- 我们知道我们在屏幕上看见的画面其实都是二维的,那么是怎么做到把三维空间中所展示的内容显示到一个二维空间上呢?这里就需要我们的视图变换以及投影变换来实现了。 整个过程我们可以理解为生活中拍照片,例如我们想去一个摄影棚拍全家福,我们可以分如下三步: 1. 首先我们可以先决定好站位问题,谁在前谁在后等等,这步操作就是模型变换(Model Transformation)。 2. 然后我们得全家移步到摄影棚拍(即把摄影棚的相机当做坐标原点),然后还要摆正相机(相机自身的旋转),把相机镜头朝向我们(相机的朝向)以及相机要和我们保持一定的距离(与相机的距离)才能拍出好的照片。这步操作就是我们本章要学习的视图变换(View/Camera Transformation) 3. 最后就是按下相机的快门,拍出照片(照片就是二维的),这步操作也是我们本章要学习的投影变换(Projection Transformation) 上诉这些变换操作就是我们常说的 **MVP 变换**,它们对应的矩阵即 **MVP 矩阵**。若对变换和矩阵不熟悉的,可参考: [王江荣:变换与矩阵](https://zhuanlan.zhihu.com/p/362551995) 视图变换(View/Camera transformation) -------------------------------- 平时大家一定也都拍过照片,想要拍出想要的照片,我们肯定要选好拍照的位置,对准要拍摄的物体,以及拿好相机(因为可以横着拍,竖着拍或者斜着拍)。在摄像机成像中,也是一样的道理,我们首先需要定义摄像机如下几个属性: * 位置(Position):即 Camera 的坐标,我们可以标记为  * 朝向(LookAt):即 Camera 往哪看,其实也就是相机的 - z 轴向量,我们可以标记为  (单位向量) * Y 轴的朝向(UpDirection):即 Camera 的 y 轴向量,我们可以标记为  (单位向量)。例如若 Camera 的 y 轴和世界坐标系的 y 轴平行,则看见的画面是正的,若 Camera 的 y 轴绕 z 轴旋转,则看见的画面也会跟着旋转。  注:我们约定在右手坐标系中,Camera 的 LookAt 方向为自身的 - z 轴方向(例如 3DMax 中的 Camera),而在左手坐标系中 LookAt 方向为自身的 z 轴方向(例如 Unity 中的 Camera)。**本文用右手坐标系进行说明以及推导。** 通过相对运动我们知道,如果摄像机所观察的物体和摄像机一起运动,那么摄像机所观察到的画面是保持不变的,例如下图中,两个摄像机的位置朝向都不相同,看见的画面确是一样的。  那么假如有个变换矩阵  ,使我们的摄像机变换为**位置在世界坐标原点,看向 - z 方向,摄像机的 y 轴即为世界坐标 y 轴**。那么摄像机所观察的物体同样使用矩阵  进行变换,即可保持所观察到的画面不变。这样的操作就是我们所谓的**视图变换,变换矩阵**  **即为视图变换矩阵。** 至于我们为什么要执行视图变换,是为了使后续在执行投影变换时,简化计算,看到后面你就知道了。 接下来我们来看看矩阵  的值应该如何计算,根据前面的描述我们可以把视图变换拆分为如下几步: 1. 通过平移变换,将 Camera 移到原点位置,即使  变为 (0,0,0) 2. 通过旋转变换,使 Camera 看向 - z 方向且自身的 y 轴与世界坐标的 y 轴重叠,即使  变为 (0,0,-1),  变为 (0,1,0)。 通过复合变换我们可知,只需要把上面两部所对应的矩阵相乘得到的结果及时我们的视图变化矩阵  的值。 首先是平移变换的矩阵,这个很简单,设  ,我们只需要移动  即可将摄像机移动到原点,对应矩阵(设为  )即为: >  移动到原点后,我们要通过旋转,使  变为 (0,0,-1) ,  变为 (0,1,0)。这一步我们有点无从下手,因为我们之前讨论旋转矩阵时都是绕什么什么轴旋转多少多少度,而在这里我们并不知道应该绕什么轴旋转多少度。 那么应该如何解决呢?这里有一个逆向思维,即:我们不知道如何将  变为 (0,0,-1) ,  变为 (0,1,0),但是我们能够知道如何将 (0,0,-1) 变为  ,(0,1,0) 变为  ,也就是上诉旋转的**逆变换**,我们假设我们原先要求的矩阵为  ,那么它的逆变换矩阵即为  。 因为通过前面的知识我们知道,旋转矩阵中的值即为 x,y,z 三个轴的单位向量旋转后的值。在这里 (0,1,0) 变为  ,(0,0,1) 变为了  ,因为  代表的是摄像机自身的 - z 轴方向,  代表的是摄像机自身的 y 轴方向,根据**叉乘**的定义可得摄像机自身的 x 轴方向即为  ,即 (1,0,0) 变为  。 那么我们就可得: >  同时又因为我们的旋转矩阵为**正交矩阵**,其逆矩阵即等于转置矩阵(这一点在之前的文章着重介绍和证明过了),  ,因此  的值即为  的转置,可得: >  所以即可求得视图变化矩阵  的值,即为上面两个矩阵相乘: >  投影变换(Projection transformation) ------------------------------- 摄像机对好要拍摄的物体后,就差最后按下快门变成照片这一步了,而这一步也就是我们的投影变换,即从三维变成二维。在图形学中,投影变换分为如下两种,分别为正交投影和透视投影。 ### 正交投影(Orthographic projection) 正交投影示意图如下:  可以发现,正交投影没有近大远小的现象,视线是互相平行的,类似于平行光照,这种投影更多的用于工程图。图中我们还可发现有一个 Near clip plane 和一个 Far clip plane,它们分别代表该摄像机能看见的最近的距离以及最远距离,即摄像机只能看见两个平面之间的物体。在正交投影中,Near clip plane 的大小等于 Far clip plane,能被摄像机拍摄到的空间即为一个长方体。 由于已经进行过了视图变换,即摄像机在原点,看向 - z 轴方向,摄像机的 y 轴与世界坐标 y 轴重叠。因此我们的所拍摄的空间也能够确定下来,如下图:  该空间的宽度即为 L 点和 R 点的距离,设 L 点和 R 点的 x 轴坐标分别为 l 和 r ,高度即为 T 点和 B 点的距离,设 T 点和 B 点的 y 轴坐标分别为 t 和 b ,长度即为 N 点和 F 点的距离,设 N 点和 F 点的 z 轴坐标分别为 n 和 f 。可得:r > l 和 t > b,但是 f < n,因为是看向 - z 方向,所以更远的位置 z 的值越小。 同时还要注意,虽然摄像机看向 - z 轴方向,但是 N 点并不一定是 Near clip plane 的中心点,因此 r 和 l 的绝对值不一定相等, t 和 b 的绝对值也不一定相等。 上面的定义也证明了我们视图变换的必要性,如果没有视图变换把摄像机给规定到原点等,我们就很难用上面这些变量来形容出摄像机所观测的空间。 从图中我们也可以发现当从三维变为二维时,空间内的物体的 x 轴和 y 轴位置不会发生变换,而是在 z 轴方向进行了压缩。我们可以把空间中的物体的 z 轴都设置为 0,那么空间中的所有物体都会被压缩到 xy 平面上,也就是变为二维的了。 然而更规范化的做法是将上诉摄像机所观测的空间(即长方体,设为 S )变换成一个**标准立方体**(**canonical cube**)。何为标准立方体?即以原点为中心,边长为 2 的立方体,也就是立方形在 x,y,z 三个轴上都是从 - 1 到 1。 整个变换过程就是我们的**正交投影变换**,其对应矩阵即为正交投影变换矩阵。该变换我们可以分为如下两步(这也体现了视图变换的重要性,使得我们做投影变换变得很简单): 1. 平移变换,将长方体 S 平移到原点。 2. 缩放变换,将长方体 S 的长宽高缩放到 2 同样的,我们来看看这两个变换的矩阵: 首先是移到原点的平移变换,即把长方体 S 的中心点移动到原点,那么我们就要求出长方体中心点的坐标。根据前面的数据,我们可以知道: 中心点 x 的坐标即为  ,中心点 y 的坐标即为  ,中心点 x 的坐标即为  因此平移矩阵(设为  )即为: >  接着是将长方体 S 变为边长为 2 的立方体的缩放变换,那么我们只需要知道长方体的长宽高即可。我们设 x 轴方向的为长度,即为 r-l ,y 轴方向的为高度,即为 t-b ,z 轴方向的为宽度,即为 n-f 。 所以缩放矩阵(设为  )即为: >  因此可以得出最终的正交投影变换矩阵(设为  )为: >  注:该变化会导致物体的被拉伸(因为长方体变成了立方体),在后续的**视口变换**过程中会再对此进行处理。 ### 透视投影(Perspective projection)  与正交投影不同的是,透视投影会有近大远小的现象,类似于我们的人眼或者照相机拍照,这种投影更具有真实感,被广泛使用。从图中我们可以看出 Far clip plane 的面积要大于 Near clip plane,因此我们摄像机所观测的区域不再是一个长方体,而是变成了一个四棱台(即四棱锥去掉顶部),也就是我们常说的**视锥体**(Frustum)。 我们来看下在视图变换后,透视投影在坐标系中的样子,如下图:  通过前面我们知道,投影变换是把相机观测的空间压缩成一个标准立方体,对于这个视锥体我们应该如何压缩呢?有个思路是这样的: 1. 我们先通过某种变换把这个视锥体压缩成一个长方体 2. 再把长方体移到原点上压缩成标准立方体,即等于执行正交投影变换 那么我们只需要计算出第一步的变换矩阵(设为  ),然后将它乘以正交投影变换矩阵,即可得到我们的透视投影变换矩阵(设为  )了。 要使视锥体变为长方体,首先我们要使  变为  的直线(  表示  点到 N 点的距离,后面同理),  变为  的直线,  变为  的直线,  变为  的直线。 首先我们来看看如何使 变为 的直线,做这一步前我们可以从 x 轴方向看向 yz 平面,来观察这个视锥体,方便理解。  设 N 点的 z 值为 n,F 点的 z 值为 f ,(**f < n < 0**)。 通过图中我们可以发现,  和  是相似三角形,可得:  ,即  。 扩展开来可得在  直线上的任意一点,设 (x,y,z),其 y 值只需乘以  即可与 T1 点的 Y 值相等,这样直线  即变为一条  的水平线,  也是同理。 对于  和  ,我们只需要从 y 轴方向观察 xz 平面即可,同样是乘以  即可。 **因此对应视锥体里的任意一点 (x,y,z),我们只需要将其 x 和 y 的值乘以  即可使该视锥体变为长方体。** 但是此时 z 值是否会发生变化我们暂时还不知道,先当做未知来处理(看着似乎不会变换,但实际上并不是这样,后面会在解释)。 关于上面的结论,我们可以得到如下一个变换矩阵: >  这个矩阵有个问题,那就是**矩阵中的 z 是一个变量,导致该矩阵并不是一个常量**。 因此我们这里需要引入齐次坐标的概念,设我们有个常量 k,通过其次坐标我们可以知道,(x,y,z,1) 是等价于 (kx,ky,kz,k) 的,那么可以得知:  因此我们可以把上面的矩阵修改为: >  这样我们矩阵中的变量 z 就被消灭掉了。 接下来我们要看看 z 值的变换,从图中我们可得知的信息为变为长方体后: > 1.Near clip plane 上的任意点,设为 (i,j,n),它的 z 值不变依旧为 n。 > 2.Far clip plane 上的任意点,设为 (k,l,f),它的 z 值不变依旧为 f。 可得如下两个方程:  设我们矩阵中的四个未知数分别为 A,B,C,D,可得:  从中可知 A=B=0,简化得:  即可算出,C=n+f,D=-nf,因此视锥体压缩为长方体的矩阵为: >  因此也可以知道在该变换过程中,任意点的 z 的值可能是会发生变化的。举个例子,视锥体的中心点  变换后为:  我们来比下大小:  由于 f < n < 0,所以  ,2(n+f) < 0,可得(负数除以负数为一个正数)  即**视锥体的中心点在变换后离摄像机更远了**。 变成长方体后,只需在执行一次正交投影变换即可,因此我们的透视投影变换矩阵为: >  ### 对称视锥体 对称视锥体,即为 N 为 Near clip plane 的中心点,F 为 Far clip plane 的中心点,因此我们可得:  那么正交投影矩阵就可以简化为: >  同样的透视投影矩阵也可以简化为: >  ### 使用 FOV 和 Aspect ratio 表示透视投影矩阵 依旧看之前的图片,如下(重新贴一下,方便看):  在图中  我们称之为 **Field of View** (for Y / for Vertical),也就是常说的 **FOV**。自然而然  是 x 轴或者是水平方向的 FOV,我们只需要知道其中一个即可,通常用 y 轴的。我们设  。  的长度(即为宽,设值为 w)与  的长度(即为高,设值为 h)比例我们称之为宽高比(**Aspect ratio**),我们假设为该值为 a,即  。 同时可得:  因为这种表示方式需要我们的视锥体为前面介绍的对称视锥体,因此我们直接将上面的式子带入对称视锥体对应的透视投影矩阵  中,得:  通过上面 yz 平面的侧面图:  我们可以发现:  ,即  。 又因为 w=ah,所以  。 所以可得,FOV 为 θ ,宽高比为 a 时,透视投影矩阵为: >  同时知道了 FOV 和宽高比,我们就可以计算出之前的 r,l,t,b 这些值。 裁剪空间(Clip Space) ---------------- 假如在 MV 变换后,**视图空间(View Space)**下的某个点对应的齐次坐标为 (x,y,z,1),那么经过透视投影变换后,其齐次坐标应为: >  也就是说投影变换后我们会得到一个齐次坐标 (x,y,z,w),其中 w 的值即为变换前视图空间下 z 的值。而投影变换后的坐标(x,y,z,w) 所在的空间即为裁剪空间,也称为齐次裁剪空间。我们 Vertex Shader 中顶点的输出坐标就是在 Clip Space 上,例如常用的 **UnityObjectToClipPos** 方法。 接着 GPU 会**在裁剪空间下做裁剪**,剔除掉不在视椎体范围内的顶点,怎么判断呢? 根据齐次坐标的定义我们知道 (x,y,z,w) 与 (x/w,y/w,z/w,1) 是等价的,即把裁剪空间下的 (x,y,z,w) 分别除以 w,这一步我们称之为**透视除法(Perspective Divide)**。 前面我们说了投影变换会把视椎体压缩成一个标准立方体,那么视椎体内的顶点在 VP 变换后得到的 (x/w,y/w,z/w,1),其取值范围即为: > -1<x/w<1 => -w<x<w > -1<y/w<1 => -w<y<w > -1<z/w<1 => -w<z<w 因此在裁剪空间中任意顶点 (x,y,z,w) 如果不满足 (-w<x<w && -w<y<w && -w<z<w) 的条件即说明不在视椎体内,需要被裁剪。 裁剪后剩下的顶点,我们做一次透视除法,即可把它们从裁剪空间变换到 NDC。 标准化设备坐标(Normalized Device Coordinate,NDC) ----------------------------------------- NDC 也就是我们前面所提到的标准立方体,即该空间内的任意坐标 (x,y,z) 满足 (-1<x<1 && -1<y<1 && -1<z<1)的条件,也就是裁剪空间中做完裁剪后再做透视除法得到的坐标。NDC 中的顶点最后都要输出到屏幕空间当中。 需要注意的是,**在 DirectX 中 NDC 的 z 方向取值范围是 0<z<1**,而不是 -1<z<1。在 Shader 代码中我们可以使用内置指令 **UNITY_NEAR_CLIP_VALUE** 来获取不同平台的 near clip plane 的值。 OpenGL 的投影矩阵 ------------ 首先因为 glFrustum() 只接收正的近平面与远平面距离值,即 **f>n>0**。因此对于前面推导出来的投影矩阵我们要对 f 和 n 的值进行取反,即将 n=-n,f=-f 代入式子中。 其次在 OpenGL 中,**视图空间是右手坐标系,而裁剪空间与 NDC 是左手坐标系**,因此我们还需要再额外的做一次**坐标系变换**,利用  矩阵将 z 轴取反即可,例如原本左手坐标系的 near clip plane 上的点 (x,y,z) 变为右手坐标系后就是点(x,y,-z)。 整个投影变换示意图如下图,变换后 **near 的 z=-1,far 的 z=1,即 z 值越大代表物体越远**。  **OpenGL 的正交投影变换矩阵**为: >  透视投影变换就比较麻烦了,在前面我们在推导视椎体变换为长方体的矩阵时,提到了一句话:视锥体里的任意一点 (x,y,z),我们只需要将其 x 和 y 的值乘以  即可使该视锥体变为长方体。这里将 n=-n 代入得  ,因此后面的变换矩阵就应该写为:  对应用齐次坐标表示的话就是:  也就是说椎体变换为长方体的矩阵雏形应该是:  代入远近平面上的点,可得如下两个方程:  最终解得视锥体压缩为长方体的矩阵为:  然后我们再乘以 OpenGL 的正交投影矩阵即可,得到 **OpenGL 的透视投影变换矩阵**为: >  使用 FOV 和 aspect 表示即为: >  此时我们发现,假如视图空间下的某个点对应的齐次坐标为 (x,y,z,1),那么经过透视投影变换后,其齐次坐标应为:  即**裁剪空间中的一个齐次坐标 (x,y,z,w),其中 w 的值为变换前视图空间下 z 的值取反**。 DirectX 的投影矩阵 ------------- 由于 DirectX 使用的是左手坐标系(即 f>n>0),因此投影变换无需像 OpenGL 那样做对称变换。且 NDC 中 z 的取值范围是 0-1,因此它的投影矩阵和之前的也会有所不同。 对于正交投影矩阵而言,步骤要分为如下两步: 1. 利用平移矩阵把视椎体移动 -n 距离,使得 near clip plane 在 xy 平面上。 2. 利用缩放矩阵缩放为 x 和 y 长度为 2,z 长度为 1 的长方体  然而由于在 DirectX 中,坐标常用 1*4 的矩阵来表示,例如坐标 (x,y,z,w) 的矩阵为[x,y,z,w],而不是之前的 4*1 的矩阵。 因此对于变换矩阵,我们要做一个转置的操作,例如原本平移变换为:  在 DirectX 中应该写为:  所以 **DirectX 的正交投影变换矩阵**为: >  参考: [Projection Transform (Direct3D 9) - Win32 apps](https://docs.microsoft.com/en-us/windows/win32/direct3d9/projection-transform) 而对于透视投影,把视椎体转为长方体的矩阵倒是和之前的一样,然后乘上正交投影矩阵得到: 同样做一个转置,因此 **DirectX 的透视投影变换矩阵**为: >  使用 FOV 和 aspect 表示即为: >  Unity 的相关矩阵 ----------- 我们知道 Unity 使用的是左手坐标系,但是其遵循的又是 OpenGL 的规范,即 NDC 中 z 的取值范围是 - 1 到 1。 那么对于正交投影矩阵而言,步骤分为如下两步: 1. 利用平移矩阵把视椎体中心点移动到原点。 2. 利用缩放矩阵缩放为 x,y,z 长度为 2 的正方体。 得到的结果为:  对吗?错! Unity 提供了 **Matrix4x4.Ortho** 的 API 供我们生成一个正交投影的矩阵,如下: ``` Matrix4x4.Ortho(float left, float right, float bottom, float top, float zNear, float zFar); ``` 但是我们会发现 API 计算出来的矩阵和我们用上面公式计算的矩阵,得到的结果中第三行第三列的值是相反的,why? 官方文档中提到这么一句: > The returned matrix embeds a z-flip operation whose purpose is to cancel the z-flip performed by the camera view matrix. [Unity - Scripting API: Matrix4x4.Ortho](https://docs.unity.cn/2021.1/Documentation/ScriptReference/Matrix4x4.Ortho.html) 因为在做视图变换的时候做了 z 轴的反转,所以投影矩阵也要做一次 z 轴的反转来抵消,wtf。 为什么视图变换会 z 轴反转呢?我们找一找视图变换的官方文档,如下: [Camera.worldToCameraMatrix](https://docs.unity.cn/2021.1/Documentation/ScriptReference/Camera-worldToCameraMatrix.html) 其中说到: > Note that camera space matches OpenGL convention: camera's forward is the negative Z axis. This is different from Unity's convention, where forward is the positive Z axis. 也就是说 Unity 的 World Space 下物体的 forward 方向 (transform.forward) 都是 z 轴的正方向,而到了 **Camera Space 下则匹配了 OpenGL 的传统,使 Camera 看向了 z 轴负方向**。 这里做了一个 z 轴反转的操作,举个例子,假如我们 Unity 中 Camera 的 Position 和 Rotate 都是 Vector.zero,那么理论上此时的 World Space 等于 Camera Space,View Matrix 是单位矩阵。然而打印 Camera.main.worldToCameraMatrix 的结果你会发现是:  这就是因为**在 Unity 里当 World Space 变到 Camera Space 时,需要做一个反转 z 轴的操作**(因此 Unity 的视图变换矩阵,相比文章前面的推理还需再乘以一个 z 轴反转的矩阵)。然后我们做投影变换到 Clip Space 时,又要给它转回来(再做一次 z 反转),使得 near clip plane 在 - 1,far clip plane 在 1。 所以 **Unity 的正交投影矩阵**为: >  可以发现它和 OpenGL 的正交投影矩阵一毛一样,这也是在视图变换做一次 z 轴反转的妙处所在。同样的 **Unity 的透视投影矩阵也和 OpenGL 的一样**为: >  使用 FOV 和 aspect 表示即为: >  同样的,Unity 提供了 **Matrix4x4.Perspective** 的 API 供我们生成一个透视投影的矩阵,如下: ``` public static Matrix4x4 Perspective(float fov, float aspect, float zNear, float zFar); ``` [Matrix4x4.Perspective](https://docs.unity.cn/2021.1/Documentation/ScriptReference/Matrix4x4.Perspective.html) 其值就可以通过上面的公式获得。(注,文档中给的矩阵 Log 都是错的(下图犯罪记录),直接复制黏贴 Matrix4x4.Ortho 的,一时语塞)  此外对于透视摄像机,我们可以使用 **Camera.projectionMatrix** 来获取其透视投影矩阵,其结果同样是 OpenGL 的矩阵,API 文档如下: [Camera.projectionMatrix](https://docs.unity.cn/2021.1/Documentation/ScriptReference/Camera-projectionMatrix.html) 虽然不管在什么平台下,Unity 的投影矩阵都是和 OpenGL 一样的,但是在 Shader 的计算中,投影矩阵会根据图形 api 的不同而有所差异。如果我们想通过 unity 的投影矩阵获得最终在 GPU/Shader 中使用的矩阵,可以使用 **GL.GetGPUProjectionMatrix** 方法来获得,得到的结果和 Shader 中的 **UNITY_MATRIX_P** 矩阵相匹配,文档如下: [GL.GetGPUProjectionMatrix](https://docs.unity.cn/2021.1/Documentation/ScriptReference/GL.GetGPUProjectionMatrix.html) 举个例子: 假如我们场景中的 Camera 设置如下,FOV=60,near=1,far=100:  game 视图的设置中,aspect=16/9:  那么 Camera.projectionMatrix 的结果如下图(该结果匹配 OpenGL 矩阵的计算公式):  如果在 OpenGL ES3 平台下,使用 GL.GetGPUProjectionMatrix 获得结果不变,因为 GPU 用的也是 OpenGL 的矩阵。而要是在 DirectX 平台下,使用 GL.GetGPUProjectionMatrix 获得的结果则为:  可以看出只有第三行的第三、四列元素发生了变化,而且这个结果也套不上 DirectX 的投影矩阵,那么这个结果是怎么来的呢?前面说了 GL.GetGPUProjectionMatrix 是根据参数里给的矩阵换算成 GPU 矩阵,也就是说是在 OpenGL 的矩阵基础上做变化。而 OpenGL 之后得到的是 z 从 - 1 到 1 的 NDC 矩形,而 DirectX 是 0-1 的矩形,且矩形的中点也从原点变到了 (0,0,0.5) 的位置,因此我们要在 OpenGL 的基础上再做一个缩放 z 轴和平移的操作。除此之外,由于 Unity 对 DirectX 的深度做了反转操作,所以最终的步骤有如下三部: 1. z 轴缩放 0.5 倍 2. z 轴反转 3. 平移 (0,0,0.5) 距离 举个例子:假设一个点在原本 OpenGL 透视变换后的 z 值变为了 0.8,那么其深度值即为 0.8*0.5+0.5=0.9。但是变为 DirectX 的话,0.8 要先缩放变为 0.4,然后反转变为 - 0.4,最后加 0.5 变为 0.1,该 z 值 0.1 就是 Direct 下的深度值,也就是 1-0.9 的值。 所以 **z 轴反转后的 DirectX 投影矩阵的变换矩阵**为: >  得到的 **z 轴反转后的 DirectX 投影矩阵**为: >  代入公式可以发现正好就是 GL.GetGPUProjectionMatrix 得到的结果。 我们将视图空间下 near clip plane 和 far clip plane 的中心的坐标 (0,0,-n) 和(0,0,-f)代入公式看看 NDC 下 z 值的坐标(视图空间下 z 值为负,是因为前面说的 unity 做了 z 轴反转的操作):  因此 Unity 下 DirectX 的深度值越大代表越近。 深度值更详细的介绍可参考: [王江荣:【Unity】深度图(Depth Texture)的简单介绍](https://zhuanlan.zhihu.com/p/389971233) 变换与矩阵 Asset简介