使用深度图重建世界坐标

使用深度图重建世界坐标参考网址:https://blog.csdn.net/puppet_master/article/details/77489948首先建立一个绘制世界坐标的项目。保证物体的顶点在0到1范围之内,这样颜色能够有意义的表示物体的世界坐标位置。然后编写一个shader,用于绘制世界坐标位置:Shader"Unlit/DrawWorldPoint&quo

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

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

首先给出项目的地址:
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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • mysql连接数据库命令_linux进入mysql的命令

    mysql连接数据库命令_linux进入mysql的命令一、MySQL连接本地数据库,用户名为“root”,密码“123”(注意:“-p”和“123”之间不能有空格)C:>mysql-hlocalhost-uroot-p123二、MySQL连接远程数据库(192.168.0.201),端口“3306”,用户名为“root”,密码“123”C:>mysql-h172.16.16.45-P3306-uroot-p123三、MySQL连接本地数据库,用户名为“root”,隐藏密码C:>mysql-hloc

  • jax java,用JAX WS实现java调用webServic「建议收藏」

    jax java,用JAX WS实现java调用webServic「建议收藏」来个简单点的:1.建个具体的服务实现:packagecom.webservice;@WebServicepublicclassWarehouse{privateMapprices;publicWarehouse(){prices=newHashMap();prices.put(“BlackwellToaster”,24.95);prices.put(“ZapXpress…

  • centos7.0 可以访问HTML文件,不能访问PHP文件,因为php-fpm没有扩展包

    centos7.0 可以访问HTML文件,不能访问PHP文件,因为php-fpm没有扩展包

    2021年10月19日
  • HDU 1556-差分数组和线段树的对比分析-Color the ball

    HDU 1556-差分数组和线段树的对比分析-Color the ball差分数组数据结构详解戳这里!线段树数据结构详解戳这里!这两个数据结构的操作主要有两个:更新和查询。假设数据结构总长度为n。差分数组:更新时间复杂度O(1)查询时间复杂度O(n)线段树:更新时间复杂度O(logn)查询时间复杂度O(logn)因此,差分数组适用于多次更新,常量次查询,数据范围在1e7以内的情况;线段树适用于多次更新,多次查询,数据范围在1e5以内的…

  • 伪代码书写规则_伪代码及其实例讲解

    伪代码书写规则_伪代码及其实例讲解伪代码书写规则输入缩进变量数组选择结构循环结构返回值注释大小写最近要用到伪代码写算法,所以在网上查阅了一些资料,写这篇博客正好整理、记录一下自己所学的书写规则,以便自己日后使用,如果能对大家有所帮助,那就更好了。文中不足,欢迎给位大神多多指点。输入赋值语句用符号←表示,x←exp表示将exp的值赋给x,其中x是一个变量,exp是一个与x同类型的变量或表达式(该表达式的结果与x同类型);多重赋…

    2022年10月24日
  • js正则使用变量_正则表达式中可以拼接变量吗

    js正则使用变量_正则表达式中可以拼接变量吗javascript正则变量evalRegExp

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号