Unity3D之笛卡爾座標系轉換——屏幕座標轉換世界座標,世界座標轉換相機座標工具

由於要作AR的標記功能,因此就要用到座標的轉換,就總結了一下屏幕座標、世界座標、相機座標之間的轉換。
首先說明的是Unity3D聽從Direct3D標準的左手笛卡爾座標系變換規則。
也就是說:ide

世界座標系就是左手笛卡爾座標系(x,y,z),相機也是左手笛卡爾座標系(u,v,w),且面向自身座標系w座標軸正上方,屏幕以中央爲原點,右和上分別爲x,y的正方向。工具

我將工具代碼直接上:this

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraTransformationUtilities : MonoBehaviour
{
    public static CameraTransformationUtilities Instance { get; private set; }
    protected CameraTransformationUtilities() { }

    private void Start()
    {
        Instance = this;
    }

    /// <param name="projectionMatrix">投影矩陣</param>
    /// <param name="viewMatrix">視點矩陣</param>
    /// <param name="projection">列表形式的投影矩陣</param>
    /// <param name="view">列表形式的視點矩陣</param>
    /// <param name="p">要投影的點的引用</param>
    /// <param name="pl">要投影的點引用列表</param>
    /// <param name="width">屏幕寬</param>
    /// <param name="height">屏幕高</param>
    /// <param name="rowBase">列表是否以行優先進行矩陣元素的存儲</param>
    /// <param name="flipLeftToRight">水平翻轉</param>
    /// <param name="flipUpsideDown">上下翻轉</param>

    public void UnprojectScreenToWorld(
        Matrix4x4 projectionMatrix, 
        Matrix4x4 viewMatrix, 
        ref Vector3 p)
    {
        // 世界座標->相機座標, 相機座標->屏幕座標 分別須要 viewMatrix和projectionMatrix
        Matrix4x4 PV = projectionMatrix * viewMatrix;

        // 逆矩陣即爲從屏幕到世界的座標變換,需指定像素深度
        p = PV.inverse.MultiplyPoint(p);
    }

    public void UnprojectScreenToWorld(
        IReadOnlyList<float> projection,
        IReadOnlyList<float> view,
        ref Vector3 p,
        bool rowBase = true
    )
    {
        Matrix4x4 projectionMatrix = ConstructMatirx(projection, rowBase);
        Matrix4x4 viewMatrix = ConstructMatirx(view, rowBase);
        UnprojectScreenToWorld(projectionMatrix, viewMatrix, ref p);
    }

    public void UnprojectScreenToWorld(
        IReadOnlyList<float> projection,
        IReadOnlyList<float> view,
        ref Vector3 p,
        float width,
        float height,
        bool rowBase = true,
        bool flipLeftToRight = false,
        bool flipUpsideDown = false
        )
    {
        Matrix4x4 projectionMatrix = ConstructMatirx(projection, rowBase);
        Matrix4x4 viewMatrix = ConstructMatirx(view, rowBase);
        ScreenSpaceTransform(ref p, width, height, flipLeftToRight, flipUpsideDown);
        UnprojectScreenToWorld(projectionMatrix, viewMatrix, ref p);
    }

    public void UnProjectScreenToWorld(
        Matrix4x4 projectionMatrix,
        Matrix4x4 viewMatrix,
        ref List<Vector3> pl
        )
    {
        Matrix4x4 PV = projectionMatrix * viewMatrix;
        for (int i = 0; i < pl.Count; i++)
        {
            pl[i] = PV.inverse.MultiplyPoint(pl[i]);
        }
    }

    public void UnProjectScreenToWorld(
        IReadOnlyList<float> projection,
        IReadOnlyList<float> view, 
        ref List<Vector3> pl, 
        float width, 
        float height, 
        bool rowBase = true,
        bool flipLeftToRight = false,
        bool flipUpsideDown = false
        )
    {
        Matrix4x4 projectionMatrix = ConstructMatirx(projection, rowBase);
        Matrix4x4 viewMatrix = ConstructMatirx(view, rowBase);
        Matrix4x4 PV = projectionMatrix * viewMatrix;
        ScreenSpaceTransform(ref pl, width, height, flipLeftToRight, flipUpsideDown);
        for(int i = 0; i < pl.Count; i++)
        {
            pl[i] = PV.inverse.MultiplyPoint(pl[i]);
        }
    }

    /// <summary>
    /// 得到相機的世界座標,聽從 Direct3D 標準的左手笛卡爾座標系。
    /// </summary>
    /// <param name="viewMatrix">視點矩陣</param>
    /// <returns></returns>
    public Vector3 GetCameraPosition(Matrix4x4 viewMatrix)
    {
        Matrix4x4 invV = viewMatrix.inverse;
        return new Vector3(invV.m30, invV.m31, invV.m32);
    }

    /// <summary>
    /// 得到相機的世界座標,聽從 Direct3D 標準的左手笛卡爾座標系。
    /// </summary>
    /// <param name="view">視點矩陣元素列表</param>
    /// <param name="rowBase">列表是否以行優先進行矩陣元素的存儲</param>
    /// <returns></returns>
    public Vector3 GetCameraPosition(IReadOnlyList<float> view, bool rowBase = true)
    {
        Matrix4x4 viewMatrix = ConstructMatirx(view, rowBase);
        Matrix4x4 invV = viewMatrix.inverse;
        return new Vector3(invV.m30, invV.m31, invV.m32);
    }

    /// <summary>
    /// 得到相機的視線方向,聽從 Direct3D 標準的左手笛卡爾座標系。
    /// </summary>
    /// <param name="viewMatrix">視點矩陣</param>
    /// <returns></returns>
    public Vector3 GetCameraViewDirection(Matrix4x4 viewMatrix)
    {
        Matrix4x4 invV = viewMatrix.inverse;
        return new Vector3(invV.m20, invV.m21, invV.m22);
    }

    /// <summary>
    /// 得到相機的視線方向,聽從 Direct3D 標準的左手笛卡爾座標系。
    /// </summary>
    /// <param name="view">視點矩陣元素列表</param>
    /// <param name="rowBase">列表是否以行優先進行矩陣元素的存儲</param>
    /// <returns></returns>
    public Vector3 GetCameraViewDirection(IReadOnlyList<float> view, bool rowBase = true)
    {
        Matrix4x4 viewMatrix = ConstructMatirx(view, rowBase);
        Matrix4x4 invV = viewMatrix.inverse;
        return new Vector3(invV.m20, invV.m21, invV.m22);
    }

    /// <summary>
    /// 將屏幕點座標轉換成以屏幕中心爲原點,
    /// 右和上分別爲x,y正方向,
    /// -1到1的座標
    /// </summary>
    /// <param name="screen">屏幕寬或高</param>
    /// <param name="p">像素座標(指定寬則爲橫座標,指定高則爲縱座標)</param>
    /// <param name="flip">是否翻轉(Unity一般狀況下會將縱座標翻轉)</param>
    /// <returns></returns>
    public void ScreenSpaceTransform(ref Vector3 p, float width, float height, bool flipLeftToRight = false, bool flipUpSideDown = false)
    {
        p.Set((p.x / width * 2 - 1) * (flipLeftToRight ? -1 : 1),
                    (p.y / height * 2 - 1) * (flipUpSideDown ? -1 : 1),
                    p.z);
    }

    public void ScreenSpaceTransform(ref List<Vector3> pl, float width, float height, bool flipLeftToRight = false, bool flipUpSideDown = false)
    {
        for(int i = 0; i<pl.Count; i++)
        {
            pl[i].Set((pl[i].x / width * 2 - 1) * (flipLeftToRight ? -1 : 1),
                    (pl[i].y / height * 2 - 1) * (flipUpSideDown ? -1 : 1),
                    pl[i].z);
        }
    }

    /// <summary>
    /// 從列表中構造Matirx4x4矩陣
    /// </summary>
    /// <param name="elements">矩陣元素列表</param>
    /// <param name="rowBase">列表是否以行優先進行矩陣元素的存儲</param>
    /// <returns></returns>
    public Matrix4x4 ConstructMatirx(IReadOnlyList<float> elements, bool rowBase = true)
    {
        if (elements.Count != 16) return Matrix4x4.identity;
        Matrix4x4 mat = new Matrix4x4();
        if (rowBase)
        {
            mat.m00 = elements[0];
            mat.m01 = elements[1];
            mat.m02 = elements[2];
            mat.m03 = elements[3];
            mat.m10 = elements[4];
            mat.m11 = elements[5];
            mat.m12 = elements[6];
            mat.m13 = elements[7];
            mat.m20 = elements[8];
            mat.m21 = elements[9];
            mat.m22 = elements[10];
            mat.m23 = elements[11];
            mat.m30 = elements[12];
            mat.m31 = elements[13];
            mat.m32 = elements[14];
            mat.m33 = elements[15];
        }
        else
        {
            mat.m00 = elements[0];
            mat.m10 = elements[1];
            mat.m20 = elements[2];
            mat.m30 = elements[3];
            mat.m01 = elements[4];
            mat.m11 = elements[5];
            mat.m21 = elements[6];
            mat.m31 = elements[7];
            mat.m02 = elements[8];
            mat.m12 = elements[9];
            mat.m22 = elements[10];
            mat.m32 = elements[11];
            mat.m03 = elements[12];
            mat.m11 = elements[13];
            mat.m23 = elements[14];
            mat.m33 = elements[15];
        }
        return mat;
    }
}

座標的轉換,用的地方特別多,尤爲是屏幕劃線展現在世界座標中,我把他造成了工具,直接能夠用。code

相關文章
相關標籤/搜索