根據(jù)深度紋理獲取世界坐標(biāo)很常用毙石,可以實現(xiàn)很多有用的功能中姜,比如簡單的霧 / 掃描線等
主要參考
https://forum.unity.com/threads/reconstructing-world-pos-from-depth-imprecision.228936/
準(zhǔn)備工作
在 unity 中開啟深度貼圖
創(chuàng)建一個腳本用來放在 Camera 組件上
void Awake()
{
cameraComponent.depthTextureMode = DepthTextureMode.Depth; // 生成獲得深度紋理
// cameraComponent.depthTextureMode = DepthTextureMode.DepthNormals; // 生成深度和法線紋理
}
根據(jù)搜索總結(jié)了下惯疙,重建世界坐標(biāo)基本有兩種計算方法
第一種最簡單最容易理解的就是在 shader 中對深度紋理采樣,然后對深度坐標(biāo)進行矩陣變換州既,乘以投影變換矩陣和相機變換矩陣的逆矩陣浙值,就可以還原得到世界坐標(biāo)
// in C#
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (postEffectMaterial == null)
{
Graphics.Blit(src, dest);
}
else
{
//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
Matrix4x4 viewMat = currentCamera.worldToCameraMatrix;
Matrix4x4 projMat = GL.GetGPUProjectionMatrix(currentCamera.projectionMatrix, false);
//Matrix4x4 viewProjMat = currentCamera.projectionMatrix * currentCamera.worldToCameraMatrix;
//有的文章里直接使用的currentCamera.projectionMatrix,如果遠離原點的話會計算不準(zhǔn)確
Matrix4x4 viewProjMat = (projMat * viewMat);
Shader.SetGlobalMatrix("_ViewProjInv", viewProjMat.inverse);
Graphics.Blit(src, dest, postEffectMaterial);
}
}
// 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;
}
第一種方法原理比較簡單悍抑,但是效率不是很高鳄炉,畢竟是逐像素計算,
第二種方法可以使用屏幕射線差值的方法传趾,《unity shader入門精要》中有非常詳細的推導(dǎo)和解釋
物體的世界坐標(biāo)為相機的世界坐標(biāo)+物體在相機坐標(biāo)系的坐標(biāo)
相機坐標(biāo)系的坐標(biāo) = 點的方向 * 點到相機的距離
點的方向可以通過計算剪裁空間四個角的射線得到
點到相機的距離為
distance = ray * depth / far; far = 1;
distance = ray * depth;
書中使用的是近裁平面推導(dǎo)迎膜,好像使用原裁平面更好推導(dǎo)
總結(jié)起來就是
- 先計算相機到屏幕四個角的射線
- 在頂點著色器中根據(jù) uv 的值選擇對應(yīng)的射線
- 在像素著色器中計算世界坐標(biāo)
// in C#
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (postEffectMaterial == null)
{
Graphics.Blit(src, dest);
}
else
{
float aspect = currentCamera.aspect;
float near = currentCamera.nearClipPlane;
float far = currentCamera.farClipPlane;
float halfFovTan = Mathf.Tan(currentCamera.fieldOfView * 0.5f * Mathf.Deg2Rad);
Vector3 up = transform.up;
Vector3 right = transform.right;
Vector3 forward = transform.forward;
Vector3 forwardVector = forward * far;
Vector3 upVector = up * far * halfFovTan;
Vector3 rightVector = right * far * halfFovTan * aspect;
Vector3 topLeft = forwardVector - rightVector + upVector;
Vector3 topRight = forwardVector + rightVector + upVector;
Vector3 bottomLeft = forwardVector - rightVector - upVector;
Vector3 bottomRight = forwardVector + rightVector - upVector;
Matrix4x4 viewPortRay = Matrix4x4.identity;
viewPortRay.SetRow(0, topLeft);
viewPortRay.SetRow(1, topRight);
viewPortRay.SetRow(2, bottomLeft);
viewPortRay.SetRow(3, bottomRight);
postEffectMaterial.SetMatrix("_ViewPortRay", viewPortRay);
Graphics.Blit(src, dest, postEffectMaterial);
}
}
# in vertex shader
v2f vertex_depth(appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord.xy;
int index = 0;
if (v.texcoord.x < 0.5 && v.texcoord.y > 0.5) {
index = 0;
} else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5) {
index = 1;
} else if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5) {
index = 2;
} else {
index = 3;
}
o.interpolateRay = _FrustumCornerRay[index];
return o;
}
# in fragment shader
fixed4 frag_depth(v2f i) : SV_Target
{
float4 color = tex2D(_MainTex, i.uv);
float depthTextureValue = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
float linear01Depth = Linear01Depth(depthTextureValue);
float3 worldPos = _WorldSpaceCameraPos + linear01Depth * i.interpolateRay.xyz;
return fixed4(worldPos, 1.0);
}
由于是在頂點著色器計算線段的差值,所有效率會比逐像素計算要高
為了方便驗證計算結(jié)果浆兰,可以在場景中簡單擺放幾個物體磕仅,使用簡單的材質(zhì)輸出世界坐標(biāo)作為顏色珊豹,然后在 camera 組件上添加屏幕后處理的腳本繪制世界坐標(biāo),開啟 / 禁用 camera 上的后處理就可以對比計算結(jié)果