首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >使用深度图重建世界坐标

使用深度图重建世界坐标

作者头像
全栈程序员站长
发布2022-08-24 16:38:16
发布2022-08-24 16:38:16
1.3K0
举报

大家好,又见面了,我是你们的朋友全栈君。

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

首先给出项目的地址: 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,打印其世界坐标的颜色:

代码语言:javascript
复制
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#脚本:

代码语言:javascript
复制
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如下:

代码语言:javascript
复制
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

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/140929.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年5月8,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档