【Unity】屏幕空間位置變換到世界空間位置的方法


屏幕空間像素的位置,是一個二維的浮點數,而世界空間的位置,則是三維的浮點數。實現的基本思路很簡單,是世界空間位置變換到屏幕空間位置的逆過程,只是稍微有些區別。如果對圖形渲染管線中的坐標變換沒有弄清楚,或者習慣了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;  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM