使用深度图重建世界坐标

在学习利用深度图重建世界坐标,遇到了很多的问题,这里需要好好的总结下,文章的最后给出参考网址以及书籍。

首先给出项目的地址:
git@gitee.com:yichichunshui/CameraDepth.git
or
https://gitee.com/yichichunshui/CameraDepth.git

然后我们介绍利用深度图来推导世界坐标的两种方法:
第一种方法,使用vp矩阵的逆矩阵的方式重建。
原理如下:
在这里插入图片描述
这就整个3D图形学的变换了。
首先一个矩阵的前三个维度所形成的向量,就是标准的正交基。他们是互相正交的轴。
一个模型的局部坐标点经过MVP矩阵变换之后得到的是齐次空间坐标的点,而不是NDC坐标的点。
NDC坐标的点要经过透视除法才能得到,NDC的坐标的点在OpenGL中xyz都被映射到[-1,1]之间,而DX中xy被映射到[-1,1]之间,而z被映射到[0,1]之间。unity中利用了OpenGL的映射方式,其xyz也是都被映射到[-1,1]之间,这个参考我之前的博文:https://blog.csdn.net/wodownload2/article/details/85069240
下面就是数学推导了。

在这里插入图片描述

上图,我们以后会经常的使用,这里给出一个通用的透视图。
关于上图的说明如下:
o点为摄像机所在位置,也就是眼睛的位置。
ABCD为近平面,其OM=n
A’B’C’D’为远平面,其OM’=f
M为ABCD的中心点,M’为A’B’C’D’的中心点。
我们现在要推导的是,视锥体中的任意一点P(x,y,z)。在近平面上找到对应的投射点P’。这里的P’的z是只知道的就是近平面n,然后根据相似三角形原理,推出:
在这里插入图片描述
能推出y’:
在这里插入图片描述

同理:也能推出x’:
在这里插入图片描述
ok,现在我们知道了投影点P’的值了:
就是(

下面的工作就是要将,这个p’点映射到规范的空间了,也就是说将x’y’z’都映射到[-1,1]范围内。
如下图:
在这里插入图片描述

于是乎,我们的只要将视锥体空间中的点P的x坐标带入上面的公式,就能得到NDC空间的x’‘了。
在这里插入图片描述
同理,y’'如下:
在这里插入图片描述
上面的式子中,都除以了z,这个对于如果改写成如下的矩阵形式是做不到的:
在这里插入图片描述
那么咋办呢?

为啥要这样,就是为了好写成矩阵的形式。
同理得到y’’=2ny/(t-b) - (t+b)/(t-b)
而z’‘稍微有些不同,我们知道近平面n,和远平面f,将其映射到-1到1,咋映射呢?
这个我真不知道咋整了。
但是网上的文章也没有讲解怎么做,而是大胆的给出,如果我们通过找到如下的公式:
zz’’ = pz+q的形式就可以了。
这样zz’‘就和z成为线性关系,也就好写成矩阵的形式了。
现在的问题就转换为求得p和q了。
而我们知道,当z=n的时候,z’’=-1
当z=f的时候,z’’=1
也是求得:
p = (f+n)/(f-n)
q =-2nf/(f-n)
于是zz’’=(f+n)z/(f-n)-2nf/(f-n)
我们将其转换为矩阵的形式:

ok,经过上的讨论之后,我们知道了,什么是齐次坐标,什么是ndc坐标。下面就要利用这种方式,来求得世界坐标。

还需要知道的是,摄像机渲染的深度图,得到的是什么,是[0,1]范围的线性深度,还是[-1,1]的ndc中的非线性坐标呢?
答案是后者。

为了将验证用深度图转换世界坐标的正确性,那么我们最好确保plane的面上的顶点在0到1范围,这样直接从颜色上,肉眼判定就可以了。
这里还有一个注意点,初始的plane是scale=1,但是其坐标的点的世界坐标,却在-5到5之间,所以在将其缩小10倍即可。
在这里插入图片描述

下面我们先用一个普通的shader,打印其世界坐标的颜色:

Shader "Unlit/DrawWorldPoint"
{
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
		
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float3 worldPos : TEXCOORD0;
			};

			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				return fixed4(i.worldPos, 1.0);
			}
			ENDCG
		}
	}
	FallBack "Legacy Shaders/Diffuse"
}

注意这个shader的最后有一个fallback,它在这里没有用,但是在后面的后处理,使用深度图的时候有用。后面会讲到。

这样得到的效果是:
在这里插入图片描述

ok,下面是使用逆矩阵的方式反推世界坐标。
首先,我们要使用后处理的方式,所以要写一个C#脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
public class InverseMatrix : MonoBehaviour
{
    private Material postEffectMat = null;
    private Camera currentCamera = null;

    void Awake()
    {
        currentCamera = GetComponent<Camera>();
    }

    void OnEnable()
    {
        if (postEffectMat == null)
            postEffectMat = new Material(Shader.Find("Unlit/InverseMatrix"));
        currentCamera.depthTextureMode |= DepthTextureMode.Depth;
    }

    void OnDisable()
    {
        currentCamera.depthTextureMode &= ~DepthTextureMode.Depth;
    }

   void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (postEffectMat == null)
        {
            Graphics.Blit(source, destination);
        }
        else
        {
            Matrix4x4 projMat = GL.GetGPUProjectionMatrix(currentCamera.projectionMatrix, false); //这句重点了,如果直接使用camera的投射矩阵的话,则会得不到准确的颜色效果。
            var vpMatrix = projMat * currentCamera.worldToCameraMatrix;
            postEffectMat.SetMatrix("_InvVP", vpMatrix.inverse);
            Graphics.Blit(source, destination, postEffectMat);
        }
    }
}

这个C#很简单,它做了两个重要的事情,一个是负责将深度图传递给我们将要编写的shader,这个是unity自己为我们做的事情,另外一个工作还要传递一个逆矩阵给我们将要编写的shader。
千呼万唤使出来,我们的后处理的shader如下:

Shader "Unlit/InverseMatrix"
{
	SubShader
	{
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			sampler2D _CameraDepthTexture;
			float4x4 _InvVP;

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float2 uv : TEXCOORD0;
			};

			v2f vert(appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
				float4 ndc = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, depth, 1);
				worldPos /= worldPos.w;
				return worldPos;
			}
			ENDCG
		}
	}
}

最核心的应该frag中的采样深度图的代码,以及使用逆矩阵变换ndc坐标到世界坐标的代码了。这里为什么要最后除以w分量呢?请参考文章最后给出的链接。
ok,这样之后我们得到的图如下:
在这里插入图片描述
上图scene视图下的plane它没有后处理效果,也就是原始的使用绘制世界坐标的的颜色。下图是用深度图反推世界坐标的颜色,我们只关注中间的plane颜色即可,两个颜色是正确的,说明反推正确。

这里有一个小小的疑问为什么, float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
用顶点的uv直接就也采样深度图了,这也是百思不得其解的,有人造吗?
至此,第一种方式验证成功。

下面就要介绍,使用射线偏移的方法,反推世界坐标了,这也是建议使用的方式,因为,使用逆矩阵的方式,是针对屏幕上的每个像素都要进行矩阵变换,这个比较耗。

这个博客有点长了,一时写不了,后面会继续补充。

参考:
https://blog.csdn.net/puppet_master/article/details/77489948
http://feepingcreature.github.io/math.html

  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值