大家好,又见面了,我是你们的朋友全栈君。
在学习利用深度图重建世界坐标,遇到了很多的问题,这里需要好好的总结下,文章的最后给出参考网址以及书籍。
首先给出项目的地址:
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账号...