与 SV_Position 相比,UnityObjectToClipPos returns Z 深度值不正确

UnityObjectToClipPos returns incorrect values for Z depth when compared to SV_Position

我一直在尝试获取裁剪平面中顶点的 Z 位置,即它在深度缓冲区中的位置,但我一直在观察影响 UnityObjectToClipPos.[=23 结果的怪异行为=]

我编写了一个表面着色器,它根据深度为顶点着色。相关代码如下:

Tags { "RenderType"="Opaque" }
LOD 200
Cull off

CGPROGRAM
#pragma target 3.0
#pragma surface surf StandardSpecular alphatest:_Cutoff addshadow vertex:vert
#pragma debug

struct Input
{
    float depth;
};

float posClipZ(float3 vertex)
{
    float4 clipPos = UnityObjectToClipPos(vertex);
    float depth = clipPos.z / clipPos.w;
#if !defined(UNITY_REVERSED_Z)
    depth = depth * 0.5 + 0.5;
#endif
    return depth;
}

void vert(inout appdata_full v, out Input o)
{
    UNITY_INITIALIZE_OUTPUT(Input, o);
    o.depth = posClipZ(v.vertex);
}

void surf(Input IN, inout SurfaceOutputStandardSpecular o)
{
    o.Albedo.x = clamp(IN.depth, 0, 1);
    o.Alpha = 1;
}
ENDCG

根据我的理解,UnityObjectToClipPos应该return顶点在相机剪辑坐标中的位置,Z坐标应该(从齐次坐标变换时)在0和1之间。然而,这根本不是我观察到的:

这显示相机与球体相交。请注意,靠近裁剪平面的相机附近或后面的顶点实际上具有负深度(我已经用其他转换为反照率颜色检查过)。似乎 clipPos.z 大多数时候实际上是不变的,只有 clipPos.w 在变化。

我成功劫持了生成的片段着色器以添加一个 SV_Position 参数,这正是我最初期望看到的:

但是,我不想使用 SV_Position,因为我希望能够从其他位置计算顶点着色器中的深度。

似乎 UnityObjectToClipPos 不适合这项任务,因为这样获得的深度甚至不是单调的。

那么,如何通过在顶点着色器中计算的深度来模拟第二张图像?它在插值方面也应该是完美的,所以我想我必须首先在顶点着色器中使用 UnityObjectToViewPos 来获得线性深度,然后在片段着色器中相应地缩放它。

我不完全确定为什么 UnityObjectToClipPos 没有 return 任何有用的东西,但无论如何它都不是完成任务的正确工具。原因是顶点的深度在深度缓冲区中不是线性的,因此首先必须使用与相机的实际距离来正确插值顶点之间所有像素的深度:

float posClipZ(float3 vertex)
{
    float3 viewPos = UnityObjectToViewPos(vertex);
    return -viewPos.z;
}

执行 fragment/surface 着色器后,LinearEyeDepth 似乎是检索预期深度值的正确函数:

void surf(Input IN, inout SurfaceOutputStandardSpecular o)
{
    o.Albedo.x = clamp(LinearEyeDepth(IN.depth), 0, 1);
    o.Alpha = 1;
}

再次强调,不要在顶点着色器中使用 LinearEyeDepth,因为值会被错误地插值。