1.概论
在屏幕空间做一些操作的时候往往需要从屏幕坐标和深度纹理中重建出相应的世界坐标。
本文会讨论两种重建世界坐标的方法,并详细讲解第二种方法。
- 使用VP逆矩阵
- CameraPosInWorld + linearDepth * Direction
2.使用VP逆矩阵法
我们知道屏幕坐标是通过Object Space下的坐标与MVP矩阵相乘,经历透视除法,再映射到0-1区间下得到相应的屏幕坐标。
经历这些操作之后,xy就是相应的屏幕坐标,而z会被用来进行深度测试比较。
这里我们省略再拉伸到屏幕像素大小的过程。
注意!!!在OpenGL中深度被映射到[-1,1], DX中深度被映射到[0,1]区间
那么我们从深度图中可以得到缺失的深度,再加上我们已知屏幕坐标,是不是进行一遍逆操作就可以得到相应的世界坐标了呢?Yes!
那么我们来分析一下这种操作的优缺点吧!
优点 : 简单 简单 简单
缺点 : 假如我们需要对每个像素进行世界坐标的重建,那么我们就要进行屏幕分辨率乘以一个4x4Matrix的矩阵乘法的操作!!!
这明显不好,尤其是在GPU中进行多次矩阵乘法,很耗时!
那么我们来看看第二种方法。
3.CameraPosInWorld + linearDepth * Direction
让我们来分析一下这个公式的每个变量的含义!
CameraPosInWorld:很简单,就是摄像机在世界空间下的坐标位置。
linearDepth:代表每个像素的世界坐标在View Space下的深度,也就是视口空间下的像素坐标的Z值。
Direction: 方向,代表起点为摄像机,终点为近平面的四个角的一个向量。
这幅图很形象的描述了我们所需的四个方向,或者说向量。
那么我们该怎么计算呢?
-
首先我们需要计算toTop和toRight两个向量,这里我们一定可以拿到摄像机的FOV角度(竖直方向的视角距离),aspect代表摄像机的宽高比。
-
那么我们进行一下简单的向量操作就可以得到我们想要的四个向量。
记住,toTop和toRight的起点还是在摄像机。
-
我们从深度图中拿到的是z值坐标,并不是从摄像机出发,沿着TL,TR,BL,BR等方向的一个深度,而是一个垂直距离,那么我们就要做一个映射,从depth,映射到我们想要的深度。
想必大家一眼就看出来了(这里以TL为例):
那么我们的表达式就变成了:
这里我们假设:
那么我们就可以在CUP中将scale与direction相乘,这里的scale对于四个方向来说都是一样的,所以并不影响我们后面的在GPU中的插值。 -
最终
那么我们传入顶点着色器,我们可以根据像素的位置来判断需要使用哪个方向,并由GPU进行插值,来得到我们所对应需要的方向,并在片段着色器中重建世界坐标。
以Unity Shader为例:
int index = 0;if (v.vertex.x < 0.5 && v.vertex.y < 0.5) {index = 0;}else if (v.vertex.x > 0.5 && v.vertex.y < 0.5) {index = 1;}else if (v.vertex.x > 0.5 && v.vertex.y > 0.5) {index = 2;}else {index = 3;}#if UNITY_UV_STARTS_AT_TOPif (_MainTex_TexelSize.y < 0)index = 3 - index;
#endif