【Unity】3D UI的焦點設置

3D UI

遊戲中經常出現一些斜向放置的UI,這些UI在世界空間中擺放,被透視攝像機所觀察。如下圖中的設置。

擺放一組帶有角度的UI:

 

下圖是旋轉後的UI,因爲使用透視相機的緣故,這裏可以看到明顯的立體效果。

但我們經常會想控制這些UI的透視,使他們的透視中心不再是屏幕的中心。下圖就是將透視中心移動到右上角的效果。

 

實現

上圖效果可以類比爲,一個普通的透視畫面,截取畫面左下角的一塊圖案,而這個圖案就是屏幕顯示的部分。這裏我們可能很快想到通過攝像機的Viewport Rect設置來實現這種效果,但是因爲我們希望在UI中使用這個效果,Viewport Rect會影響到很多Canvas相關的設置和屏幕顯示位置,使事情變得更加複雜。

於是我們想到另一個辦法,改變攝像機的矩陣,正常的相機矩陣如下圖所示:

我們可以改變攝像機矩陣,使其不再是一個對稱的形體;即近裁面中點、遠裁面中點、攝像機位置,三點不再共線。

用以下角度,在正交空間觀察:

攝像機位置相對Canva平面的位置就是透視中心,現在透視中心位於正中央,做如下圖改變,透視中心變爲了右上角,得到了上面那張透視圖:

這裏可以看到,攝像機矩陣變了,從而透視發生了變化。

我們希望輸入屏幕的相對位置,得到一個不會影響Canvas相關設置的結果,但是上圖中可以明顯看到Canvas(相機正前方的白框)已經與顯示內容分離了,其實這裏爲了不影響UI縮放等邏輯,中間加了一層轉換:

Convert便是轉換,它使用四周對齊,尺寸與CanvasPlane一致,通過變換位置使其位於變化後的攝像機矩陣中。

代碼

注意:下面的計算默認攝像機旋轉角度是(0,0,0)。

using UnityEngine;

[ExecuteInEditMode]
public class CameraMatrixSetter : MonoBehaviour
{
    [Header("視覺偏移"), Tooltip("相對屏幕中心的偏移,-1f - 1f的範圍是屏幕部分。"), SerializeField]
    private Vector2 offset;
    public Vector2 Offset
    {
        get
        {
            return offset;
        }

        set
        {
            offset = value;
            Refresh();
        }
    }

    public Canvas canvas;
    public Camera worldCamera;
    public Transform convertTransform;

    private void OnEnable()
    {
        Refresh();
    }

    private void OnDisable()
    {
        worldCamera.ResetProjectionMatrix();
        convertTransform.position = new Vector3(0, 0, canvas.planeDistance) + worldCamera.transform.position;
    }

#if UNITY_EDITOR
    private void OnValidate()
    {
        Refresh();
        UnityEditor.SceneView.RepaintAll();
    }

    private void Update()
    {
        Refresh();
    }
#endif

    public void Refresh()
    {
        if (canvas == null || worldCamera == null)
            return;

        Vector2 canvasSize = ((RectTransform)canvas.transform).sizeDelta * canvas.transform.lossyScale;
        Vector2 canvasOffsetSize = -offset * canvasSize;

        Vector2 nearPlaneOffset = canvasOffsetSize * worldCamera.nearClipPlane / canvas.planeDistance;

        worldCamera.ResetProjectionMatrix();
        FrustumPlanes decomposeProjection = worldCamera.projectionMatrix.decomposeProjection;
        decomposeProjection.left += nearPlaneOffset.x;
        decomposeProjection.right += nearPlaneOffset.x;
        decomposeProjection.top += nearPlaneOffset.y;
        decomposeProjection.bottom += nearPlaneOffset.y;
        Matrix4x4 frustumMatrix4x4 = Matrix4x4.Frustum(decomposeProjection);
        worldCamera.projectionMatrix = frustumMatrix4x4;

        UpdateCanvas(canvasOffsetSize);
    }

    private void UpdateCanvas(Vector2 canvasSize)
    {
        if (convertTransform)
        {
            convertTransform.position = new Vector3(canvasSize.x, canvasSize.y, canvas.planeDistance) + worldCamera.transform.position;
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章