后处理中使用深度图重建世界坐标 - 相对Camera坐标空间的方式
原理
下图中球体的世界坐标=相机的世界坐标+球体相对于相机的坐标。但在后处理的shader中,我们能知道的有:1) 相机的世界坐标,2) 相机信息:FOV, Near, Far, aspect等,3) 球体的z值
1) 下面的图是侧式图,通过下面的图我们可以知道 tan30=nearPlaneHalfHeight / nearPlaneDist,所以nearPlaneHalfHeight=tan30*nearPlaneDist。
然后我们可以根据屏幕的宽高比(也就是camera.aspect)求出nearPlaneHalfWidth,camera.aspect=nearPlaneHalfWidth / nearPlaneHalfHeight
图中的橙色线为nearPlaneHalfHeight,紫色线为nearPlaneHalfWidth
2) 下图是后视图:根据向量相加原则,可以得到斜角的那个向量
3) 然后再用向量相加的方式得到粉色那个向量cameraToRightTopCorner,也就是相机到Near Plane右上角的向量。以此类推可以求出相机到Near Plane 4个角的向量。
4) 我们现在先求一个在相机的视锥边缘线上的物体相对相机的坐标
因为两个角度相等,根据三角形相关定理可以知道:cameraToRightTopCorner的长度/nearPlaneDist=cameraToObject的长度/深度,
就可以求出:cameraToObject的长度=cameraToRightTopCorner的长度 / nearPlaneDist * 深度
这个物体相对相机的坐标等于=cameraToRightTopCorner向量 * cameraToObject的长度
5) 利用插值的方式求任意位置物体的坐标,这个在片元着色器中会自动帮我们做,只要我们在顶点着色器处理好4个顶点,也就是NearPlane的4个角落。
实现
using UnityEngine; [RequireComponent(typeof(Camera))] public class GetWorldPosByDepth2 : MonoBehaviour { private Camera m_Camera; private Material m_GetWorldPosByDepthMat; private Matrix4x4 m_NearPlaneCornersRay = Matrix4x4.identity; void Start() { m_Camera = GetComponent<Camera>(); m_Camera.depthTextureMode = DepthTextureMode.Depth; m_GetWorldPosByDepthMat = new Material(Shader.Find("My/DepthTex/GetWorldPosByDepth2")); } private void OnRenderImage(RenderTexture src, RenderTexture dest) { float fov = m_Camera.fieldOfView; float nearPlaneDist = m_Camera.nearClipPlane; float farPlaneDist = m_Camera.farClipPlane; float aspect = m_Camera.aspect; float halfHeight = nearPlaneDist * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad); var cameraTransform = m_Camera.transform; var centerToTop = cameraTransform.up * halfHeight; var centerToRight = cameraTransform.right * halfHeight * aspect; var cameraToRightTopCorner = cameraTransform.forward * nearPlaneDist + centerToRight + centerToTop; //相机指向Near Plane的右上角落 float scale = cameraToRightTopCorner.magnitude / nearPlaneDist; //dist/depth cameraToRightTopCorner.Normalize(); cameraToRightTopCorner *= scale; var cameraToLeftTopCorner = cameraTransform.forward * nearPlaneDist + centerToTop - centerToRight; cameraToLeftTopCorner.Normalize(); cameraToLeftTopCorner *= scale; var cameraToLeftBottomCorner = cameraTransform.forward * nearPlaneDist - centerToTop - centerToRight; cameraToLeftBottomCorner.Normalize(); cameraToLeftBottomCorner *= scale; var cameraToRightBottomCorner = cameraTransform.forward * nearPlaneDist + centerToRight - centerToTop; cameraToRightBottomCorner.Normalize(); cameraToRightBottomCorner *= scale; m_NearPlaneCornersRay.SetRow(0, cameraToLeftBottomCorner); m_NearPlaneCornersRay.SetRow(1, cameraToRightBottomCorner); m_NearPlaneCornersRay.SetRow(2, cameraToRightTopCorner); m_NearPlaneCornersRay.SetRow(3, cameraToLeftTopCorner); m_GetWorldPosByDepthMat.SetMatrix("_NearPlaneCornersRay", m_NearPlaneCornersRay); Graphics.Blit(src, dest, m_GetWorldPosByDepthMat); } }
用到的shader
//后处理中根据深度图重建世界坐标, 注意: 只适用于后处理OnRenderImage那边 Shader "My/DepthTex/GetWorldPosByDepth2" { Properties { _MainTex("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } ZTest Always Cull Off ZWrite Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float2 uv_depth : TEXCOORD1; float4 interpolatedRay : TEXCOORD2; //相机指向顶点的射线 }; sampler2D _CameraDepthTexture; //深度图 sampler2D _MainTex; //后处理提供的屏幕图 float4 _MainTex_TexelSize; float4x4 _NearPlaneCornersRay; //相机指向Near Plane 4个角落的射线。注意是存放了4个向量, 不是矩阵 v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; o.uv_depth = v.uv; #if UNITY_UV_STARTS_AT_TOP if (_MainTex_TexelSize.y < 0) o.uv_depth.y = 1 - o.uv_depth.y; #endif int index = 0; if (v.uv.x < 0.5) { if (v.uv.y < 0.5) index = 0; //左下角 else index = 3; //左上角 } else { if (v.uv.y < 0.5) index = 1; //右下 else index = 3; //右上 } #if UNITY_UV_STARTS_AT_TOP if (_MainTex_TexelSize.y < 0) index = 3 - index; #endif o.interpolatedRay = _NearPlaneCornersRay[index]; return o; } fixed4 frag(v2f i) : SV_Target { float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth); float linearDepth = LinearEyeDepth(depth); float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz; //片元着色器是逐像素绘制, 这边的interpolatedRay是一个插值后的值
//用世界坐标做相关处理 fixed4 c = tex2D(_MainTex, i.uv); return c; } ENDCG } } FallBack Off }
参考
《shader入门精要》13.3.1