
在一個開了深度霧,平面和天空盒由頭攝像機渲染,而材質球由正交相機渲染的場景下,調節正交相機的近裁剪面為負時,會出現材質球突變成霧的顏色的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}{\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
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](near plane時為-n,far plane時為f),將其映射到[0, f]可以得出映射關系是\(\frac{f·(z_{clip} + n)}{f + n}\),與unity的代碼是一致的,

D3D & Reversed-Z
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 & Reverse-Z
同上,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_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);
參考
- OpenGL Projection Matrix, http://www.songho.ca/opengl/gl_projectionmatrix.html
- Depth Precision Visualized, https://developer.nvidia.com/content/depth-precision-visualized
- Emil Persson, Depth Buffer Precision, http://www.humus.name/Articles/Persson_CreatingVastGameWorlds.pdf
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/551077.html
標籤:其他
上一篇:第14屆藍橋杯C++B組省賽題解(A-J)(更新完畢)
下一篇:返回列表
