fogerror
在一个开了深度雾,平面和天空盒由头摄像机渲染,而材质球由正交相机渲染的场景下,调节正交相机的近裁剪面为负时,会出现材质球突变成雾的颜色的bug。

需要把URP源码中的 #define _FOG_FRAGMENT 1 注释掉

一般来说,连续调节某个数值,变化也应当是连续的,而雾出现这种情况必然有哪个地方不对劲。

解析UNITY_Z_0_FAR_FROM_CLIPSPACE的实现

通过查看雾的源码,找到了UNITY_Z_0_FAR_FROM_CLIPSPACE,它是Unity内置线性雾中计算Factor的关键部分。

real ComputeFogFactor(float zPositionCS)
{
	float clipZ_0Far = UNITY_Z_0_FAR_FROM_CLIPSPACE(zPositionCS);
	return ComputeFogFactorZ0ToFar(clipZ_0Far);
}

ComputeFogFactorZ0ToFar(clipZ_0Far)的意思是将clipZ_0Far根据雾的start和end位置,计算出雾的混合因子(Factor)。clipZ_0Far是相机空间下Z的位置。我们看一下它是如何得出来的。

if UNITY_REVERSED_Z
// TODO: workaround. There's a bug where SHADER_API_GL_CORE gets erroneously defined on switch.
#if (defined(SHADER_API_GLCORE) && !defined(SHADER_API_SWITCH)) || defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)
//GL with reversed z => z clip range is [near, -far] -> remapping to [0, far]
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max((coord - _ProjectionParams.y)/(-_ProjectionParams.z-_ProjectionParams.y)*_ProjectionParams.z, 0)
#else
//D3d with reversed Z => z clip range is [near, 0] -> remapping to [0, far]
        //max is required to protect ourselves from near plane not being correct/meaningful in case of oblique matrices.
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(((1.0-(coord)/_ProjectionParams.y)*_ProjectionParams.z),0)
#endif
#elif UNITY_UV_STARTS_AT_TOP
    //D3d without reversed z => z clip range is [0, far] -> nothing to do
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#else
//Opengl => z clip range is [-near, far] -> remapping to [0, far]
#define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(((coord + _ProjectionParams.y)/(_ProjectionParams.z+_ProjectionParams.y))*_ProjectionParams.z, 0)
#endif

这个宏分为是否Reversed-Z、是否是D3D共4种情况。它的作用是将ClipSpace的Z值映射到[0, Far]的范围。

假设一点经过mvp矩阵变换后,我们得到齐次空间下的点,再除以w可以得到NDC空间下的点。雾效的计算需要知道shading point在雾的start和end范围内的比例,而start和end是相机空间下的,我们需要求出shading point在相机空间下的z值。

下面会用到投影矩阵,所有的矩阵都是列向量。假设屏幕宽高比为(alpha),Fov为(theta),近裁剪面为n,远裁剪面为f。

D3D

D3D的投影矩阵:

[begin{bmatrix} frac{1}{alphatan{(theta/2)}} & 0 & 0 & 0 \ 0 & frac{1}{tan{(theta/2)}} & 0 & 0 \ 0 && 0 && frac{f}{f - n} && frac{-fn}{f-n} \ 0 && 0 && 1 && 0 end{bmatrix} ]

设shading point在相机空间下的坐标为((x, y, z, w)),相机空间下z的范围是[n, f],与投影矩阵相乘得到clip space下的坐标。
我们不需要考虑xy,clip space下的zw为((frac{zf}{f-n} - frac{fn}{f-n}, z)),我们将其记为(z_{clip})(w_{clip})

将其除以w,转换到NDC空间下,得到(z_{ndc} = frac{f}{f-n} · frac{z- n}{z}),范围是[0, 1],那么将其映射到[near, far]即可。也就是

[lerp(n, f, z_{ndc}) \ = n· (1 - z_{ndc}) + f· z_{ndc} \ = n+ f· (1 - frac{z_{clip}}{{w_{clip}}}) ]

而Unity并不是用的这个公式,而是直接用的(z_{clip})进行映射。我是这样理解的,(z_{clip})的范围可以很容易的得出来是[0, f],而我们想要将z映射的范围是[n, f],用(z_{clip})近似也是可以的。以下的推导也以[0, f]为准

OpenGL

OpenGL的投影矩阵

[begin{bmatrix} frac{1}{alphatan{(theta/2)}} & 0 & 0 & 0\ 0 & frac{1}{tan{(theta/2)}} & 0 & 0\ 0 && 0 && frac{-(f + n)}{f - n} && frac{-2nf}{f-n}\ 0 && 0 && -1 && 0 end{bmatrix} ]

跟上面的推导类似,(z_{clip} = frac{-(f+n)·z - 2nf}{f- n}),在OpenGL的投影矩阵中,z的取值范围是[-f, -n],所以(z_{clip})的范围是[-n, f](near plane时为-n,far plane时为f)。将其映射到[0, f]可以得出映射关系是(frac{f·(z_{clip} + n)}{f + n}),与unity的代码是一致的。
20230417234416

D3D & Reversed-Z

D3D的reverse-Z是在计算投影矩阵时,near Plane映射到1,而far Plane映射到0。简单来说,可以将原来的D3D投影矩阵的n与f交换,就可以得到Reversed-Z下的投影矩阵。

[begin{bmatrix} frac{1}{alphatan{(theta/2)}} & 0 & 0 & 0 \ 0 & frac{1}{tan{(theta/2)}} & 0 & 0 \ 0 && 0 && frac{n}{n - f} && frac{-fn}{n-f} \ 0 && 0 && 1 && 0 end{bmatrix} ]

(z_{clip} = frac{n(z-f)}{n-f}),取值范围是[n, 0](near plane时为n,far plane时为0),将其映射到[0, f],可以得到映射关系为
(f·(1 - frac{z_{clip}}{n}))

OpenGL & Reverse-Z

同上,OpenGL的Reversed-Z投影矩阵为

[begin{bmatrix} frac{1}{alphatan{(theta/2)}} & 0 & 0 & 0\ 0 & frac{1}{tan{(theta/2)}} & 0 & 0\ 0 && 0 && frac{-(f + n)}{n - f} && frac{-2nf}{n-f}\ 0 && 0 && -1 && 0 end{bmatrix} ]

(z_{clip} = frac{(f+n)·z + 2nf}{f- n}),取值范围为[n, -f](near plane时为n,far plane时为-f),将其映射到[0, f],可以退出映射关系为
(frac{f(n-z)}{n+f})

结论

以上是在透视相机下的雾的UNITY_Z_0_FAR_FROM_CLIPSPACE的推导,而这个宏里没有考虑正交相机(正交相机的w部分为1),(z_{clip})在D3D下的范围是[0, 1],在OpenGL下的范围是[-1, 1],要映射到[0, f],与上面透视投影矩阵的推导方式肯定是不同的,所以就出现了显示错误。

而在开启_FOG_FRAGMENT宏的时候,效果没有问题,是因为直接使用了摄像机空间下的深度进行了FogFactor的计算,正确且直接。

float viewZ = -(mul(UNITY_MATRIX_V, positionWS).z);
// View Z is 0 at camera pos, remap 0 to near plane.
float nearToFarZ = max(viewZ - _ProjectionParams.y, 0);
fogFactor = ComputeFogFactorZ0ToFar(nearToFarZ);

参考

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/silence394/p/17328104.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!