Shader "Custom/CalcWorldPosByDepthUseDepthTexInPostProcess" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
Pass{
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert_img
#pragma fragment frag
sampler2D _CameraDepthTexture;
float4 GetWorldPositionFromDepthValue( float2 uv, float linearDepth )
{
float camPosZ = _ProjectionParams.y + (_ProjectionParams.z - _ProjectionParams.y) * linearDepth;
// unity_CameraProjection._m11 = near / t,其中t是視錐體near平面的高度的一半。
// 投影矩陣的推導見:http://www.songho.ca/opengl/gl_projectionmatrix.html。
// 這裏求的height和width是座標點所在的視錐體截面(與攝像機方向垂直)的高和寬,並且
// 假設相機投影區域的寬高比和屏幕一致。
float height = 2 * camPosZ / unity_CameraProjection._m11;
float width = _ScreenParams.x / _ScreenParams.y * height;
float camPosX = width * uv.x - width / 2;
float camPosY = height * uv.y - height / 2;
float4 camPos = float4(camPosX, camPosY, camPosZ, 1.0);
return mul(unity_CameraToWorld, camPos);
}
float4 frag( v2f_img o ) : COLOR
{
float rawDepth = SAMPLE_DEPTH_TEXTURE( _CameraDepthTexture, o.uv );
// 注意:經過投影變換之後的深度和相機空間裏的z已經不是線性關係。所以要先將其轉換爲線性深度。
// 見:https://developer.nvidia.com/content/depth-precision-visualized
float linearDepth = Linear01Depth(rawDepth);
float4 worldpos = GetWorldPositionFromDepthValue( o.uv, linearDepth );
return float4( worldpos.xyz / 255.0 , 1.0 ) ; // 除以255以便顯示顏色,測試用。
}
ENDCG
}
}
}
在上面的代碼中,frag函數中的o.uv是將取值範圍轉換到[0, 1]後的x3, y3。_CameraDepthTexture即深度貼圖,裏面存儲的就是每個像素點的z3。爲了使用深度貼圖,需要在C#腳本中將相機的depthTextureMode 爲Depth或者DepthNormal:
MyCamera.depthTextureMode = DepthTextureMode.Depth; //使用相機自己生成的 _CameraDepthTexture 必須設置這個
unity_CameraProjection是相機的投影矩陣,裏面的第2行第2個元素存儲的就是相機FOV的一半的正切值(tan)。
如何測試計算結果的正確性呢?我們可以在物體自身的材質上寫一個shader,像後處理shader一樣根據世界座標顯示物體的顏色:
Shader "Custom/GenerateDepthAndShowWoldPos" {
Properties {
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
Blend Off
Pass{
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
struct v2f {
float4 pos: SV_POSITION;
float4 worldpos : TEXCOORD0;
};
v2f vert( appdata_img v )
{
v2f o;
o.pos = mul( UNITY_MATRIX_MVP, v.vertex ) ;
o.worldpos = mul(unity_ObjectToWorld, v.vertex);
o.worldpos.w = o.pos.z / o.pos.w;
return o;
}
float4 frag( v2f o ) : COLOR
{
return float4( o.worldpos.xyz / 255.0, 1.0) ; // o.worldpos.xyz/255 是爲了顏色輸出。
}
ENDCG
}
}
FallBack "Diffuse"
}
我們知道Unity編輯器的Scene視圖是沒有後處理效果的,而在編輯器中運行遊戲時的Game視圖是有後處理效果的。因此如果Scene和Game視圖中的物體顏色一致,那就說明後處理反推世界座標的邏輯寫對了:
在上圖的Game視圖中,物體以外的背景呈現彩色,是因爲後處理shader會處理屏幕上的所有像素並反推其世界座標。不在物體上的像素全都會被映射到視錐體的far截面上。
注:實驗用的Unity版本是5.5.0p4。本文參考了前同事的一篇筆記:http://note.youdao.com/share/?id=7350142fadd3b244a80df594ddfbb9f2&type=note#/