仅做学习,如有错误望指正
涉及的知识点:depthmap,
ComputeWorldSpacePosition
,
ComputeScreenPos
1 场景搭建与实现思路
两个plane如图交叉,将在水平plane中实现基本的风格化水表面
泡沫是在两个plane的相交处生成的,== 换句话说当水面和地面的距离小于一定的阈值,则产生泡沫 ==。
现在的问题是如何得到水面和地面的距离呢?
其中一个方法从深度图信息反推出地面的顶点世界坐标。
这是一个比较暴力但合理的方法。
这里提供了两种,一种是使用unity提供的接口,另一种是自行反推。
2 深度图获取与原理
获取方式
urp提供了获取深度图的方法,在管线设置中勾选depth texture
在debugger中,找到对应的深度图名字,这样可以直接在shader中调用_CameraDepthTexture
此实验是在dx平台下建立的,深度图是近红远黑
深度图计算原理
需要知道基本的计算流程,才能够了解后面的反推过程,如果对此部分不感兴趣,可跳过这个部分。
因为算式打起来比较麻烦,这里只显示必要的重点。详细,可参考这个文章 link
观察空间坐标→裁剪空间v的坐标z范围为:-n-f(far clippng planes)
裁剪空间→ndc坐标的z范围(透视除法):-1-1-》0-1
ndc→屏幕坐标,x分量为例:
p
o
s
.
x
=
(
v
x
2
⋅
v
w
+
0.5
)
∗
p
i
x
e
l
w
i
d
t
h
pos.x =(\frac{v_x}{ 2\cdot{v_w}} + 0.5) * pixel_{width}
pos.x=(2⋅vwvx+0.5)∗pixelwidth
变换过程
MVP矩阵得到的是裁剪空间坐标。 坐标除以w之后(称为透视除法),得到了NDC坐标最后通过线性变换,得到最终的屏幕空间坐标
3 重建世界坐标
根据CameraDepthTex获得观察空间下的坐标,然后通过逆变换变成世界空间的坐标数据。如果能够办到,后面的流程就会容易很多
采样深度图
这里需要提一下ComputeScreenPos
这一个函数,这个函数输入裁剪空间的坐标数据,然后返回齐次坐标系下的屏幕坐标值的xy数据,z,w保持不变。而想获得归一化屏幕坐标的数据还要再进行一次齐次除法,获得(0,1)范围的数据
o.x = (pos.x * 0.5 + pos.w * 0.5)
o.y = (pos.y * 0.5 * _ProjectionParams.x + pos.w * 0.5)
ComputeScreenPos
思路如上,可对比ndc→屏幕坐标的过程。
使用这个函数,可以直接获取屏幕归一化的坐标值float4 screenPos = i.scrPos / i.scrPos.w;
,其中xy分量能作为采样_CameraDepthTexture
的uv坐标
这一步已经考虑到平台差异性了,因此不需要考虑额外的东西
- 采样
float4 screenPos = i.scrPos / i.scrPos.w;
float sceneRawDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, screenPos.xy);
下面提供了两种写法来实现世界坐标的重建。
重建方法1
但是_CameraDepthTexture
只提供单通道的z数据,也就是说我们无法正确得到除z值外的其他分量的信息。这一步和平台也有关系,opengl的ndc z分量范围是-1,1,dx则是0,1。
上步得到的 sceneRawDepth
范围为0,1,ndz实际的坐标会根据平台差异性处理也会不同。
由于实验是在dx下搭建的,于是,ndc数据还原为:
float4 ndc = float4(screenPos.x * 2 - 1,screenPos.y * 2 - 1, sceneRawDepth, 1);
注意这一步需要考虑差异性,dx与opengl 坐标颠倒,因此在采样的过程中,已经对y进行了处理。在还原ndc的过程中我们还需要对y分量进行bug处理
#if UNITY_UV_STARTS_AT_TOP
// Our world space, view space, screen space and NDC space are Y-up.
// Our clip space is flipped upside-down due to poor legacy Unity design.
// The flip is baked into the projection matrix, so we only have to flip
// manually when going from CS to NDC and back.
ndc.y *= -1;
#endif
现在可以根据之前所提到的变换过程,ndc*w然后进行逆变换;然而投影变换过程是一个非线性的过程,其中会带来不稳定的非线性损失。因此这里使用worldPos.w=1来反推出实际需要加入计算的w,因此式子变为如下
float4 worldPos = mul(UNITY_MATRIX_I_VP, ndc);//ndc to world
worldPos /= worldPos.w;
最后得到水面与水底的深度差值
float RawDepth = PosW.y-worldPos.y;
- 最终的代码如下所示
float4 frag_depth(v2f i) : SV_Target
{
float3 PosW = i.posW;
float4 screenPos = i.scrPos / i.scrPos.w;
float sceneRawDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, screenPos.xy);
float4 ndc = float4(screenPos.x * 2 - 1,screenPos.y * 2 - 1, sceneRawDepth, 1);//0,1->-1,1
#if UNITY_UV_STARTS_AT_TOP
ndc.y *= -1;
#endif
float4 worldPos = mul(UNITY_MATRIX_I_VP, ndc);//ndc to world
worldPos /= worldPos.w;
float RawDepth = PosW.y-worldPos.y;
return float4(RawDepth.xxx, 1);
}
重建方法2
urp 直接可以调用此函数ComputeWorldSpacePosition
获得重建坐标,方法1就是方法2的复现
float4 screenPos = i.scrPos / i.scrPos.w;
// screenPos.z = (UNITY_NEAR_CLIP_VALUE >=0)?screenPos.z:screenPos.z* 0.5 + 0.5;
float sceneRawDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture,screenPos.xy);
float3 worldPos = ComputeWorldSpacePosition(screenPos.xy, sceneRawDepth, UNITY_MATRIX_I_VP);
float RawDepth = PosW.y-worldPos.y;