问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

Unity shader 获取深度的详细数学原理

发布网友 发布时间:2022-10-13 19:07

我来回答

1个回答

热心网友 时间:2023-09-18 11:35

需要从shader 中获取深度值,主要涉及到渲染流水线中的以下几个节点

1.观察空间:观察空间是以摄像机所在位置为原点的空间。我们尝试获取的深度信息就是在这个空间之下。

2.裁剪空间(投影空间、齐次裁剪空间):将顶点转换到此空间下来判断是否在视椎体内,从而裁剪掉不在摄像机视野内的顶点;将顶点变换到此空间下可以方便做后续的投影工作。

3.屏幕空间:将没有被裁剪的顶点从裁剪空间转换到此空间,从而最终呈现在屏幕上。

主要涉及到的术语

1.裁剪矩阵(投影矩阵):

用于将顶点从观察空间转换到裁剪空间的矩阵。

经过裁剪矩阵的操作后,顶点的x、y、z分量则被转换到裁剪空间中。

裁剪空间下的顶点满足以下条件的,即为在视野内的顶点:

-w<=x<=w;

-w<=y<=w;

-w<=z<=w;

以上裁剪原理与深度获取关系不大,只需要注意:

经过裁剪矩阵的操作后,顶点的w 分量保存了顶点在观察空间下的深度信息(观察空间的顶点的z 分量)。

2.齐次除法(透视除法):

经过裁剪矩阵的操作之后,将裁剪空间下的顶点的x、y、z 分量分别除以w 分量的过程。此过程完成后,裁剪空间下的顶点将会转换到NDC 中,即变换到一个各分量从-1到1长度为2的立方体内部。如果使用的是透视摄像机,因为z 分量保留了深度信息,经过透视除法以后,越远离摄像机的顶点其xy分量数值将越小(因为w 分量是观察空间中顶点的z 分量,对于两个顶点,如果他们x、y 分量相同,而w 分量不同即深度不同,越远的顶点经过透视除法后,其数值也就越小)。齐次除法完成后,顶点的x、y 分量再经过简单的缩放映射即可投射到屏幕空间二维坐标中,而z 分量通过d = 0.5*z + 0.5 转变为0-1 范围并作为最终会存贮于深度图中的数据。

3.屏幕空间:

渲染管线中最后一个流程将裁剪空间中的顶点通过透视除法和屏幕映射映射最终从3D 的裁剪空间转换到2D 的空间中,这个2D 空间就是屏幕空间。屏幕空间是左下角为(0,0),右上角为(screenwidth, screenheight)的二维空间。

Shader 中获取深度(unity 2019.4.19 urp)

Shader 中将顶点从裁剪空间转换到屏幕空间(即齐次除法和屏幕映射)由底层完成,而获取深度的原理就是再现这一操作中某些步骤的过程。

通过顶点数据可以直接获得顶点的深度;通过顶点数据和屏幕映射公式可以逆推获得屏幕纹理坐标,使用屏幕纹理坐标可以采样深度图,然后将深度图中的数据逆推回模型空间就可以获得需要的场景深度。

1.顶点着色器中需要完成将顶点从模型空间转换到裁剪空间下的任务。

2.来到片元着色器中,经过Unity 渲染流程的处理,顶点输出的裁剪空间顶点(SV_POSITION 语义)坐标的xy 分量已经做了透视除法和屏幕映射处理转换到了屏幕空间当中,z 分量也做了透视除法并转换到(0,1)范围内,w 分量仍然是观察空间下的深度值。

获取顶点的深度值:

裁剪空间顶点坐标w 分量就是视角空间下的深度值,将其除以远裁面就是0-1 的深度值。

远裁剪面的距离可以通过内置的_ProjectionParams.z 变量来获取。 

或者比较傻的办法:

1.上面讲到片元着色器中,裁剪空间中的顶点(SV_POSITION 语义)坐标的z 分量已经做了透视除法并转换到了(0,1)范围中,此时只需要将其逆推回观察空间即可;

2.利用公式:(n表示远裁剪面;f表示近裁剪面;d表示ndc中重映射后的深度值,即第2步骤计算出的数值)

zv = 1/((n-f/n*f)*d + 1/n) 计算出观察空间下的深度值,将其除以远裁剪面就是0-1范围的观察空间下的深度值,公式:

z01=1/((n-f)/n*d + f/n)

以上公式实际上是将渲染管线处理后的顶点逆推回观察空间中,实际上是结合了投影矩阵z 分量上的处理公式、透视除法公式、屏幕映射公式三个步骤逆推出来的公式。

之所以说这个方法很傻,是因为绕了一个圈,因为顶点从裁剪空间转到屏幕空间下又逆推回了观察空间,之所以要介绍这种办法,是因为在遇到需要获取场景深度值的需求时,我们可以拿到的数据(即深度图)存储的正是步骤1 中顶点z 分量的值。

由于顶点在转换到裁剪空间时,其w 分量就是观察空间下的z 分量,如果只需要顶点的深度,还是建议直接使用裁剪空间顶点的w 分量作为深度值。

 

获取场景的深度值:

1.上面提到在片元着色器中,顶点坐标的xy 分量已经从裁剪空间转换到了屏幕空间。

2.直接将顶点坐标的xy 分量除以屏幕的长宽就可以得到0-1 范围的uv 坐标(屏幕纹理坐标),屏幕的长宽可以通过内置变量_ScreenParams 获取。

3.对深度图进行采样: SAMPLE_TEXTURE2D_X(_CamearaDepthTexture, sampler_CameraDepthTexture, uv)

采样深度图需要在shader 中作如下声明:

TEXTURE2D_X_FLOAT(_CameraDepthTexture);

SAMPLER(sampler_CameraDepthTexture);

4. 利用公式:(n表示远裁剪面;f表示近裁剪面;d表示深度图中的数据,即第四步结果的x分量)

zv = 1/((n-f/n*f)*d + 1/n) 计算出模型空间下的深度值,将其除以远裁剪面就是0-1范围的深度值,公式:

z01=1/((n-f)/n*d + f/n)

也可以在顶点着色器中计算采样坐标 , 但是因为从顶点到片元着色器有一个插值过程,所以不能在顶点着色器中进行齐次除法,因此需要乘回w 分量,计算公式:(vc 为裁剪空间下的顶点坐标、vcw为该坐标的w分量)

vcw*(vc/vcw*0.5+0.5) => 0.5vc+0.5vcw

这也是内置函数ComputeScreenPos 的实现。

然后在片元着色器中进行齐次除法获得uv 坐标,然后从步骤3继续往下执行

如果在顶点着色器使用了ComputeScreenPos获得Vs;

那么对于步骤5 可以使用unity 内部提供的两个方法Linear01Depth、LinearEyeDepth 传入屏幕纹理坐标和_ZBufferParams 来获取观察空间的场景深度和0-1线性深度,函数内部的原理即为上述步骤5 。这也是shaderGraph 中SceneDepth 节点的做法。_ZBufferParams 是Unity 提供的内置变量,里面包含了远近裁剪平面相关的预计算。 同理,顶点的坐标也可以使用这种方式来获得,只不过顶点的深度我们可以直接通过齐次除法来取得。

源码

Shader "Custom/Depth"

{

Properties

{

}

SubShader

{

Tags { "RenderType"="Transparent" "Queue"="Geometry" "RenderPipeline" = "UniversalRenderPipeline"}

LOD 200

Pass

{

HLSLPROGRAM

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

#pragma vertex vert

#pragma fragment frag

struct a2v

{

float4 vertex : POSITION;

};

struct v2f

{

float4 vertex : SV_POSITION;

//结合ComputeScreenPos 使用

//float4 screenUV : TEXCOORD5;

};

CBUFFER_START(UnityPerMaterial)

CBUFFER_END

TEXTURE2D_X_FLOAT(_CameraDepthTexture);

SAMPLER(sampler_CameraDepthTexture);

v2f vert(a2v i)

{

v2f o;

o.vertex = TransformObjectToHClip(i.vertex.xyz);

//需要在片元中做透视除法

//o.screenUV = ComputeScreenPos(o.vertex,_ProjectionParams.x);

return o;

}

half4 frag(v2f i) : SV_TARGET

{

//顶点深度

half dv = i.vertex.w;

//顶点01 深度

half dv01 = i.vertex.w / _ProjectionParams.z;

//直接获取屏幕纹理坐标

float2 screenUV = i.vertex.xy / _ScreenParams.xy;

//结合ComputeScreenPos 计算屏幕纹理坐标

//float2 screenUV = i.screenUV.xy / i.screenUV.w;

//深度图中存储的深度,需要逆推回到观察空间

float dd = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, screenUV).r;

//观察空间中的0-1场景深度

float ds01 = Linear01Depth(dd, _ZBufferParams);

//观察空间中的场景深度

float ds = LinearEyeDepth(dd, _ZBufferParams);

return ds01;

}

ENDHLSL

}

}

}
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
找专业防水队做完还漏水怎么维权 法院会受理房屋漏水造成的纠纷吗? 巴西龟最长活多久,家养!!! 养胃的药最好的是什么啊 婴儿积食发烧不愿吃药怎么办 板门穴位在哪个部位 手机设置放偷看的方法? 凝结水回收器生产厂家? 个人账户养老金预测公式:现有5万元,缴费20年,能领多少钱? 临沂比较有名的男装品牌 Unity Shader:相关数学基础 饺子馅好吃有秘诀,大厨教你实用3招,轻松调出香喷喷的饺子馅 让马停下yv这个字怎么写 图片里文字慢慢出来怎么做,谁教我! PS在图片上做文字慢慢像上移动的图片怎么做? 小天才和小米能互加吗 国务院首批24座古城 要塞 十字军 一个路由器下不能 局域网联机 !!急急急 求XP安装全过程讲解 百雀羚水嫩精纯明星美肌水和百雀羚水嫩倍现盈透精华水 美肌水和精华水哪个更好 洁面乳和洗面奶一样吗,洗的时候要揉1到2分钟吗,美肌水和精华水有区别吗,求大神 百雀羚美肌水和精华水有什么区别 红绿灯、孔雀、神秘螺、水草、小螃蟹、水晶虾这几种能混养吗?? 水晶虾,红绿灯,孔雀,水草,螺,斑马这6种可以混养么?螺和水草养点什么品种好? 梦见如来佛石象是什意思 帮我看下网站代码哪里有问题?谢谢!在线等!急! 西安施耐德电感式接近开关原理是? 《火鞋和风鞋》读后感 北京野生动物园儿童免票吗? Unity Shader:几何着色器 《Unity Shader入门精要》笔记(三) 泰国家里几个缸代表几个老婆,可一夫多妻,法律 阴历11月27属于什么星座 1990年,农历11月27日,是什么星座? 川崎2017款kx250f不好启动,什么原因? 川崎kx250f 最高时速是多少 大福摩托车有几种车型 川崎kx250f 报价是多少?重庆主城有卖吗? 川崎kawasaki kx250f多少钱 川崎摩托车的创始人是谁 在泰国旅游买了一个大蝴蝶结的包包goob什么牌子的 维生素b12功效与作用 维生素b12的作用功能 维生素b12对人体有何功效?中老年人如何正确补充维生素b12 试驾凡尔赛C5 X:南澳岛的风与情 女孩子适合玩lol里什么角色 形容从鼎盛到衰落的诗句有哪些? 唐朝由盛转衰的诗 唐诗三百首唐由盛转衰的古诗