屏幕空間像素的位置,是一個二維的浮點數,而世界空間的位置,則是三維的浮點數。實現的基本思路很簡單,是世界空間位置變換到屏幕空間位置的逆過程,只是稍微有些區別。如果對圖形渲染管線中的坐標變換沒有弄清楚,或者習慣了Unity中直接調用封裝好的函數,確實有些麻煩。
簡單的說,世界空間位置變換到屏幕空間位置的步驟是這樣的:
第一步,世界空間位置變換到裁剪空間
float4 projectionPos=mul(UNITY_MATRIX_VP, float4(pos, 1.0));
這里也可以分為兩小步,世界空間變換到觀察空間,觀察空間變換到投影空間,也就是裁剪空間
float4 viewPos=mul(UNITY_MATRIX_V, float4(worldPos, 1.0));
float4 projectionPos=mul(UNITY_MATRIX_P, viewPos);
第二步,裁剪空間變換到屏幕空間位置
float4 projectionPos——>float2 screenPos
這里也可以分為兩小步,透視除法和屏幕映射
透視除法,實際是將視椎體壓平成為一個立方體。
projectionPos.xyz/projectionPos.w;
屏幕映射
[-1,1]——>[0,1]——>屏幕的像素位置
如果我們從屏幕空間位置反推世界空間位置的話,需要知道裁剪空間或觀察空間的位置,然后直接乘逆矩陣變換到世界空間即可。
但是如果要得到裁剪空間的位置,我們缺少w的信息,只有屏幕空間的位置xy,線性深度z(unity從深度貼圖的直接取出的深度為非線性的)。
因此,我們需要通過別的方式。根據攝像機的參數設置來計算得到觀察空間的像素位置。
1.計算觀察空間的Z分量
深度圖采樣,然后線性0-1,這時候得到的值是屏幕空間的線性深度Z。
觀察空間的Z計算即,觀察空間的近裁減屏幕的位置+近裁剪平面與遠裁剪平面之間的距離*線性深度。
float zdepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv.xy);
float linearDepth=Linear01Depth(depth);
float viewPosZ=_ProjectionParams.y+(_ProjectionParams.z-_ProjectionParams.y)*linearDepth;
_ProjectionParams在Unity中的format為:
float4 _ProjectionParams; // x = 1 or -1 (-1 if projection is flipped) // y = near plane // z = far plane // w = 1/far plane
2.計算觀察空間Z為camPosZ處視椎體切面的高度和寬度
float height = 2 * camPosZ / unity_CameraProjection._m11; float width = _ScreenParams.x / _ScreenParams.y * height;
unity_CameraProjection是攝像機的投影矩陣,具體的內容可以看Unity的API文檔,unity_CameraProjection._m11的內容是:
unity_CameraProjection._m11= 2.0F * near / (top - bottom);
這里的near就是計算得到的camPosZ,top-bottom即height。
知道高度和屏幕寬高的比例后,就可以計算寬度
// x = width // y = height // z = 1 + 1.0/width // w = 1 + 1.0/height float4 _ScreenParams;
3.根據高度和寬度,以及屏幕空間位置UV,得到觀察空間的XY坐標
float camPosX = width * uv.x - width / 2; float camPosY = height * uv.y - height / 2;
這里是一個平移的操作,在屏幕空間,原點位於左下角。但是在觀察空間的視椎體切面上,原點位於屏幕的中心。所以原來位於屏幕坐標系的坐標(x,y),在平面中的坐標為(x-0.5,y-0.5)。
4.在熟悉了原理之后,以上步驟可以簡單用以下的矩陣運算實現:
float zdepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv.xy); float4 clipPos = float4(i.uv.xy, depth, 1.0); clipPos.xyz = 2.0f * clipPos.xyz - 1.0f; float4 camPos = mul(unity_CameraInvProjection, clipPos); camPos.xyz /= camPos.w; camPos.z *= -1;
5.最后,矩陣乘法變換觀察空間到世界空間
float3 worldPos=mul(unity_CameraToWorld, camPos).xyz;