Unity中实现UI的质感和圆角

质感思路有两种:

一种是玻璃质感的做法,抓取UI后面的图像做模糊(build是GrabPass,urp抓图像我有写过在往期文章),这个方式网络上有很多就不写了;

另外一种是使用CubeMap的方式去模拟质感,这种用贴图的方式会更省性能,我这里主要讲的是第二种,其中需要注意的点是给CubeMap采样的时候需要将顶点转换为世界坐标,不然会出现极坐标的情况(上图为极坐标,下图是正常的);

Unity UI质感和圆角

如果你用shaderGraph可能需要用自定义节点去写转换,黑盒似乎也会出现极坐标,具体的你可以自行测试;

关键代码:

复制代码
vert:

output.worldPos = mul(unity_ObjectToWorld,input.vertex);

----------------------------------------------------------

Frag:
float3 viewDir  = normalize(UnityWorldSpaceViewDir(input.worldPos.xyz));
float3 vrDirWS = reflect(-viewDir, input.worldNormal);
float3 var_Cubemap = texCUBElod(_Cubemap,float4(vrDirWS,6));
color.rgb += var_Cubemap;

全部代码如下:

复制代码
Shader "Unlit/RoundedBoxUI"
{
    Properties
    {
        [PerRendererData] _MainTex ("Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)

        _Cubemap ("HDRTex", cube) = "white" {} // 输入HDR单图//YJJ
        //_CubemapMip("_CubemapMip",Range(0,7)) = 6
        _RotationY ("RotationY", Range(0, 360)) = 0

        [HideInInspector] _StencilComp		("Stencil Comparison", Float) = 0
	    [HideInInspector] _Stencil			("Stencil ID", Float) = 0
	    [HideInInspector] _StencilOp		("Stencil Operation", Float) = 0
	    [HideInInspector] _StencilWriteMask	("Stencil Write Mask", Float) = 255
	    [HideInInspector] _StencilReadMask	("Stencil Read Mask", Float) = 255

	    _ColorMask ("Color Mask", Float) = 15
        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
        _BorderWidth ("Border Width", Float) = 0
        [Enum(NoBorder,0,OnlyBorder,1,Both,2)] _BorderColorType ("Border Type", Int) = 0
        [Toggle(IMAGE_SDF)] _UseImageAsSDF ("Use Image as SDF", Float) = 0
        [Enum(Off,0,On,1)]_ZWrite ("ZWrite", Float) = 1.0
    }
    SubShader
    {
        Tags
	    {
		    "Queue"="Transparent"
		    "IgnoreProjector"="True"
		    "RenderType"="Transparent"
	    }

	    Stencil
	    {
		    Ref [_Stencil]
		    Comp [_StencilComp]
		    Pass [_StencilOp]
		    ReadMask [_StencilReadMask]
		    WriteMask [_StencilWriteMask]
	    }

	    Cull Off
        Lighting Off
        ZWrite [_ZWrite]
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha, OneMinusDstAlpha One
        ColorMask [_ColorMask]

        Pass
        {
            Name "Default"
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0
		    #pragma multi_compile __ UNITY_UI_CLIP_RECT
		    #pragma multi_compile __ UNITY_UI_ALPHACLIP
            #pragma multi_compile __ IMAGE_SDF

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"
            #include "Box2DSignedDistance.cginc"

            struct vertexInput
            {
                float4 vertex : POSITION;
                float4 color : COLOR;
                float4 texcoord : TEXCOORD0;
                //--- Custom
                float4 borderRadius : TEXCOORD1;
                float3 normal : NORMAL;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct fragmentInput
            {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float4 texcoord : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
                //--- Custom
                float4 borderRadius : TEXCOORD2;
                float3 worldNormal : TEXCOORD3;
                float3 worldPos : TEXCOORD4;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;
            float _BorderWidth;
            int _BorderColorType;   samplerCUBE _Cubemap; float _RotationY; //float _CubemapMip; 

            float4 RotateAroundYInDegrees (float4 vertex, float degrees)
            {
                float alpha = degrees * 3.14 / 180.0;
                float sina, cosa;
                sincos(alpha, sina, cosa);
                float2x2 m = float2x2(cosa, -sina, sina, cosa);
                return float4(mul(m, vertex.xz), vertex.yw).xzyw;
            }

            float4 RotateAroundXInDegrees (float4 vertex, float degrees)
            {
                float alpha = degrees * 3.14 / 180.0;
                float sina, cosa;
                sincos(alpha, sina, cosa);
            
                // 创建绕X轴的旋转矩阵
                float2x2 m = float2x2(cosa, -sina, sina, cosa);
            
                // 创建完整的旋转矩阵
                float4x4 rotationMatrix = float4x4(
                    1.0, 0.0, 0.0, 0.0,
                    0.0, cosa, -sina, 0.0,
                    0.0, sina, cosa, 0.0,
                    0.0, 0.0, 0.0, 1.0
                );
            
                // 旋转顶点并返回
                return mul(rotationMatrix, vertex);
            }
            
            float4 RotateAroundZInDegrees (float4 vertex, float degrees)
            {
                float alpha = degrees * 3.14 / 180.0;
                float sina, cosa;
                sincos(alpha, sina, cosa);
            
                // 创建绕Z轴的旋转矩阵
                float4x4 rotationMatrix = float4x4(
                    cosa, -sina, 0.0, 0.0,
                    sina, cosa, 0.0, 0.0,
                    0.0, 0.0, 1.0, 0.0,
                    0.0, 0.0, 0.0, 1.0
                );
            
                // 旋转顶点并返回
                return mul(rotationMatrix, vertex);
            }
            

            fragmentInput vert(vertexInput input)
            {
                fragmentInput output;

                UNITY_INITIALIZE_OUTPUT(fragmentInput, output);
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

                output.worldPosition = input.vertex;

                output.worldPos = mul(unity_ObjectToWorld,input.vertex);//YJJ//给CubeMap采样使用

                output.vertex = UnityObjectToClipPos(output.worldPosition);
                output.texcoord = input.texcoord;
                output.color = input.color * _Color;
                output.borderRadius = input.borderRadius;

                output.worldNormal = UnityObjectToWorldNormal(input.normal);

                return output;
            }

            fixed4 frag (fragmentInput input) : SV_Target
            {
                float2 rectSize = input.texcoord.zw;
                float2 uv = input.texcoord.xy * rectSize;
                uv = uv - rectSize * 0.5;

                float dist = sdRoundBox(uv, rectSize * 0.5 - (_BorderWidth).xx, input.borderRadius);
                float2 ddDist = float2(ddx(dist), ddy(dist));
                float ddDistLen = length(ddDist);

                float alpha = saturate(((dist - _BorderWidth) / ddDistLen) + 1.0);
                float borderParam = saturate((dist) / ddDistLen);

                half4 color = half4(0.0, 0.0, 0.0, 0.0);
                
                #ifdef IMAGE_SDF
                    float4 texSample = tex2D(_MainTex, input.texcoord) + _TextureSampleAdd;
                    float c_dist = texSample.x - 0.1;
                    float c_mask = smoothstep(0.00, 0.2, c_dist);

                    color = input.color;
                    color.a *= 1.0 - alpha;
                    color.a *= saturate(c_mask);
                #else
                    color = (tex2D(_MainTex, input.texcoord) + _TextureSampleAdd) * input.color;
                    color.a *= 1.0 - alpha;
                #endif

                //描边YJJ
                //color.a = 1;//挤出描边

                //描边End
                if (_BorderColorType == 1) {
                    color.a *= borderParam;
                }
                //color.rgb *= 1.0 - borderParam;

                //color.a *= c_dist;
                #ifdef UNITY_UI_CLIP_RECT
                    color.a *= UnityGet2DClipping(input.worldPosition.xy, _ClipRect);
                #endif

                #ifdef UNITY_UI_ALPHACLIP
                    clip (color.a - 0.001);
                #endif

                //魔改反射效果
                float3 viewDir  = normalize(UnityWorldSpaceViewDir(input.worldPos.xyz));
                //float3 vDirWS=normalize(_WorldSpaceCameraPos.xyz - input.worldPosition.xyz);
                float3 vrDirWS = reflect(-viewDir, input.worldNormal);

                vrDirWS = RotateAroundZInDegrees(float4(vrDirWS,1),0).xyz;

                float3 var_Cubemap = texCUBElod(_Cubemap,float4(vrDirWS,6));
                color.rgb += var_Cubemap;
                //return float4(var_Cubemap,1);
                return color;
            }
            ENDCG
        }
    }
}

脚本:

复制代码
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class RoundedBoxUIProperties : UIBehaviour, IMeshModifier
{
    private Image _image;
    public Vector4 borderRadius;

    [Range(0,7)]public float CubemapMip = 6;

#if UNITY_EDITOR
    protected override void OnValidate()
    {
        if (_image == null)
        {
            _image = gameObject.GetComponent<Image>();
            if (_image == null) return;
        }
        _image.SetAllDirty();
    }
#endif

    protected override void OnEnable()
    {
        _image = gameObject.GetComponent<Image>();
    }
    protected override void OnDisable()
    {
        _image = null;
    }

    protected override void Start()
    {
        StartCoroutine(DelayVertexGeneration());
    }

    IEnumerator DelayVertexGeneration()
    {
        yield return new WaitForSeconds(0.1f);
        if (_image == null)
        {
            _image = gameObject.GetComponent<Image>();
            if (_image == null) yield break;
        }
        _image.SetAllDirty();
    }

    public void ModifyMesh(Mesh mesh)
    {
    }

    public void ModifyMesh(VertexHelper verts)
    {
        if (_image == null)
        {
            _image = gameObject.GetComponent<Image>();
            if (_image == null) return;
        }

        var rectTransform = (RectTransform)transform;
        var rect = rectTransform.rect;
        var offset = new Vector4(rect.x, rect.y, Mathf.Abs(rect.width), Mathf.Abs(rect.height));
        UIVertex vert = new UIVertex();

        for (int i = 0; i < verts.currentVertCount; i++)
        {
            verts.PopulateUIVertex(ref vert, i);
            var uv0 = vert.uv0;
            uv0.z = offset.z;
            uv0.w = offset.w;
            vert.uv0 = uv0;
            vert.uv1 = borderRadius * 0.5f;
            verts.SetUIVertex(vert, i);
        }
    }

    // [ContextMenu("UI模糊调整")]
    // public void UIMipBlur(){
    //     Shader.SetGlobalFloat("_CubemapMip",CubemapMip);
    //     float Test =  Shader.GetGlobalFloat("_CubemapMip");
    //     Debug.Log("" + Test);
    // }
}

Box2DSignedDistance.cginc

复制代码
float sdRoundBox( in float2 p, in float2 b, in float4 r )
{
	// We choose the radius based on the quadrant we're in
    // We cap the radius based on the minimum of the box half width/height
    r.xy = (p.x>0.0)?r.xy : r.zw;
    r.x = (p.y>0.0)?r.x : r.y;
    r.x = min(2.0f*r.x, min(b.x, b.y));

    float2 q = abs(p)-b+r.x;
    return min(max(q.x,q.y),0.0) + length(max(q,0.0)) - r.x;
}

其中shader部分写了一些矩阵用于调整角度的方法,可以自行删减冗余;

相关推荐
小贺儿开发1 天前
Unity3D 智慧城市管理平台
数据库·人工智能·unity·智慧城市·数据可视化
June bug2 天前
【领域知识】休闲游戏一次发版全流程:Google Play + Apple App Store
unity
星夜泊客2 天前
C# 基础:为什么类可以在静态方法中创建自己的实例?
开发语言·经验分享·笔记·unity·c#·游戏引擎
dzj20212 天前
PointerEnter、PointerExit、PointerDown、PointerUp——鼠标点击物体,则开始旋转,鼠标离开或者松开物体,则停止旋转
unity·pointerdown·pointerup
心前阳光2 天前
Unity 模拟父子关系
android·unity·游戏引擎
在路上看风景2 天前
26. Mipmap
unity
咸鱼永不翻身2 天前
Unity视频资源压缩详解
unity·游戏引擎·音视频
在路上看风景2 天前
4.2 OverDraw
unity
在路上看风景2 天前
1.10 CDN缓存
unity
ellis19703 天前
Unity插件SafeArea Helper适配异形屏详解
unity