graphic方法重写 unity_Unity Shader 深度值重建世界坐标

根据深度重建世界坐标

证明世界坐标重建正确的方法

首先,得先找到一种证明反推回世界空间位置正确的方法。这里,我在相机前摆放几个物体,尽量使之在世界坐标下的位置小于1,方便判定颜色如下图:

然后将几个物体的shader换成如下的一个打印世界空间位置的shader:

//puppet_master

//https://blog.csdn.net/puppet_master

//2018.6.10

//打印对象在世界空间位置

Shader "DepthTexture/WorldPosPrint"

{

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

{

float3 worldPos : TEXCOORD0;

float4 vertex : SV_POSITION;

};

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使之有shadow caster的pass

FallBack "Legacy Shaders/Diffuse"

}

然后挂上上面的重建世界坐标位置的脚本,在开启和关闭脚本前后,屏幕输出完全无变化,说明通过后处理重建世界坐标位置与直接用shader输出世界坐标位置效果一致:

逆矩阵方式重建

深度重建有几种方式,先来看一个最简单粗暴,但是看起来最容易理解的方法:

我们得到的屏幕空间深度图的坐标,xyz都是在(0,1)区间的,需要经过一步变换,变换到NDC空间,OpenGL风格的话就都是(-1,1)区间,所以需要首先对xy以及xy对应的深度z进行*2 - 1映射。然后再将结果进行VP的逆变换,就得到了世界坐标。

shader代码如下:

//puppet_master

//https://blog.csdn.net/puppet_master

//2018.6.10

//通过逆矩阵的方式从深度图构建世界坐标

Shader "DepthTexture/ReconstructPositionInvMatrix"

{

CGINCLUDE

#include "UnityCG.cginc"

sampler2D _CameraDepthTexture;

float4x4 _InverseVPMatrix;

fixed4 frag_depth(v2f_img i) : SV_Target

{

float depthTextureValue = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);

//自己操作深度的时候,需要注意Reverse_Z的情况

#if defined(UNITY_REVERSED_Z)

depthTextureValue = 1 - depthTextureValue;

#endif

float4 ndc = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, depthTextureValue * 2 - 1, 1);

float4 worldPos = mul(_InverseVPMatrix, ndc);

worldPos /= worldPos.w;

return worldPos;

}

ENDCG

SubShader

{

Pass

{

ZTest Off

Cull Off

ZWrite Off

Fog{ Mode Off }

CGPROGRAM

#pragma vertex vert_img

#pragma fragment frag_depth

ENDCG

}

}

}

C#部分:

/********************************************************************

FileName: ReconstructPositionInvMatrix.cs

Description:从深度图构建世界坐标,逆矩阵方式

Created: 2018/06/10

history: 10:6:2018 13:09 by puppet_master

https://blog.csdn.net/puppet_master

*********************************************************************/

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

[ExecuteInEditMode]

public class ReconstructPositionInvMatrix : MonoBehaviour {

private Material postEffectMat = null;

private Camera currentCamera = null;

void Awake()

{

currentCamera = GetComponent();

}

void OnEnable()

{

if (postEffectMat == null)

postEffectMat = new Material(Shader.Find("DepthTexture/ReconstructPositionInvMatrix"));

currentCamera.depthTextureMode |= DepthTextureMode.Depth;

}

void OnDisable()

{

currentCamera.depthTextureMode &= ~DepthTextureMode.Depth;

}

void OnRenderImage(RenderTexture source, RenderTexture destination)

{

if (postEffectMat == null)

{

Graphics.Blit(source, destination);

}

else

{

var vpMatrix = currentCamera.projectionMatrix * currentCamera.worldToCameraMatrix;

postEffectMat.SetMatrix("_InverseVPMatrix", vpMatrix.inverse);

Graphics.Blit(source, destination, postEffectMat);

}

}

}

效果如下,重建ok:

看起来比较简单,但是其中有一个/w的操作,如果按照正常思维来算,应该是先乘以w,然后进行逆变换,最后再把world中的w抛弃,即是最终的世界坐标,不过实际上投影变换是一个损失维度的变换,我们并不知道应该乘以哪个w,所以实际上上面的计算,并非按照理想的情况进行的计算,而是根据计算推导而来(更加详细推导请参考这篇文章,不过我感觉这个推导有点绕)。

已知条件(M为VP矩阵,M^-1即为其逆矩阵,Clip为裁剪空间,ndc为标准设备空间,world为世界空间):

ndc = Clip.xyzw / Clip.w = Clip / Clip.w

world = M^-1 * Clip

二者结合得:

world = M ^-1 * ndc * Clip.w

我们已知M和ndc,然而还是不知道Clip.w,但是有一个特殊情况,是world的w坐标,经过变换后应该是1,即

1 = world.w = (M^-1 * ndc).w * Clip.w

进而得到Clip.w = 1 / (M^ -1 * ndc).w

带入上面等式得到:

world = (M ^ -1 * ndc) / (M ^ -1 * ndc).w

所以,世界坐标就等于ndc进行VP逆变换之后再除以自身的w。

不过这种方式重建世界坐标,性能比较差,一般来说,我们都是逐顶点地进行矩阵运算,毕竟定点数一般还是比较少的,但是全屏幕逐像素进行矩阵运算,这个计算量就不是一般的大了,性能肯定是吃不消的。

补充:如果是DepthNormalTexture中的depth通过逆矩阵方式重建,计算方式略有不同:

float depth;

float3 normal;

float4 cdn = tex2D(_CameraDepthNormalsTexture, i.uv);

DecodeDepthNormal(cdn, depth, normal);

//float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);

//逆矩阵的方式使用的是1/z非线性深度,而_CameraDepthNormalsTexture中的是线性的,进行一步Linear01Depth的逆运算

depth = (1.0/depth - _ZBufferParams.y) /_ZBufferParams.x ;

//自己操作深度的时候,需要注意Reverse_Z的情况

#if defined(UNITY_REVERSED_Z)

depth = 1 - depth;

#endif

float4 ndc = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, depth * 2 - 1, 1);

float4 worldPos = mul(_InverseVPMatrix, ndc);

worldPos /= worldPos.w;

屏幕射线插值方式重建

这种方式的重建,可以参考Secrets of CryENGINE 3 Graphics Technology这个CryTech 2011年的PPT。借用一张图:

然后偶再画个平面的图:

上图中,A为相机位置,G为空间中我们要重建的一点,那么该点的世界坐标为A(worldPos) + 向量AG,我们要做的就是求得向量AG即可。根据三角形相似的原理,三角形AGH相似于三角形AFC,则得到AH / AC = AG / AF。由于三角形相似就是比例关系,所以我们可以把AH / AC看做01区间的比值,那么AC就相当于远裁剪面距离,即为1,AH就是我们深度图采样后变换到01区间的深度值,即Linear01Depth的结果d。那么,AG = AF * d。所以下一步就是求AF,即求出相机到屏幕空间每个像素点对应的射线方向。看到上面的立体图,其实我们可以根据相机的各种参数,求得视锥体对应四个边界射线的值,这个操作在vertex阶段进行,由于我们的后处理实际上就是渲染了一个Quad,上下左右四个顶点,把这个射线传递给pixel阶段时,就会自动进行插值计算,也就是说在顶点阶段的方向值到pixel阶段就变成了逐像素的射线方向。

那么我们要求的其实就相当于AB这条向量的值,以上下平面为例,三维向量只比二维多一个维度,我们已知远裁剪面距离F,相机的三个方向(相机transform.forward,.right,.up),AB = AC + CB,|BC| = tan(0.5fov) * |AC|,|AC| = Far,AC = transorm.forward * Far,CB = transform.up * tan(0.5fov) * Far。

我直接使用了远裁剪面对应的位置计算了三个方向向量,进而组合得到最终四个角的向量。用远裁剪面的计算代码比较简单(恩,我懒),不过《ShaderLab入门精要》中使用的是近裁剪面+比例计算,不确定是否有什么考虑(比如精度,没有测出来,如果有大佬知道,还望不吝赐教)。

shader代码如下:

//puppet_master

//https://blog.csdn.net/puppet_master

//2018.6.16

//通过深度图重建世界坐标,视口射线插值方式

Shader "DepthTexture/ReconstructPositionViewPortRay"

{

CGINCLUDE

#include "UnityCG.cginc"

sampler2D _CameraDepthTexture;

float4x4 _ViewPortRay;

struct v2f

{

float4 pos : SV_POSITION;

float2 uv : TEXCOORD0;

float4 rayDir : TEXCOORD1;

};

v2f vertex_depth(appdata_base v)

{

v2f o;

o.pos = UnityObjectToClipPos(v.vertex);

o.uv = v.texcoord.xy;

//用texcoord区分四个角,就四个点,if无所谓吧

int index = 0;

if (v.texcoord.x < 0.5 && v.texcoord.y > 0.5)

index = 0;

else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5)

index = 1;

else if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5)

index = 2;

else

index = 3;

o.rayDir = _ViewPortRay[index];

return o;

}

fixed4 frag_depth(v2f i) : SV_Target

{

float depthTextureValue = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);

float linear01Depth = Linear01Depth(depthTextureValue);

//worldpos = campos + 射线方向 * depth

float3 worldPos = _WorldSpaceCameraPos + linear01Depth * i.rayDir.xyz;

return fixed4(worldPos, 1.0);

}

ENDCG

SubShader

{

Pass

{

ZTest Off

Cull Off

ZWrite Off

Fog{ Mode Off }

CGPROGRAM

#pragma vertex vertex_depth

#pragma fragment frag_depth

ENDCG

}

}

}

C#代码如下:

/********************************************************************

FileName: ReconstructPositionViewPortRay.cs

Description:通过深度图重建世界坐标,视口射线插值方式

Created: 2018/06/16

history: 16:6:2018 16:17 by puppet_master

https://blog.csdn.net/puppet_master

*********************************************************************/

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

[ExecuteInEditMode]

public class ReconstructPositionViewPortRay : MonoBehaviour {

private Material postEffectMat = null;

private Camera currentCamera = null;

void Awake()

{

currentCamera = GetComponent();

}

void OnEnable()

{

if (postEffectMat == null)

postEffectMat = new Material(Shader.Find("DepthTexture/ReconstructPositionViewPortRay"));

currentCamera.depthTextureMode |= DepthTextureMode.Depth;

}

void OnDisable()

{

currentCamera.depthTextureMode &= ~DepthTextureMode.Depth;

}

void OnRenderImage(RenderTexture source, RenderTexture destination)

{

if (postEffectMat == null)

{

Graphics.Blit(source, destination);

}

else

{

var aspect = currentCamera.aspect;

var far = currentCamera.farClipPlane;

var right = transform.right;

var up = transform.up;

var forward = transform.forward;

var halfFovTan = Mathf.Tan(currentCamera.fieldOfView * 0.5f * Mathf.Deg2Rad);

//计算相机在远裁剪面处的xyz三方向向量

var rightVec = right * far * halfFovTan * aspect;

var upVec = up * far * halfFovTan;

var forwardVec = forward * far;

//构建四个角的方向向量

var topLeft = (forwardVec - rightVec + upVec);

var topRight = (forwardVec + rightVec + upVec);

var bottomLeft = (forwardVec - rightVec - upVec);

var bottomRight = (forwardVec + rightVec - upVec);

var viewPortRay = Matrix4x4.identity;

viewPortRay.SetRow(0, topLeft);

viewPortRay.SetRow(1, topRight);

viewPortRay.SetRow(2, bottomLeft);

viewPortRay.SetRow(3, bottomRight);

postEffectMat.SetMatrix("_ViewPortRay", viewPortRay);

Graphics.Blit(source, destination, postEffectMat);

}

}

}

开关后处理前后效果仍然不变:

这里我用了默认非线性的深度图进行的深度计算,需要先进行Linear01Depth计算,如果用了线性深度,比如DepthNormalTexture,那么就进行一步简单的线性映射即可。整体的射线计算,我用了Linear01Depth * 外围计算好的距离。也可以用LinearEyeDepth * 外围计算好的方向。总之,方案还是蛮多的,变种也很多,还有自己重写Graphic.Blit自己设置Quad的值把index设置在顶点的z值中。

屏幕射线插值方式重建视空间坐标

补充一条屏幕空间深度重建坐标的Tips。如果我们要求视空间的位置的话,有一种更简便并且性能更好的方式。这种方式与上面的屏幕射线插值的方式重建世界坐标的原理一致。只需要输入一个投影矩阵的逆矩阵,即在vertex阶段,从NDC坐标系的四个远裁剪面边界(+-1,+-1,1,1)乘以逆投影矩阵,得到视空间的四个远裁剪面坐标位置,然后除以齐次坐标转化到普通坐标下。这样的四个点的位置也就是视空间下从相机到该点的射线方向,经过插值到fragment阶段直接乘以01区间深度就得到了该像素点的视空间位置了。

那么就只有一个问题没有解决,在于应该如何获得NDC坐标系下的边界点。上面推导中提到过,在后处理阶段,实际上就是绘制了一个Quad,对应整个屏幕。这个Quad的四个边界点刚好对应屏幕的四个边界点,uv是(0,1)区间的,刚好对应屏幕空间,我们通过*2 - 1将其转化到(-1,1)区间就可以得到四个边界对应NDC坐标系下的xy坐标了。

v2f vert (appdata v)

{

float4 clipPos = float4(v.uv * 2 - 1.0, 1.0, 1.0);

float4 viewRay = mul(_InverseProjectionMatrix, clipPos);

o.viewRay = viewRay.xyz / viewRay.w;

return o;

}

fixed4 frag (v2f i) : SV_Target

{

float depthTextureValue = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);

float linear01Depth = Linear01Depth(depthTextureValue);

float3 viewPos = _WorldSpaceCameraPos.xyz + linear01Depth * i.viewRay;

}

此处的InverseProjectionMatrix与上文中一样,也需要自己传入,因为在后处理阶段,内置矩阵已经被替换了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/245166.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python显示表格_在Python中获取Excel表格的数量

How get number of sheet in below python example? file self.excel_file.decode(base64) excel_fileobj TemporaryFile(wb) excel_fileobj.write(file) excel_fileobj.seek(0) workbook openpyxl.load_workbook(excel_fileobj, data_onlyTrue) sheet_number ??? sheet…

asp网站本地测试服务器,小旋风asp服务器,asp本地环境调试必备

学习asp&#xff0c;要在本地搭建一个asp服务器调试环境吧&#xff0c;那么必不可少的要找asp本地调试环境软件&#xff0c;超级小旋风AspWebServer是一个不错的选择。超级小旋风AspWebServer系统基于NetBox开发&#xff0c;可以跟IIS媲美的服务器。小旋风asp服务器 该软件是由…

基于sklearn的朴素贝叶斯_Sklearn参数详解—贝叶斯

在开始学习具体的贝叶斯参数前&#xff0c;你可以先看看&#xff1a;朴素贝叶斯详解​mp.weixin.qq.com朴素贝叶斯一共有三种方法&#xff0c;分别是高斯朴素贝叶斯、多项式分布贝叶斯、伯努利朴素贝叶斯&#xff0c;在介绍不同方法的具体参数前&#xff0c;我们先看看这三种方…

快速傅里叶变换python_【原创】OpenCV-Python系列之傅里叶变换(三十八)

OpenCV-Python系列之傅里叶变换 傅里叶变换 我们生活在时间的世界中&#xff0c;早上7:00起来吃早饭&#xff0c;8:00去挤地铁&#xff0c;9:00开始上班。。。以时间为参照就是时域分析。 但是在频域中一切都是静止的&#xff01;可能有些人无法理解&#xff0c;我建议大家看看…

c# 服务器性能监控,C#服务器性能监控之WMI的代码示例详解

1.WMI简介WMI是英文Windows Management Instrumentation的简写&#xff0c;通过使用WMI&#xff0c;我们可以获取本地或远程服务器的性能参数和进程运行情况&#xff0c;以及大部分硬件信息&#xff0c;但前提是运行的用户要有足够的权限&#xff0c;如administrator组用户等。…

pythonrandrange_Python3 randrange() 函数

Python3 randrange() 函数 描述 randrange() 方法返回指定递增基数集合中的一个随机数&#xff0c;基数缺省值为1。 语法 以下是 randrange() 方法的语法:import random random.randrange ([start,] stop [,step]) 注意&#xff1a;randrange()是不能直接访问的&#xff0c;需要…

服务器信息化平台,管理系统的信息化平台.ppt

管理系统的信息化平台管理系统中计算机应用 第三章 管理系统的信息化平台 本章主要内容 任何管理信息系统的运行都需要依靠特定的环境支持&#xff0c;这种环境称为信息化处理的基础平台。 本章节介绍了信息化平台的基本组成&#xff1a;计算机平台、通信平台、网络平台和数据库…

中运算符百分号作用_SQL基础知识——LIKE运算符

LIKE 作用在WHERE子句中使用LIKE运算符来搜索列中的指定模式。 有两个通配符与LIKE运算符一起使用&#xff1a;&#xff05; - 百分号表示零个&#xff0c;一个或多个字符_ - 下划线表示单个字符注意&#xff1a; MS Access使用问号(?)而不是下划线(_)。 百分号和下划线也可以…

服务器点播直播系统,服务器点播直播系统

服务器点播直播系统 内容精选换一换在SAP系统中&#xff0c;除了SAP HANA节点使用裸金属服务器外&#xff0c;其他节点都使用弹性云服务器。Jump Host弹性云服务器&#xff0c;用户可通过访问该服务器后&#xff0c;再通过SSH协议跳转到SAP HANA及SAP应用节点。同时用于部署SAP…

eureka多了一个莫名其妙的服务_SpringCloud 服务注册与发现组件 Eureka

一、SpringCloud介绍微服务&#xff0c;为了更好的创建项目组织结构、更高效的项目的迭代效果、更优良的架构设计&#xff0c;就需要使用微服务的架构思想&#xff0c;来对项目进行搭建或者重构。企业碰到的第一个问题是服务如何进行拆分。根据业务边界来划分&#xff0c;拆分开…

怎么用贝塞尔工具画圆_Win10恶意软件删除工具怎么用?这个方法都舍不得分享...

Win10恶意软件删除工具怎么用&#xff1f;恶意软件删除工具相信很多朋友都会在windows更新中看到过&#xff0c;但是很多朋友确从来没有用过&#xff0c;微软每个月都会把恶意软件删除工具给更新一下&#xff0c;主要作用是用来分析用户电脑上是否存在风险程序的工具。其实大家…

springboot 获取bean_SpringBoot高级(自动配置 事件监听 监控)

SpringBoot自动配置condition-1Condition是Spring4.0后引入的条件化配置接口&#xff0c;通过实现Condition接口可以完成有条件的加载相应的BeanConditional要配和Condition的实现类(ClassCondition)进行使用ClassConditionpublic class ClassCondition implements Condition {…

golang延时_golang 实现延迟消息原理与方法

实现延迟消息具体思路我是看的下面这篇文章https://mp.weixin.qq.com/s/eDMV25YqCPYjxQG-dvqSqQ实现延迟消息最主要的两个结构&#xff1a;环形队列&#xff1a;通过golang中的数组实现&#xff0c;分成3600个slot。任务集合&#xff1a;通过map[key]*Task&#xff0c;每个slot…

python特征工程插件_手把手教你用Python实现自动特征工程

任何参与过机器学习比赛的人&#xff0c;都能深深体会特征工程在构建机器学习模型中的重要性&#xff0c;它决定了你在比赛排行榜中的位置。 特征工程具有强大的潜力&#xff0c;但是手动操作是个缓慢且艰巨的过程。Prateek Joshi&#xff0c;是一名数据科学家&#xff0c;花了…

2020笔记本性价比之王_笔记本电脑性价比排行2020前十名?

展开全部1、 联想(Lenovo)拯救者R7000产品材质&#xff1a;32313133353236313431303231363533e58685e5aeb931333433656631ABCD面塑料产品尺寸&#xff1a;15.6寸CPU型号&#xff1a; AMD Ryzen5 4600H显卡型号&#xff1a;NVIDIA GeForce GTX 1650屏幕参数&#xff1a; 1080P I…

ios不行安卓可以 微信签名_王者荣耀安卓、iOS互通来了!现在可以互看好友资料...

3月10日消息&#xff0c;王者荣耀在更新正式服后&#xff0c;安卓版和iOS版开通了资料互看。此前安卓和iOS上的游戏一直因为使用的服务器不同&#xff0c;不能实现两个平台的游戏互动&#xff0c;当然也不能实现同账号的资料同步。这对很多玩家来说造成一定的困扰&#xff0c;如…

python中count的作用_python中内置的.count是什么?

我一直在checkio.com上解决问题,其中一个问题是&#xff1a;“编写一个函数来查找在给定字符串中出现最大次数的字母” 最重要的解决方案是&#xff1a; import string def checkio(text): """ We iterate through latin alphabet and count each letter in the…

mysql 5.0 修改字符集_修改及查看mysql数据库的字符集

Liunx下修改MySQL字符集&#xff1a;1.查找MySQL的cnf文件的位置find / -iname *.cnf -print/usr/share/mysql/my-innodb-heavy-4G.cnf/usr/share/mysql/my-large.cnf/usr/share/mysql/my-small.cnf/usr/share/mysql/my-medium.cnf/usr/share/mysql/my-huge.cnf/usr/share/texm…

python range函数怎么表示无限_Python for循环与range函数的使用详解

for 循环 for … in 语句是另一种循环语句&#xff0c;其特点是会在一系列对象上进行迭代&#xff08;iterates&#xff09;&#xff0c;即它会遍历序列中的每一个项目 注意&#xff1a; 1、else 部分是可选的。当循环中包含它时&#xff0c;它循环中包含它时&#xff0c;它总会…

php输出查询mysql总数_PHP查询语句,如何返回总记录数??

展开全部PHP查询语句&#xff0c;e69da5e6ba903231313335323631343130323136353331333337626231有两种方法获得查询的总记录数。一是使用mysql_num_rows函数&#xff0c;例子代码&#xff1a;<?php $link mysql_connect("localhost", "mysql_user", &…