2021-12-27【Math】MVP无相机计算矩阵屏幕坐标转换

2021-12-27  本文已影响0人  持刀的要迟到了

今天要完成一个计算,实现unity的,一个物体的世界坐标转换到相机视口坐标,然后旋转相机,再把视口坐标转换为世界坐标,把物体放到对应的世界坐标。
最终达到的效果,在game视窗下,物体的屏幕坐标位置始终没变。

第三课:矩阵 (opengl-tutorial.org)
矩阵的旋转,缩放,平移
Pnew = MtranslationMrotationMscale*Pold
先缩放,再旋转,最后平移

第一步是先计算出Mview矩阵。
就是相机相对于世界的矩阵,再取反。
How do I calculate a view matrix using Matrix4x4.LookAt? - Unity Answers

        var viewMatrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).inverse;
        if (SystemInfo.usesReversedZBuffer)
        {
            viewMatrix.m20 = -viewMatrix.m20;
            viewMatrix.m21 = -viewMatrix.m21;
            viewMatrix.m22 = -viewMatrix.m22;
            viewMatrix.m23 = -viewMatrix.m23;
        }

        Debug.Log("=========================");
        Debug.Log(cam.cameraToWorldMatrix);
        Debug.Log(cam.worldToCameraMatrix);
        Debug.Log(viewMatrix);
        Debug.Log(viewMatrix.inverse);

这个步骤,就是基本的相对坐标旋转缩放转换TRS。
但是这是不够的,还需要进行屏幕坐标转换。

Debug.Log(cam.projectionMatrix);

这个投影矩阵,经过打印确定是恒定不变的。
开始实践。
再次参考文章
Calculation behind camera.WorldToScreenPoint - Unity Answers

 public Camera cam;
    public Transform go;

    private GameObject goShpere;

    private void Awake()
    {
        //goShpere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        //goShpere.transform.localScale *= 0.3f;
        P = cam.projectionMatrix;
    }

    // Update is called once per frame
    void Update()
    {
        Debug.LogError(StringUtility.GetVec3(cam.WorldToScreenPoint(go.transform.position)));
        Debug.LogError(StringUtility.GetVec3(manualWorldToScreenPoint(go.transform.position)));

        //var vScreenPos1 = manualWorldToScreenPoint(go.transform.position);
        //var vScreenPos2 = cam.WorldToScreenPoint(go.transform.position);
        //var vScreenPos = vScreenPos1;

        //var V = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).inverse;
        //if (SystemInfo.usesReversedZBuffer)
        //{
        //    V.m20 = -V.m20;
        //    V.m21 = -V.m21;
        //    V.m22 = -V.m22;
        //    V.m23 = -V.m23;
        //}
        ////V = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);



        ////var endPos = ManualScreenPointToWorldPoint(new Vector2(vScreenPos.x, vScreenPos.y),
        ////    vScreenPos.z, V.inverse, P.inverse);
        //var endPos = cam.ScreenToWorldPoint(vScreenPos);

        //goShpere.transform.position = endPos;
    }

    Matrix4x4 P;
    Vector3 manualWorldToScreenPoint(Vector3 wp)
    {
        var V = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).inverse;
        if (SystemInfo.usesReversedZBuffer)
        {
            V.m20 = -V.m20;
            V.m21 = -V.m21;
            V.m22 = -V.m22;
            V.m23 = -V.m23;
        }

        // calculate view-projection matrix
        //Matrix4x4 mat = P * cam.worldToCameraMatrix;
        Matrix4x4 mat2 = P * V;

        // multiply world point by VP matrix
        Vector4 temp = mat2 * new Vector4(wp.x, wp.y, wp.z, 1f);

        //冯乐乐
        {
            // 世界坐标 * View矩阵,得到View坐标系坐标。
            var Pview = V * new Vector4(wp.x, wp.y, wp.z, 1f);

            var tanHalfFov = Mathf.Tan(cam.fieldOfView * 0.5f);
            //var nearClipPlaneHeight = 2 * cam.nearClipPlane * tanHalfFov;
            //var farClipPlaneHeight = 2 * cam.farClipPlane * tanHalfFov;
            var whAspect = cam.aspect;

            float z = -Pview.z * (cam.farClipPlane + cam.nearClipPlane) / (cam.farClipPlane - cam.nearClipPlane)
                - 2 * cam.farClipPlane * cam.nearClipPlane / (cam.farClipPlane - cam.nearClipPlane);

            Debug.LogError("z: " + z + " " + (z / -Pview.z));

            //wp.z:0.3 - 1000 -> -0.3,-1;1000,1
            //w值经过了0,与书中描述一致。
        }


        if (temp.w == 0f)
        {
            // point is exactly on camera focus point, screen point is undefined
            // unity handles this by returning 0,0,0
            return Vector3.zero;
        }
        else
        {
            // convert x and y from clip space to window coordinates
            temp.x = (temp.x / temp.w + 1f) * .5f * cam.pixelWidth;
            temp.y = (temp.y / temp.w + 1f) * .5f * cam.pixelHeight;

            //-0.3,1000 => 0.3,1000
            float z = (temp.z + 0.3f) / (1000 + 0.3f) * (1000 - 0.3f) + 0.3f;
            return new Vector3(temp.x, temp.y, z);
        }
    }

第一次,距离是有问题的,对照了冯乐乐的版本,发现原因,是z值经过了0点。
经过区间映射,将-0.31000映射到0.31000,解决问题。

成功代码:

using UnityEngine;

public class MatrixTest : MonoBehaviour
{
    public Camera cam;
    public Transform go;
    private GameObject goShpere;

    private void Awake()
    {
        goShpere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        goShpere.transform.localScale *= 1.2f;

        InitWP2WPMatrix();
    }

    // Update is called once per frame
    void Update()
    {
        var vScreenPos = ManualWorldToScreenPoint(go.transform.position, transform);
        var endPos = ManualScreenToWorldPoint(vScreenPos, transform);

        goShpere.transform.position = endPos;
    }


    public void InitWP2WPMatrix()
    {
        P = cam.projectionMatrix;
        camPixelWidth = cam.pixelWidth;
        camPixelHeight = cam.pixelHeight;
    }

    static Matrix4x4 P;
    static int camPixelWidth;
    static int camPixelHeight;
    public static Vector4 ManualWorldToScreenPoint(Vector4 wp, Transform fakeCamera)
    {
        var V = Matrix4x4.TRS(fakeCamera.position, fakeCamera.rotation, Vector3.one).inverse;
        if (SystemInfo.usesReversedZBuffer)
        {
            V.m20 = -V.m20;
            V.m21 = -V.m21;
            V.m22 = -V.m22;
            V.m23 = -V.m23;
        }

        // calculate view-projection matrix
        //Matrix4x4 mat = P * cam.worldToCameraMatrix;
        Matrix4x4 mat2 = P * V;

        // multiply world point by VP matrix
        Vector4 temp = mat2 * new Vector4(wp.x, wp.y, wp.z, 1f);

        if (temp.w == 0f)
        {
            // point is exactly on camera focus point, screen point is undefined
            // unity handles this by returning 0,0,0
            return Vector3.zero;
        }
        else
        {
            // convert x and y from clip space to window coordinates
            temp.x = (temp.x / temp.w + 1f) * .5f * camPixelWidth;
            temp.y = (temp.y / temp.w + 1f) * .5f * camPixelHeight;

            //-0.3,1000 => 0.3,1000
            float z = (temp.z + 0.3f) / (1000 + 0.3f) * (1000 - 0.3f) + 0.3f;
            return new Vector4(temp.x, temp.y, z, temp.w);
        }
    }

    public static Vector3 ManualScreenToWorldPoint(Vector4 sp, Transform fakeCamera)
    {
        float z = (sp.z - 0.3f) / (1000 - 0.3f) * (1000 + 0.3f) - 0.3f;
        float x = (sp.x / camPixelWidth * 2 - 1) * sp.w;
        float y = (sp.y / camPixelHeight * 2 - 1) * sp.w;
        Vector4 temp = new Vector4(x, y, z, sp.w);

        var V = Matrix4x4.TRS(fakeCamera.position, fakeCamera.rotation, Vector3.one).inverse;
        if (SystemInfo.usesReversedZBuffer)
        {
            V.m20 = -V.m20;
            V.m21 = -V.m21;
            V.m22 = -V.m22;
            V.m23 = -V.m23;
        }

        // calculate view-projection matrix
        Matrix4x4 mat2 = V.inverse * P.inverse;

        // multiply world point by VP matrix
        Vector4 worldPoint = mat2 * temp;

        return worldPoint;
    }

}
上一篇 下一篇

猜你喜欢

热点阅读