欢迎访问104网
在一个开了深度雾,平面和天空盒由头摄像机渲染,而材质球由正交相机渲染的场景下,调节正交相机的近裁剪面为负时,会出现材质球突变成雾的颜色的bug。
需要把URP源码中的 #define _FOG_FRAGMENT 1 注释掉
一般来说,连续调节某个数值,变化也应当是连续的,而雾出现这种情况必然有哪个地方不对劲。
通过查看雾的源码,找到了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的投影矩阵:
\[\begin{bmatrix} \frac{1}{\alpha\tan{(\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的投影矩阵
\[\begin{bmatrix} \frac{1}{\alpha\tan{(\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。将其映射到[0, f]可以得出映射关系是\(\frac{f·(z_{clip} + n)}{f +
n}\),与unity的代码是一致的。
D3D的reverse-Z是在计算投影矩阵时,near Plane映射到1,而far Plane映射到0。简单来说,可以将原来的D3D投影矩阵的n与f交换,就可以得到Reversed-Z下的投影矩阵。
\[\begin{bmatrix} \frac{1}{\alpha\tan{(\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的Reversed-Z投影矩阵为
\[\begin{bmatrix} \frac{1}{\alpha\tan{(\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_FROMCLIPSPACE的推导,而这个宏里没有考虑正交相机(正交相机的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);
Copyright © 2018-2024 104网 版权所有 | 备案号:京ICP备104