根据深度值反推顶点的世界坐标值
顶点从世界空间转换到NDC空间:
需要先通过视角矩阵转换到摄像机空间,然后再通过透视矩阵转换到齐次裁剪空间,最后做透视除法(齐次裁剪空间的坐标除以自己的w分量),公式:
\(V_{proj} = M_{proj}M_{view}V_{world} \tag{1.1}\)
\(V_{ndc} = V_{proj} / V_{proj}.w \tag{1.2}\)
根据深度值反推世界坐标
当我们知道一个坐标的深度值,并且知道当前的屏幕空间坐标,那么就可以通过屏幕空间坐标和深度值得到NDC坐标
\(V_{ndc} = (V_{screen}.x * 2.0 - 1.0, V_{screen}.y * 2.0 - 1.0, depth, 1.0) \tag{2.1}\)
当我们知道了NDC坐标之后我们可能会就想可以根据上面的世界空间转换到NDC空间的步骤的逆直接得到世界坐标了,这个步骤会是这样:
\(V_{proj} = (M_{proj}M_{view})^{-1}V_{ndc}) * V_{proj}.w \tag{2.2}\)
是的,这时候你就会发现你并不知道\(V_{proj}.w\)的值是什么,因为这个值在做透视除法之后已经丢失了。
但是,可以世界坐标的w值时已知的且\(V_{world}.w = 1.0\),所以有:
\(V_{world}.w = ((M_{proj}M_{view})^{-1}V_{ndc}).w * V_{proj}.w = 1.0 \tag{2.3}\)
所以有:
\(V_{proj}.w = 1.0 / ((M_{proj}M_{view})^{-1}V_{ndc}).w \tag{2.4}\)
把上式带入\(\left( 2.2 \right)\),得:
\(V_{proj} = (M_{proj}M_{view})^{-1}V_{ndc}) / ((M_{proj}M_{view})^{-1}V_{ndc}).w \tag{2.5}\)
所以,世界坐标就为NDC坐标右乘\(M_{proj}M_{view})^{-1}\),再除以自身的w。
在Unity中的实例:
//main problem encountered is camera.projectionMatrix = ??????? worked but further from camera became more inaccurate
//had to use GL.GetGPUProjectionMatrix( ) seems to stay pretty exact now
//在C#中传递变换矩阵进shader:
Matrix4x4 viewMat = camera.worldToCameraMatrix;
Matrix4x4 projMat = GL.GetGPUProjectionMatrix( camera.projectionMatrix, false );
Matrix4x4 viewProjMat = (projMat * viewMat);
Shader.SetGlobalMatrix("_ViewProjInv", viewProjMat.inverse);
//in fragment shader:
uniform float4x4 _ViewProjInv;
float4 GetWorldPositionFromDepth( float2 uv_depth )
{
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv_depth);
float4 H = float4(uv_depth.x*2.0-1.0, (uv_depth.y)*2.0-1.0, depth, 1.0);
float4 D = mul(_ViewProjInv,H);
return D/D.w;
}