在uGUI正交相机中实现旋转透视效果

正常uGUI使用正交相机的话,旋转是没有透视效果的,但如果能实现较简单的透视,

对一些效果表现来说还是不错的;见下图(左为透视效果):

正常思路感觉各种麻烦。

因为uGUI使用unity的x和y方向表示宽高,z方向自然就是纵深,我们可以直接拿z值作为系数进行缩放处理,

达到伪透视的效果(美中不足,细看会有弧度感)。

 

但是,直接对z轴进行缩放会出现扭曲问题:

 

这种情况可以通过顶点细分来解决,一般细分一次即可。顶点越多,UV插值产生的扭曲影响就越小:

 

再扩展一下可支持Text组件:

 

 

最后上代码:

namespace Hont
{
    using UnityEngine;
    using UnityEngine.UI;

    [ExecuteInEditMode]
    public class UiPerspectiveFx : BaseMeshEffect
    {
        [Header("Only Image")] public int subdivision = 2; //有的材质细分2次,有的需3-4次。
        public float perspectiveScale = 1.0f;
        public Transform referencePoint;//z轴参考对象,赋予自身即可
        public bool alwaysRefresh = true;

        private void Update()
        {
            if (alwaysRefresh)
                graphic.SetVerticesDirty();
        }

        private void CalcPerspectiveScale(ref Vector3 point)
        {
            if (referencePoint)
            {
                var vertexWS = transform.TransformPoint(point);
                vertexWS.z -= referencePoint.position.z;
                var vertexZ = vertexWS.z * perspectiveScale;
                point *= 1f + vertexZ;
            }
        }

        public override void ModifyMesh(VertexHelper vh)
        {
            RectTransform rectTransform = transform as RectTransform;

            if (graphic is Image image)
            {
                vh.Clear();

                var sizeDelta = rectTransform.sizeDelta;
                Vector2 begin = -sizeDelta * 0.5f;
                Vector2 cell = sizeDelta / subdivision;
                float uvCell = 1f / subdivision;

                // Get the sprite from the Image component
                Sprite sprite = image.sprite;
                if (sprite == null)
                    return;

                // Calculate the texture rect in normalized coordinates
                Rect textureRect = sprite.textureRect;
                textureRect.x /= sprite.texture.width;
                textureRect.y /= sprite.texture.height;
                textureRect.width /= sprite.texture.width;
                textureRect.height /= sprite.texture.height;

                for (int x = 0; x < subdivision; ++x) //TODO:可进一步做缓存优化
                {
                    for (int y = 0; y < subdivision; ++y)
                    {
                        Vector3 p0 = new Vector3(begin.x + cell.x * x, begin.y + cell.y * y);
                        Vector3 p1 = new Vector3(begin.x + cell.x * x, begin.y + cell.y * (y + 1));
                        Vector3 p2 = new Vector3(begin.x + cell.x * (x + 1), begin.y + cell.y * (y + 1));
                        Vector3 p3 = new Vector3(begin.x + cell.x * (x + 1), begin.y + cell.y * y);

                        Vector2 uv0 = new Vector2(textureRect.x + x * uvCell * textureRect.width, textureRect.y + y * uvCell * textureRect.height);
                        Vector2 uv1 = new Vector2(textureRect.x + x * uvCell * textureRect.width, textureRect.y + (y + 1) * uvCell * textureRect.height);
                        Vector2 uv2 = new Vector2(textureRect.x + (x + 1) * uvCell * textureRect.width, textureRect.y + (y + 1) * uvCell * textureRect.height);
                        Vector2 uv3 = new Vector2(textureRect.x + (x + 1) * uvCell * textureRect.width, textureRect.y + y * uvCell * textureRect.height);

                        CalcPerspectiveScale(ref p0);
                        CalcPerspectiveScale(ref p1);
                        CalcPerspectiveScale(ref p2);
                        CalcPerspectiveScale(ref p3);

                        vh.AddUIVertexQuad(new UIVertex[]
                        {
                            new() {position = p0, color = graphic.color, uv0 = uv0},
                            new() {position = p1, color = graphic.color, uv0 = uv1},
                            new() {position = p2, color = graphic.color, uv0 = uv2},
                            new() {position = p3, color = graphic.color, uv0 = uv3}
                        });
                    }
                }
            }
            else if (graphic is Text)
            {
                for (int i = 0, iMax = vh.currentVertCount; i < iMax; i++)
                {
                    UIVertex vertex = default;
                    vh.PopulateUIVertex(ref vertex, i);
                    CalcPerspectiveScale(ref vertex.position);
                    vh.SetUIVertex(vertex, i);
                }
            }
        }
    }
}

 

挂载到Image/Text组件下即可:

 

posted @ 2022-03-06 16:33  HONT  阅读(1049)  评论(0编辑  收藏  举报