后处理中使用深度图重建世界坐标 - 相对Camera坐标空间的方式

发布时间 2023-03-25 07:12:45作者: yanghui01

原理

下图中球体的世界坐标=相机的世界坐标+球体相对于相机的坐标。但在后处理的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