【unity shader/风格化水表面渲染/基础笔记】urp代码版01-水面与水底的深度判断


仅做学习,如有错误望指正
涉及的知识点:depthmap, ComputeWorldSpacePositionComputeScreenPos

1 场景搭建与实现思路


两个plane如图交叉,将在水平plane中实现基本的风格化水表面
fig1 场景搭建
泡沫是在两个plane的相交处生成的,== 换句话说当水面和地面的距离小于一定的阈值,则产生泡沫 ==。
现在的问题是如何得到水面和地面的距离呢?
其中一个方法从深度图信息反推出地面的顶点世界坐标
这是一个比较暴力但合理的方法。
这里提供了两种,一种是使用unity提供的接口,另一种是自行反推。

2 深度图获取与原理


获取方式

urp提供了获取深度图的方法,在管线设置中勾选depth texture
fig2 urp设置
在debugger中,找到对应的深度图名字,这样可以直接在shader中调用_CameraDepthTexture
fig3
此实验是在dx平台下建立的,深度图是近红远黑
fig4

深度图计算原理

需要知道基本的计算流程,才能够了解后面的反推过程,如果对此部分不感兴趣,可跳过这个部分。
因为算式打起来比较麻烦,这里只显示必要的重点。详细,可参考这个文章 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=(2vwvx+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;

4 结果

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43544518/article/details/128747765